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

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 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
7e32aa0
Update EthaProtocolProtocolHandlerTests.cs
crStiv Jan 6, 2025
fcbaae9
Update EthaMessageSerializerTests.cs
crStiv Jan 6, 2025
c3d05d5
Update GetShardedBlocksMessage.cs
crStiv Jan 6, 2025
ee6869f
Update ShardedBlocksMessage.cs
crStiv Jan 6, 2025
b2fbf44
Update NewShardedBlockMessage.cs
crStiv Jan 6, 2025
b6884ed
Update EthaProtocolFactory.cs
crStiv Jan 6, 2025
cc0a671
Update EthaProtocol.cs
crStiv Jan 6, 2025
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>());
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 don't need this class, we don't need EthaProtocolFactory at all

}
}
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)
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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)
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for it

}

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));

Copy link
Contributor

Choose a reason for hiding this comment

The 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 ProtocolsManager.DefaultCapabilities, adjust tests

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 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;
}
}
Loading