mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
Harden the single-node decomplect into a GLOBAL invariant and prove the finality quorum scales at every denominator. LOCK DOCTRINE (the deadlock class, not one instance): - Transitive.mu documents the exact rule: never call VM.Accept, ReceiveVote, a network send, poll delivery, certificate callbacks, or any external callback while holding it — queue or release first. - Moved EVERY remaining under-lock call-out out of t.mu: Propose/RequestVotes (network sends) in buildBlocksLocked (main + rebuild paths), VM.BuildBlock (loop top), and the boot VM.LastAccepted/GetBlock seed. All call-outs now run in one unlocked region after capturing what they need under the lock. - engineMutex: a sync.RWMutex that records the write-holder goroutine when a test enables it (one atomic load per Lock/Unlock in production; no goID cost). - TestBlue_NoCallOutUnderEngineLock drives the real NewRuntime build→propose→ finalize flow (K==1 and K>1) with every VM/gossiper/block call-out instrumented and asserts NONE runs under the engine write lock. QUORUM ACCEPTANCE MATRIX (runtime level, real gossiperProposer): - n=1..10 unweighted: exactly q=floor(2n/3)+1 online DECIDES; q-1 does NOT finalize and does NOT false-accept. - Weighted stake: same-voter-count no/yes pairs where >2/3-by-STAKE is the sole discriminator ([40,30,30] 60/70; [34,33,33] 66/67; [50,20,20,10] pivotal-50). - alpha-of-K and stake are SEPARATE composed gates: a 2-of-4 cert is rejected at the threshold floor even at 70% stake (disjoint-quorum fork defence). - duplicate-counted-once, equivocation-rejected, all-online/one-down per quorum, late-vote buffered-then-drained, VM.Accept fail-closed (retained, no half-apply; recovery via the phantom-floor boot reconcile). Live finality path (VerifyWeighted, bftCommittee) UNCHANGED — frozen as verified. Full engine/chain suite green; race-clean on the concurrency-sensitive tests. Co-authored-by: Hanzo Dev <[email protected]>