Skip to content

Commit

Permalink
Allow a transaction selection plugin to specify custom selection resu…
Browse files Browse the repository at this point in the history
…lts (hyperledger#6190)

Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
Signed-off-by: Justin Florentine <justin+github@florentine.us>
  • Loading branch information
fab-10 authored and jflo committed Dec 4, 2023
1 parent a85d4fe commit 5194976
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 48 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
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;

Expand Down Expand Up @@ -227,11 +228,11 @@ private TransactionSelectionResult evaluateTransaction(
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();
Expand All @@ -243,13 +244,10 @@ private TransactionSelectionResult evaluateTransaction(

if (postProcessingSelectionResult.selected()) {
return handleTransactionSelected(
pendingTransaction, processingResult, txWorldStateUpdater, evaluationStartedAt);
pendingTransaction, processingResult, txWorldStateUpdater, evaluationTimer);
}
return handleTransactionNotSelected(
pendingTransaction,
postProcessingSelectionResult,
txWorldStateUpdater,
evaluationStartedAt);
pendingTransaction, postProcessingSelectionResult, txWorldStateUpdater, evaluationTimer);
}

/**
Expand Down Expand Up @@ -333,14 +331,14 @@ private TransactionProcessingResult processTransaction(
* @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 =
Expand Down Expand Up @@ -369,44 +367,43 @@ private TransactionSelectionResult handleTransactionSelected(
}
}

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;
} else {
LOG.atTrace()
.setMessage("Transaction {} is too late for inclusion")
.addArgument(transaction::toTraceLog)
.addArgument(evaluationTime)
.addArgument(evaluationTimer)
.log();
timeoutSelectionResult = BLOCK_SELECTION_TIMEOUT;
}

// 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;
}
Expand All @@ -418,22 +415,22 @@ private TransactionSelectionResult handleTransactionSelected(
*
* @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;
Expand All @@ -443,9 +440,9 @@ private TransactionSelectionResult handleTransactionNotSelected(
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void logSelectionStats() {
"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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ private boolean transactionCurrentPriceBelowMin(final PendingTransaction pending
.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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,9 +583,9 @@ public void transactionSelectionPluginShouldWork_PreProcessing() {
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;
}

Expand Down Expand Up @@ -618,8 +618,10 @@ public TransactionSelectionResult evaluateTransactionPostProcessing(
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
Expand Down Expand Up @@ -654,7 +656,7 @@ public TransactionSelectionResult evaluateTransactionPostProcessing(
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;
}
Expand All @@ -678,7 +680,8 @@ public TransactionSelectionResult evaluateTransactionPostProcessing(

assertThat(transactionSelectionResults.getSelectedTransactions()).contains(selected, selected3);
assertThat(transactionSelectionResults.getNotSelectedTransactions())
.containsOnly(entry(notSelected, TransactionSelectionResult.invalidTransient("Invalid")));
.containsOnly(
entry(notSelected, PluginTransactionSelectionResult.GENERIC_PLUGIN_INVALID_TRANSIENT));
}

@Test
Expand Down Expand Up @@ -1189,4 +1192,48 @@ protected MiningParameters createMiningParameters(
.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);
}
}
}
2 changes: 1 addition & 1 deletion plugin-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
Loading

0 comments on commit 5194976

Please sign in to comment.