From 3e6bfba9f10bb93c192f4df7fb742de21e07c85a Mon Sep 17 00:00:00 2001 From: ak88 Date: Tue, 4 Feb 2025 12:34:01 +0100 Subject: [PATCH] New filter restricting delegations in txpool (#8022) Co-authored-by: Lukasz Rozmej --- .../OnlyOneTxPerDelegatedAccountFilterTest.cs | 169 ++++++++++++++++++ .../Nethermind.TxPool.Test/TxPoolTests.cs | 89 +++++++++ .../Nethermind.TxPool/AcceptTxResult.cs | 10 ++ .../Collections/SortedPool.cs | 8 + .../Nethermind.TxPool/DelegationCache.cs | 64 +++++++ .../Filters/MalformedTxFilter.cs | 3 +- .../OnlyOneTxPerDelegatedAccountFilter.cs | 39 ++++ src/Nethermind/Nethermind.TxPool/TxPool.cs | 44 ++++- 8 files changed, 418 insertions(+), 8 deletions(-) create mode 100644 src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs create mode 100644 src/Nethermind/Nethermind.TxPool/DelegationCache.cs create mode 100644 src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.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..b1751f03c23 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs @@ -0,0 +1,169 @@ +// 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() + { + 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, 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_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.MoreThanOneTxPerDelegatedAccount)); + } + + private static object[] EipActiveCases = + { + new object[]{ true, AcceptTxResult.MoreThanOneTxPerDelegatedAccount }, + 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.WithNonce(0).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); + 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)); + } +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index 995725f12e2..b91e450f83b 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -1770,6 +1770,95 @@ 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.MoreThanOneTxPerDelegatedAccount); + } + + [TestCase(true)] + [TestCase(false)] + 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 }; + _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(0) + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(9.GWei()) + .WithMaxPriorityFeePerGas(9.GWei()) + .WithGasLimit(GasCostOf.Transaction) + .WithTo(TestItem.AddressB) + .SignedAndResolved(_ethereumEcdsa, signer).TestObject; + + if (withRemoval) + { + _txPool.RemoveTransaction(firstTx.Hash); + + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.Accepted); + } + else + { + result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast); + + result.Should().Be(AcceptTxResult.PendingDelegation); + } + } + private IDictionary GetPeers(int limit = 100) { var peers = new Dictionary(); diff --git a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs index 6ea1126b15e..f707264a6d3 100644 --- a/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs +++ b/src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs @@ -95,6 +95,16 @@ namespace Nethermind.TxPool /// public static readonly AcceptTxResult MaxTxSizeExceeded = new(16, nameof(MaxTxSizeExceeded)); + /// + /// Only one tx is allowed per delegated account. + /// + public static readonly AcceptTxResult MoreThanOneTxPerDelegatedAccount = new(17, nameof(MoreThanOneTxPerDelegatedAccount)); + + /// + /// There is a pending delegation in the tx pool already + /// + 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/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/DelegationCache.cs b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs new file mode 100644 index 00000000000..f9a9be94475 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/DelegationCache.cs @@ -0,0 +1,64 @@ +// 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.Runtime.CompilerServices; +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 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); + + 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(addressPlusNonce, 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 + 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); + } +} 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 new file mode 100644 index 00000000000..e45f477e245 --- /dev/null +++ b/src/Nethermind/Nethermind.TxPool/Filters/OnlyOneTxPerDelegatedAccountFilter.cs @@ -0,0 +1,39 @@ +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 +{ + internal sealed class OnlyOneTxPerDelegatedAccountFilter( + IChainHeadSpecProvider specProvider, + TxDistinctSortedPool standardPool, + TxDistinctSortedPool blobPool, + IReadOnlyStateProvider worldState, + ICodeInfoRepository codeInfoRepository, + DelegationCache pendingDelegations) : IIncomingTxFilter + { + public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandlingOptions txHandlingOptions) + { + IReleaseSpec spec = specProvider.GetCurrentHeadSpec(); + 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; + //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.MoreThanOneTxPerDelegatedAccount; + } + return AcceptTxResult.Accepted; + } + } +} diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index a9c6f3b90a3..5bce5d6ac21 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; @@ -51,6 +52,8 @@ public class TxPool : ITxPool, IDisposable private readonly IChainHeadInfoProvider _headInfo; private readonly ITxPoolConfig _txPoolConfig; private readonly bool _blobReorgsSupportEnabled; + private readonly DelegationCache _pendingDelegations = new(); + private readonly ILogger _logger; @@ -120,7 +123,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; @@ -146,7 +150,8 @@ 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, _pendingDelegations), ]; if (incomingTxFilter is not null) @@ -339,11 +344,6 @@ private void RemoveProcessedTransactions(Block block) } } - if (blockTx.Type == TxType.SetCode) - { - eip7702Txs++; - } - if (!IsKnown(txHash)) { discoveredForHashCache++; @@ -444,6 +444,7 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions accepted = AddCore(tx, ref state, startBroadcast); if (accepted) { + AddPendingDelegations(tx); // Clear proper snapshot if (tx.SupportsBlobs) _blobTransactionSnapshot = null; @@ -455,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; @@ -524,6 +537,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 @@ -541,6 +555,18 @@ 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) + { + if (auth.Authority is not null) + _pendingDelegations.DecrementDelegationCount(auth.Authority!, auth.Nonce); + } + } + } + private void UpdateBucketWithAddedTransaction(in AccountStruct account, EnhancedSortedSet transactions, ref Transaction? lastElement, UpdateTransactionDelegate updateTx) { if (transactions.Count != 0) @@ -679,6 +705,8 @@ public bool RemoveTransaction(Hash256? hash) if (hasBeenRemoved) { RemovedPending?.Invoke(this, new TxEventArgs(transaction)); + + RemovePendingDelegations(transaction); } _broadcaster.StopBroadcast(hash); @@ -799,6 +827,7 @@ internal void ResetAddress(Address address) _accountCache.RemoveAccounts(arrayPoolList); } + private sealed class AccountCache : IAccountStateProvider { private readonly IAccountStateProvider _provider; @@ -936,3 +965,4 @@ private static void WriteTxPoolReport(in ILogger logger) } } } +