Skip to content

Commit 0572ac7

Browse files
docs: record 2026-07-28 catalyst-testnet genesis reset
1 parent 2a1810a commit 0572ac7

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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`.

docs/testnet-handoff-catalyst-testnet.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ This file is intended to be handed to an external agent/tooling team building a
9797
> and the four diagnostic-only logging commits deployed (`e7d21f6`, `16eacb1`, `c0a6d88`, `bf27f31`)
9898
> so the next occurrence is fully traceable. Genesis hash unchanged; re-index from cycle `89256698`.
9999
100+
> **2026-07-28 genesis reset.** Root cause of the 2026-07-27 divergence found: `asia` fell behind a
101+
> real, peer-served cycle range, and `stale_observed_head_reset_ms()`'s liveness backstop silently
102+
> reset past the gap and resumed production without ever backfilling it — permanently dropping
103+
> those cycles' `compensation_entries` credit from its own balance history. See
104+
> `docs/explorer-genesis-reset-2026-07-28.md`. Fixed same day (commit `2a1810a`): the reset is now
105+
> capped by gap size (`stale_observed_head_max_forgivable_gap`, default 3 cycles) — above the cap
106+
> it refuses to reset and stays loudly, visibly deferred instead. Genesis hash unchanged; re-index
107+
> from cycle `89262084`.
108+
100109
## Network identity (MUST match)
101110

102111
- **network_id**: `catalyst-testnet`

0 commit comments

Comments
 (0)