Measured on the live chain by reading vanityData block by block: the last
anchor carrying a digest before the window is 13,267,792; heights 13,267,824
through 13,268,944 (37 anchor heights) carry the ordinary client string instead
of a digest; the digest resumes at 13,268,976. All 37 still carry an attached
seal certificate (2,620 to 3,282 byte headers), which is direct on-chain proof
that seal attachment and digest anchoring are two independent switches and were
in different states here.
What happened: several validators restarted inside a short window and were all
left holding no heard seals at the same time, while the next height was an
anchor height with a seal threshold, so no node could propose. Unblocking meant
disarming the anchor fleet-wide, which restarted block production immediately.
Blocks produced while disarmed also carry signer indices that are not strictly
increasing, because canonical ordering is applied on the anchoring path.
Why it matters to anyone running a node: those 37 heights are real, immutable
chain history. A node syncing from genesis meets them and correctly refuses
them if it carries a single anchoring configuration; nodes that were already
running do not stumble because the blocks are already in their database and are
not revalidated. So the package needs the historical disarmed windows, named
with exact bounds, in which the anchoring rules accept the header as given,
exactly like the base-fee lapse windows the second client already carries. The
fix is in progress; until it is proven by import past the window, this section
states the limit rather than hiding it.
The published patch carried a one-block boundary defect: the seal rule asks the
index-to-address map at the parent height (H-1) while the registry schedule
binds inclusively from H, so at the very first anchor the schedule was empty and
the lookup fell back to a head map that is empty on a node synced from genesis.
A valid certificate was rejected. Fixed in both lookup paths: when the schedule
has an entry at exactly blockNumber+1, answer from that entry's verified bound
registry. The boundary stays exactly one block.
Proven by import, not by assertion: the package-built node was stuck at
13,013,999 with 6,646 rejections; with the fix it crossed 13,014,000 in 42
seconds and imported 253,729 further blocks through the anchor era with zero
rejections. Tests: new PqParentHeightAlignmentTest 3/3, consensus:common 391/0,
consensus:qbft 217/0. Negative control measured: disarm the condition and the
measuring test goes red; restore it and it is green.
Patch verified to apply cleanly on a pristine upstream checkout, alone and in
series with 0001/0004/0005; file list is the previous 75 plus the new test, and
every untouched section is byte-identical to the old patch. The anchor/ source
copy in this package was brought to the same state so the two cannot diverge.
Honest limit, recorded in IMPORT-PROOF-STARE: further along, at 13,267,729, the
node stops again for a DIFFERENT reason. Blocks in a band there carry an
attached certificate whose vanityData is the ordinary client string rather than
the digest, at heights a single static interval treats as anchor heights. The
best-supported reading is that the fleet ran a different anchoring configuration
in that window (these values are not consensus-bound), so the package needs a
HISTORICAL anchoring schedule, not one value. Stated rather than implied.
A from-genesis sync of the patches-only build reached the first real anchor
(13,014,000, certificate index 0) and was rejected there: PqAnchorSealsRule
says index 0 is unbound, though the registry the node itself bound contains
key 0 and production validates the same block. Two paths over one registry
that disagree. Fix (align the index->address mapping in 0003 with production
source) needs a dedicated binary session with a negative control. Written
honestly; RUN-A-NODE already flags the build as a follower until a full
import proof. Trying the proof for real found the missing link in one evening.