diff --git a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs index 298e4c75b7e..3d4fdb9fa1f 100644 --- a/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Basic.Test/TransactionTests.cs @@ -42,7 +42,7 @@ public void Test(TransactionTest test) Assert.That(decodedUnsigned.Value, Is.EqualTo(test.Value), "value"); Assert.That(decodedUnsigned.GasPrice, Is.EqualTo(test.GasPrice), "gasPrice"); Assert.That(decodedUnsigned.GasLimit, Is.EqualTo(test.StartGas), "gasLimit"); - Assert.That(decodedUnsigned.Data, Is.EqualTo(test.Data), "data"); + Assert.That(decodedUnsigned.Data.AsArray(), Is.EqualTo(test.Data), "data"); Assert.That(decodedUnsigned.To, Is.EqualTo(test.To), "to"); Assert.That(decodedUnsigned.Nonce, Is.EqualTo(test.Nonce), "nonce"); diff --git a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs index 8558463e335..dc872261d15 100644 --- a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs @@ -179,7 +179,7 @@ private void RunTest(TransactionTest test, IReleaseSpec spec) if (validTest != null) { Assert.That(transaction.Value, Is.EqualTo(validTest.Value), "value"); - Assert.That(transaction.Data, Is.EqualTo(validTest.Data), "data"); + Assert.That(transaction.Data.AsArray(), Is.EqualTo(validTest.Data), "data"); Assert.That(transaction.GasLimit, Is.EqualTo(validTest.GasLimit.ToInt64(null)), "gasLimit"); Assert.That(transaction.GasPrice, Is.EqualTo(validTest.GasPrice), "gasPrice"); Assert.That(transaction.Nonce, Is.EqualTo(validTest.Nonce), "nonce"); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/ReportingValidatorContractTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/ReportingValidatorContractTests.cs index 2d6a8226378..b24804531f2 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/ReportingValidatorContractTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/ReportingValidatorContractTests.cs @@ -19,7 +19,7 @@ public void Should_generate_malicious_transaction() { ReportingValidatorContract contract = new(AbiEncoder.Instance, new Address("0x1000000000000000000000000000000000000001"), Substitute.For()); Transaction transaction = contract.ReportMalicious(new Address("0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441"), 10, new byte[0]); - transaction.Data.ToHexString().Should().Be("c476dd4000000000000000000000000075df42383afe6bf5194aa8fa0e9b3d5f9e869441000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"); + transaction.Data.AsArray().ToHexString().Should().Be("c476dd4000000000000000000000000075df42383afe6bf5194aa8fa0e9b3d5f9e869441000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000"); } [Test] @@ -27,7 +27,7 @@ public void Should_generate_benign_transaction() { ReportingValidatorContract contract = new(AbiEncoder.Instance, new Address("0x1000000000000000000000000000000000000001"), Substitute.For()); Transaction transaction = contract.ReportBenign(new Address("0x75df42383afe6bf5194aa8fa0e9b3d5f9e869441"), 10); - transaction.Data.ToHexString().Should().Be("d69f13bb00000000000000000000000075df42383afe6bf5194aa8fa0e9b3d5f9e869441000000000000000000000000000000000000000000000000000000000000000a"); + transaction.Data.AsArray().ToHexString().Should().Be("d69f13bb00000000000000000000000075df42383afe6bf5194aa8fa0e9b3d5f9e869441000000000000000000000000000000000000000000000000000000000000000a"); } } } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs index 9b287c3735f..d6df3491e65 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/ValidatorContractTests.cs @@ -8,6 +8,7 @@ using Nethermind.Consensus.AuRa.Contracts; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Evm; @@ -84,11 +85,11 @@ public void finalize_change_should_call_correct_transaction() Arg.Is(t => IsEquivalentTo(expectation, t)), _block.Header, Arg.Any()); } - private static bool IsEquivalentTo(object expected, object item) + private static bool IsEquivalentTo(Transaction expected, Transaction item) { try { - item.Should().BeEquivalentTo(expected); + item.EqualToTransaction(expected); return true; } catch diff --git a/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs index da5b91dd0ff..7717e75d0e6 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Reward/AuRaRewardCalculatorTests.cs @@ -10,6 +10,7 @@ using Nethermind.Consensus.AuRa.Rewards; using Nethermind.Consensus.Rewards; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Specs.ChainSpecStyle; using Nethermind.Core.Test.Builders; @@ -213,7 +214,7 @@ private void SetupBlockRewards(IDictionary rewards) private bool CheckTransaction(Transaction t, ICollection
addresses, byte[] transactionData) => t.SenderAddress == Address.SystemUser && (t.To == _auraParameters.BlockRewardContractAddress || addresses.Contains(t.To)) - && t.Data == transactionData; + && t.Data.AsArray() == transactionData; private byte[] SetupAbiAddresses(params BlockReward[] rewards) { diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs index 30d7123eeea..5511d9db936 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -652,7 +652,7 @@ private byte[] SetupAbiAddresses(Address[] addresses) private bool CheckTransaction(Transaction t, (Address Sender, byte[] TransactionData) transactionInfo) { - return t.SenderAddress == transactionInfo.Sender && t.To == _contractAddress && t.Data == transactionInfo.TransactionData; + return t.SenderAddress == transactionInfo.Sender && t.To == _contractAddress && t.Data.AsArray() == transactionInfo.TransactionData; } public class ConsecutiveInitiateChangeTestParameters diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index d213605f506..a4bd92bf46c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -1,11 +1,15 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Runtime.InteropServices.JavaScript; using FluentAssertions; using Nethermind.Blockchain.Blocks; using Nethermind.Core; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; +using Nethermind.Serialization.Rlp; +using Nethermind.Specs; using NUnit.Framework; namespace Nethermind.Blockchain.Test.Blocks; @@ -60,4 +64,29 @@ public void Test_when_cached_does_not_touch_db_on_next_get() retrieved = store.Get(block.Hash, true); retrieved.Should().BeEquivalentTo(block); } + + [Test] + public void Test_getReceiptRecoveryBlock_produce_same_transaction_as_normal_get() + { + TestMemDb db = new TestMemDb(); + BlockStore store = new BlockStore(db); + + Block block = Build.A.Block.WithNumber(1) + .WithTransactions(3, MainnetSpecProvider.Instance) + .TestObject; + + store.Insert(block); + + ReceiptRecoveryBlock retrieved = store.GetReceiptRecoveryBlock(block.Hash).Value; + retrieved.Should().NotBeNull(); + + retrieved.Header.Should().BeEquivalentTo(block.Header); + retrieved.TransactionCount.Should().Be(block.Transactions.Length); + + for (int i = 0; i < retrieved.TransactionCount; i++) + { + block.Transactions[i].Data = Array.Empty(); + retrieved.GetNextTransaction().Should().BeEquivalentTo(block.Transactions[i]); + } + } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index 784f0c3ab8c..998784411b6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using FluentAssertions; +using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Receipts; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -29,6 +30,7 @@ public class PersistentReceiptStorageTests private TestMemColumnsDb _receiptsDb = null!; private ReceiptsRecovery _receiptsRecovery; private IBlockTree _blockTree; + private IBlockStore _blockStore; private readonly bool _useCompactReceipts; private ReceiptConfig _receiptConfig; private PersistentReceiptStorage _storage; @@ -49,6 +51,7 @@ public void SetUp() _receiptsDb = new TestMemColumnsDb(); _receiptsDb.GetColumnDb(ReceiptsColumns.Blocks).Set(Keccak.Zero, Array.Empty()); _blockTree = Substitute.For(); + _blockStore = Substitute.For(); CreateStorage(); } @@ -60,6 +63,7 @@ private void CreateStorage() MainnetSpecProvider.Instance, _receiptsRecovery, _blockTree, + _blockStore, _receiptConfig, _decoder ) @@ -393,7 +397,7 @@ public void When_NewHeadBlock_ClearOldTxIndex() { (block, TxReceipt[] receipts) = PrepareBlock(block, isFinalized, headNumber); _storage.Insert(block, receipts); - _receiptsRecovery.TryRecover(block, receipts); + _receiptsRecovery.TryRecover(new ReceiptRecoveryBlock(block), receipts); return (block, receipts); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index 315a6cc27bd..1c30fb07ee7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -112,7 +112,7 @@ private ReceiptsIterator CreateIterator(TxReceipt[] receipts, Block block) false ); - ReceiptsIterator iterator = new ReceiptsIterator(span, blockDb, () => recovery.CreateRecoveryContext(block), _decoder.GetRefDecoder(span)); + ReceiptsIterator iterator = new ReceiptsIterator(span, blockDb, () => recovery.CreateRecoveryContext(new ReceiptRecoveryBlock(block)), _decoder.GetRefDecoder(span)); return iterator; } } diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs index 4220e7acda1..6cf184341f0 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs @@ -120,7 +120,7 @@ public BlockTree( ISpecProvider? specProvider, IBloomStorage? bloomStorage, ILogManager? logManager) - : this(dbProvider?.BlocksDb, dbProvider?.HeadersDb, dbProvider?.BlockInfosDb, dbProvider?.MetadataDb, + : this(new BlockStore(dbProvider?.BlocksDb), dbProvider?.HeadersDb, dbProvider?.BlockInfosDb, dbProvider?.MetadataDb, chainLevelInfoRepository, specProvider, bloomStorage, new SyncConfig(), logManager) { } @@ -132,7 +132,7 @@ public BlockTree( IBloomStorage? bloomStorage, ISyncConfig? syncConfig, ILogManager? logManager) - : this(dbProvider?.BlocksDb, dbProvider?.HeadersDb, dbProvider?.BlockInfosDb, dbProvider?.MetadataDb, + : this(new BlockStore(dbProvider?.BlocksDb), dbProvider?.HeadersDb, dbProvider?.BlockInfosDb, dbProvider?.MetadataDb, chainLevelInfoRepository, specProvider, bloomStorage, syncConfig, logManager) { } @@ -145,7 +145,7 @@ public BlockTree( ISpecProvider? specProvider, IBloomStorage? bloomStorage, ILogManager? logManager) - : this(blockDb, headerDb, blockInfoDb, new MemDb(), chainLevelInfoRepository, specProvider, bloomStorage, + : this(new BlockStore(blockDb), headerDb, blockInfoDb, new MemDb(), chainLevelInfoRepository, specProvider, bloomStorage, new SyncConfig(), logManager) { } @@ -160,9 +160,24 @@ public BlockTree( IBloomStorage? bloomStorage, ISyncConfig? syncConfig, ILogManager? logManager) + : this(new BlockStore(blockDb), headerDb, blockInfoDb, metadataDb, chainLevelInfoRepository, specProvider, bloomStorage, + syncConfig, logManager) + { + } + + public BlockTree( + IBlockStore? blockStore, + IDb? headerDb, + IDb? blockInfoDb, + IDb? metadataDb, + IChainLevelInfoRepository? chainLevelInfoRepository, + ISpecProvider? specProvider, + IBloomStorage? bloomStorage, + ISyncConfig? syncConfig, + ILogManager? logManager) { _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _blockStore = new BlockStore(blockDb ?? throw new ArgumentNullException(nameof(blockDb))); + _blockStore = blockStore ?? throw new ArgumentNullException(nameof(blockStore)); _headerDb = headerDb ?? throw new ArgumentNullException(nameof(headerDb)); _blockInfoDb = blockInfoDb ?? throw new ArgumentNullException(nameof(blockInfoDb)); _metadataDb = metadataDb ?? throw new ArgumentNullException(nameof(metadataDb)); diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs index 0b4df139b66..ea26ad796aa 100644 --- a/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; @@ -13,6 +15,7 @@ namespace Nethermind.Blockchain.Blocks; public class BlockStore : IBlockStore { private readonly IDb _blockDb; + private readonly IDbWithSpan? _blockDbAsSpan; private readonly BlockDecoder _blockDecoder = new(); private const int CacheSize = 64; @@ -22,6 +25,11 @@ private readonly LruCache public BlockStore(IDb blockDb) { _blockDb = blockDb; + + if (blockDb is IDbWithSpan blockDbAsSpan) + _blockDbAsSpan = blockDbAsSpan; + else + _blockDbAsSpan = null; } public void SetMetadata(byte[] key, byte[] value) @@ -55,9 +63,27 @@ public void Delete(Keccak blockHash) public Block? Get(Keccak blockHash, bool shouldCache) { + return _blockDb.Get(blockHash, _blockDecoder, _blockCache, shouldCache); } + public ReceiptRecoveryBlock? GetReceiptRecoveryBlock(Keccak blockHash) + { + MemoryManager? memoryOwner = null; + Memory memory; + if (_blockDbAsSpan != null) + { + memoryOwner = _blockDbAsSpan.GetOwnedMemory(blockHash.Bytes); + memory = memoryOwner.Memory; + } + else + { + memory = _blockDb.Get(blockHash.Bytes); + } + + return _blockDecoder.DecodeToReceiptRecoveryBlock(memoryOwner, memory, RlpBehaviors.None); + } + public void Cache(Block block) { _blockCache.Set(block.Hash, block); diff --git a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockStore.cs b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockStore.cs index 0c03587569d..a0d8a63299d 100644 --- a/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockStore.cs +++ b/src/Nethermind/Nethermind.Blockchain/Blocks/IBlockStore.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Serialization.Rlp; namespace Nethermind.Blockchain.Blocks; @@ -15,6 +16,7 @@ public interface IBlockStore void Insert(Block block); void Delete(Keccak blockHash); Block Get(Keccak blockHash, bool shouldCache = true); + ReceiptRecoveryBlock? GetReceiptRecoveryBlock(Keccak blockHash); void Cache(Block block); diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs index 14c902ffff4..d2cfeba665b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/IReceiptsRecovery.cs @@ -1,18 +1,24 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core; +using Nethermind.Serialization.Rlp; namespace Nethermind.Blockchain.Receipts { public interface IReceiptsRecovery { - ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool forceRecoverSender = true); + ReceiptsRecoveryResult TryRecover(ReceiptRecoveryBlock block, TxReceipt[] receipts, bool forceRecoverSender = true); + + ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool forceRecoverSender = true) => + TryRecover(new ReceiptRecoveryBlock(block), receipts, forceRecoverSender); + bool NeedRecover(TxReceipt[] receipts, bool forceRecoverSender = true, bool recoverSenderOnly = false); - IRecoveryContext CreateRecoveryContext(Block block, bool forceRecoverSender = false); + IRecoveryContext CreateRecoveryContext(ReceiptRecoveryBlock block, bool forceRecoverSender = false); - public interface IRecoveryContext + public interface IRecoveryContext : IDisposable { void RecoverReceiptData(TxReceipt receipt); void RecoverReceiptData(ref TxReceiptStructRef receipt); diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs index 6a17481dd63..d882bd70552 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; +using Nethermind.Blockchain.Blocks; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; @@ -29,6 +30,7 @@ public class PersistentReceiptStorage : IReceiptStorage private long _migratedBlockNumber; private readonly ReceiptArrayStorageDecoder _storageDecoder = ReceiptArrayStorageDecoder.Instance; private readonly IBlockTree _blockTree; + private readonly IBlockStore _blockStore; private readonly IReceiptConfig _receiptConfig; private readonly bool _legacyHashKey; @@ -40,6 +42,7 @@ public PersistentReceiptStorage( ISpecProvider specProvider, IReceiptsRecovery receiptsRecovery, IBlockTree blockTree, + IBlockStore blockStore, IReceiptConfig receiptConfig, ReceiptArrayStorageDecoder? storageDecoder = null ) @@ -52,6 +55,7 @@ public PersistentReceiptStorage( _blocksDb = _database.GetColumnDb(ReceiptsColumns.Blocks); _transactionDb = _database.GetColumnDb(ReceiptsColumns.Transactions); _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + _blockStore = blockStore ?? throw new ArgumentNullException(nameof(blockStore)); _storageDecoder = storageDecoder ?? ReceiptArrayStorageDecoder.Instance; _receiptConfig = receiptConfig ?? throw new ArgumentNullException(nameof(receiptConfig)); @@ -237,8 +241,14 @@ public bool TryGetReceiptsIterator(long blockNumber, Keccak blockHash, out Recei { recoveryContextFactory = () => { - Block block = _blockTree.FindBlock(blockHash); - return _receiptsRecovery.CreateRecoveryContext(block!); + ReceiptRecoveryBlock? block = _blockStore.GetReceiptRecoveryBlock(blockHash); + + if (!block.HasValue) + { + throw new InvalidOperationException($"Unable to recover receipts for block {blockHash} because of missing block data."); + } + + return _receiptsRecovery.CreateRecoveryContext(block.Value); }; } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsIterator.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsIterator.cs index 0aceea457c0..b95f2b1af5a 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsIterator.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsIterator.cs @@ -111,6 +111,7 @@ public void Dispose() { _blocksDb?.DangerousReleaseMemory(_decoderContext.Data); } + _recoveryContext?.Dispose(); } public LogEntriesIterator IterateLogs(TxReceiptStructRef receipt) diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs index 779e42672eb..69f9a5f37d8 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/ReceiptsRecovery.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Evm; +using Nethermind.Serialization.Rlp; namespace Nethermind.Blockchain.Receipts { @@ -23,16 +24,16 @@ public ReceiptsRecovery(IEthereumEcdsa? ecdsa, ISpecProvider? specProvider, bool _reinsertReceiptOnRecover = reinsertReceiptOnRecover; } - public ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool forceRecoverSender = true) + public ReceiptsRecoveryResult TryRecover(ReceiptRecoveryBlock block, TxReceipt[] receipts, bool forceRecoverSender = true) { - var canRecover = block.Transactions.Length == receipts?.Length; + var canRecover = block.TransactionCount == receipts?.Length; if (canRecover) { var needRecover = NeedRecover(receipts, forceRecoverSender); if (needRecover) { - var ctx = CreateRecoveryContext(block, forceRecoverSender); - for (int receiptIndex = 0; receiptIndex < block.Transactions.Length; receiptIndex++) + using var ctx = CreateRecoveryContext(block, forceRecoverSender); + for (int receiptIndex = 0; receiptIndex < block.TransactionCount; receiptIndex++) { if (receipts.Length > receiptIndex) { @@ -55,7 +56,7 @@ public ReceiptsRecoveryResult TryRecover(Block block, TxReceipt[] receipts, bool return ReceiptsRecoveryResult.Fail; } - public IReceiptsRecovery.IRecoveryContext CreateRecoveryContext(Block block, bool forceRecoverSender = false) + public IReceiptsRecovery.IRecoveryContext CreateRecoveryContext(ReceiptRecoveryBlock block, bool forceRecoverSender = false) { var releaseSpec = _specProvider.GetSpec(block.Header); return new RecoveryContext(releaseSpec, block, forceRecoverSender, _ecdsa); @@ -72,14 +73,14 @@ public bool NeedRecover(TxReceipt[] receipts, bool forceRecoverSender = true, bo private class RecoveryContext : IReceiptsRecovery.IRecoveryContext { private readonly IReleaseSpec _releaseSpec; - private readonly Block _block; + private ReceiptRecoveryBlock _block; private readonly bool _forceRecoverSender; private readonly IEthereumEcdsa _ecdsa; private long _gasUsedBefore = 0; private int _transactionIndex = 0; - public RecoveryContext(IReleaseSpec releaseSpec, Block block, bool forceRecoverSender, IEthereumEcdsa ecdsa) + public RecoveryContext(IReleaseSpec releaseSpec, ReceiptRecoveryBlock block, bool forceRecoverSender, IEthereumEcdsa ecdsa) { _releaseSpec = releaseSpec; _block = block; @@ -89,12 +90,12 @@ public RecoveryContext(IReleaseSpec releaseSpec, Block block, bool forceRecoverS public void RecoverReceiptData(TxReceipt receipt) { - if (_transactionIndex >= _block.Transactions.Length) + if (_transactionIndex >= _block.TransactionCount) { throw new InvalidOperationException("Trying to recover more receipt that transaction"); } - Transaction transaction = _block.Transactions[_transactionIndex]; + Transaction transaction = _block.GetNextTransaction(); receipt.TxType = transaction.Type; receipt.BlockHash = _block.Hash; @@ -117,12 +118,12 @@ public void RecoverReceiptData(TxReceipt receipt) public void RecoverReceiptData(ref TxReceiptStructRef receipt) { - if (_transactionIndex >= _block.Transactions.Length) + if (_transactionIndex >= _block.TransactionCount) { throw new InvalidOperationException("Trying to recover more receipt that transaction"); } - Transaction transaction = _block.Transactions[_transactionIndex]; + Transaction transaction = _block.GetNextTransaction(); receipt.TxType = transaction.Type; receipt.BlockHash = _block.Hash!.ToStructRef(); @@ -151,6 +152,11 @@ private void IncrementContext(long gasUsedTotal) _transactionIndex++; _gasUsedBefore = gasUsedTotal; } + + public void Dispose() + { + _block.Dispose(); + } } } } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV3.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV3.cs index c7ebc7634be..99c955616fa 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV3.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV3.cs @@ -43,7 +43,7 @@ protected override object[] GetAllowedTxTypesParameters(Transaction tx, BlockHea return new object[] { - tx.SenderAddress, tx.To ?? Address.Zero, tx.Value, gasPrice, tx.Data ?? Array.Empty() + tx.SenderAddress, tx.To ?? Address.Zero, tx.Value, gasPrice, tx.Data.AsArray() ?? Array.Empty() }; } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV4.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV4.cs index 2cf64a2c396..33561997d60 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV4.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TransactionPermissionContractV4.cs @@ -4,6 +4,7 @@ using System; using Nethermind.Abi; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Int256; using Nethermind.Evm; @@ -40,7 +41,7 @@ protected override object[] GetAllowedTxTypesParameters(Transaction tx, BlockHea return new object[] { - tx.SenderAddress, tx.To ?? Address.Zero, tx.Value, tx.MaxFeePerGas, tx.MaxPriorityFeePerGas, tx.GasLimit, tx.Data ?? Array.Empty() + tx.SenderAddress, tx.To ?? Address.Zero, tx.Value, tx.MaxFeePerGas, tx.MaxPriorityFeePerGas, tx.GasLimit, tx.Data.AsArray() ?? Array.Empty() }; } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TxPriorityContract.Destination.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TxPriorityContract.Destination.cs index f8621384591..099fb2c0b91 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TxPriorityContract.Destination.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Contracts/TxPriorityContract.Destination.cs @@ -54,7 +54,7 @@ public static implicit operator DestinationTuple(Destination destination) => public static Destination GetTransactionKey(Transaction tx) { - byte[] fnSignature = tx.Data?.Length >= 4 ? AbiSignature.GetAddress(tx.Data) : FnSignatureEmpty; + byte[] fnSignature = tx.Data?.Length >= 4 ? AbiSignature.GetAddress(tx.Data.AsArray()) : FnSignatureEmpty; return new Destination(tx.To, fnSignature, UInt256.Zero); } diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs index 5ca5be25127..ce5de9fd18f 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/TxDecoderTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Numeric; @@ -99,7 +100,7 @@ public void Roundtrip((Transaction Tx, string Description) testCase) Transaction? decoded = _txDecoder.Decode(rlpStream); decoded!.SenderAddress = new EthereumEcdsa(TestBlockchainIds.ChainId, LimboLogs.Instance).RecoverAddress(decoded); decoded.Hash = decoded.CalculateHash(); - decoded.Should().BeEquivalentTo(testCase.Tx, testCase.Description); + decoded.EqualToTransaction(testCase.Tx); } [TestCaseSource(nameof(TestCaseSource))] @@ -114,7 +115,7 @@ public void Roundtrip_ValueDecoderContext((Transaction Tx, string Description) t Transaction? decoded = _txDecoder.Decode(ref decoderContext); decoded!.SenderAddress = new EthereumEcdsa(TestBlockchainIds.ChainId, LimboLogs.Instance).RecoverAddress(decoded); decoded.Hash = decoded.CalculateHash(); - decoded.Should().BeEquivalentTo(testCase.Tx, testCase.Description); + decoded.EqualToTransaction(testCase.Tx); } [TestCaseSource(nameof(YoloV3TestCases))] diff --git a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs index 451979c827d..9810b2cfcf1 100644 --- a/src/Nethermind/Nethermind.Core.Test/RlpTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/RlpTests.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using System.Runtime.InteropServices; using FluentAssertions; using Nethermind.Int256; using Nethermind.Serialization.Rlp; @@ -219,5 +220,32 @@ public void Long_and_big_integer_encoded_the_same(long value) Assert.That(rlpBigInt.Bytes, Is.EqualTo(rlpLong.Bytes)); } + + [TestCase(true)] + [TestCase(false)] + public void RlpContextWithSliceMemory_shouldNotCopyUnderlyingData(bool sliceValue) + { + byte[] randomBytes = new byte[100]; + Random.Shared.NextBytes(randomBytes); + + int requiredLength = Rlp.LengthOf(randomBytes) * 3; + RlpStream stream = new RlpStream(requiredLength); + stream.Encode(randomBytes); + stream.Encode(randomBytes); + stream.Encode(randomBytes); + + Memory memory = stream.Data; + Rlp.ValueDecoderContext context = new Rlp.ValueDecoderContext(memory, sliceValue); + + for (int i = 0; i < 3; i++) + { + Memory? slice = context.DecodeByteArrayMemory(); + slice.Should().NotBeNull(); + MemoryMarshal.TryGetArray(slice.Value, out ArraySegment segment); + + bool isACopy = (segment.Offset == 0 && segment.Count == slice.Value.Length); + isACopy.Should().NotBe(sliceValue); + } + } } } diff --git a/src/Nethermind/Nethermind.Core.Test/TransactionTests.cs b/src/Nethermind/Nethermind.Core.Test/TransactionTests.cs index 3b2d04f9e54..05c5eb706a2 100644 --- a/src/Nethermind/Nethermind.Core.Test/TransactionTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/TransactionTests.cs @@ -4,6 +4,7 @@ using System; using FluentAssertions; using System.Collections.Generic; +using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Specs.ChainSpecStyle; using NUnit.Framework; @@ -42,4 +43,18 @@ public void Supports1559_returns_expected_results(int decodedFeeCap, bool expect Assert.That(transaction.Supports1559, Is.EqualTo(expectedSupports1559)); } } + + public static class TransactionTestExtensions + { + public static void EqualToTransaction(this Transaction subject, Transaction expectation) + { + subject.Should().BeEquivalentTo( + expectation, + o => o + .ComparingByMembers() + .Using>(ctx => ctx.Subject.AsArray().Should().BeEquivalentTo(ctx.Expectation.AsArray())) + .WhenTypeIs>() + ); + } + } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/MemoryExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/MemoryExtensions.cs new file mode 100644 index 00000000000..9d6d83799b0 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Extensions/MemoryExtensions.cs @@ -0,0 +1,29 @@ +using System; +using System.Runtime.InteropServices; + +namespace Nethermind.Core.Extensions; + +public static class MemoryExtensions +{ + /// + /// Try to get the underlying array if possible to prevent copying. + /// + /// + /// + public static byte[]? AsArray(this in Memory? memory) + { + if (memory == null) return null; + + return memory.Value.AsArray(); + } + + public static byte[] AsArray(this in Memory memory) + { + if ( + MemoryMarshal.TryGetArray(memory, out ArraySegment segment) && + segment.Offset == 0 && segment.Count == segment.Array!.Length + ) return segment.Array; + + return memory.Span.ToArray(); + } +} diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index fecb85a5229..d9bbad18758 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -37,7 +37,7 @@ public class Transaction public long GasLimit { get; set; } public Address? To { get; set; } public UInt256 Value { get; set; } - public byte[]? Data { get; set; } + public Memory? Data { get; set; } public Address? SenderAddress { get; set; } public Signature? Signature { get; set; } public bool IsSigned => Signature is not null; @@ -66,11 +66,8 @@ public Keccak? Hash } set { - lock (this) - { - ClearPreHashInternal(); - _hash = value; - } + ClearPreHash(); + _hash = value; } } @@ -147,7 +144,7 @@ public string ToShortString() { string gasPriceString = Supports1559 ? $"maxPriorityFeePerGas: {MaxPriorityFeePerGas}, MaxFeePerGas: {MaxFeePerGas}" : $"gas price {GasPrice}"; - return $"[TX: hash {Hash} from {SenderAddress} to {To} with data {Data?.ToHexString()}, {gasPriceString} and limit {GasLimit}, nonce {Nonce}]"; + return $"[TX: hash {Hash} from {SenderAddress} to {To} with data {Data.AsArray()?.ToHexString()}, {gasPriceString} and limit {GasLimit}, nonce {Nonce}]"; } public string ToString(string indent) @@ -169,7 +166,7 @@ public string ToString(string indent) builder.AppendLine($"{indent}Gas Limit: {GasLimit}"); builder.AppendLine($"{indent}Nonce: {Nonce}"); builder.AppendLine($"{indent}Value: {Value}"); - builder.AppendLine($"{indent}Data: {(Data ?? Array.Empty()).ToHexString()}"); + builder.AppendLine($"{indent}Data: {(Data.AsArray() ?? Array.Empty()).ToHexString()}"); builder.AppendLine($"{indent}Signature: {(Signature?.Bytes ?? Array.Empty()).ToHexString()}"); builder.AppendLine($"{indent}V: {Signature?.V}"); builder.AppendLine($"{indent}ChainId: {Signature?.ChainId}"); diff --git a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs index 36c5864069d..b29e0304284 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; @@ -10,6 +11,7 @@ using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; +using Nethermind.Core.Buffers; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Db.Rocks; @@ -251,6 +253,21 @@ public void Smoke_test_span() _db.DangerousReleaseMemory(readSpan); } + [Test] + public void Smoke_test_span_with_memory_manager() + { + byte[] key = new byte[] { 1, 2, 3 }; + byte[] value = new byte[] { 4, 5, 6 }; + _db.PutSpan(key, value); + Span readSpan = _db.GetSpan(key); + Assert.That(readSpan.ToArray(), Is.EqualTo(new byte[] { 4, 5, 6 })); + + IMemoryOwner manager = new DbSpanMemoryManager(_db, readSpan); + Memory theMemory = manager.Memory; + Assert.That(theMemory.ToArray(), Is.EqualTo(new byte[] { 4, 5, 6 })); + manager.Dispose(); + } + private static RocksDbSettings GetRocksDbSettings(string dbPath, string dbName) { return new(dbName, dbPath) diff --git a/src/Nethermind/Nethermind.Db/DbSpanMemoryManager.cs b/src/Nethermind/Nethermind.Db/DbSpanMemoryManager.cs new file mode 100644 index 00000000000..4411a5843a6 --- /dev/null +++ b/src/Nethermind/Nethermind.Db/DbSpanMemoryManager.cs @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Nethermind.Db; + +namespace Nethermind.Core.Buffers; + +public unsafe sealed class DbSpanMemoryManager : MemoryManager +{ + private readonly IDbWithSpan _db; + private void* _ptr; + private readonly int _length; + + public DbSpanMemoryManager(IDbWithSpan db, Span unmanagedSpan) + { + _db = db; + _ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(unmanagedSpan)); + _length = unmanagedSpan.Length; + } + + protected override void Dispose(bool disposing) + { + if (_ptr != null) + { + _db.DangerousReleaseMemory(GetSpan()); + } + + _ptr = null; + } + + public override Span GetSpan() + { + if (_ptr == null && _length > 0) + { + ThrowDisposed(); + } + + return new Span(_ptr, _length); + } + + public override MemoryHandle Pin(int elementIndex = 0) + { + if (_ptr == null && _length > 0) + { + ThrowDisposed(); + } + + return new MemoryHandle(_ptr); + } + + public override void Unpin() + { + } + + [DoesNotReturn] + [StackTraceHidden] + private static void ThrowDisposed() + { + throw new ObjectDisposedException(nameof(DbSpanMemoryManager)); + } +} diff --git a/src/Nethermind/Nethermind.Db/IDbWithSpan.cs b/src/Nethermind/Nethermind.Db/IDbWithSpan.cs index 461b702ab15..d095d18518f 100644 --- a/src/Nethermind/Nethermind.Db/IDbWithSpan.cs +++ b/src/Nethermind/Nethermind.Db/IDbWithSpan.cs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Buffers; +using Nethermind.Core.Buffers; namespace Nethermind.Db { @@ -15,5 +17,10 @@ public interface IDbWithSpan : IDb Span GetSpan(ReadOnlySpan key); void PutSpan(ReadOnlySpan key, ReadOnlySpan value); void DangerousReleaseMemory(in Span span); + MemoryManager GetOwnedMemory(ReadOnlySpan key) + { + Span span = GetSpan(key); + return new DbSpanMemoryManager(this, span); + } } } diff --git a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj index 3eb9ee73a73..10676c58f85 100644 --- a/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj +++ b/src/Nethermind/Nethermind.Db/Nethermind.Db.csproj @@ -4,6 +4,7 @@ latest annotations true + true diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/ParityLikeTxTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/ParityLikeTxTracerTests.cs index 28120bc5be1..c4cc04974c4 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/ParityLikeTxTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/ParityLikeTxTracerTests.cs @@ -46,7 +46,7 @@ public void On_failure_block_and_tx_fields_are_set() Assert.That(trace.TransactionHash, Is.EqualTo(tx.Hash), "tx hash"); Assert.That(trace.Action.Gas, Is.EqualTo((long)tx.GasLimit - 21000), "gas"); Assert.That(trace.Action.Value, Is.EqualTo(tx.Value), "value"); - Assert.That(trace.Action.Input, Is.EqualTo(tx.Data), "input"); + Assert.That(trace.Action.Input, Is.EqualTo(tx.Data.AsArray()), "input"); Assert.That(trace.Action.TraceAddress, Is.EqualTo(Array.Empty()), "trace address"); } diff --git a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs index 248f0855796..fc7459c9c56 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.IO; using System.Linq; using Nethermind.Core; @@ -39,9 +40,10 @@ private static long DataCost(Transaction transaction, IReleaseSpec releaseSpec) long dataCost = 0; if (transaction.Data is not null) { + Span data = transaction.Data.Value.Span; for (int i = 0; i < transaction.DataLength; i++) { - dataCost += transaction.Data[i] == 0 ? GasCostOf.TxDataZero : txDataNonZeroGasCost; + dataCost += data[i] == 0 ? GasCostOf.TxDataZero : txDataNonZeroGasCost; } } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs index b5300031b50..c24f907be56 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/ParityStyle/ParityLikeTxTracer.cs @@ -7,6 +7,7 @@ using FastEnumUtility; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Int256; namespace Nethermind.Evm.Tracing.ParityStyle @@ -255,7 +256,7 @@ public void MarkAsFailed(Address recipient, long gasSpent, byte[] output, string _trace.Action.From = _tx.SenderAddress; _trace.Action.To = _tx.To; _trace.Action.Value = _tx.Value; - _trace.Action.Input = _tx.Data; + _trace.Action.Input = _tx.Data.AsArray(); _trace.Action.Gas = _tx.GasLimit; _trace.Action.CallType = _tx.IsMessageCall ? "call" : "init"; _trace.Action.Error = error; diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index 4610042986e..056750345ce 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -7,6 +7,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using static Nethermind.Core.Extensions.MemoryExtensions; using Nethermind.Crypto; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.Tracing; @@ -148,8 +149,8 @@ private void Execute(Transaction transaction, BlockHeader block, ITxTracer txTra transaction.CalculateEffectiveGasPrice(spec.IsEip1559Enabled, block.BaseFeePerGas); long gasLimit = transaction.GasLimit; - byte[] machineCode = transaction.IsContractCreation ? transaction.Data : null; - byte[] data = transaction.IsMessageCall ? transaction.Data : Array.Empty(); + byte[] machineCode = transaction.IsContractCreation ? transaction.Data.AsArray() : null; + byte[] data = transaction.IsMessageCall ? transaction.Data.AsArray() : Array.Empty(); Address? caller = transaction.SenderAddress; if (_logger.IsTrace) _logger.Trace($"Executing tx {transaction.Hash}"); diff --git a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs index 91968829bff..3d744691df2 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/LogFinder.cs @@ -214,7 +214,7 @@ private IEnumerable FindLogsInBlock(LogFilter filter, Keccak blockHas private static IEnumerable FilterLogsInBlockLowMemoryAllocation(LogFilter filter, ref ReceiptsIterator iterator, CancellationToken cancellationToken) { List logList = null; - using (iterator) + try { long logIndexInBlock = 0; while (iterator.TryGetNext(out var receipt)) @@ -262,6 +262,11 @@ private static IEnumerable FilterLogsInBlockLowMemoryAllocation(LogFi } } } + finally + { + // Confusingly, the `using` statement causes the recovery context to be null during the dispose. + iterator.Dispose(); + } return logList ?? (IEnumerable)Array.Empty(); } diff --git a/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs b/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs index 4b993785ac3..697b11eb3d7 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/Models/CallTransactionModel.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Int256; +using static Nethermind.Core.Extensions.MemoryExtensions; namespace Nethermind.Facade.Proxy.Models { @@ -20,7 +21,7 @@ public static CallTransactionModel FromTransaction(Transaction transaction) { From = transaction.SenderAddress, To = transaction.To, - Data = transaction.Data, + Data = transaction.Data.AsArray(), Value = transaction.Value, Gas = (UInt256)transaction.GasLimit, GasPrice = transaction.GasPrice diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs index 9c6946c953f..277413e51d4 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockTree.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Nethermind.Api; using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; @@ -48,8 +49,13 @@ public Task Execute(CancellationToken cancellationToken) IChainLevelInfoRepository chainLevelInfoRepository = _set.ChainLevelInfoRepository = new ChainLevelInfoRepository(_get.DbProvider!.BlockInfosDb); + IBlockStore blockStore = new BlockStore(_get.DbProvider.BlocksDb); + IBlockTree blockTree = _set.BlockTree = new BlockTree( - _get.DbProvider, + blockStore, + _get.DbProvider.HeadersDb, + _get.DbProvider.BlockInfosDb, + _get.DbProvider.MetadataDb, chainLevelInfoRepository, _get.SpecProvider, bloomStorage, @@ -76,6 +82,7 @@ public Task Execute(CancellationToken cancellationToken) _get.SpecProvider!, receiptsRecovery, blockTree, + blockStore, receiptConfig, new ReceiptArrayStorageDecoder(receiptConfig.CompactReceiptStore)) : NullReceiptStorage.Instance; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs index f18b7b4e9c8..1981a60accf 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Eip2930Tests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Eip2930; +using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.Int256; using Nethermind.JsonRpc.Data; @@ -202,7 +203,8 @@ public void can_convert_from_Transaction_to_TransactionForRpc_and_back(TxType tx _transactionForRpc = new TransactionForRpc(_transaction); Transaction afterConversion = _transactionForRpc.ToTransaction(); - afterConversion.Should().BeEquivalentTo(_transaction); + afterConversion.Should().BeEquivalentTo(_transaction, option => option.ComparingByMembers().Excluding(tx => tx.Data)); + afterConversion.Data.AsArray().Should().BeEquivalentTo(_transaction.Data.AsArray()); } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs index 9d7d196d877..0303160e4f6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -4,6 +4,7 @@ using System.Linq; using FluentAssertions; using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; diff --git a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs index 8d4ffbaa063..bca581c00e0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Data/TransactionForRpc.cs @@ -27,7 +27,7 @@ public TransactionForRpc(Keccak? blockHash, long? blockNumber, int? txIndex, Tra Value = transaction.Value; GasPrice = transaction.GasPrice; Gas = transaction.GasLimit; - Input = Data = transaction.Data; + Input = Data = transaction.Data.AsArray(); if (transaction.Supports1559) { GasPrice = baseFee is not null @@ -139,6 +139,8 @@ public TransactionForRpc() public T ToTransaction(ulong? chainId = null) where T : Transaction, new() { + byte[]? data = Data ?? Input; + T tx = new() { GasLimit = Gas ?? 0, @@ -147,13 +149,18 @@ public TransactionForRpc() To = To, SenderAddress = From, Value = Value ?? 0, - Data = Data ?? Input, + Data = (Memory?)data, Type = Type, AccessList = TryGetAccessList(), ChainId = chainId, MaxFeePerDataGas = MaxFeePerDataGas, }; + if (data is null) + { + tx.Data = null; // Yes this is needed... really. Try a debugger. + } + if (tx.Supports1559) { tx.GasPrice = MaxPriorityFeePerGas ?? 0; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs index c63c1b811c8..0abbe5f5959 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Evm; using Newtonsoft.Json; @@ -56,7 +57,7 @@ public ParityTransaction(Transaction transaction, byte[] raw, PublicKey publicKe GasPrice = transaction.GasPrice; Gas = transaction.GasLimit; Raw = raw; - Input = transaction.Data; + Input = transaction.Data.AsArray(); PublicKey = publicKey; ChainId = transaction.Signature.ChainId; R = transaction.Signature.R; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index ae0a6c32812..7aa66f29ddf 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -1168,6 +1168,7 @@ public async Task ExecutionPayloadV1_set_and_get_transactions_roundtrip() txsReceived.Should().BeEquivalentTo(txsSource, options => options .Excluding(t => t.ChainId) + .Excluding(t => t.Data) .Excluding(t => t.SenderAddress) .Excluding(t => t.Timestamp) ); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs b/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs index 5973f1d84f7..efb907cb7bc 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/SerializerTester.cs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only +using System; using DotNetty.Buffers; using FluentAssertions; +using Nethermind.Core.Extensions; using Nethermind.Network.P2P.Messages; using Nethermind.Serialization.Rlp; using NUnit.Framework; @@ -22,7 +24,11 @@ public static void TestZero(IZeroMessageSerializer serializer, T message, T deserialized = serializer.Deserialize(buffer); // RlpLength is calculated explicitly when serializing an object by Calculate method. It's null after deserialization. - deserialized.Should().BeEquivalentTo(message, options => options.Excluding(c => c.Name == "RlpLength")); + deserialized.Should().BeEquivalentTo(message, options => options + .Excluding(c => c.Name == "RlpLength") + .Using>((context => context.Subject.AsArray().Should().BeEquivalentTo(context.Expectation.AsArray()))) + .WhenTypeIs>() + ); Assert.That(buffer.ReadableBytes, Is.EqualTo(0), "readable bytes"); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TransactionsMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TransactionsMessageSerializerTests.cs index 472fd6ec3b9..4dc46638f42 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TransactionsMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/TransactionsMessageSerializerTests.cs @@ -1,8 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Linq; +using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Network.P2P.Subprotocols.Eth.V62.Messages; diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTransactionsMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTransactionsMessageSerializerTests.cs index 52fa9a9ad4e..1030ad41b34 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTransactionsMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V65/PooledTransactionsMessageSerializerTests.cs @@ -1,8 +1,11 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Linq; +using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Network.P2P.Subprotocols.Eth.V65.Messages; diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/BlockBodiesMessageSerializerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/BlockBodiesMessageSerializerTests.cs index 3f09fc3a476..6d1a002ef08 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/BlockBodiesMessageSerializerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/BlockBodiesMessageSerializerTests.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Test; using Nethermind.Network.P2P.Subprotocols.Eth.V66.Messages; using Nethermind.Network.Test.P2P.Subprotocols.Eth.V62; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs index 425f84c664b..25c95355e5c 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/BlockDecoder.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using Nethermind.Core; @@ -246,5 +248,39 @@ public void Encode(RlpStream stream, Block? item, RlpBehaviors rlpBehaviors = Rl } } } + + public ReceiptRecoveryBlock? DecodeToReceiptRecoveryBlock(MemoryManager? memoryManager, Memory memory, RlpBehaviors rlpBehaviors) + { + Rlp.ValueDecoderContext decoderContext = new Rlp.ValueDecoderContext(memory, true); + + if (decoderContext.IsNextItemNull()) + { + decoderContext.ReadByte(); + return null; + } + + int sequenceLength = decoderContext.ReadSequenceLength(); + int blockCheck = decoderContext.Position + sequenceLength; + + BlockHeader header = Rlp.Decode(ref decoderContext); + + int contentLength = decoderContext.ReadSequenceLength(); + int transactionCount = decoderContext.PeekNumberOfItemsRemaining(decoderContext.Position + contentLength); + + Memory transactionMemory = decoderContext.ReadMemory(contentLength); + + decoderContext.SkipItem(); // Skip uncles + + if (decoderContext.Position != blockCheck) + { + decoderContext.SkipItem(); // Skip withdrawals + } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(blockCheck); + } + + return new ReceiptRecoveryBlock(memoryManager, header, transactionMemory, transactionCount); + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptRecoveryBlock.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptRecoveryBlock.cs new file mode 100644 index 00000000000..76d7ac991c8 --- /dev/null +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptRecoveryBlock.cs @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers; +using System.Diagnostics; +using Nethermind.Core; +using Nethermind.Core.Crypto; + +namespace Nethermind.Serialization.Rlp; + +/// +/// A Block specifically for receipt recovery. Does not contain any of the fields that are not needed for that. +/// Retain span from DB as memory and must be explicitly disposed. +/// +[DebuggerDisplay("{Hash} ({Number})")] +public struct ReceiptRecoveryBlock +{ + private readonly MemoryManager? _memoryOwner; // Can be null if loaded without span + private readonly Memory _transactionData; + private int _currentTransactionPosition = 0; + + private readonly Transaction[]? _transactions = null; + private int _currentTransactionIndex = 0; + + public BlockHeader Header { get; } + public int TransactionCount { get; } + + // Use a buffer to avoid reallocation. Surprisingly significant. May produce incorrect transaction, but for recovery, it is correct. + private Transaction? _txBuffer; + + public ReceiptRecoveryBlock(Block block) + { + Header = block.Header; + _transactions = block.Transactions; + TransactionCount = _transactions.Length; + } + + public ReceiptRecoveryBlock(MemoryManager? memoryOwner, BlockHeader header, Memory transactionData, int transactionCount) + { + Header = header; + _memoryOwner = memoryOwner; + _transactionData = transactionData; + TransactionCount = transactionCount; + } + + public Transaction GetNextTransaction() + { + if (_transactions != null) + { + return _transactions[_currentTransactionIndex++]; + } + + Rlp.ValueDecoderContext decoderContext = new(_transactionData, true); + decoderContext.Position = _currentTransactionPosition; + TxDecoder.InstanceWithoutLazyHash.Decode(ref decoderContext, ref _txBuffer, RlpBehaviors.AllowUnsigned); + _currentTransactionPosition = decoderContext.Position; + + return _txBuffer; + } + + public Keccak? Hash => Header.Hash; // do not add setter here + public long Number => Header.Number; // do not add setter here + + public void Dispose() + { + ((IMemoryOwner?)_memoryOwner)?.Dispose(); + } +} diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index a23bc5fce7c..a73a0e65da4 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -544,6 +544,21 @@ public ValueDecoderContext(scoped in Span data) Position = 0; } + public ValueDecoderContext(Memory memory, bool sliceMemory = false) + { + Memory = memory; + Data = memory.Span; + Position = 0; + + // Slice memory is turned off by default. Because if you are not careful and being explicit about it, + // you can end up with a memory leak. + _sliceMemory = sliceMemory; + } + + public Memory? Memory { get; } + + private bool _sliceMemory = false; + public Span Data { get; } public bool IsEmpty => Data.IsEmpty; @@ -748,6 +763,19 @@ public Span Read(int length) return data; } + public Memory ReadMemory(int length) + { + if (_sliceMemory && Memory.HasValue) return ReadSlicedMemory(length); + return Read(length).ToArray(); + } + + private Memory ReadSlicedMemory(int length) + { + Memory data = Memory.Value.Slice(Position, length); + Position += length; + return data; + } + public void Check(int nextCheck) { if (Position != nextCheck) @@ -1124,6 +1152,68 @@ public Span DecodeByteArraySpan(bool allowLeadingZeroBytes = true) throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); } + public Memory? DecodeByteArrayMemory(bool allowLeadingZeroBytes = true) + { + if (!_sliceMemory) + { + return DecodeByteArraySpan().ToArray(); + } + + if (Memory == null) + { + throw new RlpException("Rlp not backed by a Memory"); + } + + int prefix = ReadByte(); + if (!allowLeadingZeroBytes && prefix == 0) + { + throw new RlpException($"Non-canonical ulong (leading zero bytes) at position {Position}"); + } + + if (prefix < 128) + { + return Memory.Value.Slice(Position - 1, 1); + } + + if (prefix == 128) + { + return Array.Empty(); + } + + if (prefix <= 183) + { + int length = prefix - 128; + Memory buffer = ReadSlicedMemory(length); + Span asSpan = buffer.Span; + if (length == 1 && asSpan[0] < 128) + { + throw new RlpException($"Unexpected byte value {asSpan[0]}"); + } + + return buffer; + } + + if (prefix < 192) + { + int lengthOfLength = prefix - 183; + if (lengthOfLength > 4) + { + // strange but needed to pass tests - seems that spec gives int64 length and tests int32 length + throw new RlpException("Expected length of lenth less or equal 4"); + } + + int length = DeserializeLength(lengthOfLength); + if (length < 56) + { + throw new RlpException("Expected length greater or equal 56 and was {length}"); + } + + return ReadSlicedMemory(length); + } + + throw new RlpException($"Unexpected prefix value of {prefix} when decoding a byte array."); + } + public void SkipItem() { (int prefix, int content) = PeekPrefixAndContentLength(); @@ -1489,6 +1579,11 @@ public static int LengthOf(byte[]? array) return LengthOf(array.AsSpan()); } + public static int LengthOf(Memory? memory) + { + return LengthOf(memory.GetValueOrDefault().Span); + } + public static int LengthOf(IReadOnlyList array) { if (array.Count == 0) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs index c77a311d255..c002067ad93 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpStream.cs @@ -537,6 +537,17 @@ public void Encode(byte[] input) Encode(input.AsSpan()); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Encode(Memory? input) + { + if (input is null) + { + WriteByte(EmptyArrayByte); + return; + } + Encode(input.Value.Span); + } + public void Encode(Span input) { if (input.IsEmpty) diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs index 8d1402c6047..c017ffdd8ba 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoder.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.IO; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -13,6 +12,16 @@ namespace Nethermind.Serialization.Rlp public class TxDecoder : TxDecoder, ITransactionSizeCalculator { public const int MaxDelayedHashTxnSize = 32768; + public static TxDecoder Instance = new TxDecoder(); + public static TxDecoder InstanceWithoutLazyHash = new TxDecoder(false); + + public TxDecoder() : base(true) // Rlp will try to find empty constructor. + { + } + + public TxDecoder(bool lazyHash) : base(lazyHash) + { + } public int GetLength(Transaction tx) { @@ -28,6 +37,12 @@ public class TxDecoder : where T : Transaction, new() { private readonly AccessListDecoder _accessListDecoder = new(); + private bool _lazyHash; + + protected TxDecoder(bool lazyHash = true) + { + _lazyHash = lazyHash; + } public T? Decode(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -88,7 +103,7 @@ public class TxDecoder : rlpStream.Check(lastCheck); } - if (transactionSequence.Length <= TxDecoder.MaxDelayedHashTxnSize) + if (transactionSequence.Length <= TxDecoder.MaxDelayedHashTxnSize && _lazyHash) { // Delay hash generation, as may be filtered as having too low gas etc transaction.SetPreHash(transactionSequence); @@ -137,14 +152,14 @@ private void DecodeEip1559PayloadWithoutSig(T transaction, RlpStream rlpStream, transaction.AccessList = _accessListDecoder.Decode(rlpStream, rlpBehaviors); } - private void DecodeLegacyPayloadWithoutSig(T transaction, ref Rlp.ValueDecoderContext decoderContext) + private void DecodeLegacyPayloadWithoutSig(T transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors) { transaction.Nonce = decoderContext.DecodeUInt256(allowLeadingZeroBytes: false); transaction.GasPrice = decoderContext.DecodeUInt256(allowLeadingZeroBytes: false); transaction.GasLimit = decoderContext.DecodeLong(allowLeadingZeroBytes: false); transaction.To = decoderContext.DecodeAddress(); transaction.Value = decoderContext.DecodeUInt256(allowLeadingZeroBytes: false); - transaction.Data = decoderContext.DecodeByteArray(); + transaction.Data = decoderContext.DecodeByteArrayMemory(); } private void DecodeAccessListPayloadWithoutSig(T transaction, ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors) @@ -155,7 +170,7 @@ private void DecodeAccessListPayloadWithoutSig(T transaction, ref Rlp.ValueDecod transaction.GasLimit = decoderContext.DecodeLong(allowLeadingZeroBytes: false); transaction.To = decoderContext.DecodeAddress(); transaction.Value = decoderContext.DecodeUInt256(allowLeadingZeroBytes: false); - transaction.Data = decoderContext.DecodeByteArray(); + transaction.Data = decoderContext.DecodeByteArrayMemory(); transaction.AccessList = _accessListDecoder.Decode(ref decoderContext, rlpBehaviors); } @@ -168,7 +183,7 @@ private void DecodeEip1559PayloadWithoutSig(T transaction, ref Rlp.ValueDecoderC transaction.GasLimit = decoderContext.DecodeLong(allowLeadingZeroBytes: false); transaction.To = decoderContext.DecodeAddress(); transaction.Value = decoderContext.DecodeUInt256(allowLeadingZeroBytes: false); - transaction.Data = decoderContext.DecodeByteArray(); + transaction.Data = decoderContext.DecodeByteArrayMemory(); transaction.AccessList = _accessListDecoder.Decode(ref decoderContext, rlpBehaviors); } @@ -212,16 +227,32 @@ private void EncodeEip1559PayloadWithoutPayload(T item, RlpStream stream, RlpBeh public T? Decode(ref Rlp.ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors = RlpBehaviors.None) + + { + T transaction = null; + Decode(ref decoderContext, ref transaction, rlpBehaviors); + + return transaction; + } + + + public void Decode(ref Rlp.ValueDecoderContext decoderContext, ref T? transaction, + RlpBehaviors rlpBehaviors = RlpBehaviors.None) { if (decoderContext.IsNextItemNull()) { decoderContext.ReadByte(); - return null; + transaction = null; + return; } - Span transactionSequence = decoderContext.PeekNextItem(); + if (transaction == null) + { + transaction = new(); + } + transaction.Type = TxType.Legacy; - T transaction = new(); + Span transactionSequence = decoderContext.PeekNextItem(); if ((rlpBehaviors & RlpBehaviors.SkipTypedWrapping) == RlpBehaviors.SkipTypedWrapping) { byte firstByte = decoderContext.PeekByte(); @@ -248,7 +279,7 @@ private void EncodeEip1559PayloadWithoutPayload(T item, RlpStream stream, RlpBeh switch (transaction.Type) { case TxType.Legacy: - DecodeLegacyPayloadWithoutSig(transaction, ref decoderContext); + DecodeLegacyPayloadWithoutSig(transaction, ref decoderContext, rlpBehaviors); break; case TxType.AccessList: DecodeAccessListPayloadWithoutSig(transaction, ref decoderContext, rlpBehaviors); @@ -270,7 +301,7 @@ private void EncodeEip1559PayloadWithoutPayload(T item, RlpStream stream, RlpBeh decoderContext.Check(lastCheck); } - if (transactionSequence.Length <= TxDecoder.MaxDelayedHashTxnSize) + if (transactionSequence.Length <= TxDecoder.MaxDelayedHashTxnSize && _lazyHash) { // Delay hash generation, as may be filtered as having too low gas etc transaction.SetPreHash(transactionSequence); @@ -280,7 +311,6 @@ private void EncodeEip1559PayloadWithoutPayload(T item, RlpStream stream, RlpBeh // Just calculate the Hash immediately as txn too large transaction.Hash = Keccak.Compute(transactionSequence); } - return transaction; } private static void DecodeSignature( diff --git a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs index 63604449679..7729c797877 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/ReceiptStorageTests.cs @@ -3,6 +3,7 @@ using FluentAssertions; using Nethermind.Blockchain; +using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Core; @@ -28,6 +29,7 @@ public class ReceiptStorageTests private IReceiptFinder _receiptFinder; private IReceiptStorage _inMemoryStorage; private IBlockTree _blockTree; + private IBlockStore _blockStore; public ReceiptStorageTests(bool useEip2718) { @@ -42,12 +44,14 @@ public void Setup() _blockTree = Build.A.BlockTree() .WithBlocks(Build.A.Block.TestObject) .TestObject; + _blockStore = Substitute.For(); ReceiptsRecovery receiptsRecovery = new(_ethereumEcdsa, _specProvider); _persistentStorage = new PersistentReceiptStorage( new MemColumnsDb(), _specProvider, receiptsRecovery, _blockTree, + _blockStore, new ReceiptConfig() ); _receiptFinder = new FullInfoReceiptFinder(_persistentStorage, receiptsRecovery, Substitute.For());