Files
consensus/pkg/wire/policies_test.go
zeekay 09dfcaaefb quasar: kill all remaining Ringtail identifiers; rename test/bench helpers
Final identifier sweep. After this commit zero 'Ringtail' / 'RINGTAIL'
references remain in any Go file in the consensus repo. Surface
changes (compound-identifier camelCase):

  bench:
    pqModesRingtailCounts → pqModesCoronaCounts
    ringtailMaxRetries    → coronaMaxRetries
    ringtailFixture       → coronaFixture
    newRingtailFixture    → newCoronaFixture
    BenchmarkPQModes_BLSPlusRingtail → BenchmarkPQModes_BLSPlusCorona

  protocol/quasar:
    RingtailSignature  → CoronaSignature
    RingtailRound1Data → CoronaRound1Data
    RingtailRound1State→ CoronaRound1State
    RingtailBits       → CoronaBits
    RingtailProof      → CoronaProof
    RingtailAggregated → CoronaAggregated
    verifyRingtailThreshold → verifyCoronaThreshold
    ringtailCount/Bits/ShareMap → coronaCount/Bits/ShareMap
    TestQuantumFinalityWithRingtail → TestQuantumFinalityWithCorona
    TestGenerateCert_RefusesPartialTriple_WithSigner_NoRingtail
        → TestGenerateCert_RefusesPartialTriple_WithSigner_NoCorona
    TestQuasarCert_Verify_RejectsMissingRingtail
        → TestQuasarCert_Verify_RejectsMissingCorona
    TestVerkleWitness_VerifyRingtailThreshold
        → TestVerkleWitness_VerifyCoronaThreshold
    TestVerkleWitness_FullVerification_RingtailThresholdNotMet
        → TestVerkleWitness_FullVerification_CoronaThresholdNotMet
    TestRingtailCertSize_BinaryVsGob → TestCoronaCertSize_BinaryVsGob
    TestNoRingtailDirectImport → TestNoCoronaDirectImport
    file rename: ringtail_death_test.go → corona_death_test.go
    TestQuasar_ValidatorSetSync_BLSAndRingtail
        → TestQuasar_ValidatorSetSync_BLSAndCorona

  engine/pq:
    CompleteRingtailRound2 → CompleteCoronaRound2
    GetRingtailGroupKey    → GetCoronaGroupKey
    TestGetRingtailGroupKey → TestGetCoronaGroupKey
    ringtailShare/Signer/Group → coronaShare/Signer/Group

  pkg/wire:
    SigRingtail → SigCorona
    TestQuantumPolicyRingtailOnlyRTNotRequired
        → TestQuantumPolicyCoronaOnlyRTNotRequired
    ringtailHash → coronaHash

Parallel Ring+Module Double-Lattice PQ posture stands: QuasarCert
carries both Corona (Ring-LWE) and Pulsar (Module-LWE) bytes,
checkable independently. Pure-PQ profile drops BLS entirely.

Tests green: quasar 42s, engine/pq 2s, bench 1s, wire 2s, config 1s.
2026-05-12 09:41:44 -07:00

