Skip to content

Commit d47f1a1

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

File tree

10 files changed

+271
-32
lines changed

10 files changed

+271
-32
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: 55 additions & 9 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,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);
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)