|
| 1 | +# catalyst-testnet genesis reset — 2026-07-28 (explorer/indexer action required) |
| 2 | + |
| 3 | +Root cause found and fixed this time (see below). `genesis_hash` **did not change** (unchanged |
| 4 | +since the 2026-07-23 reset). |
| 5 | + |
| 6 | +## TL;DR |
| 7 | + |
| 8 | +`applied_cycle` at or above **`89262084`** is current (post-reset). Only re-indexing from the new |
| 9 | +starting cycle is needed — no chain-identity change, no `state_root` formula change. |
| 10 | + |
| 11 | +## What happened |
| 12 | + |
| 13 | +Continuation of the investigation recorded in `docs/explorer-genesis-reset-2026-07-27.md`. That |
| 14 | +reset restored liveness but did not find the root cause; the accounts-fingerprint, |
| 15 | +`APPLY_REVERTED`, `waiting_pool_reward`, and `compensation_entries` diagnostics deployed the same |
| 16 | +day (commits `e7d21f6`, `16eacb1`, `c0a6d88`, `bf27f31`) were left running to catch the next |
| 17 | +occurrence live. They did, within hours. |
| 18 | + |
| 19 | +`asia` fell ~10 cycles behind a real, peer-served range — `eu`/`us`/`rpc` all had and correctly |
| 20 | +applied those cycles. After `stale_observed_head_reset_ms()`'s 60-second no-progress budget |
| 21 | +elapsed, that liveness backstop reset `asia`'s `observed_head_cycle` down to its own applied head |
| 22 | +and let it resume production — **without ever backfilling the abandoned range**. `asia` silently |
| 23 | +dropped ~10 real cycles' worth of `compensation_entries` credit from its own balance history |
| 24 | +forever, with no error raised about the gap itself (only a downstream `state_root` mismatch, |
| 25 | +cycles later, once the deficit was already permanent). Confirmed directly: |
| 26 | +`compensation_entries` for the origin cycle logged byte-identical on all four nodes (same |
| 27 | +`entries_hash`/`total_amount`) — proving the one cycle's own mutation was correct; the divergence |
| 28 | +was entirely in the *starting* balance `asia` applied it on top of. |
| 29 | + |
| 30 | +This backstop's original design (from an earlier liveness fix, 2026-07-01..04) assumed a |
| 31 | +"phantom/unreachable head" — a single bad or reordered gossip message, safe to forgive because |
| 32 | +nothing real is lost. That premise didn't hold here: the head was real and reachable, `asia`'s own |
| 33 | +fetch pipeline was just too slow, and the backstop paved over that instead of surfacing it. |
| 34 | + |
| 35 | +## Fix shipped in this reset (commit `2a1810a`) |
| 36 | + |
| 37 | +**`stale_observed_head_reset_ms()`'s reset is now capped by gap size, not just elapsed time.** A |
| 38 | +new `stale_observed_head_max_forgivable_gap()` (default 3 cycles) bounds how large a gap the |
| 39 | +backstop may silently abandon. Above that cap, it now refuses to reset — regardless of elapsed |
| 40 | +time or peer confirmation of just the next cycle — and stays deferred instead, logging a loud, |
| 41 | +repeating `error!` and a new counter |
| 42 | +(`consensus_observed_head_stale_reset_refused_gap_too_large_total`) so the stall is visible and |
| 43 | +operator-actionable rather than silent. The existing 2-second backfill loop keeps retrying |
| 44 | +regardless, so a node that can genuinely catch up still self-heals — this only closes the |
| 45 | +silent-data-loss path. |
| 46 | + |
| 47 | +## Verification |
| 48 | + |
| 49 | +Post-reset: single genesis hash |
| 50 | +(`0x32bceec02712a1184f788ce4aebf3472e98be2f09ffd5e356148e13a01f7ea9d`, unchanged), lockstep |
| 51 | +verified by the reset script and independently via direct RPC — all four nodes byte-identical at |
| 52 | +cycle `89262084`. |
| 53 | + |
| 54 | +## Still open |
| 55 | + |
| 56 | +This fix stops the specific silent-skip mechanism from recurring; it does not address *why* |
| 57 | +`asia`'s own LSU range-fetch pipeline is slow/flaky enough to fall behind in the first place. If |
| 58 | +`asia` (or any node) falls behind again and this time stays correctly deferred (loud error, no |
| 59 | +silent divergence) rather than resuming with a gap, that is the fetch-pipeline question — a |
| 60 | +liveness/performance issue, not a correctness one — worth investigating separately. All four |
| 61 | +diagnostic-logging commits from the 2026-07-27 investigation remain deployed on this rebuilt |
| 62 | +fleet. |
| 63 | + |
| 64 | +## What the explorer/indexer needs to do |
| 65 | + |
| 66 | +Same as every prior reset: no chain-identity update, just wipe and re-index from cycle `89262084` |
| 67 | +forward. |
| 68 | + |
| 69 | +## Endpoints |
| 70 | + |
| 71 | +Unchanged — see `docs/testnet-handoff-catalyst-testnet.md`. |
0 commit comments