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));
}
}
157 changes: 147 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,86 @@ 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[]> SelfsponsoredSetCodeTxCases()
{
yield return new object[]
{
(IWorldState state, Address account, IReleaseSpec spec) =>
{
state.CreateAccount(account, UInt256.MaxValue);
},
AcceptTxResult.Accepted
};
yield return new object[]
{
//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(SelfsponsoredSetCodeTxCases))]
public void Selfsponsored_SetCode_tx_replacement_can_replace_itself_and_remove_pending_delegation_restriction(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, signer).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, signer).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
39 changes: 39 additions & 0 deletions src/Nethermind/Nethermind.TxPool/Collections/DelegationPool.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using Nethermind.Core;
using Nethermind.Int256;
using Nethermind.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Nethermind.TxPool.Collections;
public class DelegationPool : SortedPool<UInt256, Transaction, AddressAsKey>
{
public DelegationPool(int capacity, IComparer<Transaction> comparer, ILogManager logManager) : base(capacity, comparer, logManager)
{
}

protected override IComparer<Transaction> GetGroupComparer(IComparer<Transaction> comparer)
{
throw new NotImplementedException();
}

protected override UInt256 GetKey(Transaction value)
{
throw new NotImplementedException();
}

protected override IComparer<Transaction> GetUniqueComparer(IComparer<Transaction> comparer)
{
throw new NotImplementedException();
}

protected override AddressAsKey MapToGroup(Transaction value)
{
throw new NotImplementedException();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this ok to leave like that? Aren’t those required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought i had deleted this. Will remove it.

Copy link
Member

@LukaszRozmej LukaszRozmej Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is abstract, I thought those were kind of mandatory?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant the whole class. I had a more complex solution before, until I figured the current solution would be simpler. So this is no longer used at all.

}
Loading