Allow a transaction selection plugin to specify custom selection results (#6190)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
pull/6194/head
Fabio Di Fabio 1 year ago committed by GitHub
parent d94ea7758e
commit 569ef931ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      CHANGELOG.md
  2. 43
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java
  3. 2
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/TransactionSelectionResults.java
  4. 5
      ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/PriceTransactionSelector.java
  5. 59
      ethereum/blockcreation/src/test/java/org/hyperledger/besu/ethereum/blockcreation/AbstractBlockTransactionSelectorTest.java
  6. 2
      plugin-api/build.gradle
  7. 78
      plugin-api/src/main/java/org/hyperledger/besu/plugin/data/TransactionSelectionResult.java

@ -10,6 +10,7 @@
- Implement debug_traceCall [#5885](https://github.com/hyperledger/besu/pull/5885) - 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) - 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) - 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 ## 23.10.2

@ -57,6 +57,7 @@ import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier; import java.util.function.Supplier;
import com.google.common.base.Stopwatch;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -227,11 +228,11 @@ public class BlockTransactionSelector {
final PendingTransaction pendingTransaction) { final PendingTransaction pendingTransaction) {
checkCancellation(); checkCancellation();
final long evaluationStartedAt = System.currentTimeMillis(); final Stopwatch evaluationTimer = Stopwatch.createStarted();
TransactionSelectionResult selectionResult = evaluatePreProcessing(pendingTransaction); TransactionSelectionResult selectionResult = evaluatePreProcessing(pendingTransaction);
if (!selectionResult.selected()) { if (!selectionResult.selected()) {
return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationStartedAt); return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer);
} }
final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater(); final WorldUpdater txWorldStateUpdater = blockWorldStateUpdater.updater();
@ -243,13 +244,10 @@ public class BlockTransactionSelector {
if (postProcessingSelectionResult.selected()) { if (postProcessingSelectionResult.selected()) {
return handleTransactionSelected( return handleTransactionSelected(
pendingTransaction, processingResult, txWorldStateUpdater, evaluationStartedAt); pendingTransaction, processingResult, txWorldStateUpdater, evaluationTimer);
} }
return handleTransactionNotSelected( return handleTransactionNotSelected(
pendingTransaction, pendingTransaction, postProcessingSelectionResult, txWorldStateUpdater, evaluationTimer);
postProcessingSelectionResult,
txWorldStateUpdater,
evaluationStartedAt);
} }
/** /**
@ -333,14 +331,14 @@ public class BlockTransactionSelector {
* @param pendingTransaction The pending transaction. * @param pendingTransaction The pending transaction.
* @param processingResult The result of the transaction processing. * @param processingResult The result of the transaction processing.
* @param txWorldStateUpdater The world state updater. * @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. * @return The result of the transaction selection process.
*/ */
private TransactionSelectionResult handleTransactionSelected( private TransactionSelectionResult handleTransactionSelected(
final PendingTransaction pendingTransaction, final PendingTransaction pendingTransaction,
final TransactionProcessingResult processingResult, final TransactionProcessingResult processingResult,
final WorldUpdater txWorldStateUpdater, final WorldUpdater txWorldStateUpdater,
final long evaluationStartedAt) { final Stopwatch evaluationTimer) {
final Transaction transaction = pendingTransaction.getTransaction(); final Transaction transaction = pendingTransaction.getTransaction();
final long gasUsedByTransaction = final long gasUsedByTransaction =
@ -369,20 +367,19 @@ public class BlockTransactionSelector {
} }
} }
final long evaluationTime = System.currentTimeMillis() - evaluationStartedAt;
if (tooLate) { if (tooLate) {
// even if this tx passed all the checks, it is too late to include it in this block, // 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 // 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 // check if this tx took too much to evaluate, and in case remove it from the pool
final TransactionSelectionResult timeoutSelectionResult; final TransactionSelectionResult timeoutSelectionResult;
if (evaluationTime > blockTxsSelectionMaxTime) { if (evaluationTimer.elapsed(TimeUnit.MILLISECONDS) > blockTxsSelectionMaxTime) {
LOG.atWarn() LOG.atWarn()
.setMessage( .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") + ", removing it from the pool")
.addArgument(transaction::toTraceLog) .addArgument(transaction::toTraceLog)
.addArgument(evaluationTime) .addArgument(evaluationTimer)
.addArgument(blockTxsSelectionMaxTime) .addArgument(blockTxsSelectionMaxTime)
.log(); .log();
timeoutSelectionResult = TX_EVALUATION_TOO_LONG; timeoutSelectionResult = TX_EVALUATION_TOO_LONG;
@ -390,7 +387,7 @@ public class BlockTransactionSelector {
LOG.atTrace() LOG.atTrace()
.setMessage("Transaction {} is too late for inclusion") .setMessage("Transaction {} is too late for inclusion")
.addArgument(transaction::toTraceLog) .addArgument(transaction::toTraceLog)
.addArgument(evaluationTime) .addArgument(evaluationTimer)
.log(); .log();
timeoutSelectionResult = BLOCK_SELECTION_TIMEOUT; 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 // 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 // reading it could have been already executed by another thread
return handleTransactionNotSelected( return handleTransactionNotSelected(
pendingTransaction, timeoutSelectionResult, txWorldStateUpdater, evaluationStartedAt); pendingTransaction, timeoutSelectionResult, txWorldStateUpdater, evaluationTimer);
} }
pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult); pluginTransactionSelector.onTransactionSelected(pendingTransaction, processingResult);
blockWorldStateUpdater = worldState.updater(); blockWorldStateUpdater = worldState.updater();
LOG.atTrace() LOG.atTrace()
.setMessage("Selected {} for block creation, evaluated in {}ms") .setMessage("Selected {} for block creation, evaluated in {}")
.addArgument(transaction::toTraceLog) .addArgument(transaction::toTraceLog)
.addArgument(evaluationTime) .addArgument(evaluationTimer)
.log(); .log();
return SELECTED; return SELECTED;
} }
@ -418,22 +415,22 @@ public class BlockTransactionSelector {
* *
* @param pendingTransaction The unselected pending transaction. * @param pendingTransaction The unselected pending transaction.
* @param selectionResult The result of the transaction selection process. * @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. * @return The result of the transaction selection process.
*/ */
private TransactionSelectionResult handleTransactionNotSelected( private TransactionSelectionResult handleTransactionNotSelected(
final PendingTransaction pendingTransaction, final PendingTransaction pendingTransaction,
final TransactionSelectionResult selectionResult, final TransactionSelectionResult selectionResult,
final long evaluationStartedAt) { final Stopwatch evaluationTimer) {
transactionSelectionResults.updateNotSelected( transactionSelectionResults.updateNotSelected(
pendingTransaction.getTransaction(), selectionResult); pendingTransaction.getTransaction(), selectionResult);
pluginTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult); pluginTransactionSelector.onTransactionNotSelected(pendingTransaction, selectionResult);
LOG.atTrace() 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(pendingTransaction::toTraceLog)
.addArgument(selectionResult) .addArgument(selectionResult)
.addArgument(() -> System.currentTimeMillis() - evaluationStartedAt) .addArgument(evaluationTimer)
.log(); .log();
return selectionResult; return selectionResult;
@ -443,9 +440,9 @@ public class BlockTransactionSelector {
final PendingTransaction pendingTransaction, final PendingTransaction pendingTransaction,
final TransactionSelectionResult selectionResult, final TransactionSelectionResult selectionResult,
final WorldUpdater txWorldStateUpdater, final WorldUpdater txWorldStateUpdater,
final long evaluationStartedAt) { final Stopwatch evaluationTimer) {
txWorldStateUpdater.revert(); txWorldStateUpdater.revert();
return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationStartedAt); return handleTransactionNotSelected(pendingTransaction, selectionResult, evaluationTimer);
} }
private void checkCancellation() { private void checkCancellation() {

@ -111,7 +111,7 @@ public class TransactionSelectionResults {
"Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]", "Selection stats: Totals[Evaluated={}, Selected={}, NotSelected={}, Discarded={}]; Detailed[{}]",
selectedTransactions.size() + notSelectedTxs.size(), selectedTransactions.size() + notSelectedTxs.size(),
selectedTransactions.size(), selectedTransactions.size(),
notSelectedStats.size(), notSelectedTxs.size(),
notSelectedStats.entrySet().stream() notSelectedStats.entrySet().stream()
.filter(e -> e.getKey().discard()) .filter(e -> e.getKey().discard())
.map(Map.Entry::getValue) .map(Map.Entry::getValue)

@ -89,8 +89,9 @@ public class PriceTransactionSelector extends AbstractTransactionSelector {
.setMessage( .setMessage(
"Current gas price of {} is {} and lower than the configured minimum {}, skipping") "Current gas price of {} is {} and lower than the configured minimum {}, skipping")
.addArgument(pendingTransaction::toTraceLog) .addArgument(pendingTransaction::toTraceLog)
.addArgument(transactionGasPriceInBlock) .addArgument(transactionGasPriceInBlock::toHumanReadableString)
.addArgument(context.miningParameters()::getMinTransactionGasPrice) .addArgument(
context.miningParameters().getMinTransactionGasPrice()::toHumanReadableString)
.log(); .log();
return true; return true;
} }

@ -583,9 +583,9 @@ public abstract class AbstractBlockTransactionSelectorTest {
public TransactionSelectionResult evaluateTransactionPreProcessing( public TransactionSelectionResult evaluateTransactionPreProcessing(
final PendingTransaction pendingTransaction) { final PendingTransaction pendingTransaction) {
if (pendingTransaction.getTransaction().equals(notSelectedTransient)) if (pendingTransaction.getTransaction().equals(notSelectedTransient))
return TransactionSelectionResult.invalidTransient("transient"); return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
if (pendingTransaction.getTransaction().equals(notSelectedInvalid)) if (pendingTransaction.getTransaction().equals(notSelectedInvalid))
return TransactionSelectionResult.invalid("invalid"); return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID;
return SELECTED; return SELECTED;
} }
@ -618,8 +618,10 @@ public abstract class AbstractBlockTransactionSelectorTest {
assertThat(transactionSelectionResults.getSelectedTransactions()).containsOnly(selected); assertThat(transactionSelectionResults.getSelectedTransactions()).containsOnly(selected);
assertThat(transactionSelectionResults.getNotSelectedTransactions()) assertThat(transactionSelectionResults.getNotSelectedTransactions())
.containsOnly( .containsOnly(
entry(notSelectedTransient, TransactionSelectionResult.invalidTransient("transient")), entry(
entry(notSelectedInvalid, TransactionSelectionResult.invalid("invalid"))); notSelectedTransient,
PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT),
entry(notSelectedInvalid, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID));
} }
@Test @Test
@ -654,7 +656,7 @@ public abstract class AbstractBlockTransactionSelectorTest {
processingResult) { processingResult) {
// the transaction with max gas +1 should fail // the transaction with max gas +1 should fail
if (processingResult.getEstimateGasUsedByTransaction() > maxGasUsedByTransaction) { if (processingResult.getEstimateGasUsedByTransaction() > maxGasUsedByTransaction) {
return TransactionSelectionResult.invalidTransient("Invalid"); return PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT;
} }
return SELECTED; return SELECTED;
} }
@ -678,7 +680,8 @@ public abstract class AbstractBlockTransactionSelectorTest {
assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected, selected3); assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected, selected3);
assertThat(transactionSelectionResults.getNotSelectedTransactions()) assertThat(transactionSelectionResults.getNotSelectedTransactions())
.containsOnly(entry(notSelected, TransactionSelectionResult.invalidTransient("Invalid"))); .containsOnly(
entry(notSelected, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT));
} }
@Test @Test
@ -1189,4 +1192,48 @@ public abstract class AbstractBlockTransactionSelectorTest {
.build()) .build())
.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);
}
}
} }

