ITADN

`trace_call`: trace_call initializes the trace field as a non-null empty array even when trace was not requested, instead of returning null for every unrequested trace type

#23326OpenBenWhite713 创建于 4 天前
type:bug
B
BenWhite713commented
## Summary trace_call initializes the trace field as a non-null empty array even when trace was not requested, instead of returning null for every unrequested trace type. The behavior was reproduced against **erigon/3.7.0/linux-arm64/go1.26.5**. The implementation cited below is pinned to commit [`993149925e45`](https://github.com/erigontech/erigon/commit/993149925e454e7f15113a11e78f48af4f23a7a9). ## Current documentation The following wording was still present in the official documentation on **2026-08-16**: **`trace_call`** — [official documentation](https://docs.erigon.tech/interacting-with-erigon/trace) > `Object` containing `output`, `stateDiff`, `trace[]`, `vmTrace`. Each requested trace type is populated; the others are `null`. See [Response Fields Reference](#response-fields-reference) for full field semantics. ## What the endpoint does instead | Method | Documented behavior | Runtime-confirmed behavior | |---|---|---| | `trace_call` | Trace types in the returned object that are not requested are `null`. | Judge C1 and the runtime plan align on a stateDiff-only request exposing an unrequested trace as [] rather than null. | ## Relevant implementation **1. [`rpc/jsonrpc/trace_adhoc.go:1147-1164`](https://github.com/erigontech/erigon/blob/993149925e454e7f15113a11e78f48af4f23a7a9/rpc/jsonrpc/trace_adhoc.go#L1147-L1164) — `TraceAPIImpl.Call`** ```go defer cleanup() traceResult := &TraceCallResult{Trace: []*ParityTrace{}} var traceTypeTrace, traceTypeStateDiff, traceTypeVmTrace bool for _, traceType := range traceTypes { switch traceType { case TraceTypeTrace: traceTypeTrace = true case TraceTypeStateDiff: traceTypeStateDiff = true case TraceTypeVmTrace: traceTypeVmTrace = true default: return nil, fmt.Errorf("unrecognized trace type: %s", traceType) } } if traceTypeVmTrace { traceResult.VmTrace = &VmTrace{Ops: []*VmTraceOp{}} ``` **2. [`rpc/jsonrpc/trace_adhoc.go:269-283`](https://github.com/erigontech/erigon/blob/993149925e454e7f15113a11e78f48af4f23a7a9/rpc/jsonrpc/trace_adhoc.go#L269-L283) — `parseOeTracerConfig`** ```go func parseOeTracerConfig(traceConfig *config.TraceConfig) (OeTracerConfig, error) { if traceConfig != nil && traceConfig.Tracer != nil && *traceConfig.Tracer != "" { return OeTracerConfig{}, errors.New("trace_* does not support custom tracers; use debug_* (e.g. debug_traceTransaction) for named or JS tracers") } if traceConfig == nil || traceConfig.TracerConfig == nil || *traceConfig.TracerConfig == nil { return OeTracerConfig{}, nil } var config OeTracerConfig if err := json.Unmarshal(*traceConfig.TracerConfig, &config); err != nil { return OeTracerConfig{}, err } return config, nil } ``` ## Reproduction A live trace_call can request only stateDiff and expose the unrequested trace field. The documentation predicts null, while the implementation initializes Trace as a non-nil slice and therefore predicts an array. Prepare the disposable dev node as follows: 1. From the funded dev account, deploy creation bytecode `0x6001600c60003960016000f300` (runtime executes a single STOP and touches no storage); refer to the deployed address as `{{stop_target.address}}`. Bash-compatible JSON-RPC probes are shown below. Replace template values such as `{{tx_hash}}` with values produced by the setup before running them: ```bash #!/usr/bin/env bash set -euo pipefail RPC_URL="${RPC_URL:-http://127.0.0.1:8545}" echo '=== subject: trace_call ===' curl -sS -H 'Content-Type: application/json' --data @- "$RPC_URL" <<'JSON' { "jsonrpc": "2.0", "id": 1, "method": "trace_call", "params": [ { "from": "{{dev}}", "to": "{{stop_target.address}}", "data": "0x" }, [ "stateDiff" ] ] } JSON echo echo '=== control: eth_getCode ===' curl -sS -H 'Content-Type: application/json' --data @- "$RPC_URL" <<'JSON' { "jsonrpc": "2.0", "id": 2, "method": "eth_getCode", "params": [ "{{stop_target.address}}", "latest" ] } JSON echo echo '=== control: trace_call ===' curl -sS -H 'Content-Type: application/json' --data @- "$RPC_URL" <<'JSON' { "jsonrpc": "2.0", "id": 3, "method": "trace_call", "params": [ { "from": "{{dev}}", "to": "{{stop_target.address}}", "data": "0x" }, [ "trace" ] ] } JSON echo ``` The reproduction tests this documented clause: > Each requested trace type is populated; the others are `null`. ## Observed behavior The observed response matched the implementation-side prediction: - `response`: `no_error` - `$.result.stateDiff`: `jsonpath_exists` - `$.result.stateDiff`: `jsonpath_type` — `{"type": "object"}` - `$.result.trace`: `jsonpath_exists` - `$.result.trace`: `jsonpath_type` — `{"type": "array"}` The documentation-side prediction differed as follows: - `response`: `no_error` - `$.result.stateDiff`: `jsonpath_exists` - `$.result.stateDiff`: `jsonpath_type` — `{"type": "object"}` - `$.result.trace`: `jsonpath_exists` - `$.result.trace`: `jsonpath_type` — `{"type": "null"}` Representative subject response: ```json { "result": { "output": "0x", "stateDiff": { "0x71562b71999873db5b286df957af199ec94617f7": { "balance": "=", "code": "=", "nonce": { "*": { "from": "0x1", "to": "0x2" } }, "storage": {} } }, "trace": [ { "action": { "from": "0x71562b71999873db5b286df957af199ec94617f7", "callType": "call", "gas": "0x2fa9e78", "input": "0x", "to": "0x3a220f351252089d385b29beca14e27f204c296a", "value": "0x0" }, "result": { "gasUsed": "0x0", "output": "0x" }, "subtraces": 0, "traceAddress": [], "type": "call" } ], "vmTrace": null }, "transport": "http" } ``` ## Expected behavior The public contract and endpoint behavior should agree. In particular: - `trace_call` should satisfy the documented behavior: Trace types in the returned object that are not requested are `null`. ## Impact Tracing and debugging clients that follow the documentation can reject valid responses, read the wrong field, or draw an incorrect conclusion about the executed operation.
0 条评论