mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
quantumvm: count distinct validators toward both finality thresholds
AddCoronaSignature and AddBLSSignature appended caller-supplied signatures with no signer check, and TryFinalize compares len(signatures) against q.threshold. One peer resending the same signature therefore drove the count to the threshold on its own. Proven: three calls carrying one validator's signature left three entries in the list. The Corona leg is the sharp end: TryFinalize finalizes it on count alone and verifies none of the signatures, unlike the BLS leg which aggregates and verifies. So a single peer satisfied the post-quantum half of quantum finality by itself. Insertion for both legs now goes through addBLS/addCorona, which record one signature per ValidatorID, and SignBlock's own contribution goes through the same door instead of appending directly. Tests: TestOneValidatorCannotReachCoronaThreshold, TestDistinctValidatorsReachCoronaThreshold, TestOneValidatorCannotReachBLSThreshold. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+45
-5
@@ -13,6 +13,7 @@ package quantumvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
@@ -195,8 +196,8 @@ func (q *Quasar) SignBlock(ctx context.Context, blockID ids.ID, blockHash []byte
|
||||
}
|
||||
|
||||
q.mu.Lock()
|
||||
pending.BLSSignatures = append(pending.BLSSignatures, blsSig)
|
||||
pending.CoronaSignatures = append(pending.CoronaSignatures, pqSig)
|
||||
pending.addBLS(blsSig)
|
||||
pending.addCorona(pqSig)
|
||||
q.mu.Unlock()
|
||||
|
||||
q.log.Debug("Block signed with Quasar (BLS + Corona parallel)",
|
||||
@@ -209,6 +210,36 @@ func (q *Quasar) SignBlock(ctx context.Context, blockID ids.ID, blockHash []byte
|
||||
return &BlockSigs{BLS: blsSig, Corona: pqSig}, nil
|
||||
}
|
||||
|
||||
// errDuplicateSigner — a validator that already contributed a signature for this
|
||||
// block sent another. Both finality legs are counted by len(signatures) against
|
||||
// the threshold, so admitting a second signature from one validator would let a
|
||||
// single peer reach the threshold alone by resending.
|
||||
var errDuplicateSigner = errors.New("quantumvm: validator already signed this block")
|
||||
|
||||
// addBLS records one BLS signature per validator and reports whether it was new.
|
||||
// Caller holds q.mu.
|
||||
func (p *PendingBlock) addBLS(sig *quasar.BLSSignature) bool {
|
||||
for _, have := range p.BLSSignatures {
|
||||
if have.ValidatorID == sig.ValidatorID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
p.BLSSignatures = append(p.BLSSignatures, sig)
|
||||
return true
|
||||
}
|
||||
|
||||
// addCorona records one Corona signature per validator and reports whether it was
|
||||
// new. Caller holds q.mu.
|
||||
func (p *PendingBlock) addCorona(sig *quasar.CoronaSignature) bool {
|
||||
for _, have := range p.CoronaSignatures {
|
||||
if have.ValidatorID == sig.ValidatorID {
|
||||
return false
|
||||
}
|
||||
}
|
||||
p.CoronaSignatures = append(p.CoronaSignatures, sig)
|
||||
return true
|
||||
}
|
||||
|
||||
// AddBLSSignature adds a BLS signature from another validator
|
||||
func (q *Quasar) AddBLSSignature(blockID ids.ID, sig *quasar.BLSSignature) error {
|
||||
q.mu.Lock()
|
||||
@@ -219,7 +250,9 @@ func (q *Quasar) AddBLSSignature(blockID ids.ID, sig *quasar.BLSSignature) error
|
||||
return fmt.Errorf("pending block not found: %s", blockID)
|
||||
}
|
||||
|
||||
pending.BLSSignatures = append(pending.BLSSignatures, sig)
|
||||
if !pending.addBLS(sig) {
|
||||
return fmt.Errorf("%w: %s (BLS)", errDuplicateSigner, sig.ValidatorID)
|
||||
}
|
||||
|
||||
q.log.Debug("Added BLS signature",
|
||||
"blockID", blockID,
|
||||
@@ -230,7 +263,12 @@ func (q *Quasar) AddBLSSignature(blockID ids.ID, sig *quasar.BLSSignature) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddCoronaSignature adds a Corona signature from another validator
|
||||
// AddCoronaSignature adds a Corona signature from another validator.
|
||||
//
|
||||
// The Corona leg is finalized on count alone (TryFinalize does not verify Corona
|
||||
// signatures the way the BLS leg verifies its aggregate), so the count must be a
|
||||
// count of DISTINCT validators or the post-quantum half of quantum finality is
|
||||
// satisfied by one peer resending one signature.
|
||||
func (q *Quasar) AddCoronaSignature(blockID ids.ID, sig *quasar.CoronaSignature) error {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
@@ -240,7 +278,9 @@ func (q *Quasar) AddCoronaSignature(blockID ids.ID, sig *quasar.CoronaSignature)
|
||||
return fmt.Errorf("pending block not found: %s", blockID)
|
||||
}
|
||||
|
||||
pending.CoronaSignatures = append(pending.CoronaSignatures, sig)
|
||||
if !pending.addCorona(sig) {
|
||||
return fmt.Errorf("%w: %s (Corona)", errDuplicateSigner, sig.ValidatorID)
|
||||
}
|
||||
|
||||
q.log.Debug("Added Corona signature",
|
||||
"blockID", blockID,
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package quantumvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/luxfi/consensus/protocol/quasar"
|
||||
"github.com/luxfi/ids"
|
||||
"github.com/luxfi/log"
|
||||
)
|
||||
|
||||
// pendingQuasar returns a Quasar with one pending block awaiting signatures.
|
||||
func pendingQuasar(threshold int) (*Quasar, ids.ID) {
|
||||
blockID := ids.GenerateTestID()
|
||||
q := &Quasar{
|
||||
threshold: threshold,
|
||||
log: log.NewNoOpLogger(),
|
||||
pendingBlocks: map[ids.ID]*PendingBlock{
|
||||
blockID: {
|
||||
BlockID: blockID,
|
||||
BlockHash: blockID[:],
|
||||
Height: 1,
|
||||
BLSSignatures: make([]*quasar.BLSSignature, 0),
|
||||
CoronaSignatures: make([]*quasar.CoronaSignature, 0),
|
||||
},
|
||||
},
|
||||
finalizedBlocks: make(map[ids.ID]bool),
|
||||
}
|
||||
return q, blockID
|
||||
}
|
||||
|
||||
// TestOneValidatorCannotReachCoronaThreshold proves the post-quantum finality leg
|
||||
// counts distinct validators.
|
||||
//
|
||||
// TryFinalize finalizes the Corona leg on len(CoronaSignatures) >= threshold and
|
||||
// verifies none of those signatures (unlike the BLS leg, which aggregates and
|
||||
// verifies). A validator resending its signature therefore drove the count to the
|
||||
// threshold on its own and satisfied the post-quantum half of quantum finality
|
||||
// alone.
|
||||
func TestOneValidatorCannotReachCoronaThreshold(t *testing.T) {
|
||||
require := require.New(t)
|
||||
q, blockID := pendingQuasar(3)
|
||||
|
||||
sig := &quasar.CoronaSignature{Signature: []byte{1}, ValidatorID: "validator-1", SignerIndex: 0, Round: 1}
|
||||
require.NoError(q.AddCoronaSignature(blockID, sig))
|
||||
require.ErrorIs(q.AddCoronaSignature(blockID, sig), errDuplicateSigner)
|
||||
require.ErrorIs(q.AddCoronaSignature(blockID, sig), errDuplicateSigner)
|
||||
|
||||
pending := q.pendingBlocks[blockID]
|
||||
require.Len(pending.CoronaSignatures, 1, "one validator contributes one signature")
|
||||
require.Less(len(pending.CoronaSignatures), q.threshold,
|
||||
"a single validator must stay below the threshold")
|
||||
|
||||
// Even a signature with the same validator but different bytes is one vote.
|
||||
require.ErrorIs(q.AddCoronaSignature(blockID, &quasar.CoronaSignature{
|
||||
Signature: []byte{9}, ValidatorID: "validator-1", SignerIndex: 0, Round: 2,
|
||||
}), errDuplicateSigner)
|
||||
require.Len(pending.CoronaSignatures, 1)
|
||||
|
||||
_, finalized, err := q.TryFinalize(context.Background(), blockID)
|
||||
require.Error(err, "quasar core is not initialized in this fixture")
|
||||
require.False(finalized)
|
||||
require.False(pending.CoronaFinalized, "the Corona leg must not be finalized by one validator")
|
||||
}
|
||||
|
||||
// TestDistinctValidatorsReachCoronaThreshold is the other half: three different
|
||||
// validators do reach a threshold of three.
|
||||
func TestDistinctValidatorsReachCoronaThreshold(t *testing.T) {
|
||||
require := require.New(t)
|
||||
q, blockID := pendingQuasar(3)
|
||||
|
||||
for _, id := range []string{"validator-1", "validator-2", "validator-3"} {
|
||||
require.NoError(q.AddCoronaSignature(blockID, &quasar.CoronaSignature{
|
||||
Signature: []byte{1}, ValidatorID: id, Round: 1,
|
||||
}))
|
||||
}
|
||||
require.Len(q.pendingBlocks[blockID].CoronaSignatures, 3)
|
||||
}
|
||||
|
||||
// TestOneValidatorCannotReachBLSThreshold is the same rule on the classical leg,
|
||||
// where a resent signature also inflated the count compared against the
|
||||
// threshold.
|
||||
func TestOneValidatorCannotReachBLSThreshold(t *testing.T) {
|
||||
require := require.New(t)
|
||||
q, blockID := pendingQuasar(3)
|
||||
|
||||
sig := &quasar.BLSSignature{Signature: []byte{1}, ValidatorID: "validator-1"}
|
||||
require.NoError(q.AddBLSSignature(blockID, sig))
|
||||
require.ErrorIs(q.AddBLSSignature(blockID, sig), errDuplicateSigner)
|
||||
require.ErrorIs(q.AddBLSSignature(blockID, sig), errDuplicateSigner)
|
||||
|
||||
require.Len(q.pendingBlocks[blockID].BLSSignatures, 1)
|
||||
}
|
||||
Reference in New Issue
Block a user