@ -69,7 +69,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) { tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought" description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files files = sourceSets.main.allJava.files
knownHash = 'gKRXd2Ow7wYKSgeGrDMRj0+2LdCzjOhLx8UEno9btGw=' knownHash = 'nB1LhUpMWYFQpBdNJ/3Q79c8kLgUgPmEFzlRMlLUl1Y='
} }
check.dependsOn('checkAPIChanges') check.dependsOn('checkAPIChanges')

@ -24,7 +24,34 @@ import java.util.Optional;
*/ */
public class TransactionSelectionResult { 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, SELECTED,
BLOCK_FULL(true, false), BLOCK_FULL(true, false),
BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false), BLOCK_OCCUPANCY_ABOVE_THRESHOLD(true, false),
@ -36,12 +63,12 @@ public class TransactionSelectionResult {
private final boolean stop; private final boolean stop;
private final boolean discard; private final boolean discard;
Status() { BaseStatus() {
this.stop = false; this.stop = false;
this.discard = false; this.discard = false;
} }
Status(final boolean stop, final boolean discard) { BaseStatus(final boolean stop, final boolean discard) {
this.stop = stop; this.stop = stop;
this.discard = discard; this.discard = discard;
} }
@ -50,26 +77,36 @@ public class TransactionSelectionResult {
public String toString() { public String toString() {
return name() + " (stop=" + stop + ", discard=" + discard + ")"; 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 */ /** The transaction has been selected to be included in the new block */
public static final TransactionSelectionResult SELECTED = public static final TransactionSelectionResult SELECTED =
new TransactionSelectionResult(Status.SELECTED); new TransactionSelectionResult(BaseStatus.SELECTED);
/** The transaction has not been selected since the block is full. */ /** The transaction has not been selected since the block is full. */
public static final TransactionSelectionResult BLOCK_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 */ /** There was no more time to add transaction to the block */
public static final TransactionSelectionResult BLOCK_SELECTION_TIMEOUT = 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 */ /** Transaction took too much to evaluate */
public static final TransactionSelectionResult TX_EVALUATION_TOO_LONG = 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 * The transaction has not been selected since too large and the occupancy of the block is enough
* to stop the selection. * to stop the selection.
*/ */
public static final TransactionSelectionResult BLOCK_OCCUPANCY_ABOVE_THRESHOLD = 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 * The transaction has not been selected since its gas limit is greater than the block remaining
* gas, but the selection should continue. * gas, but the selection should continue.
@ -99,11 +136,22 @@ public class TransactionSelectionResult {
private final Status status; private final Status status;
private final Optional<String> maybeInvalidReason; private final Optional<String> 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); 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.status = status;
this.maybeInvalidReason = Optional.ofNullable(invalidReason); this.maybeInvalidReason = Optional.ofNullable(invalidReason);
} }
@ -116,7 +164,7 @@ public class TransactionSelectionResult {
* @return the selection result * @return the selection result
*/ */
public static TransactionSelectionResult invalidTransient(final String invalidReason) { 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 * @return the selection result
*/ */
public static TransactionSelectionResult invalid(final String invalidReason) { 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 * @return true if the selection process should stop, false otherwise
*/ */
public boolean stop() { public boolean stop() {
return status.stop; return status.stop();
} }
/** /**
@ -146,7 +194,7 @@ public class TransactionSelectionResult {
* otherwise * otherwise
*/ */
public boolean discard() { 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 * @return true if the candidate transaction is included in the new block, false otherwise
*/ */
public boolean selected() { public boolean selected() {
return Status.SELECTED.equals(status); return BaseStatus.SELECTED.equals(status);
} }
/** /**

Loading…
Cancel
Save