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

feat(network): implement EIP-7801 Sharded Blocks Subprotocol (etha) #7973

Open
wants to merge 23 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1ba9391
feat(network): implement EthaProtocol for EIP-7801
crStiv Dec 25, 2024
042985b
feat(network): add EthaMessageCode for EIP-7801
crStiv Dec 25, 2024
d2beb50
feat(network): add GetShardedBlocksMessage for etha protocol
crStiv Dec 25, 2024
9414d02
feat(network): add ShardedBlocksMessage for etha protocol
crStiv Dec 25, 2024
dad004a
feat(network): add NewShardedBlockMessage for etha protocol
crStiv Dec 25, 2024
bee8011
feat(network): add EthaProtocolHandler
crStiv Dec 25, 2024
a76ed53
feat(network): implement etha protocol message handlers
crStiv Dec 25, 2024
7a4231b
test(network): add tests for EthaProtocolHandler
crStiv Dec 25, 2024
752f5ea
test(network): extend EthaProtocolHandler tests
crStiv Dec 25, 2024
368566f
feat(network): add EthaProtocolFactory and register protocol
crStiv Dec 25, 2024
577f8f4
Update InitializeNetwork.cs
crStiv Dec 25, 2024
113360c
Create NethermindApi.cs
crStiv Dec 25, 2024
72e9991
Update InitializeNetwork.cs
crStiv Dec 25, 2024
692aac8
Create Protocol.cs
crStiv Dec 25, 2024
aa7de30
Update NethermindApi.cs
crStiv Dec 25, 2024
7844078
Create EthaProtocolIntegrationTests.cs
crStiv Dec 25, 2024
e497b41
Create EthaProtocolFactoryTests.cs
crStiv Dec 25, 2024
14a67b2
Create EthaMessageSerializer.cs
crStiv Dec 25, 2024
3c5884a
Update EthaProtocolProtocolHandlerTests.cs
crStiv Dec 25, 2024
547d7a3
Update EthaProtocolHandler.cs
crStiv Dec 25, 2024
0c82134
Create EthaMessageSerializerTests.cs
crStiv Dec 25, 2024
a50553b
Update EthaMessageSerializerTests.cs
crStiv Dec 25, 2024
0fdffff
Update EthaProtocolProtocolHandlerTests.cs
crStiv Dec 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/Nethermind/Nethermind.Init/NethermindApi.cs
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>());
}
}
21 changes: 19 additions & 2 deletions src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
_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)
Expand Down Expand Up @@ -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);
}

private Task StartDiscovery()
Expand Down Expand Up @@ -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));

if (!_api.WorldStateManager!.SupportHashLookup)
{
_api.ProtocolsManager!.RemoveSupportedCapability(new Capability(Protocol.NodeData, 1));
Expand Down
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 Handle_GetShardedBlocks_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 Handle_ShardedBlocks_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 Handle_NewShardedBlock_suggests_block_if_unknown()
{
// 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 Handle_NewShardedBlock_ignores_known_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 Handle_unknown_message_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: в реальном тесте мы бы проверили логирование ошибки,
// но так как мы используем LimboLogs, это не требуется
}
}
}
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 Can_serialize_and_deserialize_GetShardedBlocksMessage()
{
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 Can_serialize_and_deserialize_ShardedBlocksMessage()
{
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 Can_serialize_and_deserialize_NewShardedBlockMessage()
{
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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Nethermind.Core.Crypto;
using Nethermind.Network.P2P;

namespace Nethermind.Network.P2P.Subprotocols.Etha
{
public class EthaProtocol : Protocol
{
public override string Name => "etha";
public override byte Version => 1;
public override int MessageIdSpaceSize => 3; // Number of messages in protocol

public EthaProtocol() : base(nameof(EthaProtocol))
{
// Protocol initialization
}
}
}
Loading