Files
zeekayandHanzo Dev ef5d6e9ae8 Put back what the deps refresh deleted, and pin the node that carries /v1/chain
node v1.36.179 adopts api v1.1.14, so the health service compiles and the
whole module builds. That tag also carries the /v1/chain cutover, which makes
it the node this SDK is written against.

The same "deps: go.mod refresh" that reverted the wallet also deleted 211
lines from two other files, additions only, no edits, nothing touching them
since:

profiles.Consensus derives K and alpha from the live validator count and is
the single source of truth for sampling safety -- its own comment records the
K=5/alpha=3 drift that was sub-BFT and that the engine refused. Deleting it
left the derivation with no home.

The hybrid signature verifier enforces AND across the classical and PQ
halves. It exists because a red-team finding showed OR semantics let a forged
hybrid through on a valid ECDSA half and a random PQ blob, which is hybrid
security reduced to the weaker half. Deleting it took the fix with it.

Both had kept their tests, which is the only reason the loss was visible: the
tests named functions that were gone, so the packages stopped compiling, so
none of their other tests ran either. Nothing calls either function today, so
restoring them changes no live path -- it returns the capability and lets the
tests run again.

TestListProfiles expected four profiles and there are five; production
arrived with the consensus derivation and the list never caught up, because
it could not run. It now names the set it means.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 15:26:37 -07:00

66 lines
2.2 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package profiles
import "testing"
// TestConsensus_MatchesAvalanche pins the Avalanche-grade derivation: K=min(20,N),
// alpha=ceil(0.75K) at the 15/20 ratio, BFT-safe at every size.
func TestConsensus_MatchesAvalanche(t *testing.T) {
cases := []struct {
n, wantK, wantAlpha int
}{
{1, 1, 1}, // single node
{3, 3, 2}, // tiny: BFT-minimal (67%), keeps one tolerable fault
{4, 4, 3}, // 75%
{5, 5, 4}, // current Lux nets: 80% >= Avalanche's 75%
{8, 8, 6}, // 75%
{20, 20, 15}, // exactly Avalanche
{50, 20, 15}, // capped at K=20, Avalanche
}
for _, c := range cases {
got := Consensus(c.n)
if got.SampleSize != c.wantK || got.PreferenceQuorumSize != c.wantAlpha || got.ConfidenceQuorumSize != c.wantAlpha {
t.Errorf("Consensus(%d) = K=%d alpha=%d, want K=%d alpha=%d",
c.n, got.SampleSize, got.PreferenceQuorumSize, c.wantK, c.wantAlpha)
}
if !IsByzantineSafe(got.SampleSize, got.PreferenceQuorumSize) {
t.Errorf("Consensus(%d) = K=%d alpha=%d is NOT Byzantine-safe", c.n, got.SampleSize, got.PreferenceQuorumSize)
}
}
}
// TestIsByzantineSafe pins the invariant the engine enforces, including the exact
// live drift that bricked the rollout.
func TestIsByzantineSafe(t *testing.T) {
if IsByzantineSafe(5, 3) {
t.Error("K=5/alpha=3 must be rejected (2*3-5 = 1 < 2) — this is the live drift the new node refuses")
}
for _, c := range []struct {
k, a int
want bool
}{
{5, 4, true}, {5, 5, true}, {3, 2, true}, {1, 1, true},
{20, 15, true}, {20, 13, false}, {4, 3, true}, {4, 2, false},
} {
if got := IsByzantineSafe(c.k, c.a); got != c.want {
t.Errorf("IsByzantineSafe(%d,%d) = %v, want %v", c.k, c.a, got, c.want)
}
}
}
// TestAllProfilesByzantineSafe ensures no shipped profile is sub-BFT.
func TestAllProfilesByzantineSafe(t *testing.T) {
for _, name := range ListProfiles() {
p, err := GetProfile(name)
if err != nil {
t.Fatalf("GetProfile(%q): %v", name, err)
}
if !IsByzantineSafe(p.Consensus.SampleSize, p.Consensus.PreferenceQuorumSize) {
t.Errorf("profile %q is NOT Byzantine-safe: K=%d alpha=%d",
name, p.Consensus.SampleSize, p.Consensus.PreferenceQuorumSize)
}
}
}