You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Documents the traces.error/revert_reason detection arc closed by #151. geth
callTracer ground truth and the raw Portal /finalized-stream both report the
succeeded child frame [0,1] as null; ponder's RPC path cascades the reverted
ancestor's error onto it (derived DFS post-processing in rpc/actions.ts); #151
ports that same cascade into the Portal historical assemble path so the store
is byte-identical to the RPC path for drop-in determinism (the #110 access_list
precedent). End-to-end: a fresh merged-main backfill of [20350978,20351278]
diffs 424/424 rows byte-identical to the RPC store (22 non-null errors each,
0 mismatch, no over-cascade).
Closes the "documented in VALIDATION.md §5" reference from the INV-20 catalog
row. Candid on the geth-literalism vs RPC-parity tradeoff; no defect claim
against upstream.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A campaign diff surfaced the Portal historical backfill storing `traces.error = NULL` on a trace frame where the parallel RPC-path store held `error="execution reverted"` and `revert_reason="Too little received"`. The concrete case is Ethereum mainnet block 20351061, transaction index 26 (a DEX-aggregator contract `0x364e94a1…`). The transaction reverts with "Too little received" after attempting a Uniswap-V2 swap at trace path `[0,1]` then a V3 swap at `[0,2]`. The differ matched the V2 router frame `[0,1]`. The same shape appeared on block 20351088, tx 33 (corroborated by the differ record; the independent geth/Portal ground-truth capture below is for block 20351061).
1657
+
1658
+
Ground truth from `debug_traceBlockByNumber` with `callTracer` (one paid request, frozen in the operator's evidence archive) attributes the error to paths `[]`, `[0]`, `[0,2]` and reports `[0,1] = null`. The revert originates at `[0,2]` (the V3 swap), NOT at `[0,1]`. A raw Portal `/finalized-stream` capture (full trace fields, match-all, $0) agrees with the geth ground truth on the per-frame error attribution — errored `[]`, `[0]`, `[0,2]`; `[0,1]` null. The fork's pre-fix store faithfully carried this null (the pre-fix FAIL record: fresh fork Portal = null, RPC oracle = "execution reverted", 2/2 DIFFER).
1659
+
1660
+
| traceAddress | geth callTracer | Portal raw |pre-#151 fork | RPC baseline store |
The root cause of the divergence is ponder's RPC path, not the Portal path. Ponder's RPC-path trace ingestion cascades a reverted ancestor frame's error onto its succeeded descendant frames. In `rpc/actions.ts` (~lines 186–219), a DFS pre-order error propagation applies an ancestor's error to its descendants. Frame `[0,1]` succeeded, but its ancestor `[0]` reverted, so ponder's RPC store smears `[0]`'s "execution reverted" onto `[0,1]`. This is a deterministic derived post-processing behavior, not raw geth. Strictly on raw geth-literalism, the Portal raw path was the MORE faithful of the two; ponder's RPC store attributes an error where neither geth's own callTracer nor the Portal data does.
1668
+
1669
+
The resolution in PR [#151](../../pull/151) (merged `58e3ad9`) makes the Portal historical assemble path port the same ancestor-error cascade (`cascadeTraceErrors`), ensuring the Portal store is a byte-identical match to ponder's RPC store. We deliberately chose RPC-parity (drop-in determinism) over geth-literalism: an indexer must produce the SAME `traces.error` regardless of whether the RPC or Portal backend was used, or intra-deployment determinism breaks. This mirrors the [#110](../../pull/110)`access_list` "match the RPC path" decision. The cascade algorithm is byte-identical to upstream's RPC cascade (own-error-wins, transitive, sibling-isolated, DFS pre-order). This behavior is pinned by invariant **INV-20** (+ INV-20(a)/(b)/(d) oracle tests, mutation-verified). INV-20(d) pins the UNCONDITIONAL `revert_reason` assignment matching upstream — a proposed guard was rejected because it would have DIVERGED the Portal store from the RPC store.
1670
+
1671
+
End-to-end verification on merged main confirmed the change. A fresh backfill of window [20350978, 20351278] diffed against the preserved RPC-path store yielded 424 trace rows in both, identical row sets, all 424 matching on `error` + `revert_reason`, 22 non-null-error rows on each side, 0 mismatches, and no over-cascade (full-window diff archived in the operator's evidence set). Pre-#151 the same frames were 2/2 DIFFER; the flip to a full match is caused by #151, not a transient artifact.
1672
+
1673
+
**Upstream observation.** Ponder's RPC-path cascade attributes an error at `[0,1]` where geth's own `callTracer` returns null. This is a neutral observation about a derived-attribution difference — not a defect claim; the fork matches the behavior for parity.
1674
+
1654
1675
---
1655
1676
1656
1677
## 6. Current status — what a reader can rely on today
0 commit comments