-
Notifications
You must be signed in to change notification settings - Fork 463
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
Optimism CL P2P #7297
Conversation
b83d57f
to
f7266d0
Compare
ca5fd35
to
f7266d0
Compare
# Conflicts: # src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs
# Conflicts: # src/Nethermind/Nethermind.Runner/configs/op-sepolia.json
# Conflicts: # src/Nethermind/Nethermind.Runner/configs/op-sepolia.json
|
||
public class PayloadDecoder : IPayloadDecoder | ||
{ | ||
public static readonly PayloadDecoder Instance = new(); |
There was a problem hiding this comment.
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)]); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
} | ||
} | ||
|
||
private bool TryValidateAndDecodePayload(byte[] msg, [MaybeNullWhen(false)] out ExecutionPayloadV3 payload) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stackalloc
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
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(); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not in InitNetworkProtocol
?
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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?
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(); | ||
} | ||
} |
There was a problem hiding this comment.
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?
byte[] signature = decompressed[0..65]; | ||
byte[] payloadData = decompressed[65..]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)]); |
There was a problem hiding this comment.
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.
There was a problem hiding this 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)]); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetMessageId = (message => CalculateMessageId(message)) | |
GetMessageId = CalculateMessageId |
catch (Exception e) | ||
{ | ||
if (_logger.IsError) _logger.Error("Unhandled exception in Optimism CL P2P:", e); | ||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)?
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
Types of changes
What types of changes does your code introduce?
Testing
Requires testing
If yes, did you write tests?
Documentation
Requires documentation update
Requires explanation in Release Notes