-
Notifications
You must be signed in to change notification settings - Fork 466
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
feat(network): implement EIP-7801 Sharded Blocks Subprotocol (etha) #7973
Changes from all commits
1ba9391
042985b
d2beb50
9414d02
dad004a
bee8011
a76ed53
7a4231b
752f5ea
368566f
577f8f4
113360c
72e9991
692aac8
aa7de30
7844078
e497b41
14a67b2
3c5884a
547d7a3
0c82134
a50553b
0fdffff
7e32aa0
fcbaae9
c3d05d5
ee6869f
b2fbf44
b6884ed
cc0a671
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
public class NethermindApi | ||
{ | ||
private void RegisterNetwork() | ||
{ | ||
// ... existing registrations ... | ||
|
||
// Register etha protocol factory | ||
services.AddSingleton<EthaProtocolFactory>(); | ||
services.AddSingleton<IProtocolFactory>(sp => sp.GetRequiredService<EthaProtocolFactory>()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -66,13 +66,23 @@ public class InitializeNetwork : IStep | |
private readonly ILogger _logger; | ||
private readonly INetworkConfig _networkConfig; | ||
protected readonly ISyncConfig _syncConfig; | ||
|
||
public InitializeNetwork(INethermindApi api) | ||
private readonly IProtocolsManager _protocolsManager; | ||
private readonly IProtocolValidator _protocolValidator; | ||
private readonly EthaProtocolFactory _ethaProtocolFactory; | ||
|
||
public InitializeNetwork( | ||
INethermindApi api, | ||
IProtocolsManager protocolsManager, | ||
IProtocolValidator protocolValidator, | ||
EthaProtocolFactory ethaProtocolFactory) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need it, this class shouldn't be changed at all There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can simply undo all changes in this class. What to do instead is described in other comments :) |
||
{ | ||
_api = api; | ||
_logger = _api.LogManager.GetClassLogger(); | ||
_networkConfig = _api.Config<INetworkConfig>(); | ||
_syncConfig = _api.Config<ISyncConfig>(); | ||
_protocolsManager = protocolsManager; | ||
_protocolValidator = protocolValidator; | ||
_ethaProtocolFactory = ethaProtocolFactory; | ||
} | ||
|
||
public async Task Execute(CancellationToken cancellationToken) | ||
|
@@ -209,6 +219,9 @@ await StartDiscovery().ContinueWith(initDiscoveryTask => | |
ThisNodeInfo.AddInfo("Client id :", ProductInfo.ClientId); | ||
ThisNodeInfo.AddInfo("This node :", $"{_api.Enode.Info}"); | ||
ThisNodeInfo.AddInfo("Node address :", $"{_api.Enode.Address} (do not use as an account)"); | ||
|
||
// Register etha protocol | ||
_protocolsManager.AddProtocol(_ethaProtocolFactory); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for it |
||
} | ||
|
||
private Task StartDiscovery() | ||
|
@@ -404,6 +417,10 @@ private async Task InitPeer() | |
{ | ||
_api.ProtocolsManager!.AddSupportedCapability(new Capability(Protocol.Snap, 1)); | ||
} | ||
|
||
// Add etha protocol capability | ||
_api.ProtocolsManager!.AddSupportedCapability(new Capability(Protocol.Etha, 1)); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is unconditional, so should be added to default capabilities. So remove from here, add in |
||
if (!_api.WorldStateManager!.SupportHashLookup) | ||
{ | ||
_api.ProtocolsManager!.RemoveSupportedCapability(new Capability(Protocol.NodeData, 1)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
using Nethermind.Blockchain; | ||
using Nethermind.Logging; | ||
using Nethermind.Stats; | ||
using NSubstitute; | ||
using NUnit.Framework; | ||
|
||
namespace Nethermind.Network.Test.P2P.Subprotocols.Etha | ||
{ | ||
[TestFixture] | ||
public class EthaProtocolFactoryTests | ||
{ | ||
private IBlockTree _blockTree; | ||
private IMessageSerializationService _serializationService; | ||
private INodeStatsManager _nodeStatsManager; | ||
private ILogManager _logManager; | ||
private EthaProtocolFactory _factory; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
_blockTree = Substitute.For<IBlockTree>(); | ||
_serializationService = Substitute.For<IMessageSerializationService>(); | ||
_nodeStatsManager = Substitute.For<INodeStatsManager>(); | ||
_logManager = LimboLogs.Instance; | ||
|
||
_factory = new EthaProtocolFactory( | ||
_blockTree, | ||
_serializationService, | ||
_nodeStatsManager, | ||
_logManager); | ||
} | ||
|
||
[Test] | ||
public void Creates_protocol_with_correct_name() | ||
{ | ||
Assert.That(_factory.Name, Is.EqualTo("etha")); | ||
} | ||
|
||
[Test] | ||
public void Creates_protocol_instance() | ||
{ | ||
var session = Substitute.For<ISession>(); | ||
var protocol = _factory.Create(session); | ||
|
||
Assert.That(protocol, Is.Not.Null); | ||
Assert.That(protocol.Name, Is.EqualTo("etha")); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
using FluentAssertions; | ||
using Nethermind.Blockchain; | ||
using Nethermind.Core; | ||
using Nethermind.Core.Test.Builders; | ||
using Nethermind.Logging; | ||
using Nethermind.Network.P2P.Subprotocols.Etha.Messages; | ||
using Nethermind.Stats; | ||
using NSubstitute; | ||
using NUnit.Framework; | ||
|
||
namespace Nethermind.Network.Test.P2P.Subprotocols.Etha | ||
{ | ||
[TestFixture] | ||
public class EthaProtocolIntegrationTests | ||
{ | ||
private IBlockTree _blockTree; | ||
private IMessageSerializationService _serializationService; | ||
private INodeStatsManager _statsManager; | ||
private ILogManager _logManager; | ||
private EthaProtocolHandler _handler; | ||
private EthaProtocolFactory _factory; | ||
private ISession _session; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
_blockTree = Substitute.For<IBlockTree>(); | ||
_serializationService = Substitute.For<IMessageSerializationService>(); | ||
_statsManager = Substitute.For<INodeStatsManager>(); | ||
_logManager = LimboLogs.Instance; | ||
_session = Substitute.For<ISession>(); | ||
|
||
_factory = new EthaProtocolFactory( | ||
_blockTree, | ||
_serializationService, | ||
_statsManager, | ||
_logManager); | ||
} | ||
|
||
[Test] | ||
public void Protocol_properly_initialized() | ||
{ | ||
Protocol protocol = _factory.Create(_session); | ||
protocol.Should().NotBeNull(); | ||
protocol.Name.Should().Be("etha"); | ||
protocol.Version.Should().Be(1); | ||
protocol.MessageIdSpaceSize.Should().Be(3); | ||
} | ||
|
||
[Test] | ||
public void Can_handle_full_protocol_flow() | ||
{ | ||
// Arrange | ||
Block block = Build.A.Block.WithNumber(1).TestObject; | ||
_blockTree.FindBlock(block.Hash).Returns(block); | ||
|
||
Protocol protocol = _factory.Create(_session); | ||
var handler = protocol.MessageHandlers[0] as EthaProtocolHandler; | ||
handler.Should().NotBeNull(); | ||
|
||
// Act - request blocks | ||
var getMessage = new GetShardedBlocksMessage(new[] { block.Hash }); | ||
handler!.HandleMessage(new Packet(EthaMessageCode.GetShardedBlocks, _serializationService.Serialize(getMessage))); | ||
|
||
// Assert - verify response | ||
_serializationService.Received().Serialize(Arg.Is<ShardedBlocksMessage>(m => | ||
m.Blocks.Length == 1 && m.Blocks[0] == block)); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
using FluentAssertions; | ||
using Nethermind.Blockchain; | ||
using Nethermind.Core; | ||
using Nethermind.Core.Crypto; | ||
using Nethermind.Core.Test.Builders; | ||
using Nethermind.Logging; | ||
using Nethermind.Network.P2P.Subprotocols.Etha.Messages; | ||
using Nethermind.Stats; | ||
using NSubstitute; | ||
using NUnit.Framework; | ||
|
||
namespace Nethermind.Network.Test.P2P.Subprotocols.Etha | ||
{ | ||
[TestFixture] | ||
public class EthaProtocolHandlerTests | ||
{ | ||
private IBlockTree _blockTree; | ||
private IMessageSerializationService _serializationService; | ||
private INodeStatsManager _statsManager; | ||
private ILogManager _logManager; | ||
private EthaProtocolHandler _handler; | ||
|
||
[SetUp] | ||
public void Setup() | ||
{ | ||
_blockTree = Substitute.For<IBlockTree>(); | ||
_serializationService = Substitute.For<IMessageSerializationService>(); | ||
_statsManager = Substitute.For<INodeStatsManager>(); | ||
_logManager = LimboLogs.Instance; | ||
|
||
_handler = new EthaProtocolHandler( | ||
_blockTree, | ||
_serializationService, | ||
_statsManager, | ||
_logManager); | ||
} | ||
|
||
[Test] | ||
public void When_GetShardedBlocks_Received_Then_Returns_Requested_Blocks() | ||
{ | ||
// Arrange | ||
Block block = Build.A.Block.WithNumber(1).TestObject; | ||
_blockTree.FindBlock(block.Hash).Returns(block); | ||
|
||
var message = new GetShardedBlocksMessage(new[] { block.Hash }); | ||
|
||
// Act | ||
_handler.HandleMessage(new Packet(EthaMessageCode.GetShardedBlocks, _serializationService.Serialize(message))); | ||
|
||
// Assert | ||
_serializationService.Received().Serialize(Arg.Is<ShardedBlocksMessage>(m => | ||
m.Blocks.Length == 1 && m.Blocks[0] == block)); | ||
} | ||
|
||
[Test] | ||
public void When_ShardedBlocks_Received_Then_Suggests_New_Blocks() | ||
{ | ||
// Arrange | ||
Block block = Build.A.Block.WithNumber(1).TestObject; | ||
_blockTree.IsKnown(block.Hash).Returns(false); | ||
|
||
var message = new ShardedBlocksMessage(new[] { block }); | ||
|
||
// Act | ||
_handler.HandleMessage(new Packet(EthaMessageCode.ShardedBlocks, _serializationService.Serialize(message))); | ||
|
||
// Assert | ||
_blockTree.Received().SuggestBlock(block); | ||
} | ||
|
||
[Test] | ||
public void When_NewShardedBlock_Is_Unknown_Then_Suggests_Block() | ||
{ | ||
// Arrange | ||
Block block = Build.A.Block.WithNumber(1).TestObject; | ||
_blockTree.IsKnown(block.Hash).Returns(false); | ||
|
||
var message = new NewShardedBlockMessage(block); | ||
|
||
// Act | ||
_handler.HandleMessage(new Packet(EthaMessageCode.NewShardedBlock, _serializationService.Serialize(message))); | ||
|
||
// Assert | ||
_blockTree.Received().SuggestBlock(block); | ||
} | ||
|
||
[Test] | ||
public void When_NewShardedBlock_Is_Known_Then_Ignores_Block() | ||
{ | ||
// Arrange | ||
Block block = Build.A.Block.WithNumber(1).TestObject; | ||
_blockTree.IsKnown(block.Hash).Returns(true); | ||
|
||
var message = new NewShardedBlockMessage(block); | ||
|
||
// Act | ||
_handler.HandleMessage(new Packet(EthaMessageCode.NewShardedBlock, _serializationService.Serialize(message))); | ||
|
||
// Assert | ||
_blockTree.DidNotReceive().SuggestBlock(block); | ||
} | ||
|
||
[Test] | ||
public void When_Unknown_Message_Received_Then_Logs_Error() | ||
{ | ||
// Arrange | ||
var unknownMessageType = 99; | ||
var packet = new Packet(unknownMessageType, new byte[] { 1, 2, 3 }); | ||
|
||
// Act | ||
_handler.HandleMessage(packet); | ||
|
||
// Assert - verify that error was logged | ||
_logManager.Received().GetClassLogger(); | ||
// Note: in a real test we would verify the error was logged, | ||
// but since we're using LimboLogs this is not required | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
using FluentAssertions; | ||
using Nethermind.Core; | ||
using Nethermind.Core.Test.Builders; | ||
using Nethermind.Network.P2P.Subprotocols.Etha.Messages; | ||
using Nethermind.Serialization.Rlp; | ||
using NUnit.Framework; | ||
|
||
namespace Nethermind.Network.Test.P2P.Subprotocols.Etha.Messages | ||
{ | ||
[TestFixture] | ||
public class EthaMessageSerializerTests | ||
{ | ||
[Test] | ||
public void When_GetShardedBlocksMessage_Then_Can_Serialize_And_Deserialize() | ||
{ | ||
var serializer = new GetShardedBlocksMessageSerializer(); | ||
var message = new GetShardedBlocksMessage(new[] { TestItem.KeccakA, TestItem.KeccakB }); | ||
|
||
RlpStream rlpStream = new RlpStream(); | ||
serializer.Serialize(rlpStream, message); | ||
|
||
var deserialized = serializer.Deserialize(new RlpStream(rlpStream.Data)); | ||
deserialized.BlockHashes.Should().BeEquivalentTo(message.BlockHashes); | ||
} | ||
|
||
[Test] | ||
public void When_ShardedBlocksMessage_Then_Can_Serialize_And_Deserialize() | ||
{ | ||
var serializer = new ShardedBlocksMessageSerializer(); | ||
var message = new ShardedBlocksMessage(new[] { Build.A.Block.TestObject }); | ||
|
||
RlpStream rlpStream = new RlpStream(); | ||
serializer.Serialize(rlpStream, message); | ||
|
||
var deserialized = serializer.Deserialize(new RlpStream(rlpStream.Data)); | ||
deserialized.Blocks.Length.Should().Be(message.Blocks.Length); | ||
deserialized.Blocks[0].Hash.Should().Be(message.Blocks[0].Hash); | ||
} | ||
|
||
[Test] | ||
public void When_NewShardedBlockMessage_Then_Can_Serialize_And_Deserialize() | ||
{ | ||
var serializer = new NewShardedBlockMessageSerializer(); | ||
var block = Build.A.Block.TestObject; | ||
var message = new NewShardedBlockMessage(block); | ||
|
||
RlpStream rlpStream = new RlpStream(); | ||
serializer.Serialize(rlpStream, message); | ||
|
||
var deserialized = serializer.Deserialize(new RlpStream(rlpStream.Data)); | ||
deserialized.Block.Hash.Should().Be(message.Block.Hash); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Nethermind.Network.P2P.Subprotocols.Etha | ||
{ | ||
public static class EthaMessageCode | ||
{ | ||
public const int GetShardedBlocks = 0x00; | ||
public const int ShardedBlocks = 0x01; | ||
public const int NewShardedBlock = 0x02; | ||
} | ||
} |
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 don't need this class, we don't need
EthaProtocolFactory
at all