-
Notifications
You must be signed in to change notification settings - Fork 476
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New filter restricting delegations in txpool (#8022)
Co-authored-by: Lukasz Rozmej <[email protected]>
- Loading branch information
1 parent
ae13ee2
commit 3e6bfba
Showing
8 changed files
with
418 additions
and
8 deletions.
There are no files selected for viewing
169 changes: 169 additions & 0 deletions
169
src/Nethermind/Nethermind.TxPool.Test/OnlyOneTxPerDelegatedAccountFilterTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For<IReadOnlyStateProvider>(), 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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), 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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), 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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), 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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(isActive ? Prague.Instance : Cancun.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), 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<IChainHeadSpecProvider>(); | ||
headInfoProvider.GetCurrentHeadSpec().Returns(Prague.Instance); | ||
TxDistinctSortedPool standardPool = new TxDistinctSortedPool(MemoryAllowance.MemPoolSize, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
TxDistinctSortedPool blobPool = new BlobTxDistinctSortedPool(10, Substitute.For<IComparer<Transaction>>(), NullLogManager.Instance); | ||
DelegationCache pendingDelegations = new(); | ||
pendingDelegations.IncrementDelegationCount(TestItem.AddressA, 0); | ||
OnlyOneTxPerDelegatedAccountFilter filter = new(headInfoProvider, standardPool, blobPool, Substitute.For<IReadOnlyStateProvider>(), 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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<UInt256, int> _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<KeyValuePair<UInt256, int>>)_pendingDelegations).Remove( | ||
new KeyValuePair<UInt256, int>(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<ulong>(ref baseRef), | ||
Unsafe.ReadUnaligned<ulong>(ref Unsafe.Add(ref baseRef, 8)), | ||
Unsafe.ReadUnaligned<uint>(ref Unsafe.Add(ref baseRef, 16)), | ||
nonce.u1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.