mirror of https://github.com/hyperledger/besu
Refactor transaction pool tests (#4632)
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>pull/4659/head
parent
b181f99940
commit
fe0b628e0e
@ -0,0 +1,702 @@ |
||||
/* |
||||
* 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.anyLong; |
||||
import static org.mockito.ArgumentMatchers.eq; |
||||
import static org.mockito.ArgumentMatchers.nullable; |
||||
import static org.mockito.Mockito.atLeastOnce; |
||||
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.eth.transactions.sorter.AbstractPendingTransactionsSorter; |
||||
import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionValidator; |
||||
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.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.Consumer; |
||||
|
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
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(); |
||||
@Mock protected MainnetTransactionValidator transactionValidator; |
||||
@Mock protected PendingTransactionListener 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; |
||||
|
||||
private AbstractPendingTransactionsSorter transactions; |
||||
private final Transaction transaction1 = createTransaction(1); |
||||
private final Transaction transaction2 = createTransaction(2); |
||||
|
||||
private 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; |
||||
private PeerTransactionTracker peerTransactionTracker; |
||||
private ArgumentCaptor<Runnable> syncTaskCapture; |
||||
|
||||
protected abstract AbstractPendingTransactionsSorter createPendingTransactionsSorter(); |
||||
|
||||
protected abstract ExecutionContextTestFixture createExecutionContextTestFixture(); |
||||
|
||||
protected abstract FeeMarket getFeeMarket(); |
||||
|
||||
@Before |
||||
public void setUp() { |
||||
executionContext = createExecutionContextTestFixture(); |
||||
protocolContext = executionContext.getProtocolContext(); |
||||
blockchain = executionContext.getBlockchain(); |
||||
transactions = spy(createPendingTransactionsSorter()); |
||||
when(protocolSpec.getTransactionValidator()).thenReturn(transactionValidator); |
||||
when(protocolSpec.getFeeMarket()).thenReturn(getFeeMarket()); |
||||
protocolSchedule = spy(executionContext.getProtocolSchedule()); |
||||
doReturn(protocolSpec).when(protocolSchedule).getByBlockNumber(anyLong()); |
||||
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(); |
||||
transactionBroadcaster = |
||||
spy( |
||||
new TransactionBroadcaster( |
||||
ethContext, |
||||
transactions, |
||||
peerTransactionTracker, |
||||
transactionsMessageSender, |
||||
newPooledTransactionHashesMessageSender)); |
||||
|
||||
transactionPool = createTransactionPool(); |
||||
blockchain.observeBlockAdded(transactionPool); |
||||
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.of(2)); |
||||
} |
||||
|
||||
protected TransactionPool createTransactionPool() { |
||||
return createTransactionPool(b -> {}); |
||||
} |
||||
|
||||
protected TransactionPool createTransactionPool( |
||||
final Consumer<ImmutableTransactionPoolConfiguration.Builder> configConsumer) { |
||||
final ImmutableTransactionPoolConfiguration.Builder configBuilder = |
||||
ImmutableTransactionPoolConfiguration.builder(); |
||||
configConsumer.accept(configBuilder); |
||||
final TransactionPoolConfiguration config = configBuilder.build(); |
||||
|
||||
return new TransactionPool( |
||||
transactions, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
transactionBroadcaster, |
||||
ethContext, |
||||
miningParameters, |
||||
metricsSystem, |
||||
config); |
||||
} |
||||
|
||||
@Test |
||||
public void localTransactionHappyPath() { |
||||
final Transaction transaction = createTransaction(0); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
assertLocalTransactionValid(transaction); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnExclusivelyLocalTransactionsWhenAppropriate() { |
||||
final Transaction localTransaction0 = createTransaction(0); |
||||
|
||||
givenTransactionIsValid(localTransaction0); |
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
|
||||
assertLocalTransactionValid(localTransaction0); |
||||
assertRemoteTransactionValid(transaction1); |
||||
assertRemoteTransactionValid(transaction2); |
||||
|
||||
assertThat(transactions.size()).isEqualTo(3); |
||||
List<Transaction> localTransactions = transactions.getLocalTransactions(); |
||||
assertThat(localTransactions.size()).isEqualTo(1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRemoveTransactionsFromPendingListWhenIncludedInBlockOnchain() { |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
assertTransactionPending(transaction1); |
||||
appendBlock(transaction1); |
||||
|
||||
assertTransactionNotPending(transaction1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRemoveMultipleTransactionsAddedInOneBlock() { |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
transactions.addRemoteTransaction(transaction2, Optional.empty()); |
||||
appendBlock(transaction1, transaction2); |
||||
|
||||
assertTransactionNotPending(transaction1); |
||||
assertTransactionNotPending(transaction2); |
||||
assertThat(transactions.size()).isZero(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldIgnoreUnknownTransactionsThatAreAddedInABlock() { |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
appendBlock(transaction1, transaction2); |
||||
|
||||
assertTransactionNotPending(transaction1); |
||||
assertTransactionNotPending(transaction2); |
||||
assertThat(transactions.size()).isZero(); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotRemovePendingTransactionsWhenABlockAddedToAFork() { |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
final BlockHeader commonParent = getHeaderForCurrentChainHead(); |
||||
final Block canonicalHead = appendBlock(Difficulty.of(1000), commonParent); |
||||
appendBlock(Difficulty.ONE, commonParent, transaction1); |
||||
|
||||
verifyChainHeadIs(canonicalHead); |
||||
|
||||
assertTransactionPending(transaction1); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRemovePendingTransactionsFromAllBlocksOnAForkWhenItBecomesTheCanonicalChain() { |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
transactions.addRemoteTransaction(transaction2, Optional.empty()); |
||||
final BlockHeader commonParent = getHeaderForCurrentChainHead(); |
||||
final Block originalChainHead = appendBlock(Difficulty.of(1000), commonParent); |
||||
|
||||
final Block forkBlock1 = appendBlock(Difficulty.ONE, commonParent, transaction1); |
||||
verifyChainHeadIs(originalChainHead); |
||||
|
||||
final Block forkBlock2 = appendBlock(Difficulty.of(2000), forkBlock1.getHeader(), transaction2); |
||||
verifyChainHeadIs(forkBlock2); |
||||
|
||||
assertTransactionNotPending(transaction1); |
||||
assertTransactionNotPending(transaction2); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReAddTransactionsFromThePreviousCanonicalHeadWhenAReorgOccurs() { |
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transactionOtherSender); |
||||
transactions.addLocalTransaction(transaction1, Optional.empty()); |
||||
transactions.addRemoteTransaction(transactionOtherSender, Optional.empty()); |
||||
final BlockHeader commonParent = getHeaderForCurrentChainHead(); |
||||
final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction1); |
||||
final Block originalFork2 = |
||||
appendBlock(Difficulty.ONE, originalFork1.getHeader(), transactionOtherSender); |
||||
assertTransactionNotPending(transaction1); |
||||
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(transaction1); |
||||
assertTransactionPending(transactionOtherSender); |
||||
assertThat(transactions.getLocalTransactions()).contains(transaction1); |
||||
assertThat(transactions.getLocalTransactions()).doesNotContain(transactionOtherSender); |
||||
verify(listener).onTransactionAdded(transaction1); |
||||
verify(listener).onTransactionAdded(transactionOtherSender); |
||||
verifyNoMoreInteractions(listener); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotReAddTransactionsThatAreInBothForksWhenReorgHappens() { |
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
transactions.addRemoteTransaction(transaction1, Optional.empty()); |
||||
transactions.addRemoteTransaction(transaction2, Optional.empty()); |
||||
final BlockHeader commonParent = getHeaderForCurrentChainHead(); |
||||
final Block originalFork1 = appendBlock(Difficulty.of(1000), commonParent, transaction1); |
||||
final Block originalFork2 = |
||||
appendBlock(Difficulty.ONE, originalFork1.getHeader(), transaction2); |
||||
assertTransactionNotPending(transaction1); |
||||
assertTransactionNotPending(transaction2); |
||||
|
||||
final Block reorgFork1 = appendBlock(Difficulty.ONE, commonParent, transaction1); |
||||
verifyChainHeadIs(originalFork2); |
||||
|
||||
final Block reorgFork2 = appendBlock(Difficulty.of(2000), reorgFork1.getHeader()); |
||||
verifyChainHeadIs(reorgFork2); |
||||
|
||||
assertTransactionNotPending(transaction1); |
||||
assertTransactionPending(transaction2); |
||||
} |
||||
|
||||
@Test |
||||
public void addLocalTransaction_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() { |
||||
protocolSupportsTxReplayProtection(1337, true); |
||||
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); |
||||
final Transaction tx = createTransaction(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertLocalTransactionValid(tx); |
||||
} |
||||
|
||||
@Test |
||||
public void addRemoteTransactions_strictReplayProtectionOn_txWithChainId_chainIdIsConfigured() { |
||||
protocolSupportsTxReplayProtection(1337, true); |
||||
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); |
||||
final Transaction tx = createTransaction(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertRemoteTransactionValid(tx); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotAddRemoteTransactionsWhenGasPriceBelowMinimum() { |
||||
final Transaction transaction = createTransaction(1, Wei.ONE); |
||||
transactionPool.addRemoteTransactions(singletonList(transaction)); |
||||
|
||||
assertTransactionNotPending(transaction); |
||||
verifyNoInteractions(transactionValidator); // Reject before validation
|
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotAddRemoteTransactionsWhenThereIsAnLowestInvalidNonceForTheSender() { |
||||
givenTransactionIsValid(transaction2); |
||||
when(transactionValidator.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(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
when(transactionValidator.validateForSender( |
||||
eq(transaction2), eq(null), any(TransactionValidationParams.class))) |
||||
.thenReturn(ValidationResult.invalid(NONCE_TOO_LOW)); |
||||
transactionPool.addRemoteTransactions(asList(transaction1, transaction2)); |
||||
|
||||
assertTransactionPending(transaction1); |
||||
assertTransactionNotPending(transaction2); |
||||
verify(transactionBroadcaster).onTransactionsAdded(singletonList(transaction1)); |
||||
verify(transactionValidator).validate(eq(transaction1), any(Optional.class), any()); |
||||
verify(transactionValidator) |
||||
.validateForSender(eq(transaction1), eq(null), any(TransactionValidationParams.class)); |
||||
verify(transactionValidator).validate(eq(transaction2), any(Optional.class), any()); |
||||
verify(transactionValidator).validateForSender(eq(transaction2), any(), any()); |
||||
verify(transactionValidator, atLeastOnce()).getGoQuorumCompatibilityMode(); |
||||
verifyNoMoreInteractions(transactionValidator); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSender() { |
||||
final Transaction transaction1 = createTransaction(1); |
||||
final Transaction transaction2 = createTransaction(2); |
||||
final Transaction transaction3 = createTransaction(3); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
givenTransactionIsValid(transaction3); |
||||
|
||||
assertLocalTransactionValid(transaction1); |
||||
assertLocalTransactionValid(transaction2); |
||||
assertLocalTransactionValid(transaction3); |
||||
} |
||||
|
||||
@Test |
||||
public void |
||||
shouldAllowSequenceOfTransactionsWithIncreasingNonceFromSameSenderWhenSentInBatchOutOfOrder() { |
||||
final Transaction transaction1 = createTransaction(1); |
||||
final Transaction transaction2 = createTransaction(2); |
||||
final Transaction transaction3 = createTransaction(3); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
givenTransactionIsValid(transaction3); |
||||
|
||||
assertRemoteTransactionValid(transaction3); |
||||
assertRemoteTransactionValid(transaction1); |
||||
assertRemoteTransactionValid(transaction2); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldDiscardRemoteTransactionThatAlreadyExistsBeforeValidation() { |
||||
doReturn(true).when(transactions).containsTransaction(transaction1.getHash()); |
||||
transactionPool.addRemoteTransactions(singletonList(transaction1)); |
||||
|
||||
verify(transactions).containsTransaction(transaction1.getHash()); |
||||
verifyNoInteractions(transactionValidator); |
||||
verifyNoMoreInteractions(transactions); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotNotifyBatchListenerWhenRemoteTransactionDoesNotReplaceExisting() { |
||||
final Transaction transaction1 = createTransaction(1, Wei.of(100)); |
||||
final Transaction transaction2 = createTransaction(1, Wei.of(50)); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
|
||||
assertRemoteTransactionValid(transaction1); |
||||
assertRemoteTransactionInvalid(transaction2); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotNotifyBatchListenerWhenLocalTransactionDoesNotReplaceExisting() { |
||||
final Transaction transaction1 = createTransaction(1, Wei.of(10)); |
||||
final Transaction transaction2 = createTransaction(1, Wei.of(9)); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
givenTransactionIsValid(transaction2); |
||||
|
||||
assertLocalTransactionValid(transaction1); |
||||
assertLocalTransactionInvalid(transaction2, TRANSACTION_REPLACEMENT_UNDERPRICED); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRejectLocalTransactionsWhereGasLimitExceedBlockGasLimit() { |
||||
final Transaction transaction1 = |
||||
createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
|
||||
assertLocalTransactionInvalid(transaction1, 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 = |
||||
createBaseTransaction(0).gasLimit(blockGasLimit + 1).createTransaction(KEY_PAIR1); |
||||
|
||||
final Transaction nextTx = createTransaction(1); |
||||
|
||||
givenTransactionIsValid(invalidTx); |
||||
givenTransactionIsValid(nextTx); |
||||
|
||||
assertRemoteTransactionInvalid(invalidTx); |
||||
assertRemoteTransactionInvalid(nextTx); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAcceptLocalTransactionsEvenIfAnInvalidTransactionWithLowerNonceExists() { |
||||
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); |
||||
|
||||
assertLocalTransactionInvalid(invalidTx, EXCEEDS_BLOCK_GAS_LIMIT); |
||||
assertLocalTransactionValid(nextTx); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRejectLocalTransactionsWhenNonceTooFarInFuture() { |
||||
final Transaction transaction1 = createTransaction(Integer.MAX_VALUE); |
||||
|
||||
givenTransactionIsValid(transaction1); |
||||
|
||||
assertLocalTransactionInvalid(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(transaction1); |
||||
transactionPool.addLocalTransaction(transaction1); |
||||
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(transaction1); |
||||
transactionPool.addLocalTransaction(transaction1); |
||||
transactionPool.handleConnect(peer); |
||||
syncTaskCapture.getValue().run(); |
||||
verify(transactionsMessageSender).sendTransactionsToPeer(peer); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldSendFullTransactionPoolToNewlyConnectedPeer() { |
||||
final Transaction transactionLocal = createTransaction(1); |
||||
final Transaction transactionRemote = createTransaction(2); |
||||
|
||||
givenTransactionIsValid(transactionLocal); |
||||
givenTransactionIsValid(transactionRemote); |
||||
|
||||
transactionPool.addLocalTransaction(transactionLocal); |
||||
transactionPool.addRemoteTransactions(Collections.singletonList(transactionRemote)); |
||||
|
||||
RespondingEthPeer peer = EthProtocolManagerTestUtil.createPeer(ethProtocolManager); |
||||
|
||||
Set<Transaction> transactionsToSendToPeer = |
||||
peerTransactionTracker.claimTransactionsToSendToPeer(peer.getEthPeer()); |
||||
|
||||
assertThat(transactionsToSendToPeer).contains(transactionLocal, transactionRemote); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldCallValidatorWithExpectedValidationParameters() { |
||||
final ArgumentCaptor<TransactionValidationParams> txValidationParamCaptor = |
||||
ArgumentCaptor.forClass(TransactionValidationParams.class); |
||||
|
||||
when(transactionValidator.validate(eq(transaction1), any(Optional.class), any())) |
||||
.thenReturn(valid()); |
||||
when(transactionValidator.validateForSender(any(), any(), txValidationParamCaptor.capture())) |
||||
.thenReturn(valid()); |
||||
|
||||
final TransactionValidationParams expectedValidationParams = |
||||
TransactionValidationParams.transactionPool(); |
||||
|
||||
transactionPool.addLocalTransaction(transaction1); |
||||
|
||||
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(1, twoEthers.add(Wei.of(1))); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
assertLocalTransactionValid(transaction); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRejectLocalTransactionIfFeeCapExceeded() { |
||||
final Wei twoEthers = Wei.fromEth(2); |
||||
transactionPool = createTransactionPool(b -> b.txFeeCap(twoEthers)); |
||||
|
||||
final Transaction transactionLocal = createTransaction(1, twoEthers.add(1)); |
||||
|
||||
givenTransactionIsValid(transactionLocal); |
||||
|
||||
assertLocalTransactionInvalid(transactionLocal, TX_FEECAP_EXCEEDED); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRejectZeroGasPriceTransactionWhenNotMining() { |
||||
when(miningParameters.isMiningEnabled()).thenReturn(false); |
||||
|
||||
final Transaction transaction = createTransaction(0, Wei.ZERO); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
assertLocalTransactionInvalid(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 transactionNumber, final Optional<BigInteger> maybeChainId); |
||||
|
||||
protected abstract Transaction createTransaction(final int transactionNumber, final Wei maxPrice); |
||||
|
||||
protected abstract TransactionTestFixture createBaseTransaction(final int transactionNumber); |
||||
|
||||
private Transaction createTransaction(final int transactionNumber) { |
||||
return createTransaction(transactionNumber, Optional.of(BigInteger.ONE)); |
||||
} |
||||
|
||||
private Transaction createTransaction(final int transactionNumber, final KeyPair keyPair) { |
||||
return createBaseTransaction(transactionNumber).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(transactionValidator.validate(eq(transaction), any(Optional.class), any())) |
||||
.thenReturn(valid()); |
||||
when(transactionValidator.validateForSender( |
||||
eq(transaction), nullable(Account.class), any(TransactionValidationParams.class))) |
||||
.thenReturn(valid()); |
||||
} |
||||
|
||||
protected void assertLocalTransactionInvalid( |
||||
final Transaction tx, final TransactionInvalidReason invalidReason) { |
||||
final ValidationResult<TransactionInvalidReason> result = |
||||
transactionPool.addLocalTransaction(tx); |
||||
|
||||
assertThat(result.isValid()).isFalse(); |
||||
assertThat(result.getInvalidReason()).isEqualTo(invalidReason); |
||||
assertTransactionNotPending(tx); |
||||
verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); |
||||
} |
||||
|
||||
protected void assertLocalTransactionValid(final Transaction tx) { |
||||
final ValidationResult<TransactionInvalidReason> result = |
||||
transactionPool.addLocalTransaction(tx); |
||||
|
||||
assertThat(result.isValid()).isTrue(); |
||||
assertTransactionPending(tx); |
||||
verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); |
||||
assertThat(transactions.getLocalTransactions()).contains(tx); |
||||
} |
||||
|
||||
protected void assertRemoteTransactionValid(final Transaction tx) { |
||||
transactionPool.addRemoteTransactions(List.of(tx)); |
||||
|
||||
verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); |
||||
assertTransactionPending(tx); |
||||
assertThat(transactions.getLocalTransactions()).doesNotContain(tx); |
||||
} |
||||
|
||||
protected void assertRemoteTransactionInvalid(final Transaction tx) { |
||||
transactionPool.addRemoteTransactions(List.of(tx)); |
||||
|
||||
verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); |
||||
assertTransactionNotPending(tx); |
||||
} |
||||
} |
@ -0,0 +1,263 @@ |
||||
/* |
||||
* 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.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.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.AbstractPendingTransactionsSorter; |
||||
import org.hyperledger.besu.ethereum.eth.transactions.sorter.GasPricePendingTransactionsSorter; |
||||
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.plugin.data.TransactionType; |
||||
import org.hyperledger.besu.testutil.TestClock; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.time.ZoneId; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class TransactionPoolLegacyTest extends AbstractTransactionPoolTest { |
||||
|
||||
@Override |
||||
protected AbstractPendingTransactionsSorter createPendingTransactionsSorter() { |
||||
|
||||
return new GasPricePendingTransactionsSorter( |
||||
ImmutableTransactionPoolConfiguration.builder() |
||||
.txPoolMaxSize(MAX_TRANSACTIONS) |
||||
.txPoolLimitByAccountPercentage(1) |
||||
.build(), |
||||
TestClock.system(ZoneId.systemDefault()), |
||||
metricsSystem, |
||||
protocolContext.getBlockchain()::getChainHeadHeader); |
||||
} |
||||
|
||||
@Override |
||||
protected Transaction createTransaction( |
||||
final int transactionNumber, final Optional<BigInteger> 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<Transaction> 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<TransactionReceipt> 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(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertLocalTransactionValid(tx); |
||||
} |
||||
|
||||
@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); |
||||
} |
||||
|
||||
@Test |
||||
public void addLocalTransaction_strictReplayProtectionOff_txWithoutChainId_chainIdIsConfigured() { |
||||
protocolSupportsTxReplayProtection(1337, true); |
||||
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(false)); |
||||
final Transaction tx = createTransactionWithoutChainId(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertLocalTransactionValid(tx); |
||||
} |
||||
|
||||
@Test |
||||
public void addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsConfigured() { |
||||
protocolSupportsTxReplayProtection(1337, true); |
||||
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); |
||||
final Transaction tx = createTransactionWithoutChainId(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertLocalTransactionInvalid(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); |
||||
} |
||||
|
||||
@Test |
||||
public void |
||||
addLocalTransaction_strictReplayProtectionOn_txWithoutChainId_chainIdIsNotConfigured() { |
||||
protocolDoesNotSupportTxReplayProtection(); |
||||
transactionPool = createTransactionPool(b -> b.strictTransactionReplayProtectionEnabled(true)); |
||||
final Transaction tx = createTransactionWithoutChainId(1); |
||||
givenTransactionIsValid(tx); |
||||
|
||||
assertLocalTransactionValid(tx); |
||||
} |
||||
|
||||
@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); |
||||
|
||||
assertLocalTransactionInvalid(transaction, INVALID_TRANSACTION_FORMAT); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldRejectGoQuorumTransactionWithNonZeroValue() { |
||||
when(transactionValidator.getGoQuorumCompatibilityMode()).thenReturn(true); |
||||
|
||||
final Transaction transaction37 = |
||||
Transaction.builder().v(BigInteger.valueOf(37)).value(Wei.ONE).build(); |
||||
final Transaction transaction38 = |
||||
Transaction.builder().v(BigInteger.valueOf(38)).value(Wei.ONE).build(); |
||||
|
||||
final ValidationResult<TransactionInvalidReason> result37 = |
||||
transactionPool.addLocalTransaction(transaction37); |
||||
final ValidationResult<TransactionInvalidReason> result38 = |
||||
transactionPool.addLocalTransaction(transaction38); |
||||
|
||||
assertThat(result37.getInvalidReason()) |
||||
.isEqualTo(TransactionInvalidReason.ETHER_VALUE_NOT_SUPPORTED); |
||||
assertThat(result38.getInvalidReason()) |
||||
.isEqualTo(TransactionInvalidReason.ETHER_VALUE_NOT_SUPPORTED); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAcceptZeroGasPriceFrontierTransactionsWhenMining() { |
||||
when(miningParameters.isMiningEnabled()).thenReturn(true); |
||||
|
||||
final Transaction transaction = createTransaction(0, Wei.ZERO); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
assertLocalTransactionValid(transaction); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAcceptZeroGasPriceTransactionWhenMinGasPriceIsZero() { |
||||
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); |
||||
|
||||
final Transaction transaction = createTransaction(0, Wei.ZERO); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
assertLocalTransactionValid(transaction); |
||||
} |
||||
|
||||
private Transaction createTransactionWithoutChainId(final int transactionNumber) { |
||||
return createTransaction(transactionNumber, Optional.empty()); |
||||
} |
||||
|
||||
private void protocolDoesNotSupportTxReplayProtection() { |
||||
when(protocolSchedule.getChainId()).thenReturn(Optional.empty()); |
||||
} |
||||
} |
@ -0,0 +1,221 @@ |
||||
/* |
||||
* 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.mockito.Mockito.when; |
||||
|
||||
import org.hyperledger.besu.config.StubGenesisConfigOptions; |
||||
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.AbstractPendingTransactionsSorter; |
||||
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.plugin.data.TransactionType; |
||||
import org.hyperledger.besu.testutil.TestClock; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.time.ZoneId; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.function.Function; |
||||
|
||||
import org.junit.Ignore; |
||||
import org.junit.Test; |
||||
|
||||
public class TransactionPoolLondonTest extends AbstractTransactionPoolTest { |
||||
|
||||
private static final Wei BASE_FEE_FLOOR = Wei.of(7L); |
||||
|
||||
@Override |
||||
protected AbstractPendingTransactionsSorter createPendingTransactionsSorter() { |
||||
|
||||
return new BaseFeePendingTransactionsSorter( |
||||
ImmutableTransactionPoolConfiguration.builder() |
||||
.txPoolMaxSize(MAX_TRANSACTIONS) |
||||
.txPoolLimitByAccountPercentage(1) |
||||
.build(), |
||||
TestClock.system(ZoneId.systemDefault()), |
||||
metricsSystem, |
||||
protocolContext.getBlockchain()::getChainHeadHeader); |
||||
} |
||||
|
||||
@Override |
||||
protected Transaction createTransaction( |
||||
final int transactionNumber, final Optional<BigInteger> 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, |
||||
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<Transaction> 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<TransactionReceipt> 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))); |
||||
whenBlockBaseFeeIsZero(); |
||||
|
||||
final Transaction frontierTransaction = createFrontierTransaction(0, Wei.ZERO); |
||||
|
||||
givenTransactionIsValid(frontierTransaction); |
||||
assertLocalTransactionValid(frontierTransaction); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAcceptZeroGasPrice1559TxsWhenMinGasPriceIsZeroAndLondonWithZeroBaseFee() { |
||||
when(miningParameters.getMinTransactionGasPrice()).thenReturn(Wei.ZERO); |
||||
when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(Wei.ZERO))); |
||||
whenBlockBaseFeeIsZero(); |
||||
|
||||
final Transaction transaction = createTransaction(0, Wei.ZERO); |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
assertLocalTransactionValid(transaction); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldAcceptBaseFeeFloorGasPriceFrontierTransactionsWhenMining() { |
||||
final Transaction frontierTransaction = createFrontierTransaction(0, BASE_FEE_FLOOR); |
||||
|
||||
givenTransactionIsValid(frontierTransaction); |
||||
|
||||
assertLocalTransactionValid(frontierTransaction); |
||||
} |
||||
|
||||
@Test |
||||
@Override |
||||
@Ignore |
||||
public void shouldRejectLocalTransactionIfFeeCapExceeded() { |
||||
// ignore since this is going to fail until the branch with the fix is released
|
||||
} |
||||
|
||||
private void whenBlockBaseFeeIsZero() { |
||||
final BlockHeader header = |
||||
BlockHeaderBuilder.fromHeader(blockchain.getChainHeadHeader()) |
||||
.baseFee(Wei.ZERO) |
||||
.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); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue