Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/filter setcode tx #8167

Merged
merged 17 commits into from
Feb 12, 2025
ak88 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void Accept_SenderIsDelegatedWithNoTransactionsInPool_ReturnsAccepted()
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();
TxFilteringState state = new(transaction, stateProvider);

AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None);

Expand All @@ -85,7 +85,7 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithSameNonce_Return
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();
TxFilteringState state = new(transaction, stateProvider);

AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None);

Expand All @@ -111,16 +111,16 @@ public void Accept_SenderIsDelegatedWithOneTransactionInPoolWithDifferentNonce_R
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();
TxFilteringState state = new(transaction, stateProvider);

AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None);

Assert.That(result, Is.EqualTo(AcceptTxResult.MoreThanOneTxPerDelegatedAccount));
Assert.That(result, Is.EqualTo(AcceptTxResult.OnlyExactNonceForDelegatedAccount));
}

private static object[] EipActiveCases =
{
new object[]{ true, AcceptTxResult.MoreThanOneTxPerDelegatedAccount },
new object[]{ true, AcceptTxResult.OnlyExactNonceForDelegatedAccount },
new object[]{ false, AcceptTxResult.Accepted},
};
[TestCaseSource(nameof(EipActiveCases))]
Expand All @@ -142,7 +142,7 @@ public void Accept_Eip7702IsNotActivated_ReturnsExpected(bool isActive, AcceptTx
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();
TxFilteringState state = new(transaction, stateProvider);

AcceptTxResult result = filter.Accept(transaction, ref state, TxHandlingOptions.None);

Expand All @@ -157,7 +157,7 @@ public void Accept_SenderHasPendingDelegation_ReturnsPendingDelegation()
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);
pendingDelegations.IncrementDelegationCount(TestItem.AddressA);
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();
Expand All @@ -166,4 +166,44 @@ public void Accept_SenderHasPendingDelegation_ReturnsPendingDelegation()

Assert.That(result, Is.EqualTo(AcceptTxResult.PendingDelegation));
}

[TestCase(true)]
[TestCase(false)]
public void Accept_AuthorityHasPendingTransaction_ReturnsDelegatorHasPendingTx(bool useBlobPool)
{
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());
Transaction transaction;
if (useBlobPool)
{
transaction
= Build.A.Transaction
.WithShardBlobTxTypeAndFields()
.SignedAndResolved(TestItem.PrivateKeyA).TestObject;
blobPool.TryInsert(transaction.Hash, transaction, out _);
}
else
{
transaction
= Build.A.Transaction
.WithNonce(0)
.SignedAndResolved(TestItem.PrivateKeyA).TestObject;
standardPool.TryInsert(transaction.Hash, transaction, out _);
}
TxFilteringState state = new();
EthereumEcdsa ecdsa = new EthereumEcdsa(0);
AuthorizationTuple authTuple = new AuthorizationTuple(0, TestItem.AddressB, 0, new Core.Crypto.Signature(0, 0, 27), TestItem.AddressA);
Transaction setCodeTx = Build.A.Transaction
.WithType(TxType.SetCode)
.WithAuthorizationCode(authTuple)
.SignedAndResolved(TestItem.PrivateKeyB)
.TestObject;

AcceptTxResult setCodeTxResult = filter.Accept(setCodeTx, ref state, TxHandlingOptions.None);

