mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
Red confirmed by execution that the receive path was not the build path, and three of its findings were chain-integrity defects rather than gaps. Parse discarded the transaction set, so a block arriving over the network had none -- and the ML-DSA check was gated on having some. The check therefore ran only on blocks this node built itself. The same bytes verified in memory and passed off the wire, and a block carrying arbitrary transaction bytes was accepted and committed. Parse now reconstructs the full set, signatures included, and refuses a declared count its bytes cannot hold or lengths that do not partition the blob. Accept never asked whether the parent was the tip -- only that the height followed it. So an old sibling rewound a five-block chain and the height index went on serving the orphaned branch to bootstrapping peers as canonical. The tip reader answered the zero id for any failure, and the genesis seeder read that as a fresh chain: one transient read at boot committed genesis over a live tip, durably, and Initialize returned nil. Not-found is now distinguished from unreadable, and only the first means new. The harness that was supposed to defend the signer-identity fix could not see it: it gave each node its own ChainID, which production never does, so reverting the fix left the suite green. It now holds the chain constant and varies the node, which is the shape the defect lives in. Also: the proposer no longer builds a block its own Verify refuses when the clock trails the tip. 96.4% coverage.
115 lines
3.9 KiB
Go
115 lines
3.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Config contains all the foundational parameters of the QVM.
|
|
//
|
|
// Every field here governs behaviour. Q-Chain charges no user fee at all
|
|
// (LP-0130 §6 — finality-cert inclusion is a validator obligation, not
|
|
// purchasable blockspace), so the config declares no fee schedule: a number
|
|
// nothing reads is a price the chain does not actually charge, and reporting
|
|
// one over RPC tells operators otherwise.
|
|
type Config struct {
|
|
// Maximum parallel transactions to process
|
|
MaxParallelTxs int
|
|
|
|
// Quantum signature algorithm version: 1=ML-DSA-44, 2=ML-DSA-65,
|
|
// 3=ML-DSA-87. The parameter set fixes every key and signature width.
|
|
// Zero means unset and settles on ML-DSA-65; anything else is refused.
|
|
QuantumAlgorithmVersion uint32
|
|
|
|
// Enable quantum stamp validation
|
|
QuantumStampEnabled bool
|
|
|
|
// How long a quantum stamp stays valid after it is made
|
|
QuantumStampWindow time.Duration
|
|
|
|
// Parallel processing batch size
|
|
ParallelBatchSize int
|
|
|
|
// Enable Corona key support
|
|
CoronaEnabled bool
|
|
|
|
// Minimum batch size before GPU acceleration kicks in
|
|
GPUBatchThreshold int
|
|
|
|
// Committee is how many validators the finality committee holds. The
|
|
// threshold is derived from it, so it is the one number that decides how
|
|
// many faults the chain survives. Zero means unset and settles on
|
|
// CommitteeMin; anything below CommitteeMin is refused.
|
|
Committee int
|
|
}
|
|
|
|
// DefaultConfig returns a Config with default values
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
MaxParallelTxs: 100,
|
|
QuantumAlgorithmVersion: AlgorithmDefault,
|
|
QuantumStampEnabled: true,
|
|
QuantumStampWindow: 30 * time.Second,
|
|
ParallelBatchSize: 10,
|
|
CoronaEnabled: true,
|
|
GPUBatchThreshold: 8,
|
|
Committee: CommitteeMin,
|
|
}
|
|
}
|
|
|
|
// AlgorithmDefault is the parameter set an unset config settles on: ML-DSA-65,
|
|
// NIST level 3. DefaultConfig names the same constant, so a config the operator
|
|
// filled in and one left blank land on the same parameter set — two spellings of
|
|
// the default meant a chain signed under ML-DSA-44 or ML-DSA-65 depending on
|
|
// which door it came through.
|
|
const AlgorithmDefault uint32 = 2
|
|
|
|
// CommitteeMin is the smallest committee that survives one Byzantine validator.
|
|
// BFT tolerates f faults out of n ≥ 3f+1, so f ≥ 1 needs n ≥ 4; below that the
|
|
// quorum ⌊2n/3⌋+1 is the whole committee and one absent validator stops the
|
|
// chain while one dishonest validator decides it.
|
|
const CommitteeMin = 4
|
|
|
|
// Quorum is how many validators must agree, for a committee of n. It is the
|
|
// classical ⌊2n/3⌋+1, which for n ≥ CommitteeMin always lands strictly between
|
|
// 2 and n — the range the consensus core accepts.
|
|
func Quorum(n int) int { return n*2/3 + 1 }
|
|
|
|
// Validate replaces any non-positive sizing with its default and refuses an
|
|
// algorithm that does not exist.
|
|
//
|
|
// Each sizing divides or bounds a loop, so zero is not a weaker setting — it is
|
|
// a VM that batches nothing, caches nothing and builds empty blocks. The
|
|
// algorithm is different: an unrecognised number used to fall through to
|
|
// ML-DSA-65 inside the signer, so an operator who asked for something else got
|
|
// a chain signing under a parameter set nobody chose, and never heard about it.
|
|
func (c *Config) Validate() error {
|
|
switch c.QuantumAlgorithmVersion {
|
|
case 0:
|
|
c.QuantumAlgorithmVersion = AlgorithmDefault
|
|
case 1, 2, 3:
|
|
default:
|
|
return fmt.Errorf("config: quantum algorithm %d does not exist (1=ML-DSA-44, 2=ML-DSA-65, 3=ML-DSA-87)",
|
|
c.QuantumAlgorithmVersion)
|
|
}
|
|
if c.MaxParallelTxs <= 0 {
|
|
c.MaxParallelTxs = 100
|
|
}
|
|
if c.ParallelBatchSize <= 0 {
|
|
c.ParallelBatchSize = 10
|
|
}
|
|
if c.GPUBatchThreshold <= 0 {
|
|
c.GPUBatchThreshold = 8
|
|
}
|
|
if c.QuantumStampWindow <= 0 {
|
|
c.QuantumStampWindow = 30 * time.Second
|
|
}
|
|
if c.Committee == 0 {
|
|
c.Committee = CommitteeMin
|
|
}
|
|
return nil
|
|
}
|