From 6ecb769683d0aa402a8fe07f339b8c91fc073bd6 Mon Sep 17 00:00:00 2001 From: Nikita Mescheryakov Date: Thu, 15 Dec 2022 17:48:43 +0300 Subject: [PATCH] Add support for eth68 (#4866) * eth68 implementation * test * refactor serializer * change typesSize back * Tests and enable capability * serializer tests * Fix whitespaces * fix whitespaces * Refactor * Fix serialization & peer drop * RequestTransactions68 * Metrics & logs * Reduce paket max size * Calculate length properly * fix tests & length * huge transaction test * fix spaces * Make size calculated once per transaction * Refactor & optimizations * Fix hive tests * Copyright fix * ubuntu 20.04 workflow * Revert "ubuntu 20.04 workflow" This reverts commit 77701f67067313dc67b74747882de639ed6a5f74. * Revert "Revert "ubuntu 20.04 workflow"" This reverts commit 648e156df182ed317e661cad92f1b906d8bcc61d. * Remove metrics & for loop * Remove test & RequestTransactionsEth68 * EncodeList implementation * Fix test * Revert renaming * Rename write * Remove Request68 * Fix run-nethermind-tests.yml * Fix run-nethermind-tests.yml Co-authored-by: Marcin Sobczak --- .../Data/UserOperationDecoder.cs | 2 +- src/Nethermind/Nethermind.Core/Transaction.cs | 20 +- .../Nethermind.Crypto/KeccakRlpStream.cs | 9 +- .../Steps/InitializeNetwork.cs | 3 +- .../NodeRecordSigner.cs | 2 +- .../Builders/SerializationBuilder.cs | 6 + .../Eth/V68/Eth68ProtocolHandlerTests.cs | 189 ++++++++++++++++++ ...TransactionHashesMessageSerializerTests.cs | 61 ++++++ src/Nethermind/Nethermind.Network/Metrics.cs | 6 + .../Nethermind.Network/P2P/NettyRlpStream.cs | 15 ++ .../Eth/V65/IPooledTxsRequestor.cs | 2 +- .../Eth/V65/PooledTxsRequestor.cs | 4 +- .../Subprotocols/Eth/V68/Eth68MessageCode.cs | 11 + .../Eth/V68/Eth68ProtocolHandler.cs | 132 ++++++++++++ .../NewPooledTransactionHashesMessage68.cs | 30 +++ ...ledTransactionHashesMessageSerializer68.cs | 60 ++++++ .../Messages/AccountRangeMessageSerializer.cs | 2 +- .../Messages/ByteCodesMessageSerializer.cs | 2 +- .../Nethermind.Network/ProtocolsManager.cs | 2 + .../Nethermind.Serialization.Rlp/Rlp.cs | 24 ++- .../Nethermind.Serialization.Rlp/RlpStream.cs | 51 ++++- .../RlpTxDecoder.cs | 2 - .../Nethermind.Serialization.Rlp/TxDecoder.cs | 10 +- .../Nethermind.Trie/TrieNode.Decoder.cs | 2 +- 24 files changed, 628 insertions(+), 19 deletions(-) create mode 100644 src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs create mode 100644 src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/NewPooledTransactionHashesMessageSerializerTests.cs create mode 100644 src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68MessageCode.cs create mode 100644 src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs create mode 100644 src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs create mode 100644 src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs diff --git a/src/Nethermind/Nethermind.AccountAbstraction/Data/UserOperationDecoder.cs b/src/Nethermind/Nethermind.AccountAbstraction/Data/UserOperationDecoder.cs index 75835510594..27c518ab0ca 100644 --- a/src/Nethermind/Nethermind.AccountAbstraction/Data/UserOperationDecoder.cs +++ b/src/Nethermind/Nethermind.AccountAbstraction/Data/UserOperationDecoder.cs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using Nethermind.AccountAbstraction.Network; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index a54d508764f..01ab04f95b0 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -65,6 +65,15 @@ public class Transaction /// Used for sorting in edge cases. public ulong PoolIndex { get; set; } + private int? _size = null; + /// + /// Encoded transaction length + /// + public int GetLength(ITransactionSizeCalculator sizeCalculator) + { + return _size ??= sizeCalculator.GetLength(this); + } + public string ToShortString() { string gasPriceString = @@ -114,7 +123,16 @@ public string ToString(string indent) public class GeneratedTransaction : Transaction { } /// - /// System transaction that is to be executed by the node without including in the block. + /// System transaction that is to be executed by the node without including in the block. /// public class SystemTransaction : Transaction { } + + /// + /// Used inside Transaction::GetSize to calculate encoded transaction size + /// + /// Created because of cyclic dependencies between Core and Rlp modules + public interface ITransactionSizeCalculator + { + int GetLength(Transaction tx); + } } diff --git a/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs b/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs index c73578355bb..d047e941571 100644 --- a/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs +++ b/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs @@ -1,7 +1,9 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; @@ -28,6 +30,11 @@ public override void Write(Span bytesToWrite) _keccakHash.Update(bytesToWrite, 0, bytesToWrite.Length); } + public override void Write(IReadOnlyList bytesToWrite) + { + _keccakHash.Update(bytesToWrite.ToArray(), 0, bytesToWrite.Count); + } + public override void WriteByte(byte byteToWrite) { _keccakHash.Update(MemoryMarshal.CreateSpan(ref byteToWrite, 1), 0, 1); diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs index 12525740b49..f795476e697 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeNetwork.cs @@ -207,8 +207,9 @@ await InitPeer().ContinueWith(initPeerTask => // we can't add eth67 capability as default, because it needs snap protocol for syncing (GetNodeData is // no longer available). Eth67 should be added if snap is enabled OR sync is finished _api.ProtocolsManager!.AddSupportedCapability(new Capability(Protocol.Eth, 67)); + _api.ProtocolsManager!.AddSupportedCapability(new Capability(Protocol.Eth, 68)); } - else if (_logger.IsDebug) _logger.Debug("Skipped enabling eth67 capability"); + else if (_logger.IsDebug) _logger.Debug("Skipped enabling eth67 & eth68 capabilities"); if (_syncConfig.SnapSync && !stateSyncFinished) { diff --git a/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs b/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs index aa260ec1762..2412f3c2a75 100644 --- a/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs +++ b/src/Nethermind/Nethermind.Network.Enr/NodeRecordSigner.cs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using System.Net; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Network.Test/Builders/SerializationBuilder.cs b/src/Nethermind/Nethermind.Network.Test/Builders/SerializationBuilder.cs index 62eaf31c8f1..a018e992a92 100644 --- a/src/Nethermind/Nethermind.Network.Test/Builders/SerializationBuilder.cs +++ b/src/Nethermind/Nethermind.Network.Test/Builders/SerializationBuilder.cs @@ -87,6 +87,12 @@ public SerializationBuilder WithEth66() .With(new Network.P2P.Subprotocols.Eth.V66.Messages.ReceiptsMessageSerializer(new ReceiptsMessageSerializer(MainnetSpecProvider.Instance))); } + public SerializationBuilder WithEth68() + { + return WithEth66() + .With(new Network.P2P.Subprotocols.Eth.V68.Messages.NewPooledTransactionHashesMessageSerializer()); + } + public SerializationBuilder WithDiscovery(PrivateKey privateKey) { Ecdsa ecdsa = new(); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs new file mode 100644 index 00000000000..d275c50678b --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Net; +using DotNetty.Buffers; +using FluentAssertions; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Core.Timers; +using Nethermind.Logging; +using Nethermind.Network.P2P; +using Nethermind.Network.P2P.Subprotocols; +using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V65; +using Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages; +using Nethermind.Network.P2P.Subprotocols.Eth.V68; +using Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages; +using Nethermind.Network.Rlpx; +using Nethermind.Network.Test.Builders; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats; +using Nethermind.Stats.Model; +using Nethermind.Synchronization; +using Nethermind.TxPool; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V68; + +public class Eth68ProtocolHandlerTests +{ + private ISession _session; + private IMessageSerializationService _svc; + private ISyncServer _syncManager; + private ITxPool _transactionPool; + private IPooledTxsRequestor _pooledTxsRequestor; + private IGossipPolicy _gossipPolicy; + private ISpecProvider _specProvider; + private Block _genesisBlock; + private Eth68ProtocolHandler _handler; + + [SetUp] + public void Setup() + { + _svc = Build.A.SerializationService().WithEth68().TestObject; + + NetworkDiagTracer.IsEnabled = true; + + _session = Substitute.For(); + Node node = new(TestItem.PublicKeyA, new IPEndPoint(IPAddress.Broadcast, 30303)); + _session.Node.Returns(node); + _syncManager = Substitute.For(); + _transactionPool = Substitute.For(); + _pooledTxsRequestor = Substitute.For(); + _specProvider = Substitute.For(); + _gossipPolicy = Substitute.For(); + _genesisBlock = Build.A.Block.Genesis.TestObject; + _syncManager.Head.Returns(_genesisBlock.Header); + _syncManager.Genesis.Returns(_genesisBlock.Header); + ITimerFactory timerFactory = Substitute.For(); + _handler = new Eth68ProtocolHandler( + _session, + _svc, + new NodeStatsManager(timerFactory, LimboLogs.Instance), + _syncManager, + _transactionPool, + _pooledTxsRequestor, + _gossipPolicy, + _specProvider, + LimboLogs.Instance); + _handler.Init(); + } + + [TearDown] + public void TearDown() + { + _handler.Dispose(); + } + + [Test] + public void Metadata_correct() + { + _handler.ProtocolCode.Should().Be("eth"); + _handler.Name.Should().Be("eth68"); + _handler.ProtocolVersion.Should().Be(68); + _handler.MessageIdSpaceSize.Should().Be(17); + _handler.IncludeInTxPool.Should().BeTrue(); + _handler.ClientId.Should().Be(_session.Node?.ClientId); + _handler.HeadHash.Should().BeNull(); + _handler.HeadNumber.Should().Be(0); + } + + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(100)] + public void Can_handle_NewPooledTransactions_message(int txCount) + { + GenerateLists(txCount, out List types, out List sizes, out List hashes); + + var msg = new NewPooledTransactionHashesMessage68(types, sizes, hashes); + + HandleIncomingStatusMessage(); + HandleZeroMessage(msg, Eth68MessageCode.NewPooledTransactionHashes); + _pooledTxsRequestor.Received().RequestTransactionsEth66(Arg.Any>(), + Arg.Any>()); + } + + [TestCase(true)] + [TestCase(false)] + public void Should_throw_when_sizes_doesnt_match(bool removeSize) + { + GenerateLists(4, out List types, out List sizes, out List hashes); + + if (removeSize) + { + sizes.RemoveAt(sizes.Count - 1); + } + else + { + types.RemoveAt(sizes.Count - 1); + } + + var msg = new NewPooledTransactionHashesMessage68(types, sizes, hashes); + + HandleIncomingStatusMessage(); + Action action = () => HandleZeroMessage(msg, Eth68MessageCode.NewPooledTransactionHashes); + action.Should().Throw(); + } + + [Test] + public void Should_process_huge_transaction() + { + Transaction tx = Build.A.Transaction.WithType(TxType.EIP1559).WithData(new byte[2 * 1024 * 1024]) + .WithHash(TestItem.KeccakA).TestObject; + + TxDecoder txDecoder = new(); + + var msg = new NewPooledTransactionHashesMessage68(new[] { (byte)tx.Type }, + new[] { txDecoder.GetLength(tx, RlpBehaviors.None) }, new[] { tx.Hash }); + + HandleIncomingStatusMessage(); + + HandleZeroMessage(msg, Eth68MessageCode.NewPooledTransactionHashes); + _pooledTxsRequestor.Received().RequestTransactionsEth66(Arg.Any>(), + Arg.Any>()); + } + + private void HandleIncomingStatusMessage() + { + var statusMsg = new StatusMessage(); + statusMsg.GenesisHash = _genesisBlock.Hash; + statusMsg.BestHash = _genesisBlock.Hash; + + IByteBuffer statusPacket = _svc.ZeroSerialize(statusMsg); + statusPacket.ReadByte(); + _handler.HandleMessage(new ZeroPacket(statusPacket) { PacketType = 0 }); + } + + private void HandleZeroMessage(T msg, byte messageCode) where T : MessageBase + { + IByteBuffer getBlockHeadersPacket = _svc.ZeroSerialize(msg); + getBlockHeadersPacket.ReadByte(); + _handler.HandleMessage(new ZeroPacket(getBlockHeadersPacket) { PacketType = messageCode }); + } + + private void GenerateLists(int txCount, out List types, out List sizes, out List hashes) + { + TxDecoder txDecoder = new(); + types = new(); + sizes = new(); + hashes = new(); + + for (int i = 0; i < txCount; ++i) + { + Transaction tx = Build.A.Transaction.WithType((TxType)(i % 3)).WithData(new byte[i]) + .WithHash(i % 2 == 0 ? TestItem.KeccakA : TestItem.KeccakB).TestObject; + + types.Add((byte)tx.Type); + sizes.Add(txDecoder.GetLength(tx, RlpBehaviors.None)); + hashes.Add(tx.Hash); + } + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/NewPooledTransactionHashesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/NewPooledTransactionHashesMessageSerializerTests.cs new file mode 100644 index 00000000000..484a63dbb73 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/NewPooledTransactionHashesMessageSerializerTests.cs @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V68; + +[TestFixture, Parallelizable(ParallelScope.All)] +public class NewPooledTransactionHashesMessageSerializerTests +{ + private static void Test(TxType[] types, int[] sizes, Keccak[] hashes, string expected = null) + { + NewPooledTransactionHashesMessage68 message = new(types.Select(t => (byte)t).ToList(), sizes, hashes); + NewPooledTransactionHashesMessageSerializer serializer = new(); + + SerializerTester.TestZero(serializer, message, expected); + } + + [Test] + public void Roundtrip() + { + TxType[] types = { TxType.Legacy, TxType.AccessList, TxType.EIP1559 }; + int[] sizes = { 5, 10, 1500 }; + Keccak[] hashes = { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }; + Test(types, sizes, hashes); + } + + [Test] + public void Empty_serialization() + { + TxType[] types = { }; + int[] sizes = { }; + Keccak[] hashes = { }; + Test(types, sizes, hashes, "c380c0c0"); + } + + [Test] + public void Empty_hashes_serialization() + { + TxType[] types = { TxType.EIP1559 }; + int[] sizes = { 10 }; + Keccak[] hashes = { }; + Test(types, sizes, hashes, "c402c10ac0"); + } + + [Test] + public void Non_empty_serialization() + { + TxType[] types = { TxType.AccessList }; + int[] sizes = { 2 }; + Keccak[] hashes = { TestItem.KeccakA }; + Test(types, sizes, hashes, + "e5" + "01" + "c102" + "e1a0" + TestItem.KeccakA.ToString(false)); + } + +} diff --git a/src/Nethermind/Nethermind.Network/Metrics.cs b/src/Nethermind/Nethermind.Network/Metrics.cs index 4598e0f424e..d72bc05f4c9 100644 --- a/src/Nethermind/Nethermind.Network/Metrics.cs +++ b/src/Nethermind/Nethermind.Network/Metrics.cs @@ -156,6 +156,12 @@ public static class Metrics [Description("Number of eth.65 NewPooledTransactionHashes messages sent")] public static long Eth65NewPooledTransactionHashesSent { get; set; } + [Description("Number of eth.68 NewPooledTransactionHashes messages received")] + public static long Eth68NewPooledTransactionHashesReceived { get; set; } + + [Description("Number of eth.68 NewPooledTransactionHashes messages sent")] + public static long Eth68NewPooledTransactionHashesSent { get; set; } + [Description("Number of eth.65 GetPooledTransactions messages received")] public static long Eth65GetPooledTransactionsReceived { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/NettyRlpStream.cs b/src/Nethermind/Nethermind.Network/P2P/NettyRlpStream.cs index f640764a2cc..659fbd21756 100644 --- a/src/Nethermind/Nethermind.Network/P2P/NettyRlpStream.cs +++ b/src/Nethermind/Nethermind.Network/P2P/NettyRlpStream.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using DotNetty.Buffers; using Nethermind.Serialization.Rlp; @@ -31,6 +32,20 @@ public override void Write(Span bytesToWrite) _buffer.SetWriterIndex(newWriterIndex); } + public override void Write(IReadOnlyList bytesToWrite) + { + _buffer.EnsureWritable(bytesToWrite.Count, true); + Span target = + _buffer.Array.AsSpan(_buffer.ArrayOffset + _buffer.WriterIndex, bytesToWrite.Count); + for (int i = 0; i < bytesToWrite.Count; ++i) + { + target[i] = bytesToWrite[i]; + } + + int newWriterIndex = _buffer.WriterIndex + bytesToWrite.Count; + _buffer.SetWriterIndex(newWriterIndex); + } + public override void WriteByte(byte byteToWrite) { _buffer.EnsureWritable(1, true); diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/IPooledTxsRequestor.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/IPooledTxsRequestor.cs index 52f06a3be0f..45af648dc84 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/IPooledTxsRequestor.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/IPooledTxsRequestor.cs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using System; using System.Collections.Generic; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/PooledTxsRequestor.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/PooledTxsRequestor.cs index 92efd538dca..558aa3481e8 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/PooledTxsRequestor.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/PooledTxsRequestor.cs @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using System; using System.Collections.Generic; -using System.Linq; +using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68MessageCode.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68MessageCode.cs new file mode 100644 index 00000000000..db2ec6c2668 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68MessageCode.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Network.P2P.Subprotocols.Eth.V65; + +namespace Nethermind.Network.P2P.Subprotocols.Eth.V68; + +public static class Eth68MessageCode +{ + public const int NewPooledTransactionHashes = Eth65MessageCode.NewPooledTransactionHashes; +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs new file mode 100644 index 00000000000..cbf74951a8d --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Diagnostics; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Logging; +using Nethermind.Network.P2P.Subprotocols.Eth.V65; +using Nethermind.Network.P2P.Subprotocols.Eth.V67; +using Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages; +using Nethermind.Network.Rlpx; +using Nethermind.Serialization.Rlp; +using Nethermind.Stats; +using Nethermind.Synchronization; +using Nethermind.TxPool; + +namespace Nethermind.Network.P2P.Subprotocols.Eth.V68; + +public class Eth68ProtocolHandler : Eth67ProtocolHandler +{ + private readonly IPooledTxsRequestor _pooledTxsRequestor; + private readonly TxDecoder _txDecoder = new(); + + public override string Name => "eth68"; + + public override byte ProtocolVersion => 68; + + public Eth68ProtocolHandler(ISession session, + IMessageSerializationService serializer, + INodeStatsManager nodeStatsManager, + ISyncServer syncServer, + ITxPool txPool, + IPooledTxsRequestor pooledTxsRequestor, + IGossipPolicy gossipPolicy, + ISpecProvider specProvider, + ILogManager logManager) + : base(session, serializer, nodeStatsManager, syncServer, txPool, pooledTxsRequestor, gossipPolicy, + specProvider, logManager) + { + _pooledTxsRequestor = pooledTxsRequestor; + } + + public override void HandleMessage(ZeroPacket message) + { + switch (message.PacketType) + { + case Eth68MessageCode.NewPooledTransactionHashes: + NewPooledTransactionHashesMessage68 newPooledTxHashesMsg = + Deserialize(message.Content); + ReportIn(newPooledTxHashesMsg); + Handle(newPooledTxHashesMsg); + break; + default: + base.HandleMessage(message); + break; + } + } + + private void Handle(NewPooledTransactionHashesMessage68 message) + { + if (message.Hashes.Count != message.Types.Count || message.Hashes.Count != message.Sizes.Count) + { + string errorMessage = $"Wrong format of {nameof(NewPooledTransactionHashesMessage68)} message. " + + $"Hashes count: {message.Hashes.Count} " + + $"Types count: {message.Types.Count} " + + $"Sizes count: {message.Sizes.Count}"; + if (Logger.IsTrace) + Logger.Trace(errorMessage); + + throw new SubprotocolException(errorMessage); + } + + Metrics.Eth68NewPooledTransactionHashesReceived++; + + Stopwatch stopwatch = Stopwatch.StartNew(); + + _pooledTxsRequestor.RequestTransactionsEth66(Send, message.Hashes); + + stopwatch.Stop(); + + if (Logger.IsTrace) + Logger.Trace($"OUT {Counter:D5} {nameof(NewPooledTransactionHashesMessage68)} to {Node:c} " + + $"in {stopwatch.Elapsed.TotalMilliseconds}ms"); + } + + public override void SendNewTransactions(IEnumerable txs, bool sendFullTx) + { + if (sendFullTx) + { + base.SendNewTransactions(txs, sendFullTx); + return; + } + + ArrayPoolList types = new(NewPooledTransactionHashesMessage68.MaxCount); + ArrayPoolList sizes = new(NewPooledTransactionHashesMessage68.MaxCount); + ArrayPoolList hashes = new(NewPooledTransactionHashesMessage68.MaxCount); + + foreach (Transaction tx in txs) + { + if (hashes.Count == NewPooledTransactionHashesMessage68.MaxCount) + { + SendMessage(types, sizes, hashes); + types.Clear(); + sizes.Clear(); + hashes.Clear(); + } + + if (tx.Hash is not null) + { + types.Add((byte)tx.Type); + sizes.Add(tx.GetLength(_txDecoder)); + hashes.Add(tx.Hash); + } + } + + if (hashes.Count != 0) + { + SendMessage(types, sizes, hashes); + } + } + + private void SendMessage(IReadOnlyList types, IReadOnlyList sizes, IReadOnlyList hashes) + { + NewPooledTransactionHashesMessage68 message = new(types, sizes, hashes); + Metrics.Eth68NewPooledTransactionHashesSent++; + Send(message); + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs new file mode 100644 index 00000000000..56d9a8d3206 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessage68.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core.Crypto; +using Nethermind.Network.P2P.Messages; + +namespace Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages +{ + public class NewPooledTransactionHashesMessage68 : P2PMessage + { + public const int MaxCount = 1024; + + public override int PacketType { get; } = Eth68MessageCode.NewPooledTransactionHashes; + public override string Protocol { get; } = "eth"; + + public readonly IReadOnlyList Types; + public readonly IReadOnlyList Sizes; + public readonly IReadOnlyList Hashes; + + public NewPooledTransactionHashesMessage68(IReadOnlyList types, IReadOnlyList sizes, IReadOnlyList hashes) + { + Types = types; + Sizes = sizes; + Hashes = hashes; + } + + public override string ToString() => $"{nameof(NewPooledTransactionHashesMessage68)}({Hashes.Count})"; + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs new file mode 100644 index 00000000000..83a76664665 --- /dev/null +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Messages/NewPooledTransactionHashesMessageSerializer68.cs @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Linq; +using DotNetty.Buffers; +using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Network.P2P.Subprotocols.Eth.V68.Messages +{ + public class NewPooledTransactionHashesMessageSerializer + : IZeroMessageSerializer + { + public NewPooledTransactionHashesMessage68 Deserialize(IByteBuffer byteBuffer) + { + NettyRlpStream rlpStream = new(byteBuffer); + rlpStream.ReadSequenceLength(); + byte[] types = rlpStream.DecodeByteArray(); + int[] sizes = rlpStream.DecodeArray(item => item.DecodeInt()); + Keccak[] hashes = rlpStream.DecodeArray(item => item.DecodeKeccak()); + return new NewPooledTransactionHashesMessage68(types, sizes, hashes); + } + + public void Serialize(IByteBuffer byteBuffer, NewPooledTransactionHashesMessage68 message) + { + int sizesLength = 0; + for (int i = 0; i < message.Sizes.Count; i++) + { + sizesLength += Rlp.LengthOf(message.Sizes[i]); + } + + int hashesLength = 0; + for (int i = 0; i < message.Hashes.Count; i++) + { + hashesLength += Rlp.LengthOf(message.Hashes[i]); + } + + int totalSize = Rlp.LengthOf(message.Types) + Rlp.LengthOfSequence(sizesLength) + Rlp.LengthOfSequence(hashesLength); + + byteBuffer.EnsureWritable(totalSize, true); + + RlpStream rlpStream = new NettyRlpStream(byteBuffer); + + rlpStream.StartSequence(totalSize); + rlpStream.Encode(message.Types); + + rlpStream.StartSequence(sizesLength); + for (int i = 0; i < message.Sizes.Count; ++i) + { + rlpStream.Encode(message.Sizes[i]); + } + + rlpStream.StartSequence(hashesLength); + for (int i = 0; i < message.Hashes.Count; ++i) + { + rlpStream.Encode(message.Hashes[i]); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs index f16f0f7a1bd..353ec19315a 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/AccountRangeMessageSerializer.cs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using DotNetty.Buffers; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs index 8c0163f7a1f..168bb3643f7 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/ByteCodesMessageSerializer.cs @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only +// SPDX-License-Identifier: LGPL-3.0-only using DotNetty.Buffers; using Nethermind.Serialization.Rlp; diff --git a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs index 02c8c300a16..1ae4baf9a38 100644 --- a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs @@ -16,6 +16,7 @@ using Nethermind.Network.P2P.Subprotocols.Eth.V65; using Nethermind.Network.P2P.Subprotocols.Eth.V66; using Nethermind.Network.P2P.Subprotocols.Eth.V67; +using Nethermind.Network.P2P.Subprotocols.Eth.V68; using Nethermind.Network.P2P.Subprotocols.Les; using Nethermind.Network.P2P.Subprotocols.Snap; using Nethermind.Network.P2P.Subprotocols.Wit; @@ -184,6 +185,7 @@ private IDictionary> GetProtocolFa { 66 => new Eth66ProtocolHandler(session, _serializer, _stats, _syncServer, _txPool, _pooledTxsRequestor, _gossipPolicy, _specProvider, _logManager), 67 => new Eth67ProtocolHandler(session, _serializer, _stats, _syncServer, _txPool, _pooledTxsRequestor, _gossipPolicy, _specProvider, _logManager), + 68 => new Eth68ProtocolHandler(session, _serializer, _stats, _syncServer, _txPool, _pooledTxsRequestor, _gossipPolicy, _specProvider, _logManager), _ => throw new NotSupportedException($"Eth protocol version {version} is not supported.") }; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 0011a3bd4b1..aeeedbc21f9 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -1468,6 +1468,16 @@ public static int LengthOf(byte[]? array) return LengthOf(array.AsSpan()); } + public static int LengthOf(IReadOnlyList array) + { + if (array.Count == 0) + { + return 1; + } + + return LengthOfByteString(array.Count, array[0]); + } + public static int LengthOf(Span array) { if (array.Length == 0) @@ -1475,17 +1485,23 @@ public static int LengthOf(Span array) return 1; } - if (array.Length == 1 && array[0] < 128) + return LengthOfByteString(array.Length, array[0]); + } + + // Assumes that length is greater then 0 + private static int LengthOfByteString(int length, byte firstByte) + { + if (length == 1 && firstByte < 128) { return 1; } - if (array.Length < 56) + if (length < 56) { - return array.Length + 1; + return length + 1; } - return LengthOfLength(array.Length) + 1 + array.Length; + return LengthOfLength(length) + 1 + length; } public static int LengthOf(string value) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index 49442f59cab..cf1d6d5c9d9 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -149,12 +151,27 @@ public virtual void WriteByte(byte byteToWrite) Data[Position++] = byteToWrite; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Write(byte[] bytesToWrite) + { + Write(bytesToWrite.AsSpan()); + } + public virtual void Write(Span bytesToWrite) { bytesToWrite.CopyTo(Data.AsSpan(Position, bytesToWrite.Length)); Position += bytesToWrite.Length; } + public virtual void Write(IReadOnlyList bytesToWrite) + { + for (int i = 0; i < bytesToWrite.Count; ++i) + { + Data![Position + i] = bytesToWrite[i]; + } + Position += bytesToWrite.Count; + } + protected virtual string Description => Data?.Slice(0, Length).ToHexString() ?? "0x"; @@ -449,9 +466,15 @@ public void Encode(string value) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Encode(byte[] input) + { + Encode(input.AsSpan()); + } + public void Encode(Span input) { - if (input.IsEmpty || input.Length == 0) + if (input.IsEmpty) { WriteByte(EmptyArrayByte); } @@ -475,6 +498,32 @@ public void Encode(Span input) } } + public void Encode(IReadOnlyList input) + { + if (input.Count == 0) + { + WriteByte(EmptyArrayByte); + } + else if (input.Count == 1 && input[0] < 128) + { + WriteByte(input[0]); + } + else if (input.Count < 56) + { + byte smallPrefix = (byte)(input.Count + 128); + WriteByte(smallPrefix); + Write(input); + } + else + { + int lengthOfLength = Rlp.LengthOfLength(input.Count); + byte prefix = (byte)(183 + lengthOfLength); + WriteByte(prefix); + WriteEncodedLength(input.Count); + Write(input); + } + } + public int ReadNumberOfItemsRemaining(int? beforePosition = null, int maxSearch = int.MaxValue) { int positionStored = Position; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpTxDecoder.cs index e314eef8b60..6e2a21aae37 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpTxDecoder.cs @@ -14,8 +14,6 @@ namespace Nethermind.Serialization.Rlp { - public class SystemTxDecoder : TxDecoder { } - public class GeneratedTxDecoder : TxDecoder { } public class RlpTxDecoder where T : Transaction, new() diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 68b7256d830..a07118761a6 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -9,11 +9,19 @@ namespace Nethermind.Serialization.Rlp { - public class TxDecoder : TxDecoder { } + public class TxDecoder : TxDecoder, ITransactionSizeCalculator + { + public int GetLength(Transaction tx) + { + return GetLength(tx, RlpBehaviors.None); + } + } public interface ISigningHashEncoder { Span Encode(T item, V parameters); } + public class SystemTxDecoder : TxDecoder { } + public class GeneratedTxDecoder : TxDecoder { } public struct TxSignatureHashParams { diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs index 9be4911b1cb..a1fe6545058 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.Decoder.cs @@ -61,7 +61,7 @@ private static byte[] EncodeExtension(ITrieNodeResolver tree, TrieNode item) // I think it can only happen if we have a short extension to a branch with a short extension as the only child? // so | // so | - // so E - - - - - - - - - - - - - - - + // so E - - - - - - - - - - - - - - - // so | // so | rlpStream.Write(nodeRef.FullRlp);