Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimism CL P2P #7297

Merged
merged 39 commits into from
Jan 2, 2025
Merged

Optimism CL P2P #7297

merged 39 commits into from
Jan 2, 2025

Conversation

deffrian
Copy link
Contributor

@deffrian deffrian commented Jul 30, 2024

Fixes Closes Resolves #
Implementation of p2p part op Optimism CL client. We have few todos. Discovery is blocked by discv5 implementation. This is why we have pre-set peers. And GetBlockByNumber protocol. This is also kinda blocked by libp2p. I have a working implementation but it's super ugly. So better to merge it later.

Changes

  • Adds op cl client

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Documentation

Requires documentation update

  • Yes
  • No

Requires explanation in Release Notes

  • Yes
  • No

@deffrian deffrian changed the title [WIP]Optimism CL [WIP]Optimism CL P2P Dec 18, 2024
@emlautarom1 emlautarom1 self-requested a review December 18, 2024 13:57

public class PayloadDecoder : IPayloadDecoder
{
public static readonly PayloadDecoder Instance = new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going the singleton route then we should make the constructor private

}

int offset = 0;
payload.ParentBeaconBlockRoot = new(data[offset..(offset + 32)]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can leverage the ReadOnlySpan<byte>.TakeAndMove() extension method to avoid the bookkeeping of offset

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have constructor(ReadOnlySpan) for some types. Imo we can leave it as is for now, add span constructors later and adjust this function. wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, lets proceed with the current approach for now. Just keep TODO to remind ourselves about it later.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TakeAndMove is our extension method, you can create one for Span<> too. But ReadOnlySpan should probably work with all below types - if it doesn't we should consider adding it.


namespace Nethermind.Optimism.CL;

public class PayloadDecoder : IPayloadDecoder
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have some tests for both DecodePayload and DecodeTransactions, in particular for the later one since the implementation does not look straightforward

_logger = logManager.GetClassLogger();
_chainSpecEngineParameters = engineParameters;

_p2p = new OptimismCLP2P(specProvider.ChainId, engineParameters.Nodes, config, _chainSpecEngineParameters.SequencerP2PAddress, timestamper, logManager, engineRpcModule);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We never dispose of _p2p. Either we make OptimismCL an instance of IDisposable and call _p2p.Dispose, or we make Start construct an instance and dispose of it by using it.


_blocksV2TopicId = $"/optimism/{chainId}/2/blocks";

_serviceProvider = new ServiceCollection()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need a ServiceProvider? Could we store these dependencies as private readonly fields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed for libp2p DI.

src/Nethermind/Nethermind.Optimism/CL/P2P/OptimismCLP2P.cs Outdated Show resolved Hide resolved
}
}

private bool TryValidateAndDecodePayload(byte[] msg, [MaybeNullWhen(false)] out ExecutionPayloadV3 payload)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we return also an error message or we don't care about it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't care. Just reject it

return false;
}

byte[] decompressed = new byte[length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stackalloc?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

length might be pretty big. Not sure if it stack overflow safe

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayPoolList then to use pooled memory?

public interface IP2PBlockValidator
{
ValidityStatus Validate(ExecutionPayloadV3 payload, P2PTopic topic);
ValidityStatus ValidateSignature(byte[] payloadData, byte[] signature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If payloadData and signature are only read then we could use ReadOnlySpan<byte> for both args

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why but SpanSecP256k1.RecoverKeyFromCompact needs Span, not ReadOnlySpan

@deffrian deffrian changed the title [WIP]Optimism CL P2P Optimism CL P2P Dec 20, 2024
@deffrian deffrian marked this pull request as ready for review December 20, 2024 10:30
@deffrian deffrian requested a review from rubo as a code owner December 20, 2024 10:30
Comment on lines +287 to +298
StepDependencyException.ThrowIfNull(_api.EthereumEcdsa);

ICLConfig clConfig = _api.Config<ICLConfig>();
if (clConfig.Enabled)
{
CLChainSpecEngineParameters chainSpecEngineParameters = _api.ChainSpec.EngineChainSpecParametersProvider
.GetChainSpecParameters<CLChainSpecEngineParameters>();
_cl = new OptimismCL(_api.SpecProvider, chainSpecEngineParameters, clConfig, _api.EthereumJsonSerializer,
_api.EthereumEcdsa, _api.Timestamper, _api!.LogManager, opEngine);
_cl.Start();
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not in InitNetworkProtocol?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is CL startup code. In the future it will launch all CL components

return false;
}

byte[] decompressed = new byte[length];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ArrayPoolList then to use pooled memory?

Comment on lines 72 to 97
private async void OnMessage(byte[] msg)
{
await _semaphore.WaitAsync();
try
{
if (TryValidateAndDecodePayload(msg, out var payload))
{
if (_logger.IsTrace) _logger.Trace($"Received payload prom p2p: {payload}");

if (_headPayloadNumber >= (ulong)payload.BlockNumber)
{
// Old payload. skip
return;
}

if (await SendNewPayloadToEL(payload) && await SendForkChoiceUpdatedToEL(payload.BlockHash))
{
_headPayloadNumber = (ulong)payload.BlockNumber;
}
}
}
finally
{
_semaphore.Release();
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially: Maybe channel better than lock?

Comment on lines 111 to 112
byte[] signature = decompressed[0..65];
byte[] payloadData = decompressed[65..];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use spans instead of allocating arrays?

Copy link
Contributor Author

@deffrian deffrian Dec 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reply for the comment bellow: Payloads are ssz encoded, so data offsets are encoded and they are relative to the beginning. We potentially can calculate total prefix offset and subtract it from encoded offsets, but I don't think it's a good idea.

}

int offset = 0;
payload.ParentBeaconBlockRoot = new(data[offset..(offset + 32)]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TakeAndMove is our extension method, you can create one for Span<> too. But ReadOnlySpan should probably work with all below types - if it doesn't we should consider adding it.

Copy link
Contributor

@emlautarom1 emlautarom1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM as an initial PR. Some small comments though.

offset += 256;
payload.PrevRandao = new(data[offset..(offset + 32)]);
offset += 32;
payload.BlockNumber = (long)BitConverter.ToUInt64(data[offset..(offset + 8)]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this one but I got suggested some time ago that we should use BinaryPrimitives rather than BitConverter (performance?)

// domain(all zeros) + chain id + payload hash
Span<byte> sequencerSignedData = stackalloc byte[32 + 32 + 32];

// Array.Copy(_chainId, 0, new byte[10], 32, 32);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented out code

_ = _router.RunAsync(_localPeer, new Settings
{
DefaultSignaturePolicy = Settings.SignaturePolicy.StrictNoSign,
GetMessageId = (message => CalculateMessageId(message))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
GetMessageId = (message => CalculateMessageId(message))
GetMessageId = CalculateMessageId

Comment on lines 95 to 98
catch (Exception e)
{
if (_logger.IsError) _logger.Error("Unhandled exception in Optimism CL P2P:", e);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unhandled exceptions in async void methods crash the process


private async Task MainLoop()
{
while (true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe await foreach (ExecutionPayloadV3 payload in reader.ReadAllAsync()) instead?

Will we ever close the channel, shouldn't we close it when app close (we have a CancelationToken available for that)?

@deffrian deffrian merged commit 6bb6835 into master Jan 2, 2025
79 checks passed
@deffrian deffrian deleted the feature/op-cl branch January 2, 2025 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants