diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6b80cded..a0b2387fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Implement debug_traceCall [#5885](https://github.com/hyperledger/besu/pull/5885) - Transactions that takes too long to evaluate, during block creation, are dropped from the txpool [#6163](https://github.com/hyperledger/besu/pull/6163) - New option `tx-pool-min-gas-price` to set a lower bound when accepting txs to the pool [#6098](https://github.com/hyperledger/besu/pull/6098) +- Allow a transaction selection plugin to specify custom selection results [#6190](https://github.com/hyperledger/besu/pull/6190) ## 23.10.2 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 index fc8ee0bd6c..4a0707e76f 100644 --- 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 @@ -57,6 +57,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; +import com.google.common.base.Stopwatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -227,11 +228,11 @@ public class BlockTransactionSelector { final PendingTransaction pendingTransaction) { checkCancellation(); - final long evaluationStartedAt = System.currentTimeMillis(); + final Stopwatch evaluationTimer = Stopwatch.createStarted(); TransactionSelectionResult selectionResult = evaluatePreProcessing(pendingTransaction); if (!selectionResult.selected()) { - return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationStartedAt); + return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer); } final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater(); @@ -243,13 +244,10 @@ public class BlockTransactionSelector { if (postProcessingSelectionResult.selected()) { return handleTransactionSelected( - pendingTransaction, processingResult, txWorldStateUpdater, evaluationStartedAt); + pendingTransaction, processingResult, txWorldStateUpdater, evaluationTimer); } return handleTransactionNotSelected( - pendingTransaction, - postProcessingSelectionResult, - txWorldStateUpdater, - evaluationStartedAt); + pendingTransaction, postProcessingSelectionResult, txWorldStateUpdater, evaluationTimer); } /** @@ -333,14 +331,14 @@ public class BlockTransactionSelector { * @param pendingTransaction The pending transaction. * @param processingResult The result of the transaction processing. * @param txWorldStateUpdater The world state updater. - * @param evaluationStartedAt when the evaluation of this tx started + * @param evaluationTimer tracks the evaluation elapsed time * @return The result of the transaction selection process. */ private TransactionSelectionResult handleTransactionSelected( final PendingTransaction pendingTransaction, final TransactionProcessingResult processingResult, final WorldUpdater txWorldStateUpdater, - final long evaluationStartedAt) { + final Stopwatch evaluationTimer) { final Transaction transaction = pendingTransaction.getTransaction(); final long gasUsedByTransaction = @@ -369,20 +367,19 @@ public class BlockTransactionSelector { } } - final long evaluationTime = System.currentTimeMillis() - evaluationStartedAt; if (tooLate) { // even if this tx passed all the checks, it is too late to include it in this block, // so we need to treat it as not selected // check if this tx took too much to evaluate, and in case remove it from the pool final TransactionSelectionResult timeoutSelectionResult; - if (evaluationTime > blockTxsSelectionMaxTime) { + if (evaluationTimer.elapsed(TimeUnit.MILLISECONDS) > blockTxsSelectionMaxTime) { LOG.atWarn() .setMessage( - "Transaction {} is too late for inclusion, evaluated in {}ms that is over the max limit of {}" + "Transaction {} is too late for inclusion, evaluated in {} that is over the max limit of {}ms" + ", removing it from the pool") .addArgument(transaction::toTraceLog) - .addArgument(evaluationTime) + .addArgument(evaluationTimer) .addArgument(blockTxsSelectionMaxTime) .log(); timeoutSelectionResult = TX_EVALUATION_TOO_LONG; @@ -390,7 +387,7 @@ public class BlockTransactionSelector { LOG.atTrace() .setMessage("Transaction {} is too late for inclusion") .addArgument(transaction::toTraceLog) - .addArgument(evaluationTime) + .addArgument(evaluationTimer) .log(); timeoutSelectionResult = BLOCK_SELECTION_TIMEOUT; } @@ -398,15 +395,15 @@ public class BlockTransactionSelector { // do not rely on the presence of this result, since by the time it is added, the code // reading it could have been already executed by another thread return handleTransactionNotSelected( - pendingTransaction, timeoutSelectionResult, txWorldStateUpdater, evaluationStartedAt); + pendingTransaction, timeoutSelectionResult, txWorldStateUpdater, evaluationTimer); } pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult); blockWorldStateUpdater = worldState.updater(); LOG.atTrace() - .setMessage("Selected {} for block creation, evaluated in {}ms") + .setMessage("Selected {} for block creation, evaluated in {}") .addArgument(transaction::toTraceLog) - .addArgument(evaluationTime) + .addArgument(evaluationTimer) .log(); return SELECTED; } @@ -418,22 +415,22 @@ public class BlockTransactionSelector { * * @param pendingTransaction The unselected pending transaction. * @param selectionResult The result of the transaction selection process. - * @param evaluationStartedAt when the evaluation of this tx started + * @param evaluationTimer tracks the evaluation elapsed time * @return The result of the transaction selection process. */ private TransactionSelectionResult handleTransactionNotSelected( final PendingTransaction pendingTransaction, final TransactionSelectionResult selectionResult, - final long evaluationStartedAt) { + final Stopwatch evaluationTimer) { transactionSelectionResults.updateNotSelected( pendingTransaction.getTransaction(), selectionResult); pluginTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult); LOG.atTrace() - .setMessage("Not selected {} for block creation with result {}, evaluated in {}ms") + .setMessage("Not selected {} for block creation with result {}, evaluated in {}") .addArgument(pendingTransaction::toTraceLog) .addArgument(selectionResult) - .addArgument(() -> System.currentTimeMillis() - evaluationStartedAt) + .addArgument(evaluationTimer) .log(); return selectionResult; @@ -443,9 +440,9 @@ public class BlockTransactionSelector { final PendingTransaction pendingTransaction, final TransactionSelectionResult selectionResult, final WorldUpdater txWorldStateUpdater, - final long evaluationStartedAt) { + final Stopwatch evaluationTimer) { txWorldStateUpdater.revert(); - return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationStartedAt); + return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer); } private void checkCancellation() { 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 index 848789d996..dcbd871aae 100644 --- 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 @@ -111,7 +111,7 @@ public class TransactionSelectionResults { "Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]", selectedTransactions.size() + notSelectedTxs.size(), selectedTransactions.size(), - notSelectedStats.size(), + notSelectedTxs.size(), notSelectedStats.entrySet().stream() .filter(e -> e.getKey().discard()) .map(Map.Entry::getValue) 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 index a38fd4a0a4..defd75cb77 100644 --- 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 @@ -89,8 +89,9 @@ public class PriceTransactionSelector extends AbstractTransactionSelector { .setMessage( "Current gas price of {} is {} and lower than the configured minimum {}, skipping") .addArgument(pendingTransaction::toTraceLog) - .addArgument(transactionGasPriceInBlock) - .addArgument(context.miningParameters()::getMinTransactionGasPrice) + .addArgument(transactionGasPriceInBlock::toHumanReadableString) + .addArgument( + context.miningParameters().getMinTransactionGasPrice()::toHumanReadableString) .log(); return true; } 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 35bfd3754b..4e7b3eae5e 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 @@ -583,9 +583,9 @@ public abstract class AbstractBlockTransactionSelectorTest { public TransactionSelectionResult evaluateTransactionPreProcessing( final PendingTransaction pendingTransaction) { if (pendingTransaction.getTransaction().equals(notSelectedTransient)) - return TransactionSelectionResult.invalidTransient("transient"); + return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT; if (pendingTransaction.getTransaction().equals(notSelectedInvalid)) - return TransactionSelectionResult.invalid("invalid"); + return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID; return SELECTED; } @@ -618,8 +618,10 @@ public abstract class AbstractBlockTransactionSelectorTest { assertThat(transactionSelectionResults.getSelectedTransactions()).containsOnly(selected); assertThat(transactionSelectionResults.getNotSelectedTransactions()) .containsOnly( - entry(notSelectedTransient, TransactionSelectionResult.invalidTransient("transient")), - entry(notSelectedInvalid, TransactionSelectionResult.invalid("invalid"))); + entry( + notSelectedTransient, + PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT), + entry(notSelectedInvalid, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID)); } @Test @@ -654,7 +656,7 @@ public abstract class AbstractBlockTransactionSelectorTest { processingResult) { // the transaction with max gas +1 should fail if (processingResult.getEstimateGasUsedByTransaction() > maxGasUsedByTransaction) { - return TransactionSelectionResult.invalidTransient("Invalid"); + return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT; } return SELECTED; } @@ -678,7 +680,8 @@ public abstract class AbstractBlockTransactionSelectorTest { assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected, selected3); assertThat(transactionSelectionResults.getNotSelectedTransactions()) - .containsOnly(entry(notSelected, TransactionSelectionResult.invalidTransient("Invalid"))); + .containsOnly( + entry(notSelected, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT)); } @Test @@ -1189,4 +1192,48 @@ public abstract class AbstractBlockTransactionSelectorTest { .build()) .build(); } + + private static class PluginTransactionSelectionResult extends TransactionSelectionResult { + private enum PluginStatus implements Status { + PLUGIN_INVALID(false, true), + PLUGIN_INVALID_TRANSIENT(false, false); + + private final boolean stop; + private final boolean discard; + + PluginStatus(final boolean stop, final boolean discard) { + this.stop = stop; + this.discard = discard; + } + + @Override + public boolean stop() { + return stop; + } + + @Override + public boolean discard() { + return discard; + } + } + + public static final TransactionSelectionResult GENERIC_PLUGIN_INVALID_TRANSIENT = + invalidTransient("GENERIC_PLUGIN_INVALID_TRANSIENT"); + + public static final TransactionSelectionResult GENERIC_PLUGIN_INVALID = + invalid("GENERIC_PLUGIN_INVALID"); + + private PluginTransactionSelectionResult(final Status status, final String invalidReason) { + super(status, invalidReason); + } + + public static TransactionSelectionResult invalidTransient(final String invalidReason) { + return new PluginTransactionSelectionResult( + PluginStatus.PLUGIN_INVALID_TRANSIENT, invalidReason); + } + + public static TransactionSelectionResult invalid(final String invalidReason) { + return new PluginTransactionSelectionResult(PluginStatus.PLUGIN_INVALID, invalidReason); + } + } } diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index 493cd74459..4ce23dfe35 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 = 'gKRXd2Ow7wYKSgeGrDMRj0+2LdCzjOhLx8UEno9btGw=' + knownHash = 'nB1LhUpMWYFQpBdNJ/3Q79c8kLgUgPmEFzlRMlLUl1Y=' } 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 2bbf04649b..5c037983d7 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 @@ -24,7 +24,34 @@ import java.util.Optional; */ public class TransactionSelectionResult { - private enum Status { + /** + * Represent the status of a transaction selection result. Plugin can extend this to implement its + * own statuses. + */ + protected interface Status { + /** + * Should the selection process be stopped? + * + * @return true if the selection process needs to be stopped + */ + boolean stop(); + + /** + * Should the current transaction be removed from the txpool? + * + * @return yes if the transaction should be removed from the txpool + */ + boolean discard(); + + /** + * Name of this status + * + * @return the name + */ + String name(); + } + + private enum BaseStatus implements Status { SELECTED, BLOCK_FULL(true, false), BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false), @@ -36,12 +63,12 @@ public class TransactionSelectionResult { private final boolean stop; private final boolean discard; - Status() { + BaseStatus() { this.stop = false; this.discard = false; } - Status(final boolean stop, final boolean discard) { + BaseStatus(final boolean stop, final boolean discard) { this.stop = stop; this.discard = discard; } @@ -50,26 +77,36 @@ public class TransactionSelectionResult { public String toString() { return name() + " (stop=" + stop + ", discard=" + discard + ")"; } + + @Override + public boolean stop() { + return stop; + } + + @Override + public boolean discard() { + return discard; + } } /** The transaction has been selected to be included in the new block */ public static final TransactionSelectionResult SELECTED = - new TransactionSelectionResult(Status.SELECTED); + new TransactionSelectionResult(BaseStatus.SELECTED); /** The transaction has not been selected since the block is full. */ public static final TransactionSelectionResult BLOCK_FULL = - new TransactionSelectionResult(Status.BLOCK_FULL); + new TransactionSelectionResult(BaseStatus.BLOCK_FULL); /** There was no more time to add transaction to the block */ public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT = - new TransactionSelectionResult(Status.BLOCK_SELECTION_TIMEOUT); + new TransactionSelectionResult(BaseStatus.BLOCK_SELECTION_TIMEOUT); /** Transaction took too much to evaluate */ public static final TransactionSelectionResult TX_EVALUATION_TOO_LONG = - new TransactionSelectionResult(Status.TX_EVALUATION_TOO_LONG); + new TransactionSelectionResult(BaseStatus.TX_EVALUATION_TOO_LONG); /** * The transaction has not been selected since too large and the occupancy of the block is enough * to stop the selection. */ public static final TransactionSelectionResult BLOCK_OCCUPANCY_ABOVE_THRESHOLD = - new TransactionSelectionResult(Status.BLOCK_OCCUPANCY_ABOVE_THRESHOLD); + new TransactionSelectionResult(BaseStatus.BLOCK_OCCUPANCY_ABOVE_THRESHOLD); /** * The transaction has not been selected since its gas limit is greater than the block remaining * gas, but the selection should continue. @@ -99,11 +136,22 @@ public class TransactionSelectionResult { private final Status status; private final Optional maybeInvalidReason; - private TransactionSelectionResult(final Status status) { + /** + * Create a new transaction selection result with the passed status + * + * @param status the selection result status + */ + protected TransactionSelectionResult(final Status status) { this(status, null); } - private TransactionSelectionResult(final Status status, final String invalidReason) { + /** + * Create a new transaction selection result with the passed status and invalid reason + * + * @param status the selection result status + * @param invalidReason string with a custom invalid reason + */ + protected TransactionSelectionResult(final Status status, final String invalidReason) { this.status = status; this.maybeInvalidReason = Optional.ofNullable(invalidReason); } @@ -116,7 +164,7 @@ public class TransactionSelectionResult { * @return the selection result */ public static TransactionSelectionResult invalidTransient(final String invalidReason) { - return new TransactionSelectionResult(Status.INVALID_TRANSIENT, invalidReason); + return new TransactionSelectionResult(BaseStatus.INVALID_TRANSIENT, invalidReason); } /** @@ -127,7 +175,7 @@ public class TransactionSelectionResult { * @return the selection result */ public static TransactionSelectionResult invalid(final String invalidReason) { - return new TransactionSelectionResult(Status.INVALID, invalidReason); + return new TransactionSelectionResult(BaseStatus.INVALID, invalidReason); } /** @@ -136,7 +184,7 @@ public class TransactionSelectionResult { * @return true if the selection process should stop, false otherwise */ public boolean stop() { - return status.stop; + return status.stop(); } /** @@ -146,7 +194,7 @@ public class TransactionSelectionResult { * otherwise */ public boolean discard() { - return status.discard; + return status.discard(); } /** @@ -155,7 +203,7 @@ public class TransactionSelectionResult { * @return true if the candidate transaction is included in the new block, false otherwise */ public boolean selected() { - return Status.SELECTED.equals(status); + return BaseStatus.SELECTED.equals(status); } /**