mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
RED HIGH (the decentralization path). K is clamped to the live validator count at CONSTRUCTION (bftCommittee) and ChainConsensus.k is written ONLY at NewChainConsensus — never re-clamped. A chain that LAUNCHES single-validator (count=1 → K=1) and later ADDS validators via its PoS staking contract kept validator-1's K stuck at 1, so it would keep taking the K==1 path and finalize UNILATERALLY (a synthesized 1-of-1 cert, or a single-signer stake cert at alpha=1) while validators 2..N require a real k=N quorum and reject it → validator-1 serves a DIVERGENT finalized chain = cross-node double-cert fork. FIX (RED's structural (a) + the re-clamp): - ChainConsensus.Reclamp(k, alpha): setter to move the committee to a changed live set (beta unchanged; degenerate k/alpha ignored). - Transitive.reclampCommitteeLocked(): re-reads the LIVE validator count (wired from the sampler) and re-clamps k/alpha UP toward presetK (= the pre-construction-clamp target). UP-ONLY — never auto-shrinks, so a transient low read (syncing node / unresolved set) can never drop the quorum and enable a unilateral finalize. Called before the single-validator vs multi-validator decision in buildBlocksLocked + TryAccept. - buildSingleValidatorCertLocked: belt-and-suspenders guard — REFUSE to synthesize a 1-of-1 cert when the live count > 1 (returns ZERO → acceptWithCertCore requires a real quorum). count<1 (unwired / --dev / unresolved set) preserves the genuine single- validator + n=1 path. - Runtime wires presetK (pre-clamp K) + liveValidatorCount (sampler.Count) — entirely in the consensus layer; no node change. Unwired for tests/--dev (committee fixed). Regression tests: single→multi transition — after validator-2 joins, K re-clamps to 2 and block 2 does NOT finalize unilaterally (fail-secure stall, not fork); unwired sampler still synthesizes (n=1 fix preserved). Full engine/chain suite green incl -race. v1.35.12. Co-authored-by: Hanzo Dev <[email protected]>