Assert.That(setCodeTxResult, Is.EqualTo(AcceptTxResult.DelegatorHasPendingTx));
}
}
172 changes: 162 additions & 10 deletions src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1748,7 +1748,7 @@ public void Should_correctly_add_tx_to_local_pool_when_underpaid([Values] TxType
yield return ([.. Eip7702Constants.DelegationHeader, .. new byte[20]], AcceptTxResult.Accepted);
}
[TestCaseSource(nameof(CodeCases))]
public void SubmitTx_CodeIsNotDelegationAndDelegation_DelegationIsAccepted((byte[] code, AcceptTxResult expected) testCase)
public void Sender_account_has_delegation_and_normal_code((byte[] code, AcceptTxResult expected) testCase)
{
ISpecProvider specProvider = GetPragueSpecProvider();
TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 };
Expand All @@ -1770,8 +1770,16 @@ public void SubmitTx_CodeIsNotDelegationAndDelegation_DelegationIsAccepted((byte
result.Should().Be(testCase.expected);
}

[Test]
public void Delegated_account_can_only_have_one_tx()
private static IEnumerable<object> DifferentOrderNonces()
{
yield return new object[] { 0, 1, AcceptTxResult.Accepted, AcceptTxResult.OnlyExactNonceForDelegatedAccount };
yield return new object[] { 2, 5, AcceptTxResult.OnlyExactNonceForDelegatedAccount, AcceptTxResult.OnlyExactNonceForDelegatedAccount };
yield return new object[] { 1, 0, AcceptTxResult.OnlyExactNonceForDelegatedAccount, AcceptTxResult.Accepted };
yield return new object[] { 5, 0, AcceptTxResult.OnlyExactNonceForDelegatedAccount, AcceptTxResult.Accepted };
}

[TestCaseSource(nameof(DifferentOrderNonces))]
public void Delegated_account_can_only_have_one_tx_with_current_account_nonce(int firstNonce, int secondNonce, AcceptTxResult firstExpectation, AcceptTxResult secondExpectation)
{
ISpecProvider specProvider = GetPragueSpecProvider();
TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 };
Expand All @@ -1783,7 +1791,7 @@ public void Delegated_account_can_only_have_one_tx()
_stateProvider.InsertCode(signer.Address, delegation.AsMemory(), Prague.Instance);

Transaction firstTx = Build.A.Transaction
.WithNonce(0)
.WithNonce((UInt256)firstNonce)
.WithType(TxType.EIP1559)
.WithMaxFeePerGas(9.GWei())
.WithMaxPriorityFeePerGas(9.GWei())
Expand All @@ -1792,10 +1800,10 @@ public void Delegated_account_can_only_have_one_tx()
.SignedAndResolved(_ethereumEcdsa, signer).TestObject;

AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast);
result.Should().Be(AcceptTxResult.Accepted);
result.Should().Be(firstExpectation);

Transaction secondTx = Build.A.Transaction
.WithNonce(1)
.WithNonce((UInt256)secondNonce)
.WithType(TxType.EIP1559)
.WithMaxFeePerGas(9.GWei())
.WithMaxPriorityFeePerGas(9.GWei())
Expand All @@ -1805,19 +1813,21 @@ public void Delegated_account_can_only_have_one_tx()

result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast);

result.Should().Be(AcceptTxResult.MoreThanOneTxPerDelegatedAccount);
result.Should().Be(secondExpectation);
}

[TestCase(true)]
[TestCase(false)]
public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delegation_removal(bool withRemoval)
public void Tx_with_conflicting_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;
PrivateKey sponsor = TestItem.PrivateKeyB;
_stateProvider.CreateAccount(signer.Address, UInt256.MaxValue);
_stateProvider.CreateAccount(sponsor.Address, UInt256.MaxValue);

EthereumEcdsa ecdsa = new EthereumEcdsa(_specProvider.ChainId);

Expand All @@ -1829,12 +1839,50 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega
.WithGasLimit(100_000)
.WithAuthorizationCode(ecdsa.Sign(signer, specProvider.ChainId, TestItem.AddressC, 0))
.WithTo(TestItem.AddressB)
.SignedAndResolved(_ethereumEcdsa, signer).TestObject;
.SignedAndResolved(_ethereumEcdsa, sponsor).TestObject;

AcceptTxResult result = _txPool.SubmitTx(firstTx, TxHandlingOptions.PersistentBroadcast);
result.Should().Be(AcceptTxResult.Accepted);

Transaction secondTx = Build.A.Transaction
.WithNonce(0)
.WithType(TxType.EIP1559)
.WithMaxFeePerGas(12.GWei())
.WithMaxPriorityFeePerGas(12.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);
}
}

[TestCase(true)]
[TestCase(false)]
public void SetCode_tx_has_authority_with_pending_transaction_is_rejected_then_is_accepted_after_tx_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.EIP1559)
.WithMaxFeePerGas(9.GWei())
Expand All @@ -1843,6 +1891,19 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega
.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.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;

if (withRemoval)
{
_txPool.RemoveTransaction(firstTx.Hash);
Expand All @@ -1855,10 +1916,101 @@ public void Tx_with_pending_delegation_is_rejected_then_is_accepted_after_delega
{
result = _txPool.SubmitTx(secondTx, TxHandlingOptions.PersistentBroadcast);

result.Should().Be(AcceptTxResult.PendingDelegation);
result.Should().Be(AcceptTxResult.DelegatorHasPendingTx);
}
}

