1515package org .hyperledger .besu .ethereum .blockcreation .txselection ;
1616
1717import static org .hyperledger .besu .plugin .data .TransactionSelectionResult .BLOCK_SELECTION_TIMEOUT ;
18+ import static org .hyperledger .besu .plugin .data .TransactionSelectionResult .BLOCK_SELECTION_TIMEOUT_INVALID_TX ;
1819import static org .hyperledger .besu .plugin .data .TransactionSelectionResult .INVALID_TX_EVALUATION_TOO_LONG ;
1920import static org .hyperledger .besu .plugin .data .TransactionSelectionResult .SELECTED ;
2021import static org .hyperledger .besu .plugin .data .TransactionSelectionResult .TX_EVALUATION_TOO_LONG ;
5253import org .hyperledger .besu .plugin .services .tracer .BlockAwareOperationTracer ;
5354import org .hyperledger .besu .plugin .services .txselection .PluginTransactionSelector ;
5455
56+ import java .time .Duration ;
5557import java .util .List ;
5658import java .util .concurrent .CancellationException ;
5759import java .util .concurrent .ExecutionException ;
60+ import java .util .concurrent .FutureTask ;
5861import java .util .concurrent .TimeUnit ;
5962import java .util .concurrent .TimeoutException ;
6063import java .util .concurrent .atomic .AtomicBoolean ;
@@ -97,11 +100,12 @@ public class BlockTransactionSelector {
97100 new TransactionSelectionResults ();
98101 private final List <AbstractTransactionSelector > transactionSelectors ;
99102 private final PluginTransactionSelector pluginTransactionSelector ;
100- private final BlockAwareOperationTracer pluginOperationTracer ;
103+ private final BlockAwareOperationTracer operationTracer ;
101104 private final EthScheduler ethScheduler ;
102105 private final AtomicBoolean isTimeout = new AtomicBoolean (false );
103106 private final long blockTxsSelectionMaxTime ;
104107 private WorldUpdater blockWorldStateUpdater ;
108+ private volatile TransactionEvaluationContext currTxEvaluationContext ;
105109
106110 public BlockTransactionSelector (
107111 final MiningParameters miningParameters ,
@@ -139,7 +143,8 @@ public BlockTransactionSelector(
139143 transactionPool );
140144 transactionSelectors = createTransactionSelectors (blockSelectionContext );
141145 this .pluginTransactionSelector = pluginTransactionSelector ;
142- this .pluginOperationTracer = pluginTransactionSelector .getOperationTracer ();
146+ this .operationTracer =
147+ new InterruptibleOperationTracer (pluginTransactionSelector .getOperationTracer ());
143148 blockWorldStateUpdater = worldState .updater ();
144149 blockTxsSelectionMaxTime = miningParameters .getBlockTxsSelectionMaxTime ();
145150 }
@@ -178,15 +183,17 @@ public TransactionSelectionResults buildTransactionListForBlock() {
178183 }
179184
180185 private void timeLimitedSelection () {
181- final var txSelection =
182- ethScheduler . scheduleBlockCreationTask (
186+ final var txSelectionTask =
187+ new FutureTask < Void > (
183188 () ->
184189 blockSelectionContext
185190 .transactionPool ()
186- .selectTransactions (this ::evaluateTransaction ));
191+ .selectTransactions (this ::evaluateTransaction ),
192+ null );
193+ ethScheduler .scheduleBlockCreationTask (txSelectionTask );
187194
188195 try {
189- txSelection .get (blockTxsSelectionMaxTime , TimeUnit .MILLISECONDS );
196+ txSelectionTask .get (blockTxsSelectionMaxTime , TimeUnit .MILLISECONDS );
190197 } catch (InterruptedException | ExecutionException e ) {
191198 if (isCancelled .get ()) {
192199 throw new CancellationException ("Cancelled during transaction selection" );
@@ -197,6 +204,9 @@ private void timeLimitedSelection() {
197204 synchronized (isTimeout ) {
198205 isTimeout .set (true );
199206 }
207+
208+ cancelEvaluatingTxWithGraceTime (txSelectionTask );
209+
200210 LOG .warn (
201211 "Interrupting the selection of transactions for block inclusion as it exceeds the maximum configured duration of "
202212 + blockTxsSelectionMaxTime
@@ -205,6 +215,40 @@ private void timeLimitedSelection() {
205215 }
206216 }
207217
218+ private void cancelEvaluatingTxWithGraceTime (final FutureTask <Void > txSelectionTask ) {
219+ final long elapsedTime =
220+ currTxEvaluationContext .getEvaluationTimer ().elapsed (TimeUnit .MILLISECONDS );
221+ // adding 100ms so we are sure it take strictly more than the block selection max time
222+ final long txRemainingTime = (blockTxsSelectionMaxTime - elapsedTime ) + 100 ;
223+
224+ LOG .atDebug ()
225+ .setMessage (
226+ "Transaction {} is processing for {}ms, giving it {}ms grace time, before considering it taking too much time to execute" )
227+ .addArgument (currTxEvaluationContext .getPendingTransaction ()::toTraceLog )
228+ .addArgument (elapsedTime )
229+ .addArgument (txRemainingTime )
230+ .log ();
231+
232+ ethScheduler .scheduleFutureTask (
233+ () -> {
234+ if (!txSelectionTask .isDone ()) {
235+ LOG .atDebug ()
236+ .setMessage (
237+ "Transaction {} is still processing after the grace time, total processing time {}ms,"
238+ + " greater than max block selection time of {}ms, forcing an interrupt" )
239+ .addArgument (currTxEvaluationContext .getPendingTransaction ()::toTraceLog )
240+ .addArgument (
241+ () ->
242+ currTxEvaluationContext .getEvaluationTimer ().elapsed (TimeUnit .MILLISECONDS ))
243+ .addArgument (blockTxsSelectionMaxTime )
244+ .log ();
245+
246+ txSelectionTask .cancel (true );
247+ }
248+ },
249+ Duration .ofMillis (txRemainingTime ));
250+ }
251+
208252 /**
209253 * Evaluates a list of transactions and updates the selection results accordingly. If a
210254 * transaction is not selected during the evaluation, it is updated as not selected in the
@@ -236,6 +280,7 @@ private TransactionSelectionResult evaluateTransaction(
236280
237281 final TransactionEvaluationContext evaluationContext =
238282 createTransactionEvaluationContext (pendingTransaction );
283+ currTxEvaluationContext = evaluationContext ;
239284
240285 TransactionSelectionResult selectionResult = evaluatePreProcessing (evaluationContext );
241286 if (!selectionResult .selected ()) {
@@ -337,7 +382,7 @@ private TransactionProcessingResult processTransaction(
337382 blockSelectionContext .pendingBlockHeader (),
338383 pendingTransaction .getTransaction (),
339384 blockSelectionContext .miningBeneficiary (),
340- pluginOperationTracer ,
385+ operationTracer ,
341386 blockHashLookup ,
342387 false ,
343388 TransactionValidationParams .mining (),
@@ -422,14 +467,10 @@ private TransactionSelectionResult handleTransactionNotSelected(
422467 final var pendingTransaction = evaluationContext .getPendingTransaction ();
423468
424469 // check if this tx took too much to evaluate, and in case it was invalid remove it from the
425- // pool, otherwise penalize it.
470+ // pool, otherwise penalize it. Not synchronized since there is no state change here.
426471 final TransactionSelectionResult actualResult =
427472 isTimeout .get ()
428- ? transactionTookTooLong (evaluationContext , selectionResult )
429- ? selectionResult .discard ()
430- ? INVALID_TX_EVALUATION_TOO_LONG
431- : TX_EVALUATION_TOO_LONG
432- : BLOCK_SELECTION_TIMEOUT
473+ ? rewriteSelectionResultForTimeout (evaluationContext , selectionResult )
433474 : selectionResult ;
434475
435476 transactionSelectionResults .updateNotSelected (evaluationContext .getTransaction (), actualResult );
@@ -446,6 +487,34 @@ private TransactionSelectionResult handleTransactionNotSelected(
446487 return actualResult ;
447488 }
448489
490+ /**
491+ * In case of a block creation timeout, we rewrite the selection result, so we can easily spot
492+ * what happened looking at the transaction selection results.
493+ *
494+ * @param evaluationContext The current selection session data.
495+ * @param selectionResult The result of the transaction selection process.
496+ * @return the rewritten selection result
497+ */
498+ private TransactionSelectionResult rewriteSelectionResultForTimeout (
499+ final TransactionEvaluationContext evaluationContext ,
500+ final TransactionSelectionResult selectionResult ) {
501+
502+ if (transactionTookTooLong (evaluationContext , selectionResult )) {
503+ return selectionResult .discard () ? INVALID_TX_EVALUATION_TOO_LONG : TX_EVALUATION_TOO_LONG ;
504+ }
505+
506+ return selectionResult .discard () ? BLOCK_SELECTION_TIMEOUT_INVALID_TX : BLOCK_SELECTION_TIMEOUT ;
507+ }
508+
509+ /**
510+ * Check if the evaluation of this tx took more than the block creation max time, because if true
511+ * we want to penalize it. We penalize it, instead of directly removing, because it could happen
512+ * that the tx will evaluate in time next time. Invalid txs are always removed.
513+ *
514+ * @param evaluationContext The current selection session data.
515+ * @param selectionResult The result of the transaction selection process.
516+ * @return true if the evaluation of this tx took more than the block creation max time
517+ */
449518 private boolean transactionTookTooLong (
450519 final TransactionEvaluationContext evaluationContext ,
451520 final TransactionSelectionResult selectionResult ) {
0 commit comments