Files
consensus/engine/pq/engine_test.go
zeekay 9b9bdda290 quasar: rename Ringtail→Corona, MLDSAProof→MLDSARollup, drop "triple"
The Quasar cert layers are the right cardinality of "Double Lattice +
Identity Rollup + optional Classical Fast-path", not "Triple Quantum":

  BLS-12-381  classical fast-path (pairing) — empty in pure-PQ
  Corona      Ring-LWE  threshold ML-DSA (luxfi/corona; production R-LWE)
  Pulsar      Module-LWE threshold ML-DSA (luxfi/pulsar; Class N1 byte-equal
                                            to FIPS 204)
  MLDSARollup per-validator ML-DSA-65 rolled into one STARK / Groth16 proof
                                            (Z-Chain → P3Q)

"Triple Quantum" misled because BLS is classical — at most one of three
legs was quantum in the historic BLS+Ringtail+ZK shape. The honest PQ
cardinality is **Double Lattice** (Ring + Module). Pure-PQ profiles
drop BLS entirely and rely on Corona + Pulsar.

Ringtail is the academic R-LWE construction with trusted-dealer DKG;
it stays out of any open public-chain path. Corona is the production
Ring-LWE library (Pedersen DKG over R_q + proactive resharing).

Surface (in protocol/quasar/types.go):
  QuasarCert.BLS         → unchanged (classical fast-path, optional)
  QuasarCert.Ringtail    → QuasarCert.Corona       (Ring-LWE threshold sig)
  QuasarCert.MLDSAProof  → QuasarCert.MLDSARollup  (succinct identity rollup)
  QuasarCert            now also has a Pulsar field (Module-LWE threshold sig,
                                                     populated when wired)

  QuasarSig.Ringtail     → QuasarSig.Corona  (also added Pulsar []byte)
  FinalityEvent.Ringtail / MLDSAProof → Corona / Pulsar / MLDSARollup

  New predicates:
    QuasarCert.IsDoubleLattice()       — Corona ∧ Pulsar (honest PQ count)
    QuasarCert.HasClassicalFastPath()  — len(BLS) > 0
    QuasarCert.HasIdentityRollup()     — len(MLDSARollup) > 0

The wire byte layout is unchanged (length-prefixed slots). Callers that
read the bytes don't notice. Source-level references that compiled
before the rename now compile against the new identifiers; bulk
rename swept the consensus repo (22 files, 219 insertions, 151
deletions) plus the engine/pq facing FinalityEvent.

Tests: GOWORK=off go test -short -count=1 ./... → all 28 packages green
(quasar suite 32s; integration 60s; no regressions).
2026-05-12 08:40:06 -07:00

228 lines
5.1 KiB
Go

