mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-08 15:13:58 -05:00
Add a tx selector to skip txs from the same sender after the first not selected (#8216)
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
This commit is contained in:
@@ -4,9 +4,12 @@
|
||||
### Breaking Changes
|
||||
### Upcoming Breaking Changes
|
||||
### Additions and Improvements
|
||||
- Add a tx selector to skip txs from the same sender after the first not selected [#8216](https://github.com/hyperledger/besu/pull/8216)
|
||||
|
||||
#### Prague
|
||||
- Add timestamps to enable Prague hardfork on Sepolia and Holesky test networks [#8163](https://github.com/hyperledger/besu/pull/8163)
|
||||
- Update system call addresses to match [devnet-6](https://github.com/ethereum/execution-spec-tests/releases/) values [#8209](https://github.com/hyperledger/besu/issues/8209)
|
||||
|
||||
#### Plugins
|
||||
- Extend simulate transaction on pending block plugin API [#8174](https://github.com/hyperledger/besu/pull/8174)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlockSi
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.MinPriorityFeePerGasTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.PriceTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.ProcessingResultTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.SkipSenderTransactionSelector;
|
||||
import org.hyperledger.besu.ethereum.chain.Blockchain;
|
||||
import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState;
|
||||
@@ -151,6 +152,7 @@ public class BlockTransactionSelector {
|
||||
private List<AbstractTransactionSelector> createTransactionSelectors(
|
||||
final BlockSelectionContext context) {
|
||||
return List.of(
|
||||
new SkipSenderTransactionSelector(context),
|
||||
new BlockSizeTransactionSelector(context),
|
||||
new BlobSizeTransactionSelector(context),
|
||||
new PriceTransactionSelector(context),
|
||||
@@ -442,7 +444,8 @@ public class BlockTransactionSelector {
|
||||
evaluationContext, BLOCK_SELECTION_TIMEOUT, txWorldStateUpdater);
|
||||
}
|
||||
|
||||
pluginTransactionSelector.onTransactionSelected(evaluationContext, processingResult);
|
||||
notifySelected(evaluationContext, processingResult);
|
||||
|
||||
blockWorldStateUpdater = worldState.updater();
|
||||
LOG.atTrace()
|
||||
.setMessage("Selected {} for block creation, evaluated in {}")
|
||||
@@ -475,7 +478,7 @@ public class BlockTransactionSelector {
|
||||
: selectionResult;
|
||||
|
||||
transactionSelectionResults.updateNotSelected(evaluationContext.getTransaction(), actualResult);
|
||||
pluginTransactionSelector.onTransactionNotSelected(evaluationContext, actualResult);
|
||||
notifyNotSelected(evaluationContext, actualResult);
|
||||
LOG.atTrace()
|
||||
.setMessage(
|
||||
"Not selected {} for block creation with result {} (original result {}), evaluated in {}")
|
||||
@@ -550,6 +553,26 @@ public class BlockTransactionSelector {
|
||||
return handleTransactionNotSelected(evaluationContext, selectionResult);
|
||||
}
|
||||
|
||||
private void notifySelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
|
||||
for (var selector : transactionSelectors) {
|
||||
selector.onTransactionSelected(evaluationContext, processingResult);
|
||||
}
|
||||
pluginTransactionSelector.onTransactionSelected(evaluationContext, processingResult);
|
||||
}
|
||||
|
||||
private void notifyNotSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult selectionResult) {
|
||||
|
||||
for (var selector : transactionSelectors) {
|
||||
selector.onTransactionNotSelected(evaluationContext, selectionResult);
|
||||
}
|
||||
pluginTransactionSelector.onTransactionNotSelected(evaluationContext, selectionResult);
|
||||
}
|
||||
|
||||
private void checkCancellation() {
|
||||
if (isCancelled.get()) {
|
||||
throw new CancellationException("Cancelled during transaction selection.");
|
||||
|
||||
@@ -25,7 +25,7 @@ import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
* transactions.
|
||||
*/
|
||||
public abstract class AbstractTransactionSelector {
|
||||
final BlockSelectionContext context;
|
||||
protected final BlockSelectionContext context;
|
||||
|
||||
public AbstractTransactionSelector(final BlockSelectionContext context) {
|
||||
this.context = context;
|
||||
@@ -55,4 +55,24 @@ public abstract class AbstractTransactionSelector {
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult);
|
||||
|
||||
/**
|
||||
* Method called when a transaction is selected to be added to a block.
|
||||
*
|
||||
* @param evaluationContext The current selection context
|
||||
* @param processingResult The result of processing the selected transaction.
|
||||
*/
|
||||
public void onTransactionSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionProcessingResult processingResult) {}
|
||||
|
||||
/**
|
||||
* Method called when a transaction is not selected to be added to a block.
|
||||
*
|
||||
* @param evaluationContext The current selection context
|
||||
* @param transactionSelectionResult The transaction selection result
|
||||
*/
|
||||
public void onTransactionNotSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult transactionSelectionResult) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright contributors to 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.blockcreation.txselection.selectors;
|
||||
|
||||
import org.hyperledger.besu.datatypes.Address;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionEvaluationContext;
|
||||
import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
|
||||
import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class SkipSenderTransactionSelector extends AbstractTransactionSelector {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SkipSenderTransactionSelector.class);
|
||||
private final Set<Address> skippedSenders = new HashSet<>();
|
||||
|
||||
public SkipSenderTransactionSelector(final BlockSelectionContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPreProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults ignored) {
|
||||
final var sender = evaluationContext.getTransaction().getSender();
|
||||
if (skippedSenders.contains(sender)) {
|
||||
LOG.atTrace()
|
||||
.setMessage("Not selecting tx {} since its sender {} is in the skip list")
|
||||
.addArgument(() -> evaluationContext.getPendingTransaction().toTraceLog())
|
||||
.addArgument(sender)
|
||||
.log();
|
||||
|
||||
return TransactionSelectionResult.SENDER_WITH_PREVIOUS_TX_NOT_SELECTED;
|
||||
}
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransactionSelectionResult evaluateTransactionPostProcessing(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResults blockTransactionResults,
|
||||
final TransactionProcessingResult processingResult) {
|
||||
// All necessary checks were done in the pre-processing method, so nothing to do here.
|
||||
return TransactionSelectionResult.SELECTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a transaction is not selected we can skip processing any following transaction from the
|
||||
* same sender, since it will never be selected due to the nonce gap, so we add the sender to the
|
||||
* skip list.
|
||||
*
|
||||
* @param evaluationContext The current selection context
|
||||
* @param transactionSelectionResult The transaction selection result
|
||||
*/
|
||||
@Override
|
||||
public void onTransactionNotSelected(
|
||||
final TransactionEvaluationContext evaluationContext,
|
||||
final TransactionSelectionResult transactionSelectionResult) {
|
||||
final var sender = evaluationContext.getTransaction().getSender();
|
||||
skippedSenders.add(sender);
|
||||
LOG.trace("Sender {} added to the skip list", sender);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,11 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER1;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER2;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER3;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER4;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER5;
|
||||
import static org.hyperledger.besu.ethereum.core.MiningConfiguration.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME;
|
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.EXECUTION_INTERRUPTED;
|
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.NONCE_TOO_LOW;
|
||||
@@ -62,7 +67,6 @@ import org.hyperledger.besu.ethereum.core.MiningConfiguration;
|
||||
import org.hyperledger.besu.ethereum.core.MutableWorldState;
|
||||
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
|
||||
import org.hyperledger.besu.ethereum.core.Transaction;
|
||||
import org.hyperledger.besu.ethereum.core.TransactionReceipt;
|
||||
import org.hyperledger.besu.ethereum.difficulty.fixed.FixedDifficultyProtocolSchedule;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthContext;
|
||||
import org.hyperledger.besu.ethereum.eth.manager.EthScheduler;
|
||||
@@ -80,7 +84,6 @@ import org.hyperledger.besu.ethereum.trie.diffbased.common.provider.WorldStateQu
|
||||
import org.hyperledger.besu.evm.gascalculator.GasCalculator;
|
||||
import org.hyperledger.besu.evm.gascalculator.LondonGasCalculator;
|
||||
import org.hyperledger.besu.evm.internal.EvmConfiguration;
|
||||
import org.hyperledger.besu.evm.worldstate.WorldState;
|
||||
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
import org.hyperledger.besu.plugin.services.MetricsSystem;
|
||||
@@ -127,10 +130,6 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
protected static final double MIN_OCCUPANCY_80_PERCENT = 0.8;
|
||||
protected static final double MIN_OCCUPANCY_100_PERCENT = 1;
|
||||
protected static final BigInteger CHAIN_ID = BigInteger.valueOf(42L);
|
||||
protected static final KeyPair keyPair =
|
||||
SignatureAlgorithmFactory.getInstance().generateKeyPair();
|
||||
protected static final Address sender =
|
||||
Address.extract(Hash.hash(keyPair.getPublicKey().getEncodedBytes()));
|
||||
|
||||
protected final MetricsSystem metricsSystem = new NoOpMetricsSystem();
|
||||
protected GenesisConfig genesisConfig;
|
||||
@@ -180,7 +179,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
|
||||
worldState = InMemoryKeyValueStorageProvider.createInMemoryWorldState();
|
||||
final var worldStateUpdater = worldState.updater();
|
||||
worldStateUpdater.createAccount(sender, 0, Wei.of(1_000_000_000L));
|
||||
Arrays.stream(Sender.values())
|
||||
.map(Sender::address)
|
||||
.forEach(address -> worldStateUpdater.createAccount(address, 0, Wei.of(1_000_000_000L)));
|
||||
worldStateUpdater.commit();
|
||||
|
||||
when(protocolContext.getWorldStateArchive().getWorldState(any(WorldStateQueryParams.class)))
|
||||
@@ -303,9 +304,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
Wei.ZERO,
|
||||
transactionSelectionService);
|
||||
|
||||
final List<Transaction> transactionsToInject = Lists.newArrayList();
|
||||
final List<Transaction> transactionsToInject = new ArrayList<>(5);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final Transaction tx = createTransaction(i, Wei.of(7), 100_000);
|
||||
final Transaction tx = createTransaction(i, Wei.of(7), 100_000, Sender.values()[i]);
|
||||
transactionsToInject.add(tx);
|
||||
if (i == 1) {
|
||||
ensureTransactionIsInvalid(tx, TransactionInvalidReason.NONCE_TOO_LOW);
|
||||
@@ -389,9 +390,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
// NOTE - PendingTransactions outputs these in nonce order
|
||||
final Transaction[] txs =
|
||||
new Transaction[] {
|
||||
createTransaction(1, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.79)),
|
||||
createTransaction(2, Wei.of(10), blockHeader.getGasLimit()),
|
||||
createTransaction(3, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.1))
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.79), SENDER1),
|
||||
createTransaction(0, Wei.of(10), blockHeader.getGasLimit(), SENDER2),
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.1), SENDER3)
|
||||
};
|
||||
|
||||
for (final Transaction tx : txs) {
|
||||
@@ -425,10 +426,10 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
// NOTE - PendingTransactions will output these in nonce order.
|
||||
final Transaction[] txs =
|
||||
new Transaction[] {
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.15)),
|
||||
createTransaction(1, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.79)),
|
||||
createTransaction(2, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.25)),
|
||||
createTransaction(3, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.1))
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.15), SENDER1),
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.79), SENDER2),
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.25), SENDER3),
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.1), SENDER4)
|
||||
};
|
||||
|
||||
for (Transaction tx : txs) {
|
||||
@@ -443,6 +444,44 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
.containsOnly(entry(txs[2], TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ifATransactionIsNotSelectedFollowingOnesFromTheSameSenderAreSkipped() {
|
||||
final ProcessableBlockHeader blockHeader = createBlock(300_000);
|
||||
final Address miningBeneficiary = AddressHelpers.ofValue(1);
|
||||
final BlockTransactionSelector selector =
|
||||
createBlockSelectorAndSetupTxPool(
|
||||
defaultTestMiningConfiguration,
|
||||
transactionProcessor,
|
||||
blockHeader,
|
||||
miningBeneficiary,
|
||||
Wei.ZERO,
|
||||
transactionSelectionService);
|
||||
|
||||
// Add 3 transactions from the same sender to the Pending Transactions
|
||||
// first is selected
|
||||
// second id not selected
|
||||
// third is skipped, not processed, since cannot be selected due to the nonce gap
|
||||
final Transaction[] txs =
|
||||
new Transaction[] {
|
||||
createTransaction(0, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.5), SENDER1),
|
||||
createTransaction(1, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.6), SENDER1),
|
||||
createTransaction(2, Wei.of(10), (long) (blockHeader.getGasLimit() * 0.1), SENDER1)
|
||||
};
|
||||
|
||||
for (Transaction tx : txs) {
|
||||
ensureTransactionIsValid(tx);
|
||||
}
|
||||
transactionPool.addRemoteTransactions(Arrays.stream(txs).toList());
|
||||
|
||||
final TransactionSelectionResults results = selector.buildTransactionListForBlock();
|
||||
|
||||
assertThat(results.getSelectedTransactions()).containsExactly(txs[0]);
|
||||
assertThat(results.getNotSelectedTransactions())
|
||||
.containsOnly(
|
||||
entry(txs[1], TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS),
|
||||
entry(txs[2], TransactionSelectionResult.SENDER_WITH_PREVIOUS_TX_NOT_SELECTED));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactionSelectionStopsWhenBlockIsFull() {
|
||||
final ProcessableBlockHeader blockHeader = createBlock(3_000_000);
|
||||
@@ -471,18 +510,18 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
// 4) min gas cost (not selected since selection stopped after tx 3)
|
||||
// NOTE - PendingTransactions outputs these in nonce order
|
||||
|
||||
final long gasLimit0 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit2 = blockHeader.getGasLimit() - gasLimit0 - minTxGasCost;
|
||||
final long gasLimit3 = minTxGasCost;
|
||||
final long gasLimit4 = minTxGasCost;
|
||||
final long gasLimit0s1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit1s1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit0s2 = blockHeader.getGasLimit() - gasLimit0s1 - minTxGasCost;
|
||||
final long gasLimit1s2 = minTxGasCost;
|
||||
final long gasLimit2s2 = minTxGasCost;
|
||||
|
||||
final List<Transaction> transactionsToInject = Lists.newArrayList();
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(7), gasLimit0));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(7), gasLimit1));
|
||||
transactionsToInject.add(createTransaction(2, Wei.of(7), gasLimit2));
|
||||
transactionsToInject.add(createTransaction(3, Wei.of(7), gasLimit3));
|
||||
transactionsToInject.add(createTransaction(4, Wei.of(7), gasLimit4));
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(7), gasLimit0s1, SENDER1));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(7), gasLimit1s1, SENDER1));
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(7), gasLimit0s2, SENDER2));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(7), gasLimit1s2, SENDER2));
|
||||
transactionsToInject.add(createTransaction(2, Wei.of(7), gasLimit2s2, SENDER2));
|
||||
|
||||
for (final Transaction tx : transactionsToInject) {
|
||||
ensureTransactionIsValid(tx);
|
||||
@@ -532,16 +571,16 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
// 3) min gas cost (skipped since not enough gas remaining)
|
||||
// NOTE - PendingTransactions outputs these in nonce order
|
||||
|
||||
final long gasLimit0 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit2 = blockHeader.getGasLimit() - gasLimit0 - (minTxGasCost - 1);
|
||||
final long gasLimit3 = minTxGasCost;
|
||||
final long gasLimit0s1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit1s1 = (long) (blockHeader.getGasLimit() * 0.9);
|
||||
final long gasLimit0s2 = blockHeader.getGasLimit() - gasLimit0s1 - (minTxGasCost - 1);
|
||||
final long gasLimit1s2 = minTxGasCost;
|
||||
|
||||
final List<Transaction> transactionsToInject = Lists.newArrayList();
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(10), gasLimit0));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(10), gasLimit1));
|
||||
transactionsToInject.add(createTransaction(2, Wei.of(10), gasLimit2));
|
||||
transactionsToInject.add(createTransaction(3, Wei.of(10), gasLimit3));
|
||||
final List<Transaction> transactionsToInject = new ArrayList<>(4);
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(10), gasLimit0s1, SENDER1));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(10), gasLimit1s1, SENDER1));
|
||||
transactionsToInject.add(createTransaction(0, Wei.of(10), gasLimit0s2, SENDER2));
|
||||
transactionsToInject.add(createTransaction(1, Wei.of(10), gasLimit1s2, SENDER2));
|
||||
|
||||
for (final Transaction tx : transactionsToInject) {
|
||||
ensureTransactionIsValid(tx);
|
||||
@@ -600,13 +639,13 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
final ProcessableBlockHeader blockHeader = createBlock(300_000);
|
||||
final Address miningBeneficiary = AddressHelpers.ofValue(1);
|
||||
|
||||
final Transaction selected = createTransaction(0, Wei.of(10), 21_000);
|
||||
final Transaction selected = createTransaction(0, Wei.of(10), 21_000, SENDER1);
|
||||
ensureTransactionIsValid(selected, 21_000, 0);
|
||||
|
||||
final Transaction notSelectedTransient = createTransaction(1, Wei.of(10), 21_000);
|
||||
final Transaction notSelectedTransient = createTransaction(1, Wei.of(10), 21_000, SENDER1);
|
||||
ensureTransactionIsValid(notSelectedTransient, 21_000, 0);
|
||||
|
||||
final Transaction notSelectedInvalid = createTransaction(2, Wei.of(10), 21_000);
|
||||
final Transaction notSelectedInvalid = createTransaction(2, Wei.of(10), 21_000, SENDER2);
|
||||
ensureTransactionIsValid(notSelectedInvalid, 21_000, 0);
|
||||
|
||||
final PluginTransactionSelectorFactory transactionSelectorFactory =
|
||||
@@ -723,12 +762,12 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
Wei.ZERO,
|
||||
transactionSelectionService);
|
||||
|
||||
transactionPool.addRemoteTransactions(List.of(selected, notSelected, selected3));
|
||||
transactionPool.addRemoteTransactions(List.of(selected, notSelected));
|
||||
|
||||
final TransactionSelectionResults transactionSelectionResults =
|
||||
selector.buildTransactionListForBlock();
|
||||
|
||||
assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected, selected3);
|
||||
assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected);
|
||||
assertThat(transactionSelectionResults.getNotSelectedTransactions())
|
||||
.containsOnly(
|
||||
entry(notSelected, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT));
|
||||
@@ -1170,7 +1209,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
|
||||
final List<Transaction> transactionsToInject = new ArrayList<>(txCount);
|
||||
for (int i = 0; i < txCount - 1; i++) {
|
||||
final Transaction tx = createTransaction(i, Wei.of(7), 100_000);
|
||||
final Transaction tx = createTransaction(0, Wei.of(7), 100_000, Sender.values()[i]);
|
||||
transactionsToInject.add(tx);
|
||||
if (processingTooLate) {
|
||||
ensureTransactionIsInvalid(tx, txInvalidReason, fastProcessingTxTime);
|
||||
@@ -1179,7 +1218,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
}
|
||||
}
|
||||
|
||||
final Transaction lateTx = createTransaction(2, Wei.of(7), 100_000);
|
||||
final Transaction lateTx = createTransaction(0, Wei.of(7), 100_000, SENDER5);
|
||||
transactionsToInject.add(lateTx);
|
||||
if (processingTooLate) {
|
||||
ensureTransactionIsInvalid(lateTx, txInvalidReason, longProcessingTxTime);
|
||||
@@ -1293,7 +1332,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
worldState,
|
||||
transactionPool,
|
||||
blockHeader,
|
||||
this::createReceipt,
|
||||
protocolSchedule.getByBlockHeader(blockHeader).getTransactionReceiptFactory(),
|
||||
this::isCancelled,
|
||||
miningBeneficiary,
|
||||
blobGasPrice,
|
||||
@@ -1317,6 +1356,11 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
|
||||
protected Transaction createTransaction(
|
||||
final int nonce, final Wei gasPrice, final long gasLimit) {
|
||||
return createTransaction(nonce, gasPrice, gasLimit, SENDER1);
|
||||
}
|
||||
|
||||
protected Transaction createTransaction(
|
||||
final int nonce, final Wei gasPrice, final long gasLimit, final Sender sender) {
|
||||
return Transaction.builder()
|
||||
.gasLimit(gasLimit)
|
||||
.gasPrice(gasPrice)
|
||||
@@ -1324,10 +1368,10 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
.payload(Bytes.EMPTY)
|
||||
.to(Address.ID)
|
||||
.value(Wei.of(nonce))
|
||||
.sender(sender)
|
||||
.sender(sender.address())
|
||||
.chainId(CHAIN_ID)
|
||||
.guessType()
|
||||
.signAndBuild(keyPair);
|
||||
.signAndBuild(sender.keyPair());
|
||||
}
|
||||
|
||||
protected Transaction createEIP1559Transaction(
|
||||
@@ -1335,6 +1379,15 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
final Wei maxFeePerGas,
|
||||
final Wei maxPriorityFeePerGas,
|
||||
final long gasLimit) {
|
||||
return createEIP1559Transaction(nonce, maxFeePerGas, maxPriorityFeePerGas, gasLimit, SENDER1);
|
||||
}
|
||||
|
||||
protected Transaction createEIP1559Transaction(
|
||||
final int nonce,
|
||||
final Wei maxFeePerGas,
|
||||
final Wei maxPriorityFeePerGas,
|
||||
final long gasLimit,
|
||||
final Sender sender) {
|
||||
return Transaction.builder()
|
||||
.type(TransactionType.EIP1559)
|
||||
.gasLimit(gasLimit)
|
||||
@@ -1344,19 +1397,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
.payload(Bytes.EMPTY)
|
||||
.to(Address.ID)
|
||||
.value(Wei.of(nonce))
|
||||
.sender(sender)
|
||||
.sender(sender.address())
|
||||
.chainId(CHAIN_ID)
|
||||
.signAndBuild(keyPair);
|
||||
}
|
||||
|
||||
// This is a duplicate of the MainnetProtocolSpec::frontierTransactionReceiptFactory
|
||||
private TransactionReceipt createReceipt(
|
||||
final TransactionType __,
|
||||
final TransactionProcessingResult result,
|
||||
final WorldState worldState,
|
||||
final long gasUsed) {
|
||||
return new TransactionReceipt(
|
||||
worldState.rootHash(), gasUsed, Lists.newArrayList(), Optional.empty());
|
||||
.signAndBuild(sender.keyPair());
|
||||
}
|
||||
|
||||
protected void ensureTransactionIsValid(final Transaction tx) {
|
||||
@@ -1507,4 +1550,34 @@ public abstract class AbstractBlockTransactionSelectorTest {
|
||||
return new PluginTransactionSelectionResult(PluginStatus.PLUGIN_INVALID, invalidReason);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum Sender {
|
||||
// it is important to keep the addresses of the senders sorted, to make the tests reproducible,
|
||||
// since a different sender address can change the order in which txs are selected,
|
||||
// if all the other sorting fields are equal
|
||||
SENDER1(4), // 0x1eff47bc3a10a45d4b230b5d10e37751fe6aa718
|
||||
SENDER2(2), // 0x2b5ad5c4795c026514f8317c7a215e218dccd6cf
|
||||
SENDER3(3), // 0x6813eb9362372eef6200f3b1dbc3f819671cba69
|
||||
SENDER4(1), // 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf
|
||||
SENDER5(5); // 0xe1ab8145f7e55dc933d51a18c793f901a3a0b276
|
||||
|
||||
private final KeyPair keyPair;
|
||||
private final Address address;
|
||||
|
||||
Sender(final int seed) {
|
||||
final var privateKey =
|
||||
SignatureAlgorithmFactory.getInstance().createPrivateKey(BigInteger.valueOf(seed));
|
||||
final var publicKey = SignatureAlgorithmFactory.getInstance().createPublicKey(privateKey);
|
||||
this.keyPair = new KeyPair(privateKey, publicKey);
|
||||
this.address = Address.extract(Hash.hash(publicKey.getEncodedBytes()));
|
||||
}
|
||||
|
||||
public KeyPair keyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public Address address() {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ package org.hyperledger.besu.ethereum.blockcreation;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER1;
|
||||
import static org.hyperledger.besu.ethereum.blockcreation.AbstractBlockTransactionSelectorTest.Sender.SENDER2;
|
||||
import static org.hyperledger.besu.ethereum.core.MiningConfiguration.DEFAULT_NON_POA_BLOCK_TXS_SELECTION_MAX_TIME;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@@ -246,19 +248,23 @@ public class LondonFeeMarketBlockTransactionSelectorTest
|
||||
ImmutableMiningConfiguration.builder().from(defaultTestMiningConfiguration).build();
|
||||
miningConfiguration.setMinPriorityFeePerGas(Wei.of(7));
|
||||
|
||||
final Transaction txSelected1 = createEIP1559Transaction(1, Wei.of(8), Wei.of(8), 100_000);
|
||||
final Transaction txSelected1 =
|
||||
createEIP1559Transaction(0, Wei.of(8), Wei.of(8), 100_000, SENDER1);
|
||||
ensureTransactionIsValid(txSelected1);
|
||||
|
||||
// transaction txNotSelected1 should not be selected
|
||||
final Transaction txNotSelected1 = createEIP1559Transaction(2, Wei.of(7), Wei.of(7), 100_000);
|
||||
final Transaction txNotSelected1 =
|
||||
createEIP1559Transaction(1, Wei.of(7), Wei.of(7), 100_000, SENDER1);
|
||||
ensureTransactionIsValid(txNotSelected1);
|
||||
|
||||
// transaction txSelected2 should be selected
|
||||
final Transaction txSelected2 = createEIP1559Transaction(3, Wei.of(8), Wei.of(8), 100_000);
|
||||
final Transaction txSelected2 =
|
||||
createEIP1559Transaction(0, Wei.of(8), Wei.of(8), 100_000, SENDER2);
|
||||
ensureTransactionIsValid(txSelected2);
|
||||
|
||||
// transaction txNotSelected2 should not be selected
|
||||
final Transaction txNotSelected2 = createEIP1559Transaction(4, Wei.of(8), Wei.of(6), 100_000);
|
||||
final Transaction txNotSelected2 =
|
||||
createEIP1559Transaction(1, Wei.of(8), Wei.of(6), 100_000, SENDER2);
|
||||
ensureTransactionIsValid(txNotSelected2);
|
||||
|
||||
final BlockTransactionSelector selector =
|
||||
|
||||
@@ -42,12 +42,10 @@ import org.hyperledger.besu.evm.account.AccountState;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -315,8 +313,6 @@ public class LayeredPendingTransactions implements PendingTransactions {
|
||||
|
||||
@Override
|
||||
public void selectTransactions(final PendingTransactions.TransactionSelector selector) {
|
||||
final Set<Address> skipSenders = new HashSet<>();
|
||||
|
||||
final Map<Byte, List<SenderPendingTransactions>> candidateTxsByScore;
|
||||
synchronized (this) {
|
||||
// since selecting transactions for block creation is a potential long operation
|
||||
@@ -332,50 +328,39 @@ public class LayeredPendingTransactions implements PendingTransactions {
|
||||
for (final var senderTxs : entry.getValue()) {
|
||||
LOG.trace("Evaluating sender txs {}", senderTxs);
|
||||
|
||||
if (!skipSenders.contains(senderTxs.sender())) {
|
||||
for (final var candidatePendingTx : senderTxs.pendingTransactions()) {
|
||||
final var selectionResult = selector.evaluateTransaction(candidatePendingTx);
|
||||
|
||||
for (final var candidatePendingTx : senderTxs.pendingTransactions()) {
|
||||
final var selectionResult = selector.evaluateTransaction(candidatePendingTx);
|
||||
LOG.atTrace()
|
||||
.setMessage("Selection result {} for transaction {}")
|
||||
.addArgument(selectionResult)
|
||||
.addArgument(candidatePendingTx::toTraceLog)
|
||||
.log();
|
||||
|
||||
if (selectionResult.discard()) {
|
||||
ethScheduler.scheduleTxWorkerTask(
|
||||
() -> {
|
||||
synchronized (this) {
|
||||
prioritizedTransactions.remove(candidatePendingTx, INVALIDATED);
|
||||
}
|
||||
});
|
||||
logDiscardedTransaction(candidatePendingTx, selectionResult);
|
||||
} else if (selectionResult.penalize()) {
|
||||
ethScheduler.scheduleTxWorkerTask(
|
||||
() -> {
|
||||
synchronized (this) {
|
||||
prioritizedTransactions.penalize(candidatePendingTx);
|
||||
}
|
||||
});
|
||||
LOG.atTrace()
|
||||
.setMessage("Selection result {} for transaction {}")
|
||||
.addArgument(selectionResult)
|
||||
.setMessage("Transaction {} penalized")
|
||||
.addArgument(candidatePendingTx::toTraceLog)
|
||||
.log();
|
||||
}
|
||||
|
||||
if (selectionResult.discard()) {
|
||||
ethScheduler.scheduleTxWorkerTask(
|
||||
() -> {
|
||||
synchronized (this) {
|
||||
prioritizedTransactions.remove(candidatePendingTx, INVALIDATED);
|
||||
}
|
||||
});
|
||||
logDiscardedTransaction(candidatePendingTx, selectionResult);
|
||||
} else if (selectionResult.penalize()) {
|
||||
ethScheduler.scheduleTxWorkerTask(
|
||||
() -> {
|
||||
synchronized (this) {
|
||||
prioritizedTransactions.penalize(candidatePendingTx);
|
||||
}
|
||||
});
|
||||
LOG.atTrace()
|
||||
.setMessage("Transaction {} penalized")
|
||||
.addArgument(candidatePendingTx::toTraceLog)
|
||||
.log();
|
||||
}
|
||||
|
||||
if (selectionResult.stop()) {
|
||||
LOG.trace("Stopping selection");
|
||||
break selection;
|
||||
}
|
||||
|
||||
if (!selectionResult.selected()) {
|
||||
// avoid processing other txs from this sender if this one is skipped
|
||||
// since the following will not be selected due to the nonce gap
|
||||
LOG.trace("Skipping remaining txs for sender {}", candidatePendingTx.getSender());
|
||||
skipSenders.add(candidatePendingTx.getSender());
|
||||
break;
|
||||
}
|
||||
if (selectionResult.stop()) {
|
||||
LOG.trace("Stopping selection");
|
||||
break selection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package org.hyperledger.besu.ethereum.eth.transactions.layered;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.entry;
|
||||
import static org.hyperledger.besu.datatypes.TransactionType.BLOB;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ADDED;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedResult.ALREADY_KNOWN;
|
||||
@@ -23,15 +24,13 @@ import static org.hyperledger.besu.ethereum.eth.transactions.TransactionAddedRes
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.MOVE;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.layered.AddReason.NEW;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredRemovalReason.PoolRemovalReason.DROPPED;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredRemovalReason.PoolRemovalReason.INVALIDATED;
|
||||
import static org.hyperledger.besu.ethereum.eth.transactions.layered.LayeredRemovalReason.PoolRemovalReason.REPLACED;
|
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE;
|
||||
import static org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_FULL;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.CURRENT_TX_PRICE_BELOW_MIN;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
|
||||
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
@@ -49,6 +48,7 @@ import org.hyperledger.besu.ethereum.eth.transactions.ImmutableTransactionPoolCo
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransaction;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionAddedListener;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.PendingTransactionDroppedListener;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.RemovalReason;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolConfiguration;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolMetrics;
|
||||
import org.hyperledger.besu.ethereum.eth.transactions.TransactionPoolReplacementHandler;
|
||||
@@ -57,10 +57,12 @@ import org.hyperledger.besu.evm.account.Account;
|
||||
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
import java.util.SequencedMap;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -479,46 +481,6 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest {
|
||||
assertThat(iterationOrder).containsExactly(transaction0, transaction1, transaction2);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
public void ignoreSenderTransactionsAfterASkippedOne(
|
||||
final TransactionSelectionResult skipSelectionResult) {
|
||||
final Transaction transaction0a = createTransaction(0, DEFAULT_BASE_FEE.add(Wei.of(20)), KEYS1);
|
||||
final Transaction transaction1a = createTransaction(1, DEFAULT_BASE_FEE.add(Wei.of(20)), KEYS1);
|
||||
final Transaction transaction2a = createTransaction(2, DEFAULT_BASE_FEE.add(Wei.of(20)), KEYS1);
|
||||
final Transaction transaction0b = createTransaction(0, DEFAULT_BASE_FEE.add(Wei.of(10)), KEYS2);
|
||||
|
||||
pendingTransactions.addTransaction(
|
||||
createLocalPendingTransaction(transaction0a), Optional.empty());
|
||||
pendingTransactions.addTransaction(
|
||||
createLocalPendingTransaction(transaction1a), Optional.empty());
|
||||
pendingTransactions.addTransaction(
|
||||
createLocalPendingTransaction(transaction2a), Optional.empty());
|
||||
pendingTransactions.addTransaction(
|
||||
createLocalPendingTransaction(transaction0b), Optional.empty());
|
||||
|
||||
final List<Transaction> iterationOrder = new ArrayList<>(3);
|
||||
pendingTransactions.selectTransactions(
|
||||
pendingTx -> {
|
||||
iterationOrder.add(pendingTx.getTransaction());
|
||||
// pretending that the 2nd tx of the 1st sender is not selected
|
||||
return pendingTx.getNonce() == 1 ? skipSelectionResult : SELECTED;
|
||||
});
|
||||
|
||||
// the 3rd tx of the 1st must not be processed, since the 2nd is skipped
|
||||
// but the 2nd sender must not be affected
|
||||
assertThat(iterationOrder).containsExactly(transaction0a, transaction1a, transaction0b);
|
||||
}
|
||||
|
||||
static Stream<TransactionSelectionResult> ignoreSenderTransactionsAfterASkippedOne() {
|
||||
return Stream.of(
|
||||
CURRENT_TX_PRICE_BELOW_MIN,
|
||||
BLOB_PRICE_BELOW_CURRENT_MIN,
|
||||
TX_TOO_LARGE_FOR_REMAINING_GAS,
|
||||
TransactionSelectionResult.invalidTransient(GAS_PRICE_BELOW_CURRENT_BASE_FEE.name()),
|
||||
TransactionSelectionResult.invalid(UPFRONT_COST_EXCEEDS_BALANCE.name()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notForceNonceOrderWhenSendersDiffer() {
|
||||
final Account sender2 = mock(Account.class);
|
||||
@@ -547,9 +509,10 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest {
|
||||
@Test
|
||||
public void invalidTransactionIsDeletedFromPendingTransactions() {
|
||||
final var pendingTx0 = createRemotePendingTransaction(transaction0);
|
||||
final var pendingTx1 = createRemotePendingTransaction(transaction1);
|
||||
pendingTransactions.addTransaction(pendingTx0, Optional.empty());
|
||||
pendingTransactions.addTransaction(pendingTx1, Optional.empty());
|
||||
|
||||
final var droppedTxCollector = new DroppedTransactionCollector();
|
||||
pendingTransactions.subscribeDroppedTransactions(droppedTxCollector);
|
||||
|
||||
final List<PendingTransaction> parsedTransactions = new ArrayList<>(1);
|
||||
pendingTransactions.selectTransactions(
|
||||
@@ -558,11 +521,11 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest {
|
||||
return TransactionSelectionResult.invalid(UPFRONT_COST_EXCEEDS_BALANCE.name());
|
||||
});
|
||||
|
||||
// only the first is processed since not being selected will automatically skip the processing
|
||||
// all the other txs from the same sender
|
||||
|
||||
// assert that first tx is removed from the pool
|
||||
assertThat(droppedTxCollector.droppedTransactions)
|
||||
.containsExactly(entry(transaction0, INVALIDATED));
|
||||
assertThat(parsedTransactions).containsExactly(pendingTx0);
|
||||
assertThat(pendingTransactions.getPendingTransactions()).containsExactly(pendingTx1);
|
||||
assertThat(pendingTransactions.getPendingTransactions()).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -947,4 +910,13 @@ public class LayeredPendingTransactionsTest extends BaseTransactionPoolTest {
|
||||
ReadyTransactions readyTransactions,
|
||||
SparseTransactions sparseTransactions,
|
||||
EvictCollectorLayer evictedCollector) {}
|
||||
|
||||
static class DroppedTransactionCollector implements PendingTransactionDroppedListener {
|
||||
final SequencedMap<Transaction, RemovalReason> droppedTransactions = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onTransactionDropped(final Transaction transaction, final RemovalReason reason) {
|
||||
droppedTransactions.put(transaction, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ Calculated : ${currentHash}
|
||||
tasks.register('checkAPIChanges', FileStateChecker) {
|
||||
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
|
||||
files = sourceSets.main.allJava.files
|
||||
knownHash = 'gZIQWjSStog+2qxsbGPPp0OwpYjl8QDM5pv9QWdtADA='
|
||||
knownHash = 'CrhgK6qq7Ym5AARLCQ0lQvZQpsejUgoFcnL1rU+ZGBs='
|
||||
}
|
||||
check.dependsOn('checkAPIChanges')
|
||||
|
||||
|
||||
@@ -176,6 +176,13 @@ public class TransactionSelectionResult {
|
||||
public static final TransactionSelectionResult PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN =
|
||||
TransactionSelectionResult.invalidTransient("PRIORITY_FEE_PER_GAS_BELOW_CURRENT_MIN");
|
||||
|
||||
/**
|
||||
* The transaction has not been selected since its sender already had a previous transaction not
|
||||
* selected
|
||||
*/
|
||||
public static final TransactionSelectionResult SENDER_WITH_PREVIOUS_TX_NOT_SELECTED =
|
||||
TransactionSelectionResult.invalidTransient("SENDER_WITH_PREVIOUS_TX_NOT_SELECTED");
|
||||
|
||||
private final Status status;
|
||||
private final Optional<String> maybeInvalidReason;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user