Files
zeekayandHanzo Dev 82ab92a70f cert: give the export rung a distinct-signer floor, so two thirds means two thirds of the parties
The Quasar rung read only stake. Two thirds of the stake is one signature
wherever two thirds of the stake is one validator, so a whale holding a
supermajority could mint an export-grade certificate alone — and export-grade
finality is what a bridge, a settlement and a cross-chain message admit on. A
certificate one key can produce is not a claim that a Byzantine supermajority
agreed, however much weight stands behind the key.

VerifyWeighted now demands config.TwoThirdsCount(n) = floor(2n/3)+1 DISTINCT
signers of a Quasar certificate as well as the stake. Two independent
predicates, neither sufficient alone, the same shape Nova has one rung up.

TwoThirdsCount is the count that value has always been, given its own name and
one home. It is derived from TwoThirdsStakeFloor over unit weights rather than
restated, so the count and the stake predicate cannot drift. It replaces three
spellings of the same closed form that had grown apart in reach if not yet in
value: config.EqualStakeSupermajorityThreshold (the alpha sizer), the private
chain.quasarQuorum (the attestor's assembly trigger), and the clause that was
missing from the verifier entirely.

Stake is read first and the count second. Both must hold, so the order names
only which clause a refusal reports: on an equal-stake set the two are one bar
in two units and bind together, and the count clause binds ALONE only where the
weights are lopsided — so reaching it means the stake was there and the signers
were not. Every verdict the R3 corpus froze is unchanged.

Liveness. Nothing that exports today stops exporting. QuasarAttestor, the only
producer of export certificates, already assembled at exactly floor(2n/3)+1
attestations, and FeasibleParams already sized alpha — the floor
HandleIncomingCert applies to a gossiped Quasar cert — to the same number.
Producer, gossip door and verifier now read one function, so the verifier's new
clause refuses nothing the engine can build and exactly what it never could.
quasar_count_floor_test.go holds all three to that.

The tests that moved were the ones written on three seats, where floor(2n/3)+1
is three and no two-signer certificate can isolate the stake half any more.
They ask the same question on four.

Rust: the same clause in Cert::verify_quasar_supermajority, the same name.
1.22.4 -> 1.22.5 — this changes what the crate accepts.

Also, at the already-admitted door: the zero-weight refusal is justified by
agreement with Register, not by the phantom-signer argument alone. A seat whose
key does not decode can never sign and is refused anyway, because the weight is
read before the key is — and deferring it would make admission depend on which
keys a node's crypto build can parse.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-01 17:39:39 -07:00

97 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// quorum.go — the NOVA-tier thresholds, as a clean function of the live count n.
//
// This file owns ONLY the Nova (β-ignition / local-execution) rung of the ladder
// in finality.go. The QUASAR rung — the ⅔ certificate — has exactly ONE definition
// and it is NOT here: config.TwoThirdsStakeFloor is its stake half (the strict >⅔
// predicate, overflow-safe) and config.TwoThirdsCount is the SAME supermajority in
// seats (⌊2n/3⌋+1), and QuorumCert.VerifyWeighted demands both over DISTINCT
// signers. Nova must never re-express either; both of its floors deliberately sit
// BELOW Quasar's (majority < ⅔, and NovaSignerFloor saturates at 3 where the export
// count grows with n), because Nova authorizes local execution while Quasar
// authorizes export.
package chain
// NovaQuorum is the majority ⌊n/2⌋+1 the sampler needs to IGNITE a block to Nova
// (acceptance, local execution). It tolerates ⌊(n-1)/2⌋ crash faults — the MOST a net
// of n can lose and still make safe local progress under the crash-fault model
// (foundation-run, non-equivocating validators): any two majorities intersect and a
// non-equivocating node cannot back two conflicting blocks, and a partition lets at
// most one side hold a majority. It is DELIBERATELY below the Quasar ⅔ stake gate —
// a single equivocator can fork a bare majority, so Nova is never a Byzantine
// guarantee (that is Quasar's job). n<1 → 1 (a lone node self-ignites; never 0, which
// would let a transiently-empty validator view self-accept — the 1085013 fork class).
func NovaQuorum(n int) int {
if n < 1 {
return 1
}
return n/2 + 1
}
// minBFTCommittee is the smallest Byzantine-fault-tolerant committee: K=4/α=3 with
// f=⌊(K−1)/3⌋=1, so 2α−K = 2 ≥ f+1. bftCommittee floors a shrinking committee here,
// and NovaSignerFloor reads the same constant so the "a lone node can never ignite"
// guard has ONE definition.
const minBFTCommittee = 4
// NovaSignerFloor is the minimum number of DISTINCT signers a Nova cert needs
// irrespective of how stake is distributed. The Nova gate proper is a strict
// majority of STAKE (config.HalfStakeFloor, enforced in QuorumCert.VerifyWeighted);
// this count is the independent guard the stake predicate cannot give — a validator
// holding a stake majority on its own would otherwise self-ignite, which is the
// 1085013 self-finality fork class. It is NovaQuorum of the minimal BFT committee
// (3), capped by the majority of the live set so a genuinely small chain (n≤3, dev)
// is not made unsatisfiable.
//
// On an equal-stake set the stake majority already implies ⌊n/2⌋+1 signers, so this
// floor never binds there; it binds exactly where the weights are lopsided.
//
// It is a SATURATING floor and the export rung's is not: Quasar demands
// config.TwoThirdsCount(n) = ⌊2n/3⌋+1, which grows with the set. That difference is
// the two rungs' different jobs. Nova is asking only "is this more than one party?"
// before letting a block drive local execution it can still reorg away, so a small
// constant answers it. Quasar is asking "did a Byzantine supermajority agree?"
// before letting a block leave the chain irreversibly, and that question has no
// answer smaller than the supermajority itself.
func NovaSignerFloor(n int) int {
if q := NovaQuorum(n); q < NovaQuorum(minBFTCommittee) {
return q
}
return NovaQuorum(minBFTCommittee)
}
// CrashTolerance is the number of simultaneous crash faults Nova ignition survives:
// n − NovaQuorum(n) = ⌈n/2⌉−1. Beyond it the sampler cannot reach a majority, so Nova
// PAUSES fail-closed (no ignition ⇒ no acceptance ⇒ no fork) and resumes the instant a
// node returns. Feeds the degraded-mode signal (responsive stake below NovaQuorum ⇒
// degraded, novaHeight stalls, quasarHeight stalls further behind).
func CrashTolerance(n int) int {
if n < 2 {
return 0
}
return n - NovaQuorum(n)
}
// NovaBeta is the β confidence depth: the number of CONSECUTIVE majority sampling
// rounds required to IGNITE Nova.
//
// - n==1: β=1 — a lone node ignites immediately; there are no peers to confirm
// against and any wait is a phantom-peer stall.
// - 2≤n: β=2 — one confirming round of HYSTERESIS so a transient one-round majority
// flip cannot ignite, WITHOUT the fragility of a deep β at zero margin (with only
// ⌊n/2⌋+1 alive, a single flap during a long β window resets the count and stalls
// liveness exactly when margin is thin). β is NOT a Byzantine-safety knob on a K=n
// net (no random subsampling ⇒ β cannot dilute an equivocator; that is the Quasar
// cert's role).
//
// Large permissionless nets (K<n, real subsampling) raise β to the classical negl(β)
// metastable depth — a set-size migration, not applied to the small trusted deploy.
func NovaBeta(n int) int {
if n <= 1 {
return 1
}
return 2
}