Files
consensus/config/red_floor_differential_test.go
zeekay d24ee92d1b consensus(finality): collapse acceptance onto one cert-gated finalize path
THE WEDGE: finality could be triggered by a raw α-of-K vote COUNT on three
ungated paths (processPendingBlocks, DrainAccepted, and the K==1 short-circuit),
none of which checked the stake-weighted ⅔ predicate. On a skewed-stake set an
α-count quorum that did NOT hold ⅔ of stake could finalize — a safety break that
also manifested as the block-height wedge (a block "accepted" by count but not
by a verifiable cert, leaving the chain unable to make verifiable progress).

THE FIX (one rule, one place): no VerifiedQuorumCert, no finality.

- verified_cert.go: introduce VerifiedQuorumCert — the unforgeable finality
  authority token. Its only field is unexported, so no other file can mint a
  non-zero one with a struct literal. The sole multi-validator producer is
  BuildVerifiedQuorumCert, which runs the FULL predicate (VerifyWeighted, strict
  >⅔ of stake when a StakeSource is wired; count-only Verify on equal-stake
  chains) before wrapping. A raw count can never produce this token.

- AcceptWithCert is now the SOLE finalizer. It takes a VerifiedQuorumCert by
  value and refuses the zero value (ErrNoVerifiedQC). It is impossible to call
  the finalize body without first clearing the predicate. The old
  finalizePendingLocked body is unchanged — only its reachability is gated.

- Every trigger (vote arrived, re-poll, pending-queue change, build/verify,
  poll timeout, gossiped-in cert) routes to TryAccept, which obtains a cert or
  returns ErrNoVerifiedQC and lets the trigger retry on its next tick. The
  incoming-cert path (quorum_topology.go) re-verifies then promotes via
  wrapVerifiedCert → AcceptWithCert — one finalization road for everything.

- Remove the 3 ungated finalize sites: processPendingBlocks and DrainAccepted
  no longer finalize on count; they signal liveness only. The K==1 path
  finalizes through BuildSingleValidatorCert (the 1-of-1 quorum), still
  cert-gated.

- Rename the count helper HasEnoughResponses -> HasEnoughResponsesForRetry to
  make its role unambiguous at every call site: it is a LIVENESS retry signal,
  never an acceptance authority (structural form of HIGH-3).

Tests: accept_cert_gate_test.go proves AcceptWithCert(zero cert)==ErrNoVerifiedQC,
that BuildVerifiedQuorumCert refuses sub-quorum / forged-signature vote sets, and
that a real ⅔ quorum finalizes. red_processpending_stake_bypass_test.go +
config/red_floor_differential_test.go pin the skewed-stake REJECT (the wedge) and
the liveness path. Green under -race.
2026-06-24 14:42:55 -07:00

79 lines
2.7 KiB
Go

// Copyright (C) 2019-2026, Lux Industries, Inc. All rights reserved.
// RED differential: prove TwoThirdsStakeFloor == the ORIGINAL inline cert math
// for all inputs incl. boundaries and near-2^64, and that it never overflows.
package config
import (
"math"
"math/rand"
"testing"
)
// origInlineFloor is the EXACT pre-refactor inline computation from
// QuorumCert.VerifyWeighted (git 056ead174^ engine/chain/quorum_cert.go).
func origInlineFloor(total uint64) uint64 {
q, r := total/3, total%3
twoThirdsFloor := 2 * q
if r == 2 {
twoThirdsFloor++
}
return twoThirdsFloor
}
// bigIntFloor is an independent oracle: floor(2*total/3) via 128-bit-safe math.
// 2*total can overflow uint64, so compute in the structurally-safe form.
func bigIntFloor(total uint64) uint64 {
// floor(2*total/3): use total/3 and 2*(total%3)/3 — but verify against a
// genuinely independent route: (2*total)/3 done in big-enough width.
hi := total / 3 * 2
lo := (total % 3) * 2 / 3
return hi + lo
}
func TestRED_FloorMatchesInlineExhaustiveSmall(t *testing.T) {
for total := uint64(0); total < 100000; total++ {
got := TwoThirdsStakeFloor(total)
if got != origInlineFloor(total) {
t.Fatalf("DIVERGENCE total=%d: refactor=%d inline=%d", total, got, origInlineFloor(total))
}
if got != bigIntFloor(total) {
t.Fatalf("ORACLE MISMATCH total=%d: refactor=%d oracle=%d", total, got, bigIntFloor(total))
}
// The floor must never reach total (a supermajority must be a strict subset
// of stake), and voted==total must always strictly exceed it.
if total >= 1 && !(total > got) {
t.Fatalf("floor>=total at total=%d (floor=%d) — full stake would not be a supermajority", total, got)
}
}
}
func TestRED_FloorNoOverflowNear2p64(t *testing.T) {
edges := []uint64{
math.MaxUint64, math.MaxUint64 - 1, math.MaxUint64 - 2, math.MaxUint64 - 3,
1 << 63, (1 << 63) + 1, (1 << 63) - 1, 1 << 62, math.MaxUint64 / 3 * 3,
}
for _, total := range edges {
got := TwoThirdsStakeFloor(total)
if got != origInlineFloor(total) {
t.Fatalf("DIVERGENCE near-2^64 total=%d: refactor=%d inline=%d", total, got, origInlineFloor(total))
}
if got != bigIntFloor(total) {
t.Fatalf("ORACLE MISMATCH near-2^64 total=%d: refactor=%d oracle=%d", total, got, bigIntFloor(total))
}
// floor < total (no overflow wrap; supermajority is a strict subset).
if !(got < total) {
t.Fatalf("OVERFLOW/WRAP at total=%d: floor=%d not < total", total, got)
}
}
}
func TestRED_FloorRandomDifferential(t *testing.T) {
rng := rand.New(rand.NewSource(0xC0FFEE))
for i := 0; i < 5_000_000; i++ {
total := rng.Uint64()
if TwoThirdsStakeFloor(total) != origInlineFloor(total) {
t.Fatalf("DIVERGENCE total=%d", total)
}
}
}