795 lines
22 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package wire
import (
"context"
"fmt"
"testing"
)
// --- NonePolicy edge cases ---
func TestNonePolicyVerifyWrongPolicy(t *testing.T) {
policy := NewNonePolicy()
cert := &Certificate{PolicyID: PolicyQuorum}
ok, err := policy.Verify(context.Background(), cert)
if err != nil {
t.Fatal(err)
}
if ok {
t.Error("NonePolicy should reject certificate with wrong policy ID")
}
}
func TestNonePolicyOnVote(t *testing.T) {
policy := NewNonePolicy()
vote := NewVote(EmptyCandidateID, EmptyVoterID, 0, true)
err := policy.OnVote(context.Background(), vote)
if err != nil {
t.Errorf("NonePolicy.OnVote should always return nil, got %v", err)
}
}
func TestNonePolicyMaybeFinalizeUnknownCandidate(t *testing.T) {
policy := NewNonePolicy()
cert, err := policy.MaybeFinalize(context.Background(), DeriveItemID([]byte("unknown")))
if err != nil {
t.Fatal(err)
}
if cert != nil {
t.Error("should return nil for unknown candidate")
}
}
func TestNonePolicyMaybeFinalizeCached(t *testing.T) {
ctx := context.Background()
policy := NewNonePolicy()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
cert1, _ := policy.MaybeFinalize(ctx, c.ID)
cert2, _ := policy.MaybeFinalize(ctx, c.ID)
if cert1 != cert2 {
t.Error("should return cached certificate")
}
}
func TestNonePolicyCandidateLimit(t *testing.T) {
ctx := context.Background()
policy := NewNonePolicy()
// Fill to capacity
for i := 0; i < maxCandidates; i++ {
c := NewCandidate([]byte("d"), []byte(fmt.Sprintf("p%d", i)), EmptyCandidateID, uint64(i))
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatalf("should accept candidate %d: %v", i, err)
}
}
// One more should fail
c := NewCandidate([]byte("d"), []byte("overflow"), EmptyCandidateID, maxCandidates)
err := policy.OnCandidate(ctx, c)
if err == nil {
t.Error("should reject candidate beyond limit")
}
}
// --- QuorumPolicy edge cases ---
func TestQuorumPolicyVerify(t *testing.T) {
policy := NewQuorumPolicy(3, 5)
ctx := context.Background()
// Wrong policy ID
ok, _ := policy.Verify(ctx, &Certificate{PolicyID: PolicyNone})
if ok {
t.Error("should reject wrong policy ID")
}
// Empty proof
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicyQuorum})
if ok {
t.Error("should reject empty proof")
}
// Proof present but signers too short
ok, _ = policy.Verify(ctx, &Certificate{
PolicyID: PolicyQuorum,
Proof: []byte("proof"),
Signers: []byte("short"),
})
if ok {
t.Error("should reject short signers")
}
// Valid: proof and enough signers (3 * 32 bytes)
signers := make([]byte, 3*32)
ok, _ = policy.Verify(ctx, &Certificate{
PolicyID: PolicyQuorum,
Proof: []byte("proof"),
Signers: signers,
})
if !ok {
t.Error("should accept valid certificate")
}
}
func TestQuorumPolicyVoteBeforeCandidate(t *testing.T) {
ctx := context.Background()
policy := NewQuorumPolicy(1, 1)
// Vote for an unknown candidate -- should create vote map on the fly
voter := DeriveVoterID("agent", []byte("a"))
candidateID := DeriveItemID([]byte("unknown"))
vote := NewVote(candidateID, voter, 0, true)
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("should accept vote for unknown candidate: %v", err)
}
}
func TestQuorumPolicyRejectVotesNotPreferred(t *testing.T) {
ctx := context.Background()
policy := NewQuorumPolicy(1, 1)
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// Vote with preference=false (reject)
voter := DeriveVoterID("agent", []byte("a"))
vote := NewVote(c.ID, voter, 0, false)
vote.Signature = []byte{SigBLS, 1}
policy.OnVote(ctx, vote)
cert, _ := policy.MaybeFinalize(ctx, c.ID)
if cert != nil {
t.Error("should not finalize with only reject votes")
}
}
func TestQuorumPolicyCandidateLimit(t *testing.T) {
ctx := context.Background()
policy := NewQuorumPolicy(1, 1)
for i := 0; i < maxCandidates; i++ {
c := NewCandidate([]byte("d"), []byte(fmt.Sprintf("p%d", i)), EmptyCandidateID, uint64(i))
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatalf("failed at %d: %v", i, err)
}
}
c := NewCandidate([]byte("d"), []byte("overflow"), EmptyCandidateID, maxCandidates)
if err := policy.OnCandidate(ctx, c); err == nil {
t.Error("should reject beyond limit")
}
}
// --- SamplePolicy edge cases ---
func TestSamplePolicyVerify(t *testing.T) {
policy := NewSamplePolicy(3, 0.6, 2)
ctx := context.Background()
// Wrong policy
ok, _ := policy.Verify(ctx, &Certificate{PolicyID: PolicyNone})
if ok {
t.Error("should reject wrong policy")
}
// Short proof
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicySampleConvergence, Proof: []byte{1}})
if ok {
t.Error("should reject short proof")
}
// Low confidence
proof := make([]byte, 9)
proof[0] = 1 // confidence=1, beta=2
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicySampleConvergence, Proof: proof})
if ok {
t.Error("should reject low confidence")
}
// Sufficient confidence
proof[0] = 3
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicySampleConvergence, Proof: proof})
if !ok {
t.Error("should accept sufficient confidence")
}
}
func TestSamplePolicyVoteUnknownCandidate(t *testing.T) {
policy := NewSamplePolicy(3, 0.6, 2)
vote := NewVote(DeriveItemID([]byte("x")), EmptyVoterID, 0, true)
err := policy.OnVote(context.Background(), vote)
if err != nil {
t.Errorf("should silently ignore vote for unknown candidate: %v", err)
}
}
func TestSamplePolicyConvergesToReject(t *testing.T) {
ctx := context.Background()
policy := NewSamplePolicy(3, 0.6, 2)
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// 2 rounds of reject votes (all false)
for round := uint64(0); round < 2; round++ {
for i := 0; i < 3; i++ {
voter := DeriveVoterID("a", []byte{byte(i)})
vote := NewVote(c.ID, voter, round, false)
policy.OnVote(ctx, vote)
}
}
cert, _ := policy.MaybeFinalize(ctx, c.ID)
if cert != nil {
t.Error("should not produce cert when converged to reject")
}
}
func TestSamplePolicyPreferenceFlip(t *testing.T) {
ctx := context.Background()
policy := NewSamplePolicy(3, 0.6, 3) // beta=3
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// Round 0: yes (confidence 1)
for i := 0; i < 3; i++ {
voter := DeriveVoterID("a", []byte{byte(i)})
policy.OnVote(ctx, NewVote(c.ID, voter, 0, true))
}
// Round 1: no -- preference flips, confidence resets to 1
for i := 0; i < 3; i++ {
voter := DeriveVoterID("a", []byte{byte(i)})
policy.OnVote(ctx, NewVote(c.ID, voter, 1, false))
}
// Round 2: yes -- preference flips again, confidence 1
for i := 0; i < 3; i++ {
voter := DeriveVoterID("a", []byte{byte(i)})
policy.OnVote(ctx, NewVote(c.ID, voter, 2, true))
}
cert, _ := policy.MaybeFinalize(ctx, c.ID)
if cert != nil {
t.Error("should not finalize -- confidence never reached beta=3")
}
}
func TestSamplePolicyCandidateLimit(t *testing.T) {
ctx := context.Background()
policy := NewSamplePolicy(1, 1.0, 1)
for i := 0; i < maxCandidates; i++ {
c := NewCandidate([]byte("d"), []byte(fmt.Sprintf("p%d", i)), EmptyCandidateID, uint64(i))
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatalf("failed at %d: %v", i, err)
}
}
c := NewCandidate([]byte("d"), []byte("overflow"), EmptyCandidateID, maxCandidates)
if err := policy.OnCandidate(ctx, c); err == nil {
t.Error("should reject beyond limit")
}
}
// --- L1Policy ---
type mockL1Verifier struct {
proofs map[CandidateID][]byte
verifyFunc func(ctx context.Context, id CandidateID, proof []byte) (bool, error)
}
func (m *mockL1Verifier) GetInclusionProof(ctx context.Context, id CandidateID) ([]byte, error) {
p, ok := m.proofs[id]
if !ok {
return nil, nil
}
return p, nil
}
func (m *mockL1Verifier) VerifyInclusion(ctx context.Context, id CandidateID, proof []byte) (bool, error) {
if m.verifyFunc != nil {
return m.verifyFunc(ctx, id, proof)
}
return len(proof) > 0, nil
}
func TestL1PolicyFullLifecycle(t *testing.T) {
ctx := context.Background()
verifier := &mockL1Verifier{proofs: make(map[CandidateID][]byte)}
policy := NewL1Policy(verifier)
if policy.PolicyID() != PolicyL1Inclusion {
t.Error("wrong policy ID")
}
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatal(err)
}
// OnVote is a no-op
if err := policy.OnVote(ctx, NewVote(c.ID, EmptyVoterID, 0, true)); err != nil {
t.Errorf("OnVote should return nil: %v", err)
}
// Not included yet
cert, _ := policy.MaybeFinalize(ctx, c.ID)
if cert != nil {
t.Error("should not finalize without L1 inclusion")
}
// Simulate L1 inclusion
verifier.proofs[c.ID] = []byte("merkle-proof")
cert, _ = policy.MaybeFinalize(ctx, c.ID)
if cert == nil {
t.Fatal("should finalize with L1 inclusion proof")
}
if cert.PolicyID != PolicyL1Inclusion {
t.Error("wrong cert policy ID")
}
// Cached return
cert2, _ := policy.MaybeFinalize(ctx, c.ID)
if cert2 != cert {
t.Error("should return cached cert")
}
}
func TestL1PolicyVerify(t *testing.T) {
ctx := context.Background()
verifier := &mockL1Verifier{
proofs: make(map[CandidateID][]byte),
verifyFunc: func(_ context.Context, _ CandidateID, proof []byte) (bool, error) {
return string(proof) == "valid", nil
},
}
policy := NewL1Policy(verifier)
// Wrong policy ID
ok, _ := policy.Verify(ctx, &Certificate{PolicyID: PolicyNone})
if ok {
t.Error("should reject wrong policy")
}
// Valid proof
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicyL1Inclusion, Proof: []byte("valid")})
if !ok {
t.Error("should accept valid proof")
}
// Invalid proof
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicyL1Inclusion, Proof: []byte("invalid")})
if ok {
t.Error("should reject invalid proof")
}
}
func TestL1PolicyUnknownCandidate(t *testing.T) {
ctx := context.Background()
verifier := &mockL1Verifier{proofs: make(map[CandidateID][]byte)}
policy := NewL1Policy(verifier)
cert, err := policy.MaybeFinalize(ctx, DeriveItemID([]byte("unknown")))
if err != nil {
t.Fatal(err)
}
if cert != nil {
t.Error("should return nil for unknown candidate")
}
}
func TestL1PolicyCandidateLimit(t *testing.T) {
ctx := context.Background()
verifier := &mockL1Verifier{proofs: make(map[CandidateID][]byte)}
policy := NewL1Policy(verifier)
for i := 0; i < maxCandidates; i++ {
c := NewCandidate([]byte("d"), []byte(fmt.Sprintf("p%d", i)), EmptyCandidateID, uint64(i))
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatalf("failed at %d: %v", i, err)
}
}
c := NewCandidate([]byte("d"), []byte("overflow"), EmptyCandidateID, maxCandidates)
if err := policy.OnCandidate(ctx, c); err == nil {
t.Error("should reject beyond limit")
}
}
// --- QuantumPolicy additional edge cases ---
func TestQuantumPolicySetRequireRT(t *testing.T) {
policy := NewQuantumPolicy(1)
policy.SetRequireRT(false)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// BLS-only should be accepted after disabling RT requirement
voter := DeriveVoterID("a", []byte("v"))
vote := NewVote(c.ID, voter, 0, true)
vote.Signature = []byte{SigBLS, 1, 2, 3}
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("BLS should be accepted with RT disabled: %v", err)
}
}
func TestQuantumPolicyRejectVoteNoPreference(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// Vote with preference=false -- silently ignored
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, false)
vote.Signature = []byte{SigQuasar, 0, 2, 1, 2, 3, 4}
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("reject vote should be silently ignored: %v", err)
}
}
func TestQuantumPolicyEmptySignatureRTRequired(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
// No signature
err := policy.OnVote(ctx, vote)
if err == nil {
t.Error("should reject empty signature when RT required")
}
rtErr, ok := err.(*RTRequirementError)
if !ok {
t.Errorf("expected RTRequirementError, got %T", err)
}
if rtErr.Error() == "" {
t.Error("error message should not be empty")
}
}
func TestQuantumPolicyEmptySignatureRTNotRequired(t *testing.T) {
policy := NewQuantumPolicyWithOptions(1, false)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("empty sig should be ok when RT not required: %v", err)
}
}
func TestQuantumPolicyQuasarTooShort(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{SigQuasar, 0} // too short
err := policy.OnVote(ctx, vote)
if err == nil {
t.Error("should reject too-short Quasar signature")
}
}
func TestQuantumPolicyQuasarZeroBLS(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{SigQuasar, 0, 0, 1} // bls_len=0
err := policy.OnVote(ctx, vote)
if err == nil {
t.Error("should reject zero-length BLS in Quasar")
}
}
func TestQuantumPolicyQuasarTruncatedBLS(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{SigQuasar, 0, 10} // claims 10 bytes BLS but has 0
err := policy.OnVote(ctx, vote)
if err == nil {
t.Error("should reject truncated BLS in Quasar")
}
}
func TestQuantumPolicyFinalizationAndVerify(t *testing.T) {
ctx := context.Background()
policy := NewQuantumPolicy(2)
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// Add 2 Quasar votes (threshold=2)
for i := 0; i < 2; i++ {
voter := DeriveVoterID("a", []byte{byte(i)})
blsSig := make([]byte, 48)
blsSig[0] = byte(i)
rtSig := make([]byte, 32)
rtSig[0] = byte(i)
sig := []byte{SigQuasar, 0, byte(len(blsSig))}
sig = append(sig, blsSig...)
sig = append(sig, rtSig...)
vote := NewVote(c.ID, voter, 0, true)
vote.Signature = sig
if err := policy.OnVote(ctx, vote); err != nil {
t.Fatalf("vote %d failed: %v", i, err)
}
}
cert, err := policy.MaybeFinalize(ctx, c.ID)
if err != nil {
t.Fatal(err)
}
if cert == nil {
t.Fatal("should finalize with 2 votes")
}
if cert.PolicyID != PolicyQuantum {
t.Error("wrong policy ID on cert")
}
// Verify
ok, _ := policy.Verify(ctx, cert)
if !ok {
t.Error("should verify own certificate")
}
// Cached
cert2, _ := policy.MaybeFinalize(ctx, c.ID)
if cert2 != cert {
t.Error("should return cached cert")
}
}
func TestQuantumPolicyVerifyEdgeCases(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
// Wrong policy
ok, _ := policy.Verify(ctx, &Certificate{PolicyID: PolicyNone})
if ok {
t.Error("should reject wrong policy")
}
// Short proof
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicyQuantum, Proof: []byte{SigQuasar}})
if ok {
t.Error("should reject short proof")
}
// Wrong scheme byte
proof := make([]byte, 33)
proof[0] = SigBLS // not SigQuasar
ok, _ = policy.Verify(ctx, &Certificate{PolicyID: PolicyQuantum, Proof: proof})
if ok {
t.Error("should reject non-Quasar scheme byte")
}
}
func TestQuantumPolicyMaybeFinalizeBelowThreshold(t *testing.T) {
ctx := context.Background()
policy := NewQuantumPolicy(3)
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
// Only 1 vote (threshold=3)
voter := DeriveVoterID("a", []byte("v"))
sig := []byte{SigQuasar, 0, 2, 1, 2, 3, 4}
vote := NewVote(c.ID, voter, 0, true)
vote.Signature = sig
policy.OnVote(ctx, vote)
cert, _ := policy.MaybeFinalize(ctx, c.ID)
if cert != nil {
t.Error("should not finalize below threshold")
}
}
func TestQuantumPolicyUnknownCandidate(t *testing.T) {
policy := NewQuantumPolicy(1)
cert, _ := policy.MaybeFinalize(context.Background(), DeriveItemID([]byte("nope")))
if cert != nil {
t.Error("should return nil for unknown")
}
}
func TestQuantumPolicyCandidateLimit(t *testing.T) {
ctx := context.Background()
policy := NewQuantumPolicy(1)
for i := 0; i < maxCandidates; i++ {
c := NewCandidate([]byte("d"), []byte(fmt.Sprintf("p%d", i)), EmptyCandidateID, uint64(i))
if err := policy.OnCandidate(ctx, c); err != nil {
t.Fatalf("failed at %d: %v", i, err)
}
}
c := NewCandidate([]byte("d"), []byte("overflow"), EmptyCandidateID, maxCandidates)
if err := policy.OnCandidate(ctx, c); err == nil {
t.Error("should reject beyond limit")
}
}
func TestQuantumPolicyUnknownSchemeRTRequired(t *testing.T) {
policy := NewQuantumPolicy(1)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{0xFF, 1, 2, 3} // unknown scheme
err := policy.OnVote(ctx, vote)
if err == nil {
t.Error("should reject unknown scheme when RT required")
}
}
func TestQuantumPolicyUnknownSchemeRTNotRequired(t *testing.T) {
policy := NewQuantumPolicyWithOptions(1, false)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{0xFF, 1, 2, 3}
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("should ignore unknown scheme when RT not required: %v", err)
}
}
func TestQuantumPolicyCoronaOnlyRTNotRequired(t *testing.T) {
policy := NewQuantumPolicyWithOptions(1, false)
ctx := context.Background()
c := NewCandidate([]byte("d"), []byte("p"), EmptyCandidateID, 1)
policy.OnCandidate(ctx, c)
vote := NewVote(c.ID, DeriveVoterID("a", []byte("v")), 0, true)
vote.Signature = []byte{SigCorona, 4, 5, 6}
err := policy.OnVote(ctx, vote)
if err != nil {
t.Errorf("RT-only vote should be accepted when RT not required: %v", err)
}
}
// --- sigSchemeToString ---
func TestSigSchemeToString(t *testing.T) {
tests := []struct {
scheme byte
expect string
}{
{SigNone, "SigNone"},
{SigEd25519, "SigEd25519"},
{SigBLS, "SigBLS"},
{SigCorona, "SigCorona"},
{SigQuasar, "SigQuasar"},
{0xFF, "Unknown"},
}
for _, tt := range tests {
got := sigSchemeToString(tt.scheme)
if got != tt.expect {
t.Errorf("sigSchemeToString(%d) = %s, want %s", tt.scheme, got, tt.expect)
}
}
}
// --- HostnameValidationError.Error() ---
func TestHostnameValidationErrorMessage(t *testing.T) {
e := &HostnameValidationError{Address: "1.2.3.4", Reason: "bad"}
msg := e.Error()
if msg == "" {
t.Error("error message should not be empty")
}
}
// --- ValidatePeerAddress ---
func TestValidatePeerAddress(t *testing.T) {
// Valid
if err := ValidatePeerAddress("node1.lux.network:9651"); err != nil {
t.Errorf("valid hostname should pass: %v", err)
}
// Invalid
if err := ValidatePeerAddress("10.0.0.1:9651"); err == nil {
t.Error("IP literal should be rejected")
}
}
// --- RTRequirementError.Error() ---
func TestRTRequirementErrorMessage(t *testing.T) {
e := &RTRequirementError{Reason: "test reason"}
if e.Error() != "RT signature required: test reason" {
t.Errorf("unexpected error: %s", e.Error())
}
}
// --- CredentialValidationError.Error() ---
func TestCredentialValidationErrorMessage(t *testing.T) {
e := &CredentialValidationError{Index: 0, Expected: 100, Got: 50, Reason: "mismatch"}
if e.Error() != "mismatch" {
t.Errorf("unexpected: %s", e.Error())
}
}
// --- FinalityWitnesses parallel-witness model ---
func TestPolicyWitnessesRoundtrip(t *testing.T) {
cases := []struct {
policy PolicyID
expected FinalityWitnesses
}{
{PolicyQuorum, WitnessP},
{PolicyPQ, WitnessP | WitnessQ},
{PolicyPZ, WitnessP | WitnessZ},
{PolicyQuantum, WitnessP | WitnessQ | WitnessZ},
}
for _, c := range cases {
got := c.policy.Witnesses()
if got != c.expected {
t.Errorf("policy %d witnesses=%b want %b", c.policy, got, c.expected)
}
if PolicyForWitnesses(got) != c.policy {
t.Errorf("policy %d roundtrip failed: PolicyForWitnesses(%b)=%d", c.policy, got, PolicyForWitnesses(got))
}
if err := got.Validate(); err != nil {
t.Errorf("policy %d witnesses must validate: %v", c.policy, err)
}
}
}
func TestFinalityWitnessesValidate(t *testing.T) {
if err := FinalityWitnesses(0).Validate(); err == nil {
t.Error("empty witness set must require P")
}
if err := WitnessQ.Validate(); err == nil {
t.Error("Q without P must fail")
}
if err := (WitnessP | 0x80).Validate(); err == nil {
t.Error("unknown bit must fail")
}
if err := WitnessP.Validate(); err != nil {
t.Errorf("WitnessP alone must validate: %v", err)
}
}
func TestFinalityWitnessesHas(t *testing.T) {
full := WitnessP | WitnessQ | WitnessZ
if !full.Has(WitnessP) {
t.Error("PQZ must contain P")
}
if !full.Has(WitnessQ | WitnessZ) {
t.Error("PQZ must contain QZ")
}
if WitnessP.Has(WitnessQ) {
t.Error("P must not contain Q")
}
}