mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
Three fixes for single-node consensus (--dev mode, K=1): 1. Self-voting: when K=1 and all sampled validators are self, deliver a self-vote via callback instead of polling 0 peers. 2. Block acceptance: call Verify() before Accept() — PlatformVM requires block state prepared during Verify(). Without it, Accept() fails silently due to missing block state. 3. DrainAccepted: after each VM notification, check for blocks that consensus accepted and call their Verify+Accept pipeline. Multi-node consensus (K>1) is unchanged — self-voting only activates when K=1 and no external validators exist in the sample. Tests: - TestSingleNodeSelfVote: K=1 self-votes, no network queries - TestMultiNodeDoesNotSelfVote: K=3 uses network, no self-vote - TestSelfVoterNilWhenKGreaterThan1: callback only for K=1
244 lines
5.6 KiB
Go
244 lines
5.6 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
)
|
|
|
|
// TestConsensusThresholds verifies thresholds are properly configured
|
|
func TestConsensusThresholds(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params Parameters
|
|
k int
|
|
alpha int
|
|
alphaF float64
|
|
minRatio float64
|
|
}{
|
|
{
|
|
name: "DefaultParams",
|
|
params: DefaultParams(),
|
|
k: 20,
|
|
alpha: 14, // 70% of 20
|
|
alphaF: 0.69, // 69% threshold
|
|
minRatio: 0.69,
|
|
},
|
|
{
|
|
name: "MainnetParams",
|
|
params: MainnetParams(),
|
|
k: 21,
|
|
alpha: 15, // ~71% of 21
|
|
alphaF: 0.69, // 69% threshold
|
|
minRatio: 0.69,
|
|
},
|
|
{
|
|
name: "TestnetParams",
|
|
params: TestnetParams(),
|
|
k: 11,
|
|
alpha: 8, // ~73% of 11
|
|
alphaF: 0.69, // 69% threshold
|
|
minRatio: 0.69,
|
|
},
|
|
{
|
|
name: "LocalParams",
|
|
params: LocalParams(),
|
|
k: 3,
|
|
alpha: 2, // 2/3 of 3
|
|
alphaF: 0.67, // 2/3 threshold
|
|
minRatio: 0.66,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Check K value
|
|
if tt.params.K != tt.k {
|
|
t.Errorf("K = %d, want %d", tt.params.K, tt.k)
|
|
}
|
|
|
|
// Check Alpha matches expected threshold
|
|
if tt.params.Alpha != tt.alphaF {
|
|
t.Errorf("Alpha = %f, want %f", tt.params.Alpha, tt.alphaF)
|
|
}
|
|
|
|
// Check AlphaPreference matches
|
|
if tt.params.AlphaPreference != tt.alpha {
|
|
t.Errorf("AlphaPreference = %d, want %d", tt.params.AlphaPreference, tt.alpha)
|
|
}
|
|
|
|
// Verify AlphaPreference meets minimum ratio of K
|
|
minAlpha := math.Ceil(float64(tt.params.K) * tt.minRatio)
|
|
if float64(tt.params.AlphaPreference) < minAlpha {
|
|
t.Errorf("AlphaPreference %d is below %.0f%% threshold %f",
|
|
tt.params.AlphaPreference, tt.minRatio*100, minAlpha)
|
|
}
|
|
|
|
// Check that parameters are valid
|
|
if err := tt.params.Valid(); err != nil {
|
|
t.Errorf("Parameters validation failed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestByzantineTolerance31 verifies maximum Byzantine tolerance is 31%
|
|
func TestByzantineTolerance31(t *testing.T) {
|
|
tests := []struct {
|
|
byzantineWeight uint64
|
|
totalWeight uint64
|
|
canTolerate bool
|
|
}{
|
|
{31, 100, true}, // Exactly 31%
|
|
{30, 100, true}, // Below threshold
|
|
{32, 100, false}, // Above threshold
|
|
{310, 1000, true}, // 31% at scale
|
|
{311, 1000, false}, // Slightly above 31%
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := CanTolerateFailure(tt.byzantineWeight, tt.totalWeight)
|
|
if result != tt.canTolerate {
|
|
t.Errorf("CanTolerateFailure(%d, %d) = %v, want %v",
|
|
tt.byzantineWeight, tt.totalWeight, result, tt.canTolerate)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestQuorumCalculation verifies 69% quorum calculations
|
|
func TestQuorumCalculation(t *testing.T) {
|
|
tests := []struct {
|
|
totalWeight uint64
|
|
expectedQuorum uint64
|
|
}{
|
|
{100, 69},
|
|
{1000, 690},
|
|
{10000, 6900},
|
|
{33, 23}, // Ceiling of 22.77
|
|
{67, 47}, // Ceiling of 46.23
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
quorum := CalculateQuorum(tt.totalWeight)
|
|
if quorum != tt.expectedQuorum {
|
|
t.Errorf("CalculateQuorum(%d) = %d, want %d",
|
|
tt.totalWeight, quorum, tt.expectedQuorum)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestHasSuperMajority verifies super majority checks
|
|
func TestHasSuperMajority(t *testing.T) {
|
|
tests := []struct {
|
|
weight uint64
|
|
totalWeight uint64
|
|
hasMajority bool
|
|
}{
|
|
{69, 100, true}, // Exactly 69%
|
|
{70, 100, true}, // Above threshold
|
|
{68, 100, false}, // Below threshold
|
|
{690, 1000, true}, // 69% at scale
|
|
{689, 1000, false}, // Just below
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := HasSuperMajority(tt.weight, tt.totalWeight)
|
|
if result != tt.hasMajority {
|
|
t.Errorf("HasSuperMajority(%d, %d) = %v, want %v",
|
|
tt.weight, tt.totalWeight, result, tt.hasMajority)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestAlphaForK verifies Alpha calculation for different K values
|
|
func TestAlphaForK(t *testing.T) {
|
|
tests := []struct {
|
|
k int
|
|
expectedAlpha int
|
|
}{
|
|
{20, 14}, // Ceiling of 13.8
|
|
{21, 15}, // Ceiling of 14.49
|
|
{11, 8}, // Ceiling of 7.59
|
|
{5, 4}, // Ceiling of 3.45
|
|
{100, 69}, // Exactly 69
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
alpha := AlphaForK(tt.k)
|
|
if alpha != tt.expectedAlpha {
|
|
t.Errorf("AlphaForK(%d) = %d, want %d",
|
|
tt.k, alpha, tt.expectedAlpha)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestParameterValidation verifies parameter validation with 69% threshold
|
|
func TestParameterValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
params Parameters
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "Valid 69% threshold",
|
|
params: Parameters{
|
|
K: 20,
|
|
Alpha: 0.69,
|
|
Beta: 14,
|
|
},
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "Below 2/3 threshold",
|
|
params: Parameters{
|
|
K: 20,
|
|
Alpha: 0.65, // Below minimum 2/3 threshold
|
|
Beta: 14,
|
|
},
|
|
expectedErr: ErrInvalidAlpha,
|
|
},
|
|
{
|
|
name: "Invalid K",
|
|
params: Parameters{
|
|
K: 0,
|
|
Alpha: 0.69,
|
|
Beta: 14,
|
|
},
|
|
expectedErr: ErrInvalidK,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := tt.params.Valid()
|
|
if err != tt.expectedErr {
|
|
t.Errorf("Valid() error = %v, want %v", err, tt.expectedErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkQuorumCalculation benchmarks quorum calculations
|
|
func BenchmarkQuorumCalculation(b *testing.B) {
|
|
weights := []uint64{100, 1000, 10000, 100000, 1000000}
|
|
|
|
for _, w := range weights {
|
|
b.Run(fmt.Sprintf("weight_%d", w), func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
_ = CalculateQuorum(w)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkHasSuperMajority benchmarks super majority checks
|
|
func BenchmarkHasSuperMajority(b *testing.B) {
|
|
totalWeight := uint64(1000000)
|
|
weight := uint64(690000)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = HasSuperMajority(weight, totalWeight)
|
|
}
|
|
}
|