Files
consensus/config/constants_test.go
zeekayandHanzo Dev 4abf37c952 config: K is a number of draws, not a headcount
The live guard refused to start a chain whose K was below the number of
validators that existed. But sampling is stake-weighted: a validator is drawn in
proportion to what it staked, however many validators there are, and K only
sets how many draws a poll makes. Tying K to the headcount gave a validator
holding a billionth of the stake the same standing as one holding a fifth, and
refused to boot a producing chain because a small staker had joined.

The guard keeps what makes a committee safe — the overlap bound and at least
single-fault tolerance — and stops counting heads. The tier floor stays a
reported target: refusing to start does not add validators, it pins every node
on whatever it last booted.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-16 11:42:58 -07:00

25 lines
973 B
Go

package config
import "testing"
// AlphaForK must return the SMALLEST sample-vote count that reaches the 69%
// agreement threshold: at or above 69% of K, and one vote fewer must fall short.
// Rounding down anywhere here lets a chain finalize under the Byzantine bound;
// rounding up more than necessary costs liveness. Checked across every K a
// committee is plausibly sized at rather than a handful of sampled rows.
func TestAlphaForKIsTheCeilingOfTheThreshold(t *testing.T) {
for k := 1; k <= 1000; k++ {
alpha := AlphaForK(k)
if float64(alpha) < float64(k)*ConsensusSuperMajority {
t.Fatalf("AlphaForK(%d) = %d, under the %.0f%% threshold", k, alpha, ConsensusSuperMajority*100)
}
if alpha > 1 && float64(alpha-1) >= float64(k)*ConsensusSuperMajority {
t.Fatalf("AlphaForK(%d) = %d, but %d already clears the threshold", k, alpha, alpha-1)
}
if alpha > k {
t.Fatalf("AlphaForK(%d) = %d, more votes than the sample holds", k, alpha)
}
}
}