mirror of
https://github.com/luxfi/consensus.git
synced 2026-09-24 00:26:58 +00:00
Add FPC threshold selector with PRF-based phase-dependent threshold selection. Integrates seamlessly with Wave protocol for dynamic alpha thresholds. Features: - PRF-based deterministic threshold selection using SHA-256 - Configurable theta range [θ_min, θ_max] (default: 0.5-0.8) - Phase-dependent thresholds: α = ⌈θ·k⌉ - Backward compatible with EnableFPC flag (default: false) - Zero breaking changes to existing Wave API Test Coverage: - 10/10 FPC unit tests PASS (determinism, theta range, benchmarks) - 5/5 FPC integration tests PASS (enabled/disabled, variation, comparison) - 18/18 Wave tests PASS (all existing tests still work) Implementation: - /protocol/wave/fpc/fpc.go (83 lines) - Core threshold selector - /protocol/wave/fpc/fpc_test.go (177 lines) - Comprehensive tests - /protocol/wave/fpc_integration_test.go (274 lines) - Integration demos - /protocol/wave/wave.go - Wave integration with FPC support All 78/78 protocol tests passing. FPC is production-ready!
83 lines
2.1 KiB
Go
83 lines
2.1 KiB
Go
package fpc
|
||
|
||
import (
|
||
"crypto/sha256"
|
||
"encoding/binary"
|
||
"math"
|
||
)
|
||
|
||
// Selector provides phase-dependent threshold selection for FPC
|
||
type Selector struct {
|
||
thetaMin float64
|
||
thetaMax float64
|
||
seed []byte
|
||
}
|
||
|
||
// NewSelector creates a new FPC threshold selector
|
||
func NewSelector(thetaMin, thetaMax float64, seed []byte) *Selector {
|
||
if thetaMin <= 0 || thetaMin >= 1 {
|
||
thetaMin = 0.5
|
||
}
|
||
if thetaMax <= thetaMin || thetaMax > 1 {
|
||
thetaMax = 0.8
|
||
}
|
||
if seed == nil {
|
||
seed = []byte("lux-fpc-default-seed")
|
||
}
|
||
return &Selector{
|
||
thetaMin: thetaMin,
|
||
thetaMax: thetaMax,
|
||
seed: seed,
|
||
}
|
||
}
|
||
|
||
// SelectThreshold picks θ ∈ [θ_min, θ_max] using PRF for phase
|
||
// Returns α = ⌈θ·k⌉ for both preference and confidence
|
||
func (s *Selector) SelectThreshold(phase uint64, k int) int {
|
||
theta := s.computeTheta(phase)
|
||
return int(math.Ceil(theta * float64(k)))
|
||
}
|
||
|
||
// computeTheta uses PRF to deterministically select θ for a given phase
|
||
func (s *Selector) computeTheta(phase uint64) float64 {
|
||
// Create PRF input: seed || phase
|
||
h := sha256.New()
|
||
h.Write(s.seed)
|
||
|
||
phaseBytes := make([]byte, 8)
|
||
binary.BigEndian.PutUint64(phaseBytes, phase)
|
||
h.Write(phaseBytes)
|
||
|
||
hash := h.Sum(nil)
|
||
|
||
// Convert first 8 bytes of hash to uint64, normalize to [0,1]
|
||
hashUint := binary.BigEndian.Uint64(hash[:8])
|
||
normalized := float64(hashUint) / float64(^uint64(0))
|
||
|
||
// Scale to [thetaMin, thetaMax]
|
||
theta := s.thetaMin + normalized*(s.thetaMax-s.thetaMin)
|
||
|
||
return theta
|
||
}
|
||
|
||
// Theta returns the raw theta value for a phase (for testing/debugging)
|
||
func (s *Selector) Theta(phase uint64) float64 {
|
||
return s.computeTheta(phase)
|
||
}
|
||
|
||
// Range returns the configured theta range
|
||
func (s *Selector) Range() (min, max float64) {
|
||
return s.thetaMin, s.thetaMax
|
||
}
|
||
|
||
// DefaultSelector returns a selector with default parameters
|
||
func DefaultSelector() *Selector {
|
||
return NewSelector(0.5, 0.8, nil)
|
||
}
|
||
|
||
// SelectThreshold is a convenience function using default selector
|
||
func SelectThreshold(phase uint64, k int, thetaMin, thetaMax float64) int {
|
||
s := NewSelector(thetaMin, thetaMax, nil)
|
||
return s.SelectThreshold(phase, k)
|
||
}
|