Skip to content

Commit c8bdf92

Browse files
committed
Interrupt candidate tx execution on block creation timeout
Signed-off-by: Fabio Di Fabio <fabio.difabio@consensys.net>
1 parent 682daa3 commit c8bdf92

File tree

9 files changed

+298
-36
lines changed

9 files changed

+298
-36
lines changed

CHANGELOG.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Changelog
22

33
## [Unreleased]
4-
- Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647)
5-
64

75
### Upcoming Breaking Changes
86
- k8s (KUBERNETES) Nat method is now deprecated and will be removed in a future release
@@ -14,7 +12,8 @@
1412
- Remove privacy test classes support [#7569](https://github.com/hyperledger/besu/pull/7569)
1513
- Add Blob Transaction Metrics [#7622](https://github.com/hyperledger/besu/pull/7622)
1614
- Implemented support for emptyBlockPeriodSeconds in QBFT [#6965](https://github.com/hyperledger/besu/pull/6965)
17-
15+
- Add configuration of Consolidation Request Contract Address via genesis configuration [#7647](https://github.com/hyperledger/besu/pull/7647)
16+
- Interrupt pending transaction processing on block creation timeout [#7673](https://github.com/hyperledger/besu/pull/7673)
1817

1918
### Bug fixes
2019
- Fix mounted data path directory permissions for besu user [#7575](https://github.com/hyperledger/besu/pull/7575)

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/BlockTransactionSelector.java

Lines changed: 82 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package org.hyperledger.besu.ethereum.blockcreation.txselection;
1616

1717
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT;
18+
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.BLOCK_SELECTION_TIMEOUT_INVALID_TX;
1819
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.INVALID_TX_EVALUATION_TOO_LONG;
1920
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.SELECTED;
2021
import static org.hyperledger.besu.plugin.data.TransactionSelectionResult.TX_EVALUATION_TOO_LONG;
@@ -52,9 +53,11 @@
5253
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
5354
import org.hyperledger.besu.plugin.services.txselection.PluginTransactionSelector;
5455

56+
import java.time.Duration;
5557
import java.util.List;
5658
import java.util.concurrent.CancellationException;
5759
import java.util.concurrent.ExecutionException;
60+
import java.util.concurrent.FutureTask;
5861
import java.util.concurrent.TimeUnit;
5962
import java.util.concurrent.TimeoutException;
6063
import 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) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright contributors to Hyperledger Besu.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
5+
* the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
11+
* specific language governing permissions and limitations under the License.
12+
*
13+
* SPDX-License-Identifier: Apache-2.0
14+
*/
15+
package org.hyperledger.besu.ethereum.blockcreation.txselection;
16+
17+
import org.hyperledger.besu.datatypes.Address;
18+
import org.hyperledger.besu.datatypes.Transaction;
19+
import org.hyperledger.besu.datatypes.Wei;
20+
import org.hyperledger.besu.evm.frame.ExceptionalHaltReason;
21+
import org.hyperledger.besu.evm.frame.MessageFrame;
22+
import org.hyperledger.besu.evm.log.Log;
23+
import org.hyperledger.besu.evm.operation.Operation;
24+
import org.hyperledger.besu.evm.worldstate.WorldView;
25+
import org.hyperledger.besu.plugin.data.BlockBody;
26+
import org.hyperledger.besu.plugin.data.BlockHeader;
27+
import org.hyperledger.besu.plugin.data.ProcessableBlockHeader;
28+
import org.hyperledger.besu.plugin.services.tracer.BlockAwareOperationTracer;
29+
30+
import java.util.List;
31+
import java.util.Optional;
32+
import java.util.Set;
33+
34+
import org.apache.tuweni.bytes.Bytes;
35+
36+
public class InterruptibleOperationTracer implements BlockAwareOperationTracer {
37+
private final BlockAwareOperationTracer delegate;
38+
39+
public InterruptibleOperationTracer(final BlockAwareOperationTracer delegate) {
40+
this.delegate = delegate;
41+
}
42+
43+
@Override
44+
public void traceStartBlock(final BlockHeader blockHeader, final BlockBody blockBody) {
45+
delegate.traceStartBlock(blockHeader, blockBody);
46+
}
47+
48+
@Override
49+
public void traceEndBlock(final BlockHeader blockHeader, final BlockBody blockBody) {
50+
delegate.traceEndBlock(blockHeader, blockBody);
51+
}
52+
53+
@Override
54+
public void traceStartBlock(final ProcessableBlockHeader processableBlockHeader) {
55+
delegate.traceStartBlock(processableBlockHeader);
56+
}
57+
58+
@Override
59+
public boolean isExtendedTracing() {
60+
return delegate.isExtendedTracing();
61+
}
62+
63+
@Override
64+
public void tracePreExecution(final MessageFrame frame) {
65+
checkInterrupt();
66+
delegate.tracePreExecution(frame);
67+
}
68+
69+
@Override
70+
public void tracePostExecution(
71+
final MessageFrame frame, final Operation.OperationResult operationResult) {
72+
checkInterrupt();
73+
delegate.tracePostExecution(frame, operationResult);
74+
}
75+
76+
@Override
77+
public void tracePrecompileCall(
78+
final MessageFrame frame, final long gasRequirement, final Bytes output) {
79+
checkInterrupt();
80+
delegate.tracePrecompileCall(frame, gasRequirement, output);
81+
}
82+
83+
@Override
84+
public void traceAccountCreationResult(
85+
final MessageFrame frame, final Optional<ExceptionalHaltReason> haltReason) {
86+
checkInterrupt();
87+
delegate.traceAccountCreationResult(frame, haltReason);
88+
}
89+
90+
@Override
91+
public void tracePrepareTransaction(final WorldView worldView, final Transaction transaction) {
92+
delegate.tracePrepareTransaction(worldView, transaction);
93+
}
94+
95+
@Override
96+
public void traceStartTransaction(final WorldView worldView, final Transaction transaction) {
97+
delegate.traceStartTransaction(worldView, transaction);
98+
}
99+
100+
@Override
101+
public void traceBeforeRewardTransaction(
102+
final WorldView worldView, final Transaction tx, final Wei miningReward) {
103+
delegate.traceBeforeRewardTransaction(worldView, tx, miningReward);
104+
}
105+
106+
@Override
107+
public void traceEndTransaction(
108+
final WorldView worldView,
109+
final Transaction tx,
110+
final boolean status,
111+
final Bytes output,
112+
final List<Log> logs,
113+
final long gasUsed,
114+
final Set<Address> selfDestructs,
115+
final long timeNs) {
116+
delegate.traceEndTransaction(
117+
worldView, tx, status, output, logs, gasUsed, selfDestructs, timeNs);
118+
}
119+
120+
@Override
121+
public void traceContextEnter(final MessageFrame frame) {
122+
checkInterrupt();
123+
delegate.traceContextEnter(frame);
124+
}
125+
126+
@Override
127+
public void traceContextReEnter(final MessageFrame frame) {
128+
checkInterrupt();
129+
delegate.traceContextReEnter(frame);
130+
}
131+
132+
@Override
133+
public void traceContextExit(final MessageFrame frame) {
134+
checkInterrupt();
135+
delegate.traceContextExit(frame);
136+
}
137+
138+
private void checkInterrupt() {
139+
if (Thread.interrupted()) {
140+
throw new RuntimeException(new InterruptedException("Transaction execution interrupted"));
141+
}
142+
}
143+
}

ethereum/blockcreation/src/main/java/org/hyperledger/besu/ethereum/blockcreation/txselection/selectors/ProcessingResultTransactionSelector.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ private TransactionSelectionResult transactionSelectionResultForInvalidResult(
112112
private boolean isTransientValidationError(final TransactionInvalidReason invalidReason) {
113113
return invalidReason.equals(TransactionInvalidReason.UPFRONT_COST_EXCEEDS_BALANCE)
114114
|| invalidReason.equals(TransactionInvalidReason.GAS_PRICE_BELOW_CURRENT_BASE_FEE)
115-
|| invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH);
115+
|| invalidReason.equals(TransactionInvalidReason.NONCE_TOO_HIGH)
116+
|| invalidReason.equals(TransactionInvalidReason.EXECUTION_INTERRUPTED);
116117
}
117118
}

0 commit comments

Comments
 (0)