Files
consensus/engine/driver.go
T
zeekay c40e832a9e consensus: drop Quasar/Annulus/Lux from profile identifiers
First-principles cleanup: family names (Quasar, Annulus) and the Lux
brand prefix belong in papers/docs, not Go identifiers. Canonical names:

  ProfileLuxStrictPQ    -> ProfileStrictPQ
  ProfileLuxPermissive  -> ProfilePermissive
  ProfileLuxFIPS        -> ProfileFIPS
  QuasarStrictPQ()      -> removed (was 0x06 byte-identical to 0x01)
  AnnulusStrictPQ()     -> removed (was alias for the above)
  ProfileNameLuxStrictPQ etc. -> removed

ProfileZooStrictPQ (0x04) and ProfileHanzoStrictPQ (0x05) kept — chain-
specific siblings, not crypto family branding. Wire-format cSHAKE256
customization tags (LUX_PQ_PERMIT_V1 etc.) intentionally stay brand-
prefixed as domain separators.

IsPQ(), ProfileByID(), validatePolicy() switches updated.
ProfileName strings drop LUX_ prefix: "LUX_STRICT_PQ" -> "STRICT_PQ".
Golden ProfileHash in security_profile_e2e_pq_test.go re-pinned.
2026-05-11 16:50:46 -07:00

305 lines
7.5 KiB
Go

