mirror of https://github.com/hyperledger/besu
Keep track of blobs that are part of multiple transactions (#7723)
* keep track of blobs that are part of multiple transactions Signed-off-by: stefan.pingel@consensys.net <stefan.pingel@consensys.net> Signed-off-by: Stefan Pingel <16143240+pinges@users.noreply.github.com>pull/7752/head
parent
11a62a0720
commit
0cd51065d1
@ -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