From 1a334af12cdf7a58b085b4b616a828116300aaff Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 21:49:38 +0530 Subject: [PATCH 01/12] resolving conflicts Signed-off-by: Sagar Khandagre --- .../parameters/TransactionTraceParams.java | 9 +++++++ .../jsonrpc/internal/results/StructLog.java | 8 +++++++ .../ethereum/vm/DebugOperationTracer.java | 11 +++++++++ .../besu/evm/tracing/TraceFrame.java | 24 +++++++++++++++++++ 4 files changed, 52 insertions(+) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java index 3f5bb4ec6ac..235a2aeb9d6 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java @@ -71,6 +71,13 @@ default boolean disableStack() { @JsonProperty(value = "limit") @Nullable Integer limit(); + + @JsonProperty(value = "enableReturnData") + @Nullable Boolean enableReturnDataNullable(); + + default boolean enableReturnData() { + return Boolean.TRUE.equals(enableReturnDataNullable()); + } @JsonProperty("tracer") @JsonInclude(JsonInclude.Include.NON_NULL) @@ -134,6 +141,8 @@ default TraceOptions traceOptions() { } if (limit() != null) { builder.limit(limit()); + if (enableReturnDataNullable() != null) { + builder.traceReturnData(enableReturnData()); } var opCodeTracerConfig = builder.traceOpcodes(opcodes()).build(); diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java index 5bf18523ca5..b6ea21b0221 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java @@ -41,6 +41,7 @@ public class StructLog { private final String[] stack; private final Object storage; private final String reason; + private final String returnData; public StructLog(final TraceFrame traceFrame) { depth = traceFrame.getDepth() + 1; @@ -64,6 +65,7 @@ public StructLog(final TraceFrame traceFrame) { storage = traceFrame.getStorage().map(StructLog::formatStorage).orElse(null); reason = traceFrame.getRevertReason().map(bytes -> toCompactHex(bytes, true)).orElse(null); + returnData = traceFrame.getReturnData().map(Bytes::toHexString).orElse(null); } private static Map formatStorage(final Map storage) { @@ -145,6 +147,12 @@ public String reason() { return reason; } + @JsonGetter("returnData") + @JsonInclude(JsonInclude.Include.NON_NULL) + public String returnData() { + return returnData; + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index c35e8fc2a17..4f07394de29 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -86,6 +86,8 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o && currentOperation instanceof AbstractCreateOperation ? forceCaptureMem(frame) : Optional.empty()); + final Optional returnData = captureReturnData(frame); + final Optional memory = captureMemory(frame); final Optional stackPostExecution = captureStack(frame); if (!traceFrames.isEmpty()) { @@ -122,6 +124,7 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o .setValue(frame.getApparentValue()) .setInputData(inputData) .setOutputData(outputData) + .setReturnData(returnData) .setStack(preExecutionStack) .setMemory(memory) .setStorage(storage) @@ -234,6 +237,14 @@ private void addNewTraceFrame( traceFrames.add(traceFrame); } + + private Optional captureReturnData(final MessageFrame frame) { + if (!options.traceReturnData()) { + return Optional.empty(); + } + return Optional.of(frame.getReturnData()); + } + private Optional> captureStorage(final MessageFrame frame) { if (!options.traceStorage()) { return Optional.empty(); diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java index 2fe465520e1..ac3f8430010 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java @@ -68,6 +68,7 @@ public class TraceFrame { private final Optional softFailureReason; private final OptionalLong gasAvailableForChildCall; + private final Optional returnData; /** Private constructor - only accessible through Builder */ private TraceFrame(final Builder builder) { @@ -103,6 +104,7 @@ private TraceFrame(final Builder builder) { this.precompileOutputData = builder.precompileOutputData; this.softFailureReason = builder.softFailureReason; this.gasAvailableForChildCall = builder.gasAvailableForChildCall; + this.returnData = builder.returnData; } /** @@ -158,6 +160,7 @@ public static class Builder { private Optional precompileOutputData = Optional.empty(); private Optional softFailureReason = Optional.empty(); private OptionalLong gasAvailableForChildCall = OptionalLong.empty(); + private Optional returnData = Optional.empty(); /** Default constructor */ public Builder() {} @@ -200,6 +203,7 @@ public Builder(final TraceFrame traceFrame) { this.precompileOutputData = traceFrame.precompileOutputData; this.softFailureReason = traceFrame.softFailureReason; this.gasAvailableForChildCall = traceFrame.gasAvailableForChildCall; + this.returnData = traceFrame.returnData; } /** @@ -575,6 +579,17 @@ public Builder setGasAvailableForChildCall(final OptionalLong gasAvailableForChi return this; } + /** + * Sets the return data buffer captured after the opcode executed. + * + * @param returnData the return data buffer contents + * @return this builder instance for method chaining + */ + public Builder setReturnData(final Optional returnData) { + this.returnData = returnData; + return this; + } + /** * Builds the TraceFrame instance. * @@ -874,6 +889,15 @@ public OptionalLong getGasAvailableForChildCall() { return gasAvailableForChildCall; } + /** + * Return data buffer at the time this opcode was traced. + * + * @return the return data buffer, or empty if not captured + */ + public Optional getReturnData() { + return returnData; + } + @Override public String toString() { return MoreObjects.toStringHelper(this) From e043603f74267fba79086274df35bcab63bc7700 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 3 Apr 2026 09:14:54 +0530 Subject: [PATCH 02/12] apply formatting Signed-off-by: Sagar Khandagre --- .../org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index 4f07394de29..e3210d1ebba 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -237,7 +237,6 @@ private void addNewTraceFrame( traceFrames.add(traceFrame); } - private Optional captureReturnData(final MessageFrame frame) { if (!options.traceReturnData()) { return Optional.empty(); From 701e86e8e67b2945f92f9d9396a8ff975cc11cb7 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 10 Apr 2026 19:51:57 +0530 Subject: [PATCH 03/12] implements test by priority Signed-off-by: Sagar Khandagre --- .../parameters/TransactionTraceParams.java | 3 +- .../TransactionTraceParamsTest.java | 32 ++++++++++ .../internal/results/StructLogTest.java | 60 +++++++++++++++++++ .../ethereum/vm/DebugOperationTracerTest.java | 35 +++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java index 235a2aeb9d6..f2ba9405812 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParams.java @@ -71,7 +71,7 @@ default boolean disableStack() { @JsonProperty(value = "limit") @Nullable Integer limit(); - + @JsonProperty(value = "enableReturnData") @Nullable Boolean enableReturnDataNullable(); @@ -141,6 +141,7 @@ default TraceOptions traceOptions() { } if (limit() != null) { builder.limit(limit()); + } if (enableReturnDataNullable() != null) { builder.traceReturnData(enableReturnData()); } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParamsTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParamsTest.java index eaee3ce96c9..a6c4c2f16f1 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParamsTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/parameters/TransactionTraceParamsTest.java @@ -138,4 +138,36 @@ public void negativeLimitShouldFailDuringDeserialization() { assertThatThrownBy(() -> MAPPER.readValue("{\"limit\": -1}", TransactionTraceParams.class)) .hasMessageContaining("limit must be >= 0, got: -1"); } + + @Test + public void enableReturnDataTrueShouldSetTraceReturnData() throws Exception { + final TransactionTraceParams params = + MAPPER.readValue("{\"enableReturnData\": true}", TransactionTraceParams.class); + final OpCodeTracerConfig config = params.traceOptions().opCodeTracerConfig(); + + assertThat(config.traceReturnData()) + .describedAs("enableReturnData: true should set traceReturnData to true") + .isTrue(); + } + + @Test + public void enableReturnDataFalseShouldSetTraceReturnDataFalse() throws Exception { + final TransactionTraceParams params = + MAPPER.readValue("{\"enableReturnData\": false}", TransactionTraceParams.class); + final OpCodeTracerConfig config = params.traceOptions().opCodeTracerConfig(); + + assertThat(config.traceReturnData()) + .describedAs("enableReturnData: false should set traceReturnData to false") + .isFalse(); + } + + @Test + public void missingEnableReturnDataShouldDefaultToFalse() throws Exception { + final TransactionTraceParams params = MAPPER.readValue("{}", TransactionTraceParams.class); + final OpCodeTracerConfig config = params.traceOptions().opCodeTracerConfig(); + + assertThat(config.traceReturnData()) + .describedAs("traceReturnData should default to false when enableReturnData is absent") + .isFalse(); + } } diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java index 07396fcdac6..f0519ae9a54 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java @@ -246,4 +246,64 @@ public void testToCompactHexWithLargeData() { String result = StructLog.toCompactHex(bytes, true); assertEquals("0x102030405060708090a", result, "Expected correct hex output for large data"); } + + // --- returnData tests --- + + @Test + public void returnDataShouldBePresentWhenCaptured() { + when(traceFrame.getDepth()).thenReturn(0); + when(traceFrame.getGasRemaining()).thenReturn(0L); + when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); + when(traceFrame.getGasRefund()).thenReturn(0L); + when(traceFrame.getMemory()).thenReturn(Optional.empty()); + when(traceFrame.getOpcode()).thenReturn("PUSH1"); + when(traceFrame.getPc()).thenReturn(0); + when(traceFrame.getStack()).thenReturn(Optional.empty()); + when(traceFrame.getStorage()).thenReturn(Optional.empty()); + when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); + when(traceFrame.getReturnData()).thenReturn(Optional.of(Bytes.fromHexString("0xdeadbeef"))); + + final StructLog log = new StructLog(traceFrame); + + assertThat(log.returnData()).isEqualTo("0xdeadbeef"); + } + + @Test + public void returnDataShouldBeNullWhenNotCaptured() { + when(traceFrame.getDepth()).thenReturn(0); + when(traceFrame.getGasRemaining()).thenReturn(0L); + when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); + when(traceFrame.getGasRefund()).thenReturn(0L); + when(traceFrame.getMemory()).thenReturn(Optional.empty()); + when(traceFrame.getOpcode()).thenReturn("PUSH1"); + when(traceFrame.getPc()).thenReturn(0); + when(traceFrame.getStack()).thenReturn(Optional.empty()); + when(traceFrame.getStorage()).thenReturn(Optional.empty()); + when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); + when(traceFrame.getReturnData()).thenReturn(Optional.empty()); + + final StructLog log = new StructLog(traceFrame); + + assertThat(log.returnData()).isNull(); + } + + @Test + public void returnDataShouldBeAbsentFromJsonWhenNotCaptured() throws Exception { + when(traceFrame.getDepth()).thenReturn(0); + when(traceFrame.getGasRemaining()).thenReturn(0L); + when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); + when(traceFrame.getGasRefund()).thenReturn(0L); + when(traceFrame.getMemory()).thenReturn(Optional.empty()); + when(traceFrame.getOpcode()).thenReturn("PUSH1"); + when(traceFrame.getPc()).thenReturn(0); + when(traceFrame.getStack()).thenReturn(Optional.empty()); + when(traceFrame.getStorage()).thenReturn(Optional.empty()); + when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); + when(traceFrame.getReturnData()).thenReturn(Optional.empty()); + + final StructLog log = new StructLog(traceFrame); + final String json = objectMapper.writeValueAsString(log); + + assertThat(json).doesNotContain("returnData"); + } } diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 024744abbbd..5a56d56a04d 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -222,6 +222,41 @@ void shouldNotRecordStorageWhenDisabled() { assertThat(traceFrame.getStorage()).isEmpty(); } + @Test + void shouldRecordReturnDataWhenEnabled() { + final MessageFrame frame = validMessageFrame(); + frame.setReturnData(Bytes.fromHexString("0xdeadbeef")); + final TraceFrame traceFrame = + traceFrame( + frame, + OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) + .traceStorage(false) + .traceMemory(false) + .traceStack(false) + .traceReturnData(true) + .build(), + false); + assertThat(traceFrame.getReturnData()).isPresent(); + assertThat(traceFrame.getReturnData().get()).isEqualTo(Bytes.fromHexString("0xdeadbeef")); + } + + @Test + void shouldNotRecordReturnDataWhenDisabled() { + final MessageFrame frame = validMessageFrame(); + frame.setReturnData(Bytes.fromHexString("0xdeadbeef")); + final TraceFrame traceFrame = + traceFrame( + frame, + OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) + .traceStorage(false) + .traceMemory(false) + .traceStack(false) + .traceReturnData(false) + .build(), + false); + assertThat(traceFrame.getReturnData()).isEmpty(); + } + @Test void shouldNotAddGasWhenDisabled() { final TraceFrame traceFrame = From 3efcc57cf76d7b6f9eb722c4dd271fc6ce1111e1 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Sat, 11 Apr 2026 18:37:37 +0530 Subject: [PATCH 04/12] add returnData field in the correct order - EIP-3155 Signed-off-by: Sagar Khandagre --- .../besu/ethereum/api/jsonrpc/internal/results/StructLog.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java index b6ea21b0221..a06b6ca9d63 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java @@ -27,7 +27,7 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -@JsonPropertyOrder({"pc", "op", "gas", "gasCost", "depth", "refund", "stack", "memory", "storage"}) +@JsonPropertyOrder({"pc", "op", "gas", "gasCost", "depth", "refund", "stack", "memory", "returnData", "storage"}) public class StructLog { private static final char[] hexChars = "0123456789abcdef".toCharArray(); From 8e4ca0bf3d4b6a096536fe3c5225eea26e267e27 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 19:59:21 +0530 Subject: [PATCH 05/12] applying a targeted fix in captureReturnData and adding a test that ensures empty return buffer is omitted even when traceReturnData is enabled Signed-off-by: Sagar Khandagre --- .../besu/ethereum/vm/DebugOperationTracer.java | 3 ++- .../ethereum/vm/DebugOperationTracerTest.java | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index e3210d1ebba..b0f9fefadfe 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -241,7 +241,8 @@ private Optional captureReturnData(final MessageFrame frame) { if (!options.traceReturnData()) { return Optional.empty(); } - return Optional.of(frame.getReturnData()); + final Bytes returnData = frame.getReturnData(); + return (returnData == null || returnData.isEmpty()) ? Optional.empty() : Optional.of(returnData); } private Optional> captureStorage(final MessageFrame frame) { diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 5a56d56a04d..89703a3ccd3 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -240,6 +240,23 @@ void shouldRecordReturnDataWhenEnabled() { assertThat(traceFrame.getReturnData().get()).isEqualTo(Bytes.fromHexString("0xdeadbeef")); } + @Test + void shouldNotRecordEmptyReturnDataWhenEnabled() { + final MessageFrame frame = validMessageFrame(); + frame.setReturnData(Bytes.EMPTY); + final TraceFrame traceFrame = + traceFrame( + frame, + OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) + .traceStorage(false) + .traceMemory(false) + .traceStack(false) + .traceReturnData(true) + .build(), + false); + assertThat(traceFrame.getReturnData()).isEmpty(); + } + @Test void shouldNotRecordReturnDataWhenDisabled() { final MessageFrame frame = validMessageFrame(); From 3bdfb44e15710edfefef85b8e2327cdffdc0c485 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 21:02:56 +0530 Subject: [PATCH 06/12] Keep DEFAULT + traceReturnData(...) only for the returnData-specific tests. Signed-off-by: Sagar Khandagre --- .../besu/ethereum/vm/DebugOperationTracerTest.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 89703a3ccd3..2f9b468367b 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -225,14 +225,12 @@ void shouldNotRecordStorageWhenDisabled() { @Test void shouldRecordReturnDataWhenEnabled() { final MessageFrame frame = validMessageFrame(); + setupStorageForCapture(frame); frame.setReturnData(Bytes.fromHexString("0xdeadbeef")); final TraceFrame traceFrame = traceFrame( frame, OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) - .traceStorage(false) - .traceMemory(false) - .traceStack(false) .traceReturnData(true) .build(), false); @@ -243,14 +241,12 @@ void shouldRecordReturnDataWhenEnabled() { @Test void shouldNotRecordEmptyReturnDataWhenEnabled() { final MessageFrame frame = validMessageFrame(); + setupStorageForCapture(frame); frame.setReturnData(Bytes.EMPTY); final TraceFrame traceFrame = traceFrame( frame, OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) - .traceStorage(false) - .traceMemory(false) - .traceStack(false) .traceReturnData(true) .build(), false); @@ -260,14 +256,12 @@ void shouldNotRecordEmptyReturnDataWhenEnabled() { @Test void shouldNotRecordReturnDataWhenDisabled() { final MessageFrame frame = validMessageFrame(); + setupStorageForCapture(frame); frame.setReturnData(Bytes.fromHexString("0xdeadbeef")); final TraceFrame traceFrame = traceFrame( frame, OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) - .traceStorage(false) - .traceMemory(false) - .traceStack(false) .traceReturnData(false) .build(), false); From f76fb335fa12b848fe5fff6e53df321f6754aac3 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 21:14:52 +0530 Subject: [PATCH 07/12] adding returnData in toString() Signed-off-by: Sagar Khandagre --- .../main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java b/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java index ac3f8430010..8128912da49 100644 --- a/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java +++ b/evm/src/main/java/org/hyperledger/besu/evm/tracing/TraceFrame.java @@ -910,6 +910,7 @@ public String toString() { .add("stack", stack) .add("memory", memory) .add("storage", storage) + .add("returnData", returnData) .toString(); } } From 88dc8853e4b9a8e6b2dc11cbfb40897869aa67e8 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 21:34:30 +0530 Subject: [PATCH 08/12] reduce duplication in test Signed-off-by: Sagar Khandagre --- .../internal/results/StructLogTest.java | 38 ++++++------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java index f0519ae9a54..3d7839774a7 100644 --- a/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java +++ b/ethereum/api/src/test/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLogTest.java @@ -251,16 +251,7 @@ public void testToCompactHexWithLargeData() { @Test public void returnDataShouldBePresentWhenCaptured() { - when(traceFrame.getDepth()).thenReturn(0); - when(traceFrame.getGasRemaining()).thenReturn(0L); - when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); - when(traceFrame.getGasRefund()).thenReturn(0L); - when(traceFrame.getMemory()).thenReturn(Optional.empty()); - when(traceFrame.getOpcode()).thenReturn("PUSH1"); - when(traceFrame.getPc()).thenReturn(0); - when(traceFrame.getStack()).thenReturn(Optional.empty()); - when(traceFrame.getStorage()).thenReturn(Optional.empty()); - when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); + setupMinimalTraceFrameForReturnDataTests(); when(traceFrame.getReturnData()).thenReturn(Optional.of(Bytes.fromHexString("0xdeadbeef"))); final StructLog log = new StructLog(traceFrame); @@ -270,16 +261,7 @@ public void returnDataShouldBePresentWhenCaptured() { @Test public void returnDataShouldBeNullWhenNotCaptured() { - when(traceFrame.getDepth()).thenReturn(0); - when(traceFrame.getGasRemaining()).thenReturn(0L); - when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); - when(traceFrame.getGasRefund()).thenReturn(0L); - when(traceFrame.getMemory()).thenReturn(Optional.empty()); - when(traceFrame.getOpcode()).thenReturn("PUSH1"); - when(traceFrame.getPc()).thenReturn(0); - when(traceFrame.getStack()).thenReturn(Optional.empty()); - when(traceFrame.getStorage()).thenReturn(Optional.empty()); - when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); + setupMinimalTraceFrameForReturnDataTests(); when(traceFrame.getReturnData()).thenReturn(Optional.empty()); final StructLog log = new StructLog(traceFrame); @@ -289,6 +271,16 @@ public void returnDataShouldBeNullWhenNotCaptured() { @Test public void returnDataShouldBeAbsentFromJsonWhenNotCaptured() throws Exception { + setupMinimalTraceFrameForReturnDataTests(); + when(traceFrame.getReturnData()).thenReturn(Optional.empty()); + + final StructLog log = new StructLog(traceFrame); + final String json = objectMapper.writeValueAsString(log); + + assertThat(json).doesNotContain("returnData"); + } + + private void setupMinimalTraceFrameForReturnDataTests() { when(traceFrame.getDepth()).thenReturn(0); when(traceFrame.getGasRemaining()).thenReturn(0L); when(traceFrame.getGasCost()).thenReturn(OptionalLong.empty()); @@ -299,11 +291,5 @@ public void returnDataShouldBeAbsentFromJsonWhenNotCaptured() throws Exception { when(traceFrame.getStack()).thenReturn(Optional.empty()); when(traceFrame.getStorage()).thenReturn(Optional.empty()); when(traceFrame.getRevertReason()).thenReturn(Optional.empty()); - when(traceFrame.getReturnData()).thenReturn(Optional.empty()); - - final StructLog log = new StructLog(traceFrame); - final String json = objectMapper.writeValueAsString(log); - - assertThat(json).doesNotContain("returnData"); } } From 90408245ebc5b7d8a30888ac5082eb8a47dd1347 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Wed, 29 Apr 2026 21:58:42 +0530 Subject: [PATCH 09/12] fix Signed-off-by: Sagar Khandagre --- .../org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index b0f9fefadfe..62e7d1e2489 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -87,7 +87,6 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o ? forceCaptureMem(frame) : Optional.empty()); final Optional returnData = captureReturnData(frame); - final Optional memory = captureMemory(frame); final Optional stackPostExecution = captureStack(frame); if (!traceFrames.isEmpty()) { From 626a5314f1d6e7f9b13c7bd97a917bbf3e482810 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Thu, 30 Apr 2026 08:34:10 +0530 Subject: [PATCH 10/12] apply spotless Signed-off-by: Sagar Khandagre --- .../api/jsonrpc/internal/results/StructLog.java | 13 ++++++++++++- .../besu/ethereum/vm/DebugOperationTracer.java | 4 +++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java index a06b6ca9d63..d9d16cca7ec 100644 --- a/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java +++ b/ethereum/api/src/main/java/org/hyperledger/besu/ethereum/api/jsonrpc/internal/results/StructLog.java @@ -27,7 +27,18 @@ import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; -@JsonPropertyOrder({"pc", "op", "gas", "gasCost", "depth", "refund", "stack", "memory", "returnData", "storage"}) +@JsonPropertyOrder({ + "pc", + "op", + "gas", + "gasCost", + "depth", + "refund", + "stack", + "memory", + "returnData", + "storage" +}) public class StructLog { private static final char[] hexChars = "0123456789abcdef".toCharArray(); diff --git a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java index 62e7d1e2489..44aef097f17 100644 --- a/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java +++ b/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracer.java @@ -241,7 +241,9 @@ private Optional captureReturnData(final MessageFrame frame) { return Optional.empty(); } final Bytes returnData = frame.getReturnData(); - return (returnData == null || returnData.isEmpty()) ? Optional.empty() : Optional.of(returnData); + return (returnData == null || returnData.isEmpty()) + ? Optional.empty() + : Optional.of(returnData); } private Optional> captureStorage(final MessageFrame frame) { From 47600748cdcf95194c4b789914d2f5658731e044 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 1 May 2026 09:41:04 +0530 Subject: [PATCH 11/12] adding change log Signed-off-by: Sagar Khandagre --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95c3dcca40f..bf1cd289096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Fix `engine_forkchoiceUpdatedV1` now returns `-38003 INVALID_PAYLOAD_ATTRIBUTES` for invalid payload attribute timestamps (zero or not greater than head). [#10353](https://github.com/besu-eth/besu/pull/10353) ### Additions and Improvements +- Add `enableReturnData` parameter to `debug_traceTransaction` and `debug_traceBlockByNumber`, and include `returnData` in `StructLog` when captured; the field is omitted when return data is empty or not captured. [#10172](https://github.com/besu-eth/besu/pull/10172) ## 26.5.0 From 9bddf72d134c39e631f7e3c6e3c661fdc46ea4e5 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Thu, 7 May 2026 10:46:06 +0530 Subject: [PATCH 12/12] apply suggested changes Signed-off-by: Sagar Khandagre --- .../hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java index 2f9b468367b..ceaf374a4d7 100644 --- a/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java +++ b/ethereum/core/src/test/java/org/hyperledger/besu/ethereum/vm/DebugOperationTracerTest.java @@ -239,7 +239,7 @@ void shouldRecordReturnDataWhenEnabled() { } @Test - void shouldNotRecordEmptyReturnDataWhenEnabled() { + void shouldRecordEmptyReturnDataWhenEnabled() { final MessageFrame frame = validMessageFrame(); setupStorageForCapture(frame); frame.setReturnData(Bytes.EMPTY);