diff --git a/.github/workflows/qa-rpc-integration-tests-gnosis.yml b/.github/workflows/qa-rpc-integration-tests-gnosis.yml index c85a4b9717d..453311ab5a5 100644 --- a/.github/workflows/qa-rpc-integration-tests-gnosis.yml +++ b/.github/workflows/qa-rpc-integration-tests-gnosis.yml @@ -73,7 +73,7 @@ jobs: uses: actions/cache@v5 with: path: ${{ runner.workspace }}/rpc-tests - key: rpc-tests-${{ runner.os }}-${{ runner.arch }}-v2.7.0 + key: rpc-tests-${{ runner.os }}-${{ runner.arch }}-v2.8.1 - name: Run RPC Integration Tests id: test_step diff --git a/.github/workflows/qa-rpc-integration-tests.yml b/.github/workflows/qa-rpc-integration-tests.yml index ddefe5c4499..0f510821537 100644 --- a/.github/workflows/qa-rpc-integration-tests.yml +++ b/.github/workflows/qa-rpc-integration-tests.yml @@ -73,7 +73,7 @@ jobs: uses: actions/cache@v5 with: path: ${{ runner.workspace }}/rpc-tests - key: rpc-tests-${{ runner.os }}-${{ runner.arch }}-v2.7.0 + key: rpc-tests-${{ runner.os }}-${{ runner.arch }}-v2.8.1 - name: Run RPC Integration Tests id: test_step diff --git a/.github/workflows/scripts/run_rpc_tests_ethereum.sh b/.github/workflows/scripts/run_rpc_tests_ethereum.sh index 379436ccbc4..a386ad8ac9c 100755 --- a/.github/workflows/scripts/run_rpc_tests_ethereum.sh +++ b/.github/workflows/scripts/run_rpc_tests_ethereum.sh @@ -43,4 +43,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.7.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.8.1 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh b/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh index 0c0caf49309..8bf9f349ba0 100755 --- a/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh +++ b/.github/workflows/scripts/run_rpc_tests_ethereum_latest.sh @@ -34,4 +34,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.7.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" "latest" "$REFERENCE_HOST" "do-not-compare-error-message" "$DUMP_RESPONSE" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.8.1 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" "latest" "$REFERENCE_HOST" "do-not-compare-error-message" "$DUMP_RESPONSE" diff --git a/.github/workflows/scripts/run_rpc_tests_gnosis.sh b/.github/workflows/scripts/run_rpc_tests_gnosis.sh index 57ea703f571..f69935ba278 100755 --- a/.github/workflows/scripts/run_rpc_tests_gnosis.sh +++ b/.github/workflows/scripts/run_rpc_tests_gnosis.sh @@ -22,5 +22,5 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" gnosis v2.7.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" gnosis v2.8.1 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh b/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh index ab83d7e9331..50ca5b08782 100755 --- a/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh +++ b/.github/workflows/scripts/run_rpc_tests_remote_ethereum.sh @@ -31,4 +31,4 @@ DISABLED_TEST_LIST=( DISABLED_TESTS=$(IFS=,; echo "${DISABLED_TEST_LIST[*]}") # Call the main test runner script with the required and optional parameters -"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.7.0 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" +"$(dirname "$0")/run_rpc_tests.sh" mainnet v2.8.1 "$DISABLED_TESTS" "$WORKSPACE" "$RESULT_DIR" diff --git a/ChangeLog.md b/ChangeLog.md index e801a1eb00b..3ad7c9dbdc2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,44 @@ # Changelog +## [3.5.0] – 2026-04-25 + +### Breaking Changes + +#### `debug_trace*` RPC: `enableMemory` / `enableReturnData` replace `disableMemory` / `disableReturnData` + +Aligns Erigon with the execution-apis specification ([ethereum/execution-apis#762](https://github.com/ethereum/execution-apis/pull/762)) and Geth behavior. + +**What changed:** + +| Field | Before (Erigon) | After (Erigon / Geth / Spec) | +|-------|-----------------|------------------------------| +| Memory in trace | `disableMemory` (default: included) | `enableMemory` (default: excluded) | +| Return data in trace | `disableReturnData` (default: included) | `enableReturnData` (default: excluded) | + +The change is **twofold**: +1. The JSON key is renamed (`disable*` → `enable*`). +2. The default value is inverted: previously memory and return data were **included** by default (opt-out model); now they are **excluded** by default (opt-in model), matching the spec and Geth. + +**Migration:** + +```jsonc +// Before — disable memory explicitly +{ "disableMemory": true } + +// After — enable memory explicitly +{ "enableMemory": true } + +// Before — memory included by default (no flag needed) +{} + +// After — must opt in +{ "enableMemory": true } +``` + +Affected RPC methods: `debug_traceTransaction`, `debug_traceBlockByHash`, `debug_traceBlockByNumber`, `debug_traceCall`. + +--- + ## [3.3.0] – 2025-11-17 ### Added diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index 82688064efd..41b6a60fca3 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -121,10 +121,10 @@ func Main(ctx *cli.Context) error { if ctx.Bool(TraceFlag.Name) { // Configure the EVM logger logConfig := &trace_logger.LogConfig{ - DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - DisableMemory: ctx.Bool(TraceDisableMemoryFlag.Name), - DisableReturnData: ctx.Bool(TraceDisableReturnDataFlag.Name), - Debug: true, + DisableStack: ctx.Bool(TraceDisableStackFlag.Name), + EnableMemory: !ctx.Bool(TraceDisableMemoryFlag.Name), + EnableReturnData: !ctx.Bool(TraceDisableReturnDataFlag.Name), + Debug: true, } var prevFile *os.File // This one closes the last file diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go index cd3a40443b3..8283ababd43 100644 --- a/cmd/evm/runner.go +++ b/cmd/evm/runner.go @@ -151,11 +151,11 @@ func runCmd(ctx *cli.Context) error { log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int(VerbosityFlag.Name)), log.StderrHandler)) } logconfig := &logger.LogConfig{ - DisableMemory: ctx.Bool(DisableMemoryFlag.Name), - DisableStack: ctx.Bool(DisableStackFlag.Name), - DisableStorage: ctx.Bool(DisableStorageFlag.Name), - DisableReturnData: ctx.Bool(DisableReturnDataFlag.Name), - Debug: ctx.Bool(DebugFlag.Name), + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), + Debug: ctx.Bool(DebugFlag.Name), } var ( diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go index 3667349d023..6e6ec05e730 100644 --- a/cmd/evm/staterunner.go +++ b/cmd/evm/staterunner.go @@ -68,10 +68,10 @@ func stateTestCmd(ctx *cli.Context) error { // Configure the EVM logger config := &logger.LogConfig{ - DisableMemory: ctx.Bool(DisableMemoryFlag.Name), - DisableStack: ctx.Bool(DisableStackFlag.Name), - DisableStorage: ctx.Bool(DisableStorageFlag.Name), - DisableReturnData: ctx.Bool(DisableReturnDataFlag.Name), + EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), + DisableStack: ctx.Bool(DisableStackFlag.Name), + DisableStorage: ctx.Bool(DisableStorageFlag.Name), + EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), } cfg := vm.Config{} if machineFriendlyOutput { diff --git a/execution/tests/state_test.go b/execution/tests/state_test.go index 12bd8c6b206..9611a170c8b 100644 --- a/execution/tests/state_test.go +++ b/execution/tests/state_test.go @@ -174,7 +174,7 @@ func withTrace(t *testing.T, test func(vm.Config) error) { t.Error(err) buf := new(bytes.Buffer) w := bufio.NewWriter(buf) - tracer := logger.NewJSONLogger(&logger.LogConfig{DisableMemory: true}, w) + tracer := logger.NewJSONLogger(&logger.LogConfig{EnableMemory: false}, w) config.Tracer = tracer.Tracer().Hooks err2 := test(config) if !reflect.DeepEqual(err, err2) { diff --git a/execution/tracing/tracers/logger/json_stream.go b/execution/tracing/tracers/logger/json_stream.go index 8a45c4a7210..d1184812c51 100644 --- a/execution/tracing/tracers/logger/json_stream.go +++ b/execution/tracing/tracers/logger/json_stream.go @@ -23,6 +23,7 @@ import ( "github.com/holiman/uint256" "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/common/hexutil" "github.com/erigontech/erigon/execution/tracing" "github.com/erigontech/erigon/execution/tracing/tracers" "github.com/erigontech/erigon/execution/types" @@ -196,7 +197,7 @@ func (l *JsonStreamLogger) OnOpcode(pc uint64, typ byte, gas, cost uint64, scope } l.stream.WriteArrayEnd() } - if !l.cfg.DisableMemory { + if l.cfg.EnableMemory { l.stream.WriteMore() l.stream.WriteObjectField("memory") l.stream.WriteArrayStart() @@ -212,6 +213,11 @@ func (l *JsonStreamLogger) OnOpcode(pc uint64, typ byte, gas, cost uint64, scope } l.stream.WriteArrayEnd() } + if l.cfg.EnableReturnData && len(rData) > 0 { + l.stream.WriteMore() + l.stream.WriteObjectField("returnData") + l.stream.WriteString(hexutil.Encode(rData)) + } if outputStorage { l.stream.WriteMore() l.stream.WriteObjectField("storage") diff --git a/execution/tracing/tracers/logger/json_stream_test.go b/execution/tracing/tracers/logger/json_stream_test.go index ce9ba96d6d6..2aa5bba1420 100644 --- a/execution/tracing/tracers/logger/json_stream_test.go +++ b/execution/tracing/tracers/logger/json_stream_test.go @@ -66,6 +66,10 @@ func (m *mockIBS) GetRefund() mdgas.MdGas { return mdgas.MdGas{} } // It closes the stream the same way ExecuteTraceTx does after execution. // storageKey/storageVal are pushed onto the stack for SSTORE (top=key, below=val). func captureOnOpcode(t *testing.T, cfg *LogConfig, memory []byte, storageKey, storageVal *common.Hash) map[string]json.RawMessage { + return captureOnOpcodeWithReturnData(t, cfg, memory, nil, storageKey, storageVal) +} + +func captureOnOpcodeWithReturnData(t *testing.T, cfg *LogConfig, memory []byte, rData []byte, storageKey, storageVal *common.Hash) map[string]json.RawMessage { t.Helper() var buf bytes.Buffer stream := jsonstream.New(&buf) @@ -84,7 +88,7 @@ func captureOnOpcode(t *testing.T, cfg *LogConfig, memory []byte, storageKey, st scope.stack = []uint256.Int{val, key} // bottom=val, top=key } - l.OnOpcode(0, byte(op), 100, 3, scope, nil, 1, nil) + l.OnOpcode(0, byte(op), 100, 3, scope, rData, 1, nil) // Mirror what ExecuteTraceTx does to close the stream after execution. stream.WriteArrayEnd() @@ -153,7 +157,7 @@ func TestJsonStreamLogger_MemoryEncoding(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - obj := captureOnOpcode(t, nil, tt.memory, nil, nil) + obj := captureOnOpcode(t, &LogConfig{EnableMemory: true}, tt.memory, nil, nil) raw, ok := obj["memory"] if !ok { t.Fatal("missing 'memory' field") @@ -201,6 +205,54 @@ func TestJsonStreamLogger_StorageEncoding(t *testing.T) { } } +// TestJsonStreamLogger_EnableMemory verifies that the memory field is present when +// EnableMemory is true and absent when false (the default). +func TestJsonStreamLogger_EnableMemory(t *testing.T) { + mem := bytes.Repeat([]byte{0xab}, 32) + + t.Run("enableMemory=true includes memory field", func(t *testing.T) { + obj := captureOnOpcode(t, &LogConfig{EnableMemory: true}, mem, nil, nil) + if _, ok := obj["memory"]; !ok { + t.Error("expected 'memory' field to be present, but it was absent") + } + }) + + t.Run("enableMemory=false excludes memory field", func(t *testing.T) { + obj := captureOnOpcode(t, &LogConfig{EnableMemory: false}, mem, nil, nil) + if _, ok := obj["memory"]; ok { + t.Error("expected 'memory' field to be absent, but it was present") + } + }) +} + +// TestJsonStreamLogger_EnableReturnData verifies that the returnData field is present +// when EnableReturnData is true and absent when false (the default). +func TestJsonStreamLogger_EnableReturnData(t *testing.T) { + rData := []byte{0xde, 0xad, 0xbe, 0xef} + + t.Run("enableReturnData=true includes returnData field", func(t *testing.T) { + obj := captureOnOpcodeWithReturnData(t, &LogConfig{EnableReturnData: true}, nil, rData, nil, nil) + raw, ok := obj["returnData"] + if !ok { + t.Fatal("expected 'returnData' field to be present, but it was absent") + } + var got string + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("cannot parse returnData: %v", err) + } + if got != "0xdeadbeef" { + t.Errorf("returnData: got %s, want 0xdeadbeef", got) + } + }) + + t.Run("enableReturnData=false excludes returnData field", func(t *testing.T) { + obj := captureOnOpcodeWithReturnData(t, &LogConfig{EnableReturnData: false}, nil, rData, nil, nil) + if _, ok := obj["returnData"]; ok { + t.Error("expected 'returnData' field to be absent, but it was present") + } + }) +} + // TestStructLog_ErrorOmitempty verifies that the 'error' field is omitted from // MarshalJSON output when there is no error, and present when there is. func TestStructLog_ErrorOmitempty(t *testing.T) { diff --git a/execution/tracing/tracers/logger/logger.go b/execution/tracing/tracers/logger/logger.go index 26832adafbe..77ef9cbb5bd 100644 --- a/execution/tracing/tracers/logger/logger.go +++ b/execution/tracing/tracers/logger/logger.go @@ -52,12 +52,12 @@ func (s Storage) Copy() Storage { // LogConfig are the configuration options for structured logger the EVM type LogConfig struct { - DisableMemory bool `json:"disableMemory"` // disable memory capture - DisableStack bool `json:"disableStack"` // disable stack capture - DisableStorage bool `json:"disableStorage"` // disable storage capture - DisableReturnData bool `json:"disableReturnData"` // disable return data capture - Debug bool `json:"debug"` // print output during capture end - Limit int `json:"limit"` // maximum length of output, but zero means unlimited + EnableMemory bool `json:"enableMemory"` // enable memory capture + DisableStack bool `json:"disableStack"` // disable stack capture + DisableStorage bool `json:"disableStorage"` // disable storage capture + EnableReturnData bool `json:"enableReturnData"` // enable return data capture + Debug bool `json:"debug"` // print output during capture end + Limit int `json:"limit"` // maximum length of output, but zero means unlimited // Chain overrides, can be used to execute a trace using future fork rules Overrides *chain.Config `json:"overrides,omitempty"` } @@ -194,7 +194,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope // Copy a snapshot of the current memory state to a new buffer var mem []byte - if !l.cfg.DisableMemory { + if l.cfg.EnableMemory { mem = make([]byte, len(memory)) copy(mem, memory) } @@ -236,7 +236,7 @@ func (l *StructLogger) OnOpcode(pc uint64, opcode byte, gas, cost uint64, scope storage = l.storage[contractAddr].Copy() } var rdata []byte - if !l.cfg.DisableReturnData { + if l.cfg.EnableReturnData { rdata = make([]byte, len(rData)) copy(rdata, rData) } diff --git a/execution/tracing/tracers/logger/logger_json.go b/execution/tracing/tracers/logger/logger_json.go index 71bfe89c29f..b7d93427c45 100644 --- a/execution/tracing/tracers/logger/logger_json.go +++ b/execution/tracing/tracers/logger/logger_json.go @@ -81,7 +81,7 @@ func (l *JSONLogger) OnOpcode(pc uint64, typ byte, gas, cost uint64, scope traci RefundCounter: l.env.IntraBlockState.GetRefund().Total(), Err: err, } - if !l.cfg.DisableMemory { + if l.cfg.EnableMemory { log.Memory = memory } if !l.cfg.DisableStack { @@ -92,6 +92,9 @@ func (l *JSONLogger) OnOpcode(pc uint64, typ byte, gas, cost uint64, scope traci } log.Stack = logstack } + if l.cfg.EnableReturnData { + log.ReturnData = rData + } _ = l.encoder.Encode(log) }