From fa1c64759b3a3d19ab041d973486a9a41f33862d Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 3 Apr 2026 17:00:30 +0530 Subject: [PATCH 1/8] Fix storage snapshot timing in DebugOperationTracer Per execution-apis spec (PR #762), the storage field in debug trace struct logs must only be emitted for SLOAD and SSTORE opcodes, showing only the single slot touched by that operation. Previously, captureStorage() was called on every opcode and returned the full accumulated dirty-storage map from getUpdatedStorage(), so every frame after the first SSTORE would include all previously written slots regardless of opcode. Changes: - captureStorage() now returns Optional.empty() for all opcodes except SLOAD and SSTORE - SSTORE: uses frame.getMaybeUpdatedStorage() (set by SStoreOperation via frame.storageWasUpdated()) to return only the written slot - SLOAD: captures the slot key in tracePreExecution() before the opcode pops it off the stack, then reads the loaded value from the stack top in tracePostExecution() - Removed ModificationNotAllowedException import (no longer used) Tests: - shouldRecordStorageWhenEnabled -> split into three focused tests: shouldRecordStorageForSstoreWhenEnabled, shouldRecordStorageForSloadWhenEnabled, shouldNotRecordStorageForNonStorageOpcodeWhenEnabled - shouldCaptureFrameWhenExceptionalHaltOccurs: storage is now empty for a non-storage opcode halt (MUL), assertion updated accordingly Signed-off-by: Sagar Khandagre --- .../ethereum/vm/DebugOperationTracer.java | 62 ++++++++-- .../ethereum/vm/DebugOperationTracerTest.java | 108 +++++++++++++----- 2 files changed, 129 insertions(+), 41 deletions(-) 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 44aef097f17..45a1877fd97 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 @@ -17,7 +17,6 @@ import org.hyperledger.besu.datatypes.Address; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.evm.Code; -import org.hyperledger.besu.evm.ModificationNotAllowedException; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.operation.AbstractCreateOperation; @@ -42,6 +41,7 @@ public class DebugOperationTracer extends AbstractDebugOperationTracer { private List traceFrames = new ArrayList<>(); private TraceFrame lastFrame; + private Optional preExecutionStorageKey = Optional.empty(); private Bytes inputData; private int stepCount; private boolean limitReached; @@ -57,6 +57,19 @@ public DebugOperationTracer(final OpCodeTracerConfig options, final boolean reco super(options, recordChildCallGas); } + @Override + public void tracePreExecution(final MessageFrame frame) { + super.tracePreExecution(frame); + if (traceOpcode + && options.traceStorage() + && "SLOAD".equals(frame.getCurrentOperation().getName()) + && frame.stackSize() > 0) { + preExecutionStorageKey = Optional.of(UInt256.fromBytes(frame.getStackItem(0))); + } else { + preExecutionStorageKey = Optional.empty(); + } + } + @Override protected void capturePreExecutionState(final MessageFrame frame) { if (options.limit() > 0 && stepCount >= options.limit()) { @@ -99,7 +112,8 @@ public void tracePostExecution(final MessageFrame frame, final OperationResult o return; } - final Optional> storage = captureStorage(frame); + final Optional> storage = + captureStorage(frame, currentOperation, operationResult); final Optional> maybeRefunds = frame.getRefunds().isEmpty() ? Optional.empty() : Optional.of(frame.getRefunds()); final long thisGasCost = computeGasCost(currentOperation, operationResult, frame); @@ -246,20 +260,43 @@ private Optional captureReturnData(final MessageFrame frame) { : Optional.of(returnData); } - private Optional> captureStorage(final MessageFrame frame) { + private Optional> captureStorage( + final MessageFrame frame, + final Operation currentOperation, + final OperationResult operationResult) { if (!options.traceStorage()) { return Optional.empty(); } - try { - Map updatedStorage = - frame.getWorldUpdater().getAccount(frame.getRecipientAddress()).getUpdatedStorage(); - if (updatedStorage.isEmpty()) return Optional.empty(); - final Map storageContents = new TreeMap<>(updatedStorage); - - return Optional.of(storageContents); - } catch (final ModificationNotAllowedException e) { - return Optional.of(new TreeMap<>()); + // Per execution-apis spec, the storage field is only emitted for SLOAD and SSTORE opcodes, + // showing only the single slot touched by that specific operation. + final String opName = currentOperation.getName(); + if ("SSTORE".equals(opName)) { + // SStoreOperation calls frame.storageWasUpdated(key, newValue) on success; + // empty if SSTORE halted (e.g. insufficient gas), which is correct. + return frame + .getMaybeUpdatedStorage() + .map( + entry -> { + final Map map = new TreeMap<>(); + map.put(entry.getOffset(), UInt256.fromBytes(entry.getValue())); + return map; + }); + } + if ("SLOAD".equals(opName) && operationResult.getHaltReason() == null) { + // preExecutionStorageKey holds the slot key captured before the opcode ran; + // after SLOAD executes the loaded value sits at the top of the stack. + return preExecutionStorageKey.flatMap( + key -> { + if (frame.stackSize() == 0) { + return Optional.empty(); + } + final UInt256 loadedValue = UInt256.fromBytes(frame.getStackItem(0)); + final Map map = new TreeMap<>(); + map.put(key, loadedValue); + return Optional.of(map); + }); } + return Optional.empty(); } private Optional captureMemory(final MessageFrame frame) { @@ -297,6 +334,7 @@ public void reset() { lastFrame = null; stepCount = 0; limitReached = false; + preExecutionStorageKey = Optional.empty(); } public List copyTraceFrames() { 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 ceaf374a4d7..96515cfa779 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 @@ -15,8 +15,6 @@ package org.hyperledger.besu.ethereum.vm; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.hyperledger.besu.datatypes.Wei; import org.hyperledger.besu.ethereum.core.BlockHeader; @@ -25,7 +23,6 @@ import org.hyperledger.besu.ethereum.core.MessageFrameTestFixture; import org.hyperledger.besu.ethereum.referencetests.ReferenceTestBlockchain; import org.hyperledger.besu.evm.EVM; -import org.hyperledger.besu.evm.account.MutableAccount; import org.hyperledger.besu.evm.frame.ExceptionalHaltReason; import org.hyperledger.besu.evm.frame.MessageFrame; import org.hyperledger.besu.evm.gascalculator.CancunGasCalculator; @@ -39,9 +36,7 @@ import org.hyperledger.besu.evm.worldstate.WorldUpdater; import java.util.List; -import java.util.Map; import java.util.OptionalLong; -import java.util.TreeMap; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -83,6 +78,30 @@ public OperationResult execute(final MessageFrame frame, final EVM evm) { private final CallOperation callOperation = new CallOperation(new CancunGasCalculator()); + private static final UInt256 SLOAD_RETURNED_VALUE = + UInt256.fromHexString("0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"); + + private final Operation SSTORE_OPERATION = + new AbstractOperation(0x55, "SSTORE", 2, 0, null) { + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + final UInt256 key = UInt256.fromBytes(frame.popStackItem()); + final UInt256 value = UInt256.fromBytes(frame.popStackItem()); + frame.storageWasUpdated(key, value); + return new OperationResult(5000L, null); + } + }; + + private final Operation SLOAD_OPERATION = + new AbstractOperation(0x54, "SLOAD", 1, 1, null) { + @Override + public OperationResult execute(final MessageFrame frame, final EVM evm) { + frame.popStackItem(); // consume the key + frame.pushStackItem(SLOAD_RETURNED_VALUE); // push the loaded value + return new OperationResult(2100L, null); + } + }; + @Test void shouldRecordProgramCounter() { final MessageFrame frame = validMessageFrame(); @@ -192,20 +211,60 @@ void shouldNotRecordMemoryWhenDisabled() { } @Test - void shouldRecordStorageWhenEnabled() { + void shouldRecordStorageForSstoreWhenEnabled() { + final MessageFrame frame = validMessageFrame(); + final UInt256 storageKey = UInt256.fromHexString("0x01"); + final UInt256 storageValue = UInt256.fromHexString("0xdeadbeef"); + frame.pushStackItem(storageValue); // value (popped second by SSTORE) + frame.pushStackItem(storageKey); // key (popped first by SSTORE) + final DebugOperationTracer tracer = + new DebugOperationTracer( + OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) + .traceStorage(true) + .traceMemory(false) + .traceStack(false) + .build(), + false); + traceFrame(frame, tracer, SSTORE_OPERATION); + final TraceFrame traceFrame = getOnlyTraceFrame(tracer); + assertThat(traceFrame.getStorage()).isPresent(); + assertThat(traceFrame.getStorage().get()).hasSize(1); + assertThat(traceFrame.getStorage().get()).containsEntry(storageKey, storageValue); + } + + @Test + void shouldRecordStorageForSloadWhenEnabled() { final MessageFrame frame = validMessageFrame(); - final Map updatedStorage = setupStorageForCapture(frame); + final UInt256 storageKey = UInt256.fromHexString("0x02"); + frame.pushStackItem(storageKey); // key (popped by SLOAD) + final DebugOperationTracer tracer = + new DebugOperationTracer( + OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) + .traceStorage(true) + .traceMemory(false) + .traceStack(false) + .build(), + false); + traceFrame(frame, tracer, SLOAD_OPERATION); + final TraceFrame traceFrame = getOnlyTraceFrame(tracer); + assertThat(traceFrame.getStorage()).isPresent(); + assertThat(traceFrame.getStorage().get()).hasSize(1); + assertThat(traceFrame.getStorage().get()).containsEntry(storageKey, SLOAD_RETURNED_VALUE); + } + + @Test + void shouldNotRecordStorageForNonStorageOpcodeWhenEnabled() { + // storage is only emitted for SLOAD/SSTORE; non-storage opcodes produce empty storage final TraceFrame traceFrame = traceFrame( - frame, + validMessageFrame(), OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) .traceStorage(true) .traceMemory(false) .traceStack(false) .build(), false); - assertThat(traceFrame.getStorage()).isPresent(); - assertThat(traceFrame.getStorage()).contains(updatedStorage); + assertThat(traceFrame.getStorage()).isEmpty(); } @Test @@ -323,7 +382,6 @@ void childGasFlagDoesNotMatterForNonCallOperations() { @Test void shouldCaptureFrameWhenExceptionalHaltOccurs() { final MessageFrame frame = validMessageFrame(); - final Map updatedStorage = setupStorageForCapture(frame); final DebugOperationTracer tracer = new DebugOperationTracer( @@ -340,7 +398,7 @@ void shouldCaptureFrameWhenExceptionalHaltOccurs() { final TraceFrame traceFrame = getOnlyTraceFrame(tracer); assertThat(traceFrame.getExceptionalHaltReason()) .contains(ExceptionalHaltReason.INSUFFICIENT_GAS); - assertThat(traceFrame.getStorage()).contains(updatedStorage); + assertThat(traceFrame.getStorage()).isEmpty(); } @Test @@ -589,23 +647,6 @@ private MessageFrame validMessageFrameWithInitiatedMemory(final Bytes32 initiate return frame; } - private Map setupStorageForCapture(final MessageFrame frame) { - final MutableAccount account = mock(MutableAccount.class); - when(worldUpdater.getAccount(frame.getRecipientAddress())).thenReturn(account); - - final Map updatedStorage = new TreeMap<>(); - updatedStorage.put(UInt256.ZERO, UInt256.valueOf(233)); - updatedStorage.put(UInt256.ONE, UInt256.valueOf(2424)); - when(account.getUpdatedStorage()).thenReturn(updatedStorage); - final Bytes32 word1 = Bytes32.fromHexString("0x01"); - final Bytes32 word2 = Bytes32.fromHexString("0x02"); - final Bytes32 word3 = Bytes32.fromHexString("0x03"); - frame.writeMemory(0, 32, word1); - frame.writeMemory(32, 32, word2); - frame.writeMemory(64, 32, word3); - return updatedStorage; - } - private static DebugOperationTracer createDebugOperationTracerWithMemory() { final OpCodeTracerConfig config = OpCodeTracerConfigBuilder.createFrom(OpCodeTracerConfig.DEFAULT) @@ -615,4 +656,13 @@ private static DebugOperationTracer createDebugOperationTracerWithMemory() { .build(); return new DebugOperationTracer(config, false); } + + private void setupStorageForCapture(final MessageFrame frame) { + final Bytes32 word1 = Bytes32.fromHexString("0x01"); + final Bytes32 word2 = Bytes32.fromHexString("0x02"); + final Bytes32 word3 = Bytes32.fromHexString("0x03"); + frame.writeMemory(0, 32, word1); + frame.writeMemory(32, 32, word2); + frame.writeMemory(64, 32, word3); + } } From fb605f3267c27477bf88b5ea2db61ba5a211c889 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 3 Apr 2026 17:03:22 +0530 Subject: [PATCH 2/8] Update TraceFrame storage Javadoc to reflect SLOAD/SSTORE-only semantics Per execution-apis spec (PR #762), the storage field is only populated for SLOAD and SSTORE opcodes and contains a single-entry map for the slot touched. Update the getter and builder setter Javadoc accordingly. Signed-off-by: Sagar Khandagre --- .../hyperledger/besu/evm/tracing/TraceFrame.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 8128912da49..add0c0b0b33 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 @@ -373,9 +373,13 @@ public Builder setMemory(final Optional memory) { } /** - * Sets the storage for this operation. + * Sets the storage entry touched by this operation. * - * @param storage the storage as an optional map of UInt256 keys and values + *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE + * opcodes and contains solely the single slot accessed or written by that operation. + * + * @param storage the storage slot touched, as an optional single-entry map of slot key to + * value; empty for all opcodes other than SLOAD and SSTORE * @return this builder instance for method chaining */ public Builder setStorage(final Optional> storage) { @@ -727,9 +731,13 @@ public Optional getMemory() { } /** - * data storage slots and values + * Returns the storage slot touched by this operation. + * + *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE opcodes + * and contains solely the single slot accessed or written by that operation. Empty for all + * other opcodes. * - * @return data storage slots and values + * @return an optional single-entry map of slot key to value, present only for SLOAD/SSTORE */ public Optional> getStorage() { return storage; From 526440d61bb65c3182ca4c00f2b6c90658e05066 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 3 Apr 2026 17:26:00 +0530 Subject: [PATCH 3/8] Apply formatting to DebugOperationTracer, DebugOperationTracerTest, TraceFrame Signed-off-by: Sagar Khandagre --- .../org/hyperledger/besu/evm/tracing/TraceFrame.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 add0c0b0b33..cfecd1152df 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 @@ -375,8 +375,8 @@ public Builder setMemory(final Optional memory) { /** * Sets the storage entry touched by this operation. * - *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE - * opcodes and contains solely the single slot accessed or written by that operation. + *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE opcodes and + * contains solely the single slot accessed or written by that operation. * * @param storage the storage slot touched, as an optional single-entry map of slot key to * value; empty for all opcodes other than SLOAD and SSTORE @@ -733,9 +733,9 @@ public Optional getMemory() { /** * Returns the storage slot touched by this operation. * - *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE opcodes - * and contains solely the single slot accessed or written by that operation. Empty for all - * other opcodes. + *

Per the execution-apis spec, this field is only populated for SLOAD and SSTORE opcodes and + * contains solely the single slot accessed or written by that operation. Empty for all other + * opcodes. * * @return an optional single-entry map of slot key to value, present only for SLOAD/SSTORE */ From 1fef808da1ce55bd97801da228bf27e1887c5f17 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Thu, 30 Apr 2026 15:48:53 +0530 Subject: [PATCH 4/8] added null check for frame.getCurrentOperation(); Signed-off-by: Sagar Khandagre --- .../hyperledger/besu/ethereum/vm/DebugOperationTracer.java | 4 +++- 1 file changed, 3 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 45a1877fd97..f502eafa6b0 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 @@ -60,9 +60,11 @@ public DebugOperationTracer(final OpCodeTracerConfig options, final boolean reco @Override public void tracePreExecution(final MessageFrame frame) { super.tracePreExecution(frame); + final Operation currentOperation = frame.getCurrentOperation(); + final String operationName = currentOperation != null ? currentOperation.getName() : null; if (traceOpcode && options.traceStorage() - && "SLOAD".equals(frame.getCurrentOperation().getName()) + && "SLOAD".equals(operationName) && frame.stackSize() > 0) { preExecutionStorageKey = Optional.of(UInt256.fromBytes(frame.getStackItem(0))); } else { From ee739ecd2c093e447be3afa49da29d15e7b25a9f Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Fri, 1 May 2026 09:17:02 +0530 Subject: [PATCH 5/8] added change log entry Signed-off-by: Sagar Khandagre --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3f5852e40..d6c59ca1b58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ ### Additions and Improvements - The option to set a different block period for empty BFT blocks (`emptyblockperiodseconds`) is no longer experimental. The experimental flag `xemptyblockperiodseconds` will be removed in a future release. [#10264](https://github.com/besu-eth/besu/pull/10264) - Release worker threads after engine API timeout to avoid blocking subsequent requests [#10311](https://github.com/besu-eth/besu/pull/10311) +- `debug_trace*`: add optional `limit` parameter to cap opcode trace capture after N steps (`0` keeps unlimited behavior); negative values are rejected as invalid params (`-32602`). [#10176](https://github.com/besu-eth/besu/pull/10176) - `evmtool blocktest --verbose` flag, default off, removes noise from output [#10348](https://github.com/besu-eth/besu/pull/10348) - Bound precompile result caches to the semantic-prefix slice and apply a 16 MB byte-weight cap per cache, providing a uniform memory ceiling across all 14 precompile caches [#10350](https://github.com/besu-eth/besu/pull/10350) - Lazy RLP decoding of `GetBlockBodies` messages reduces memory and CPU spent on dropped requests [#10342](https://github.com/besu-eth/besu/pull/10342) From 53dcd5d4f9688efc570a649fdf5d24498d600b4c Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Tue, 5 May 2026 09:06:31 +0530 Subject: [PATCH 6/8] fix changelog entry Signed-off-by: Sagar Khandagre --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6c59ca1b58..dc2f4c3709f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ### Bug fixes - 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) +- Fix `debug_trace*` `storage` field to emit only for SLOAD/SSTORE opcodes showing the single slot touched, matching the execution-apis spec and geth behaviour [#10176](https://github.com/besu-eth/besu/pull/10176) ### Additions and Improvements - Add `eth_getStorageValues` JSON-RPC method for batched reads of multiple storage slots across multiple accounts in a single call [#10259](https://github.com/besu-eth/besu/pull/10259) @@ -59,11 +60,11 @@ - Enforce that `blob_versioned_hashes` match the supplied blobs [#10278](https://github.com/besu-eth/besu/pull/10278) - Restrict no-reorg behavior to the prefix of the known finalized chain (per execution-apis #786) [#10335](https://github.com/besu-eth/besu/pull/10335) - `eth_getFilterLogs`: cache the chain head once when resolving default `latest..latest` bounds, so a block arriving between the two reads no longer expands the queried range into `[N, N+1]` and returns extra logs. [#10368](https://github.com/besu-eth/besu/pull/10368) +- `eth_getFilterLogs`: cache the chain head once when resolving default `latest..latest` bounds, so a block arriving between the two reads no longer expands the queried range into `[N, N+1]` and returns extra logs. ### Additions and Improvements - The option to set a different block period for empty BFT blocks (`emptyblockperiodseconds`) is no longer experimental. The experimental flag `xemptyblockperiodseconds` will be removed in a future release. [#10264](https://github.com/besu-eth/besu/pull/10264) - Release worker threads after engine API timeout to avoid blocking subsequent requests [#10311](https://github.com/besu-eth/besu/pull/10311) -- `debug_trace*`: add optional `limit` parameter to cap opcode trace capture after N steps (`0` keeps unlimited behavior); negative values are rejected as invalid params (`-32602`). [#10176](https://github.com/besu-eth/besu/pull/10176) - `evmtool blocktest --verbose` flag, default off, removes noise from output [#10348](https://github.com/besu-eth/besu/pull/10348) - Bound precompile result caches to the semantic-prefix slice and apply a 16 MB byte-weight cap per cache, providing a uniform memory ceiling across all 14 precompile caches [#10350](https://github.com/besu-eth/besu/pull/10350) - Lazy RLP decoding of `GetBlockBodies` messages reduces memory and CPU spent on dropped requests [#10342](https://github.com/besu-eth/besu/pull/10342) From 1ca8acacf1c7651e89942f16a52c9e8904906ac9 Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Tue, 12 May 2026 10:38:03 +0530 Subject: [PATCH 7/8] fix entry Signed-off-by: Sagar Khandagre --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc2f4c3709f..940cd3695b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,7 +60,6 @@ - Enforce that `blob_versioned_hashes` match the supplied blobs [#10278](https://github.com/besu-eth/besu/pull/10278) - Restrict no-reorg behavior to the prefix of the known finalized chain (per execution-apis #786) [#10335](https://github.com/besu-eth/besu/pull/10335) - `eth_getFilterLogs`: cache the chain head once when resolving default `latest..latest` bounds, so a block arriving between the two reads no longer expands the queried range into `[N, N+1]` and returns extra logs. [#10368](https://github.com/besu-eth/besu/pull/10368) -- `eth_getFilterLogs`: cache the chain head once when resolving default `latest..latest` bounds, so a block arriving between the two reads no longer expands the queried range into `[N, N+1]` and returns extra logs. ### Additions and Improvements - The option to set a different block period for empty BFT blocks (`emptyblockperiodseconds`) is no longer experimental. The experimental flag `xemptyblockperiodseconds` will be removed in a future release. [#10264](https://github.com/besu-eth/besu/pull/10264) From ebe9425dfc6719ed1cca518df694f58bf09309ba Mon Sep 17 00:00:00 2001 From: Sagar Khandagre Date: Tue, 12 May 2026 10:42:37 +0530 Subject: [PATCH 8/8] fix(test): update integration test storage assertions for SLOAD/SSTORE-only semantics TraceTransactionIntegrationTest expected storage to persist across non-storage frames (PUSH2, DUP6), which matched the old broken behaviour captureStorage() fixed in fa1c64759b. Update assertions so DUP6 and PUSH2 frames assert storage is empty, and only the SSTORE frame asserts the single touched slot. Signed-off-by: Sagar Khandagre --- .../ethereum/vm/TraceTransactionIntegrationTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java index adf365b6705..968ca5a0945 100644 --- a/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java +++ b/ethereum/core/src/integration-test/java/org/hyperledger/besu/ethereum/vm/TraceTransactionIntegrationTest.java @@ -158,21 +158,21 @@ public void shouldTraceSStoreOperation() { assertThat(result.isSuccessful()).isTrue(); - // No storage changes before the SSTORE call. + // Non-storage opcodes produce no storage entry (per execution-apis spec). TraceFrame frame = tracer.getTraceFrames().get(170); assertThat(frame.getOpcode()).isEqualTo("DUP6"); + assertThat(frame.getStorage()).isEmpty(); - // Storage changes show up in the SSTORE frame. + // Storage is emitted only for the SSTORE frame, showing the single slot touched. frame = tracer.getTraceFrames().get(171); assertThat(frame.getOpcode()).isEqualTo("SSTORE"); assertStorageContainsExactly( frame, entry("0x01", "0x6261720000000000000000000000000000000000000000000000000000000006")); - // And storage changes are still present in future frames. + // After SSTORE, non-storage opcodes produce no storage entry (per execution-apis spec). frame = tracer.getTraceFrames().get(172); assertThat(frame.getOpcode()).isEqualTo("PUSH2"); - assertStorageContainsExactly( - frame, entry("0x01", "0x6261720000000000000000000000000000000000000000000000000000000006")); + assertThat(frame.getStorage()).isEmpty(); } @Test