// Copyright (C) 2019-2025, Lux Partners Limited All rights reserved.
// See the file LICENSE for licensing terms.
package engine
import (
"context"
crand "crypto/rand"
"errors"
"sync"
"time"
"github.com/luxfi/consensus/config"
"github.com/luxfi/consensus/core/types"
"github.com/luxfi/consensus/protocol/focus"
"github.com/luxfi/consensus/protocol/prism"
"github.com/luxfi/consensus/protocol/wave"
"github.com/luxfi/ids"
)
var ErrNoTransport = errors.New("no real transport configured: SimpleTransport cannot send vote requests over the network")
// Driver implements Lux's consensus protocol using Photon → Wave → Focus → Prism → Quasar
type Driver struct {
mu sync.RWMutex
// Configuration
k int // Sample size (K rays for Photon)
alpha float64 // Threshold ratio
beta uint32 // Confidence threshold
// Protocol components
wave *wave.Wave[ids.ID]
focus *focus.Confidence[ids.ID]
prismCut prism.Cut[ids.ID]
transport wave.Transport[ids.ID]
// State tracking
preference ids.ID
decided map[ids.ID]bool
decisions map[ids.ID]types.Decision
// Confidence tracking
consecutiveSuccesses map[ids.ID]uint32
}
// NewLuxConsensus creates a new Lux consensus instance with stake-weighted sampling.
// The cut parameter provides the peer sampling strategy (use prism.NewStakeWeightedCut
// for production, or prism.NewUniformCut for testing).
// The transport parameter handles network vote requests.
func NewLuxConsensus(k int, alpha int, beta int, opts ...Option) *Driver {
alphaRatio := float64(alpha) / float64(k)
// Ensure beta is non-negative for uint32 conversion
if beta < 0 {
beta = 0
}
// #nosec G115 -- beta is guaranteed >= 0 after check above
betaU32 := uint32(beta)
o := options{}
for _, opt := range opts {
opt(&o)
}
// Use provided cut or fall back to SimpleCut.
var cut prism.Cut[ids.ID]
if o.cut != nil {
cut = o.cut
} else {
cut = &SimpleCut{k: k}
}
// Use provided transport or fall back to SimpleTransport.
var transport wave.Transport[ids.ID]
if o.transport != nil {
transport = o.transport
} else {
transport = &SimpleTransport{}
}
// Generate FPC seed from crypto/rand for this instance.
var fpcSeed [32]byte
if _, err := crand.Read(fpcSeed[:]); err != nil {
panic("failed to generate FPC seed: " + err.Error())
}
// Create Wave configuration with FPC enabled for dynamic thresholds
waveCfg := wave.Config{
K: k,
Alpha: alphaRatio,
Beta: betaU32,
RoundTO: 1 * time.Second,
EnableFPC: true, // Enable Fast Probabilistic Consensus
ThetaMin: 0.5, // FPC minimum threshold
ThetaMax: 0.8, // FPC maximum threshold
FPCSeed: fpcSeed[:],
}
// Create consensus components
w, err := wave.New[ids.ID](waveCfg, cut, transport)
if err != nil {
panic("failed to create wave: " + err.Error())
}
f := focus.NewConfidence[ids.ID](beta, alphaRatio)
return &Driver{
k: k,
alpha: alphaRatio,
beta: betaU32,
wave: &w,
focus: f,
prismCut: cut,
transport: transport,
decided: make(map[ids.ID]bool),
decisions: make(map[ids.ID]types.Decision),
consecutiveSuccesses: make(map[ids.ID]uint32),
}
}
// Option configures Driver construction.
type Option func(*options)
type options struct {
cut prism.Cut[ids.ID]
transport wave.Transport[ids.ID]
}
// WithCut sets the peer sampling strategy.
func WithCut(cut prism.Cut[ids.ID]) Option {
return func(o *options) { o.cut = cut }
}
// WithTransport sets the network transport for vote requests.
func WithTransport(transport wave.Transport[ids.ID]) Option {
return func(o *options) { o.transport = transport }
}
// RecordVote records a vote for an item
func (lc *Driver) RecordVote(item ids.ID) {
lc.mu.Lock()
defer lc.mu.Unlock()
// If already decided, ignore vote
if lc.decided[item] {
return
}
// Increment consecutive successes
lc.consecutiveSuccesses[item]++
}
// Poll conducts a consensus poll using Lux protocols
func (lc *Driver) Poll(responses map[ids.ID]int) bool {
lc.mu.Lock()
defer lc.mu.Unlock()
ctx := context.Background()
for item, votes := range responses {
// Skip if already decided
if lc.decided[item] {
continue
}
// Calculate vote ratio
totalVotes := 0
for _, v := range responses {
totalVotes += v
}
if totalVotes == 0 {
continue
}
ratio := float64(votes) / float64(totalVotes)
// Update Focus confidence tracking
lc.focus.Update(item, ratio)
// Check if decision reached
confidence, decided := lc.focus.State(item)
if decided {
lc.decided[item] = true
if ratio >= lc.alpha {
lc.decisions[item] = types.DecideAccept
lc.preference = item
} else {
lc.decisions[item] = types.DecideReject
}
return false // Stop polling, decision made
}
// Use Wave protocol for threshold checking
lc.wave.Tick(ctx, item)
state, exists := lc.wave.State(item)
if exists && state.Decided {
lc.decided[item] = true
lc.decisions[item] = state.Result
if state.Result == types.DecideAccept {
lc.preference = item
}
return false // Stop polling, decision made
}
// Update preference based on confidence
if confidence > 0 && ratio >= lc.alpha {
lc.preference = item
}
}
// Continue polling if no decision
return true
}
// Decided returns whether consensus has been reached
func (lc *Driver) Decided() bool {
lc.mu.RLock()
defer lc.mu.RUnlock()
return len(lc.decided) > 0
}
// Preference returns the current preferred item
func (lc *Driver) Preference() ids.ID {
lc.mu.RLock()
defer lc.mu.RUnlock()
return lc.preference
}
// Decision returns the decision for an item
func (lc *Driver) Decision(item ids.ID) (types.Decision, bool) {
lc.mu.RLock()
defer lc.mu.RUnlock()
decision, exists := lc.decisions[item]
return decision, exists
}
// SimpleCut implements a basic Cut for sampling
type SimpleCut struct {
k int
}
func (c *SimpleCut) Sample(k int) []types.NodeID {
nodes := make([]types.NodeID, k)
for i := 0; i < k; i++ {
// Create a proper NodeID - it's actually a ShortID (20-byte array)
nodes[i] = ids.GenerateTestNodeID()
}
return nodes
}
// Luminance implements Cut interface
func (c *SimpleCut) Luminance() prism.Luminance {
// Return basic luminance for testing
return prism.Luminance{
ActivePeers: c.k,
TotalPeers: c.k,
Lx: float64(c.k), // 1 lx per peer
}
}
// SimpleTransport implements basic transport for voting
type SimpleTransport struct {
mu sync.RWMutex
votes map[ids.ID]bool
}
func (t *SimpleTransport) RequestVotes(_ context.Context, _ []types.NodeID, _ ids.ID) <-chan wave.Photon[ids.ID] {
// SimpleTransport has no real network connectivity.
// Return a closed empty channel so callers see zero votes rather than
// fabricated "Prefer: true" responses that bypass Sybil resistance.
ch := make(chan wave.Photon[ids.ID])
close(ch)
return ch
}
// Err returns ErrNoTransport because SimpleTransport has no network connectivity.
func (t *SimpleTransport) Err() error {
return ErrNoTransport
}
func (t *SimpleTransport) MakeLocalPhoton(item ids.ID, prefer bool) wave.Photon[ids.ID] {
return wave.Photon[ids.ID]{
Item: item,
Prefer: prefer,
Sender: ids.GenerateTestNodeID(),
Timestamp: time.Now(),
}
}
// Parameters returns the consensus parameters
func (lc *Driver) Parameters() config.Parameters {
return config.Parameters{
K: lc.k,
Alpha: lc.alpha,
Beta: lc.beta,
AlphaPreference: int(lc.alpha * float64(lc.k)),
AlphaConfidence: int(lc.alpha * float64(lc.k)),
}
}