From 1a48fd6ed699aab43bb5f471c24df6e7382ecc13 Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 21 Nov 2024 12:27:12 +0100 Subject: [PATCH 01/18] start --- .../Nethermind.TxPool/AcceptTxResult.cs | 4 ++ .../Filters/DelegatedAccountFilter.cs | 29 +++++++++++++ .../OnlyOneTxPerDelegatedAccountFilter.cs | 35 +++++++++++++++ .../Filters/OnlyOneTxPerDelegatedEOA.cs | 35 +++++++++++++++ src/Nethermind/Nethermind.TxPool/TxPool.cs | 43 +++++++++++++++++-- 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs create mode 100644 src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs create mode 100644 src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs diff --git a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs index f8a0033dfe7..582dddc1066 100644 --- a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs +++ b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs @@ -89,6 +89,10 @@ namespace Nethermind.TxPool /// Ignores transactions if tx type is not supported /// public static readonly AcceptTxResult NotSupportedTxType = new(15, nameof(NotSupportedTxType)); + /// + /// Only one tx is allowed per delegated account. + /// + public static readonly AcceptTxResult OnlyOneTxPerDelegatedAccount = new(16, nameof(OnlyOneTxPerDelegatedAccount)); private int Id { get; } private string Code { get; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs new file mode 100644 index 00000000000..c74811200d6 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Specs; +using Nethermind.State; +using Nethermind.TxPool.Collections; +using System.Collections; + +namespace Nethermind.TxPool.Filters; +internal sealed class DelegatedAccountFilter( + IChainHeadSpecProvider specProvider, + IDictionary pendingDelegations + ) + : IIncomingTxFilter +{ + + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) + { + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + if (!spec.IsEip7702Enabled) + return AcceptTxResult.Accepted; + + + return AcceptTxResult.Accepted; + } +} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs new file mode 100644 index 00000000000..c503fdf8243 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -0,0 +1,35 @@ +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; +using Nethermind.TxPool.Collections; + +namespace Nethermind.TxPool.Filters +{ + internal sealed class OnlyOneTxPerDelegatedAccountFilter( + IChainHeadSpecProvider specProvider, + TxDistinctSortedPool standardPool, + TxDistinctSortedPool blobPool, + IReadOnlyStateProvider worldState, + ICodeInfoRepository codeInfoRepository + ) : IIncomingTxFilter + { + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) + { + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + if (!spec.IsEip7702Enabled + || !tx.HasAuthorizationList) + return AcceptTxResult.Accepted; + + if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) + return AcceptTxResult.Accepted; + + if (standardPool.ContainsBucket(tx.SenderAddress!) || blobPool.ContainsBucket(tx.SenderAddress!)) + { + return AcceptTxResult.OnlyOneTxPerDelegatedAccount; + + } + return AcceptTxResult.Accepted; + } + } +} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs new file mode 100644 index 00000000000..1541e6da3a4 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs @@ -0,0 +1,35 @@ +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.State; +using Nethermind.TxPool.Collections; + +namespace Nethermind.TxPool.Filters +{ + internal sealed class OnlyOneTxPerDelegatedEOA( + IChainHeadSpecProvider specProvider, + TxDistinctSortedPool standardPool, + TxDistinctSortedPool blobPool, + IReadOnlyStateProvider worldState, + ICodeInfoRepository codeInfoRepository + ) : IIncomingTxFilter + { + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) + { + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + if (!spec.IsEip7702Enabled + || !tx.HasAuthorizationList) + return AcceptTxResult.Accepted; + + if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) + return AcceptTxResult.Accepted; + + if (standardPool.ContainsBucket(tx.SenderAddress!) || blobPool.ContainsBucket(tx.SenderAddress!)) + { + return AcceptTxResult.OnlyOneTxPerDelegatedAccount; + + } + return AcceptTxResult.Accepted; + } + } +} diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index e9786211d4b..43ee57b3ad4 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -9,6 +10,7 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; +using Microsoft.AspNetCore.DataProtection.KeyManagement; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Collections; @@ -51,6 +53,8 @@ public class TxPool : ITxPool, IDisposable private readonly IChainHeadInfoProvider _headInfo; private readonly ITxPoolConfig _txPoolConfig; private readonly bool _blobReorgsSupportEnabled; + private readonly ConcurrentDictionary _pendingDelegations = new(); + private readonly ILogger _logger; @@ -120,7 +124,8 @@ public TxPool(IEthereumEcdsa ecdsa, _blobTransactions = txPoolConfig.BlobsSupport.IsPersistentStorage() ? new PersistentBlobTxDistinctSortedPool(blobTxStorage, _txPoolConfig, comparer, logManager) : new BlobTxDistinctSortedPool(txPoolConfig.BlobsSupport == BlobsSupportMode.InMemory ? _txPoolConfig.InMemoryBlobPoolSize : 0, comparer, logManager); - if (_blobTransactions.Count > 0) _blobTransactions.UpdatePool(_accounts, _updateBucket); + if (_blobTransactions.Count > 0) + _blobTransactions.UpdatePool(_accounts, _updateBucket); _headInfo.HeadChanged += OnHeadChange; @@ -145,7 +150,9 @@ public TxPool(IEthereumEcdsa ecdsa, new LowNonceFilter(_logger), // has to be after UnknownSenderFilter as it uses sender new FutureNonceFilter(txPoolConfig), new GapNonceFilter(_transactions, _blobTransactions, _logger), - new RecoverAuthorityFilter(ecdsa) + new RecoverAuthorityFilter(ecdsa), + new OnlyOneTxPerDelegatedAccountFilter(_specProvider, _transactions, _blobTransactions, chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository ), + new DelegatedAccountFilter(_specProvider, _pendingDelegations), ]; if (incomingTxFilter is not null) @@ -405,7 +412,6 @@ public void RemovePeer(PublicKey nodeId) if (_logger.IsTrace) _logger.Trace($"Removed a peer from TX pool: {nodeId}"); } } - public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions) { Metrics.PendingTransactionsReceived++; @@ -437,6 +443,13 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions accepted = AddCore(tx, ref state, startBroadcast); if (accepted) { + if (tx.HasAuthorizationList) + { + foreach (var auth in tx.AuthorizationList) + { + IncrementDelegationCount(auth.Authority!, true); + } + } // Clear proper snapshot if (tx.SupportsBlobs) _blobTransactionSnapshot = null; @@ -672,6 +685,14 @@ public bool RemoveTransaction(Hash256? hash) if (hasBeenRemoved) { RemovedPending?.Invoke(this, new TxEventArgs(transaction)); + + if (transaction.HasAuthorizationList) + { + foreach (var auth in transaction.AuthorizationList) + { + IncrementDelegationCount(auth.Authority!, false); + } + } } _broadcaster.StopBroadcast(hash); @@ -792,6 +813,21 @@ internal void ResetAddress(Address address) _accountCache.RemoveAccounts(arrayPoolList); } + private void IncrementDelegationCount(AddressAsKey key, bool increment) + { + int value = increment ? 1 : -1; + var lastCount = _pendingDelegations.AddOrUpdate(key, + (k) => 1, + (k, c) => c + value); + + if (lastCount == 0) + { + //Remove() is threadsafe and only removes if the count is the same as the updated one + ((ICollection>)_pendingDelegations).Remove( + new KeyValuePair(key, lastCount)); + } + } + private sealed class AccountCache : IAccountStateProvider { private readonly IAccountStateProvider _provider; @@ -929,3 +965,4 @@ private static void WriteTxPoolReport(in ILogger logger) } } } + From 64c0936011d1172bc7844fea7b5ce6c8ea8a3dc9 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 25 Nov 2024 21:31:38 +0100 Subject: [PATCH 02/18] reject if pending delegation --- .../Nethermind.TxPool/AcceptTxResult.cs | 5 +++ .../Filters/DelegatedAccountFilter.cs | 8 ++--- .../Filters/OnlyOneTxPerDelegatedEOA.cs | 35 ------------------- src/Nethermind/Nethermind.TxPool/TxPool.cs | 2 +- 4 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs diff --git a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs index 582dddc1066..834a8e76f6a 100644 --- a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs +++ b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs @@ -93,6 +93,11 @@ namespace Nethermind.TxPool /// Only one tx is allowed per delegated account. /// public static readonly AcceptTxResult OnlyOneTxPerDelegatedAccount = new(16, nameof(OnlyOneTxPerDelegatedAccount)); + /// + /// There is a pending delegation in the tx pool already + /// + public static readonly AcceptTxResult PendingDelegation = new(17, nameof(PendingDelegation)); + private int Id { get; } private string Code { get; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs index c74811200d6..491b567d579 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs @@ -3,10 +3,6 @@ using Nethermind.Core; using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.Specs; -using Nethermind.State; -using Nethermind.TxPool.Collections; using System.Collections; namespace Nethermind.TxPool.Filters; @@ -23,6 +19,10 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!spec.IsEip7702Enabled) return AcceptTxResult.Accepted; + if (pendingDelegations.Contains(tx.SenderAddress!)) + { + return AcceptTxResult.PendingDelegation; + } return AcceptTxResult.Accepted; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs deleted file mode 100644 index 1541e6da3a4..00000000000 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedEOA.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Nethermind.Core; -using Nethermind.Core.Specs; -using Nethermind.Evm; -using Nethermind.State; -using Nethermind.TxPool.Collections; - -namespace Nethermind.TxPool.Filters -{ - internal sealed class OnlyOneTxPerDelegatedEOA( - IChainHeadSpecProvider specProvider, - TxDistinctSortedPool standardPool, - TxDistinctSortedPool blobPool, - IReadOnlyStateProvider worldState, - ICodeInfoRepository codeInfoRepository - ) : IIncomingTxFilter - { - public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) - { - IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); - if (!spec.IsEip7702Enabled - || !tx.HasAuthorizationList) - return AcceptTxResult.Accepted; - - if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) - return AcceptTxResult.Accepted; - - if (standardPool.ContainsBucket(tx.SenderAddress!) || blobPool.ContainsBucket(tx.SenderAddress!)) - { - return AcceptTxResult.OnlyOneTxPerDelegatedAccount; - - } - return AcceptTxResult.Accepted; - } - } -} diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 43ee57b3ad4..abfd7dbe86a 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -53,7 +53,7 @@ public class TxPool : ITxPool, IDisposable private readonly IChainHeadInfoProvider _headInfo; private readonly ITxPoolConfig _txPoolConfig; private readonly bool _blobReorgsSupportEnabled; - private readonly ConcurrentDictionary _pendingDelegations = new(); + private readonly ConcurrentDictionary _pendingDelegations = new(); private readonly ILogger _logger; From e7df773f1d9595441d5181d70bd9d0d4f4ef0629 Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 26 Nov 2024 23:22:28 +0100 Subject: [PATCH 03/18] unit test --- .../Nethermind.TxPool.Test/TxPoolTests.cs | 130 ++++++++++++++++++ .../Filters/DelegatedAccountFilter.cs | 2 +- .../Filters/MalformedTxFilter.cs | 3 +- .../OnlyOneTxPerDelegatedAccountFilter.cs | 4 +- 4 files changed, 134 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 61f3fd989a4..b64cea66ee8 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -23,6 +23,7 @@ using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Serialization.Rlp; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; @@ -1729,6 +1730,135 @@ public void SubmitTx_CodeIsNotDelegationAndDelegation_DelegationIsAccepted((byte result.Should().Be(testCase.expected); } + [Test] + public void Delegated_account_can_only_have_one_tx() + { + ISpecProvider specProvider = GetPragueSpecProvider(); + TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; + _txPool = CreatePool(txPoolConfig, specProvider); + + PrivateKey signer = TestItem.PrivateKeyA; + _stateProvider.CreateAccount(signer.Address, UInt256.MaxValue); + byte[] delegation = [.. Eip7702Constants.DelegationHeader, .. TestItem.AddressC.Bytes]; + _stateProvider.InsertCode(signer.Address, delegation.AsMemory(), Prague.Instance); + + Transaction firstTx = Build.A.Transaction + .WithNonce(0) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction ) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(AcceptTxResult.Accepted); + + Transaction secondTx = Build.A.Transaction + .WithNonce(1) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.OnlyOneTxPerDelegatedAccount); + } + + [Test] + public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delegation_removal() + { + ISpecProvider specProvider = GetPragueSpecProvider(); + TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; + _txPool = CreatePool(txPoolConfig, specProvider); + + PrivateKey signer = TestItem.PrivateKeyA; + _stateProvider.CreateAccount(signer.Address, UInt256.MaxValue); + + EthereumEcdsa ecdsa = new EthereumEcdsa(_specProvider.ChainId); + + Transaction firstTx = Build.A.Transaction + .WithNonce(0) + .WithType(TxType.SetCode) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithAuthorizationCode(ecdsa.Sign(signer, specProvider.ChainId, TestItem.AddressC, 0)) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(AcceptTxResult.Accepted); + + Transaction secondTx = Build.A.Transaction + .WithNonce(1) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.PendingDelegation); + + _txPool.RemoveTransaction(firstTx.Hash); + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.AlreadyKnown); + } + + + [Test] + public void Test() + { + ISpecProvider specProvider = GetPragueSpecProvider(); + TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; + _txPool = CreatePool(txPoolConfig, specProvider); + + PrivateKey signer = TestItem.PrivateKeyA; + _stateProvider.CreateAccount(signer.Address, UInt256.MaxValue); + + EthereumEcdsa ecdsa = new EthereumEcdsa(_specProvider.ChainId); + + Transaction firstTx = Build.A.Transaction + .WithNonce(0) + .WithType(TxType.SetCode) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(100_000) + .WithAuthorizationCode(ecdsa.Sign(signer, specProvider.ChainId, TestItem.AddressC, 0)) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(AcceptTxResult.Accepted); + + Transaction secondTx = Build.A.Transaction + .WithNonce(1) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.PendingDelegation); + + _txPool.RemoveTransaction(firstTx.Hash); + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.AlreadyKnown); + } + private IDictionary GetPeers(int limit = 100) { var peers = new Dictionary(); diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs index 491b567d579..09db886e5f9 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs @@ -19,7 +19,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!spec.IsEip7702Enabled) return AcceptTxResult.Accepted; - if (pendingDelegations.Contains(tx.SenderAddress!)) + if (pendingDelegations.Contains(new AddressAsKey(tx.SenderAddress!))) { return AcceptTxResult.PendingDelegation; } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs index 52f0753471d..4baeef28abe 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/MalformedTxFilter.cs @@ -19,7 +19,8 @@ internal sealed class MalformedTxFilter( public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); - if (!txValidator.IsWellFormed(tx, spec)) + ValidationResult result = txValidator.IsWellFormed(tx, spec); + if (!result) { Metrics.PendingTransactionsMalformed++; // It may happen that other nodes send us transactions that were signed for another chain or don't have enough gas. diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index c503fdf8243..3696dd4f916 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -17,8 +17,7 @@ ICodeInfoRepository codeInfoRepository public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); - if (!spec.IsEip7702Enabled - || !tx.HasAuthorizationList) + if (!spec.IsEip7702Enabled) return AcceptTxResult.Accepted; if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) @@ -27,7 +26,6 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (standardPool.ContainsBucket(tx.SenderAddress!) || blobPool.ContainsBucket(tx.SenderAddress!)) { return AcceptTxResult.OnlyOneTxPerDelegatedAccount; - } return AcceptTxResult.Accepted; } From 7be934f961f6f135b8d247d3f68da8ef1028d657 Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 28 Nov 2024 18:14:29 +0100 Subject: [PATCH 04/18] remove --- .../Nethermind.TxPool.Test/TxPoolTests.cs | 1 - .../Filters/DelegatedAccountFilter.cs | 29 ------------------- .../OnlyOneTxPerDelegatedAccountFilter.cs | 12 ++++++-- src/Nethermind/Nethermind.TxPool/TxPool.cs | 12 ++++++-- 4 files changed, 19 insertions(+), 35 deletions(-) delete mode 100644 src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index b64cea66ee8..a66cfd68549 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -23,7 +23,6 @@ using Nethermind.Evm; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; diff --git a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs deleted file mode 100644 index 09db886e5f9..00000000000 --- a/src/Nethermind/Nethermind.TxPool/Filters/DelegatedAccountFilter.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core; -using Nethermind.Core.Specs; -using System.Collections; - -namespace Nethermind.TxPool.Filters; -internal sealed class DelegatedAccountFilter( - IChainHeadSpecProvider specProvider, - IDictionary pendingDelegations - ) - : IIncomingTxFilter -{ - - public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) - { - IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); - if (!spec.IsEip7702Enabled) - return AcceptTxResult.Accepted; - - if (pendingDelegations.Contains(new AddressAsKey(tx.SenderAddress!))) - { - return AcceptTxResult.PendingDelegation; - } - - return AcceptTxResult.Accepted; - } -} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 3696dd4f916..fd0d12b8407 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -22,9 +22,17 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; - - if (standardPool.ContainsBucket(tx.SenderAddress!) || blobPool.ContainsBucket(tx.SenderAddress!)) + Transaction[] currentTx; + if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTx) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTx)) { + foreach (Transaction t in currentTx) + { + if (t.Nonce == tx.Nonce) + { + //This is a replacement tx so accept it, and let the comparers check for correct replacement rules + return AcceptTxResult.Accepted; + } + } return AcceptTxResult.OnlyOneTxPerDelegatedAccount; } return AcceptTxResult.Accepted; diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index abfd7dbe86a..5400de3d13c 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using Microsoft.AspNetCore.DataProtection.KeyManagement; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Collections; @@ -152,7 +151,6 @@ public TxPool(IEthereumEcdsa ecdsa, new GapNonceFilter(_transactions, _blobTransactions, _logger), new RecoverAuthorityFilter(ecdsa), new OnlyOneTxPerDelegatedAccountFilter(_specProvider, _transactions, _blobTransactions, chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository ), - new DelegatedAccountFilter(_specProvider, _pendingDelegations), ]; if (incomingTxFilter is not null) @@ -346,6 +344,14 @@ private void RemoveProcessedTransactions(Block block) if (blockTx.Type == TxType.SetCode) { eip7702Txs++; + + if (blockTx.HasAuthorizationList) + { + foreach (AuthorizationTuple tuple in blockTx.AuthorizationList) + { + + } + } } if (!IsKnown(txHash)) @@ -498,7 +504,7 @@ private AcceptTxResult AddCore(Transaction tx, ref TxFilteringState state, bool : worstTx.GasBottleneck; bool inserted = relevantPool.TryInsert(tx.Hash!, tx, out Transaction? removed); - + if (!inserted) { // it means it failed on adding to the pool - it is possible when new tx has the same sender From edb1c2124edcbbdbe49079580ab8ffb72ec8fe5e Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 19 Dec 2024 17:27:43 +0100 Subject: [PATCH 05/18] combine address and nonce --- .../Filters/OnlyOneTxPerDelegatedAccountFilter.cs | 8 ++++---- src/Nethermind/Nethermind.TxPool/TxPool.cs | 13 ++++++++----- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index fd0d12b8407..15a987735da 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -22,12 +22,12 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; - Transaction[] currentTx; - if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTx) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTx)) + Transaction[] currentTxs; + if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) { - foreach (Transaction t in currentTx) + foreach (Transaction existingTx in currentTxs) { - if (t.Nonce == tx.Nonce) + if (existingTx.Nonce == tx.Nonce) { //This is a replacement tx so accept it, and let the comparers check for correct replacement rules return AcceptTxResult.Accepted; diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 3c09bdf27cd..9cc18e42b53 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -52,7 +52,7 @@ public class TxPool : ITxPool, IDisposable private readonly IChainHeadInfoProvider _headInfo; private readonly ITxPoolConfig _txPoolConfig; private readonly bool _blobReorgsSupportEnabled; - private readonly ConcurrentDictionary _pendingDelegations = new(); + private readonly ConcurrentDictionary _pendingDelegations = new(); private readonly ILogger _logger; @@ -460,7 +460,7 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions { foreach (var auth in tx.AuthorizationList) { - IncrementDelegationCount(auth.Authority!, true); + IncrementDelegationCount(auth.Authority!, auth.Nonce, true); } } // Clear proper snapshot @@ -703,7 +703,7 @@ public bool RemoveTransaction(Hash256? hash) { foreach (var auth in transaction.AuthorizationList) { - IncrementDelegationCount(auth.Authority!, false); + IncrementDelegationCount(auth.Authority!, auth.Nonce, false); } } } @@ -826,10 +826,13 @@ internal void ResetAddress(Address address) _accountCache.RemoveAccounts(arrayPoolList); } - private void IncrementDelegationCount(AddressAsKey key, bool increment) + private void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool increment) { + UInt256 addressPlusNonce = new (key.Value.Bytes); + addressPlusNonce += nonce; + int value = increment ? 1 : -1; - var lastCount = _pendingDelegations.AddOrUpdate(key, + var lastCount = _pendingDelegations.AddOrUpdate(addressPlusNonce, (k) => 1, (k, c) => c + value); From 775dda6d2cb22304b06fbbeb2ea65945a0783500 Mon Sep 17 00:00:00 2001 From: ak88 Date: Sun, 29 Dec 2024 18:34:46 +0100 Subject: [PATCH 06/18] combine nonce and address --- src/Nethermind/Nethermind.TxPool/TxPool.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 9cc18e42b53..5496a8ac94a 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -828,12 +828,19 @@ internal void ResetAddress(Address address) private void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool increment) { + //A nonce cannot exceed 2^64-1 and an address is 20 bytes, so we can pack them together in one u256 UInt256 addressPlusNonce = new (key.Value.Bytes); + nonce <<= 64 * 3; addressPlusNonce += nonce; - + int value = increment ? 1 : -1; var lastCount = _pendingDelegations.AddOrUpdate(addressPlusNonce, - (k) => 1, + (k) => + { + if (increment) + return 1; + return 0; + }, (k, c) => c + value); if (lastCount == 0) From 1f8fa3969ea0618865a0df62750dddf387d21921 Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 31 Dec 2024 16:13:37 +0100 Subject: [PATCH 07/18] delegation cache --- .../Nethermind.TxPool/DelegationCache.cs | 53 +++++++++++++++++++ .../OnlyOneTxPerDelegatedAccountFilter.cs | 12 ++++- src/Nethermind/Nethermind.TxPool/TxPool.cs | 32 ++--------- 3 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 src/Nethermind/Nethermind.TxPool/DelegationCache.cs diff --git a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs new file mode 100644 index 00000000000..87724635ee4 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.TxPool; +internal sealed class DelegationCache +{ + private readonly ConcurrentDictionary _pendingDelegations = new(); + + public bool HasPending(AddressAsKey key, UInt256 nonce) + { + return _pendingDelegations.ContainsKey(KeyMask(key, nonce)); + } + + public void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool increment) + { + UInt256 addressPlusNonce = KeyMask(key, nonce); + + int value = increment ? 1 : -1; + var lastCount = _pendingDelegations.AddOrUpdate(addressPlusNonce, + (k) => + { + if (increment) + return 1; + return 0; + }, + (k, c) => c + value); + + if (lastCount == 0) + { + //Remove() is threadsafe and only removes if the count is the same as the updated one + ((ICollection>)_pendingDelegations).Remove( + new KeyValuePair(key, lastCount)); + } + } + + private static UInt256 KeyMask(AddressAsKey key, UInt256 nonce) + { + //A nonce cannot exceed 2^64-1 and an address is 20 bytes, so we can pack them together in one u256 + UInt256 addressPlusNonce = new(key.Value.Bytes); + nonce <<= 64 * 3; + addressPlusNonce += nonce; + return addressPlusNonce; + } +} diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 15a987735da..01dcce028d5 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -1,8 +1,10 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm; +using Nethermind.Int256; using Nethermind.State; using Nethermind.TxPool.Collections; +using System.Collections.Concurrent; namespace Nethermind.TxPool.Filters { @@ -11,8 +13,8 @@ internal sealed class OnlyOneTxPerDelegatedAccountFilter( TxDistinctSortedPool standardPool, TxDistinctSortedPool blobPool, IReadOnlyStateProvider worldState, - ICodeInfoRepository codeInfoRepository - ) : IIncomingTxFilter + ICodeInfoRepository codeInfoRepository, + DelegationCache pendingDelegations) : IIncomingTxFilter { public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) { @@ -20,9 +22,15 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!spec.IsEip7702Enabled) return AcceptTxResult.Accepted; + if (pendingDelegations.HasPending(tx.SenderAddress!, tx.Nonce)) + { + return AcceptTxResult.PendingDelegation; + } + if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; Transaction[] currentTxs; + if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) { foreach (Transaction existingTx in currentTxs) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 5496a8ac94a..a3a9bd277d9 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -52,7 +52,7 @@ public class TxPool : ITxPool, IDisposable private readonly IChainHeadInfoProvider _headInfo; private readonly ITxPoolConfig _txPoolConfig; private readonly bool _blobReorgsSupportEnabled; - private readonly ConcurrentDictionary _pendingDelegations = new(); + private readonly DelegationCache _pendingDelegations = new(); private readonly ILogger _logger; @@ -150,7 +150,7 @@ public TxPool(IEthereumEcdsa ecdsa, new FutureNonceFilter(txPoolConfig), new GapNonceFilter(_transactions, _blobTransactions, _logger), new RecoverAuthorityFilter(ecdsa), - new OnlyOneTxPerDelegatedAccountFilter(_specProvider, _transactions, _blobTransactions, chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository ), + new OnlyOneTxPerDelegatedAccountFilter(_specProvider, _transactions, _blobTransactions, chainHeadInfoProvider.ReadOnlyStateProvider, chainHeadInfoProvider.CodeInfoRepository, _pendingDelegations), ]; if (incomingTxFilter is not null) @@ -460,7 +460,7 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions { foreach (var auth in tx.AuthorizationList) { - IncrementDelegationCount(auth.Authority!, auth.Nonce, true); + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, true); } } // Clear proper snapshot @@ -703,7 +703,7 @@ public bool RemoveTransaction(Hash256? hash) { foreach (var auth in transaction.AuthorizationList) { - IncrementDelegationCount(auth.Authority!, auth.Nonce, false); + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, false); } } } @@ -826,30 +826,6 @@ internal void ResetAddress(Address address) _accountCache.RemoveAccounts(arrayPoolList); } - private void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool increment) - { - //A nonce cannot exceed 2^64-1 and an address is 20 bytes, so we can pack them together in one u256 - UInt256 addressPlusNonce = new (key.Value.Bytes); - nonce <<= 64 * 3; - addressPlusNonce += nonce; - - int value = increment ? 1 : -1; - var lastCount = _pendingDelegations.AddOrUpdate(addressPlusNonce, - (k) => - { - if (increment) - return 1; - return 0; - }, - (k, c) => c + value); - - if (lastCount == 0) - { - //Remove() is threadsafe and only removes if the count is the same as the updated one - ((ICollection>)_pendingDelegations).Remove( - new KeyValuePair(key, lastCount)); - } - } private sealed class AccountCache : IAccountStateProvider { From d35ba1d27dc3a7debb40241778b01803baeaa353 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 13 Jan 2025 12:10:32 +0100 Subject: [PATCH 08/18] tx filter test --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs diff --git a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs new file mode 100644 index 00000000000..f1e500046a1 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -0,0 +1,161 @@ +// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Logging; +using Nethermind.Specs.Forks; +using Nethermind.State; +using Nethermind.Trie.Pruning; +using Nethermind.TxPool.Collections; +using Nethermind.TxPool.Filters; +using NSubstitute; +using NUnit.Framework; +using Org.BouncyCastle.Pqc.Crypto.Lms; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.TxPool.Test; +internal class OnlyOneTxPerDelegatedAccountFilterTest +{ + [Test] + public void Accept_SenderIsNotDelegated_ReturnsAccepted() + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), new DelegationCache()); + Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new (); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); + } + + [Test] + public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), new DelegationCache()); + Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new(); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); + } + + [Test] + public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_ReturnsAccepted() + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + standardPool.TryInsert(inPool.Hash, inPool); + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + CodeInfoRepository codeInfoRepository = new (); + byte[] code = [..Eip7702Constants.DelegationHeader, ..TestItem.PrivateKeyA.Address.Bytes]; + codeInfoRepository.InsertCode(stateProvider, code, TestItem.AddressA, Prague.Instance); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, codeInfoRepository, new DelegationCache()); + Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new(); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); + } + + [Test] + public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_ReturnsOnlyOneTxPerDelegatedAccount() + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + standardPool.TryInsert(inPool.Hash, inPool); + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + CodeInfoRepository codeInfoRepository = new(); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. TestItem.PrivateKeyA.Address.Bytes]; + codeInfoRepository.InsertCode(stateProvider, code, TestItem.AddressA, Prague.Instance); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, codeInfoRepository, new DelegationCache()); + Transaction transaction = Build.A.Transaction.WithNonce(1).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new(); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(AcceptTxResult.OnlyOneTxPerDelegatedAccount)); + } + + private static object[] EipActiveCases = + { + new object[]{ true, AcceptTxResult.OnlyOneTxPerDelegatedAccount }, + new object[]{ false, AcceptTxResult.Accepted}, + }; + [TestCaseSource(nameof(EipActiveCases))] + public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTxResult expected) + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + standardPool.TryInsert(inPool.Hash, inPool); + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + CodeInfoRepository codeInfoRepository = new(); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. TestItem.PrivateKeyA.Address.Bytes]; + codeInfoRepository.InsertCode(stateProvider, code, TestItem.AddressA, Prague.Instance); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, codeInfoRepository, new DelegationCache()); + Transaction transaction = Build.A.Transaction.WithNonce(1).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new(); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(expected)); + } + + [Test] + public void Accept_SenderHasPendingDelegation_ReturnsPendingDelegation() + { + IChainHeadSpecProvider headInfoProvider = Substitute.For(); + headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); + TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); + TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); + DelegationCache pendingDelegations = new (); + pendingDelegations.IncrementDelegationCount(TestItem.AddressA, 0, true); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), pendingDelegations); + Transaction transaction = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + TxFilteringState state = new(); + + AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); + + Assert.That(result, Is.EqualTo(AcceptTxResult.PendingDelegation)); + } +} From ce4cf843714ef2437dbd0f1aa4245b5d0a2c8bf1 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 13 Jan 2025 12:13:55 +0100 Subject: [PATCH 09/18] Format --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 10 +++++----- src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs | 4 ++-- .../Filters/OnlyOneTxPerDelegatedAccountFilter.cs | 2 +- src/Nethermind/Nethermind.TxPool/TxPool.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs index f1e500046a1..0f0b94ce539 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -35,11 +35,11 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; - TxFilteringState state = new (); + TxFilteringState state = new(); AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); - Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); + Assert.That(result, Is.EqualTo(AcceptTxResult.Accepted)); } [Test] @@ -72,8 +72,8 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return TrieStore trieStore = new(stateDb, LimboLogs.Instance); IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); stateProvider.CreateAccount(TestItem.AddressA, 0); - CodeInfoRepository codeInfoRepository = new (); - byte[] code = [..Eip7702Constants.DelegationHeader, ..TestItem.PrivateKeyA.Address.Bytes]; + CodeInfoRepository codeInfoRepository = new(); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. TestItem.PrivateKeyA.Address.Bytes]; codeInfoRepository.InsertCode(stateProvider, code, TestItem.AddressA, Prague.Instance); OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, codeInfoRepository, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; @@ -148,7 +148,7 @@ public void Accept_SenderHasPendingDelegation_ReturnsPendingDelegation() headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); - DelegationCache pendingDelegations = new (); + DelegationCache pendingDelegations = new(); pendingDelegations.IncrementDelegationCount(TestItem.AddressA, 0, true); OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), pendingDelegations); Transaction transaction = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index acd0cfec37b..4a52c5637ab 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1762,7 +1762,7 @@ public void Delegated_account_can_only_have_one_tx() ISpecProvider specProvider = GetPragueSpecProvider(); TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; _txPool = CreatePool(txPoolConfig, specProvider); - + PrivateKey signer = TestItem.PrivateKeyA; _stateProvider.CreateAccount(signer.Address, UInt256.MaxValue); byte[] delegation = [.. Eip7702Constants.DelegationHeader, .. TestItem.AddressC.Bytes]; @@ -1773,7 +1773,7 @@ public void Delegated_account_can_only_have_one_tx() .WithType(TxType.EIP1559) .WithMaxFeePerGas(9.GWei()) .WithMaxPriorityFeePerGas(9.GWei()) - .WithGasLimit(GasCostOf.Transaction ) + .WithGasLimit(GasCostOf.Transaction) .WithTo(TestItem.AddressB) .SignedAndResolved(_ethereumEcdsa, signer).TestObject; diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 01dcce028d5..444941f288a 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -30,7 +30,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; Transaction[] currentTxs; - + if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) { foreach (Transaction existingTx in currentTxs) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index a3a9bd277d9..1071cd1d8e9 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -511,7 +511,7 @@ private AcceptTxResult AddCore(Transaction tx, ref TxFilteringState state, bool : worstTx.GasBottleneck; bool inserted = relevantPool.TryInsert(tx.Hash!, tx, out Transaction? removed); - + if (!inserted) { // it means it failed on adding to the pool - it is possible when new tx has the same sender From 2e6e13af1b05571dfe52424838b8082ac9ecdf85 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 13 Jan 2025 17:09:57 +0100 Subject: [PATCH 10/18] remove delegations when evicted --- src/Nethermind/Nethermind.TxPool/TxPool.cs | 33 +++++++++------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 1071cd1d8e9..3461cc133a9 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -343,19 +343,6 @@ private void RemoveProcessedTransactions(Block block) } } - if (blockTx.Type == TxType.SetCode) - { - eip7702Txs++; - - if (blockTx.HasAuthorizationList) - { - foreach (AuthorizationTuple tuple in blockTx.AuthorizationList) - { - - } - } - } - if (!IsKnown(txHash)) { discoveredForHashCache++; @@ -543,6 +530,7 @@ private AcceptTxResult AddCore(Transaction tx, ref TxFilteringState state, bool if (removed is not null) { + RemovePendingDelegations(removed); EvictedPending?.Invoke(this, new TxEventArgs(removed)); // transaction which was on last position in sorted TxPool and was deleted to give // a place for a newly added tx (with higher priority) is now removed from hashCache @@ -560,6 +548,17 @@ private AcceptTxResult AddCore(Transaction tx, ref TxFilteringState state, bool return AcceptTxResult.Accepted; } + private void RemovePendingDelegations(Transaction transaction) + { + if (transaction.HasAuthorizationList) + { + foreach (var auth in transaction.AuthorizationList) + { + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, false); + } + } + } + private void UpdateBucketWithAddedTransaction(in AccountStruct account, EnhancedSortedSet transactions, ref Transaction? lastElement, UpdateTransactionDelegate updateTx) { if (transactions.Count != 0) @@ -699,13 +698,7 @@ public bool RemoveTransaction(Hash256? hash) { RemovedPending?.Invoke(this, new TxEventArgs(transaction)); - if (transaction.HasAuthorizationList) - { - foreach (var auth in transaction.AuthorizationList) - { - _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, false); - } - } + RemovePendingDelegations(transaction); } _broadcaster.StopBroadcast(hash); From 963bba11ac65f70ee7d46ffdafbcf3d292cd7f47 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 13 Jan 2025 17:24:52 +0100 Subject: [PATCH 11/18] format --- src/Nethermind/Nethermind.TxPool/TxPool.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 3461cc133a9..42d582edee1 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -698,7 +698,7 @@ public bool RemoveTransaction(Hash256? hash) { RemovedPending?.Invoke(this, new TxEventArgs(transaction)); - RemovePendingDelegations(transaction); + RemovePendingDelegations(transaction); } _broadcaster.StopBroadcast(hash); From ee6fd8971087d09f8511b4a635553a56d24bf917 Mon Sep 17 00:00:00 2001 From: ak88 Date: Wed, 15 Jan 2025 10:33:28 +0100 Subject: [PATCH 12/18] small refactor --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 2 +- .../Nethermind.TxPool.Test/TxPoolTests.cs | 46 ------------------- .../Nethermind.TxPool/DelegationCache.cs | 15 ++++-- .../OnlyOneTxPerDelegatedAccountFilter.cs | 3 +- src/Nethermind/Nethermind.TxPool/TxPool.cs | 4 +- 5 files changed, 16 insertions(+), 54 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs index 0f0b94ce539..9591455df21 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -149,7 +149,7 @@ public void Accept_SenderHasPendingDelegation_ReturnsPendingDelegation() TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); DelegationCache pendingDelegations = new(); - pendingDelegations.IncrementDelegationCount(TestItem.AddressA, 0, true); + pendingDelegations.IncrementDelegationCount(TestItem.AddressA, 0); OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), pendingDelegations); Transaction transaction = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; TxFilteringState state = new(); diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 4a52c5637ab..32ff62476d0 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1820,53 +1820,7 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega result.Should().Be(AcceptTxResult.Accepted); Transaction secondTx = Build.A.Transaction - .WithNonce(1) - .WithType(TxType.EIP1559) - .WithMaxFeePerGas(9.GWei()) - .WithMaxPriorityFeePerGas(9.GWei()) - .WithGasLimit(GasCostOf.Transaction) - .WithTo(TestItem.AddressB) - .SignedAndResolved(_ethereumEcdsa, signer).TestObject; - - result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - - result.Should().Be(AcceptTxResult.PendingDelegation); - - _txPool.RemoveTransaction(firstTx.Hash); - - result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - - result.Should().Be(AcceptTxResult.AlreadyKnown); - } - - - [Test] - public void Test() - { - ISpecProvider specProvider = GetPragueSpecProvider(); - TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; - _txPool = CreatePool(txPoolConfig, specProvider); - - PrivateKey signer = TestItem.PrivateKeyA; - _stateProvider.CreateAccount(signer.Address, UInt256.MaxValue); - - EthereumEcdsa ecdsa = new EthereumEcdsa(_specProvider.ChainId); - - Transaction firstTx = Build.A.Transaction .WithNonce(0) - .WithType(TxType.SetCode) - .WithMaxFeePerGas(9.GWei()) - .WithMaxPriorityFeePerGas(9.GWei()) - .WithGasLimit(100_000) - .WithAuthorizationCode(ecdsa.Sign(signer, specProvider.ChainId, TestItem.AddressC, 0)) - .WithTo(TestItem.AddressB) - .SignedAndResolved(_ethereumEcdsa, signer).TestObject; - - AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast); - result.Should().Be(AcceptTxResult.Accepted); - - Transaction secondTx = Build.A.Transaction - .WithNonce(1) .WithType(TxType.EIP1559) .WithMaxFeePerGas(9.GWei()) .WithMaxPriorityFeePerGas(9.GWei()) diff --git a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs index 87724635ee4..a130183cd70 100644 --- a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -20,7 +20,16 @@ public bool HasPending(AddressAsKey key, UInt256 nonce) return _pendingDelegations.ContainsKey(KeyMask(key, nonce)); } - public void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool increment) + public void DecrementDelegationCount(AddressAsKey key, UInt256 nonce) + { + InternalIncrement(key, nonce, false); + } + public void IncrementDelegationCount(AddressAsKey key, UInt256 nonce) + { + InternalIncrement(key, nonce, true); + } + + private void InternalIncrement(AddressAsKey key, UInt256 nonce, bool increment) { UInt256 addressPlusNonce = KeyMask(key, nonce); @@ -37,8 +46,8 @@ public void IncrementDelegationCount(AddressAsKey key, UInt256 nonce, bool incre if (lastCount == 0) { //Remove() is threadsafe and only removes if the count is the same as the updated one - ((ICollection>)_pendingDelegations).Remove( - new KeyValuePair(key, lastCount)); + ((ICollection>)_pendingDelegations).Remove( + new KeyValuePair(addressPlusNonce, lastCount)); } } diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 444941f288a..1fae1544f68 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -23,14 +23,13 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl return AcceptTxResult.Accepted; if (pendingDelegations.HasPending(tx.SenderAddress!, tx.Nonce)) - { return AcceptTxResult.PendingDelegation; - } if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; Transaction[] currentTxs; + //Transactios from the same source can only have blob transactions or another type if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) { foreach (Transaction existingTx in currentTxs) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 42d582edee1..7ddc6997d23 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -447,7 +447,7 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions { foreach (var auth in tx.AuthorizationList) { - _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, true); + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce); } } // Clear proper snapshot @@ -554,7 +554,7 @@ private void RemovePendingDelegations(Transaction transaction) { foreach (var auth in transaction.AuthorizationList) { - _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce, false); + _pendingDelegations.DecrementDelegationCount(auth.Authority!, auth.Nonce); } } } From 5bdab60671d6337396fcab51cfcead930f6e20c1 Mon Sep 17 00:00:00 2001 From: ak88 Date: Wed, 15 Jan 2025 12:26:36 +0100 Subject: [PATCH 13/18] skip bad sigs --- .../Filters/OnlyOneTxPerDelegatedAccountFilter.cs | 2 +- src/Nethermind/Nethermind.TxPool/TxPool.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 1fae1544f68..5ae2864a606 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -29,7 +29,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl return AcceptTxResult.Accepted; Transaction[] currentTxs; - //Transactios from the same source can only have blob transactions or another type + //Transactios from the same source can only be either blob transactions or some other type if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) { foreach (Transaction existingTx in currentTxs) diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 04ee6a813cd..84cf7ba7a15 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -448,7 +448,8 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions { foreach (var auth in tx.AuthorizationList) { - _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce); + if (auth.Authority is not null) + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce); } } // Clear proper snapshot @@ -555,7 +556,8 @@ private void RemovePendingDelegations(Transaction transaction) { foreach (var auth in transaction.AuthorizationList) { - _pendingDelegations.DecrementDelegationCount(auth.Authority!, auth.Nonce); + if (auth.Authority is not null) + _pendingDelegations.DecrementDelegationCount(auth.Authority!, auth.Nonce); } } } From 067d504b8a1b85a0790b3160906159faf20cf4fb Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 16 Jan 2025 23:21:34 +0100 Subject: [PATCH 14/18] Update src/Nethermind/Nethermind.TxPool/DelegationCache.cs Co-authored-by: Lukasz Rozmej --- src/Nethermind/Nethermind.TxPool/DelegationCache.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs index a130183cd70..426c9cfd984 100644 --- a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -54,9 +54,10 @@ private void InternalIncrement(AddressAsKey key, UInt256 nonce, bool increment) private static UInt256 KeyMask(AddressAsKey key, UInt256 nonce) { //A nonce cannot exceed 2^64-1 and an address is 20 bytes, so we can pack them together in one u256 - UInt256 addressPlusNonce = new(key.Value.Bytes); - nonce <<= 64 * 3; - addressPlusNonce += nonce; - return addressPlusNonce; + ref byte baseRef = ref key.Value.Bytes[0]; + return new UInt256(Unsafe.ReadUnaligned(ref baseRef), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref baseRef, 8)), + Unsafe.ReadUnaligned(ref Unsafe.Add(ref baseRef, 16)), + nonce.u1); } } From cc6b5d7e31b8a54b18ddb7389666ec14be9b8fff Mon Sep 17 00:00:00 2001 From: ak88 Date: Thu, 16 Jan 2025 23:25:34 +0100 Subject: [PATCH 15/18] code review changes --- .../Nethermind.TxPool/DelegationCache.cs | 1 + src/Nethermind/Nethermind.TxPool/TxPool.cs | 21 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs index 426c9cfd984..f9a9be94475 100644 --- a/src/Nethermind/Nethermind.TxPool/DelegationCache.cs +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -7,6 +7,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 84cf7ba7a15..64310c77d2f 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -444,14 +444,7 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions accepted = AddCore(tx, ref state, startBroadcast); if (accepted) { - if (tx.HasAuthorizationList) - { - foreach (var auth in tx.AuthorizationList) - { - if (auth.Authority is not null) - _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce); - } - } + AddPendingDelegations(tx); // Clear proper snapshot if (tx.SupportsBlobs) _blobTransactionSnapshot = null; @@ -463,6 +456,18 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions return accepted; } + private void AddPendingDelegations(Transaction tx) + { + if (tx.HasAuthorizationList) + { + foreach (var auth in tx.AuthorizationList) + { + if (auth.Authority is not null) + _pendingDelegations.IncrementDelegationCount(auth.Authority!, auth.Nonce); + } + } + } + private AcceptTxResult FilterTransactions(Transaction tx, TxHandlingOptions handlingOptions, ref TxFilteringState state) { IIncomingTxFilter[] filters = _preHashFilters; From e8fbbbc22cf1f07221bcd0661d9537059eb8056d Mon Sep 17 00:00:00 2001 From: ak88 Date: Fri, 17 Jan 2025 00:43:33 +0100 Subject: [PATCH 16/18] avoid array allocation --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 2 +- .../Nethermind.TxPool/Collections/SortedPool.cs | 8 ++++++++ .../Filters/OnlyOneTxPerDelegatedAccountFilter.cs | 12 +----------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs index 9591455df21..bfcbb9c489d 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -122,7 +122,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); - Transaction inPool = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; + Transaction inPool = Build.A.Transaction.WithNonce(0).SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; standardPool.TryInsert(inPool.Hash, inPool); IDb stateDb = new MemDb(); IDb codeDb = new MemDb(); diff --git a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs index f9bc553d4ae..49d9e53fad0 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.cs @@ -530,6 +530,14 @@ public bool TryGetBucketsWorstValue(TGroupKey groupKey, out TValue? item) return false; } + public bool BucketEmptyExcept(TGroupKey groupKey, Func predicate) + { + using var lockRelease = Lock.Acquire(); + if (_buckets.TryGetValue(groupKey, out EnhancedSortedSet? bucket) && bucket.Count > 0) + return bucket.Any(predicate); + return true; + } + protected void EnsureCapacity(int? expectedCapacity = null) { expectedCapacity ??= _capacity; // expectedCapacity is added for testing purpose. null should be used in production code diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index 5ae2864a606..a6a243449a0 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -27,19 +27,9 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; - Transaction[] currentTxs; - //Transactios from the same source can only be either blob transactions or some other type - if (standardPool.TryGetBucket(tx.SenderAddress!, out currentTxs) || blobPool.TryGetBucket(tx.SenderAddress!, out currentTxs)) + if (!standardPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce) || !blobPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce)) { - foreach (Transaction existingTx in currentTxs) - { - if (existingTx.Nonce == tx.Nonce) - { - //This is a replacement tx so accept it, and let the comparers check for correct replacement rules - return AcceptTxResult.Accepted; - } - } return AcceptTxResult.OnlyOneTxPerDelegatedAccount; } return AcceptTxResult.Accepted; From 5d4ac71ede8c0b7ea0cb2ca9a4cd9abd434506f7 Mon Sep 17 00:00:00 2001 From: ak88 Date: Fri, 31 Jan 2025 19:36:42 +0100 Subject: [PATCH 17/18] code review --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 14 ++++++++--- .../Nethermind.TxPool.Test/TxPoolTests.cs | 24 ++++++++++++------- .../Nethermind.TxPool/AcceptTxResult.cs | 15 ++++++------ .../OnlyOneTxPerDelegatedAccountFilter.cs | 7 +++--- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs index bfcbb9c489d..b1751f03c23 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -45,11 +45,19 @@ public void Accept_SenderIsNotDelegated_ReturnsAccepted() [Test] public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted() { + IDb stateDb = new MemDb(); + IDb codeDb = new MemDb(); + TrieStore trieStore = new(stateDb, LimboLogs.Instance); + IWorldState stateProvider = new WorldState(trieStore, codeDb, LimboLogs.Instance); + stateProvider.CreateAccount(TestItem.AddressA, 0); + CodeInfoRepository codeInfoRepository = new(); + byte[] code = [.. Eip7702Constants.DelegationHeader, .. TestItem.PrivateKeyA.Address.Bytes]; + codeInfoRepository.InsertCode(stateProvider, code, TestItem.AddressA, Prague.Instance); IChainHeadSpecProvider headInfoProvider = Substitute.For(); headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For>(), NullLogManager.Instance); TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For>(), NullLogManager.Instance); - OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For(), new CodeInfoRepository(), new DelegationCache()); + OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, stateProvider, codeInfoRepository, new DelegationCache()); Transaction transaction = Build.A.Transaction.SignedAndResolved(new EthereumEcdsa(0), TestItem.PrivateKeyA).TestObject; TxFilteringState state = new(); @@ -107,12 +115,12 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None); - Assert.That(result, Is.EqualTo(AcceptTxResult.OnlyOneTxPerDelegatedAccount)); + Assert.That(result, Is.EqualTo(AcceptTxResult.MoreThanOneTxPerDelegatedAccount)); } private static object[] EipActiveCases = { - new object[]{ true, AcceptTxResult.OnlyOneTxPerDelegatedAccount }, + new object[]{ true, AcceptTxResult.MoreThanOneTxPerDelegatedAccount }, new object[]{ false, AcceptTxResult.Accepted}, }; [TestCaseSource(nameof(EipActiveCases))] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index f82d6d9a155..efc5a5fd1ec 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1805,11 +1805,12 @@ public void Delegated_account_can_only_have_one_tx() result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - result.Should().Be(AcceptTxResult.OnlyOneTxPerDelegatedAccount); + result.Should().Be(AcceptTxResult.MoreThanOneTxPerDelegatedAccount); } - [Test] - public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delegation_removal() + [TestCase(true)] + [TestCase(true)] + public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delegation_removal(bool withRemoval) { ISpecProvider specProvider = GetPragueSpecProvider(); TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 }; @@ -1842,15 +1843,20 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega .WithTo(TestItem.AddressB) .SignedAndResolved(_ethereumEcdsa, signer).TestObject; - result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - - result.Should().Be(AcceptTxResult.PendingDelegation); + if (withRemoval) + { + _txPool.RemoveTransaction(firstTx.Hash); - _txPool.RemoveTransaction(firstTx.Hash); + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + result.Should().Be(AcceptTxResult.Accepted); + } + else + { + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - result.Should().Be(AcceptTxResult.AlreadyKnown); + result.Should().Be(AcceptTxResult.AlreadyKnown); + } } private IDictionary GetPeers(int limit = 100) diff --git a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs index 0a2bccd1af5..f707264a6d3 100644 --- a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs +++ b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs @@ -89,20 +89,21 @@ namespace Nethermind.TxPool /// Ignores transactions if tx type is not supported /// public static readonly AcceptTxResult NotSupportedTxType = new(15, nameof(NotSupportedTxType)); + /// - /// Only one tx is allowed per delegated account. + /// Transaction size exceeds configured max size. /// - public static readonly AcceptTxResult OnlyOneTxPerDelegatedAccount = new(16, nameof(OnlyOneTxPerDelegatedAccount)); + public static readonly AcceptTxResult MaxTxSizeExceeded = new(16, nameof(MaxTxSizeExceeded)); + /// - /// There is a pending delegation in the tx pool already + /// Only one tx is allowed per delegated account. /// - public static readonly AcceptTxResult PendingDelegation = new(17, nameof(PendingDelegation)); - + public static readonly AcceptTxResult MoreThanOneTxPerDelegatedAccount = new(17, nameof(MoreThanOneTxPerDelegatedAccount)); /// - /// Transaction size exceeds configured max size. + /// There is a pending delegation in the tx pool already /// - public static readonly AcceptTxResult MaxTxSizeExceeded = new(16, nameof(MaxTxSizeExceeded)); + public static readonly AcceptTxResult PendingDelegation = new(18, nameof(PendingDelegation)); /// /// The node is syncing and cannot accept transactions at this time. diff --git a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs index a6a243449a0..e45f477e245 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -27,10 +27,11 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl if (!codeInfoRepository.TryGetDelegation(worldState, tx.SenderAddress!, out _)) return AcceptTxResult.Accepted; - //Transactios from the same source can only be either blob transactions or some other type - if (!standardPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce) || !blobPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce)) + //Transactios from the same source can only be either blob transactions or other type + if (tx.SupportsBlobs ? !blobPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce) + : !standardPool.BucketEmptyExcept(tx.SenderAddress!, (t) => t.Nonce == tx.Nonce)) { - return AcceptTxResult.OnlyOneTxPerDelegatedAccount; + return AcceptTxResult.MoreThanOneTxPerDelegatedAccount; } return AcceptTxResult.Accepted; } From 3f402c31a63f35254d3d6b860b98a8a652018df0 Mon Sep 17 00:00:00 2001 From: ak88 Date: Mon, 3 Feb 2025 23:06:38 +0100 Subject: [PATCH 18/18] test fix --- src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index efc5a5fd1ec..b91e450f83b 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1809,7 +1809,7 @@ public void Delegated_account_can_only_have_one_tx() } [TestCase(true)] - [TestCase(true)] + [TestCase(false)] public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delegation_removal(bool withRemoval) { ISpecProvider specProvider = GetPragueSpecProvider(); @@ -1855,7 +1855,7 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega { result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); - result.Should().Be(AcceptTxResult.AlreadyKnown); + result.Should().Be(AcceptTxResult.PendingDelegation); } }