mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
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]>
66 lines
2.2 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|