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,39 @@ 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+ final long txRemainingTime = blockTxsSelectionMaxTime - elapsedTime ;
222+
223+ LOG .atDebug ()
224+ .setMessage (
225+ "Transaction {} is processing for {}ms, giving it {}ms grace time, before considering it taking too much time to execute" )
226+ .addArgument (currTxEvaluationContext .getPendingTransaction ()::toTraceLog )
227+ .addArgument (elapsedTime )
228+ .addArgument (txRemainingTime )
229+ .log ();
230+
231+ ethScheduler .scheduleFutureTask (
232+ () -> {
233+ if (!txSelectionTask .isDone ()) {
234+ LOG .atDebug ()
235+ .setMessage (
236+ "Transaction {} is still processing after the grace time, total processing time {}ms,"
237+ + " greater than max block selection time of {}ms, forcing an interrupt" )
238+ .addArgument (currTxEvaluationContext .getPendingTransaction ()::toTraceLog )
239+ .addArgument (
240+ () ->
241+ currTxEvaluationContext .getEvaluationTimer ().elapsed (TimeUnit .MILLISECONDS ))
242+ .addArgument (blockTxsSelectionMaxTime )
243+ .log ();
244+
245+ txSelectionTask .cancel (true );
246+ }
247+ },
248+ Duration .ofMillis (txRemainingTime ));
249+ }
250+
208251 /**
209252 * Evaluates a list of transactions and updates the selection results accordingly. If a
210253 * transaction is not selected during the evaluation, it is updated as not selected in the
@@ -236,6 +279,7 @@ private TransactionSelectionResult evaluateTransaction(
236279
237280 final TransactionEvaluationContext evaluationContext =
238281 createTransactionEvaluationContext (pendingTransaction );
282+ currTxEvaluationContext = evaluationContext ;
239283
240284 TransactionSelectionResult selectionResult = evaluatePreProcessing (evaluationContext );
241285 if (!selectionResult .selected ()) {
@@ -337,7 +381,7 @@ private TransactionProcessingResult processTransaction(
337381 blockSelectionContext .pendingBlockHeader (),
338382 pendingTransaction .getTransaction (),
339383 blockSelectionContext .miningBeneficiary (),
340- pluginOperationTracer ,
384+ operationTracer ,
341385 blockHashLookup ,
342386 false ,
343387 TransactionValidationParams .mining (),
@@ -422,14 +466,16 @@ private TransactionSelectionResult handleTransactionNotSelected(
422466 final var pendingTransaction = evaluationContext .getPendingTransaction ();
423467
424468 // check if this tx took too much to evaluate, and in case it was invalid remove it from the
425- // pool, otherwise penalize it.
469+ // pool, otherwise penalize it. Not synchronized since there is no state change here.
426470 final TransactionSelectionResult actualResult =
427471 isTimeout .get ()
428472 ? transactionTookTooLong (evaluationContext , selectionResult )
429473 ? selectionResult .discard ()
430474 ? INVALID_TX_EVALUATION_TOO_LONG
431475 : TX_EVALUATION_TOO_LONG
432- : BLOCK_SELECTION_TIMEOUT
476+ : selectionResult .discard ()
477+ ? BLOCK_SELECTION_TIMEOUT_INVALID_TX
478+ : BLOCK_SELECTION_TIMEOUT
433479 : selectionResult ;
434480
435481 transactionSelectionResults .updateNotSelected (evaluationContext .getTransaction (), actualResult );
0 commit comments