mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
682 lines
19 KiB
Go
682 lines
19 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package quasar
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/luxfi/consensus/protocol/quasar"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
)
|
|
|
|
// Quasar is the gravitational center of Lux consensus.
|
|
// It binds P-Chain (BLS signatures) and Q-Chain (Corona post-quantum threshold)
|
|
// into unified hybrid finality across all Lux networks.
|
|
//
|
|
// Architecture:
|
|
// ALL validators have BOTH keypairs:
|
|
// - BLS keypair → aggregate signatures (classical, fast)
|
|
// - Corona keypair → threshold signatures (post-quantum, 2-round)
|
|
//
|
|
// Both signature paths run IN PARALLEL:
|
|
//
|
|
// Block arrives
|
|
// │
|
|
// ├─────────────────────────────────────────┐
|
|
// │ │
|
|
// ▼ ▼
|
|
// BLS PATH (fast) CORONA PATH (quantum-safe)
|
|
// ──────────────── ─────────────────────────────
|
|
// All validators sign Round 1: All validators
|
|
// with BLS keys generate commitments
|
|
// │ │
|
|
// ▼ ▼
|
|
// Aggregate into Round 2: All validators
|
|
// single 96-byte sig compute partial signatures
|
|
// │ │
|
|
// └─────────────────┬───────────────────────┘
|
|
// │
|
|
// ▼
|
|
// Finalize: combine into
|
|
// threshold signature
|
|
// │
|
|
// ▼
|
|
// ┌─────────────────┐
|
|
// │ HYBRID PROOF │
|
|
// │ BLS Aggregate │ ← 96 bytes (2/3+ validators)
|
|
// │ Corona Thresh │ ← ~KB (t-of-n threshold)
|
|
// └─────────────────┘
|
|
// │
|
|
// ▼
|
|
// QUANTUM FINALITY
|
|
//
|
|
// The quasar ensures blocks achieve finality only when BOTH complete:
|
|
// 1. 2/3+ validator weight signed via BLS (fast, classical)
|
|
// 2. t-of-n validators completed Corona threshold (post-quantum secure)
|
|
|
|
var (
|
|
ErrQuasarNotStarted = errors.New("quasar not started")
|
|
ErrPChainNotConnected = errors.New("P-Chain not connected")
|
|
ErrQChainNotConnected = errors.New("Q-Chain not connected")
|
|
ErrCoronaNotConnected = errors.New("Corona coordinator not connected")
|
|
ErrInsufficientWeight = errors.New("insufficient validator weight")
|
|
ErrInsufficientSigners = errors.New("insufficient Corona signers")
|
|
ErrFinalityFailed = errors.New("hybrid finality verification failed")
|
|
ErrBLSFailed = errors.New("BLS aggregation failed")
|
|
ErrCoronaFailed = errors.New("Corona threshold signing failed")
|
|
)
|
|
|
|
// PChainProvider provides P-Chain state and finality events
|
|
type PChainProvider interface {
|
|
GetFinalizedHeight() uint64
|
|
GetValidators(height uint64) ([]ValidatorState, error)
|
|
SubscribeFinality() <-chan FinalityEvent
|
|
}
|
|
|
|
// QuantumSignerFallback provides fallback single-signer quantum signatures
|
|
type QuantumSignerFallback interface {
|
|
SignMessage(msg []byte) ([]byte, error)
|
|
}
|
|
|
|
// ValidatorState represents a validator's current state
|
|
// Each validator has BOTH BLS and Corona keys
|
|
type ValidatorState struct {
|
|
NodeID ids.NodeID
|
|
Weight uint64
|
|
BLSPubKey []byte // BLS public key for aggregate signatures
|
|
CoronaKey []byte // Corona public key share for threshold sigs
|
|
Active bool
|
|
}
|
|
|
|
// FinalityEvent represents a P-Chain finality event
|
|
type FinalityEvent struct {
|
|
Height uint64
|
|
BlockID ids.ID
|
|
Validators []ValidatorState
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// QuantumFinality represents a block that achieved hybrid quantum finality
|
|
type QuantumFinality struct {
|
|
BlockID ids.ID
|
|
PChainHeight uint64
|
|
QChainHeight uint64
|
|
BLSProof []byte // Aggregated BLS signature (96 bytes)
|
|
CoronaProof []byte // Serialized Corona threshold signature
|
|
SignerBitset []byte // Which validators signed BLS
|
|
CoronaSigners []ids.NodeID // Which validators participated in Corona
|
|
TotalWeight uint64
|
|
SignerWeight uint64
|
|
BLSLatency time.Duration
|
|
CoronaLatency time.Duration
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// Quasar binds P-Chain and Q-Chain consensus into hybrid quantum finality
|
|
type Quasar struct {
|
|
mu sync.RWMutex
|
|
|
|
log log.Logger
|
|
core *quasar.Quasar
|
|
|
|
// Chain connections
|
|
pChain PChainProvider
|
|
quantumFallback QuantumSignerFallback
|
|
|
|
// Corona threshold coordinator
|
|
corona *CoronaCoordinator
|
|
|
|
// State
|
|
pHeight uint64
|
|
qHeight uint64
|
|
finalized map[ids.ID]*QuantumFinality
|
|
|
|
// Configuration
|
|
threshold int // Corona threshold (t in t-of-n)
|
|
quorumNum uint64 // BLS quorum numerator
|
|
quorumDen uint64 // BLS quorum denominator
|
|
maxFinalized int // max finalized entries before pruning
|
|
|
|
// Channels
|
|
finalityCh chan *QuantumFinality
|
|
stopCh chan struct{}
|
|
running bool
|
|
}
|
|
|
|
// NewQuasar creates a new Quasar consensus hub
|
|
func NewQuasar(log log.Logger, threshold int, quorumNum, quorumDen uint64) (*Quasar, error) {
|
|
core, err := quasar.NewQuasar(threshold)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create quasar core: %w", err)
|
|
}
|
|
|
|
return &Quasar{
|
|
log: log,
|
|
core: core,
|
|
threshold: threshold,
|
|
quorumNum: quorumNum,
|
|
quorumDen: quorumDen,
|
|
finalized: make(map[ids.ID]*QuantumFinality),
|
|
maxFinalized: 10000,
|
|
finalityCh: make(chan *QuantumFinality, 100),
|
|
stopCh: make(chan struct{}),
|
|
}, nil
|
|
}
|
|
|
|
// ConnectPChain connects the P-Chain finality provider
|
|
func (q *Quasar) ConnectPChain(p PChainProvider) {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
|
|
q.pChain = p
|
|
if p != nil {
|
|
q.pHeight = p.GetFinalizedHeight()
|
|
}
|
|
|
|
q.log.Info("quasar: P-Chain connected", "height", q.pHeight)
|
|
}
|
|
|
|
// ConnectQuantumFallback connects the quantum signer fallback
|
|
func (q *Quasar) ConnectQuantumFallback(f QuantumSignerFallback) {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
|
|
q.quantumFallback = f
|
|
q.log.Info("quasar: quantum fallback connected")
|
|
}
|
|
|
|
// ConnectCorona connects the Corona threshold coordinator
|
|
func (q *Quasar) ConnectCorona(rc *CoronaCoordinator) {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
|
|
q.corona = rc
|
|
q.log.Info("quasar: Corona coordinator connected")
|
|
}
|
|
|
|
// InitializeCorona initializes the Corona coordinator with validators
|
|
func (q *Quasar) InitializeCorona(validators []ids.NodeID) error {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
|
|
if q.corona == nil {
|
|
// Create coordinator if not provided
|
|
numParties := len(validators)
|
|
threshold := (numParties * 2 / 3) + 1 // 2/3 + 1 threshold
|
|
if threshold < 2 {
|
|
threshold = 2
|
|
}
|
|
|
|
rc, err := NewCoronaCoordinator(q.log, CoronaConfig{
|
|
NumParties: numParties,
|
|
Threshold: threshold,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create Corona coordinator: %w", err)
|
|
}
|
|
q.corona = rc
|
|
}
|
|
|
|
if err := q.corona.Initialize(validators); err != nil {
|
|
return fmt.Errorf("failed to initialize Corona: %w", err)
|
|
}
|
|
|
|
q.log.Info("quasar: Corona initialized",
|
|
"validators", len(validators),
|
|
"threshold", q.corona.Stats().Threshold,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Start begins the quasar consensus loop
|
|
func (q *Quasar) Start(ctx context.Context) error {
|
|
q.mu.Lock()
|
|
if q.pChain == nil {
|
|
q.mu.Unlock()
|
|
return ErrPChainNotConnected
|
|
}
|
|
if q.quantumFallback == nil {
|
|
q.mu.Unlock()
|
|
return ErrQChainNotConnected
|
|
}
|
|
q.running = true
|
|
q.mu.Unlock()
|
|
|
|
// Subscribe to P-Chain finality
|
|
sub := q.pChain.SubscribeFinality()
|
|
go q.run(ctx, sub)
|
|
|
|
q.log.Info("quasar: started")
|
|
return nil
|
|
}
|
|
|
|
// Stop halts the quasar
|
|
func (q *Quasar) Stop() {
|
|
q.mu.Lock()
|
|
if q.running {
|
|
close(q.stopCh)
|
|
q.running = false
|
|
}
|
|
q.mu.Unlock()
|
|
q.log.Info("quasar: stopped")
|
|
}
|
|
|
|
// run is the main finality loop.
|
|
// Bounded by ctx.Done() and q.stopCh — exits when either fires.
|
|
// The goroutine is started in Start() and guaranteed to terminate
|
|
// when Stop() closes stopCh or the parent context is cancelled.
|
|
func (q *Quasar) run(ctx context.Context, sub <-chan FinalityEvent) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-q.stopCh:
|
|
return
|
|
case event := <-sub:
|
|
if err := q.processFinality(ctx, event); err != nil {
|
|
q.log.Error("quasar: finality failed",
|
|
"height", event.Height,
|
|
"error", err,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// processFinality processes a P-Chain finality event into hybrid finality
|
|
// Both BLS and Corona paths run IN PARALLEL
|
|
func (q *Quasar) processFinality(ctx context.Context, event FinalityEvent) error {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
|
|
// Sync validators to quasar core
|
|
for _, v := range event.Validators {
|
|
if v.Active {
|
|
_, _ = q.core.AddValidator(v.NodeID.String(), v.Weight)
|
|
}
|
|
}
|
|
|
|
// Create finality message
|
|
msg := q.createMessage(event)
|
|
msgStr := string(msg) // Corona uses string message
|
|
|
|
// Run BLS and Corona IN PARALLEL
|
|
var blsProof, signerBitset []byte
|
|
var signerWeight uint64
|
|
var coronaSig Signature
|
|
var blsLatency, coronaLatency time.Duration
|
|
var blsErr, coronaErr error
|
|
var wg sync.WaitGroup
|
|
|
|
// BLS path
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
start := time.Now()
|
|
blsProof, signerBitset, signerWeight, blsErr = q.collectBLS(event, msg)
|
|
blsLatency = time.Since(start)
|
|
}()
|
|
|
|
// Corona path - REQUIRED for Q-Chain validator consensus.
|
|
// No fallback mode: if Corona coordinator is not initialized,
|
|
// finality MUST fail to prevent accepting BLS-only proofs.
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if q.corona == nil || !q.corona.IsInitialized() {
|
|
// STRICT: No fallback allowed for validators.
|
|
// RT signatures are REQUIRED for quantum-safe consensus.
|
|
coronaErr = ErrCoronaNotConnected
|
|
return
|
|
}
|
|
// Full threshold signing
|
|
start := time.Now()
|
|
coronaSig, coronaErr = q.collectCorona(msgStr)
|
|
coronaLatency = time.Since(start)
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
// Check BLS result
|
|
if blsErr != nil {
|
|
return fmt.Errorf("BLS collection: %w", blsErr)
|
|
}
|
|
|
|
// Check Corona result
|
|
if coronaErr != nil {
|
|
return fmt.Errorf("Corona threshold: %w", coronaErr)
|
|
}
|
|
|
|
// Check quorum
|
|
totalWeight := q.totalWeight(event.Validators)
|
|
if !q.checkQuorum(signerWeight, totalWeight) {
|
|
return ErrInsufficientWeight
|
|
}
|
|
|
|
// Record finality
|
|
q.qHeight++
|
|
var coronaSigners []ids.NodeID
|
|
var coronaProof []byte
|
|
if coronaSig != nil {
|
|
coronaSigners = coronaSig.Signers()
|
|
coronaProof = coronaSig.Bytes()
|
|
}
|
|
finality := &QuantumFinality{
|
|
BlockID: event.BlockID,
|
|
PChainHeight: event.Height,
|
|
QChainHeight: q.qHeight,
|
|
BLSProof: blsProof,
|
|
CoronaProof: coronaProof,
|
|
SignerBitset: signerBitset,
|
|
CoronaSigners: coronaSigners,
|
|
TotalWeight: totalWeight,
|
|
SignerWeight: signerWeight,
|
|
BLSLatency: blsLatency,
|
|
CoronaLatency: coronaLatency,
|
|
Timestamp: time.Now(),
|
|
}
|
|
|
|
q.finalized[event.BlockID] = finality
|
|
q.pHeight = event.Height
|
|
|
|
// Prune old finality entries to bound memory
|
|
if q.maxFinalized > 0 && len(q.finalized) > q.maxFinalized {
|
|
cutoff := q.qHeight - uint64(q.maxFinalized)
|
|
for id, f := range q.finalized {
|
|
if f.QChainHeight < cutoff {
|
|
delete(q.finalized, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Emit
|
|
select {
|
|
case q.finalityCh <- finality:
|
|
default:
|
|
}
|
|
|
|
q.log.Info("quasar: hybrid finality achieved",
|
|
"block", event.BlockID,
|
|
"pHeight", event.Height,
|
|
"qHeight", q.qHeight,
|
|
"weight", fmt.Sprintf("%d/%d", signerWeight, totalWeight),
|
|
"blsLatency", blsLatency,
|
|
"coronaLatency", coronaLatency,
|
|
"coronaSigners", len(coronaSigners),
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// createMessage creates the finality message to sign
|
|
func (q *Quasar) createMessage(event FinalityEvent) []byte {
|
|
msg := make([]byte, 48) // 32 (blockID) + 8 (height) + 8 (timestamp)
|
|
copy(msg[:32], event.BlockID[:])
|
|
putUint64BE(msg[32:40], event.Height)
|
|
putUint64BE(msg[40:48], uint64(event.Timestamp.UnixNano()))
|
|
return msg
|
|
}
|
|
|
|
// collectBLS collects BLS signatures from validators and aggregates them
|
|
func (q *Quasar) collectBLS(event FinalityEvent, msg []byte) ([]byte, []byte, uint64, error) {
|
|
var signerBitset []byte
|
|
var signerWeight uint64
|
|
signatures := make([]*quasar.QuasarSig, 0, len(event.Validators))
|
|
|
|
for i, v := range event.Validators {
|
|
if !v.Active {
|
|
continue
|
|
}
|
|
|
|
sig, err := q.core.SignMessage(v.NodeID.String(), msg)
|
|
if err != nil {
|
|
continue // Skip failed signers
|
|
}
|
|
|
|
signatures = append(signatures, sig)
|
|
signerWeight += v.Weight
|
|
|
|
// Set bit
|
|
byteIdx := i / 8
|
|
for len(signerBitset) <= byteIdx {
|
|
signerBitset = append(signerBitset, 0)
|
|
}
|
|
signerBitset[byteIdx] |= 1 << uint(i%8)
|
|
}
|
|
|
|
if len(signatures) == 0 {
|
|
return nil, nil, 0, errors.New("no BLS signatures")
|
|
}
|
|
|
|
agg, err := q.core.AggregateSignatures(msg, signatures)
|
|
if err != nil {
|
|
return nil, nil, 0, err
|
|
}
|
|
|
|
return agg.BLSAggregated, signerBitset, signerWeight, nil
|
|
}
|
|
|
|
// collectCorona runs the 2-round Corona threshold protocol in parallel
|
|
func (q *Quasar) collectCorona(message string) (Signature, error) {
|
|
if q.corona == nil {
|
|
return nil, ErrCoronaNotConnected
|
|
}
|
|
|
|
// Use the high-level Sign API which handles all rounds internally
|
|
sig, err := q.corona.Sign([]byte(message))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("corona signing failed: %w", err)
|
|
}
|
|
|
|
// Verify the signature
|
|
if !q.corona.Verify([]byte(message), sig) {
|
|
return nil, ErrCoronaFailed
|
|
}
|
|
|
|
q.log.Debug("corona signature complete",
|
|
"signers", len(sig.Signers()),
|
|
"type", sig.Type(),
|
|
)
|
|
|
|
return sig, nil
|
|
}
|
|
|
|
// totalWeight calculates total validator weight
|
|
func (q *Quasar) totalWeight(validators []ValidatorState) uint64 {
|
|
var total uint64
|
|
for _, v := range validators {
|
|
if v.Active {
|
|
total += v.Weight
|
|
}
|
|
}
|
|
return total
|
|
}
|
|
|
|
// checkQuorum verifies quorum is met using cross-multiplication to avoid
|
|
// integer division truncation.
|
|
//
|
|
// signerWeight / totalWeight >= quorumNum / quorumDen
|
|
// is equivalent to:
|
|
// signerWeight * quorumDen >= totalWeight * quorumNum
|
|
//
|
|
// Overflow bound: safe for totalWeight < 2^62 with quorumNum <= 3.
|
|
// Production values: totalWeight is sum of validator weights (well under 2^60),
|
|
// quorumNum=2, quorumDen=3.
|
|
func (q *Quasar) checkQuorum(signerWeight, totalWeight uint64) bool {
|
|
return signerWeight*q.quorumDen >= totalWeight*q.quorumNum
|
|
}
|
|
|
|
// GetFinality returns finality for a block
|
|
func (q *Quasar) GetFinality(blockID ids.ID) (*QuantumFinality, bool) {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
f, ok := q.finalized[blockID]
|
|
return f, ok
|
|
}
|
|
|
|
// Subscribe returns channel for finality events
|
|
func (q *Quasar) Subscribe() <-chan *QuantumFinality {
|
|
return q.finalityCh
|
|
}
|
|
|
|
// Verify verifies a hybrid finality proof.
|
|
// Both BLS and Corona proofs are REQUIRED - no fallback mode.
|
|
// This ensures quantum-safe consensus for Q-Chain validators.
|
|
func (q *Quasar) Verify(finality *QuantumFinality) error {
|
|
if finality == nil {
|
|
return ErrFinalityFailed
|
|
}
|
|
|
|
// STRICT: Both proofs are REQUIRED
|
|
if len(finality.BLSProof) == 0 {
|
|
return fmt.Errorf("%w: BLS proof missing", ErrBLSFailed)
|
|
}
|
|
if len(finality.CoronaProof) == 0 {
|
|
return fmt.Errorf("%w: RT proof missing - required for Q-Chain validators", ErrCoronaFailed)
|
|
}
|
|
|
|
if !q.checkQuorum(finality.SignerWeight, finality.TotalWeight) {
|
|
return ErrInsufficientWeight
|
|
}
|
|
|
|
// Verify BLS via hybrid engine
|
|
agg := &quasar.AggregatedSignature{
|
|
BLSAggregated: finality.BLSProof,
|
|
}
|
|
|
|
// Reconstruct message for verification
|
|
msg := make([]byte, 48)
|
|
copy(msg[:32], finality.BlockID[:])
|
|
putUint64BE(msg[32:40], finality.PChainHeight)
|
|
putUint64BE(msg[40:48], uint64(finality.Timestamp.UnixNano()))
|
|
|
|
if !q.core.VerifyAggregatedSignature(msg, agg) {
|
|
return ErrBLSFailed
|
|
}
|
|
|
|
// Verify Corona threshold signature
|
|
// RT signatures MUST have the "RT" prefix marker followed by threshold data
|
|
if len(finality.CoronaProof) < 3 {
|
|
return fmt.Errorf("%w: RT proof too short", ErrCoronaFailed)
|
|
}
|
|
if finality.CoronaProof[0] != 'R' || finality.CoronaProof[1] != 'T' {
|
|
return fmt.Errorf("%w: invalid RT proof marker", ErrCoronaFailed)
|
|
}
|
|
|
|
// Verify threshold signers meet minimum requirement
|
|
if len(finality.CoronaSigners) < q.threshold {
|
|
return fmt.Errorf("%w: need %d signers, have %d",
|
|
ErrInsufficientSigners, q.threshold, len(finality.CoronaSigners))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Stats returns quasar statistics
|
|
func (q *Quasar) Stats() QuasarStats {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
|
|
var coronaStats CoronaStats
|
|
if q.corona != nil {
|
|
coronaStats = q.corona.Stats()
|
|
}
|
|
|
|
return QuasarStats{
|
|
PChainHeight: q.pHeight,
|
|
QChainHeight: q.qHeight,
|
|
FinalizedBlocks: len(q.finalized),
|
|
Threshold: q.threshold,
|
|
QuorumNum: q.quorumNum,
|
|
QuorumDen: q.quorumDen,
|
|
Running: q.running,
|
|
CoronaParties: coronaStats.NumParties,
|
|
CoronaThreshold: coronaStats.Threshold,
|
|
CoronaReady: coronaStats.Initialized,
|
|
}
|
|
}
|
|
|
|
// QuasarStats contains quasar statistics
|
|
type QuasarStats struct {
|
|
PChainHeight uint64
|
|
QChainHeight uint64
|
|
FinalizedBlocks int
|
|
Threshold int
|
|
QuorumNum uint64
|
|
QuorumDen uint64
|
|
Running bool
|
|
CoronaParties int
|
|
CoronaThreshold int
|
|
CoronaReady bool
|
|
}
|
|
|
|
// GetCore returns the underlying quasar core for testing
|
|
func (q *Quasar) GetCore() *quasar.Quasar {
|
|
return q.core
|
|
}
|
|
|
|
// GetCorona returns the Corona coordinator
|
|
func (q *Quasar) GetCorona() *CoronaCoordinator {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
return q.corona
|
|
}
|
|
|
|
// CheckQuorum verifies quorum is met (exported for testing)
|
|
func (q *Quasar) CheckQuorum(signerWeight, totalWeight uint64) bool {
|
|
return q.checkQuorum(signerWeight, totalWeight)
|
|
}
|
|
|
|
// CreateMessage creates the finality message to sign (exported for testing)
|
|
func (q *Quasar) CreateMessage(event FinalityEvent) []byte {
|
|
return q.createMessage(event)
|
|
}
|
|
|
|
// TotalWeight calculates total validator weight (exported for testing)
|
|
func (q *Quasar) TotalWeight(validators []ValidatorState) uint64 {
|
|
return q.totalWeight(validators)
|
|
}
|
|
|
|
// GetConfig returns quorum configuration (exported for testing)
|
|
func (q *Quasar) GetConfig() (threshold int, quorumNum, quorumDen uint64) {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
return q.threshold, q.quorumNum, q.quorumDen
|
|
}
|
|
|
|
// IsRunning returns whether the Quasar is currently running (exported for testing)
|
|
func (q *Quasar) IsRunning() bool {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
return q.running
|
|
}
|
|
|
|
// SetFinalized adds a finality record (exported for testing/benchmarking)
|
|
func (q *Quasar) SetFinalized(blockID ids.ID, finality *QuantumFinality) {
|
|
q.mu.Lock()
|
|
defer q.mu.Unlock()
|
|
q.finalized[blockID] = finality
|
|
}
|
|
|
|
// GetFinalized retrieves a finality record (exported for testing)
|
|
func (q *Quasar) GetFinalized(blockID ids.ID) (*QuantumFinality, bool) {
|
|
q.mu.RLock()
|
|
defer q.mu.RUnlock()
|
|
f, ok := q.finalized[blockID]
|
|
return f, ok
|
|
}
|
|
|
|
// Helper: big-endian uint64
|
|
func putUint64BE(b []byte, v uint64) {
|
|
for i := 0; i < 8; i++ {
|
|
b[i] = byte(v >> (56 - i*8))
|
|
}
|
|
}
|