package pq
import (
"context"
"testing"
"time"
"github.com/luxfi/consensus/config"
"github.com/luxfi/ids"
)
func TestNew(t *testing.T) {
params := config.DefaultParams()
engine := NewConsensus(params)
if engine == nil {
t.Fatal("New() returned nil")
}
if engine.params.K != params.K {
t.Errorf("K mismatch: got %d, want %d", engine.params.K, params.K)
}
if engine.Height() != 0 {
t.Errorf("Initial height should be 0, got %d", engine.Height())
}
}
func TestInitialize(t *testing.T) {
engine := NewConsensus(config.DefaultParams())
ctx := context.Background()
blsKey, pqKey := GenerateTestKeys()
err := engine.Initialize(ctx, blsKey, pqKey)
if err != nil {
t.Fatalf("Initialize failed: %v", err)
}
}
func TestProcessBlock(t *testing.T) {
engine := NewConsensus(config.DefaultParams())
ctx := context.Background()
blsKey, pqKey := GenerateTestKeys()
err := engine.Initialize(ctx, blsKey, pqKey)
if err != nil {
t.Fatalf("Initialize failed: %v", err)
}
blockID := ids.GenerateTestID()
// Test insufficient votes
votes := map[string]int{
blockID.String(): 10,
"other": 11,
}
err = engine.ProcessBlock(ctx, blockID, votes)
if err == nil {
t.Error("ProcessBlock should fail with insufficient votes")
}
// Test sufficient votes
votes = map[string]int{
blockID.String(): 18,
"other": 3,
}
err = engine.ProcessBlock(ctx, blockID, votes)
if err != nil {
t.Errorf("ProcessBlock failed with sufficient votes: %v", err)
}
// Check finalized
if !engine.IsFinalized(blockID) {
t.Error("Block should be finalized")
}
// Check height increased
if engine.Height() != 1 {
t.Errorf("Height should be 1, got %d", engine.Height())
}
// Test already finalized
err = engine.ProcessBlock(ctx, blockID, votes)
if err != nil {
t.Error("ProcessBlock should succeed for already finalized block")
}
}
func TestFinalityChannel(t *testing.T) {
engine := NewConsensus(config.DefaultParams())
ctx := context.Background()
blsKey, pqKey := GenerateTestKeys()
err := engine.Initialize(ctx, blsKey, pqKey)
if err != nil {
t.Fatalf("Initialize failed: %v", err)
}
// Start listening for finality events
finality := engine.FinalityChannel()
blockID := ids.GenerateTestID()
votes := map[string]int{
blockID.String(): 18,
"other": 3,
}
// Process block in background
go func() {
time.Sleep(10 * time.Millisecond)
_ = engine.ProcessBlock(ctx, blockID, votes)
}()
// Wait for finality event
select {
case event := <-finality:
if event.BlockID != blockID {
t.Errorf("Wrong block ID in event: got %s, want %s", event.BlockID, blockID)
}
if event.Height != 1 {
t.Errorf("Wrong height in event: got %d, want 1", event.Height)
}
if len(event.MLDSARollup) == 0 {
t.Error("Missing PQ proof in event")
}
if len(event.BLSProof) == 0 {
t.Error("Missing BLS proof in event")
}
case <-time.After(100 * time.Millisecond):
t.Fatal("Timeout waiting for finality event")
}
}
func TestSetFinalizedCallback(t *testing.T) {
engine := NewConsensus(config.DefaultParams())
called := false
engine.SetFinalizedCallback(func(event FinalityEvent) {
called = true
if event.Height == 0 {
t.Error("Event height should not be 0")
}
})
// Trigger callback through quasar
if engine.quasar != nil {
// This tests that the callback is properly wired
// In production, this would be triggered by actual consensus
t.Log("Quasar is properly initialized for event horizon callbacks")
}
// The callback is set, even if not triggered in this test
_ = called
// Test metrics
metrics := engine.Metrics()
if metrics["height"] != uint64(0) {
t.Errorf("Wrong height in metrics: %v", metrics["height"])
}
if metrics["k"] != engine.params.K {
t.Errorf("Wrong K in metrics: %v", metrics["k"])
}
}
func TestMultipleBlocks(t *testing.T) {
engine := NewConsensus(config.XChainParams()) // Fast params
ctx := context.Background()
blsKey, pqKey := GenerateTestKeys()
err := engine.Initialize(ctx, blsKey, pqKey)
if err != nil {
t.Fatalf("Initialize failed: %v", err)
}
// Process multiple blocks
for i := 0; i < 5; i++ {
blockID := ids.GenerateTestID()
votes := map[string]int{
blockID.String(): 4, // 4/5 = 0.8 > 0.6 (alpha for XChain)
"other": 1,
}
err := engine.ProcessBlock(ctx, blockID, votes)
if err != nil {
t.Errorf("Failed to process block %d: %v", i, err)
}
if !engine.IsFinalized(blockID) {
t.Errorf("Block %d should be finalized", i)
}
}
if engine.Height() != 5 {
t.Errorf("Height should be 5, got %d", engine.Height())
}
}
func BenchmarkProcessBlock(b *testing.B) {
engine := NewConsensus(config.XChainParams())
ctx := context.Background()
blsKey, pqKey := GenerateTestKeys()
_ = engine.Initialize(ctx, blsKey, pqKey)
votes := map[string]int{
"block": 4,
"other": 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
blockID := ids.GenerateTestID()
votes[blockID.String()] = 4
_ = engine.ProcessBlock(ctx, blockID, votes)
}
}
func BenchmarkMetrics(b *testing.B) {
engine := NewConsensus(config.DefaultParams())
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = engine.Metrics()
}
}