mirror of https://github.com/hyperledger/besu
pull/7638/head
commit
66a9de2dc3
@ -0,0 +1,585 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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 java.util.stream.Collectors.toList; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.hyperledger.besu.ethereum.mainnet.ValidationResult.valid; |
||||
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.never; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.when; |
||||
import static org.mockito.quality.Strictness.LENIENT; |
||||
|
||||
import org.hyperledger.besu.config.GenesisConfigFile; |
||||
import org.hyperledger.besu.crypto.KeyPair; |
||||
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; |
||||
import org.hyperledger.besu.datatypes.BlobsWithCommitments; |
||||
import org.hyperledger.besu.datatypes.TransactionType; |
||||
import org.hyperledger.besu.datatypes.Wei; |
||||
import org.hyperledger.besu.ethereum.ProtocolContext; |
||||
import org.hyperledger.besu.ethereum.chain.BadBlockManager; |
||||
import org.hyperledger.besu.ethereum.chain.MutableBlockchain; |
||||
import org.hyperledger.besu.ethereum.core.BlobTestFixture; |
||||
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.EthProtocolManager; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthProtocolManagerTestUtil; |
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler; |
||||
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.TransactionPoolValidatorService; |
||||
import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidator; |
||||
import org.hyperledger.besu.plugin.services.txvalidator.PluginTransactionPoolValidatorFactory; |
||||
import org.hyperledger.besu.testutil.DeterministicEthScheduler; |
||||
|
||||
import java.math.BigInteger; |
||||
import java.util.Arrays; |
||||
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.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import org.junit.jupiter.api.extension.ExtensionContext; |
||||
import org.mockito.Answers; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.jupiter.MockitoExtension; |
||||
import org.mockito.junit.jupiter.MockitoSettings; |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@ExtendWith(MockitoExtension.class) |
||||
@MockitoSettings(strictness = LENIENT) |
||||
public abstract class AbstractTransactionPoolTestBase { |
||||
|
||||
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); |
||||
protected static final Wei DEFAULT_MIN_GAS_PRICE = Wei.of(50L); |
||||
|
||||
protected final EthScheduler ethScheduler = new DeterministicEthScheduler(); |
||||
|
||||
@Mock(answer = Answers.RETURNS_DEEP_STUBS) |
||||
protected TransactionValidatorFactory transactionValidatorFactory; |
||||
|
||||
@Mock protected PendingTransactionAddedListener listener; |
||||
|
||||
@Mock protected TransactionsMessageSender transactionsMessageSender; |
||||
@Mock protected NewPooledTransactionHashesMessageSender newPooledTransactionHashesMessageSender; |
||||
@Mock protected ProtocolSpec protocolSpec; |
||||
|
||||
protected ProtocolSchedule protocolSchedule; |
||||
|
||||
protected final MetricsSystem metricsSystem = new NoOpMetricsSystem(); |
||||
protected MutableBlockchain blockchain; |
||||
protected TransactionBroadcaster transactionBroadcaster; |
||||
|
||||
protected PendingTransactions transactions; |
||||
protected final Transaction transaction0 = createTransaction(0); |
||||
protected final Transaction transaction1 = createTransaction(1); |
||||
protected final Transaction transactionWithBlobs = createBlobTransaction(2); |
||||
protected final Transaction transactionWithBlobsReplacement = |
||||
createReplacementTransactionWithBlobs(2); |
||||
protected final Transaction transactionWithSameBlobs = |
||||
createBlobTransactionWithSameBlobs(3, transactionWithBlobs.getBlobsWithCommitments().get()); |
||||
protected final Transaction transactionWithSameBlobsReplacement = |
||||
createReplacementTransactionWithBlobs(3); |
||||
|
||||
protected final Transaction transactionOtherSender = createTransaction(1, KEY_PAIR2); |
||||
private ExecutionContextTestFixture executionContext; |
||||
protected ProtocolContext protocolContext; |
||||
protected TransactionPool transactionPool; |
||||
protected long blockGasLimit; |
||||
protected EthProtocolManager ethProtocolManager; |
||||
protected EthContext ethContext; |
||||
protected ArgumentCaptor<Runnable> syncTaskCapture; |
||||
protected PeerTransactionTracker peerTransactionTracker; |
||||
private BlobTestFixture blobTestFixture; |
||||
|
||||
protected abstract PendingTransactions createPendingTransactions( |
||||
final TransactionPoolConfiguration poolConfig, |
||||
final BiFunction<PendingTransaction, PendingTransaction, Boolean> |
||||
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 var genesisConfigFile = GenesisConfigFile.fromResource("/txpool-test-genesis.json"); |
||||
final ProtocolSchedule protocolSchedule = |
||||
new ProtocolScheduleBuilder( |
||||
genesisConfigFile.getConfigOptions(), |
||||
BigInteger.valueOf(1), |
||||
ProtocolSpecAdapters.create(0, Function.identity()), |
||||
new PrivacyParameters(), |
||||
false, |
||||
EvmConfiguration.DEFAULT, |
||||
MiningParameters.MINING_DISABLED, |
||||
new BadBlockManager(), |
||||
false, |
||||
new NoOpMetricsSystem()) |
||||
.createProtocolSchedule(); |
||||
final ExecutionContextTestFixture executionContextTestFixture = |
||||
ExecutionContextTestFixture.builder(genesisConfigFile) |
||||
.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 |
||||
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 = spy(ethContext.getScheduler()); |
||||
syncTaskCapture = ArgumentCaptor.forClass(Runnable.class); |
||||
doNothing().when(ethScheduler).scheduleSyncWorkerTask(syncTaskCapture.capture()); |
||||
doReturn(ethScheduler).when(ethContext).getScheduler(); |
||||
|
||||
peerTransactionTracker = new PeerTransactionTracker(ethContext.getEthPeers()); |
||||
transactionBroadcaster = |
||||
spy( |
||||
new TransactionBroadcaster( |
||||
ethContext, |
||||
peerTransactionTracker, |
||||
transactionsMessageSender, |
||||
newPooledTransactionHashesMessageSender)); |
||||
|
||||
transactionPool = createTransactionPool(); |
||||
blockchain.observeBlockAdded(transactionPool); |
||||
} |
||||
|
||||
protected TransactionPool createTransactionPool() { |
||||
return createTransactionPool(b -> b.minGasPrice(Wei.of(2))); |
||||
} |
||||
|
||||
TransactionPool createTransactionPool( |
||||
final Consumer<ImmutableTransactionPoolConfiguration.Builder> configConsumer) { |
||||
final ImmutableTransactionPoolConfiguration.Builder configBuilder = |
||||
ImmutableTransactionPoolConfiguration.builder(); |
||||
configConsumer.accept(configBuilder); |
||||
final TransactionPoolConfiguration poolConfig = configBuilder.build(); |
||||
|
||||
final TransactionPoolReplacementHandler transactionReplacementHandler = |
||||
new TransactionPoolReplacementHandler( |
||||
poolConfig.getPriceBump(), poolConfig.getBlobPriceBump()); |
||||
|
||||
final BiFunction<PendingTransaction, PendingTransaction, Boolean> transactionReplacementTester = |
||||
(t1, t2) -> |
||||
transactionReplacementHandler.shouldReplace( |
||||
t1, t2, protocolContext.getBlockchain().getChainHeadHeader()); |
||||
|
||||
transactions = spy(createPendingTransactions(poolConfig, transactionReplacementTester)); |
||||
|
||||
final TransactionPool txPool = |
||||
new TransactionPool( |
||||
() -> transactions, |
||||
protocolSchedule, |
||||
protocolContext, |
||||
transactionBroadcaster, |
||||
ethContext, |
||||
new TransactionPoolMetrics(metricsSystem), |
||||
poolConfig, |
||||
new BlobCache()); |
||||
txPool.setEnabled(); |
||||
return txPool; |
||||
} |
||||
|
||||
static TransactionPoolValidatorService getTransactionPoolValidatorServiceReturning( |
||||
final String errorMessage) { |
||||
return new TransactionPoolValidatorService() { |
||||
@Override |
||||
public PluginTransactionPoolValidator createTransactionValidator() { |
||||
return (transaction, isLocal, hasPriority) -> Optional.ofNullable(errorMessage); |
||||
} |
||||
|
||||
@Override |
||||
public void registerPluginTransactionValidatorFactory( |
||||
final PluginTransactionPoolValidatorFactory pluginTransactionPoolValidatorFactory) {} |
||||
}; |
||||
} |
||||
|
||||
@SuppressWarnings("unused") |
||||
private static boolean isBaseFeeMarket(final ExtensionContext extensionContext) { |
||||
final Class<?> cz = extensionContext.getTestClass().get(); |
||||
|
||||
return cz.equals(LegacyTransactionPoolBaseFeeTest.class) |
||||
|| cz.equals(LayeredTransactionPoolBaseFeeTest.class) |
||||
|| cz.equals(BlobV1TransactionPoolTest.class); |
||||
} |
||||
|
||||
protected void assertTransactionNotPending(final Transaction transaction) { |
||||
assertThat(transactions.getTransactionByHash(transaction.getHash())).isEmpty(); |
||||
} |
||||
|
||||
protected void addAndAssertRemoteTransactionInvalid(final Transaction tx) { |
||||
transactionPool.addRemoteTransactions(List.of(tx)); |
||||
|
||||
verify(transactionBroadcaster, never()).onTransactionsAdded(singletonList(tx)); |
||||
assertTransactionNotPending(tx); |
||||
} |
||||
|
||||
protected void assertTransactionPending(final Transaction t) { |
||||
assertThat(transactions.getTransactionByHash(t.getHash())).contains(t); |
||||
} |
||||
|
||||
protected void addAndAssertRemoteTransactionsValid(final Transaction... txs) { |
||||
addAndAssertRemoteTransactionsValid(false, txs); |
||||
} |
||||
|
||||
protected void addAndAssertRemotePriorityTransactionsValid(final Transaction... txs) { |
||||
addAndAssertRemoteTransactionsValid(true, txs); |
||||
} |
||||
|
||||
protected void addAndAssertRemoteTransactionsValid( |
||||
final boolean hasPriority, 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); |
||||
if (hasPriority) { |
||||
assertThat(transactions.getPriorityTransactions()).contains(txs); |
||||
} |
||||
} |
||||
|
||||
protected void addAndAssertTransactionViaApiValid( |
||||
final Transaction tx, final boolean disableLocalPriority) { |
||||
final ValidationResult<TransactionInvalidReason> result = |
||||
transactionPool.addTransactionViaApi(tx); |
||||
|
||||
assertThat(result.isValid()).isTrue(); |
||||
assertTransactionPending(tx); |
||||
verify(transactionBroadcaster).onTransactionsAdded(singletonList(tx)); |
||||
assertThat(transactions.getLocalTransactions()).contains(tx); |
||||
if (disableLocalPriority) { |
||||
assertThat(transactions.getPriorityTransactions()).doesNotContain(tx); |
||||
} else { |
||||
assertThat(transactions.getPriorityTransactions()).contains(tx); |
||||
} |
||||
} |
||||
|
||||
protected void addAndAssertTransactionViaApiInvalid( |
||||
final Transaction tx, final TransactionInvalidReason invalidReason) { |
||||
final ValidationResult<TransactionInvalidReason> 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(Optional.class), any())) |
||||
.thenReturn(valid()); |
||||
when(transactionValidatorFactory |
||||
.get() |
||||
.validateForSender( |
||||
eq(transaction), nullable(Account.class), any(TransactionValidationParams.class))) |
||||
.thenReturn(valid()); |
||||
} |
||||
|
||||
protected abstract Block appendBlock( |
||||
final Difficulty difficulty, |
||||
final BlockHeader parentBlock, |
||||
final Transaction... transactionsToAdd); |
||||
|
||||
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<BigInteger> maybeChainId) { |
||||
return createBaseTransaction(transactionNumber) |
||||
.chainId(maybeChainId) |
||||
.createTransaction(KEY_PAIR1); |
||||
} |
||||
|
||||
protected abstract Transaction createTransaction(final int nonce, 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 void verifyChainHeadIs(final Block forkBlock2) { |
||||
assertThat(blockchain.getChainHeadHash()).isEqualTo(forkBlock2.getHash()); |
||||
} |
||||
|
||||
protected BlockHeader getHeaderForCurrentChainHead() { |
||||
return blockchain.getBlockHeader(blockchain.getChainHeadHash()).get(); |
||||
} |
||||
|
||||
protected void appendBlock(final Transaction... transactionsToAdd) { |
||||
appendBlock(Difficulty.ONE, getHeaderForCurrentChainHead(), transactionsToAdd); |
||||
} |
||||
|
||||
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 protocolDoesNotSupportTxReplayProtection() { |
||||
when(protocolSchedule.getChainId()).thenReturn(Optional.empty()); |
||||
} |
||||
|
||||
protected Transaction createTransactionWithoutChainId(final int transactionNumber) { |
||||
return createTransaction(transactionNumber, Optional.empty()); |
||||
} |
||||
|
||||
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 Transaction createFrontierTransaction(final int transactionNumber, final Wei gasPrice) { |
||||
return new TransactionTestFixture() |
||||
.nonce(transactionNumber) |
||||
.gasPrice(gasPrice) |
||||
.gasLimit(blockGasLimit) |
||||
.type(TransactionType.FRONTIER) |
||||
.createTransaction(KEY_PAIR1); |
||||
} |
||||
|
||||
protected Transaction createBlobTransaction(final int nonce) { |
||||
if (blobTestFixture == null) { |
||||
blobTestFixture = new BlobTestFixture(); |
||||
} |
||||
return new TransactionTestFixture() |
||||
.nonce(nonce) |
||||
.gasLimit(blockGasLimit) |
||||
.gasPrice(null) |
||||
.maxFeePerGas(Optional.of(Wei.of(5000L))) |
||||
.maxPriorityFeePerGas(Optional.of(Wei.of(1000L))) |
||||
.type(TransactionType.BLOB) |
||||
.blobsWithCommitments(Optional.of(blobTestFixture.createBlobsWithCommitments(6))) |
||||
.createTransaction(KEY_PAIR1); |
||||
} |
||||
|
||||
protected Transaction createBlobTransactionWithSameBlobs( |
||||
final int nonce, final BlobsWithCommitments blobsWithCommitments) { |
||||
return new TransactionTestFixture() |
||||
.nonce(nonce) |
||||
.gasLimit(blockGasLimit) |
||||
.gasPrice(null) |
||||
.maxFeePerGas(Optional.of(Wei.of(5000L))) |
||||
.maxPriorityFeePerGas(Optional.of(Wei.of(1000L))) |
||||
.type(TransactionType.BLOB) |
||||
.blobsWithCommitments(Optional.of(blobsWithCommitments)) |
||||
.createTransaction(KEY_PAIR1); |
||||
} |
||||
|
||||
protected Transaction createReplacementTransactionWithBlobs(final int nonce) { |
||||
if (blobTestFixture == null) { |
||||
blobTestFixture = new BlobTestFixture(); |
||||
} |
||||
return new TransactionTestFixture() |
||||
.nonce(nonce) |
||||
.gasLimit(blockGasLimit) |
||||
.gasPrice(null) |
||||
.maxFeePerGas(Optional.of(Wei.of(5000L * 10))) |
||||
.maxPriorityFeePerGas(Optional.of(Wei.of(1000L * 10))) |
||||
.maxFeePerBlobGas(Optional.of(Wei.of(5000L))) |
||||
.type(TransactionType.BLOB) |
||||
.blobsWithCommitments(Optional.of(blobTestFixture.createBlobsWithCommitments(6))) |
||||
.createTransaction(KEY_PAIR1); |
||||
} |
||||
|
||||
protected int addTxAndGetPendingTxsCount( |
||||
final Wei genesisBaseFee, |
||||
final Wei minGasPrice, |
||||
final Wei lastBlockBaseFee, |
||||
final Wei txMaxFeePerGas, |
||||
final boolean isLocal, |
||||
final boolean hasPriority) { |
||||
when(protocolSpec.getFeeMarket()).thenReturn(FeeMarket.london(0, Optional.of(genesisBaseFee))); |
||||
whenBlockBaseFeeIs(lastBlockBaseFee); |
||||
|
||||
final Transaction transaction = createTransaction(0, txMaxFeePerGas); |
||||
if (hasPriority) { |
||||
transactionPool = |
||||
createTransactionPool( |
||||
b -> b.minGasPrice(minGasPrice).prioritySenders(Set.of(transaction.getSender()))); |
||||
} else { |
||||
transactionPool = |
||||
createTransactionPool(b -> b.minGasPrice(minGasPrice).noLocalPriority(true)); |
||||
} |
||||
|
||||
givenTransactionIsValid(transaction); |
||||
|
||||
if (isLocal) { |
||||
transactionPool.addTransactionViaApi(transaction); |
||||
} else { |
||||
transactionPool.addRemoteTransactions(List.of(transaction)); |
||||
} |
||||
|
||||
return transactions.size(); |
||||
} |
||||
|
||||
protected Block appendBlockGasPriceMarket( |
||||
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; |
||||
} |
||||
|
||||
protected Block appendBlockBaseFeeMarket( |
||||
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; |
||||
} |
||||
} |
@ -0,0 +1,141 @@ |
||||
/* |
||||
* Copyright contributors to Hyperledger Besu. |
||||
* |
||||
* 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 org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
import org.hyperledger.besu.datatypes.BlobsWithCommitments; |
||||
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.sorter.BaseFeePendingTransactionsSorter; |
||||
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; |
||||
import org.hyperledger.besu.testutil.TestClock; |
||||
|
||||
import java.time.ZoneId; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.function.BiFunction; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
public class BlobV1TransactionPoolTest extends AbstractTransactionPoolTestBase { |
||||
|
||||
@Override |
||||
protected PendingTransactions createPendingTransactions( |
||||
final TransactionPoolConfiguration poolConfig, |
||||
final BiFunction<PendingTransaction, PendingTransaction, Boolean> |
||||
transactionReplacementTester) { |
||||
|
||||
return new BaseFeePendingTransactionsSorter( |
||||
poolConfig, |
||||
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); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldReturnBlobWhenTransactionAddedToPool() { |
||||
givenTransactionIsValid(transactionWithBlobs); |
||||
|
||||
addAndAssertRemoteTransactionsValid(transactionWithBlobs); |
||||
|
||||
assertTransactionPending(transactionWithBlobs); |
||||
// assert that the blobs are returned from the tx pool
|
||||
final List<BlobsWithCommitments.BlobQuad> expectedBlobQuads = |
||||
transactionWithBlobs.getBlobsWithCommitments().get().getBlobQuads(); |
||||
|
||||
expectedBlobQuads.forEach( |
||||
bq -> assertThat(transactionPool.getBlobQuad(bq.versionedHash())).isEqualTo(bq)); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldNotReturnBlobsWhenAllTxsContainingBlobsHaveBeenReplaced() { |
||||
givenTransactionIsValid(transactionWithBlobs); |
||||
givenTransactionIsValid(transactionWithBlobsReplacement); |
||||
givenTransactionIsValid(transactionWithSameBlobs); // contains same blobs as transactionBlob
|
||||
givenTransactionIsValid(transactionWithSameBlobsReplacement); |
||||
|
||||
addAndAssertRemoteTransactionsValid(transactionWithBlobs); |
||||
assertTransactionPending(transactionWithBlobs); |
||||
|
||||
final List<BlobsWithCommitments.BlobQuad> expectedBlobQuads = |
||||
transactionWithBlobs.getBlobsWithCommitments().get().getBlobQuads(); |
||||
|
||||
// assert that the blobs are returned from the tx pool
|
||||
expectedBlobQuads.forEach( |
||||
bq -> assertThat(transactionPool.getBlobQuad(bq.versionedHash())).isEqualTo(bq)); |
||||
|
||||
// add different transaction that contains the same blobs
|
||||
addAndAssertRemoteTransactionsValid(transactionWithSameBlobs); |
||||
|
||||
assertTransactionPending(transactionWithBlobs); |
||||
assertTransactionPending(transactionWithSameBlobs); |
||||
// assert that the blobs are still returned from the tx pool
|
||||
expectedBlobQuads.forEach( |
||||
bq -> assertThat(transactionPool.getBlobQuad(bq.versionedHash())).isEqualTo(bq)); |
||||
|
||||
// replace the second blob transaction with tx with different blobs
|
||||
addAndAssertRemoteTransactionsValid(transactionWithSameBlobsReplacement); |
||||
assertTransactionPending(transactionWithSameBlobsReplacement); |
||||
assertTransactionNotPending(transactionWithSameBlobs); |
||||
|
||||
// assert that the blob is still returned from the tx pool
|
||||
expectedBlobQuads.forEach( |
||||
bq -> assertThat(transactionPool.getBlobQuad(bq.versionedHash())).isEqualTo(bq)); |
||||
|
||||
// replace the first blob transaction with tx with different blobs
|
||||
addAndAssertRemoteTransactionsValid(transactionWithBlobsReplacement); |
||||
assertTransactionPending(transactionWithBlobsReplacement); |
||||
assertTransactionNotPending(transactionWithBlobs); |
||||
|
||||
// All txs containing the expected blobs have been replaced,
|
||||
// so the blobs should no longer be returned from the tx pool
|
||||
expectedBlobQuads.forEach( |
||||
bq -> assertThat(transactionPool.getBlobQuad(bq.versionedHash())).isNull()); |
||||
} |
||||
} |
Loading…
Reference in new issue