From c4f66c475833c410d6fdeded8fb83477ff1ddff4 Mon Sep 17 00:00:00 2001 From: Gabriel-Trintinalia Date: Thu, 28 Sep 2023 10:33:44 +1000 Subject: [PATCH] BlockTransactionSelector refactoring (#5931) Signed-off-by: Gabriel-Trintinalia --- .../IbftBlockHeightManagerTest.java | 2 +- .../ibft/statemachine/IbftRoundTest.java | 2 +- .../PkiQbftBlockCreatorTest.java | 2 +- .../QbftBlockHeightManagerTest.java | 2 +- .../qbft/statemachine/QbftRoundTest.java | 2 +- .../blockcreation/AbstractBlockCreator.java | 3 +- .../ethereum/blockcreation/BlockCreator.java | 2 +- .../BlockTransactionSelector.java | 517 ------------------ .../txselection/BlockSelectionContext.java | 34 ++ .../txselection/BlockTransactionSelector.java | 320 +++++++++++ .../TransactionSelectionResults.java | 161 ++++++ .../AbstractTransactionSelector.java | 57 ++ .../BlobPriceTransactionSelector.java | 81 +++ .../BlockSizeTransactionSelector.java | 150 +++++ .../selectors/PriceTransactionSelector.java | 98 ++++ .../ProcessingResultTransactionSelector.java | 114 ++++ .../AbstractBlockCreatorTest.java | 4 +- .../AbstractBlockTransactionSelectorTest.java | 36 +- .../blockcreation/BlockMinerTest.java | 2 +- ...FeeMarketBlockTransactionSelectorTest.java | 14 +- .../blockcreation/PoWBlockCreatorTest.java | 2 +- .../LayeredPendingTransactionsTest.java | 4 +- plugin-api/build.gradle | 2 +- .../data/TransactionSelectionResult.java | 4 +- .../txselection/TransactionSelector.java | 15 +- 25 files changed, 1054 insertions(+), 576 deletions(-) delete mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/BlockTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockSelectionContext.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/AbstractTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlobPriceTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/BlockSizeTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java create mode 100644 ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java 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: - * - *

- * - * 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); }