Files
node/chains/quorum_params_test.go
zeekay b88a2e3fac deps: bump consensus v1.25.20 / crypto v1.19.22 / chains v1.3.18 / runtime v1.1.3; wire LocalBFTParams for local dev nets
Publishes the consensus quorum-finality cascade into node:
- consensus v1.25.19 -> v1.25.20 (chain quorum-cert finality, LocalBFTParams)
- crypto v1.19.21 -> v1.19.22 (BLS deserialization/aggregation hardening)
- chains v1.3.17 -> v1.3.18, runtime v1.1.1 -> v1.1.3 (value-path tags)

selectConsensusParams now routes explicitly-local dev networks (devnet 3 /
localnet 1337, EXACT IDs not a range) to consensus.LocalBFTParams (K=4/α=3,
f=1) — the minimal real-BFT committee. Default K=20 is unsatisfiable on a
few localhost validators (α=14 unreachable -> P-Chain freezes at height 0).
K=4 makes quorum reachable while still clearing ValidateForValueNetwork and
the CRITICAL-2 multi-node-is-BFT regression. A custom value L1 (high
networkID) keeps the large Default set. precompile resolves to v0.5.56
(MVS-correct: chains v1.3.18's own requirement; node has no evm/dex dep).

go mod verify: all modules verified; chains quorum params tests green.
2026-06-23 01:43:41 -07:00

133 lines
5.8 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_params_test.go — CRITICAL-2 node-layer regression: the node must NEVER
// wire a non-BFT consensus param set for a multi-validator (sybil-protected)
// chain. The round-1 hole was manager.go selecting LocalParams() (K=3/α=2 → f=0,
// CFT) for ALL multi-node nets — a single Byzantine validator forks K=3/α=2.
// These tests pin selectConsensusParams to a BFT-safe set for every multi-node
// network and prove the value-network backstop (ValidateForValueNetwork) accepts
// the selected params.
package chains
import (
"testing"
consensusconfig "github.com/luxfi/consensus/config"
"github.com/luxfi/constants"
)
// TestSelectConsensusParams_MultiNodeIsBFT proves that for EVERY multi-validator
// (sybilProtection==true) network the node selects a Byzantine-fault-tolerant
// param set (f≥1, i.e. K≥4) that also clears the value-network validator — and
// that it is NEVER LocalParams (K=3) or any K<4 set. This is the node half of
// CRITICAL-2 (the consensus half is config.ValidateForValueNetwork, tested in
// the consensus module).
func TestSelectConsensusParams_MultiNodeIsBFT(t *testing.T) {
local := consensusconfig.LocalParams()
cases := []struct {
name string
networkID uint32
}{
{"mainnet", constants.MainnetID},
{"testnet", constants.TestnetID},
{"localnet-multinode", constants.LocalID},
{"unittest-multinode", constants.UnitTestID},
{"arbitrary-multinode", 424242},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := selectConsensusParams(true /* sybilProtection */, tc.networkID)
// MUST be Byzantine-fault-tolerant: f≥1 ⟹ K≥4.
if p.ByzantineFaultTolerance() < 1 {
t.Fatalf("multi-node net %q got non-BFT params K=%d f=%d (CRITICAL-2: a single faulty validator forks)",
tc.name, p.K, p.ByzantineFaultTolerance())
}
// MUST NOT be the CFT LocalParams (K=3/α=2) that was the round-1 hole.
if p.K == local.K && p.AlphaPreference == local.AlphaPreference && p.K == 3 {
t.Fatalf("multi-node net %q selected LocalParams (K=3/α=2) — the CRITICAL-2 fork config", tc.name)
}
// The selected params MUST themselves pass Valid() (the BFT α-floor:
// 2·AlphaPreference K ≥ f+1).
if err := p.Valid(); err != nil {
t.Fatalf("multi-node net %q selected params fail Valid(): %v (K=%d α=%d)",
tc.name, err, p.K, p.AlphaPreference)
}
})
}
}
// TestSelectConsensusParams_LocalDevIsSatisfiableBFT proves that an explicitly-
// local dev network (devnet 3 / localnet 1337) selects the MINIMAL real-BFT set
// (LocalBFTParams: K=4/α=3, f=1) — satisfiable by a handful of localhost
// validators — and NOT the large Default (K=20/α=14), whose α=14 quorum is
// unreachable with 3-4 validators (the freeze-at-height-0 bug). It must still be
// genuinely BFT (f≥1) and pass the value backstop (K=4 ≥ 4), so production
// safety / CRITICAL-2 is untouched: a custom VALUE L1 (high networkID) still gets
// the large Default set, never the local one.
func TestSelectConsensusParams_LocalDevIsSatisfiableBFT(t *testing.T) {
for _, networkID := range []uint32{constants.DevnetID, constants.LocalID} {
p := selectConsensusParams(true /* sybilProtection */, networkID)
// Satisfiable on a small committee: α must be small (K=4 → α=3), NOT 14.
if p.K != 4 || p.AlphaPreference != 3 {
t.Fatalf("local dev net %d: want minimal-BFT K=4/α=3, got K=%d/α=%d (Default K=20 is unsatisfiable on localhost)",
networkID, p.K, p.AlphaPreference)
}
// Still genuinely BFT (f≥1) — does NOT regress CRITICAL-2.
if p.ByzantineFaultTolerance() < 1 {
t.Fatalf("local dev net %d: K=%d is not BFT (f=%d)", networkID, p.K, p.ByzantineFaultTolerance())
}
// Must clear the value backstop the manager asserts before engine start.
if err := p.ValidateForValueNetwork(networkID); err != nil {
t.Fatalf("local dev net %d: minimal-BFT params must pass the value backstop, got %v", networkID, err)
}
}
// A custom value L1 with a high networkID is NOT a local dev network: it must
// keep the large Default set (the isLocalDevNetwork predicate is EXACT, not a
// >=1337 range that would wrongly catch value L1s).
const customValueL1 = uint32(909090)
if p := selectConsensusParams(true, customValueL1); p.K != consensusconfig.DefaultParams().K {
t.Fatalf("custom value L1 %d must get Default K=%d, got K=%d (must NOT match the local-dev path)",
customValueL1, consensusconfig.DefaultParams().K, p.K)
}
}
// TestSelectConsensusParams_SingleNodeIsK1 proves --dev / sybil-disabled selects
// the K=1 single-validator regime (the sole validator's accept is the 1-of-1
// quorum) — and NOT a multi-node BFT set.
func TestSelectConsensusParams_SingleNodeIsK1(t *testing.T) {
p := selectConsensusParams(false /* sybilProtection */, constants.LocalID)
if p.K != 1 {
t.Fatalf("sybil-disabled (single-node) must select K=1, got K=%d", p.K)
}
}
// TestSelectConsensusParams_ValueBackstop proves the params selected for a
// multi-node net pass the STRICTER value-network validator for that net — the
// fail-closed backstop asserted at the manager call site before starting the
// engine. (Mainnet enforces K≥11, so MainnetParams K=21 passes; Default K=20
// passes for an arbitrary value net.)
func TestSelectConsensusParams_ValueBackstop(t *testing.T) {
cases := []struct {
name string
networkID uint32
}{
{"mainnet", constants.MainnetID},
{"arbitrary-value-net", 909090},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
p := selectConsensusParams(true, tc.networkID)
if err := p.ValidateForValueNetwork(tc.networkID); err != nil {
t.Fatalf("selected params for value net %q must pass the value backstop, got %v (K=%d)",
tc.name, err, p.K)
}
})
}
}