private static IEnumerable<object[]> SetCodeReplacedTxCases()
{
yield return new object[]
{
//Not self sponsored
TestItem.PrivateKeyB,
(IWorldState state, Address account, IReleaseSpec spec) =>
{
state.CreateAccount(account, UInt256.MaxValue);
state.CreateAccount(TestItem.AddressB, UInt256.MaxValue);
},
AcceptTxResult.Accepted
};
yield return new object[]
{
//Self sponsored
TestItem.PrivateKeyA,
(IWorldState state, Address account, IReleaseSpec spec) =>
{
state.CreateAccount(account, UInt256.MaxValue);
},
AcceptTxResult.Accepted
};
yield return new object[]
{
//Self sponsored
TestItem.PrivateKeyA,
//Account is delegated so the last transaction should not be accepted
(IWorldState state, Address account, IReleaseSpec spec) =>
{
state.CreateAccount(account, UInt256.MaxValue);
byte[] delegation = [..Eip7702Constants.DelegationHeader, ..TestItem.AddressB.Bytes];
state.InsertCode(account, delegation, spec);
},
AcceptTxResult.OnlyExactNonceForDelegatedAccount
};
}

[TestCaseSource(nameof(SetCodeReplacedTxCases))]
public void SetCode_tx_can_be_replaced_itself_and_remove_pending_delegation_restriction(
PrivateKey sponsor, Action<IWorldState, Address, IReleaseSpec> accountSetup, AcceptTxResult lastExpectation)
{
ISpecProvider specProvider = GetPragueSpecProvider();
TxPoolConfig txPoolConfig = new TxPoolConfig { Size = 30, PersistentBlobStorageSize = 0 };
_txPool = CreatePool(txPoolConfig, specProvider);

PrivateKey signer = TestItem.PrivateKeyA;
accountSetup(_stateProvider, signer.Address, Prague.Instance);

EthereumEcdsa ecdsa = new EthereumEcdsa(_specProvider.ChainId);

Transaction firstSetcodeTx = 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, sponsor).TestObject;

AcceptTxResult result = _txPool.SubmitTx(firstSetcodeTx, TxHandlingOptions.PersistentBroadcast);
result.Should().Be(AcceptTxResult.Accepted);

Transaction replacementTx = Build.A.Transaction
.WithNonce(0)
.WithType(TxType.EIP1559)
.WithMaxFeePerGas(12.GWei())
.WithMaxPriorityFeePerGas(12.GWei())
.WithGasLimit(GasCostOf.Transaction)
.WithTo(TestItem.AddressB)
.SignedAndResolved(_ethereumEcdsa, sponsor).TestObject;

result = _txPool.SubmitTx(replacementTx, TxHandlingOptions.PersistentBroadcast);

result.Should().Be(AcceptTxResult.Accepted);

Transaction thirdTx = 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(thirdTx, TxHandlingOptions.PersistentBroadcast);

result.Should().Be(lastExpectation);
}

private IDictionary<ITxPoolPeer, PrivateKey> GetPeers(int limit = 100)
{
var peers = new Dictionary<ITxPoolPeer, PrivateKey>();
Expand Down
7 changes: 6 additions & 1 deletion src/Nethermind/Nethermind.TxPool/AcceptTxResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,18 @@ namespace Nethermind.TxPool
/// <summary>
/// Only one tx is allowed per delegated account.
ak88 marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
public static readonly AcceptTxResult MoreThanOneTxPerDelegatedAccount = new(17, nameof(MoreThanOneTxPerDelegatedAccount));
public static readonly AcceptTxResult OnlyExactNonceForDelegatedAccount = new(17, nameof(OnlyExactNonceForDelegatedAccount));
ak88 marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// There is a pending delegation in the tx pool already
/// </summary>
public static readonly AcceptTxResult PendingDelegation = new(18, nameof(PendingDelegation));

/// <summary>
/// There is a pending transaction from a delegation in the tx pool already.
/// </summary>
public static readonly AcceptTxResult DelegatorHasPendingTx = new(19, nameof(DelegatorHasPendingTx));

/// <summary>
/// The node is syncing and cannot accept transactions at this time.
/// </summary>
Expand Down
Loading