diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java index f66d7f41f94..1fcd45f84fe 100644 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionPoolTest.java @@ -17,15 +17,20 @@ import static java.util.Arrays.asList; import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; import static org.hyperledger.besu.ethereum.mainnet.ValidationResult.valid; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_TOO_LOW; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INVALID_TRANSACTION_FORMAT; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURE_REQUIRED; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED; import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TX_FEECAP_EXCEEDED; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.doNothing; @@ -39,17 +44,24 @@ import static org.mockito.Mockito.when; import static org.mockito.quality.Strictness.LENIENT; +import org.hyperledger.besu.config.StubGenesisConfigOptions; import org.hyperledger.besu.crypto.KeyPair; import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.TransactionType; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.ProtocolContext; import org.hyperledger.besu.ethereum.chain.MutableBlockchain; import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockBody; import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; +import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; import org.hyperledger.besu.ethereum.core.Difficulty; import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; import org.hyperledger.besu.ethereum.core.MiningParameters; +import org.hyperledger.besu.ethereum.core.PrivacyParameters; import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionReceipt; import org.hyperledger.besu.ethereum.core.TransactionTestFixture; import org.hyperledger.besu.ethereum.eth.manager.EthContext; import org.hyperledger.besu.ethereum.eth.manager.EthPeer; @@ -58,30 +70,40 @@ import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; import org.hyperledger.besu.ethereum.eth.messages.EthPV65; +import org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredTransactionPoolBaseFeeTest; +import org.hyperledger.besu.ethereum.eth.transactions.sorter.LegacyTransactionPoolBaseFeeTest; +import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; +import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; +import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; import org.hyperledger.besu.ethereum.mainnet.TransactionValidatorFactory; import org.hyperledger.besu.ethereum.mainnet.ValidationResult; import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; import org.hyperledger.besu.evm.account.Account; +import org.hyperledger.besu.evm.internal.EvmConfiguration; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidator; import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionValidatorFactory; import java.math.BigInteger; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Answers; @@ -98,9 +120,9 @@ public abstract class AbstractTransactionPoolTest { protected static final int MAX_TRANSACTIONS = 5; protected static final KeyPair KEY_PAIR1 = SignatureAlgorithmFactory.getInstance().generateKeyPair(); - private static final KeyPair KEY_PAIR2 = SignatureAlgorithmFactory.getInstance().generateKeyPair(); + protected static final Wei BASE_FEE_FLOOR = Wei.of(7L); @Mock(answer = Answers.RETURNS_DEEP_STUBS) protected TransactionValidatorFactory transactionValidatorFactory; @@ -115,26 +137,80 @@ public abstract class AbstractTransactionPoolTest { protected final MetricsSystem metricsSystem = new NoOpMetricsSystem(); protected MutableBlockchain blockchain; - private TransactionBroadcaster transactionBroadcaster; + protected TransactionBroadcaster transactionBroadcaster; protected PendingTransactions transactions; - private final Transaction transaction1 = createTransaction(1); - private final Transaction transaction2 = createTransaction(2); + protected final Transaction transaction0 = createTransaction(0); + protected final Transaction transaction1 = createTransaction(1); - private final Transaction transactionOtherSender = createTransaction(1, KEY_PAIR2); + protected final Transaction transactionOtherSender = createTransaction(1, KEY_PAIR2); private ExecutionContextTestFixture executionContext; protected ProtocolContext protocolContext; protected TransactionPool transactionPool; protected long blockGasLimit; protected EthProtocolManager ethProtocolManager; - private EthContext ethContext; + protected EthContext ethContext; private PeerTransactionTracker peerTransactionTracker; private ArgumentCaptor syncTaskCapture; - protected abstract PendingTransactions createPendingTransactionsSorter(); + protected abstract PendingTransactions createPendingTransactions( + final TransactionPoolConfiguration poolConfig, + final BiFunction + transactionReplacementTester); + + protected TransactionTestFixture createBaseTransactionGasPriceMarket( + final int transactionNumber) { + return new TransactionTestFixture() + .nonce(transactionNumber) + .gasLimit(blockGasLimit) + .type(TransactionType.FRONTIER); + } + + protected TransactionTestFixture createBaseTransactionBaseFeeMarket(final int nonce) { + return new TransactionTestFixture() + .nonce(nonce) + .gasLimit(blockGasLimit) + .gasPrice(null) + .maxFeePerGas(Optional.of(Wei.of(5000L))) + .maxPriorityFeePerGas(Optional.of(Wei.of(1000L))) + .type(TransactionType.EIP1559); + } protected abstract ExecutionContextTestFixture createExecutionContextTestFixture(); + protected static ExecutionContextTestFixture createExecutionContextTestFixtureBaseFeeMarket() { + final ProtocolSchedule protocolSchedule = + new ProtocolScheduleBuilder( + new StubGenesisConfigOptions().londonBlock(0L).baseFeePerGas(10L), + BigInteger.valueOf(1), + ProtocolSpecAdapters.create(0, Function.identity()), + new PrivacyParameters(), + false, + EvmConfiguration.DEFAULT) + .createProtocolSchedule(); + final ExecutionContextTestFixture executionContextTestFixture = + ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); + + final Block block = + new Block( + new BlockHeaderTestFixture() + .gasLimit( + executionContextTestFixture + .getBlockchain() + .getChainHeadBlock() + .getHeader() + .getGasLimit()) + .difficulty(Difficulty.ONE) + .baseFeePerGas(Wei.of(10L)) + .parentHash(executionContextTestFixture.getBlockchain().getChainHeadHash()) + .number(executionContextTestFixture.getBlockchain().getChainHeadBlockNumber() + 1) + .buildHeader(), + new BlockBody(List.of(), List.of())); + executionContextTestFixture.getBlockchain().appendBlock(block, List.of()); + + return executionContextTestFixture; + } + protected abstract FeeMarket getFeeMarket(); @BeforeEach @@ -142,7 +218,6 @@ public void setUp() { executionContext = createExecutionContextTestFixture(); protocolContext = executionContext.getProtocolContext(); blockchain = executionContext.getBlockchain(); - transactions = spy(createPendingTransactionsSorter()); when(protocolSpec.getTransactionValidatorFactory()).thenReturn(transactionValidatorFactory); when(protocolSpec.getFeeMarket()).thenReturn(getFeeMarket()); protocolSchedule = spy(executionContext.getProtocolSchedule()); @@ -179,13 +254,23 @@ protected TransactionPool createTransactionPool( return createTransactionPool(configConsumer, null); } - protected TransactionPool createTransactionPool( + private TransactionPool createTransactionPool( final Consumer configConsumer, final PluginTransactionValidatorFactory pluginTransactionValidatorFactory) { final ImmutableTransactionPoolConfiguration.Builder configBuilder = ImmutableTransactionPoolConfiguration.builder(); configConsumer.accept(configBuilder); - final TransactionPoolConfiguration config = configBuilder.build(); + final TransactionPoolConfiguration poolConfig = configBuilder.build(); + + final TransactionPoolReplacementHandler transactionReplacementHandler = + new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); + + final BiFunction transactionReplacementTester = + (t1, t2) -> + transactionReplacementHandler.shouldReplace( + t1, t2, protocolContext.getBlockchain().getChainHeadHeader()); + + transactions = spy(createPendingTransactions(poolConfig, transactionReplacementTester)); final TransactionPool txPool = new TransactionPool( @@ -196,9 +281,8 @@ protected TransactionPool createTransactionPool( ethContext, miningParameters, new TransactionPoolMetrics(metricsSystem), - config, + poolConfig, pluginTransactionValidatorFactory); - txPool.setEnabled(); return txPool; } @@ -211,23 +295,22 @@ public void localTransactionHappyPath(final boolean disableLocalTxs) { givenTransactionIsValid(transaction); - assertTransactionViaApiValid(transaction, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction, disableLocalTxs); } @ParameterizedTest @ValueSource(booleans = {true, false}) - public void shouldReturnExclusivelyLocalTransactionsWhenAppropriate( - final boolean disableLocalTxs) { + public void shouldReturnLocalTransactionsWhenAppropriate(final boolean disableLocalTxs) { this.transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - final Transaction localTransaction0 = createTransaction(0); + final Transaction localTransaction2 = createTransaction(2); - givenTransactionIsValid(localTransaction0); + givenTransactionIsValid(localTransaction2); + givenTransactionIsValid(transaction0); givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - assertTransactionViaApiValid(localTransaction0, disableLocalTxs); - assertRemoteTransactionValid(transaction1); - assertRemoteTransactionValid(transaction2); + addAndAssertTransactionViaApiValid(localTransaction2, disableLocalTxs); + addAndAssertRemoteTransactionValid(transaction0); + addAndAssertRemoteTransactionValid(transaction1); assertThat(transactions.size()).isEqualTo(3); List localTransactions = transactions.getLocalTransactions(); @@ -236,74 +319,92 @@ public void shouldReturnExclusivelyLocalTransactionsWhenAppropriate( @Test public void shouldRemoveTransactionsFromPendingListWhenIncludedInBlockOnchain() { - transactions.addRemoteTransaction(transaction1, Optional.empty()); - assertTransactionPending(transaction1); - appendBlock(transaction1); + givenTransactionIsValid(transaction0); - assertTransactionNotPending(transaction1); + addAndAssertRemoteTransactionValid(transaction0); + + appendBlock(transaction0); + + assertTransactionNotPending(transaction0); } @Test public void shouldRemoveMultipleTransactionsAddedInOneBlock() { - transactions.addRemoteTransaction(transaction1, Optional.empty()); - transactions.addRemoteTransaction(transaction2, Optional.empty()); - appendBlock(transaction1, transaction2); + givenTransactionIsValid(transaction0); + givenTransactionIsValid(transaction1); + + addAndAssertRemoteTransactionValid(transaction0); + addAndAssertRemoteTransactionValid(transaction1); + + appendBlock(transaction0, transaction1); + assertTransactionNotPending(transaction0); assertTransactionNotPending(transaction1); - assertTransactionNotPending(transaction2); assertThat(transactions.size()).isZero(); } @Test public void shouldIgnoreUnknownTransactionsThatAreAddedInABlock() { - transactions.addRemoteTransaction(transaction1, Optional.empty()); - appendBlock(transaction1, transaction2); + givenTransactionIsValid(transaction0); + addAndAssertRemoteTransactionValid(transaction0); + + appendBlock(transaction0, transaction1); + + assertTransactionNotPending(transaction0); assertTransactionNotPending(transaction1); - assertTransactionNotPending(transaction2); assertThat(transactions.size()).isZero(); } @Test public void shouldNotRemovePendingTransactionsWhenABlockAddedToAFork() { - transactions.addRemoteTransaction(transaction1, Optional.empty()); + givenTransactionIsValid(transaction0); + + addAndAssertRemoteTransactionValid(transaction0); + final BlockHeader commonParent = getHeaderForCurrentChainHead(); final Block canonicalHead = appendBlock(Difficulty.of(1000), commonParent); - appendBlock(Difficulty.ONE, commonParent, transaction1); + appendBlock(Difficulty.ONE, commonParent, transaction0); verifyChainHeadIs(canonicalHead); - assertTransactionPending(transaction1); + assertTransactionPending(transaction0); } @Test public void shouldRemovePendingTransactionsFromAllBlocksOnAForkWhenItBecomesTheCanonicalChain() { - transactions.addRemoteTransaction(transaction1, Optional.empty()); - transactions.addRemoteTransaction(transaction2, Optional.empty()); + givenTransactionIsValid(transaction0); + givenTransactionIsValid(transaction1); + + addAndAssertRemoteTransactionValid(transaction0); + addAndAssertRemoteTransactionValid(transaction1); + final BlockHeader commonParent = getHeaderForCurrentChainHead(); final Block originalChainHead = appendBlock(Difficulty.of(1000), commonParent); - final Block forkBlock1 = appendBlock(Difficulty.ONE, commonParent, transaction1); + final Block forkBlock1 = appendBlock(Difficulty.ONE, commonParent, transaction0); verifyChainHeadIs(originalChainHead); - final Block forkBlock2 = appendBlock(Difficulty.of(2000), forkBlock1.getHeader(), transaction2); + final Block forkBlock2 = appendBlock(Difficulty.of(2000), forkBlock1.getHeader(), transaction1); verifyChainHeadIs(forkBlock2); + assertTransactionNotPending(transaction0); assertTransactionNotPending(transaction1); - assertTransactionNotPending(transaction2); } @Test public void shouldReAddTransactionsFromThePreviousCanonicalHeadWhenAReorgOccurs() { - givenTransactionIsValid(transaction1); + givenTransactionIsValid(transaction0); givenTransactionIsValid(transactionOtherSender); - transactions.addLocalTransaction(transaction1, Optional.empty()); - transactions.addRemoteTransaction(transactionOtherSender, Optional.empty()); + + transactionPool.addTransactionViaApi(transaction0); + transactionPool.addRemoteTransactions(List.of(transactionOtherSender)); + final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction1); + final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction0); final Block originalFork2 = appendBlock(Difficulty.ONE, originalFork1.getHeader(), transactionOtherSender); - assertTransactionNotPending(transaction1); + assertTransactionNotPending(transaction0); assertTransactionNotPending(transactionOtherSender); assertThat(transactions.getLocalTransactions()).isEmpty(); @@ -314,36 +415,38 @@ public void shouldReAddTransactionsFromThePreviousCanonicalHeadWhenAReorgOccurs( final Block reorgFork2 = appendBlock(Difficulty.of(2000), reorgFork1.getHeader()); verifyChainHeadIs(reorgFork2); - assertTransactionPending(transaction1); + assertTransactionPending(transaction0); assertTransactionPending(transactionOtherSender); - assertThat(transactions.getLocalTransactions()).contains(transaction1); + assertThat(transactions.getLocalTransactions()).contains(transaction0); assertThat(transactions.getLocalTransactions()).doesNotContain(transactionOtherSender); - verify(listener).onTransactionAdded(transaction1); + verify(listener).onTransactionAdded(transaction0); verify(listener).onTransactionAdded(transactionOtherSender); verifyNoMoreInteractions(listener); } @Test public void shouldNotReAddTransactionsThatAreInBothForksWhenReorgHappens() { + givenTransactionIsValid(transaction0); givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - transactions.addRemoteTransaction(transaction1, Optional.empty()); - transactions.addRemoteTransaction(transaction2, Optional.empty()); + + addAndAssertRemoteTransactionValid(transaction0); + addAndAssertRemoteTransactionValid(transaction1); + final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction1); + final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction0); final Block originalFork2 = - appendBlock(Difficulty.ONE, originalFork1.getHeader(), transaction2); + appendBlock(Difficulty.ONE, originalFork1.getHeader(), transaction1); + assertTransactionNotPending(transaction0); assertTransactionNotPending(transaction1); - assertTransactionNotPending(transaction2); - final Block reorgFork1 = appendBlock(Difficulty.ONE, commonParent, transaction1); + final Block reorgFork1 = appendBlock(Difficulty.ONE, commonParent, transaction0); verifyChainHeadIs(originalFork2); final Block reorgFork2 = appendBlock(Difficulty.of(2000), reorgFork1.getHeader()); verifyChainHeadIs(reorgFork2); - assertTransactionNotPending(transaction1); - assertTransactionPending(transaction2); + assertTransactionNotPending(transaction0); + assertTransactionPending(transaction1); } @ParameterizedTest @@ -359,7 +462,7 @@ public void addLocalTransaction_strictReplayProtectionOn_txWithChainId_chainIdIs final Transaction tx = createTransaction(1); givenTransactionIsValid(tx); - assertTransactionViaApiValid(tx, disableLocalTxs); + addAndAssertTransactionViaApiValid(tx, disableLocalTxs); } @Test @@ -369,7 +472,7 @@ public void addRemoteTransactions_strictReplayProtectionOn_txWithChainId_chainId final Transaction tx = createTransaction(1); givenTransactionIsValid(tx); - assertRemoteTransactionValid(tx); + addAndAssertRemoteTransactionValid(tx); } @Test @@ -381,39 +484,26 @@ public void shouldNotAddRemoteTransactionsWhenGasPriceBelowMinimum() { verifyNoMoreInteractions(transactionValidatorFactory); } - @Test - public void shouldNotAddRemoteTransactionsWhenThereIsAnLowestInvalidNonceForTheSender() { - givenTransactionIsValid(transaction2); - when(transactionValidatorFactory.get().validate(eq(transaction1), any(Optional.class), any())) - .thenReturn(ValidationResult.invalid(NONCE_TOO_LOW)); - - transactionPool.addRemoteTransactions(asList(transaction1, transaction2)); - - assertTransactionNotPending(transaction1); - assertTransactionNotPending(transaction2); - verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(transaction2)); - } - @Test public void shouldNotAddRemoteTransactionsThatAreInvalidAccordingToStateDependentChecks() { + givenTransactionIsValid(transaction0); givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); when(transactionValidatorFactory .get() - .validateForSender(eq(transaction2), eq(null), any(TransactionValidationParams.class))) + .validateForSender(eq(transaction1), eq(null), any(TransactionValidationParams.class))) .thenReturn(ValidationResult.invalid(NONCE_TOO_LOW)); - transactionPool.addRemoteTransactions(asList(transaction1, transaction2)); + transactionPool.addRemoteTransactions(asList(transaction0, transaction1)); - assertTransactionPending(transaction1); - assertTransactionNotPending(transaction2); - verify(transactionBroadcaster).onTransactionsAdded(singletonList(transaction1)); + assertTransactionPending(transaction0); + assertTransactionNotPending(transaction1); + verify(transactionBroadcaster).onTransactionsAdded(singletonList(transaction0)); verify(transactionValidatorFactory.get()) - .validate(eq(transaction1), any(Optional.class), any()); + .validate(eq(transaction0), any(Optional.class), any()); verify(transactionValidatorFactory.get()) - .validateForSender(eq(transaction1), eq(null), any(TransactionValidationParams.class)); + .validateForSender(eq(transaction0), eq(null), any(TransactionValidationParams.class)); verify(transactionValidatorFactory.get()) - .validate(eq(transaction2), any(Optional.class), any()); - verify(transactionValidatorFactory.get()).validateForSender(eq(transaction2), any(), any()); + .validate(eq(transaction1), any(Optional.class), any()); + verify(transactionValidatorFactory.get()).validateForSender(eq(transaction1), any(), any()); verifyNoMoreInteractions(transactionValidatorFactory.get()); } @@ -430,46 +520,44 @@ public void shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSender( givenTransactionIsValid(transaction2); givenTransactionIsValid(transaction3); - assertTransactionViaApiValid(transaction1, disableLocalTxs); - assertTransactionViaApiValid(transaction2, disableLocalTxs); - assertTransactionViaApiValid(transaction3, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction1, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction2, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction3, disableLocalTxs); } @Test public void shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSenderWhenSentInBatchOutOfOrder() { - final Transaction transaction1 = createTransaction(1); final Transaction transaction2 = createTransaction(2); - final Transaction transaction3 = createTransaction(3); + givenTransactionIsValid(transaction0); givenTransactionIsValid(transaction1); givenTransactionIsValid(transaction2); - givenTransactionIsValid(transaction3); - assertRemoteTransactionValid(transaction3); - assertRemoteTransactionValid(transaction1); - assertRemoteTransactionValid(transaction2); + addAndAssertRemoteTransactionValid(transaction2); + addAndAssertRemoteTransactionValid(transaction0); + addAndAssertRemoteTransactionValid(transaction1); } @Test public void shouldDiscardRemoteTransactionThatAlreadyExistsBeforeValidation() { - doReturn(true).when(transactions).containsTransaction(transaction1); - transactionPool.addRemoteTransactions(singletonList(transaction1)); + doReturn(true).when(transactions).containsTransaction(transaction0); + transactionPool.addRemoteTransactions(singletonList(transaction0)); - verify(transactions).containsTransaction(transaction1); + verify(transactions).containsTransaction(transaction0); verifyNoInteractions(transactionValidatorFactory); } @Test public void shouldNotNotifyBatchListenerWhenRemoteTransactionDoesNotReplaceExisting() { - final Transaction transaction1 = createTransaction(1, Wei.of(100)); - final Transaction transaction2 = createTransaction(1, Wei.of(50)); + final Transaction transaction0a = createTransaction(0, Wei.of(100)); + final Transaction transaction0b = createTransaction(0, Wei.of(50)); - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); + givenTransactionIsValid(transaction0a); + givenTransactionIsValid(transaction0b); - assertRemoteTransactionValid(transaction1); - assertRemoteTransactionInvalid(transaction2); + addAndAssertRemoteTransactionValid(transaction0a); + addAndAssertRemoteTransactionInvalid(transaction0b); } @ParameterizedTest @@ -477,48 +565,34 @@ public void shouldNotNotifyBatchListenerWhenRemoteTransactionDoesNotReplaceExist public void shouldNotNotifyBatchListenerWhenLocalTransactionDoesNotReplaceExisting( final boolean disableLocalTxs) { transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - final Transaction transaction1 = createTransaction(1, Wei.of(10)); - final Transaction transaction2 = createTransaction(1, Wei.of(9)); + final Transaction transaction0a = createTransaction(0, Wei.of(10)); + final Transaction transaction0b = createTransaction(0, Wei.of(9)); - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); + givenTransactionIsValid(transaction0a); + givenTransactionIsValid(transaction0b); - assertTransactionViaApiValid(transaction1, disableLocalTxs); - assertTransactionViaApiInvalid(transaction2, TRANSACTION_REPLACEMENT_UNDERPRICED); + addAndAssertTransactionViaApiValid(transaction0a, disableLocalTxs); + addAndAssertTransactionViaApiInvalid(transaction0b, TRANSACTION_REPLACEMENT_UNDERPRICED); } @Test public void shouldRejectLocalTransactionsWhereGasLimitExceedBlockGasLimit() { - final Transaction transaction1 = + final Transaction transaction0 = createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - givenTransactionIsValid(transaction1); + givenTransactionIsValid(transaction0); - assertTransactionViaApiInvalid(transaction1, EXCEEDS_BLOCK_GAS_LIMIT); + addAndAssertTransactionViaApiInvalid(transaction0, EXCEEDS_BLOCK_GAS_LIMIT); } @Test public void shouldRejectRemoteTransactionsWhereGasLimitExceedBlockGasLimit() { - final Transaction transaction1 = - createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - - givenTransactionIsValid(transaction1); - - assertRemoteTransactionInvalid(transaction1); - } - - @Test - public void shouldRejectRemoteTransactionsAnInvalidTransactionWithLowerNonceExists() { - final Transaction invalidTx = + final Transaction transaction0 = createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - final Transaction nextTx = createTransaction(1); - - givenTransactionIsValid(invalidTx); - givenTransactionIsValid(nextTx); + givenTransactionIsValid(transaction0); - assertRemoteTransactionInvalid(invalidTx); - assertRemoteTransactionInvalid(nextTx); + addAndAssertRemoteTransactionInvalid(transaction0); } @Test @@ -532,19 +606,19 @@ public void shouldAcceptLocalTransactionsEvenIfAnInvalidTransactionWithLowerNonc givenTransactionIsValid(invalidTx); givenTransactionIsValid(nextTx); - assertTransactionViaApiInvalid(invalidTx, EXCEEDS_BLOCK_GAS_LIMIT); - assertTransactionViaApiValid(nextTx, false); + addAndAssertTransactionViaApiInvalid(invalidTx, EXCEEDS_BLOCK_GAS_LIMIT); + addAndAssertTransactionViaApiValid(nextTx, false); } @ParameterizedTest @ValueSource(booleans = {true, false}) public void shouldRejectLocalTransactionsWhenNonceTooFarInFuture(final boolean disableLocalTxs) { transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - final Transaction transaction1 = createTransaction(Integer.MAX_VALUE); + final Transaction transactionFarFuture = createTransaction(Integer.MAX_VALUE); - givenTransactionIsValid(transaction1); + givenTransactionIsValid(transactionFarFuture); - assertTransactionViaApiInvalid(transaction1, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER); + addAndAssertTransactionViaApiInvalid(transactionFarFuture, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER); } @Test @@ -558,8 +632,8 @@ public void shouldSendPooledTransactionHashesIfPeerSupportsEth65() { EthPeer peer = mock(EthPeer.class); when(peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)).thenReturn(true); - givenTransactionIsValid(transaction1); - transactionPool.addLocalTransaction(transaction1); + givenTransactionIsValid(transaction0); + transactionPool.addTransactionViaApi(transaction0); transactionPool.handleConnect(peer); syncTaskCapture.getValue().run(); verify(newPooledTransactionHashesMessageSender).sendTransactionHashesToPeer(peer); @@ -570,8 +644,8 @@ public void shouldSendFullTransactionsIfPeerDoesNotSupportEth65() { EthPeer peer = mock(EthPeer.class); when(peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)).thenReturn(false); - givenTransactionIsValid(transaction1); - transactionPool.addLocalTransaction(transaction1); + givenTransactionIsValid(transaction0); + transactionPool.addTransactionViaApi(transaction0); transactionPool.handleConnect(peer); syncTaskCapture.getValue().run(); verify(transactionsMessageSender).sendTransactionsToPeer(peer); @@ -579,21 +653,18 @@ public void shouldSendFullTransactionsIfPeerDoesNotSupportEth65() { @Test public void shouldSendFullTransactionPoolToNewlyConnectedPeer() { - final Transaction transactionLocal = createTransaction(1); - final Transaction transactionRemote = createTransaction(2); - - givenTransactionIsValid(transactionLocal); - givenTransactionIsValid(transactionRemote); + givenTransactionIsValid(transaction0); + givenTransactionIsValid(transaction1); - transactionPool.addLocalTransaction(transactionLocal); - transactionPool.addRemoteTransactions(Collections.singletonList(transactionRemote)); + transactionPool.addTransactionViaApi(transaction0); + transactionPool.addRemoteTransactions(Collections.singletonList(transaction1)); RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); Set transactionsToSendToPeer = peerTransactionTracker.claimTransactionsToSendToPeer(peer.getEthPeer()); - assertThat(transactionsToSendToPeer).contains(transactionLocal, transactionRemote); + assertThat(transactionsToSendToPeer).contains(transaction0, transaction1); } @Test @@ -601,7 +672,7 @@ public void shouldCallValidatorWithExpectedValidationParameters() { final ArgumentCaptor txValidationParamCaptor = ArgumentCaptor.forClass(TransactionValidationParams.class); - when(transactionValidatorFactory.get().validate(eq(transaction1), any(Optional.class), any())) + when(transactionValidatorFactory.get().validate(eq(transaction0), any(Optional.class), any())) .thenReturn(valid()); when(transactionValidatorFactory .get() @@ -611,7 +682,7 @@ public void shouldCallValidatorWithExpectedValidationParameters() { final TransactionValidationParams expectedValidationParams = TransactionValidationParams.transactionPool(); - transactionPool.addLocalTransaction(transaction1); + transactionPool.addTransactionViaApi(transaction0); assertThat(txValidationParamCaptor.getValue()) .usingRecursiveComparison() @@ -624,11 +695,11 @@ public void shouldIgnoreFeeCapIfSetZero(final boolean disableLocalTxs) { final Wei twoEthers = Wei.fromEth(2); transactionPool = createTransactionPool(b -> b.txFeeCap(Wei.ZERO).disableLocalTransactions(disableLocalTxs)); - final Transaction transaction = createTransaction(1, twoEthers.add(Wei.of(1))); + final Transaction transaction = createTransaction(0, twoEthers.add(Wei.of(1))); givenTransactionIsValid(transaction); - assertTransactionViaApiValid(transaction, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction, disableLocalTxs); } @Test @@ -637,11 +708,11 @@ public void shouldRejectLocalTransactionIfFeeCapExceeded() { transactionPool = createTransactionPool(b -> b.txFeeCap(twoEthers).disableLocalTransactions(false)); - final Transaction transactionLocal = createTransaction(1, twoEthers.add(1)); + final Transaction transactionLocal = createTransaction(0, twoEthers.add(1)); givenTransactionIsValid(transactionLocal); - assertTransactionViaApiInvalid(transactionLocal, TX_FEECAP_EXCEEDED); + addAndAssertTransactionViaApiInvalid(transactionLocal, TX_FEECAP_EXCEEDED); } @ParameterizedTest @@ -654,7 +725,7 @@ public void shouldRejectZeroGasPriceLocalTransactionWhenNotMining(final boolean givenTransactionIsValid(transaction); - assertTransactionViaApiInvalid(transaction, GAS_PRICE_TOO_LOW); + addAndAssertTransactionViaApiInvalid(transaction, GAS_PRICE_TOO_LOW); } @ParameterizedTest @@ -666,11 +737,9 @@ public void transactionNotRejectedByPluginShouldBeAdded(final boolean disableLoc createTransactionPool( b -> b.disableLocalTransactions(disableLocalTxs), pluginTransactionValidatorFactory); - final Transaction transaction = createTransaction(0); + givenTransactionIsValid(transaction0); - givenTransactionIsValid(transaction); - - assertTransactionViaApiValid(transaction, disableLocalTxs); + addAndAssertTransactionViaApiValid(transaction0, disableLocalTxs); } @ParameterizedTest @@ -682,12 +751,10 @@ public void transactionRejectedByPluginShouldNotBeAdded(final boolean disableLoc createTransactionPool( b -> b.disableLocalTransactions(disableLocalTxs), pluginTransactionValidatorFactory); - final Transaction transaction = createTransaction(0); + givenTransactionIsValid(transaction0); - givenTransactionIsValid(transaction); - - assertTransactionViaApiInvalid( - transaction, TransactionInvalidReason.PLUGIN_TX_VALIDATOR_INVALIDATED); + addAndAssertTransactionViaApiInvalid( + transaction0, TransactionInvalidReason.PLUGIN_TX_VALIDATOR_INVALIDATED); } @Test @@ -696,38 +763,344 @@ public void remoteTransactionRejectedByPluginShouldNotBeAdded() { getPluginTransactionValidatorFactoryReturning(false); this.transactionPool = createTransactionPool(b -> {}, pluginTransactionValidatorFactory); - final Transaction transaction = createTransaction(0); + givenTransactionIsValid(transaction0); + + addAndAssertRemoteTransactionInvalid(transaction0); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisabledIf("isBaseFeeMarket") + public void + addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured_protectionNotSupportedAtCurrentBlock( + final boolean disableLocalTxs) { + protocolSupportsTxReplayProtection(1337, false); + transactionPool = + createTransactionPool( + b -> + b.strictTransactionReplayProtectionEnabled(true) + .disableLocalTransactions(disableLocalTxs)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertTransactionViaApiValid(tx, disableLocalTxs); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void + addRemoteTransactions_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() { + protocolSupportsTxReplayProtection(1337, true); + transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertRemoteTransactionValid(tx); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisabledIf("isBaseFeeMarket") + public void addLocalTransaction_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured( + final boolean disableLocalTxs) { + protocolSupportsTxReplayProtection(1337, true); + transactionPool = + createTransactionPool( + b -> + b.strictTransactionReplayProtectionEnabled(false) + .disableLocalTransactions(disableLocalTxs)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertTransactionViaApiValid(tx, disableLocalTxs); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { + protocolSupportsTxReplayProtection(1337, true); + transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertTransactionViaApiInvalid(tx, REPLAY_PROTECTED_SIGNATURE_REQUIRED); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void + addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { + protocolSupportsTxReplayProtection(1337, true); + transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertRemoteTransactionValid(tx); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisabledIf("isBaseFeeMarket") + public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured( + final boolean disableLocalTxs) { + protocolDoesNotSupportTxReplayProtection(); + transactionPool = + createTransactionPool( + b -> + b.strictTransactionReplayProtectionEnabled(true) + .disableLocalTransactions(disableLocalTxs)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertTransactionViaApiValid(tx, disableLocalTxs); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void + addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() { + protocolDoesNotSupportTxReplayProtection(); + transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); + final Transaction tx = createTransactionWithoutChainId(1); + givenTransactionIsValid(tx); + + addAndAssertRemoteTransactionValid(tx); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void shouldIgnoreEIP1559TransactionWhenNotAllowed() { + final Transaction transaction = + createBaseTransaction(1) + .type(TransactionType.EIP1559) + .maxFeePerGas(Optional.of(Wei.of(100L))) + .maxPriorityFeePerGas(Optional.of(Wei.of(50L))) + .gasLimit(10) + .gasPrice(null) + .createTransaction(KEY_PAIR1); + + givenTransactionIsValid(transaction); + + addAndAssertTransactionViaApiInvalid(transaction, INVALID_TRANSACTION_FORMAT); + } + + @Test + @DisabledIf("isBaseFeeMarket") + public void shouldAcceptZeroGasPriceFrontierLocalTransactionsWhenMining() { + transactionPool = createTransactionPool(b -> b.disableLocalTransactions(false)); + when(miningParameters.isMiningEnabled()).thenReturn(true); + + final Transaction transaction = createTransaction(0, Wei.ZERO); + + givenTransactionIsValid(transaction); + + addAndAssertTransactionViaApiValid(transaction, false); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + @DisabledIf("isBaseFeeMarket") + public void shouldAcceptZeroGasPriceTransactionWhenMinGasPriceIsZero( + final boolean disableLocalTxs) { + transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); + when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); + + final Transaction transaction = createTransaction(0, Wei.ZERO); + + givenTransactionIsValid(transaction); + + addAndAssertTransactionViaApiValid(transaction, disableLocalTxs); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void shouldAcceptZeroGasPriceFrontierTxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee( + final boolean disableLocalTxs) { + transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); + when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); + when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); + whenBlockBaseFeeIs(Wei.ZERO); + + final Transaction frontierTransaction = createFrontierTransaction(0, Wei.ZERO); + + givenTransactionIsValid(frontierTransaction); + addAndAssertTransactionViaApiValid(frontierTransaction, disableLocalTxs); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void shouldAcceptZeroGasPrice1559TxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee( + final boolean disableLocalTxs) { + transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); + when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); + when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); + whenBlockBaseFeeIs(Wei.ZERO); + + final Transaction transaction = createTransaction(0, Wei.ZERO); givenTransactionIsValid(transaction); + addAndAssertTransactionViaApiValid(transaction, disableLocalTxs); + } + + @Test + public void shouldAcceptBaseFeeFloorGasPriceFrontierLocalTransactionsWhenMining() { + transactionPool = createTransactionPool(b -> b.disableLocalTransactions(false)); + final Transaction frontierTransaction = createFrontierTransaction(0, BASE_FEE_FLOOR); + + givenTransactionIsValid(frontierTransaction); + + addAndAssertTransactionViaApiValid(frontierTransaction, false); + } + + @Test + public void shouldRejectRemote1559TxsWhenMaxFeePerGasBelowMinGasPrice() { + final Wei genesisBaseFee = Wei.of(100L); + final Wei minGasPrice = Wei.of(200L); + final Wei lastBlockBaseFee = minGasPrice.add(50L); + final Wei txMaxFeePerGas = minGasPrice.subtract(1L); + + assertThat( + add1559TxAndGetPendingTxsCount( + genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) + .isEqualTo(0); + } + + @Test + public void shouldAcceptRemote1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinGasPrice() { + final Wei genesisBaseFee = Wei.of(100L); + final Wei minGasPrice = Wei.of(200L); + final Wei lastBlockBaseFee = minGasPrice.add(50L); + final Wei txMaxFeePerGas = minGasPrice; + + assertThat( + add1559TxAndGetPendingTxsCount( + genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) + .isEqualTo(1); + } + + @Test + public void shouldRejectLocal1559TxsWhenMaxFeePerGasBelowMinGasPrice() { + final Wei genesisBaseFee = Wei.of(100L); + final Wei minGasPrice = Wei.of(200L); + final Wei lastBlockBaseFee = minGasPrice.add(50L); + final Wei txMaxFeePerGas = minGasPrice.subtract(1L); + + assertThat( + add1559TxAndGetPendingTxsCount( + genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) + .isEqualTo(0); + } + + @Test + public void shouldAcceptLocal1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinMinGasPrice() { + final Wei genesisBaseFee = Wei.of(100L); + final Wei minGasPrice = Wei.of(200L); + final Wei lastBlockBaseFee = minGasPrice.add(50L); + final Wei txMaxFeePerGas = minGasPrice; + + assertThat( + add1559TxAndGetPendingTxsCount( + genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) + .isEqualTo(1); + } + + @Test + public void addRemoteTransactionsShouldAllowDuplicates() { + final Transaction transaction1 = createTransaction(1, Wei.of(7L)); + final Transaction transaction2 = createTransaction(2, Wei.of(7L)); + final Transaction transaction3 = createTransaction(2, Wei.of(7L)); + final Transaction transaction4 = createTransaction(3, Wei.of(7L)); + + givenTransactionIsValid(transaction1); + givenTransactionIsValid(transaction2); + givenTransactionIsValid(transaction3); + givenTransactionIsValid(transaction4); - assertRemoteTransactionInvalid(transaction); + assertThatCode( + () -> + transactionPool.addRemoteTransactions( + List.of(transaction1, transaction2, transaction3, transaction4))) + .doesNotThrowAnyException(); } - @NotNull private static PluginTransactionValidatorFactory getPluginTransactionValidatorFactoryReturning( final boolean b) { final PluginTransactionValidator pluginTransactionValidator = transaction -> b; return () -> pluginTransactionValidator; } - private void assertTransactionPending(final Transaction t) { - assertThat(transactions.getTransactionByHash(t.getHash())).contains(t); + @SuppressWarnings("unused") + private static boolean isBaseFeeMarket(final ExtensionContext extensionContext) { + final Class cz = extensionContext.getTestClass().get(); + + return cz.equals(LegacyTransactionPoolBaseFeeTest.class) + || cz.equals(LayeredTransactionPoolBaseFeeTest.class); } - private void assertTransactionNotPending(final Transaction transaction) { + protected void assertTransactionNotPending(final Transaction transaction) { assertThat(transactions.getTransactionByHash(transaction.getHash())).isEmpty(); } - private void verifyChainHeadIs(final Block forkBlock2) { - assertThat(blockchain.getChainHeadHash()).isEqualTo(forkBlock2.getHash()); + protected void addAndAssertRemoteTransactionInvalid(final Transaction tx) { + transactionPool.addRemoteTransactions(List.of(tx)); + + verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); + assertTransactionNotPending(tx); } - private void appendBlock(final Transaction... transactionsToAdd) { - appendBlock(Difficulty.ONE, getHeaderForCurrentChainHead(), transactionsToAdd); + protected void assertTransactionPending(final Transaction t) { + assertThat(transactions.getTransactionByHash(t.getHash())).contains(t); } - private BlockHeader getHeaderForCurrentChainHead() { - return blockchain.getBlockHeader(blockchain.getChainHeadHash()).get(); + protected void addAndAssertRemoteTransactionValid(final Transaction... txs) { + transactionPool.addRemoteTransactions(List.of(txs)); + + verify(transactionBroadcaster) + .onTransactionsAdded( + argThat(btxs -> btxs.size() == txs.length && btxs.containsAll(List.of(txs)))); + Arrays.stream(txs).forEach(this::assertTransactionPending); + assertThat(transactions.getLocalTransactions()).doesNotContain(txs); + } + + protected void addAndAssertTransactionViaApiValid(final Transaction tx) { + addAndAssertTransactionViaApiValid(tx, false); + } + + protected void addAndAssertTransactionViaApiValid( + final Transaction tx, final boolean disableLocals) { + final ValidationResult result = + transactionPool.addTransactionViaApi(tx); + + assertThat(result.isValid()).isTrue(); + assertTransactionPending(tx); + verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); + if (disableLocals) { + assertThat(transactions.getLocalTransactions()).doesNotContain(tx); + } else { + assertThat(transactions.getLocalTransactions()).contains(tx); + } + } + + protected void addAndAssertTransactionViaApiInvalid( + final Transaction tx, final TransactionInvalidReason invalidReason) { + final ValidationResult result = + transactionPool.addTransactionViaApi(tx); + + assertThat(result.isValid()).isFalse(); + assertThat(result.getInvalidReason()).isEqualTo(invalidReason); + assertTransactionNotPending(tx); + verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); + } + + @SuppressWarnings("unchecked") + protected void givenTransactionIsValid(final Transaction transaction) { + when(transactionValidatorFactory.get().validate(eq(transaction), any(Optional.class), any())) + .thenReturn(valid()); + when(transactionValidatorFactory + .get() + .validateForSender( + eq(transaction), nullable(Account.class), any(TransactionValidationParams.class))) + .thenReturn(valid()); } protected abstract Block appendBlock( @@ -735,19 +1108,47 @@ protected abstract Block appendBlock( final BlockHeader parentBlock, final Transaction... transactionsToAdd); - protected abstract Transaction createTransaction( - final int transactionNumber, final Optional maybeChainId); + protected Transaction createTransactionGasPriceMarket( + final int transactionNumber, final Wei maxPrice) { + return createBaseTransaction(transactionNumber).gasPrice(maxPrice).createTransaction(KEY_PAIR1); + } + + protected Transaction createTransactionBaseFeeMarket(final int nonce, final Wei maxPrice) { + return createBaseTransaction(nonce) + .maxFeePerGas(Optional.of(maxPrice)) + .maxPriorityFeePerGas(Optional.of(maxPrice.divide(5L))) + .createTransaction(KEY_PAIR1); + } + + protected abstract TransactionTestFixture createBaseTransaction(final int nonce); + + protected Transaction createTransaction( + final int transactionNumber, final Optional maybeChainId) { + return createBaseTransaction(transactionNumber) + .chainId(maybeChainId) + .createTransaction(KEY_PAIR1); + } + + protected abstract Transaction createTransaction(final int nonce, final Wei maxPrice); - protected abstract Transaction createTransaction(final int transactionNumber, final Wei maxPrice); + protected Transaction createTransaction(final int nonce) { + return createTransaction(nonce, Optional.of(BigInteger.ONE)); + } + + protected Transaction createTransaction(final int nonce, final KeyPair keyPair) { + return createBaseTransaction(nonce).createTransaction(keyPair); + } - protected abstract TransactionTestFixture createBaseTransaction(final int transactionNumber); + protected void verifyChainHeadIs(final Block forkBlock2) { + assertThat(blockchain.getChainHeadHash()).isEqualTo(forkBlock2.getHash()); + } - private Transaction createTransaction(final int transactionNumber) { - return createTransaction(transactionNumber, Optional.of(BigInteger.ONE)); + protected BlockHeader getHeaderForCurrentChainHead() { + return blockchain.getBlockHeader(blockchain.getChainHeadHash()).get(); } - private Transaction createTransaction(final int transactionNumber, final KeyPair keyPair) { - return createBaseTransaction(transactionNumber).createTransaction(keyPair); + protected void appendBlock(final Transaction... transactionsToAdd) { + appendBlock(Difficulty.ONE, getHeaderForCurrentChainHead(), transactionsToAdd); } protected void protocolSupportsTxReplayProtection( @@ -756,53 +1157,98 @@ protected void protocolSupportsTxReplayProtection( when(protocolSchedule.getChainId()).thenReturn(Optional.of(BigInteger.valueOf(chainId))); } - protected void givenTransactionIsValid(final Transaction transaction) { - when(transactionValidatorFactory.get().validate(eq(transaction), any(Optional.class), any())) - .thenReturn(valid()); - when(transactionValidatorFactory - .get() - .validateForSender( - eq(transaction), nullable(Account.class), any(TransactionValidationParams.class))) - .thenReturn(valid()); + protected void protocolDoesNotSupportTxReplayProtection() { + when(protocolSchedule.getChainId()).thenReturn(Optional.empty()); } - protected void assertTransactionViaApiInvalid( - final Transaction tx, final TransactionInvalidReason invalidReason) { - final ValidationResult result = - transactionPool.addTransactionViaApi(tx); + protected Transaction createTransactionWithoutChainId(final int transactionNumber) { + return createTransaction(transactionNumber, Optional.empty()); + } - assertThat(result.isValid()).isFalse(); - assertThat(result.getInvalidReason()).isEqualTo(invalidReason); - assertTransactionNotPending(tx); - verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); + protected void whenBlockBaseFeeIs(final Wei baseFee) { + final BlockHeader header = + BlockHeaderBuilder.fromHeader(blockchain.getChainHeadHeader()) + .baseFee(baseFee) + .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) + .parentHash(blockchain.getChainHeadHash()) + .buildBlockHeader(); + blockchain.appendBlock(new Block(header, BlockBody.empty()), emptyList()); } - protected void assertTransactionViaApiValid(final Transaction tx, final boolean disableLocals) { - final ValidationResult result = - transactionPool.addTransactionViaApi(tx); + protected Transaction createFrontierTransaction(final int transactionNumber, final Wei gasPrice) { + return new TransactionTestFixture() + .nonce(transactionNumber) + .gasPrice(gasPrice) + .gasLimit(blockGasLimit) + .type(TransactionType.FRONTIER) + .createTransaction(KEY_PAIR1); + } - assertThat(result.isValid()).isTrue(); - assertTransactionPending(tx); - verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); - if (disableLocals) { - assertThat(transactions.getLocalTransactions()).doesNotContain(tx); + protected int add1559TxAndGetPendingTxsCount( + final Wei genesisBaseFee, + final Wei minGasPrice, + final Wei lastBlockBaseFee, + final Wei txMaxFeePerGas, + final boolean isLocal) { + when(miningParameters.getMinTransactionGasPrice()).thenReturn(minGasPrice); + when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(genesisBaseFee))); + whenBlockBaseFeeIs(lastBlockBaseFee); + + final Transaction transaction = createTransaction(0, txMaxFeePerGas); + + givenTransactionIsValid(transaction); + + if (isLocal) { + transactionPool.addTransactionViaApi(transaction); } else { - assertThat(transactions.getLocalTransactions()).contains(tx); + transactionPool.addRemoteTransactions(List.of(transaction)); } - } - protected void assertRemoteTransactionValid(final Transaction tx) { - transactionPool.addRemoteTransactions(List.of(tx)); - - verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); - assertTransactionPending(tx); - assertThat(transactions.getLocalTransactions()).doesNotContain(tx); + return transactions.size(); } - protected void assertRemoteTransactionInvalid(final Transaction tx) { - transactionPool.addRemoteTransactions(List.of(tx)); + protected Block appendBlockGasPriceMarket( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction[] transactionsToAdd) { + final List transactionList = asList(transactionsToAdd); + final Block block = + new Block( + new BlockHeaderTestFixture() + .difficulty(difficulty) + .gasLimit(parentBlock.getGasLimit()) + .parentHash(parentBlock.getHash()) + .number(parentBlock.getNumber() + 1) + .buildHeader(), + new BlockBody(transactionList, emptyList())); + final List transactionReceipts = + transactionList.stream() + .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) + .collect(toList()); + blockchain.appendBlock(block, transactionReceipts); + return block; + } - verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); - assertTransactionNotPending(tx); + protected Block appendBlockBaseFeeMarket( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction[] transactionsToAdd) { + final List transactionList = asList(transactionsToAdd); + final Block block = + new Block( + new BlockHeaderTestFixture() + .baseFeePerGas(Wei.of(10L)) + .gasLimit(parentBlock.getGasLimit()) + .difficulty(difficulty) + .parentHash(parentBlock.getHash()) + .number(parentBlock.getNumber() + 1) + .buildHeader(), + new BlockBody(transactionList, emptyList())); + final List transactionReceipts = + transactionList.stream() + .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) + .collect(toList()); + blockchain.appendBlock(block, transactionReceipts); + return block; } } diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionsLayeredPendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionsLayeredPendingTransactionsTest.java deleted file mode 100644 index 3a6c9c8fbf2..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/AbstractTransactionsLayeredPendingTransactionsTest.java +++ /dev/null @@ -1,712 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.transactions; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.hyperledger.besu.ethereum.mainnet.ValidationResult.valid; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_TOO_LOW; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TRANSACTION_REPLACEMENT_UNDERPRICED; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.TX_FEECAP_EXCEEDED; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.crypto.KeyPair; -import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.ProtocolContext; -import org.hyperledger.besu.ethereum.chain.MutableBlockchain; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.Difficulty; -import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; -import org.hyperledger.besu.ethereum.core.MiningParameters; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; -import org.hyperledger.besu.ethereum.eth.manager.EthContext; -import org.hyperledger.besu.ethereum.eth.manager.EthPeer; -import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManager; -import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; -import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; -import org.hyperledger.besu.ethereum.eth.manager.RespondingEthPeer; -import org.hyperledger.besu.ethereum.eth.messages.EthPV65; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpec; -import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams; -import org.hyperledger.besu.ethereum.mainnet.TransactionValidatorFactory; -import org.hyperledger.besu.ethereum.mainnet.ValidationResult; -import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; -import org.hyperledger.besu.evm.account.Account; -import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; -import org.hyperledger.besu.plugin.services.MetricsSystem; - -import java.math.BigInteger; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import org.mockito.junit.jupiter.MockitoSettings; -import org.mockito.quality.Strictness; - -@SuppressWarnings("unchecked") -@ExtendWith(MockitoExtension.class) -@MockitoSettings(strictness = Strictness.LENIENT) -public abstract class AbstractTransactionsLayeredPendingTransactionsTest { - - protected static final KeyPair KEY_PAIR1 = - SignatureAlgorithmFactory.getInstance().generateKeyPair(); - - private static final KeyPair KEY_PAIR2 = - SignatureAlgorithmFactory.getInstance().generateKeyPair(); - - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - protected TransactionValidatorFactory transactionValidatorFactory; - - @Mock protected PendingTransactionAddedListener listener; - @Mock protected MiningParameters miningParameters; - @Mock protected TransactionsMessageSender transactionsMessageSender; - @Mock protected NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender; - @Mock protected ProtocolSpec protocolSpec; - - protected ProtocolSchedule protocolSchedule; - - protected final MetricsSystem metricsSystem = new NoOpMetricsSystem(); - protected MutableBlockchain blockchain; - private TransactionBroadcaster transactionBroadcaster; - - protected PendingTransactions transactions; - private final Transaction transaction0 = createTransaction(0); - private final Transaction transaction1 = createTransaction(1); - - private final Transaction transactionOtherSender = createTransaction(0, KEY_PAIR2); - private ExecutionContextTestFixture executionContext; - protected ProtocolContext protocolContext; - protected TransactionPool transactionPool; - protected TransactionPoolConfiguration poolConfig; - protected long blockGasLimit; - protected EthProtocolManager ethProtocolManager; - private EthContext ethContext; - private PeerTransactionTracker peerTransactionTracker; - private ArgumentCaptor syncTaskCapture; - - protected abstract PendingTransactions createPendingTransactionsSorter( - final TransactionPoolConfiguration poolConfig, - BiFunction transactionReplacementTester); - - protected abstract ExecutionContextTestFixture createExecutionContextTestFixture(); - - protected abstract FeeMarket getFeeMarket(); - - @BeforeEach - public void setUp() { - executionContext = createExecutionContextTestFixture(); - protocolContext = executionContext.getProtocolContext(); - blockchain = executionContext.getBlockchain(); - - when(protocolSpec.getTransactionValidatorFactory()).thenReturn(transactionValidatorFactory); - when(protocolSpec.getFeeMarket()).thenReturn(getFeeMarket()); - protocolSchedule = spy(executionContext.getProtocolSchedule()); - doReturn(protocolSpec).when(protocolSchedule).getByBlockHeader(any()); - blockGasLimit = blockchain.getChainHeadBlock().getHeader().getGasLimit(); - ethProtocolManager = EthProtocolManagerTestUtil.create(); - ethContext = spy(ethProtocolManager.ethContext()); - - final EthScheduler ethScheduler = mock(EthScheduler.class); - syncTaskCapture = ArgumentCaptor.forClass(Runnable.class); - doNothing().when(ethScheduler).scheduleSyncWorkerTask(syncTaskCapture.capture()); - doReturn(ethScheduler).when(ethContext).getScheduler(); - - peerTransactionTracker = new PeerTransactionTracker(); - - transactionPool = createTransactionPool(); - - blockchain.observeBlockAdded(transactionPool); - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.of(2)); - } - - protected TransactionPool createTransactionPool() { - return createTransactionPool(b -> {}); - } - - protected TransactionPool createTransactionPool( - final Consumer configConsumer) { - final ImmutableTransactionPoolConfiguration.Builder configBuilder = - ImmutableTransactionPoolConfiguration.builder(); - configConsumer.accept(configBuilder); - poolConfig = configBuilder.build(); - - final TransactionPoolReplacementHandler transactionReplacementHandler = - new TransactionPoolReplacementHandler(poolConfig.getPriceBump()); - - final BiFunction transactionReplacementTester = - (t1, t2) -> - transactionReplacementHandler.shouldReplace( - t1, t2, protocolContext.getBlockchain().getChainHeadHeader()); - - transactions = spy(createPendingTransactionsSorter(poolConfig, transactionReplacementTester)); - - transactionBroadcaster = - spy( - new TransactionBroadcaster( - ethContext, - peerTransactionTracker, - transactionsMessageSender, - newPooledTransactionHashesMessageSender)); - final TransactionPool txPool = - new TransactionPool( - () -> transactions, - protocolSchedule, - protocolContext, - transactionBroadcaster, - ethContext, - miningParameters, - new TransactionPoolMetrics(metricsSystem), - poolConfig, - null); - txPool.setEnabled(); - return txPool; - } - - @Test - public void localTransactionHappyPath() { - final Transaction transaction = createTransaction(0); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionValid(transaction); - } - - @Test - public void shouldReturnExclusivelyLocalTransactionsWhenAppropriate() { - final Transaction localTransaction0 = createTransaction(0, KEY_PAIR2); - - givenTransactionIsValid(localTransaction0); - givenTransactionIsValid(transaction0); - givenTransactionIsValid(transaction1); - - addAndAssertLocalTransactionValid(localTransaction0); - addAndAssertRemoteTransactionValid(transaction0); - addAndAssertRemoteTransactionValid(transaction1); - - assertThat(transactions.size()).isEqualTo(3); - List localTransactions = transactions.getLocalTransactions(); - assertThat(localTransactions.size()).isEqualTo(1); - } - - @Test - public void shouldRemoveTransactionsFromPendingListWhenIncludedInBlockOnchain() { - transactions.addRemoteTransaction(transaction0, Optional.empty()); - assertTransactionPending(transaction0); - appendBlock(transaction0); - - assertTransactionNotPending(transaction0); - } - - @Test - public void shouldRemoveMultipleTransactionsAddedInOneBlock() { - transactions.addRemoteTransaction(transaction0, Optional.empty()); - transactions.addRemoteTransaction(transaction1, Optional.empty()); - appendBlock(transaction0, transaction1); - - assertTransactionNotPending(transaction0); - assertTransactionNotPending(transaction1); - assertThat(transactions.size()).isZero(); - } - - @Test - public void shouldIgnoreUnknownTransactionsThatAreAddedInABlock() { - transactions.addRemoteTransaction(transaction0, Optional.empty()); - appendBlock(transaction0, transaction1); - - assertTransactionNotPending(transaction0); - assertTransactionNotPending(transaction1); - assertThat(transactions.size()).isZero(); - } - - @Test - public void shouldNotRemovePendingTransactionsWhenABlockAddedToAFork() { - transactions.addRemoteTransaction(transaction0, Optional.empty()); - final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block canonicalHead = appendBlock(Difficulty.of(1000), commonParent); - appendBlock(Difficulty.ONE, commonParent, transaction0); - - verifyChainHeadIs(canonicalHead); - - assertTransactionPending(transaction0); - } - - @Test - public void shouldRemovePendingTransactionsFromAllBlocksOnAForkWhenItBecomesTheCanonicalChain() { - transactions.addRemoteTransaction(transaction0, Optional.empty()); - transactions.addRemoteTransaction(transaction1, Optional.empty()); - final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block originalChainHead = appendBlock(Difficulty.of(1000), commonParent); - - final Block forkBlock1 = appendBlock(Difficulty.ONE, commonParent, transaction0); - verifyChainHeadIs(originalChainHead); - - final Block forkBlock2 = appendBlock(Difficulty.of(2000), forkBlock1.getHeader(), transaction1); - verifyChainHeadIs(forkBlock2); - - assertTransactionNotPending(transaction0); - assertTransactionNotPending(transaction1); - } - - @Test - public void shouldReAddTransactionsFromThePreviousCanonicalHeadWhenAReorgOccurs() { - givenTransactionIsValid(transaction0); - givenTransactionIsValid(transactionOtherSender); - transactions.addLocalTransaction(transaction0, Optional.empty()); - transactions.addRemoteTransaction(transactionOtherSender, Optional.empty()); - final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction0); - final Block originalFork2 = - appendBlock(Difficulty.ONE, originalFork1.getHeader(), transactionOtherSender); - assertTransactionNotPending(transaction0); - assertTransactionNotPending(transactionOtherSender); - assertThat(transactions.getLocalTransactions()).isEmpty(); - - final Block reorgFork1 = appendBlock(Difficulty.ONE, commonParent); - verifyChainHeadIs(originalFork2); - - transactions.subscribePendingTransactions(listener); - final Block reorgFork2 = appendBlock(Difficulty.of(2000), reorgFork1.getHeader()); - verifyChainHeadIs(reorgFork2); - - assertTransactionPending(transaction0); - assertTransactionPending(transactionOtherSender); - assertThat(transactions.getLocalTransactions()).contains(transaction0); - assertThat(transactions.getLocalTransactions()).doesNotContain(transactionOtherSender); - verify(listener).onTransactionAdded(transaction0); - verify(listener).onTransactionAdded(transactionOtherSender); - verifyNoMoreInteractions(listener); - } - - @Test - public void shouldNotReAddTransactionsThatAreInBothForksWhenReorgHappens() { - givenTransactionIsValid(transaction0); - givenTransactionIsValid(transaction1); - transactions.addRemoteTransaction(transaction0, Optional.empty()); - transactions.addRemoteTransaction(transaction1, Optional.empty()); - final BlockHeader commonParent = getHeaderForCurrentChainHead(); - final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction0); - final Block originalFork2 = - appendBlock(Difficulty.ONE, originalFork1.getHeader(), transaction1); - assertTransactionNotPending(transaction0); - assertTransactionNotPending(transaction1); - - final Block reorgFork1 = appendBlock(Difficulty.ONE, commonParent, transaction1); - verifyChainHeadIs(originalFork2); - - final Block reorgFork2 = appendBlock(Difficulty.of(2000), reorgFork1.getHeader()); - verifyChainHeadIs(reorgFork2); - - assertTransactionPending(transaction0); - assertTransactionNotPending(transaction1); - } - - @Test - public void addLocalTransaction_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransaction(0); - givenTransactionIsValid(tx); - - addAndAssertLocalTransactionValid(tx); - } - - @Test - public void addRemoteTransactions_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransaction(0); - givenTransactionIsValid(tx); - - addAndAssertRemoteTransactionValid(tx); - } - - @Test - public void shouldNotAddRemoteTransactionsWhenGasPriceBelowMinimum() { - final Transaction transaction = createTransaction(1, Wei.ONE); - transactionPool.addRemoteTransactions(singletonList(transaction)); - - assertTransactionNotPending(transaction); - verifyNoMoreInteractions(transactionValidatorFactory); - } - - @Test - public void shouldNotAddRemoteTransactionsThatAreInvalidAccordingToStateDependentChecks() { - givenTransactionIsValid(transaction0); - givenTransactionIsValid(transaction1); - when(transactionValidatorFactory - .get() - .validateForSender(eq(transaction1), eq(null), any(TransactionValidationParams.class))) - .thenReturn(ValidationResult.invalid(NONCE_TOO_LOW)); - transactionPool.addRemoteTransactions(asList(transaction0, transaction1)); - - assertTransactionPending(transaction0); - assertTransactionNotPending(transaction1); - verify(transactionBroadcaster).onTransactionsAdded(singletonList(transaction0)); - verify(transactionValidatorFactory.get()) - .validate(eq(transaction0), any(Optional.class), any()); - verify(transactionValidatorFactory.get()) - .validateForSender(eq(transaction0), eq(null), any(TransactionValidationParams.class)); - verify(transactionValidatorFactory.get()) - .validate(eq(transaction1), any(Optional.class), any()); - verify(transactionValidatorFactory.get()).validateForSender(eq(transaction1), any(), any()); - verifyNoMoreInteractions(transactionValidatorFactory.get()); - } - - @Test - public void shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSender() { - final Transaction transaction1 = createTransaction(0); - final Transaction transaction2 = createTransaction(1); - final Transaction transaction3 = createTransaction(2); - - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - givenTransactionIsValid(transaction3); - - addAndAssertLocalTransactionValid(transaction1); - addAndAssertLocalTransactionValid(transaction2); - addAndAssertLocalTransactionValid(transaction3); - } - - @Test - public void - shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSenderWhenSentInBatchOutOfOrder() { - final Transaction transaction1 = createTransaction(0); - final Transaction transaction2 = createTransaction(1); - final Transaction transaction3 = createTransaction(2); - - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - givenTransactionIsValid(transaction3); - - transactionPool.addRemoteTransactions(List.of(transaction3, transaction1, transaction2)); - assertRemoteTransactionValid(transaction3); - assertRemoteTransactionValid(transaction1); - assertRemoteTransactionValid(transaction2); - } - - @Test - public void shouldDiscardRemoteTransactionThatAlreadyExistsBeforeValidation() { - doReturn(true).when(transactions).containsTransaction(transaction0); - transactionPool.addRemoteTransactions(singletonList(transaction0)); - - verify(transactions).containsTransaction(transaction0); - verifyNoInteractions(transactionValidatorFactory); - } - - @Test - public void shouldNotNotifyBatchListenerWhenRemoteTransactionDoesNotReplaceExisting() { - final Transaction transaction1 = createTransaction(0, Wei.of(100)); - final Transaction transaction2 = createTransaction(0, Wei.of(50)); - - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - - addAndAssertRemoteTransactionValid(transaction1); - addAndAssertRemoteTransactionInvalid(transaction2); - } - - @Test - public void shouldNotNotifyBatchListenerWhenLocalTransactionDoesNotReplaceExisting() { - final Transaction transaction1 = createTransaction(0, Wei.of(10)); - final Transaction transaction2 = createTransaction(0, Wei.of(9)); - - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - - addAndAssertLocalTransactionValid(transaction1); - addAndAssertLocalTransactionInvalid(transaction2, TRANSACTION_REPLACEMENT_UNDERPRICED); - } - - @Test - public void shouldRejectLocalTransactionsWhereGasLimitExceedBlockGasLimit() { - final Transaction transaction1 = - createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - - givenTransactionIsValid(transaction1); - - addAndAssertLocalTransactionInvalid(transaction1, EXCEEDS_BLOCK_GAS_LIMIT); - } - - @Test - public void shouldRejectRemoteTransactionsWhereGasLimitExceedBlockGasLimit() { - final Transaction transaction1 = - createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - - givenTransactionIsValid(transaction1); - - addAndAssertRemoteTransactionInvalid(transaction1); - } - - @Test - public void - shouldAcceptAsPostponedLocalTransactionsEvenIfAnInvalidTransactionWithLowerNonceExists() { - final Transaction invalidTx = - createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); - - final Transaction nextTx = createBaseTransaction(1).gasLimit(1).createTransaction(KEY_PAIR1); - - givenTransactionIsValid(invalidTx); - givenTransactionIsValid(nextTx); - - addAndAssertLocalTransactionInvalid(invalidTx, EXCEEDS_BLOCK_GAS_LIMIT); - final ValidationResult result = - transactionPool.addLocalTransaction(nextTx); - - assertThat(result.isValid()).isTrue(); - } - - @Test - public void shouldRejectLocalTransactionsWhenNonceTooFarInFuture() { - final Transaction transaction1 = createTransaction(Integer.MAX_VALUE); - - givenTransactionIsValid(transaction1); - - addAndAssertLocalTransactionInvalid(transaction1, NONCE_TOO_FAR_IN_FUTURE_FOR_SENDER); - } - - @Test - public void shouldNotNotifyBatchListenerIfNoTransactionsAreAdded() { - transactionPool.addRemoteTransactions(emptyList()); - verifyNoInteractions(transactionBroadcaster); - } - - @Test - public void shouldSendPooledTransactionHashesIfPeerSupportsEth65() { - EthPeer peer = mock(EthPeer.class); - when(peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)).thenReturn(true); - - givenTransactionIsValid(transaction0); - transactionPool.addLocalTransaction(transaction0); - transactionPool.handleConnect(peer); - syncTaskCapture.getValue().run(); - verify(newPooledTransactionHashesMessageSender).sendTransactionHashesToPeer(peer); - } - - @Test - public void shouldSendFullTransactionsIfPeerDoesNotSupportEth65() { - EthPeer peer = mock(EthPeer.class); - when(peer.hasSupportForMessage(EthPV65.NEW_POOLED_TRANSACTION_HASHES)).thenReturn(false); - - givenTransactionIsValid(transaction0); - transactionPool.addLocalTransaction(transaction0); - transactionPool.handleConnect(peer); - syncTaskCapture.getValue().run(); - verify(transactionsMessageSender).sendTransactionsToPeer(peer); - } - - @Test - public void shouldSendFullTransactionPoolToNewlyConnectedPeer() { - final Transaction transactionLocal = createTransaction(0); - final Transaction transactionRemote = createTransaction(1); - - givenTransactionIsValid(transactionLocal); - givenTransactionIsValid(transactionRemote); - - transactionPool.addLocalTransaction(transactionLocal); - transactionPool.addRemoteTransactions(Collections.singletonList(transactionRemote)); - - RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); - - Set transactionsToSendToPeer = - peerTransactionTracker.claimTransactionsToSendToPeer(peer.getEthPeer()); - - assertThat(transactionsToSendToPeer).contains(transactionLocal, transactionRemote); - } - - @Test - public void shouldCallValidatorWithExpectedValidationParameters() { - final ArgumentCaptor txValidationParamCaptor = - ArgumentCaptor.forClass(TransactionValidationParams.class); - - when(transactionValidatorFactory.get().validate(eq(transaction0), any(Optional.class), any())) - .thenReturn(valid()); - when(transactionValidatorFactory - .get() - .validateForSender(any(), any(), txValidationParamCaptor.capture())) - .thenReturn(valid()); - - final TransactionValidationParams expectedValidationParams = - TransactionValidationParams.transactionPool(); - - transactionPool.addLocalTransaction(transaction0); - - assertThat(txValidationParamCaptor.getValue()) - .usingRecursiveComparison() - .isEqualTo(expectedValidationParams); - } - - @Test - public void shouldIgnoreFeeCapIfSetZero() { - final Wei twoEthers = Wei.fromEth(2); - transactionPool = createTransactionPool(b -> b.txFeeCap(Wei.ZERO)); - final Transaction transaction = createTransaction(0, twoEthers.add(Wei.of(1))); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionValid(transaction); - } - - @Test - public void shouldRejectLocalTransactionIfFeeCapExceeded() { - final Wei twoEthers = Wei.fromEth(2); - transactionPool = createTransactionPool(b -> b.txFeeCap(twoEthers)); - - final Transaction transactionLocal = createTransaction(0, twoEthers.add(1)); - - givenTransactionIsValid(transactionLocal); - - addAndAssertLocalTransactionInvalid(transactionLocal, TX_FEECAP_EXCEEDED); - } - - @Test - public void shouldRejectZeroGasPriceTransactionWhenNotMining() { - when(miningParameters.isMiningEnabled()).thenReturn(false); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionInvalid(transaction, GAS_PRICE_TOO_LOW); - } - - private void assertTransactionPending(final Transaction t) { - assertThat(transactions.getTransactionByHash(t.getHash())).contains(t); - } - - private void assertTransactionNotPending(final Transaction transaction) { - assertThat(transactions.getTransactionByHash(transaction.getHash())).isEmpty(); - } - - private void verifyChainHeadIs(final Block forkBlock2) { - assertThat(blockchain.getChainHeadHash()).isEqualTo(forkBlock2.getHash()); - } - - private void appendBlock(final Transaction... transactionsToAdd) { - appendBlock(Difficulty.ONE, getHeaderForCurrentChainHead(), transactionsToAdd); - } - - private BlockHeader getHeaderForCurrentChainHead() { - return blockchain.getBlockHeader(blockchain.getChainHeadHash()).get(); - } - - protected abstract Block appendBlock( - final Difficulty difficulty, - final BlockHeader parentBlock, - final Transaction... transactionsToAdd); - - protected abstract Transaction createTransaction( - final int nonce, final Optional maybeChainId); - - protected abstract Transaction createTransaction(final int nonce, final Wei maxPrice); - - protected abstract TransactionTestFixture createBaseTransaction(final int nonce); - - private Transaction createTransaction(final int nonce) { - return createTransaction(nonce, Optional.of(BigInteger.ONE)); - } - - private Transaction createTransaction(final int nonce, final KeyPair keyPair) { - return createBaseTransaction(nonce).createTransaction(keyPair); - } - - protected void protocolSupportsTxReplayProtection( - final long chainId, final boolean isSupportedAtCurrentBlock) { - when(protocolSpec.isReplayProtectionSupported()).thenReturn(isSupportedAtCurrentBlock); - when(protocolSchedule.getChainId()).thenReturn(Optional.of(BigInteger.valueOf(chainId))); - } - - protected void givenTransactionIsValid(final Transaction transaction) { - when(transactionValidatorFactory.get().validate(eq(transaction), any(Optional.class), any())) - .thenReturn(valid()); - when(transactionValidatorFactory - .get() - .validateForSender( - eq(transaction), nullable(Account.class), any(TransactionValidationParams.class))) - .thenReturn(valid()); - } - - protected void addAndAssertLocalTransactionInvalid( - final Transaction tx, final TransactionInvalidReason invalidReason) { - final ValidationResult result = - transactionPool.addLocalTransaction(tx); - - assertThat(result.isValid()).isFalse(); - assertThat(result.getInvalidReason()).isEqualTo(invalidReason); - assertTransactionNotPending(tx); - verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); - } - - protected void addAndAssertLocalTransactionValid(final Transaction tx) { - final ValidationResult result = - transactionPool.addLocalTransaction(tx); - - assertThat(result.isValid()).isTrue(); - assertTransactionPending(tx); - verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); - assertThat(transactions.getLocalTransactions()).contains(tx); - } - - protected void addAndAssertRemoteTransactionValid(final Transaction tx) { - transactionPool.addRemoteTransactions(List.of(tx)); - - assertRemoteTransactionValid(tx); - } - - protected void assertRemoteTransactionValid(final Transaction tx) { - verify(transactionBroadcaster) - .onTransactionsAdded(argThat((List list) -> list.contains(tx))); - assertTransactionPending(tx); - assertThat(transactions.getLocalTransactions()).doesNotContain(tx); - } - - protected void addAndAssertRemoteTransactionInvalid(final Transaction tx) { - transactionPool.addRemoteTransactions(List.of(tx)); - - verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); - assertTransactionNotPending(tx); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java deleted file mode 100644 index cc40d47aec0..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLegacyTest.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.transactions; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INVALID_TRANSACTION_FORMAT; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURE_REQUIRED; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.TransactionType; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Difficulty; -import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; -import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; -import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.testutil.TestClock; -import org.hyperledger.besu.util.number.Fraction; - -import java.math.BigInteger; -import java.time.ZoneId; -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.junit.jupiter.MockitoExtension; - -@SuppressWarnings("unchecked") -@ExtendWith(MockitoExtension.class) -public class TransactionPoolLegacyTest extends AbstractTransactionPoolTest { - - @Override - protected PendingTransactions createPendingTransactionsSorter() { - - return new GasPricePendingTransactionsSorter( - ImmutableTransactionPoolConfiguration.builder() - .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) - .build(), - TestClock.system(ZoneId.systemDefault()), - metricsSystem, - protocolContext.getBlockchain()::getChainHeadHeader); - } - - @Override - protected Transaction createTransaction( - final int transactionNumber, final Optional maybeChainId) { - return createBaseTransaction(transactionNumber) - .chainId(maybeChainId) - .createTransaction(KEY_PAIR1); - } - - @Override - protected Transaction createTransaction(final int transactionNumber, final Wei maxPrice) { - return createBaseTransaction(transactionNumber).gasPrice(maxPrice).createTransaction(KEY_PAIR1); - } - - @Override - protected TransactionTestFixture createBaseTransaction(final int transactionNumber) { - return new TransactionTestFixture() - .nonce(transactionNumber) - .gasLimit(blockGasLimit) - .type(TransactionType.FRONTIER); - } - - @Override - protected ExecutionContextTestFixture createExecutionContextTestFixture() { - return ExecutionContextTestFixture.create(); - } - - @Override - protected FeeMarket getFeeMarket() { - return FeeMarket.legacy(); - } - - @Override - protected Block appendBlock( - final Difficulty difficulty, - final BlockHeader parentBlock, - final Transaction... transactionsToAdd) { - final List transactionList = asList(transactionsToAdd); - final Block block = - new Block( - new BlockHeaderTestFixture() - .difficulty(difficulty) - .gasLimit(parentBlock.getGasLimit()) - .parentHash(parentBlock.getHash()) - .number(parentBlock.getNumber() + 1) - .buildHeader(), - new BlockBody(transactionList, emptyList())); - final List transactionReceipts = - transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) - .collect(toList()); - blockchain.appendBlock(block, transactionReceipts); - return block; - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void - addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured_protectionNotSupportedAtCurrentBlock( - final boolean disableLocalTxs) { - protocolSupportsTxReplayProtection(1337, false); - transactionPool = - createTransactionPool( - b -> - b.strictTransactionReplayProtectionEnabled(true) - .disableLocalTransactions(disableLocalTxs)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertTransactionViaApiValid(tx, disableLocalTxs); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertRemoteTransactionValid(tx); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void addLocalTransaction_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured( - final boolean disableLocalTxs) { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = - createTransactionPool( - b -> - b.strictTransactionReplayProtectionEnabled(false) - .disableLocalTransactions(disableLocalTxs)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertTransactionViaApiValid(tx, disableLocalTxs); - } - - @Test - public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertTransactionViaApiInvalid(tx, REPLAY_PROTECTED_SIGNATURE_REQUIRED); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertRemoteTransactionValid(tx); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured( - final boolean disableLocalTxs) { - protocolDoesNotSupportTxReplayProtection(); - transactionPool = - createTransactionPool( - b -> - b.strictTransactionReplayProtectionEnabled(true) - .disableLocalTransactions(disableLocalTxs)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertTransactionViaApiValid(tx, disableLocalTxs); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() { - protocolDoesNotSupportTxReplayProtection(); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(1); - givenTransactionIsValid(tx); - - assertRemoteTransactionValid(tx); - } - - @Test - public void shouldIgnoreEIP1559TransactionWhenNotAllowed() { - final Transaction transaction = - createBaseTransaction(1) - .type(TransactionType.EIP1559) - .maxFeePerGas(Optional.of(Wei.of(100L))) - .maxPriorityFeePerGas(Optional.of(Wei.of(50L))) - .gasLimit(10) - .gasPrice(null) - .createTransaction(KEY_PAIR1); - - givenTransactionIsValid(transaction); - - assertTransactionViaApiInvalid(transaction, INVALID_TRANSACTION_FORMAT); - } - - @Test - public void shouldAcceptZeroGasPriceFrontierLocalTransactionsWhenMining() { - transactionPool = createTransactionPool(b -> b.disableLocalTransactions(false)); - when(miningParameters.isMiningEnabled()).thenReturn(true); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - - assertTransactionViaApiValid(transaction, false); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void shouldAcceptZeroGasPriceTransactionWhenMinGasPriceIsZero( - final boolean disableLocalTxs) { - transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - - assertTransactionViaApiValid(transaction, disableLocalTxs); - } - - private Transaction createTransactionWithoutChainId(final int transactionNumber) { - return createTransaction(transactionNumber, Optional.empty()); - } - - private void protocolDoesNotSupportTxReplayProtection() { - when(protocolSchedule.getChainId()).thenReturn(Optional.empty()); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java deleted file mode 100644 index 2f151cac80d..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/TransactionPoolLondonTest.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.transactions; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.config.StubGenesisConfigOptions; -import org.hyperledger.besu.datatypes.TransactionType; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Difficulty; -import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; -import org.hyperledger.besu.ethereum.eth.transactions.sorter.BaseFeePendingTransactionsSorter; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; -import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.evm.internal.EvmConfiguration; -import org.hyperledger.besu.testutil.TestClock; -import org.hyperledger.besu.util.number.Fraction; - -import java.math.BigInteger; -import java.time.ZoneId; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -@SuppressWarnings("unchecked") -public class TransactionPoolLondonTest extends AbstractTransactionPoolTest { - - private static final Wei BASE_FEE_FLOOR = Wei.of(7L); - - @Override - protected PendingTransactions createPendingTransactionsSorter() { - - return new BaseFeePendingTransactionsSorter( - ImmutableTransactionPoolConfiguration.builder() - .txPoolMaxSize(MAX_TRANSACTIONS) - .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) - .build(), - TestClock.system(ZoneId.systemDefault()), - metricsSystem, - protocolContext.getBlockchain()::getChainHeadHeader); - } - - @Override - protected Transaction createTransaction( - final int transactionNumber, final Optional maybeChainId) { - return createBaseTransaction(transactionNumber) - .chainId(maybeChainId) - .createTransaction(KEY_PAIR1); - } - - @Override - protected Transaction createTransaction(final int transactionNumber, final Wei maxPrice) { - return createBaseTransaction(transactionNumber) - .maxFeePerGas(Optional.of(maxPrice)) - .maxPriorityFeePerGas(Optional.of(maxPrice.divide(5L))) - .createTransaction(KEY_PAIR1); - } - - @Override - protected TransactionTestFixture createBaseTransaction(final int transactionNumber) { - return new TransactionTestFixture() - .nonce(transactionNumber) - .gasLimit(blockGasLimit) - .gasPrice(null) - .maxFeePerGas(Optional.of(Wei.of(5000L))) - .maxPriorityFeePerGas(Optional.of(Wei.of(1000L))) - .type(TransactionType.EIP1559); - } - - @Override - protected ExecutionContextTestFixture createExecutionContextTestFixture() { - final ProtocolSchedule protocolSchedule = - new ProtocolScheduleBuilder( - new StubGenesisConfigOptions().londonBlock(0L).baseFeePerGas(10L), - BigInteger.valueOf(1), - ProtocolSpecAdapters.create(0, Function.identity()), - new PrivacyParameters(), - false, - EvmConfiguration.DEFAULT) - .createProtocolSchedule(); - final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); - - final Block block = - new Block( - new BlockHeaderTestFixture() - .gasLimit( - executionContextTestFixture - .getBlockchain() - .getChainHeadBlock() - .getHeader() - .getGasLimit()) - .difficulty(Difficulty.ONE) - .baseFeePerGas(Wei.of(10L)) - .parentHash(executionContextTestFixture.getBlockchain().getChainHeadHash()) - .number(executionContextTestFixture.getBlockchain().getChainHeadBlockNumber() + 1) - .buildHeader(), - new BlockBody(List.of(), List.of())); - executionContextTestFixture.getBlockchain().appendBlock(block, List.of()); - - return executionContextTestFixture; - } - - @Override - protected FeeMarket getFeeMarket() { - return FeeMarket.london(0L, Optional.of(BASE_FEE_FLOOR)); - } - - @Override - protected Block appendBlock( - final Difficulty difficulty, - final BlockHeader parentBlock, - final Transaction... transactionsToAdd) { - final List transactionList = asList(transactionsToAdd); - final Block block = - new Block( - new BlockHeaderTestFixture() - .baseFeePerGas(Wei.of(10L)) - .gasLimit(parentBlock.getGasLimit()) - .difficulty(difficulty) - .parentHash(parentBlock.getHash()) - .number(parentBlock.getNumber() + 1) - .buildHeader(), - new BlockBody(transactionList, emptyList())); - final List transactionReceipts = - transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) - .collect(toList()); - blockchain.appendBlock(block, transactionReceipts); - return block; - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void shouldAcceptZeroGasPriceFrontierTxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee( - final boolean disableLocalTxs) { - transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); - whenBlockBaseFeeIs(Wei.ZERO); - - final Transaction frontierTransaction = createFrontierTransaction(0, Wei.ZERO); - - givenTransactionIsValid(frontierTransaction); - assertTransactionViaApiValid(frontierTransaction, disableLocalTxs); - } - - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void shouldAcceptZeroGasPrice1559TxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee( - final boolean disableLocalTxs) { - transactionPool = createTransactionPool(b -> b.disableLocalTransactions(disableLocalTxs)); - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); - whenBlockBaseFeeIs(Wei.ZERO); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - assertTransactionViaApiValid(transaction, disableLocalTxs); - } - - @Test - public void shouldAcceptBaseFeeFloorGasPriceFrontierLocalTransactionsWhenMining() { - transactionPool = createTransactionPool(b -> b.disableLocalTransactions(false)); - final Transaction frontierTransaction = createFrontierTransaction(0, BASE_FEE_FLOOR); - - givenTransactionIsValid(frontierTransaction); - - assertTransactionViaApiValid(frontierTransaction, false); - } - - @Test - public void shouldRejectRemote1559TxsWhenMaxFeePerGasBelowMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice.subtract(1L); - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) - .isEqualTo(0); - } - - @Test - public void shouldAcceptRemote1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice; - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) - .isEqualTo(1); - } - - @Test - public void shouldRejectLocal1559TxsWhenMaxFeePerGasBelowMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice.subtract(1L); - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) - .isEqualTo(0); - } - - @Test - public void shouldAcceptLocal1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice; - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) - .isEqualTo(1); - } - - @Test - public void addRemoteTransactionsShouldAllowDuplicates() { - final Transaction transaction1 = createTransaction(1, Wei.of(7L)); - final Transaction transaction2 = createTransaction(2, Wei.of(7L)); - final Transaction transaction3 = createTransaction(2, Wei.of(7L)); - final Transaction transaction4 = createTransaction(3, Wei.of(7L)); - - givenTransactionIsValid(transaction1); - givenTransactionIsValid(transaction2); - givenTransactionIsValid(transaction3); - givenTransactionIsValid(transaction4); - - assertThatCode( - () -> - transactionPool.addRemoteTransactions( - List.of(transaction1, transaction2, transaction3, transaction4))) - .doesNotThrowAnyException(); - } - - private int add1559TxAndGetPendingTxsCount( - final Wei genesisBaseFee, - final Wei minGasPrice, - final Wei lastBlockBaseFee, - final Wei txMaxFeePerGas, - final boolean isLocal) { - when(miningParameters.getMinTransactionGasPrice()).thenReturn(minGasPrice); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(genesisBaseFee))); - whenBlockBaseFeeIs(lastBlockBaseFee); - - final Transaction transaction = createTransaction(0, txMaxFeePerGas); - - givenTransactionIsValid(transaction); - - if (isLocal) { - transactionPool.addLocalTransaction(transaction); - } else { - transactionPool.addRemoteTransactions(List.of(transaction)); - } - - return transactions.size(); - } - - private void whenBlockBaseFeeIs(final Wei baseFee) { - final BlockHeader header = - BlockHeaderBuilder.fromHeader(blockchain.getChainHeadHeader()) - .baseFee(baseFee) - .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) - .parentHash(blockchain.getChainHeadHash()) - .buildBlockHeader(); - blockchain.appendBlock(new Block(header, BlockBody.empty()), emptyList()); - } - - private Transaction createFrontierTransaction(final int transactionNumber, final Wei gasPrice) { - return new TransactionTestFixture() - .nonce(transactionNumber) - .gasPrice(gasPrice) - .gasLimit(blockGasLimit) - .type(TransactionType.FRONTIER) - .createTransaction(KEY_PAIR1); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java new file mode 100644 index 00000000000..6245fe83ed8 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/AbstractLayeredTransactionPoolTest.java @@ -0,0 +1,76 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.EXCEEDS_BLOCK_GAS_LIMIT; + +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.AbstractTransactionPoolTest; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; +import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; + +import java.util.function.BiFunction; + +import org.junit.jupiter.api.Test; + +public abstract class AbstractLayeredTransactionPoolTest extends AbstractTransactionPoolTest { + @Override + protected PendingTransactions createPendingTransactions( + final TransactionPoolConfiguration poolConfig, + final BiFunction + transactionReplacementTester) { + + final var txPoolMetrics = new TransactionPoolMetrics(metricsSystem); + final TransactionsLayer sparseLayer = + new SparseTransactions( + poolConfig, new EndLayer(txPoolMetrics), txPoolMetrics, transactionReplacementTester); + final TransactionsLayer readyLayer = + new ReadyTransactions(poolConfig, sparseLayer, txPoolMetrics, transactionReplacementTester); + return new LayeredPendingTransactions( + poolConfig, + createPrioritizedTransactions( + poolConfig, readyLayer, txPoolMetrics, transactionReplacementTester)); + } + + protected abstract AbstractPrioritizedTransactions createPrioritizedTransactions( + final TransactionPoolConfiguration poolConfig, + final TransactionsLayer nextLayer, + final TransactionPoolMetrics txPoolMetrics, + final BiFunction + transactionReplacementTester); + + @Test + public void + shouldAcceptAsPostponedLocalTransactionsEvenIfAnInvalidTransactionWithLowerNonceExists() { + final Transaction invalidTx = + createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); + + final Transaction nextTx = createBaseTransaction(1).gasLimit(1).createTransaction(KEY_PAIR1); + + givenTransactionIsValid(invalidTx); + givenTransactionIsValid(nextTx); + + addAndAssertTransactionViaApiInvalid(invalidTx, EXCEEDS_BLOCK_GAS_LIMIT); + final ValidationResult result = + transactionPool.addTransactionViaApi(nextTx); + + assertThat(result.isValid()).isTrue(); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLegacyTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLegacyTest.java deleted file mode 100644 index 57cb9e1452f..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLegacyTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.transactions.layered; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.INVALID_TRANSACTION_FORMAT; -import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.REPLAY_PROTECTED_SIGNATURE_REQUIRED; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.datatypes.TransactionType; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Difficulty; -import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; -import org.hyperledger.besu.ethereum.eth.transactions.AbstractTransactionsLayeredPendingTransactionsTest; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; -import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; - -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; -import java.util.function.BiFunction; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; - -@SuppressWarnings("unchecked") -@ExtendWith(MockitoExtension.class) -public class LayeredPendingTransactionsLegacyTest - extends AbstractTransactionsLayeredPendingTransactionsTest { - - @Override - protected PendingTransactions createPendingTransactionsSorter( - final TransactionPoolConfiguration poolConfig, - final BiFunction - transactionReplacementTester) { - - final var txPoolMetrics = new TransactionPoolMetrics(metricsSystem); - return new LayeredPendingTransactions( - poolConfig, - new GasPricePrioritizedTransactions( - poolConfig, new EndLayer(txPoolMetrics), txPoolMetrics, transactionReplacementTester)); - } - - @Override - protected Transaction createTransaction( - final int nonce, final Optional maybeChainId) { - return createBaseTransaction(nonce).chainId(maybeChainId).createTransaction(KEY_PAIR1); - } - - @Override - protected Transaction createTransaction(final int nonce, final Wei maxPrice) { - return createBaseTransaction(nonce).gasPrice(maxPrice).createTransaction(KEY_PAIR1); - } - - @Override - protected TransactionTestFixture createBaseTransaction(final int nonce) { - return new TransactionTestFixture() - .nonce(nonce) - .gasLimit(blockGasLimit) - .type(TransactionType.FRONTIER); - } - - @Override - protected ExecutionContextTestFixture createExecutionContextTestFixture() { - return ExecutionContextTestFixture.create(); - } - - @Override - protected FeeMarket getFeeMarket() { - return FeeMarket.legacy(); - } - - @Override - protected Block appendBlock( - final Difficulty difficulty, - final BlockHeader parentBlock, - final Transaction... transactionsToAdd) { - final List transactionList = asList(transactionsToAdd); - final Block block = - new Block( - new BlockHeaderTestFixture() - .difficulty(difficulty) - .gasLimit(parentBlock.getGasLimit()) - .parentHash(parentBlock.getHash()) - .number(parentBlock.getNumber() + 1) - .buildHeader(), - new BlockBody(transactionList, emptyList())); - final List transactionReceipts = - transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) - .collect(toList()); - blockchain.appendBlock(block, transactionReceipts); - return block; - } - - @Test - public void - addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured_protectionNotSupportedAtCurrentBlock() { - protocolSupportsTxReplayProtection(1337, false); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertLocalTransactionValid(tx); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertRemoteTransactionValid(tx); - } - - @Test - public void addLocalTransaction_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertLocalTransactionValid(tx); - } - - @Test - public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertLocalTransactionInvalid(tx, REPLAY_PROTECTED_SIGNATURE_REQUIRED); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { - protocolSupportsTxReplayProtection(1337, true); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertRemoteTransactionValid(tx); - } - - @Test - public void - addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() { - protocolDoesNotSupportTxReplayProtection(); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertLocalTransactionValid(tx); - } - - @Test - public void - addRemoteTransactions_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() { - protocolDoesNotSupportTxReplayProtection(); - transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); - final Transaction tx = createTransactionWithoutChainId(0); - givenTransactionIsValid(tx); - - addAndAssertRemoteTransactionValid(tx); - } - - @Test - public void shouldIgnoreEIP1559TransactionWhenNotAllowed() { - final Transaction transaction = - createBaseTransaction(1) - .type(TransactionType.EIP1559) - .maxFeePerGas(Optional.of(Wei.of(100L))) - .maxPriorityFeePerGas(Optional.of(Wei.of(50L))) - .gasLimit(10) - .gasPrice(null) - .createTransaction(KEY_PAIR1); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionInvalid(transaction, INVALID_TRANSACTION_FORMAT); - } - - @Test - public void shouldAcceptZeroGasPriceFrontierTransactionsWhenMining() { - when(miningParameters.isMiningEnabled()).thenReturn(true); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionValid(transaction); - } - - @Test - public void shouldAcceptZeroGasPriceTransactionWhenMinGasPriceIsZero() { - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - - addAndAssertLocalTransactionValid(transaction); - } - - private Transaction createTransactionWithoutChainId(final int nonce) { - return createTransaction(nonce, Optional.empty()); - } - - private void protocolDoesNotSupportTxReplayProtection() { - when(protocolSchedule.getChainId()).thenReturn(Optional.empty()); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLondonTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLondonTest.java deleted file mode 100644 index 068988464ab..00000000000 --- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsLondonTest.java +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright Hyperledger Besu Contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - * - * SPDX-License-Identifier: Apache-2.0 - */ -package org.hyperledger.besu.ethereum.eth.transactions.layered; - -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; -import static java.util.stream.Collectors.toList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.when; - -import org.hyperledger.besu.config.StubGenesisConfigOptions; -import org.hyperledger.besu.datatypes.TransactionType; -import org.hyperledger.besu.datatypes.Wei; -import org.hyperledger.besu.ethereum.core.Block; -import org.hyperledger.besu.ethereum.core.BlockBody; -import org.hyperledger.besu.ethereum.core.BlockHeader; -import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder; -import org.hyperledger.besu.ethereum.core.BlockHeaderTestFixture; -import org.hyperledger.besu.ethereum.core.Difficulty; -import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; -import org.hyperledger.besu.ethereum.core.PrivacyParameters; -import org.hyperledger.besu.ethereum.core.Transaction; -import org.hyperledger.besu.ethereum.core.TransactionReceipt; -import org.hyperledger.besu.ethereum.core.TransactionTestFixture; -import org.hyperledger.besu.ethereum.eth.transactions.AbstractTransactionsLayeredPendingTransactionsTest; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; -import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; -import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; -import org.hyperledger.besu.ethereum.mainnet.MainnetBlockHeaderFunctions; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSchedule; -import org.hyperledger.besu.ethereum.mainnet.ProtocolScheduleBuilder; -import org.hyperledger.besu.ethereum.mainnet.ProtocolSpecAdapters; -import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; -import org.hyperledger.besu.evm.internal.EvmConfiguration; - -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; -import java.util.function.BiFunction; -import java.util.function.Function; - -import org.junit.jupiter.api.Test; - -public class LayeredPendingTransactionsLondonTest - extends AbstractTransactionsLayeredPendingTransactionsTest { - - private static final Wei BASE_FEE_FLOOR = Wei.of(7L); - - @Override - protected PendingTransactions createPendingTransactionsSorter( - final TransactionPoolConfiguration poolConfig, - final BiFunction - transactionReplacementTester) { - - final var txPoolMetrics = new TransactionPoolMetrics(metricsSystem); - return new LayeredPendingTransactions( - poolConfig, - new BaseFeePrioritizedTransactions( - poolConfig, - protocolContext.getBlockchain()::getChainHeadHeader, - new EndLayer(txPoolMetrics), - txPoolMetrics, - transactionReplacementTester, - FeeMarket.london(0L))); - } - - @Override - protected Transaction createTransaction( - final int nonce, final Optional maybeChainId) { - return createBaseTransaction(nonce).chainId(maybeChainId).createTransaction(KEY_PAIR1); - } - - @Override - protected Transaction createTransaction(final int nonce, final Wei maxPrice) { - return createBaseTransaction(nonce) - .maxFeePerGas(Optional.of(maxPrice)) - .maxPriorityFeePerGas(Optional.of(maxPrice.divide(5L))) - .createTransaction(KEY_PAIR1); - } - - @Override - protected TransactionTestFixture createBaseTransaction(final int nonce) { - return new TransactionTestFixture() - .nonce(nonce) - .gasLimit(blockGasLimit) - .gasPrice(null) - .maxFeePerGas(Optional.of(Wei.of(5000L))) - .maxPriorityFeePerGas(Optional.of(Wei.of(1000L))) - .type(TransactionType.EIP1559); - } - - @Override - protected ExecutionContextTestFixture createExecutionContextTestFixture() { - final ProtocolSchedule protocolSchedule = - new ProtocolScheduleBuilder( - new StubGenesisConfigOptions().londonBlock(0L).baseFeePerGas(10L), - BigInteger.valueOf(1), - ProtocolSpecAdapters.create(0, Function.identity()), - new PrivacyParameters(), - false, - EvmConfiguration.DEFAULT) - .createProtocolSchedule(); - final ExecutionContextTestFixture executionContextTestFixture = - ExecutionContextTestFixture.builder().protocolSchedule(protocolSchedule).build(); - - final Block block = - new Block( - new BlockHeaderTestFixture() - .gasLimit( - executionContextTestFixture - .getBlockchain() - .getChainHeadBlock() - .getHeader() - .getGasLimit()) - .difficulty(Difficulty.ONE) - .baseFeePerGas(Wei.of(10L)) - .parentHash(executionContextTestFixture.getBlockchain().getChainHeadHash()) - .number(executionContextTestFixture.getBlockchain().getChainHeadBlockNumber() + 1) - .buildHeader(), - new BlockBody(List.of(), List.of())); - executionContextTestFixture.getBlockchain().appendBlock(block, List.of()); - - return executionContextTestFixture; - } - - @Override - protected FeeMarket getFeeMarket() { - return FeeMarket.london(0L, Optional.of(BASE_FEE_FLOOR)); - } - - @Override - protected Block appendBlock( - final Difficulty difficulty, - final BlockHeader parentBlock, - final Transaction... transactionsToAdd) { - final List transactionList = asList(transactionsToAdd); - final Block block = - new Block( - new BlockHeaderTestFixture() - .baseFeePerGas(Wei.of(10L)) - .gasLimit(parentBlock.getGasLimit()) - .difficulty(difficulty) - .parentHash(parentBlock.getHash()) - .number(parentBlock.getNumber() + 1) - .buildHeader(), - new BlockBody(transactionList, emptyList())); - final List transactionReceipts = - transactionList.stream() - .map(transaction -> new TransactionReceipt(1, 1, emptyList(), Optional.empty())) - .collect(toList()); - blockchain.appendBlock(block, transactionReceipts); - return block; - } - - @Test - public void shouldAcceptZeroGasPriceFrontierTxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee() { - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); - whenBlockBaseFeeIs(Wei.ZERO); - - final Transaction frontierTransaction = createFrontierTransaction(0, Wei.ZERO); - - givenTransactionIsValid(frontierTransaction); - addAndAssertLocalTransactionValid(frontierTransaction); - } - - @Test - public void shouldAcceptZeroGasPrice1559TxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee() { - when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); - whenBlockBaseFeeIs(Wei.ZERO); - - final Transaction transaction = createTransaction(0, Wei.ZERO); - - givenTransactionIsValid(transaction); - addAndAssertLocalTransactionValid(transaction); - } - - @Test - public void shouldAcceptBaseFeeFloorGasPriceFrontierTransactionsWhenMining() { - final Transaction frontierTransaction = createFrontierTransaction(0, BASE_FEE_FLOOR); - - givenTransactionIsValid(frontierTransaction); - - addAndAssertLocalTransactionValid(frontierTransaction); - } - - @Test - public void shouldRejectRemote1559TxsWhenMaxFeePerGasBelowMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice.subtract(1L); - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) - .isEqualTo(0); - } - - @Test - public void shouldAcceptRemote1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice; - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, false)) - .isEqualTo(1); - } - - @Test - public void shouldRejectLocal1559TxsWhenMaxFeePerGasBelowMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice.subtract(1L); - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) - .isEqualTo(0); - } - - @Test - public void shouldAcceptLocal1559TxsWhenMaxFeePerGasIsAtLeastEqualToMinMinGasPrice() { - final Wei genesisBaseFee = Wei.of(100L); - final Wei minGasPrice = Wei.of(200L); - final Wei lastBlockBaseFee = minGasPrice.add(50L); - final Wei txMaxFeePerGas = minGasPrice; - - assertThat( - add1559TxAndGetPendingTxsCount( - genesisBaseFee, minGasPrice, lastBlockBaseFee, txMaxFeePerGas, true)) - .isEqualTo(1); - } - - private int add1559TxAndGetPendingTxsCount( - final Wei genesisBaseFee, - final Wei minGasPrice, - final Wei lastBlockBaseFee, - final Wei txMaxFeePerGas, - final boolean isLocal) { - when(miningParameters.getMinTransactionGasPrice()).thenReturn(minGasPrice); - when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(genesisBaseFee))); - whenBlockBaseFeeIs(lastBlockBaseFee); - - final Transaction transaction = createTransaction(0, txMaxFeePerGas); - - givenTransactionIsValid(transaction); - - if (isLocal) { - transactionPool.addTransactionViaApi(transaction); - } else { - transactionPool.addRemoteTransactions(List.of(transaction)); - } - - return transactions.size(); - } - - private void whenBlockBaseFeeIs(final Wei baseFee) { - final BlockHeader header = - BlockHeaderBuilder.fromHeader(blockchain.getChainHeadHeader()) - .baseFee(baseFee) - .blockHeaderFunctions(new MainnetBlockHeaderFunctions()) - .parentHash(blockchain.getChainHeadHash()) - .buildBlockHeader(); - blockchain.appendBlock(new Block(header, BlockBody.empty()), emptyList()); - } - - private Transaction createFrontierTransaction(final int transactionNumber, final Wei gasPrice) { - return new TransactionTestFixture() - .nonce(transactionNumber) - .gasPrice(gasPrice) - .gasLimit(blockGasLimit) - .type(TransactionType.FRONTIER) - .createTransaction(KEY_PAIR1); - } -} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java new file mode 100644 index 00000000000..e30d8814111 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolBaseFeeTest.java @@ -0,0 +1,77 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; + +import java.util.Optional; +import java.util.function.BiFunction; + +public class LayeredTransactionPoolBaseFeeTest extends AbstractLayeredTransactionPoolTest { + + @Override + protected AbstractPrioritizedTransactions createPrioritizedTransactions( + final TransactionPoolConfiguration poolConfig, + final TransactionsLayer nextLayer, + final TransactionPoolMetrics txPoolMetrics, + final BiFunction + transactionReplacementTester) { + return new BaseFeePrioritizedTransactions( + poolConfig, + protocolContext.getBlockchain()::getChainHeadHeader, + nextLayer, + txPoolMetrics, + transactionReplacementTester, + FeeMarket.london(0L)); + } + + @Override + protected Transaction createTransaction(final int nonce, final Wei maxPrice) { + return createTransactionBaseFeeMarket(nonce, maxPrice); + } + + @Override + protected TransactionTestFixture createBaseTransaction(final int nonce) { + return createBaseTransactionBaseFeeMarket(nonce); + } + + @Override + protected ExecutionContextTestFixture createExecutionContextTestFixture() { + return createExecutionContextTestFixtureBaseFeeMarket(); + } + + @Override + protected FeeMarket getFeeMarket() { + return FeeMarket.london(0L, Optional.of(BASE_FEE_FLOOR)); + } + + @Override + protected Block appendBlock( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction... transactionsToAdd) { + return appendBlockBaseFeeMarket(difficulty, parentBlock, transactionsToAdd); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java new file mode 100644 index 00000000000..34022ea0f9c --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredTransactionPoolGasPriceTest.java @@ -0,0 +1,71 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.layered; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; + +import java.util.function.BiFunction; + +public class LayeredTransactionPoolGasPriceTest extends AbstractLayeredTransactionPoolTest { + + @Override + protected AbstractPrioritizedTransactions createPrioritizedTransactions( + final TransactionPoolConfiguration poolConfig, + final TransactionsLayer nextLayer, + final TransactionPoolMetrics txPoolMetrics, + final BiFunction + transactionReplacementTester) { + return new GasPricePrioritizedTransactions( + poolConfig, nextLayer, txPoolMetrics, transactionReplacementTester); + } + + @Override + protected Transaction createTransaction(final int nonce, final Wei maxPrice) { + return createTransactionGasPriceMarket(nonce, maxPrice); + } + + @Override + protected TransactionTestFixture createBaseTransaction(final int nonce) { + return createBaseTransactionGasPriceMarket(nonce); + } + + @Override + protected ExecutionContextTestFixture createExecutionContextTestFixture() { + return ExecutionContextTestFixture.create(); + } + + @Override + protected FeeMarket getFeeMarket() { + return FeeMarket.legacy(); + } + + @Override + protected Block appendBlock( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction... transactionsToAdd) { + return appendBlockGasPriceMarket(difficulty, parentBlock, transactionsToAdd); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractLegacyTransactionPoolTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractLegacyTransactionPoolTest.java new file mode 100644 index 00000000000..1b35bd26516 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/AbstractLegacyTransactionPoolTest.java @@ -0,0 +1,61 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.sorter; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.eth.transactions.AbstractTransactionPoolTest; +import org.hyperledger.besu.ethereum.mainnet.ValidationResult; + +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +@SuppressWarnings("unchecked") +public abstract class AbstractLegacyTransactionPoolTest extends AbstractTransactionPoolTest { + + @Test + public void shouldNotAddRemoteTransactionsWhenThereIsALowestInvalidNonceForTheSender() { + givenTransactionIsValid(transaction1); + when(transactionValidatorFactory.get().validate(eq(transaction0), any(Optional.class), any())) + .thenReturn(ValidationResult.invalid(NONCE_TOO_LOW)); + + transactionPool.addRemoteTransactions(asList(transaction0, transaction1)); + + assertTransactionNotPending(transaction0); + assertTransactionNotPending(transaction1); + verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(transaction1)); + } + + @Test + public void shouldRejectRemoteTransactionsWhenAnInvalidTransactionWithLowerNonceExists() { + final Transaction invalidTx = + createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); + + givenTransactionIsValid(invalidTx); + givenTransactionIsValid(transaction1); + + addAndAssertRemoteTransactionInvalid(invalidTx); + addAndAssertRemoteTransactionInvalid(transaction1); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java new file mode 100644 index 00000000000..96ea292e983 --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolBaseFeeTest.java @@ -0,0 +1,81 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.sorter; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; + +import java.time.ZoneId; +import java.util.Optional; +import java.util.function.BiFunction; + +public class LegacyTransactionPoolBaseFeeTest extends AbstractLegacyTransactionPoolTest { + + @Override + protected PendingTransactions createPendingTransactions( + final TransactionPoolConfiguration poolConfig, + final BiFunction + transactionReplacementTester) { + + return new BaseFeePendingTransactionsSorter( + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(MAX_TRANSACTIONS) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) + .build(), + TestClock.system(ZoneId.systemDefault()), + metricsSystem, + protocolContext.getBlockchain()::getChainHeadHeader); + } + + @Override + protected Transaction createTransaction(final int transactionNumber, final Wei maxPrice) { + return createTransactionBaseFeeMarket(transactionNumber, maxPrice); + } + + @Override + protected TransactionTestFixture createBaseTransaction(final int transactionNumber) { + return createBaseTransactionBaseFeeMarket(transactionNumber); + } + + @Override + protected ExecutionContextTestFixture createExecutionContextTestFixture() { + return createExecutionContextTestFixtureBaseFeeMarket(); + } + + @Override + protected FeeMarket getFeeMarket() { + return FeeMarket.london(0L, Optional.of(BASE_FEE_FLOOR)); + } + + @Override + protected Block appendBlock( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction... transactionsToAdd) { + return appendBlockBaseFeeMarket(difficulty, parentBlock, transactionsToAdd); + } +} diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java new file mode 100644 index 00000000000..5d20e04de0e --- /dev/null +++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/sorter/LegacyTransactionPoolGasPriceTest.java @@ -0,0 +1,80 @@ +/* + * Copyright Hyperledger Besu Contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package org.hyperledger.besu.ethereum.eth.transactions.sorter; + +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Block; +import org.hyperledger.besu.ethereum.core.BlockHeader; +import org.hyperledger.besu.ethereum.core.Difficulty; +import org.hyperledger.besu.ethereum.core.ExecutionContextTestFixture; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.TransactionTestFixture; +import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction; +import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactions; +import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration; +import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; +import org.hyperledger.besu.testutil.TestClock; +import org.hyperledger.besu.util.number.Fraction; + +import java.time.ZoneId; +import java.util.function.BiFunction; + +public class LegacyTransactionPoolGasPriceTest extends AbstractLegacyTransactionPoolTest { + + @Override + protected PendingTransactions createPendingTransactions( + final TransactionPoolConfiguration poolConfig, + final BiFunction + transactionReplacementTester) { + + return new GasPricePendingTransactionsSorter( + ImmutableTransactionPoolConfiguration.builder() + .txPoolMaxSize(MAX_TRANSACTIONS) + .txPoolLimitByAccountPercentage(Fraction.fromFloat(1.0f)) + .build(), + TestClock.system(ZoneId.systemDefault()), + metricsSystem, + protocolContext.getBlockchain()::getChainHeadHeader); + } + + @Override + protected Transaction createTransaction(final int transactionNumber, final Wei maxPrice) { + return createTransactionGasPriceMarket(transactionNumber, maxPrice); + } + + @Override + protected TransactionTestFixture createBaseTransaction(final int transactionNumber) { + return createBaseTransactionGasPriceMarket(transactionNumber); + } + + @Override + protected ExecutionContextTestFixture createExecutionContextTestFixture() { + return ExecutionContextTestFixture.create(); + } + + @Override + protected FeeMarket getFeeMarket() { + return FeeMarket.legacy(); + } + + @Override + protected Block appendBlock( + final Difficulty difficulty, + final BlockHeader parentBlock, + final Transaction... transactionsToAdd) { + return appendBlockGasPriceMarket(difficulty, parentBlock, transactionsToAdd); + } +}