diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
index 6b53fcef3e3..a421774420c 100644
--- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
+++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftBlockHeightManagerTest.java
@@ -60,7 +60,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
diff --git a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java
index 986b1f7da5f..008d69330fb 100644
--- a/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java
+++ b/consensus/ibft/src/test/java/org/hyperledger/besu/consensus/ibft/statemachine/IbftRoundTest.java
@@ -47,7 +47,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java
index 32d55e7f22e..669563d6f5b 100644
--- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java
+++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/blockcreation/PkiQbftBlockCreatorTest.java
@@ -32,7 +32,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java
index 669ed154e97..92471799cfd 100644
--- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java
+++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftBlockHeightManagerTest.java
@@ -59,7 +59,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
diff --git a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java
index 2ac14e79b55..627f5cd9e2e 100644
--- a/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java
+++ b/consensus/qbft/src/test/java/org/hyperledger/besu/consensus/qbft/statemachine/QbftRoundTest.java
@@ -50,7 +50,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.Block;
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
index 80037c8c6c1..3fa80f1dd0a 100644
--- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreator.java
@@ -22,7 +22,8 @@
import org.hyperledger.besu.datatypes.TransactionType;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
import org.hyperledger.besu.ethereum.core.BlockHeader;
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java
index c82d96fa858..5931d49f369 100644
--- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockCreator.java
@@ -14,7 +14,7 @@
*/
package org.hyperledger.besu.ethereum.blockcreation;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeader;
import org.hyperledger.besu.ethereum.core.Transaction;
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java
deleted file mode 100644
index 1130d5021a4..00000000000
--- a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Copyright ConsenSys AG.
- *
- * 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;
-
-import org.hyperledger.besu.datatypes.Address;
-import org.hyperledger.besu.datatypes.TransactionType;
-import org.hyperledger.besu.datatypes.Wei;
-import org.hyperledger.besu.ethereum.GasLimitCalculator;
-import org.hyperledger.besu.ethereum.chain.Blockchain;
-import org.hyperledger.besu.ethereum.core.LogsWrapper;
-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.eth.transactions.TransactionPool;
-import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor;
-import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
-import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
-import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
-import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
-import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
-import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
-import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
-import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup;
-import org.hyperledger.besu.evm.gascalculator.GasCalculator;
-import org.hyperledger.besu.evm.log.Log;
-import org.hyperledger.besu.evm.worldstate.WorldUpdater;
-import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
-import org.hyperledger.besu.plugin.services.txselection.TransactionSelector;
-import org.hyperledger.besu.plugin.services.txselection.TransactionSelectorFactory;
-
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.concurrent.CancellationException;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import com.google.common.collect.Lists;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Responsible for extracting transactions from PendingTransactions and determining if the
- * transaction is suitable for inclusion in the block defined by the provided
- * ProcessableBlockHeader.
- *
- *
If a transaction is suitable for inclusion, the world state must be updated, and a receipt
- * generated.
- *
- *
The output from this class's execution will be:
- *
- *
- * - A list of transactions to include in the block being constructed.
- *
- A list of receipts for inclusion in the block.
- *
- The root hash of the world state at the completion of transaction execution.
- *
- The amount of gas consumed when executing all transactions.
- *
- A list of transactions evaluated but not included in the block being constructed.
- *
- *
- * Once "used" this class must be discarded and another created. This class contains state which is
- * not cleared between executions of buildTransactionListForBlock().
- */
-public class BlockTransactionSelector {
-
- public static class TransactionSelectionResults {
- private final List selectedTransactions = Lists.newArrayList();
- private final Map> transactionsByType =
- new EnumMap<>(TransactionType.class);
- private final List receipts = Lists.newArrayList();
- private final Map notSelectedTransactions =
- new HashMap<>();
- private long cumulativeGasUsed = 0;
- private long cumulativeBlobGasUsed = 0;
-
- private void updateSelected(
- final Transaction transaction,
- final TransactionReceipt receipt,
- final long gasUsed,
- final long blobGasUsed) {
- selectedTransactions.add(transaction);
- transactionsByType
- .computeIfAbsent(transaction.getType(), type -> new ArrayList<>())
- .add(transaction);
- receipts.add(receipt);
- cumulativeGasUsed += gasUsed;
- cumulativeBlobGasUsed += blobGasUsed;
- LOG.atTrace()
- .setMessage(
- "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative blob gas used {}")
- .addArgument(transaction::toTraceLog)
- .addArgument(selectedTransactions::size)
- .addArgument(cumulativeGasUsed)
- .addArgument(cumulativeBlobGasUsed)
- .log();
- }
-
- public void updateNotSelected(
- final Transaction transaction, final TransactionSelectionResult res) {
- notSelectedTransactions.put(transaction, res);
- }
-
- public List getSelectedTransactions() {
- return selectedTransactions;
- }
-
- public List getTransactionsByType(final TransactionType type) {
- return transactionsByType.getOrDefault(type, List.of());
- }
-
- public List getReceipts() {
- return receipts;
- }
-
- public long getCumulativeGasUsed() {
- return cumulativeGasUsed;
- }
-
- public long getCumulativeBlobGasUsed() {
- return cumulativeBlobGasUsed;
- }
-
- public Map getNotSelectedTransactions() {
- return notSelectedTransactions;
- }
-
- public void logSelectionStats() {
- if (LOG.isDebugEnabled()) {
- final Map notSelectedStats =
- notSelectedTransactions.values().stream()
- .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
-
- LOG.debug(
- "Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]",
- selectedTransactions.size() + notSelectedTransactions.size(),
- selectedTransactions.size(),
- notSelectedStats.size(),
- notSelectedStats.entrySet().stream()
- .filter(e -> e.getKey().discard())
- .map(Map.Entry::getValue)
- .mapToInt(Long::intValue)
- .sum(),
- notSelectedStats.entrySet().stream()
- .map(e -> e.getKey().toString() + "=" + e.getValue())
- .sorted()
- .collect(Collectors.joining(", ")));
- }
- }
-
- @Override
- public boolean equals(final Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- TransactionSelectionResults that = (TransactionSelectionResults) o;
- return cumulativeGasUsed == that.cumulativeGasUsed
- && cumulativeBlobGasUsed == that.cumulativeBlobGasUsed
- && selectedTransactions.equals(that.selectedTransactions)
- && notSelectedTransactions.equals(that.notSelectedTransactions)
- && receipts.equals(that.receipts);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(
- selectedTransactions,
- notSelectedTransactions,
- receipts,
- cumulativeGasUsed,
- cumulativeBlobGasUsed);
- }
-
- public String toTraceLog() {
- return "cumulativeGasUsed="
- + cumulativeGasUsed
- + ", cumulativeBlobGasUsed="
- + cumulativeBlobGasUsed
- + ", selectedTransactions="
- + selectedTransactions.stream()
- .map(Transaction::toTraceLog)
- .collect(Collectors.joining("; "))
- + ", notSelectedTransactions="
- + notSelectedTransactions.entrySet().stream()
- .map(e -> e.getValue() + ":" + e.getKey().toTraceLog())
- .collect(Collectors.joining(";"));
- }
- }
-
- private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class);
- private static final TransactionSelector ALWAYS_SELECT =
- (_1, _2, _3, _4) -> TransactionSelectionResult.SELECTED;
- private final Wei minTransactionGasPrice;
- private final Double minBlockOccupancyRatio;
- private final Supplier isCancelled;
- private final MainnetTransactionProcessor transactionProcessor;
- private final ProcessableBlockHeader processableBlockHeader;
- private final Blockchain blockchain;
- private final MutableWorldState worldState;
- private final TransactionPool transactionPool;
- private final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory;
- private final Address miningBeneficiary;
- private final Wei blobGasPrice;
- private final FeeMarket feeMarket;
- private final GasCalculator gasCalculator;
- private final GasLimitCalculator gasLimitCalculator;
- private final TransactionSelector transactionSelector;
- private final TransactionSelectionResults transactionSelectionResults =
- new TransactionSelectionResults();
-
- public BlockTransactionSelector(
- final MainnetTransactionProcessor transactionProcessor,
- final Blockchain blockchain,
- final MutableWorldState worldState,
- final TransactionPool transactionPool,
- final ProcessableBlockHeader processableBlockHeader,
- final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory,
- final Wei minTransactionGasPrice,
- final Double minBlockOccupancyRatio,
- final Supplier isCancelled,
- final Address miningBeneficiary,
- final Wei blobGasPrice,
- final FeeMarket feeMarket,
- final GasCalculator gasCalculator,
- final GasLimitCalculator gasLimitCalculator,
- final Optional transactionSelectorFactory) {
- this.transactionProcessor = transactionProcessor;
- this.blockchain = blockchain;
- this.worldState = worldState;
- this.transactionPool = transactionPool;
- this.processableBlockHeader = processableBlockHeader;
- this.transactionReceiptFactory = transactionReceiptFactory;
- this.isCancelled = isCancelled;
- this.minTransactionGasPrice = minTransactionGasPrice;
- this.minBlockOccupancyRatio = minBlockOccupancyRatio;
- this.miningBeneficiary = miningBeneficiary;
- this.blobGasPrice = blobGasPrice;
- this.feeMarket = feeMarket;
- this.gasCalculator = gasCalculator;
- this.gasLimitCalculator = gasLimitCalculator;
- this.transactionSelector =
- transactionSelectorFactory.map(TransactionSelectorFactory::create).orElse(ALWAYS_SELECT);
- }
-
- /*
- This function iterates over (potentially) all transactions in the PendingTransactions, this is a
- long-running process. If running in a thread, it can be cancelled via the isCancelled supplier (which will result
- in this throwing a CancellationException).
- */
- public TransactionSelectionResults buildTransactionListForBlock() {
- LOG.atDebug()
- .setMessage("Transaction pool stats {}")
- .addArgument(transactionPool::logStats)
- .log();
- transactionPool.selectTransactions(
- pendingTransaction -> {
- final var res = evaluateTransaction(pendingTransaction);
- if (!res.selected()) {
- transactionSelectionResults.updateNotSelected(pendingTransaction, res);
- }
- return res;
- });
- LOG.atTrace()
- .setMessage("Transaction selection result {}")
- .addArgument(transactionSelectionResults::toTraceLog)
- .log();
- return transactionSelectionResults;
- }
-
- /**
- * Evaluate the given transactions and return the result of that evaluation.
- *
- * @param transactions The set of transactions to evaluate.
- * @return The {@code TransactionSelectionResults} results of transaction evaluation.
- */
- public TransactionSelectionResults evaluateTransactions(final List transactions) {
- transactions.forEach(
- transaction -> {
- final var res = evaluateTransaction(transaction);
- if (!res.selected()) {
- transactionSelectionResults.updateNotSelected(transaction, res);
- }
- });
- return transactionSelectionResults;
- }
-
- /*
- * Passed into the PendingTransactions, and is called on each transaction until sufficient
- * transactions are found which fill a block worth of gas.
- *
- * This function will continue to be called until the block under construction is suitably
- * full (in terms of gasLimit) and the provided transaction's gasLimit does not fit within
- * the space remaining in the block.
- *
- */
- private TransactionSelectionResult evaluateTransaction(final Transaction transaction) {
- if (isCancelled.get()) {
- throw new CancellationException("Cancelled during transaction selection.");
- }
-
- if (transactionTooLargeForBlock(transaction)) {
- LOG.atTrace()
- .setMessage("Transaction {} too large to select for block creation")
- .addArgument(transaction::toTraceLog)
- .log();
- if (blockOccupancyAboveThreshold()) {
- LOG.trace("Block occupancy above threshold, completing operation");
- return TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
- } else if (blockFull()) {
- LOG.trace("Block full, completing operation");
- return TransactionSelectionResult.BLOCK_FULL;
- } else {
- return TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS;
- }
- }
-
- if (transactionCurrentPriceBelowMin(transaction)) {
- return TransactionSelectionResult.CURRENT_TX_PRICE_BELOW_MIN;
- }
- if (transactionDataPriceBelowMin(transaction)) {
- return TransactionSelectionResult.DATA_PRICE_BELOW_CURRENT_MIN;
- }
-
- final WorldUpdater worldStateUpdater = worldState.updater();
- final BlockHashLookup blockHashLookup =
- new CachingBlockHashLookup(processableBlockHeader, blockchain);
-
- final TransactionProcessingResult effectiveResult =
- transactionProcessor.processTransaction(
- blockchain,
- worldStateUpdater,
- processableBlockHeader,
- transaction,
- miningBeneficiary,
- blockHashLookup,
- false,
- TransactionValidationParams.mining(),
- blobGasPrice);
-
- if (!effectiveResult.isInvalid()) {
-
- final long gasUsedByTransaction =
- transaction.getGasLimit() - effectiveResult.getGasRemaining();
-
- final long cumulativeGasUsed =
- transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;
-
- // check if the transaction is valid also for an optional configured plugin
- final TransactionSelectionResult txSelectionResult =
- transactionSelector.selectTransaction(
- transaction,
- effectiveResult.getStatus() == TransactionProcessingResult.Status.SUCCESSFUL,
- getLogs(effectiveResult.getLogs()),
- cumulativeGasUsed);
-
- if (txSelectionResult.equals(TransactionSelectionResult.SELECTED)) {
-
- worldStateUpdater.commit();
- final TransactionReceipt receipt =
- transactionReceiptFactory.create(
- transaction.getType(), effectiveResult, worldState, cumulativeGasUsed);
-
- final long blobGasUsed = gasCalculator.blobGasCost(transaction.getBlobCount());
-
- transactionSelectionResults.updateSelected(
- transaction, receipt, gasUsedByTransaction, blobGasUsed);
-
- LOG.atTrace()
- .setMessage("Selected {} for block creation")
- .addArgument(transaction::toTraceLog)
- .log();
-
- return TransactionSelectionResult.SELECTED;
- }
-
- // the transaction is not valid for the plugin
- return txSelectionResult;
- }
-
- return transactionSelectionResultForInvalidResult(
- transaction, effectiveResult.getValidationResult());
- }
-
- private List getLogs(final List logs) {
- return logs.stream().map(LogsWrapper::new).collect(Collectors.toList());
- }
-
- private boolean transactionDataPriceBelowMin(final Transaction transaction) {
- if (transaction.getType().supportsBlob()) {
- if (transaction.getMaxFeePerBlobGas().orElseThrow().lessThan(blobGasPrice)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean transactionCurrentPriceBelowMin(final Transaction transaction) {
- // Here we only care about EIP1159 since for Frontier and local transactions the checks
- // that we do when accepting them in the pool are enough
- if (transaction.getType().supports1559FeeMarket()
- && !transactionPool.isLocalSender(transaction.getSender())) {
-
- // For EIP1559 transactions, the price is dynamic and depends on network conditions, so we can
- // only calculate at this time the current minimum price the transaction is willing to pay
- // and if it is above the minimum accepted by the node.
- // If below we do not delete the transaction, since when we added the transaction to the pool,
- // we assured sure that the maxFeePerGas is >= of the minimum price accepted by the node
- // and so the price of the transaction could satisfy this rule in the future
- final Wei currentMinTransactionGasPriceInBlock =
- feeMarket
- .getTransactionPriceCalculator()
- .price(transaction, processableBlockHeader.getBaseFee());
- if (minTransactionGasPrice.compareTo(currentMinTransactionGasPriceInBlock) > 0) {
- LOG.trace(
- "Current gas fee of {} is lower than configured minimum {}, skipping",
- transaction,
- minTransactionGasPrice);
- return true;
- }
- }
- return false;
- }
-
- private TransactionSelectionResult transactionSelectionResultForInvalidResult(
- final Transaction transaction,
- final ValidationResult invalidReasonValidationResult) {
-
- final TransactionInvalidReason invalidReason = invalidReasonValidationResult.getInvalidReason();
- // If the invalid reason is transient, then leave the transaction in the pool and continue
- if (isTransientValidationError(invalidReason)) {
- LOG.atTrace()
- .setMessage("Transient validation error {} for transaction {} keeping it in the pool")
- .addArgument(invalidReason)
- .addArgument(transaction::toTraceLog)
- .log();
- return TransactionSelectionResult.invalidTransient(invalidReason.name());
- }
- // If the transaction was invalid for any other reason, delete it, and continue.
- LOG.atTrace()
- .setMessage("Delete invalid transaction {}, reason {}")
- .addArgument(transaction::toTraceLog)
- .addArgument(invalidReason)
- .log();
- return TransactionSelectionResult.invalid(invalidReason.name());
- }
-
- private boolean isTransientValidationError(final TransactionInvalidReason invalidReason) {
- return invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE)
- || invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH);
- }
-
- private boolean transactionTooLargeForBlock(final Transaction transaction) {
- final long blobGasUsed = gasCalculator.blobGasCost(transaction.getBlobCount());
-
- if (blobGasUsed
- > gasLimitCalculator.currentBlobGasLimit()
- - transactionSelectionResults.getCumulativeBlobGasUsed()) {
- return true;
- }
-
- return transaction.getGasLimit() + blobGasUsed
- > processableBlockHeader.getGasLimit() - transactionSelectionResults.getCumulativeGasUsed();
- }
-
- private boolean blockOccupancyAboveThreshold() {
- final long gasAvailable = processableBlockHeader.getGasLimit();
-
- final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
- final long gasRemaining = gasAvailable - gasUsed;
- final double occupancyRatio = (double) gasUsed / (double) gasAvailable;
-
- LOG.trace(
- "Min block occupancy ratio {}, gas used {}, available {}, remaining {}, used/available {}",
- minBlockOccupancyRatio,
- gasUsed,
- gasAvailable,
- gasRemaining,
- occupancyRatio);
-
- return occupancyRatio >= minBlockOccupancyRatio;
- }
-
- private boolean blockFull() {
- final long gasAvailable = processableBlockHeader.getGasLimit();
- final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
-
- final long gasRemaining = gasAvailable - gasUsed;
-
- if (gasRemaining < gasCalculator.getMinimumTransactionCost()) {
- LOG.trace(
- "Block full, remaining gas {} is less than minimum transaction gas cost {}",
- gasRemaining,
- gasCalculator.getMinimumTransactionCost());
- return true;
- }
- return false;
- }
-}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java
new file mode 100644
index 00000000000..0292f0bf67c
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.GasLimitCalculator;
+import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
+import org.hyperledger.besu.ethereum.eth.transactions.TransactionPool;
+import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+
+public record BlockSelectionContext(
+ GasCalculator gasCalculator,
+ GasLimitCalculator gasLimitCalculator,
+ Wei minTransactionGasPrice,
+ Double minBlockOccupancyRatio,
+ ProcessableBlockHeader processableBlockHeader,
+ FeeMarket feeMarket,
+ Wei blobGasPrice,
+ Address miningBeneficiary,
+ TransactionPool transactionPool) {}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java
new file mode 100644
index 00000000000..68ea7444060
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection;
+
+import org.hyperledger.besu.datatypes.Address;
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.GasLimitCalculator;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.AbstractTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlobPriceTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.BlockSizeTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.PriceTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.selectors.ProcessingResultTransactionSelector;
+import org.hyperledger.besu.ethereum.chain.Blockchain;
+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.eth.transactions.TransactionPool;
+import org.hyperledger.besu.ethereum.mainnet.AbstractBlockProcessor;
+import org.hyperledger.besu.ethereum.mainnet.MainnetTransactionProcessor;
+import org.hyperledger.besu.ethereum.mainnet.TransactionValidationParams;
+import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.ethereum.vm.BlockHashLookup;
+import org.hyperledger.besu.ethereum.vm.CachingBlockHashLookup;
+import org.hyperledger.besu.evm.gascalculator.GasCalculator;
+import org.hyperledger.besu.evm.worldstate.WorldUpdater;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+import org.hyperledger.besu.plugin.services.txselection.TransactionSelector;
+import org.hyperledger.besu.plugin.services.txselection.TransactionSelectorFactory;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.CancellationException;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Responsible for extracting transactions from PendingTransactions and determining if the
+ * transaction is suitable for inclusion in the block defined by the provided
+ * ProcessableBlockHeader.
+ *
+ * If a transaction is suitable for inclusion, the world state must be updated, and a receipt
+ * generated.
+ *
+ *
The output from this class's execution will be:
+ *
+ *
+ * - A list of transactions to include in the block being constructed.
+ *
- A list of receipts for inclusion in the block.
+ *
- The root hash of the world state at the completion of transaction execution.
+ *
- The amount of gas consumed when executing all transactions.
+ *
- A list of transactions evaluated but not included in the block being constructed.
+ *
+ *
+ * Once "used" this class must be discarded and another created. This class contains state which is
+ * not cleared between executions of buildTransactionListForBlock().
+ */
+public class BlockTransactionSelector {
+ private static final Logger LOG = LoggerFactory.getLogger(BlockTransactionSelector.class);
+ private final Supplier isCancelled;
+ private final MainnetTransactionProcessor transactionProcessor;
+ private final Blockchain blockchain;
+ private final MutableWorldState worldState;
+ private final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory;
+ private final BlockSelectionContext blockSelectionContext;
+ private final TransactionSelectionResults transactionSelectionResults =
+ new TransactionSelectionResults();
+ private final List transactionSelectors;
+ private final List externalTransactionSelectors;
+
+ public BlockTransactionSelector(
+ final MainnetTransactionProcessor transactionProcessor,
+ final Blockchain blockchain,
+ final MutableWorldState worldState,
+ final TransactionPool transactionPool,
+ final ProcessableBlockHeader processableBlockHeader,
+ final AbstractBlockProcessor.TransactionReceiptFactory transactionReceiptFactory,
+ final Wei minTransactionGasPrice,
+ final Double minBlockOccupancyRatio,
+ final Supplier isCancelled,
+ final Address miningBeneficiary,
+ final Wei blobGasPrice,
+ final FeeMarket feeMarket,
+ final GasCalculator gasCalculator,
+ final GasLimitCalculator gasLimitCalculator,
+ final Optional transactionSelectorFactory) {
+ this.transactionProcessor = transactionProcessor;
+ this.blockchain = blockchain;
+ this.worldState = worldState;
+ this.transactionReceiptFactory = transactionReceiptFactory;
+ this.isCancelled = isCancelled;
+ this.blockSelectionContext =
+ new BlockSelectionContext(
+ gasCalculator,
+ gasLimitCalculator,
+ minTransactionGasPrice,
+ minBlockOccupancyRatio,
+ processableBlockHeader,
+ feeMarket,
+ blobGasPrice,
+ miningBeneficiary,
+ transactionPool);
+ transactionSelectors = createTransactionSelectors(blockSelectionContext);
+ externalTransactionSelectors =
+ createExternalTransactionSelectors(
+ transactionSelectorFactory.map(List::of).orElseGet(List::of));
+ }
+
+ /**
+ * Builds a list of transactions for a block by iterating over all transactions in the
+ * PendingTransactions pool. This operation can be long-running and, if executed in a separate
+ * thread, can be cancelled via the isCancelled supplier, which would result in a
+ * CancellationException.
+ *
+ * @return The {@code TransactionSelectionResults} containing the results of transaction
+ * evaluation.
+ */
+ public TransactionSelectionResults buildTransactionListForBlock() {
+ LOG.atDebug()
+ .setMessage("Transaction pool stats {}")
+ .addArgument(blockSelectionContext.transactionPool().logStats())
+ .log();
+ blockSelectionContext
+ .transactionPool()
+ .selectTransactions(
+ pendingTransaction -> {
+ final var res = evaluateTransaction(pendingTransaction);
+ if (!res.selected()) {
+ transactionSelectionResults.updateNotSelected(pendingTransaction, res);
+ }
+ return res;
+ });
+ LOG.atTrace()
+ .setMessage("Transaction selection result {}")
+ .addArgument(transactionSelectionResults::toTraceLog)
+ .log();
+ return transactionSelectionResults;
+ }
+
+ /**
+ * Evaluates a list of transactions and updates the selection results accordingly. If a
+ * transaction is not selected during the evaluation, it is updated as not selected in the
+ * transaction selection results.
+ *
+ * @param transactions The list of transactions to be evaluated.
+ * @return The {@code TransactionSelectionResults} containing the results of the transaction
+ * evaluations.
+ */
+ public TransactionSelectionResults evaluateTransactions(final List transactions) {
+ transactions.forEach(
+ transaction -> {
+ final var res = evaluateTransaction(transaction);
+ if (!res.selected()) {
+ transactionSelectionResults.updateNotSelected(transaction, res);
+ }
+ });
+ return transactionSelectionResults;
+ }
+
+ /*
+ * Passed into the PendingTransactions, and is called on each transaction until sufficient
+ * transactions are found which fill a block worth of gas.
+ *
+ * This function will continue to be called until the block under construction is suitably
+ * full (in terms of gasLimit) and the provided transaction's gasLimit does not fit within
+ * the space remaining in the block.
+ *
+ */
+ private TransactionSelectionResult evaluateTransaction(final Transaction transaction) {
+ if (isCancelled.get()) {
+ throw new CancellationException("Cancelled during transaction selection.");
+ }
+
+ TransactionSelectionResult selectionResult = evaluateTransactionPreProcessing(transaction);
+ if (!selectionResult.selected()) {
+ return selectionResult;
+ }
+
+ final WorldUpdater worldStateUpdater = worldState.updater();
+ final BlockHashLookup blockHashLookup =
+ new CachingBlockHashLookup(blockSelectionContext.processableBlockHeader(), blockchain);
+
+ final TransactionProcessingResult effectiveResult =
+ transactionProcessor.processTransaction(
+ blockchain,
+ worldStateUpdater,
+ blockSelectionContext.processableBlockHeader(),
+ transaction,
+ blockSelectionContext.miningBeneficiary(),
+ blockHashLookup,
+ false,
+ TransactionValidationParams.mining(),
+ blockSelectionContext.blobGasPrice());
+
+ var transactionWithProcessingContextResult =
+ evaluateTransactionPostProcessing(transaction, effectiveResult);
+ if (!transactionWithProcessingContextResult.selected()) {
+ return transactionWithProcessingContextResult;
+ }
+
+ final long gasUsedByTransaction = transaction.getGasLimit() - effectiveResult.getGasRemaining();
+ final long cumulativeGasUsed =
+ transactionSelectionResults.getCumulativeGasUsed() + gasUsedByTransaction;
+
+ worldStateUpdater.commit();
+ final TransactionReceipt receipt =
+ transactionReceiptFactory.create(
+ transaction.getType(), effectiveResult, worldState, cumulativeGasUsed);
+
+ final long blobGasUsed =
+ blockSelectionContext.gasCalculator().blobGasCost(transaction.getBlobCount());
+
+ transactionSelectionResults.updateSelected(
+ transaction, receipt, gasUsedByTransaction, blobGasUsed);
+
+ LOG.atTrace()
+ .setMessage("Selected {} for block creation")
+ .addArgument(transaction::toTraceLog)
+ .log();
+
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * This method evaluates a transaction by pre-processing it through a series of selectors. It
+ * first processes the transaction through internal selectors, and if the transaction is selected,
+ * it then processes it through external selectors. If the transaction is selected by all
+ * selectors, it returns SELECTED.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @return The result of the transaction selection process.
+ */
+ private TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction) {
+
+ // Process the transaction through internal selectors
+ for (var selector : transactionSelectors) {
+ TransactionSelectionResult result =
+ selector.evaluateTransactionPreProcessing(transaction, transactionSelectionResults);
+ // If the transaction is not selected by any internal selector, return the result
+ if (!result.equals(TransactionSelectionResult.SELECTED)) {
+ return result;
+ }
+ }
+
+ // Process the transaction through external selectors
+ for (var selector : externalTransactionSelectors) {
+ TransactionSelectionResult result = selector.evaluateTransactionPreProcessing(transaction);
+ // If the transaction is not selected by any external selector, return the result
+ if (!result.equals(TransactionSelectionResult.SELECTED)) {
+ return result;
+ }
+ }
+ // If the transaction is selected by all selectors, return SELECTED
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * This method evaluates a transaction by processing it through a series of selectors. Each
+ * selector may use the transaction and/or the result of the transaction processing to decide
+ * whether the transaction should be included in a block. If the transaction is selected by all
+ * selectors, it returns SELECTED.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param processingResult The result of the transaction processing.
+ * @return The result of the transaction selection process.
+ */
+ private TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction, final TransactionProcessingResult processingResult) {
+
+ // Process the transaction through internal selectors
+ for (var selector : transactionSelectors) {
+ TransactionSelectionResult result =
+ selector.evaluateTransactionPostProcessing(
+ transaction, transactionSelectionResults, processingResult);
+ // If the transaction is not selected by any selector, return the result
+ if (!result.equals(TransactionSelectionResult.SELECTED)) {
+ return result;
+ }
+ }
+
+ // TODO: External selectors are not used here because TransactionProcessingResult is not
+ // exposed to the Plugin API yet.
+
+ // If the transaction is selected by all selectors, return SELECTED
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ private List createTransactionSelectors(
+ final BlockSelectionContext context) {
+ return List.of(
+ new BlockSizeTransactionSelector(context),
+ new PriceTransactionSelector(context),
+ new BlobPriceTransactionSelector(context),
+ new ProcessingResultTransactionSelector(context));
+ }
+
+ private List createExternalTransactionSelectors(
+ final List transactionSelectorFactory) {
+ return transactionSelectorFactory.stream()
+ .map(TransactionSelectorFactory::create)
+ .collect(Collectors.toList());
+ }
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java
new file mode 100644
index 00000000000..b28640bae40
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection;
+
+import org.hyperledger.besu.datatypes.TransactionType;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.core.TransactionReceipt;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TransactionSelectionResults {
+ private static final Logger LOG = LoggerFactory.getLogger(TransactionSelectionResults.class);
+
+ private final List selectedTransactions = Lists.newArrayList();
+ private final Map> transactionsByType =
+ new EnumMap<>(TransactionType.class);
+ private final List receipts = Lists.newArrayList();
+ private final Map notSelectedTransactions =
+ new HashMap<>();
+ private long cumulativeGasUsed = 0;
+ private long cumulativeBlobGasUsed = 0;
+
+ void updateSelected(
+ final Transaction transaction,
+ final TransactionReceipt receipt,
+ final long gasUsed,
+ final long blobGasUsed) {
+ selectedTransactions.add(transaction);
+ transactionsByType
+ .computeIfAbsent(transaction.getType(), type -> new ArrayList<>())
+ .add(transaction);
+ receipts.add(receipt);
+ cumulativeGasUsed += gasUsed;
+ cumulativeBlobGasUsed += blobGasUsed;
+ LOG.atTrace()
+ .setMessage(
+ "New selected transaction {}, total transactions {}, cumulative gas used {}, cumulative blob gas used {}")
+ .addArgument(transaction::toTraceLog)
+ .addArgument(selectedTransactions::size)
+ .addArgument(cumulativeGasUsed)
+ .addArgument(cumulativeBlobGasUsed)
+ .log();
+ }
+
+ public void updateNotSelected(
+ final Transaction transaction, final TransactionSelectionResult res) {
+ notSelectedTransactions.put(transaction, res);
+ }
+
+ public List getSelectedTransactions() {
+ return selectedTransactions;
+ }
+
+ public List getTransactionsByType(final TransactionType type) {
+ return transactionsByType.getOrDefault(type, List.of());
+ }
+
+ public List getReceipts() {
+ return receipts;
+ }
+
+ public long getCumulativeGasUsed() {
+ return cumulativeGasUsed;
+ }
+
+ public long getCumulativeBlobGasUsed() {
+ return cumulativeBlobGasUsed;
+ }
+
+ public Map getNotSelectedTransactions() {
+ return notSelectedTransactions;
+ }
+
+ public void logSelectionStats() {
+ if (LOG.isDebugEnabled()) {
+ final Map notSelectedStats =
+ notSelectedTransactions.values().stream()
+ .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+
+ LOG.debug(
+ "Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]",
+ selectedTransactions.size() + notSelectedTransactions.size(),
+ selectedTransactions.size(),
+ notSelectedStats.size(),
+ notSelectedStats.entrySet().stream()
+ .filter(e -> e.getKey().discard())
+ .map(Map.Entry::getValue)
+ .mapToInt(Long::intValue)
+ .sum(),
+ notSelectedStats.entrySet().stream()
+ .map(e -> e.getKey().toString() + "=" + e.getValue())
+ .sorted()
+ .collect(Collectors.joining(", ")));
+ }
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TransactionSelectionResults that = (TransactionSelectionResults) o;
+ return cumulativeGasUsed == that.cumulativeGasUsed
+ && cumulativeBlobGasUsed == that.cumulativeBlobGasUsed
+ && selectedTransactions.equals(that.selectedTransactions)
+ && notSelectedTransactions.equals(that.notSelectedTransactions)
+ && receipts.equals(that.receipts);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(
+ selectedTransactions,
+ notSelectedTransactions,
+ receipts,
+ cumulativeGasUsed,
+ cumulativeBlobGasUsed);
+ }
+
+ public String toTraceLog() {
+ return "cumulativeGasUsed="
+ + cumulativeGasUsed
+ + ", cumulativeBlobGasUsed="
+ + cumulativeBlobGasUsed
+ + ", selectedTransactions="
+ + selectedTransactions.stream()
+ .map(Transaction::toTraceLog)
+ .collect(Collectors.joining("; "))
+ + ", notSelectedTransactions="
+ + notSelectedTransactions.entrySet().stream()
+ .map(e -> e.getValue() + ":" + e.getKey().toTraceLog())
+ .collect(Collectors.joining(";"));
+ }
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java
new file mode 100644
index 00000000000..3e89bb61006
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
+
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+/**
+ * This class represents an abstract transaction selector which provides methods to evaluate
+ * transactions.
+ */
+public abstract class AbstractTransactionSelector {
+ final BlockSelectionContext context;
+
+ public AbstractTransactionSelector(final BlockSelectionContext context) {
+ this.context = context;
+ }
+
+ /**
+ * Evaluates a transaction in the context of other transactions in the same block.
+ *
+ * @param transaction The transaction to be evaluated within a block.
+ * @param blockTransactionResults The results of other transaction evaluations in the same block.
+ * @return The result of the transaction evaluation
+ */
+ public abstract TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction, final TransactionSelectionResults blockTransactionResults);
+
+ /**
+ * Evaluates a transaction considering other transactions in the same block and a transaction
+ * processing result.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param blockTransactionResults The results of other transaction evaluations in the same block.
+ * @param processingResult The result of transaction processing.
+ * @return The result of the transaction evaluation
+ */
+ public abstract TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults blockTransactionResults,
+ final TransactionProcessingResult processingResult);
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java
new file mode 100644
index 00000000000..abf38621fd2
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
+
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class extends AbstractTransactionSelector and provides a specific implementation for
+ * evaluating transactions based on Blob price. It checks if a transaction's current blob price is
+ * below the minimum and determines the selection result accordingly.
+ */
+public class BlobPriceTransactionSelector extends AbstractTransactionSelector {
+ private static final Logger LOG = LoggerFactory.getLogger(BlobPriceTransactionSelector.class);
+
+ public BlobPriceTransactionSelector(final BlockSelectionContext context) {
+ super(context);
+ }
+
+ /**
+ * Evaluates a transaction considering its blob price.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param ignored The results of other transaction evaluations in the same block.
+ * @return The result of the transaction selection.
+ */
+ @Override
+ public TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction, final TransactionSelectionResults ignored) {
+ if (transactionBlobPriceBelowMin(transaction)) {
+ return TransactionSelectionResult.BLOB_PRICE_BELOW_CURRENT_MIN;
+ }
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ @Override
+ public TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults blockTransactionResults,
+ final TransactionProcessingResult processingResult) {
+ // All necessary checks were done in the pre-processing method, so nothing to do here.
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * Checks if the transaction's blob price is below the minimum.
+ *
+ * @param transaction The transaction to be checked.
+ * @return True if the transaction's data price is below the minimum, false otherwise.
+ */
+ private boolean transactionBlobPriceBelowMin(final Transaction transaction) {
+ if (transaction.getType().supportsBlob()) {
+ if (transaction.getMaxFeePerBlobGas().orElseThrow().lessThan(context.blobGasPrice())) {
+ LOG.trace(
+ "Max fee per Blob Gas {} below {}",
+ transaction.getMaxFeePerBlobGas(),
+ context.blobGasPrice());
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java
new file mode 100644
index 00000000000..7cef4995e5e
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
+
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class extends AbstractTransactionSelector and provides a specific implementation for
+ * evaluating transactions based on block size. It checks if a transaction is too large for the
+ * block and determines the selection result accordingly.
+ */
+public class BlockSizeTransactionSelector extends AbstractTransactionSelector {
+ private static final Logger LOG = LoggerFactory.getLogger(BlockSizeTransactionSelector.class);
+
+ public BlockSizeTransactionSelector(final BlockSelectionContext context) {
+ super(context);
+ }
+
+ /**
+ * Evaluates a transaction considering other transactions in the same block. If the transaction is
+ * too large for the block returns a selection result based on block occupancy.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param transactionSelectionResults The results of other transaction evaluations in the same
+ * block.
+ * @return The result of the transaction selection.
+ */
+ @Override
+ public TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults transactionSelectionResults) {
+ if (transactionTooLargeForBlock(transaction, transactionSelectionResults)) {
+ LOG.atTrace()
+ .setMessage("Transaction {} too large to select for block creation")
+ .addArgument(transaction::toTraceLog)
+ .log();
+ if (blockOccupancyAboveThreshold(transactionSelectionResults)) {
+ LOG.trace("Block occupancy above threshold, completing operation");
+ return TransactionSelectionResult.BLOCK_OCCUPANCY_ABOVE_THRESHOLD;
+ } else if (blockFull(transactionSelectionResults)) {
+ LOG.trace("Block full, completing operation");
+ return TransactionSelectionResult.BLOCK_FULL;
+ } else {
+ return TransactionSelectionResult.TX_TOO_LARGE_FOR_REMAINING_GAS;
+ }
+ }
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ @Override
+ public TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults blockTransactionResults,
+ final TransactionProcessingResult processingResult) {
+ // All necessary checks were done in the pre-processing method, so nothing to do here.
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * Checks if the transaction is too large for the block.
+ *
+ * @param transaction The transaction to be checked.
+ * @param transactionSelectionResults The results of other transaction evaluations in the same
+ * block.
+ * @return True if the transaction is too large for the block, false otherwise.
+ */
+ private boolean transactionTooLargeForBlock(
+ final Transaction transaction,
+ final TransactionSelectionResults transactionSelectionResults) {
+ final long blobGasUsed = context.gasCalculator().blobGasCost(transaction.getBlobCount());
+
+ if (blobGasUsed
+ > context.gasLimitCalculator().currentBlobGasLimit()
+ - transactionSelectionResults.getCumulativeBlobGasUsed()) {
+ return true;
+ }
+
+ return transaction.getGasLimit() + blobGasUsed
+ > context.processableBlockHeader().getGasLimit()
+ - transactionSelectionResults.getCumulativeGasUsed();
+ }
+
+ /**
+ * Checks if the block occupancy is above the threshold.
+ *
+ * @param transactionSelectionResults The results of other transaction evaluations in the same
+ * block.
+ * @return True if the block occupancy is above the threshold, false otherwise.
+ */
+ private boolean blockOccupancyAboveThreshold(
+ final TransactionSelectionResults transactionSelectionResults) {
+ final long gasAvailable = context.processableBlockHeader().getGasLimit();
+
+ final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
+ final long gasRemaining = gasAvailable - gasUsed;
+ final double occupancyRatio = (double) gasUsed / (double) gasAvailable;
+
+ LOG.trace(
+ "Min block occupancy ratio {}, gas used {}, available {}, remaining {}, used/available {}",
+ context.minBlockOccupancyRatio(),
+ gasUsed,
+ gasAvailable,
+ gasRemaining,
+ occupancyRatio);
+
+ return occupancyRatio >= context.minBlockOccupancyRatio();
+ }
+
+ /**
+ * Checks if the block is full.
+ *
+ * @param transactionSelectionResults The results of other transaction evaluations in the same
+ * block.
+ * @return True if the block is full, false otherwise.
+ */
+ private boolean blockFull(final TransactionSelectionResults transactionSelectionResults) {
+ final long gasAvailable = context.processableBlockHeader().getGasLimit();
+ final long gasUsed = transactionSelectionResults.getCumulativeGasUsed();
+
+ final long gasRemaining = gasAvailable - gasUsed;
+
+ if (gasRemaining < context.gasCalculator().getMinimumTransactionCost()) {
+ LOG.trace(
+ "Block full, remaining gas {} is less than minimum transaction gas cost {}",
+ gasRemaining,
+ context.gasCalculator().getMinimumTransactionCost());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java
new file mode 100644
index 00000000000..15755100e5c
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
+
+import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class extends AbstractTransactionSelector and provides a specific implementation for
+ * evaluating transactions based on transaction price. It checks if a transaction's current price is
+ * below the minimum and determines the selection result accordingly.
+ */
+public class PriceTransactionSelector extends AbstractTransactionSelector {
+ private static final Logger LOG = LoggerFactory.getLogger(PriceTransactionSelector.class);
+
+ public PriceTransactionSelector(final BlockSelectionContext context) {
+ super(context);
+ }
+
+ /**
+ * Evaluates a transaction considering its price. If the transaction's current price is below the
+ * minimum, it returns a selection result indicating the reason.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param ignored The results of other transaction evaluations in the same block.
+ * @return The result of the transaction selection.
+ */
+ @Override
+ public TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction, final TransactionSelectionResults ignored) {
+ if (transactionCurrentPriceBelowMin(transaction)) {
+ return TransactionSelectionResult.CURRENT_TX_PRICE_BELOW_MIN;
+ }
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ @Override
+ public TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults blockTransactionResults,
+ final TransactionProcessingResult processingResult) {
+ // All necessary checks were done in the pre-processing method, so nothing to do here.
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * Checks if the transaction's current price is below the minimum.
+ *
+ * @param transaction The transaction to be checked.
+ * @return True if the transaction's current price is below the minimum, false otherwise.
+ */
+ private boolean transactionCurrentPriceBelowMin(final Transaction transaction) {
+ // Here we only care about EIP1159 since for Frontier and local transactions the checks
+ // that we do when accepting them in the pool are enough
+ if (transaction.getType().supports1559FeeMarket()
+ && !context.transactionPool().isLocalSender(transaction.getSender())) {
+
+ // For EIP1559 transactions, the price is dynamic and depends on network conditions, so we can
+ // only calculate at this time the current minimum price the transaction is willing to pay
+ // and if it is above the minimum accepted by the node.
+ // If below we do not delete the transaction, since when we added the transaction to the pool,
+ // we assured sure that the maxFeePerGas is >= of the minimum price accepted by the node
+ // and so the price of the transaction could satisfy this rule in the future
+ final Wei currentMinTransactionGasPriceInBlock =
+ context
+ .feeMarket()
+ .getTransactionPriceCalculator()
+ .price(transaction, context.processableBlockHeader().getBaseFee());
+ if (context.minTransactionGasPrice().compareTo(currentMinTransactionGasPriceInBlock) > 0) {
+ LOG.trace(
+ "Current gas fee of {} is lower than configured minimum {}, skipping",
+ transaction,
+ context.minTransactionGasPrice());
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java
new file mode 100644
index 00000000000..1fe71ee48f9
--- /dev/null
+++ b/ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright Hyperledger Besu Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+package org.hyperledger.besu.ethereum.blockcreation.txselection.selectors;
+
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockSelectionContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.core.Transaction;
+import org.hyperledger.besu.ethereum.mainnet.ValidationResult;
+import org.hyperledger.besu.ethereum.processing.TransactionProcessingResult;
+import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason;
+import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class extends AbstractTransactionSelector and provides a specific implementation for
+ * evaluating transactions based on processing results. It checks if a transaction is invalid and
+ * determines the selection result accordingly.
+ */
+public class ProcessingResultTransactionSelector extends AbstractTransactionSelector {
+ private static final Logger LOG =
+ LoggerFactory.getLogger(ProcessingResultTransactionSelector.class);
+
+ public ProcessingResultTransactionSelector(final BlockSelectionContext context) {
+ super(context);
+ }
+
+ @Override
+ public TransactionSelectionResult evaluateTransactionPreProcessing(
+ final Transaction transaction, final TransactionSelectionResults blockTransactionResults) {
+ // All checks depend on processingResult and will be done in the post-processing method, so
+ // nothing to do here.
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * Evaluates a transaction considering other transactions in the same block and a processing
+ * result. If the processing result is invalid, it determines the selection result for the invalid
+ * result.
+ *
+ * @param transaction The transaction to be evaluated.
+ * @param blockTransactionResults The results of other transaction evaluations in the same block.
+ * @param processingResult The processing result of the transaction.
+ * @return The result of the transaction selection.
+ */
+ @Override
+ public TransactionSelectionResult evaluateTransactionPostProcessing(
+ final Transaction transaction,
+ final TransactionSelectionResults blockTransactionResults,
+ final TransactionProcessingResult processingResult) {
+
+ if (processingResult.isInvalid()) {
+ return transactionSelectionResultForInvalidResult(
+ transaction, processingResult.getValidationResult());
+ }
+ return TransactionSelectionResult.SELECTED;
+ }
+
+ /**
+ * Determines the transaction selection result for an invalid result. If the invalid reason is
+ * transient, returns an invalid transient result. If the invalid reason is not transient, returns
+ * an invalid result.
+ *
+ * @param transaction The invalid transaction.
+ * @param invalidReasonValidationResult The validation result containing the invalid reason.
+ * @return The transaction selection result.
+ */
+ private TransactionSelectionResult transactionSelectionResultForInvalidResult(
+ final Transaction transaction,
+ final ValidationResult invalidReasonValidationResult) {
+
+ final TransactionInvalidReason invalidReason = invalidReasonValidationResult.getInvalidReason();
+ // If the invalid reason is transient, then leave the transaction in the pool and continue
+ if (isTransientValidationError(invalidReason)) {
+ LOG.atTrace()
+ .setMessage("Transient validation error {} for transaction {} keeping it in the pool")
+ .addArgument(invalidReason)
+ .addArgument(transaction::toTraceLog)
+ .log();
+ return TransactionSelectionResult.invalidTransient(invalidReason.name());
+ }
+ // If the transaction was invalid for any other reason, delete it, and continue.
+ LOG.atTrace()
+ .setMessage("Delete invalid transaction {}, reason {}")
+ .addArgument(transaction::toTraceLog)
+ .addArgument(invalidReason)
+ .log();
+ return TransactionSelectionResult.invalid(invalidReason.name());
+ }
+
+ /**
+ * Checks if the invalid reason is a transient validation error.
+ *
+ * @param invalidReason The invalid reason.
+ * @return True if the invalid reason is transient, false otherwise.
+ */
+ private boolean isTransientValidationError(final TransactionInvalidReason invalidReason) {
+ return invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE)
+ || invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH);
+ }
+}
diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java
index 460e0809b41..b5ad06d8248 100644
--- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java
+++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockCreatorTest.java
@@ -40,6 +40,7 @@
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
import org.hyperledger.besu.ethereum.core.BlobTestFixture;
import org.hyperledger.besu.ethereum.core.BlockDataGenerator;
@@ -100,8 +101,7 @@ abstract class AbstractBlockCreatorTest {
void findDepositsFromReceipts() {
final AbstractBlockCreator blockCreator =
blockCreatorWithAllowedDeposits(Optional.of(DEFAULT_DEPOSIT_CONTRACT_ADDRESS));
- final BlockTransactionSelector.TransactionSelectionResults transactionResults =
- mock(BlockTransactionSelector.TransactionSelectionResults.class);
+ final TransactionSelectionResults transactionResults = mock(TransactionSelectionResults.class);
BlockDataGenerator blockDataGenerator = new BlockDataGenerator();
TransactionReceipt receiptWithoutDeposit1 = blockDataGenerator.receipt();
TransactionReceipt receiptWithoutDeposit2 = blockDataGenerator.receipt();
diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java
index 50e9cb48821..b15e61354b4 100644
--- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java
+++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java
@@ -30,6 +30,8 @@
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.GasLimitCalculator;
import org.hyperledger.besu.ethereum.ProtocolContext;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.chain.DefaultBlockchain;
import org.hyperledger.besu.ethereum.chain.GenesisState;
import org.hyperledger.besu.ethereum.chain.MutableBlockchain;
@@ -192,8 +194,7 @@ public void emptyPendingTransactionsResultsInEmptyVettingResult() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).isEmpty();
assertThat(results.getNotSelectedTransactions()).isEmpty();
@@ -221,8 +222,7 @@ public void validPendingTransactionIsIncludedInTheBlock() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).containsExactly(transaction);
assertThat(results.getNotSelectedTransactions()).isEmpty();
@@ -258,8 +258,7 @@ public void invalidTransactionsAreSkippedButBlockStillFills() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
final Transaction invalidTx = transactionsToInject.get(1);
@@ -298,8 +297,7 @@ public void subsetOfPendingTransactionsIncludedWhenBlockGasLimitHit() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions().size()).isEqualTo(3);
@@ -348,8 +346,7 @@ public void transactionTooLargeForBlockDoesNotPreventMoreBeingAddedIfBlockOccupa
}
transactionPool.addRemoteTransactions(Arrays.stream(txs).toList());
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).containsExactly(txs[0], txs[2]);
assertThat(results.getNotSelectedTransactions())
@@ -387,8 +384,7 @@ public void transactionSelectionStopsWhenSufficientBlockOccupancyIsReached() {
}
transactionPool.addRemoteTransactions(Arrays.stream(txs).toList());
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).containsExactly(txs[0], txs[1]);
assertThat(results.getNotSelectedTransactions())
@@ -437,8 +433,7 @@ public void transactionSelectionStopsWhenBlockIsFull() {
}
transactionPool.addRemoteTransactions(transactionsToInject);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions())
.containsExactly(
@@ -493,8 +488,7 @@ public void transactionSelectionStopsWhenRemainingGasIsNotEnoughForAnyMoreTransa
}
transactionPool.addRemoteTransactions(transactionsToInject);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions())
.containsExactly(transactionsToInject.get(0), transactionsToInject.get(2));
@@ -530,8 +524,7 @@ public void shouldDiscardTransactionsThatFailValidation() {
transactionPool.addRemoteTransactions(List.of(validTransaction, invalidTransaction));
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(transactionPool.getTransactionByHash(validTransaction.getHash())).isPresent();
assertThat(transactionPool.getTransactionByHash(invalidTransaction.getHash())).isNotPresent();
@@ -559,7 +552,7 @@ public void transactionSelectionPluginShouldWork() {
final TransactionSelectorFactory transactionSelectorFactory =
() ->
- (tx, s, logs, cg) -> {
+ (tx) -> {
if (tx.equals(notSelectedTransient))
return TransactionSelectionResult.invalidTransient("transient");
if (tx.equals(notSelectedInvalid))
@@ -581,7 +574,7 @@ public void transactionSelectionPluginShouldWork() {
transactionPool.addRemoteTransactions(
List.of(selected, notSelectedInvalid, notSelectedTransient));
- final BlockTransactionSelector.TransactionSelectionResults transactionSelectionResults =
+ final TransactionSelectionResults transactionSelectionResults =
selector.buildTransactionListForBlock();
assertThat(transactionPool.getTransactionByHash(notSelectedTransient.getHash())).isPresent();
@@ -612,8 +605,7 @@ public void transactionWithIncorrectNonceRemainsInPoolAndNotSelected() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(transactionPool.getTransactionByHash(futureTransaction.getHash())).isPresent();
assertThat(results.getSelectedTransactions()).isEmpty();
diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java
index 7bd64afe109..4222ad476c0 100644
--- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java
+++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/BlockMinerTest.java
@@ -23,7 +23,7 @@
import org.hyperledger.besu.ethereum.ProtocolContext;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.chain.MinedBlockObserver;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockBody;
diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java
index 0912c9d488a..29860d555a6 100644
--- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java
+++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/LondonFeeMarketBlockTransactionSelectorTest.java
@@ -21,6 +21,8 @@
import org.hyperledger.besu.config.GenesisConfigFile;
import org.hyperledger.besu.datatypes.Address;
import org.hyperledger.besu.datatypes.Wei;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.BlockTransactionSelector;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.AddressHelpers;
import org.hyperledger.besu.ethereum.core.PrivacyParameters;
import org.hyperledger.besu.ethereum.core.ProcessableBlockHeader;
@@ -117,8 +119,7 @@ public void eip1559TransactionCurrentGasPriceLessThanMinimumIsSkippedAndKeptInTh
final var addResults = transactionPool.addRemoteTransactions(List.of(tx));
assertThat(addResults).extractingByKey(tx.getHash()).isEqualTo(ValidationResult.valid());
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).isEmpty();
assertThat(results.getNotSelectedTransactions())
@@ -147,8 +148,7 @@ public void eip1559TransactionCurrentGasPriceGreaterThanMinimumIsSelected() {
ensureTransactionIsValid(tx);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).containsExactly(tx);
assertThat(results.getNotSelectedTransactions()).isEmpty();
@@ -177,8 +177,7 @@ public void eip1559LocalTransactionCurrentGasPriceLessThanMinimumIsSelected() {
ensureTransactionIsValid(tx);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions()).containsExactly(tx);
assertThat(results.getNotSelectedTransactions()).isEmpty();
@@ -210,8 +209,7 @@ public void transactionFromSameSenderWithMixedTypes() {
Wei.ZERO,
MIN_OCCUPANCY_80_PERCENT);
- final BlockTransactionSelector.TransactionSelectionResults results =
- selector.buildTransactionListForBlock();
+ final TransactionSelectionResults results = selector.buildTransactionListForBlock();
assertThat(results.getSelectedTransactions())
.containsExactly(txFrontier1, txLondon1, txFrontier2, txLondon2);
diff --git a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java
index 07f5885f2b8..818210f88a4 100644
--- a/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java
+++ b/ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/PoWBlockCreatorTest.java
@@ -26,7 +26,7 @@
import org.hyperledger.besu.datatypes.Hash;
import org.hyperledger.besu.datatypes.Wei;
import org.hyperledger.besu.ethereum.blockcreation.BlockCreator.BlockCreationResult;
-import org.hyperledger.besu.ethereum.blockcreation.BlockTransactionSelector.TransactionSelectionResults;
+import org.hyperledger.besu.ethereum.blockcreation.txselection.TransactionSelectionResults;
import org.hyperledger.besu.ethereum.core.Block;
import org.hyperledger.besu.ethereum.core.BlockHeaderBuilder;
import org.hyperledger.besu.ethereum.core.Difficulty;
diff --git a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java
index 3fa94c3be1c..f3135045ad5 100644
--- a/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java
+++ b/ethereum/eth/src/test/java/org/hyperledger/besu/ethereum/eth/transactions/layered/LayeredPendingTransactionsTest.java
@@ -23,10 +23,10 @@
import static org.hyperledger.besu.ethereum.eth.transactions.layered.TransactionsLayer.RemovalReason.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.DATA_PRICE_BELOW_CURRENT_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;
@@ -401,7 +401,7 @@ public void ignoreSenderTransactionsAfterASkippedOne(
static Stream ignoreSenderTransactionsAfterASkippedOne() {
return Stream.of(
CURRENT_TX_PRICE_BELOW_MIN,
- DATA_PRICE_BELOW_CURRENT_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()));
diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle
index 2aded925ce1..b66f7fef949 100644
--- a/plugin-api/build.gradle
+++ b/plugin-api/build.gradle
@@ -69,7 +69,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 = 'yJgCLn/XmaOwyIlpSw/6gbsM5eNNQQs6hmpTMvkezqk='
+ knownHash = 'ON5/4jw14IPAL/Civ3ld6tvwrLsGS9eI38w5C0xRzdY='
}
check.dependsOn('checkAPIChanges')
diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java
index 08d3683bdce..ffae842ca72 100644
--- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java
+++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java
@@ -78,8 +78,8 @@ public String toString() {
* The transaction has not been selected since its data price is below the current network data
* price, but the selection should continue.
*/
- public static final TransactionSelectionResult DATA_PRICE_BELOW_CURRENT_MIN =
- TransactionSelectionResult.invalidTransient("DATA_PRICE_BELOW_CURRENT_MIN");
+ public static final TransactionSelectionResult BLOB_PRICE_BELOW_CURRENT_MIN =
+ TransactionSelectionResult.invalidTransient("BLOB_PRICE_BELOW_CURRENT_MIN");
private final Status status;
private final Optional maybeInvalidReason;
diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionSelector.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionSelector.java
index 6322bd81387..c2682b5d8ab 100644
--- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionSelector.java
+++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/txselection/TransactionSelector.java
@@ -17,28 +17,17 @@
import org.hyperledger.besu.datatypes.Transaction;
import org.hyperledger.besu.plugin.Unstable;
-import org.hyperledger.besu.plugin.data.Log;
import org.hyperledger.besu.plugin.data.TransactionSelectionResult;
-import java.util.List;
-
/** Interface for the transaction selector */
@Unstable
public interface TransactionSelector {
-
/**
- * Method called to decide whether a transaction is added to a block. The method can also indicate
+ * Method called to decide whether a transaction is added to a block. The result can also indicate
* that no further transactions can be added to the block.
*
* @param transaction candidate transaction
- * @param success true, if the transaction executed successfully
- * @param logs the logs created by this transaction
- * @param cumulativeGasUsed gas used by this and all previous transaction in the block
* @return TransactionSelectionResult that indicates whether to include the transaction
*/
- TransactionSelectionResult selectTransaction(
- final Transaction transaction,
- final boolean success,
- final List logs,
- final long cumulativeGasUsed);
+ TransactionSelectionResult evaluateTransactionPreProcessing(Transaction transaction);
}