Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
- Enforce EIP-7928 Block Access List item budget per transaction in both processing and mining [#10250](https://github.com/besu-eth/besu/pull/10250)
- Include `slotNumber` in `payloadIdentifier` generation [#10242](https://github.com/besu-eth/besu/pull/10242)
- Change Block Access List index encoding to `uint32` [#10279](https://github.com/besu-eth/besu/pull/10279)
- Add `limit` support to debug opcode tracing to stop capture after N EVM steps (`0` keeps the previous unlimited behavior) and reject negative values at the RPC parameter boundary [#10173](https://github.com/besu-eth/besu/pull/10173)

### Plugin API
- Publish Guava as an API dependency from `plugin-api` [#10248](https://github.com/besu-eth/besu/pull/10248)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ default boolean disableStack() {
return Boolean.TRUE.equals(disableStackNullable());
}

@JsonProperty(value = "limit")
@Nullable Integer limit();

@JsonProperty("tracer")
@JsonInclude(JsonInclude.Include.NON_NULL)
@Nullable String tracer();
Expand All @@ -93,6 +96,13 @@ default Set<String> opcodes() {
@JsonInclude(JsonInclude.Include.NON_NULL)
StateOverrideMap stateOverrides();

@Value.Check
default void validate() {
if (limit() != null && limit() < 0) {
throw new IllegalArgumentException("limit must be >= 0, got: " + limit());
}
}

/**
* Convert JSON-RPC parameters to a {@link TraceOptions} object.
*
Expand Down Expand Up @@ -122,6 +132,9 @@ default TraceOptions traceOptions() {
if (disableStackNullable() != null) {
builder.traceStack(!disableStack());
}
if (limit() != null) {
builder.limit(limit());
}
var opCodeTracerConfig = builder.traceOpcodes(opcodes()).build();

return new TraceOptions(tracerType, opCodeTracerConfig, tracerConfig(), stateOverrides());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.parameters;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.hyperledger.besu.ethereum.debug.TraceOptions;
import org.hyperledger.besu.evm.tracing.OpCodeTracerConfigBuilder.OpCodeTracerConfig;
Expand Down Expand Up @@ -131,4 +132,10 @@ public void nonOpcodeTracerShouldRespectExplicitEnableMemoryFalse() throws Excep
.describedAs("explicit enableMemory=false should be respected for callTracer")
.isFalse();
}

@Test
public void negativeLimitShouldFailDuringDeserialization() {
assertThatThrownBy(() -> MAPPER.readValue("{\"limit\": -1}", TransactionTraceParams.class))
.hasMessageContaining("limit must be >= 0, got: -1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ public class DebugOperationTracer extends AbstractDebugOperationTracer {
private TraceFrame lastFrame;

private Bytes inputData;
private int stepCount;
private boolean limitReached;

/**
* Creates the operation tracer.
Expand All @@ -57,16 +59,19 @@ public DebugOperationTracer(final OpCodeTracerConfig options, final boolean reco

@Override
protected void capturePreExecutionState(final MessageFrame frame) {
if (options.limit() > 0 && stepCount >= options.limit()) {
limitReached = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep this is better

return;
}
limitReached = false;
stepCount++;
if (lastFrame != null && frame.getDepth() > lastFrame.getDepth())
inputData = frame.getInputData().copy();
else inputData = frame.getInputData();
}

@Override
public void tracePostExecution(final MessageFrame frame, final OperationResult operationResult) {
if (!traceOpcode) {
return;
}
final Operation currentOperation = frame.getCurrentOperation();
final String opcode = currentOperation.getName();
final int opcodeNumber = (opcode != null) ? currentOperation.getOpcode() : Integer.MAX_VALUE;
Expand All @@ -89,6 +94,9 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o
TraceFrame.from(lastTraceFrame).setGasRemainingPostExecution(gasRemaining).build();
traceFrames.add(updatedLast);
}
if (limitReached || !traceOpcode) {
return;
}

final Optional<Map<UInt256, UInt256>> storage = captureStorage(frame);
final Optional<Map<Address, Wei>> maybeRefunds =
Expand Down Expand Up @@ -275,6 +283,8 @@ public List<TraceFrame> getTraceFrames() {
public void reset() {
traceFrames = new ArrayList<>();
lastFrame = null;
stepCount = 0;
limitReached = false;
}

public List<TraceFrame> copyTraceFrames() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,64 @@ void shouldHandleCallScenarioAndDepthChange() {
.isEqualTo(WORD_1);
}

@Test
void shouldCaptureAtMostLimitFrames() {
final OpCodeTracerConfig config =
OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT)
.traceStorage(false)
.traceMemory(false)
.traceStack(false)
.limit(2)
.build();
final DebugOperationTracer tracer = new DebugOperationTracer(config, false);
final MessageFrame frame = validMessageFrame();

for (int i = 0; i < 5; i++) {
traceFrame(frame, tracer, anOperation);
}

assertThat(tracer.getTraceFrames()).hasSize(2);
}

@Test
void shouldBackfillGasRemainingPostExecutionOnLastCapturedFrameAtLimit() {
final OpCodeTracerConfig config =
OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT)
.traceStorage(false)
.traceMemory(false)
.traceStack(false)
.limit(1)
.build();
final DebugOperationTracer tracer = new DebugOperationTracer(config, false);
final MessageFrame frame = validMessageFrame();

traceFrame(frame, tracer, anOperation);
traceFrame(frame, tracer, anOperation);

assertThat(tracer.getTraceFrames()).hasSize(1);
assertThat(tracer.getTraceFrames().get(0).getGasRemainingPostExecution())
.isNotEqualTo(OptionalLong.empty());
}

@Test
void shouldCaptureAllFramesWhenLimitIsZero() {
final OpCodeTracerConfig config =
OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT)
.traceStorage(false)
.traceMemory(false)
.traceStack(false)
.limit(0)
.build();
final DebugOperationTracer tracer = new DebugOperationTracer(config, false);
final MessageFrame frame = validMessageFrame();

for (int i = 0; i < 5; i++) {
traceFrame(frame, tracer, anOperation);
}

assertThat(tracer.getTraceFrames()).hasSize(5);
}

private TraceFrame traceFrame(final MessageFrame frame) {
return traceFrame(
frame,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public final class OpCodeTracerConfigBuilder {
private boolean traceReturnData;
private Set<String> traceOpcodes;
private boolean eip3155Strict;
private int limit;

/**
* Create an OpcodeTracerConfig builder from a previous built config.
Expand Down Expand Up @@ -57,6 +58,7 @@ private OpCodeTracerConfigBuilder(final OpCodeTracerConfig opCodeTracerConfig) {
this.traceReturnData = opCodeTracerConfig.traceReturnData();
this.traceOpcodes = opCodeTracerConfig.traceOpcodes();
this.eip3155Strict = opCodeTracerConfig.eip3155Strict();
this.limit = opCodeTracerConfig.limit();
}

/**
Expand Down Expand Up @@ -114,6 +116,18 @@ public OpCodeTracerConfigBuilder traceOpcodes(final Set<String> traceOpcodes) {
return this;
}

/**
* Set the maximum number of steps to trace. Zero means unlimited.
*
* @param n maximum number of steps, or 0 for unlimited
* @return the current builder
*/
public OpCodeTracerConfigBuilder limit(final int n) {
if (n < 0) throw new IllegalArgumentException("limit must be >= 0, got: " + n);
limit = n;
return this;
}

/**
* Set eip3155Strict flag.
*
Expand All @@ -132,7 +146,7 @@ public OpCodeTracerConfigBuilder eip3155Strict(final boolean enable) {
*/
public OpCodeTracerConfig build() {
return new Config(
traceStorage, traceMemory, traceStack, traceReturnData, traceOpcodes, eip3155Strict);
traceStorage, traceMemory, traceStack, traceReturnData, traceOpcodes, eip3155Strict, limit);
}

/**
Expand All @@ -144,7 +158,7 @@ public OpCodeTracerConfig build() {
public sealed interface OpCodeTracerConfig permits Config {
/** static default OpcodeTracerConfig which can be accessed externally */
OpCodeTracerConfig DEFAULT =
new Config(true, false, true, false, Collections.emptySet(), false);
new Config(true, false, true, false, Collections.emptySet(), false, 0);

/**
* Check if tracing of storage is enabled.
Expand Down Expand Up @@ -188,6 +202,13 @@ public sealed interface OpCodeTracerConfig permits Config {
* @return true if enabled, false otherwise
*/
boolean eip3155Strict();

/**
* Maximum number of steps to trace. Zero means unlimited.
*
* @return the step limit, or 0 for unlimited
*/
int limit();
}

private record Config(
Expand All @@ -196,6 +217,7 @@ private record Config(
boolean traceStack,
boolean traceReturnData,
Set<String> traceOpcodes,
boolean eip3155Strict)
boolean eip3155Strict,
int limit)
implements OpCodeTracerConfig {}
}
Loading