mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
The block-1082879 mainnet freeze: 600+ outer proposervm wrappers of ONE inner block piled up at a height and never converged. Two determinism holes in the fork-choice let different validators pick different representatives, so prevotes never aligned and no POL/cert ever formed. 1. convergedWinnerAtHeightLocked chose the winner among EQUAL-canonical siblings by Go map-iteration order (`canon.Compare(winnerCanon) < 0` never updates on a tie) — nondeterministic per node. Add the outer-id tie-break so equal-canonical aliases resolve to the SAME winner everywhere. 2. It grouped children by the OUTER parentID. A forked parent (same inner block, different outer wrappers) split its children across groups, and the view-change pass keyed `parents[height]=parentID` last-writer-wins in random map order — so nodes computed winners over different sibling subsets. Group by the parent CANONICAL identity instead; enumerate distinct (height, parent-canonical) groups with a deterministic lowest-outer-id representative. parentIsProvenLoserLocked gets the same canonical-grandparent grouping so every fork-choice / winner / parent path collapses aliases to one deterministic representative before making progress decisions. Outer IDs stay transport aliases only. New canonical_representative_test.go pins it: equal-canonical winner determinism over 600 wrappers x 400 randomized maps, forked-parent children converging as one group, genuine-fork non-merge, accepted-tip children matched. All fail on the pre-fix code (proven by stash), pass after. Safety unchanged: an equal-canonical alias (sc==pc) is never a loser; only strictly-lower canonicals win; no vote manufactured. Co-authored-by: Hanzo Dev <[email protected]>