cargo fmt

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-09-08 09:53:52 -07:00
co-authored by Hanzo Dev
parent c98bbf4e9a
commit 33e6b708c3
16 changed files with 546 additions and 222 deletions
+6 -10
View File
@@ -53,7 +53,8 @@ fn certified(n: usize) -> (ValidatorSet, QuorumCert) {
let sk = SecretKey::key_gen(&ikm, &[]).expect("key_gen");
let id = node_id(i);
let proof = pop::sign(&sk, &id, &sk.sk_to_pk().compress());
set.insert(id, 100, &sk.sk_to_pk().compress(), &proof).expect("insert");
set.insert(id, 100, &sk.sk_to_pk().compress(), &proof)
.expect("insert");
votes.push(Vote {
node_id: id,
accept: true,
@@ -103,14 +104,7 @@ fn bench_assemble(c: &mut Criterion) {
let (_, cert) = certified(21);
let votes = cert.votes.clone();
c.bench_function("cert_assemble_21", |b| {
b.iter(|| {
QuorumCert::assemble(
Finality::Quasar,
position(),
21,
black_box(&votes),
)
})
b.iter(|| QuorumCert::assemble(Finality::Quasar, position(), 21, black_box(&votes)))
});
}
@@ -127,7 +121,9 @@ fn bench_ballots(c: &mut Criterion) {
let mut engine = QuasarEngine::new(config.clone());
engine.start().unwrap();
for i in 0..21 {
engine.add_validator(lux_consensus::NodeID::from(node_id(i)), 1).unwrap();
engine
.add_validator(lux_consensus::NodeID::from(node_id(i)), 1)
.unwrap();
}
let block = lux_consensus::Block::new(
lux_consensus::ID::from([11u8; 32]),
+5 -1
View File
@@ -115,7 +115,11 @@ pub fn canonical_vote_message(pos: &Position, accept: bool) -> Vec<u8> {
// signed and the bytes finalized cannot drift apart. See
// `Position::signed_identity`.
let canonical = pos.signed_identity();
let parent = if pos.parent_canonical_id == EMPTY { &pos.parent_id } else { &pos.parent_canonical_id };
let parent = if pos.parent_canonical_id == EMPTY {
&pos.parent_id
} else {
&pos.parent_canonical_id
};
buf.extend_from_slice(&canonical);
buf.extend_from_slice(parent);
+58 -54
View File
@@ -62,7 +62,6 @@ use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant, SystemTime};
// The finality standard: the bytes a validator signs and the thresholds that
// decide. Held to the Go definitions by tests/conformance.rs.
pub mod finality;
@@ -96,20 +95,20 @@ pub fn sha256(input: &[u8]) -> [u8; 32] {
}
// Re-export all public types
pub use crate::engine::*;
pub use crate::errors::*;
pub use crate::finality::{
canonical_vote_message, crash_tolerance, half_stake_floor, nova_beta, nova_quorum,
nova_signer_floor, signer_floor, two_thirds_count, two_thirds_stake_floor, weighted_quasar,
Finality, Position, QC_FINALITY, QUORUM_CERT_VERSION, VOTE_MESSAGE_LEN, VOTE_TAG,
};
pub use crate::types::*;
pub use crate::errors::*;
pub use crate::focus::*;
pub use crate::fpc::*;
pub use crate::photon::*;
pub use crate::focus::*;
pub use crate::wave::*;
pub use crate::quasar::*;
pub use crate::engine::*;
pub use crate::types::*;
pub use crate::vote::{SignedVote, Slot, Tally, VoteTransport, VOTE};
pub use crate::wave::*;
// ============= TYPES MODULE =============
@@ -302,36 +301,33 @@ pub mod types {
pub type Certificate = crate::cert::QuorumCert;
/// Security level for Corona post-quantum crypto
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Default)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SecurityLevel {
Low = 2, // Corona Level 2
Low = 2, // Corona Level 2
#[default]
Medium = 3, // Corona Level 3 - Default
High = 5, // Corona Level 5
High = 5, // Corona Level 5
}
/// Quasar consensus configuration
#[derive(Debug, Clone)]
pub struct QuasarConfig {
// Wave parameters
pub k: usize, // Sample/committee size
pub alpha: f64, // Fixed threshold ratio (0.5-0.8)
pub beta: u32, // Consecutive rounds for finality
pub k: usize, // Sample/committee size
pub alpha: f64, // Fixed threshold ratio (0.5-0.8)
pub beta: u32, // Consecutive rounds for finality
pub round_timeout: Duration, // Round timeout
// FPC parameters
pub enable_fpc: bool, // Enable FPC adaptive thresholds
pub theta_min: f64, // Minimum FPC threshold (0.5)
pub theta_max: f64, // Maximum FPC threshold (0.8)
pub fpc_seed: [u8; 32], // PRF seed for FPC
pub enable_fpc: bool, // Enable FPC adaptive thresholds
pub theta_min: f64, // Minimum FPC threshold (0.5)
pub theta_max: f64, // Maximum FPC threshold (0.8)
pub fpc_seed: [u8; 32], // PRF seed for FPC
// Photon parameters
pub base_luminance: f64, // Base luminance in lux (100.0)
pub max_luminance: f64, // Maximum luminance (1000.0)
pub min_luminance: f64, // Minimum luminance (10.0)
pub base_luminance: f64, // Base luminance in lux (100.0)
pub max_luminance: f64, // Maximum luminance (1000.0)
pub min_luminance: f64, // Minimum luminance (10.0)
pub success_multiplier: f64, // Success brightens (1.1)
pub failure_multiplier: f64, // Failure dims (0.9)
@@ -554,7 +550,6 @@ pub mod fpc {
}
}
/// θ for a phase, from the PRF the network runs.
///
/// `θ(phase) = θ_min + sha256(seed ‖ be64(phase))[0..8] / (2⁶⁴−1) · (θ_max − θ_min)`
@@ -573,8 +568,7 @@ pub mod fpc {
// Big-endian, first 8 bytes — the same window and order Go reads.
let hash_u64 = u64::from_be_bytes([
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5], hash[6], hash[7],
hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
]);
let normalized = (hash_u64 as f64) / (u64::MAX as f64);
@@ -713,7 +707,8 @@ pub mod photon {
let k = k.min(self.peers.len());
// Calculate weights based on luminance
let weights: Vec<f64> = self.peers
let weights: Vec<f64> = self
.peers
.iter()
.map(|p| self.luminance.brightness(p))
.collect();
@@ -786,8 +781,8 @@ pub mod focus {
/// votes above the alpha threshold.
#[derive(Debug)]
pub struct Focus<ID: Eq + std::hash::Hash + Clone> {
threshold: u32, // β - consecutive rounds needed
alpha: f64, // Ratio threshold
threshold: u32, // β - consecutive rounds needed
alpha: f64, // Ratio threshold
states: HashMap<ID, FocusState>,
}
@@ -915,7 +910,9 @@ pub mod focus {
/// Get decision for an item
pub fn decision(&self, id: &ID) -> Decision {
self.states.get(id).map_or(Decision::Undecided, |s| s.decision)
self.states
.get(id)
.map_or(Decision::Undecided, |s| s.decision)
}
/// Get current confidence level
@@ -1041,8 +1038,7 @@ pub mod wave {
pub fn record_vote(&mut self, vote: Vote) -> bool {
let block_id = vote.block_id.clone();
let state = self.states.entry(block_id.clone())
.or_default();
let state = self.states.entry(block_id.clone()).or_default();
if state.decided {
return false;
@@ -1146,7 +1142,9 @@ pub mod wave {
/// Get decision for a block
pub fn decision(&self, block_id: &ID) -> Decision {
self.states.get(block_id).map_or(Decision::Undecided, |s| s.decision)
self.states
.get(block_id)
.map_or(Decision::Undecided, |s| s.decision)
}
/// Reset state for a block
@@ -1231,7 +1229,9 @@ pub mod quasar {
bls_pubkey: &[u8],
pop: &[u8],
) -> Result<()> {
Ok(self.validators.insert(*id.as_bytes(), weight, bls_pubkey, pop)?)
Ok(self
.validators
.insert(*id.as_bytes(), weight, bls_pubkey, pop)?)
}
/// Remove a validator
@@ -1292,7 +1292,10 @@ pub mod quasar {
if !seen.insert(id) {
continue;
}
if !self.validators.verify_vote(&id, &message, &v.signature, position.height) {
if !self
.validators
.verify_vote(&id, &message, &v.signature, position.height)
{
continue;
}
accepted.push(CertVote {
@@ -1372,7 +1375,8 @@ pub mod quasar {
/// `StakeSource` impl on `ValidatorSet`). A set that reads a P-chain
/// epoch must take that height from the caller.
pub fn verify_certificate(&self, cert: &Certificate) -> bool {
cert.verify_weighted(&self.validators, &self.validators, 0).is_ok()
cert.verify_weighted(&self.validators, &self.validators, 0)
.is_ok()
}
/// Whether the position with this signed identity has quantum finality.
@@ -1628,13 +1632,17 @@ pub mod engine {
}
fn is_accepted(&self, id: &ID) -> bool {
self.status.read().unwrap()
self.status
.read()
.unwrap()
.get(id)
.is_some_and(|s| *s == Status::Accepted)
}
fn get_status(&self, id: &ID) -> Status {
self.status.read().unwrap()
self.status
.read()
.unwrap()
.get(id)
.copied()
.unwrap_or(Status::Unknown)
@@ -1667,7 +1675,6 @@ pub mod engine {
Ok(())
}
}
}
// ============= CONVENIENCE FUNCTIONS =============
@@ -1704,7 +1711,7 @@ pub fn generate_block_id() -> ID {
state ^= state >> 7;
state ^= state << 17;
let chunk = state.to_le_bytes();
bytes[i*8..(i+1)*8].copy_from_slice(&chunk);
bytes[i * 8..(i + 1) * 8].copy_from_slice(&chunk);
}
ID::new(bytes)
@@ -1824,12 +1831,7 @@ mod tests {
}
// Add a block
let block = Block::new(
ID::from([1u8; 32]),
ID::zero(),
1,
b"test".to_vec(),
);
let block = Block::new(ID::from([1u8; 32]), ID::zero(), 1, b"test".to_vec());
engine.add(block.clone()).unwrap();
// Record votes
@@ -1861,15 +1863,17 @@ mod tests {
}
// Create chain of blocks
let blocks: Vec<Block> = (1..=3).map(|height| {
let mut id = [0u8; 32];
id[0] = height as u8;
let mut parent_id = [0u8; 32];
if height > 1 {
parent_id[0] = (height - 1) as u8;
}
Block::new(ID::from(id), ID::from(parent_id), height, vec![])
}).collect();
let blocks: Vec<Block> = (1..=3)
.map(|height| {
let mut id = [0u8; 32];
id[0] = height as u8;
let mut parent_id = [0u8; 32];
if height > 1 {
parent_id[0] = (height - 1) as u8;
}
Block::new(ID::from(id), ID::from(parent_id), height, vec![])
})
.collect();
// Add blocks
for block in &blocks {
+4 -1
View File
@@ -503,7 +503,10 @@ impl Tally {
// and the rung's count clause reading it again is deliberate there: a
// certificate from elsewhere has no issue to be consistent with, and a set
// that moved under it is exactly what that clause refuses.
let held = Held { set: stake, n: stake.signer_count(self.epoch_height) };
let held = Held {
set: stake,
n: stake.signer_count(self.epoch_height),
};
let derived = signer_floor(self.tier, held.n);
// The floor is a count of seats and the certificate states it in a `u32`.
// A set claiming more signers than that can hold is not a set this tally
+20 -31
View File
@@ -53,8 +53,8 @@ fn test_block_operations() {
fn test_consensus_flow() {
let mut config = QuasarConfig::testnet();
config.k = 3;
config.alpha = 0.67; // Need ~2 votes for acceptance
config.beta = 1; // Just 1 round for quick test
config.alpha = 0.67; // Need ~2 votes for acceptance
config.beta = 1; // Just 1 round for quick test
let mut chain = QuasarEngine::new(config);
chain.start().unwrap();
@@ -67,21 +67,12 @@ fn test_consensus_flow() {
}
// Create and add a block
let block = Block::new(
make_id(1),
ID::zero(),
1,
b"Test Block".to_vec(),
);
let block = Block::new(make_id(1), ID::zero(), 1, b"Test Block".to_vec());
chain.add(block.clone()).unwrap();
// Record votes
for i in 0..3 {
let vote = Vote::new(
block.id.clone(),
VoteType::Preference,
make_node_id(i),
);
let vote = Vote::new(block.id.clone(), VoteType::Preference, make_node_id(i));
chain.record_vote(vote).unwrap();
}
@@ -140,11 +131,7 @@ fn test_error_handling() {
assert_eq!(status, Status::Unknown);
// Try to vote for non-existent block (should error)
let vote = Vote::new(
non_existent,
VoteType::Commit,
make_node_id(1),
);
let vote = Vote::new(non_existent, VoteType::Commit, make_node_id(1));
assert!(chain.record_vote(vote).is_err());
chain.stop().unwrap();
@@ -162,14 +149,20 @@ fn test_full_quasar_flow() {
}
// Create chain of blocks
let blocks: Vec<Block> = (1..=3).map(|height| {
Block::new(
make_id(height as u8),
if height > 1 { make_id((height - 1) as u8) } else { ID::zero() },
height,
format!("Block {}", height).into_bytes(),
)
}).collect();
let blocks: Vec<Block> = (1..=3)
.map(|height| {
Block::new(
make_id(height as u8),
if height > 1 {
make_id((height - 1) as u8)
} else {
ID::zero()
},
height,
format!("Block {}", height).into_bytes(),
)
})
.collect();
// Add blocks
for block in &blocks {
@@ -179,11 +172,7 @@ fn test_full_quasar_flow() {
// Vote on each block (alpha=5 for testnet)
for block in &blocks {
for i in 0..5 {
let vote = Vote::new(
block.id.clone(),
VoteType::Preference,
make_node_id(i),
);
let vote = Vote::new(block.id.clone(), VoteType::Preference, make_node_id(i));
engine.record_vote(vote).unwrap();
}
}
+204 -55
View File
@@ -76,7 +76,8 @@ fn committee(n: u8, weight: u64) -> (Vec<Signer>, ValidatorSet) {
let signers: Vec<Signer> = (1..=n).map(|i| Signer::new(i, weight)).collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
(signers, set)
}
@@ -192,7 +193,10 @@ fn votes_signed_over_another_position_are_refused() {
elsewhere.height += 1;
let other_message = canonical_vote_message(&elsewhere, true);
let votes: Vec<Vote> = signers[..4].iter().map(|s| s.vote(&other_message)).collect();
let votes: Vec<Vote> = signers[..4]
.iter()
.map(|s| s.vote(&other_message))
.collect();
let cert = QuorumCert::assemble(Finality::Quasar, position(), 4, &votes).expect("assemble");
assert_eq!(cert.verify(&set, 0), Err(CertError::SigInvalid(0)));
@@ -210,7 +214,7 @@ fn a_reject_signature_cannot_pass_as_an_accept() {
.iter()
.map(|s| Vote {
node_id: s.id,
accept: true, // claims accept
accept: true, // claims accept
signature: s.sign(&reject_message), // signed reject
})
.collect();
@@ -228,7 +232,10 @@ fn a_duplicate_voter_is_refused() {
let mut cert = valid_cert(&signers, 4, 4);
cert.votes[1] = cert.votes[0].clone();
assert_eq!(cert.verify(&set, 0), Err(CertError::NotStrictlyIncreasing(1)));
assert_eq!(
cert.verify(&set, 0),
Err(CertError::NotStrictlyIncreasing(1))
);
}
/// Order is part of the certificate. Without it the same votes have many
@@ -239,7 +246,10 @@ fn a_reordered_certificate_is_refused() {
let mut cert = valid_cert(&signers, 4, 4);
cert.votes.reverse();
assert_eq!(cert.verify(&set, 0), Err(CertError::NotStrictlyIncreasing(1)));
assert_eq!(
cert.verify(&set, 0),
Err(CertError::NotStrictlyIncreasing(1))
);
}
/// A finality certificate witnesses acceptance. A reject vote inside one is a
@@ -292,7 +302,10 @@ fn a_wrong_version_or_type_is_refused() {
let mut cert = valid_cert(&signers, 4, 4);
cert.qc_type = 9;
assert_eq!(cert.verify(&set, 0), Err(CertError::Type { got: 9, want: 1 }));
assert_eq!(
cert.verify(&set, 0),
Err(CertError::Type { got: 9, want: 1 })
);
}
#[test]
@@ -319,7 +332,8 @@ fn a_count_quorum_without_the_stake_is_refused() {
.collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
let cert = valid_cert(&signers, 4, 4);
@@ -357,13 +371,18 @@ fn exactly_two_thirds_is_refused_and_one_more_passes() {
.collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
assert_eq!(set.signer_stake(0), 600);
assert_eq!(two_thirds_stake_floor(600), 400);
let five = declaring(&signers, Finality::Quasar, 6, 5);
assert_eq!(five.voter_count(), two_thirds_count(6), "the count floor is met");
assert_eq!(
five.voter_count(),
two_thirds_count(6),
"the count floor is met"
);
assert_eq!(
five.verify_weighted(&set, &set, 0),
Err(CertError::StakeBelowSupermajority {
@@ -420,7 +439,8 @@ fn nova_needs_both_the_majority_and_the_signer_floor() {
.collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
let message = canonical_vote_message(&position(), true);
@@ -504,7 +524,10 @@ fn each_signature_is_checked_against_its_own_signer() {
// One id repeated — the ordering clause catches it before any key is read.
let mut dup = cert.clone();
dup.votes[1].node_id = dup.votes[0].node_id;
assert_eq!(dup.verify(&set, 0), Err(CertError::NotStrictlyIncreasing(1)));
assert_eq!(
dup.verify(&set, 0),
Err(CertError::NotStrictlyIncreasing(1))
);
// Zero, garbage, and the wrong length.
for bad in [
@@ -560,12 +583,24 @@ fn an_invalid_public_key_is_refused_at_registration() {
let mut set = ValidatorSet::new();
// 48 bytes that are not a point, in the two shapes a wire decode produces.
assert_eq!(set.insert(node(1), 100, &[0u8; 48], &s.pop()), Err(CertError::KeyEncoding));
assert_eq!(set.insert(node(2), 100, &[0xABu8; 48], &s.pop()), Err(CertError::KeyEncoding));
assert_eq!(
set.insert(node(1), 100, &[0u8; 48], &s.pop()),
Err(CertError::KeyEncoding)
);
assert_eq!(
set.insert(node(2), 100, &[0xABu8; 48], &s.pop()),
Err(CertError::KeyEncoding)
);
// A 96-byte signature offered where a 48-byte key belongs.
assert_eq!(set.insert(node(4), 100, &[0u8; 96], &s.pop()), Err(CertError::KeyEncoding));
assert_eq!(
set.insert(node(4), 100, &[0u8; 96], &s.pop()),
Err(CertError::KeyEncoding)
);
// No key at all is not a malformed key: it names the other door.
assert_eq!(set.insert(node(3), 100, &[], &s.pop()), Err(CertError::NoKey));
assert_eq!(
set.insert(node(3), 100, &[], &s.pop()),
Err(CertError::NoKey)
);
assert!(set.is_empty(), "nothing refused left a member behind");
}
@@ -592,12 +627,17 @@ fn a_key_without_its_proof_is_refused() {
("another validator's proof", other.pop()),
// The right secret, the right key, the WRONG node — the proof binds the
// identity, so it does not travel to a second one.
("this key's proof, made for another node", pop::sign(&s.sk, &other.id, &s.public())),
(
"this key's proof, made for another node",
pop::sign(&s.sk, &other.id, &s.public()),
),
// A signature by this secret over this preimage, in the VOTE domain.
// Collapse the two tags and this passes.
(
"a vote-domain signature over the proof preimage",
s.sk.sign(&pop::message(&s.id, &s.public()), DST, &[]).compress().to_vec(),
s.sk.sign(&pop::message(&s.id, &s.public()), DST, &[])
.compress()
.to_vec(),
),
] {
let mut set = ValidatorSet::new();
@@ -626,13 +666,21 @@ fn one_key_belongs_to_one_node() {
let second = node(9);
let mut set = ValidatorSet::new();
set.insert(s.id, 100, &s.public(), &s.pop()).expect("first id");
set.insert(s.id, 100, &s.public(), &s.pop())
.expect("first id");
// A genuine, node-bound proof for the second id — possession is satisfied,
// and uniqueness is what refuses it.
let proof = pop::sign(&s.sk, &second, &s.public());
assert_eq!(pop::verify(&second, &s.public(), &proof), Ok(()), "the proof is genuine");
assert_eq!(set.insert(second, 100, &s.public(), &proof), Err(CertError::DuplicateKey));
assert_eq!(
pop::verify(&second, &s.public(), &proof),
Ok(()),
"the proof is genuine"
);
assert_eq!(
set.insert(second, 100, &s.public(), &proof),
Err(CertError::DuplicateKey)
);
assert_eq!(set.len(), 1, "the second id never became a member");
assert!(!set.contains(&second));
@@ -647,36 +695,57 @@ fn one_node_holds_one_key() {
let b = Signer::new(2, 100);
let mut set = ValidatorSet::new();
set.insert(a.id, 100, &a.public(), &a.pop()).expect("admitted");
set.insert(a.id, 100, &a.public(), &a.pop())
.expect("admitted");
// The same node under a SECOND key it genuinely holds.
let second = pop::sign(&b.sk, &a.id, &b.public());
assert_eq!(pop::verify(&a.id, &b.public(), &second), Ok(()), "the proof is genuine");
assert_eq!(set.insert(a.id, 100, &b.public(), &second), Err(CertError::DuplicateNode));
assert_eq!(
pop::verify(&a.id, &b.public(), &second),
Ok(()),
"the proof is genuine"
);
assert_eq!(
set.insert(a.id, 100, &b.public(), &second),
Err(CertError::DuplicateNode)
);
// The identical registration offered twice fails on the KEY axis first,
// which is the order Go iterates them in.
assert_eq!(set.insert(a.id, 100, &a.public(), &a.pop()), Err(CertError::DuplicateKey));
assert_eq!(
set.insert(a.id, 100, &a.public(), &a.pop()),
Err(CertError::DuplicateKey)
);
// Neither door restates a member: an unkeyed admission of a keyed node is
// refused, and so is the reverse — a re-admission cannot quietly de-key a
// validator or change its weight.
assert_eq!(set.insert_unkeyed(a.id, 5), Err(CertError::DuplicateNode));
set.insert_unkeyed(b.id, 5).expect("a new node, no key");
assert_eq!(set.insert(b.id, 100, &b.public(), &b.pop()), Err(CertError::DuplicateNode));
assert_eq!(
set.insert(b.id, 100, &b.public(), &b.pop()),
Err(CertError::DuplicateNode)
);
// Nothing moved.
assert_eq!(set.len(), 2);
assert_eq!(set.weight(&a.id, 0), 100);
assert_eq!(set.public_key(&a.id).map(|k| k.compress()), Some(a.public()));
assert_eq!(
set.public_key(&a.id).map(|k| k.compress()),
Some(a.public())
);
assert!(!set.can_verify(&b.id));
// Re-keying is a retraction and a fresh admission, and nothing else: the old
// key is freed with the member, and the node comes back under the new one.
set.remove(&a.id);
assert!(!set.contains(&a.id));
set.insert(a.id, 100, &b.public(), &second).expect("re-admitted under a new key");
assert_eq!(set.public_key(&a.id).map(|k| k.compress()), Some(b.public()));
set.insert(a.id, 100, &b.public(), &second)
.expect("re-admitted under a new key");
assert_eq!(
set.public_key(&a.id).map(|k| k.compress()),
Some(b.public())
);
}
/// A KEYED SIGNER WITH NO STAKE is a phantom: it raises the count of distinct
@@ -688,21 +757,28 @@ fn a_zero_weight_signer_is_refused_at_registration() {
let s = Signer::new(1, 100);
let mut set = ValidatorSet::new();
assert_eq!(set.insert(s.id, 0, &s.public(), &s.pop()), Err(CertError::ZeroWeight));
assert_eq!(
set.insert(s.id, 0, &s.public(), &s.pop()),
Err(CertError::ZeroWeight)
);
assert!(set.is_empty());
// The clause ORDER, pinned: a registration that is both weightless and
// unproven is refused for its weight, because Go checks the O(1) clause
// first and a port that reordered them would name a different reason — and
// would spend a pairing on a registration that was inadmissible on its face.
assert_eq!(set.insert(s.id, 0, &s.public(), &[]), Err(CertError::ZeroWeight));
assert_eq!(
set.insert(s.id, 0, &s.public(), &[]),
Err(CertError::ZeroWeight)
);
// No key beats both, as it does in Go.
assert_eq!(set.insert(s.id, 0, &[], &[]), Err(CertError::NoKey));
// A member with no KEY may hold no stake: it can never sign, so it is no
// phantom signer — it only raises `n`, which is the direction that makes
// every floor harder rather than easier. Go's flatten carries these too.
set.insert_unkeyed(s.id, 0).expect("a keyless member may be weightless");
set.insert_unkeyed(s.id, 0)
.expect("a keyless member may be weightless");
assert_eq!(set.len(), 1);
assert!(!set.can_verify(&s.id));
}
@@ -810,7 +886,9 @@ fn the_engine_issues_certificates_only_from_signatures() {
})
.collect();
let cert = q.create_certificate(pos.clone(), &signed).expect("certificate");
let cert = q
.create_certificate(pos.clone(), &signed)
.expect("certificate");
assert_eq!(cert.votes.len(), 4);
assert_eq!(cert.tier, Finality::Quasar);
assert!(q.verify_certificate(&cert));
@@ -858,10 +936,20 @@ fn a_keyless_member_cannot_be_certified() {
let mut q = QuasarConsensus::new(&config);
// Two keyed, two not.
q.add_validator_with_key(NodeID::from(signers[0].id), 100, &signers[0].public(), &signers[0].pop())
.unwrap();
q.add_validator_with_key(NodeID::from(signers[1].id), 100, &signers[1].public(), &signers[1].pop())
.unwrap();
q.add_validator_with_key(
NodeID::from(signers[0].id),
100,
&signers[0].public(),
&signers[0].pop(),
)
.unwrap();
q.add_validator_with_key(
NodeID::from(signers[1].id),
100,
&signers[1].public(),
&signers[1].pop(),
)
.unwrap();
q.add_validator(NodeID::from(signers[2].id), 100).unwrap();
q.add_validator(NodeID::from(signers[3].id), 100).unwrap();
@@ -890,7 +978,10 @@ fn a_keyless_member_cannot_be_certified() {
// clause rather than reporting that something did not reach something.
assert!(matches!(
q.create_certificate(pos, &votes),
Err(ConsensusError::Cert(CertError::MinCommittee { n: 2, need: 4 }))
Err(ConsensusError::Cert(CertError::MinCommittee {
n: 2,
need: 4
}))
));
}
@@ -1040,7 +1131,8 @@ fn a_set_whose_weights_overflow_is_refused_at_admission() {
#[test]
fn an_unkeyed_member_cannot_overflow_the_total_either() {
let mut set = ValidatorSet::new();
set.insert_unkeyed(node(1), u64::MAX).expect("the first fits");
set.insert_unkeyed(node(1), u64::MAX)
.expect("the first fits");
assert_eq!(
set.insert_unkeyed(node(2), 1),
Err(CertError::WeightOverflow)
@@ -1066,11 +1158,15 @@ fn the_total_is_exact_at_the_boundary_and_after_a_retraction() {
set.insert_unkeyed(node(2), 10).expect("insert");
assert_eq!(set.carried(), u64::MAX);
assert_eq!(set.insert_unkeyed(node(3), 1), Err(CertError::WeightOverflow));
assert_eq!(
set.insert_unkeyed(node(3), 1),
Err(CertError::WeightOverflow)
);
set.remove(&node(2));
assert_eq!(set.carried(), u64::MAX - 10);
set.insert_unkeyed(node(3), 10).expect("the freed room is real");
set.insert_unkeyed(node(3), 10)
.expect("the freed room is real");
assert_eq!(set.carried(), u64::MAX);
}
@@ -1101,7 +1197,9 @@ fn a_clamping_stake_source_is_refused_rather_than_read() {
.unwrap_or(0)
}
fn signer_stake(&self, _epoch_height: u64) -> u64 {
self.weights.iter().fold(0u64, |a, (_, w)| a.saturating_add(*w))
self.weights
.iter()
.fold(0u64, |a, (_, w)| a.saturating_add(*w))
}
fn signer_count(&self, _epoch_height: u64) -> i64 {
self.weights.len() as i64
@@ -1184,7 +1282,10 @@ fn one_bad_registration_refuses_the_whole_set() {
refused += 1;
}
}
assert_eq!((partial.len(), partial.signer_stake(0), refused), (3, 300, 1));
assert_eq!(
(partial.len(), partial.signer_stake(0), refused),
(3, 300, 1)
);
// Repaired, the set is admitted whole.
registrations[3].proof = signers[3].pop();
@@ -1255,7 +1356,8 @@ fn whale(n: u8, heavy: u64) -> (Vec<Signer>, ValidatorSet) {
.collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
(signers, set)
}
@@ -1310,7 +1412,11 @@ fn meeting_the_count_without_the_stake_is_refused_too() {
assert_eq!(
cert.verify_weighted(&set, &set, 0),
Err(CertError::StakeBelowSupermajority { voted: 4, signer: 104, need_above: 69 }),
Err(CertError::StakeBelowSupermajority {
voted: 4,
signer: 104,
need_above: 69
}),
);
}
@@ -1319,7 +1425,17 @@ fn meeting_the_count_without_the_stake_is_refused_too() {
/// form. It is never above the set, so it is never a rung nothing can satisfy.
#[test]
fn the_export_floor_is_the_supermajority_in_seats() {
for (n, want) in [(1, 1), (2, 2), (3, 3), (4, 3), (5, 4), (11, 8), (21, 15), (41, 28), (100, 67)] {
for (n, want) in [
(1, 1),
(2, 2),
(3, 3),
(4, 3),
(5, 4),
(11, 8),
(21, 15),
(41, 28),
(100, 67),
] {
assert_eq!(two_thirds_count(n), want, "two_thirds_count({n})");
}
for n in 1i64..=1000 {
@@ -1386,7 +1502,8 @@ fn keyless_stake_is_in_no_floor_and_export_still_reaches() {
let (signers, mut set) = committee(6, 100);
let mut spectator = [0u8; 20];
spectator[0] = 200;
set.insert_unkeyed(spectator, 300).expect("a keyless member");
set.insert_unkeyed(spectator, 300)
.expect("a keyless member");
// What the set carries, and what can actually sign.
assert_eq!(set.carried(), 900, "the chain carries nine hundred");
@@ -1510,7 +1627,10 @@ fn an_export_certificate_needs_a_byzantine_committee() {
let cert = declaring(&signers, Finality::Quasar, i64::from(n), usize::from(n));
// Both quorum floors are MET, so neither can be what refuses it.
assert!(cert.voter_count() >= two_thirds_count(set.signer_count(0)), "n={n}");
assert!(
cert.voter_count() >= two_thirds_count(set.signer_count(0)),
"n={n}"
);
assert!(
u64::from(n) * 100 > two_thirds_stake_floor(set.signer_stake(0)),
"n={n}: unanimity does not clear the stake floor, so this case does not \
@@ -1519,7 +1639,10 @@ fn an_export_certificate_needs_a_byzantine_committee() {
assert_eq!(
cert.verify_weighted(&set, &set, 0),
Err(CertError::MinCommittee { n: i64::from(n), need: 4 }),
Err(CertError::MinCommittee {
n: i64::from(n),
need: 4
}),
"n={n}: a unanimous certificate over a set with no Byzantine fault budget \
minted export finality"
);
@@ -1605,7 +1728,11 @@ fn a_certificate_may_not_declare_a_quorum_the_set_does_not_derive() {
under.threshold = 1;
assert_eq!(
under.verify_weighted(&set, &set, 0),
Err(CertError::ThresholdNotDerived { declared: 1, derived: 3, n: 4 }),
Err(CertError::ThresholdNotDerived {
declared: 1,
derived: 3,
n: 4
}),
);
// And an over-claim, for the same reason in the other direction: a certificate
@@ -1616,7 +1743,11 @@ fn a_certificate_may_not_declare_a_quorum_the_set_does_not_derive() {
over.threshold = 4;
assert_eq!(
over.verify_weighted(&set, &set, 0),
Err(CertError::ThresholdNotDerived { declared: 4, derived: 3, n: 4 }),
Err(CertError::ThresholdNotDerived {
declared: 4,
derived: 3,
n: 4
}),
);
}
@@ -1633,7 +1764,11 @@ fn the_accept_rung_derives_its_quorum_the_same_way() {
under.threshold = 1;
assert_eq!(
under.verify_weighted(&set, &set, 0),
Err(CertError::ThresholdNotDerived { declared: 1, derived: 3, n: 5 }),
Err(CertError::ThresholdNotDerived {
declared: 1,
derived: 3,
n: 5
}),
);
}
@@ -1641,8 +1776,16 @@ fn the_accept_rung_derives_its_quorum_the_same_way() {
#[test]
fn the_signer_floor_is_the_rungs_own_arithmetic() {
for n in 1..=64i64 {
assert_eq!(signer_floor(Finality::Nova, n), nova_signer_floor(n), "n={n}");
assert_eq!(signer_floor(Finality::Quasar, n), two_thirds_count(n), "n={n}");
assert_eq!(
signer_floor(Finality::Nova, n),
nova_signer_floor(n),
"n={n}"
);
assert_eq!(
signer_floor(Finality::Quasar, n),
two_thirds_count(n),
"n={n}"
);
}
for rung in [Finality::Photon, Finality::Wave, Finality::Horizon] {
assert_eq!(signer_floor(rung, 41), 0, "{rung:?} is not an accept rung");
@@ -1678,7 +1821,10 @@ fn an_unresolved_set_is_not_named_by_the_derived_clause() {
// ------------------------------------------------ the issuer states no quorum
/// A committee of `n` keyed validators at equal weight, under `config`.
fn issuer(config: &lux_consensus::QuasarConfig, n: u8) -> (Vec<Signer>, lux_consensus::QuasarConsensus) {
fn issuer(
config: &lux_consensus::QuasarConfig,
n: u8,
) -> (Vec<Signer>, lux_consensus::QuasarConsensus) {
use lux_consensus::{NodeID, QuasarConsensus};
let signers: Vec<Signer> = (1..=n).map(|i| Signer::new(i, 100)).collect();
let mut q = QuasarConsensus::new(config);
@@ -1738,7 +1884,10 @@ fn the_issuer_derives_its_certificates_quorum_from_the_set() {
let cert = q
.create_certificate(pos.clone(), &ballots(&signers, &pos, voters))
.unwrap_or_else(|e| {
panic!("k={} n={n}: the issuer refused its own quorum: {e}", config.k)
panic!(
"k={} n={n}: the issuer refused its own quorum: {e}",
config.k
)
});
assert_eq!(
+14 -4
View File
@@ -64,7 +64,8 @@ fn committee(n: u8, weight: u64) -> (Vec<Signer>, ValidatorSet) {
let signers: Vec<Signer> = (1..=n).map(|i| Signer::new(i, weight)).collect();
let mut set = ValidatorSet::new();
for s in &signers {
set.insert(s.id, s.weight, &s.public(), &s.pop()).expect("insert");
set.insert(s.id, s.weight, &s.public(), &s.pop())
.expect("insert");
}
(signers, set)
}
@@ -199,7 +200,11 @@ fn retracting_an_unkeyed_member_leaves_the_signable_stake_alone() {
set.remove(&spectator);
assert_eq!(set.carried(), 400, "the carried total kept a departed member");
assert_eq!(
set.carried(),
400,
"the carried total kept a departed member"
);
assert_eq!(
set.signer_stake(0),
400,
@@ -220,7 +225,11 @@ fn retracting_a_keyed_member_frees_both_the_stake_and_the_key() {
set.remove(&departing.id);
assert_eq!(set.carried(), 300, "the carried total kept a departed signer");
assert_eq!(
set.carried(),
300,
"the carried total kept a departed signer"
);
assert_eq!(
set.signer_stake(0),
300,
@@ -364,7 +373,8 @@ fn a_real_shortfall_reports_the_stake_it_actually_had() {
.collect();
let mut set = ValidatorSet::new();
for sgn in &signers {
set.insert(sgn.id, sgn.weight, &sgn.public(), &sgn.pop()).expect("insert");
set.insert(sgn.id, sgn.weight, &sgn.public(), &sgn.pop())
.expect("insert");
}
let text = cert(&signers[1..], 4, 5, Finality::Quasar)
+8 -3
View File
@@ -56,7 +56,13 @@ fn an_undecided_round_resets_confidence() {
/// round is itself evidence for the new preference.
#[test]
fn an_opposing_round_switches_preference_and_restarts_at_one() {
let flip = [Some(true), Some(true), Some(false), Some(false), Some(false)];
let flip = [
Some(true),
Some(true),
Some(false),
Some(false),
Some(false),
];
assert_eq!(run(&flip, 3), Some((4, Decision::Reject)));
// Restarting at one, not zero: three NOs after the switch, the switch
@@ -72,8 +78,7 @@ fn an_opposing_round_switches_preference_and_restarts_at_one() {
/// keeps changing its mind about does not become final by attrition.
#[test]
fn alternating_rounds_never_decide() {
let alternating: Vec<Option<bool>> =
(0..1000).map(|i| Some(i % 2 == 0)).collect();
let alternating: Vec<Option<bool>> = (0..1000).map(|i| Some(i % 2 == 0)).collect();
assert_eq!(run(&alternating, 2), None);
}
+37 -10
View File
@@ -17,13 +17,16 @@ use serde_json::Value;
fn corpus() -> Value {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../../conformance/corpus.json");
let raw = std::fs::read_to_string(path)
.unwrap_or_else(|e| panic!("read {path}: {e} — regenerate with `go test ./conformance -update`"));
let raw = std::fs::read_to_string(path).unwrap_or_else(|e| {
panic!("read {path}: {e} — regenerate with `go test ./conformance -update`")
});
serde_json::from_str(&raw).expect("corpus.json does not parse")
}
fn id(v: &Value, key: &str) -> Id {
let s = v[key].as_str().unwrap_or_else(|| panic!("case has no {key}"));
let s = v[key]
.as_str()
.unwrap_or_else(|| panic!("case has no {key}"));
let bytes = hex::decode(s).unwrap_or_else(|e| panic!("{key}: {e}"));
let mut out = EMPTY;
assert_eq!(bytes.len(), 32, "{key} is {} bytes, want 32", bytes.len());
@@ -83,7 +86,10 @@ fn signed_messages_match_go() {
let accept = case["accept"].as_bool().unwrap();
let got = hex::encode(canonical_vote_message(&pos, accept));
let want = case["message"].as_str().unwrap();
assert_eq!(got, want, "vote case {name}: this crate signs different bytes than the network");
assert_eq!(
got, want,
"vote case {name}: this crate signs different bytes than the network"
);
}
}
@@ -130,13 +136,21 @@ fn count_thresholds_match_go() {
for row in c["threshold"]["count"].as_array().expect("no count rows") {
let n = row["n"].as_i64().unwrap();
assert_eq!(nova_quorum(n), row["novaQuorum"].as_i64().unwrap(), "novaQuorum n={n}");
assert_eq!(
nova_quorum(n),
row["novaQuorum"].as_i64().unwrap(),
"novaQuorum n={n}"
);
assert_eq!(
nova_signer_floor(n),
row["novaSignerFloor"].as_i64().unwrap(),
"novaSignerFloor n={n}"
);
assert_eq!(nova_beta(n), row["novaBeta"].as_i64().unwrap(), "novaBeta n={n}");
assert_eq!(
nova_beta(n),
row["novaBeta"].as_i64().unwrap(),
"novaBeta n={n}"
);
assert_eq!(
crash_tolerance(n),
row["crashTolerance"].as_i64().unwrap(),
@@ -149,7 +163,10 @@ fn count_thresholds_match_go() {
);
}
for row in c["threshold"]["weighted"].as_array().expect("no weighted rows") {
for row in c["threshold"]["weighted"]
.as_array()
.expect("no weighted rows")
{
let weights: Vec<u64> = row["weights"]
.as_array()
.unwrap()
@@ -180,7 +197,11 @@ fn ladder_matches_go() {
Finality::Quasar,
Finality::Horizon,
];
assert_eq!(rungs.len(), all.len(), "the ladder has a different number of rungs");
assert_eq!(
rungs.len(),
all.len(),
"the ladder has a different number of rungs"
);
for (row, rung) in rungs.iter().zip(all.iter().copied()) {
assert_eq!(row["name"].as_str().unwrap(), rung.name());
@@ -207,7 +228,10 @@ fn ladder_matches_go() {
// Stated outright, not read from the corpus: nothing below Quasar leaves the
// chain. If the corpus were re-blessed with this inverted, this still fails.
assert!(!Finality::Nova.authorizes_export(), "Nova must never authorize export");
assert!(
!Finality::Nova.authorizes_export(),
"Nova must never authorize export"
);
assert!(Finality::Nova.authorizes_local_execution());
assert!(Finality::Quasar.authorizes_export());
assert!(!Finality::Quasar.authorizes_irreversible_settlement());
@@ -227,7 +251,10 @@ fn certificate_header_matches_go() {
case["length"].as_u64().unwrap() as usize,
"{name}: recorded length disagrees with the recorded bytes"
);
assert!(wire.len() > 4, "{name}: certificate is too short to carry a header");
assert!(
wire.len() > 4,
"{name}: certificate is too short to carry a header"
);
assert_eq!(
u16::from_be_bytes([wire[0], wire[1]]),
+74 -18
View File
@@ -10,8 +10,8 @@
//! started. Both are doors that failed open once.
use lux_consensus::{
quick_start, ConsensusError, Engine, EventHorizon, NodeID, QuasarConfig,
QuasarConsensus, QuasarEngine, Status, VoteType, ID,
quick_start, ConsensusError, Engine, EventHorizon, NodeID, QuasarConfig, QuasarConsensus,
QuasarEngine, Status, VoteType, ID,
};
fn id(n: u8) -> ID {
@@ -66,7 +66,10 @@ fn nothing_enters_an_engine_that_was_never_started() {
let vote = lux_consensus::new_vote(id(1), VoteType::Preference, voter(1));
assert!(
matches!(engine.record_vote(vote), Err(ConsensusError::NotInitialized)),
matches!(
engine.record_vote(vote),
Err(ConsensusError::NotInitialized)
),
"a vote was counted by an unstarted engine"
);
}
@@ -109,7 +112,11 @@ fn stopping_shuts_the_doors_again() {
Err(ConsensusError::NotInitialized)
));
assert!(matches!(
engine.record_vote(lux_consensus::new_vote(id(1), VoteType::Preference, voter(1))),
engine.record_vote(lux_consensus::new_vote(
id(1),
VoteType::Preference,
voter(1)
)),
Err(ConsensusError::NotInitialized)
));
}
@@ -147,7 +154,11 @@ fn an_engine_with_no_validators_counts_nobody() {
for i in 1..=9u8 {
assert!(
matches!(
engine.record_vote(lux_consensus::new_vote(id(1), VoteType::Preference, voter(i))),
engine.record_vote(lux_consensus::new_vote(
id(1),
VoteType::Preference,
voter(i)
)),
Err(ConsensusError::NotValidator)
),
"voter {i} was counted by an engine that holds no validators"
@@ -167,7 +178,11 @@ fn a_vote_for_an_unknown_block_is_refused() {
assert!(
matches!(
engine.record_vote(lux_consensus::new_vote(id(9), VoteType::Preference, voter(1))),
engine.record_vote(lux_consensus::new_vote(
id(9),
VoteType::Preference,
voter(1)
)),
Err(ConsensusError::BlockNotFound)
),
"a vote created a tally for a block nobody proposed"
@@ -219,13 +234,21 @@ fn members_carry_a_block_to_accepted_and_the_height_follows() {
for i in 1..=3u8 {
engine
.record_vote(lux_consensus::new_vote(id(1), VoteType::Preference, voter(i)))
.record_vote(lux_consensus::new_vote(
id(1),
VoteType::Preference,
voter(i),
))
.expect("a member's vote was refused");
}
assert!(engine.is_accepted(&id(1)), "three of three did not decide");
assert_eq!(engine.get_status(&id(1)), Status::Accepted);
assert_eq!(engine.height(), 7, "the accepted height did not follow the block");
assert_eq!(
engine.height(),
7,
"the accepted height did not follow the block"
);
}
/// The same committee voting to cancel rejects the block instead. Accept and
@@ -275,7 +298,10 @@ fn there_is_no_quorum_until_the_set_reaches_it() {
assert!(!quasar.has_quorum(), "an empty set reported a quorum");
quasar.add_validator(voter(1), 100).expect("add_validator");
assert!(!quasar.has_quorum(), "one of a two-member quorum reported one");
assert!(
!quasar.has_quorum(),
"one of a two-member quorum reported one"
);
quasar.add_validator(voter(2), 100).expect("add_validator");
assert!(quasar.has_quorum());
}
@@ -290,11 +316,18 @@ fn unsigned_ballots_finalize_nothing() {
engine.add(block(1, 7)).expect("add");
for i in 1..=3u8 {
engine
.record_vote(lux_consensus::new_vote(id(1), VoteType::Preference, voter(i)))
.record_vote(lux_consensus::new_vote(
id(1),
VoteType::Preference,
voter(i),
))
.expect("record_vote");
}
assert!(engine.is_accepted(&id(1)), "the preference did not converge");
assert!(
engine.is_accepted(&id(1)),
"the preference did not converge"
);
let quasar = QuasarConsensus::new(&quorum_of_three());
assert!(
@@ -314,7 +347,11 @@ fn the_horizon_ignores_a_block_from_an_unregistered_chain() {
let mut horizon = EventHorizon::new(&quorum_of_three());
horizon.accept_block("zoo", id(1));
assert_eq!(horizon.height(), 0, "an unregistered chain moved the height");
assert_eq!(
horizon.height(),
0,
"an unregistered chain moved the height"
);
horizon.register_chain("zoo".to_string());
horizon.accept_block("zoo", id(1));
@@ -368,7 +405,10 @@ fn every_engine_refusal_names_itself() {
for (err, expected) in cases {
let text = err.to_string();
assert_eq!(text, expected, "{err:?} does not name itself");
assert!(!seen.contains(&text), "{text:?} names two different clauses");
assert!(
!seen.contains(&text),
"{text:?} names two different clauses"
);
seen.push(text);
}
@@ -381,7 +421,10 @@ fn every_engine_refusal_names_itself() {
ConsensusError::NetworkError("peer gone".into()).to_string(),
"Network error: peer gone"
);
assert_eq!(ConsensusError::Other("anything".into()).to_string(), "anything");
assert_eq!(
ConsensusError::Other("anything".into()).to_string(),
"anything"
);
}
/// A vote is a preference for the block or against it, and the two commit types
@@ -417,7 +460,11 @@ fn a_block_short_of_a_quorum_is_in_flight_and_not_rejected() {
// Two of a committee of three: fewer than k, so no round has closed.
for i in 1..=2u8 {
engine
.record_vote(lux_consensus::new_vote(id(1), VoteType::Preference, voter(i)))
.record_vote(lux_consensus::new_vote(
id(1),
VoteType::Preference,
voter(i),
))
.expect("record_vote");
}
@@ -433,9 +480,18 @@ fn a_block_short_of_a_quorum_is_in_flight_and_not_rejected() {
/// diff applied to the wrong field.
#[test]
fn an_engine_runs_the_configuration_it_was_given() {
assert_eq!(QuasarEngine::testnet().config().k, QuasarConfig::testnet().k);
assert_eq!(QuasarEngine::mainnet().config().k, QuasarConfig::mainnet().k);
assert_eq!(QuasarEngine::default().config().k, QuasarConfig::default().k);
assert_eq!(
QuasarEngine::testnet().config().k,
QuasarConfig::testnet().k
);
assert_eq!(
QuasarEngine::mainnet().config().k,
QuasarConfig::mainnet().k
);
assert_eq!(
QuasarEngine::default().config().k,
QuasarConfig::default().k
);
// The three are genuinely different committees, so the presets are not one
// config under three names.
+7 -2
View File
@@ -117,7 +117,10 @@ fn each_phase_draws_independently() {
for (phase, _, _, _) in GO {
let t = s.theta(phase);
assert!((0.5..=0.8).contains(&t), "phase {phase}: θ={t} out of range");
assert!(
(0.5..=0.8).contains(&t),
"phase {phase}: θ={t} out of range"
);
}
}
@@ -131,6 +134,8 @@ fn a_different_seed_is_a_different_schedule() {
other[31] ^= 0x01;
let b = FpcSelector::new(0.5, 0.8, other);
let differ = (0..16).filter(|&p| a.theta(p).to_bits() != b.theta(p).to_bits()).count();
let differ = (0..16)
.filter(|&p| a.theta(p).to_bits() != b.theta(p).to_bits())
.count();
assert_eq!(differ, 16, "one flipped seed bit left a phase unchanged");
}
+33 -9
View File
@@ -16,11 +16,11 @@
//! counted twice on the way into evidence.
use blst::min_pk::SecretKey;
use lux_consensus::pop;
use lux_consensus::{
canonical_vote_message, ConsensusError, Finality, NodeID, Position, QuasarConfig,
QuasarConsensus, Vote, VoteType, ID,
};
use lux_consensus::pop;
fn node_id(n: u8) -> NodeID {
NodeID([n; 20])
@@ -56,7 +56,10 @@ fn the_block_id_pads_and_truncates_where_the_node_id_cannot() {
// makes this constructor wrong for a validator.
let a = ID::from_slice(&[7u8; 33]);
let b = ID::from_slice(&[7u8; 34]);
assert_eq!(a, b, "truncation is lossy, and that is the point being stated");
assert_eq!(
a, b,
"truncation is lossy, and that is the point being stated"
);
// Both `From` routes agree with the constructor they wrap.
assert_eq!(ID::from([9u8; 32]), ID::new([9u8; 32]));
@@ -190,7 +193,11 @@ fn the_set_handed_out_is_the_set_that_decides() {
VoteType::Preference,
NodeID::from(s.id),
)
.with_signature(s.sk.sign(&message, lux_consensus::cert::DST, &[]).compress().to_vec())
.with_signature(
s.sk.sign(&message, lux_consensus::cert::DST, &[])
.compress()
.to_vec(),
)
})
.collect();
@@ -236,12 +243,20 @@ fn a_validator_contributes_one_vote_to_the_evidence() {
VoteType::Preference,
NodeID::from(s.id),
)
.with_signature(s.sk.sign(&message, lux_consensus::cert::DST, &[]).compress().to_vec())
.with_signature(
s.sk.sign(&message, lux_consensus::cert::DST, &[])
.compress()
.to_vec(),
)
};
// Two validators, one of them shouting: three ballots, two signers, and the
// threshold is three.
let shouted = vec![ballot(&signers[0]), ballot(&signers[0]), ballot(&signers[1])];
let shouted = vec![
ballot(&signers[0]),
ballot(&signers[0]),
ballot(&signers[1]),
];
assert!(
matches!(
quasar.create_certificate(pos.clone(), &shouted),
@@ -255,7 +270,11 @@ fn a_validator_contributes_one_vote_to_the_evidence() {
let cert = quasar
.create_certificate(pos, &distinct)
.expect("three distinct signers is a quorum");
assert_eq!(cert.votes.len(), 3, "the evidence carries one vote per signer");
assert_eq!(
cert.votes.len(),
3,
"the evidence carries one vote per signer"
);
}
/// A ballot against the block is not evidence for it. `create_certificate`
@@ -277,8 +296,11 @@ fn a_signed_cancel_is_not_evidence_of_acceptance() {
let cancels: Vec<Vote> = signers
.iter()
.map(|s| {
Vote::new(ID::from(pos.block_id), VoteType::Cancel, NodeID::from(s.id))
.with_signature(s.sk.sign(&message, lux_consensus::cert::DST, &[]).compress().to_vec())
Vote::new(ID::from(pos.block_id), VoteType::Cancel, NodeID::from(s.id)).with_signature(
s.sk.sign(&message, lux_consensus::cert::DST, &[])
.compress()
.to_vec(),
)
})
.collect();
@@ -306,7 +328,9 @@ fn a_signed_cancel_is_not_evidence_of_acceptance() {
/// this code, and a test of it would pass or fail by machine.
#[test]
fn a_generated_id_fills_every_lane() {
let draws: Vec<ID> = (0..16).map(|_| lux_consensus::generate_block_id()).collect();
let draws: Vec<ID> = (0..16)
.map(|_| lux_consensus::generate_block_id())
.collect();
for lane in 0..4 {
let range = lane * 8..(lane + 1) * 8;
+16 -11
View File
@@ -31,9 +31,7 @@ use blst::{
blst_p1_from_affine, blst_p1_to_affine, blst_p1_uncompress, BLST_ERROR,
};
use lux_consensus::cert::{
CertError, NodeId, QuorumCert, ValidatorSet, Vote, VoteVerifier, DST,
};
use lux_consensus::cert::{CertError, NodeId, QuorumCert, ValidatorSet, Vote, VoteVerifier, DST};
use lux_consensus::finality::{canonical_vote_message, Finality, Position};
use lux_consensus::pop;
use lux_consensus::quasar::QuasarConsensus;
@@ -261,7 +259,8 @@ fn the_rogue_key_cannot_register_without_possession() {
let mut set = ValidatorSet::new();
for h in &honest {
set.insert(h.id, 100, &h.pk(), &h.pop()).expect("honest registers");
set.insert(h.id, 100, &h.pk(), &h.pop())
.expect("honest registers");
}
// The best proof the attacker can make — by the one secret it holds, whose
@@ -272,7 +271,10 @@ fn the_rogue_key_cannot_register_without_possession() {
);
// No proof at all, and a well-formed proof over the wrong (someone else's)
// key, are refused just the same.
assert_eq!(set.insert(rogue.id, 100, &rogue.pk, &[]), Err(CertError::PopInvalid));
assert_eq!(
set.insert(rogue.id, 100, &rogue.pk, &[]),
Err(CertError::PopInvalid)
);
assert_eq!(
set.insert(rogue.id, 100, &rogue.pk, &honest[0].pop()),
Err(CertError::PopInvalid),
@@ -485,11 +487,10 @@ fn a_proof_for_one_node_does_not_register_the_key_under_another() {
#[test]
fn a_vote_dst_signature_is_not_a_proof_of_possession() {
let h = Honest::new(1);
let vote_dst = h
.sk
.sign(&pop::message(&h.id, &h.pk()), DST, &[])
.compress()
.to_vec();
let vote_dst =
h.sk.sign(&pop::message(&h.id, &h.pk()), DST, &[])
.compress()
.to_vec();
let mut set = ValidatorSet::new();
assert_eq!(
@@ -522,7 +523,11 @@ fn the_identity_key_is_refused_before_the_proof() {
#[test]
fn signed_identity_uses_the_transport_id_only_in_the_degrade() {
let mut p = position(); // canonical_id = [12; 32], block_id = [11; 32]
assert_eq!(p.signed_identity(), [12u8; 32], "non-degrade: the canonical id");
assert_eq!(
p.signed_identity(),
[12u8; 32],
"non-degrade: the canonical id"
);
p.canonical_id = [0u8; 32]; // EMPTY
assert_eq!(p.signed_identity(), [11u8; 32], "degrade: the transport id");
}
+44 -10
View File
@@ -61,7 +61,11 @@ fn a_validator_votes_once_per_block() {
}
let state = wave.state(&id(1)).expect("state");
assert_eq!(state.yes_count, 1, "one voter was counted {} times", state.yes_count);
assert_eq!(
state.yes_count, 1,
"one voter was counted {} times",
state.yes_count
);
assert_eq!(state.votes.len(), 1);
assert!(!wave.is_decided(&id(1)));
}
@@ -81,7 +85,10 @@ fn fewer_than_k_answers_is_not_a_round() {
"confidence accumulated before k answers were in"
);
assert!(wave.record_vote(yes(1, 3)), "k answers did not close the round");
assert!(
wave.record_vote(yes(1, 3)),
"k answers did not close the round"
);
assert!(wave.is_decided(&id(1)));
assert_eq!(wave.decision(&id(1)), Decision::Accept);
}
@@ -163,13 +170,24 @@ fn asking_for_the_threshold_does_not_advance_the_phase() {
let phase = wave.phase();
let first = wave.threshold();
for _ in 0..20 {
assert_eq!(wave.threshold(), first, "the threshold moved while being read");
assert_eq!(
wave.threshold(),
first,
"the threshold moved while being read"
);
}
assert_eq!(wave.phase(), phase, "reading the threshold advanced the phase");
assert_eq!(
wave.phase(),
phase,
"reading the threshold advanced the phase"
);
// A vote is what advances it — the round, not the question.
wave.record_vote(yes(1, 1));
assert!(wave.phase() > phase, "a recorded vote did not advance the phase");
assert!(
wave.phase() > phase,
"a recorded vote did not advance the phase"
);
}
/// With FPC off the threshold is the configured count and the phase never moves,
@@ -202,7 +220,10 @@ fn an_unheard_of_block_is_undecided_and_holds_no_state() {
// Opening a tally for it is explicit, and starts empty.
let state = wave.get_or_create_state(&id(9));
assert_eq!((state.yes_count, state.no_count, state.confidence), (0, 0, 0));
assert_eq!(
(state.yes_count, state.no_count, state.confidence),
(0, 0, 0)
);
assert!(!state.decided);
assert!(wave.state(&id(9)).is_some());
}
@@ -262,13 +283,20 @@ fn beta_consecutive_ratios_decide_and_a_middling_round_resets() {
// 2 of 4 is 0.5, which is neither above alpha nor below 1-alpha: no quorum.
assert!(!focus.update(1, 2, 4));
assert_eq!(focus.confidence(&1), 0, "a middling round did not reset the run");
assert_eq!(
focus.confidence(&1),
0,
"a middling round did not reset the run"
);
assert!(!focus.is_decided(&1));
for _ in 0..2 {
assert!(!focus.update(1, 4, 4));
}
assert!(focus.update(1, 4, 4), "three consecutive rounds did not decide");
assert!(
focus.update(1, 4, 4),
"three consecutive rounds did not decide"
);
assert!(focus.is_decided(&1));
assert_eq!(focus.decision(&1), Decision::Accept);
}
@@ -295,7 +323,10 @@ fn a_decided_item_does_not_reopen() {
let settled = focus.decision(&1);
for _ in 0..5 {
assert!(!focus.update(1, 0, 4), "a decided item reported a new decision");
assert!(
!focus.update(1, 0, 4),
"a decided item reported a new decision"
);
}
assert_eq!(focus.decision(&1), settled);
assert_eq!(focus.state(&1).expect("state").last_ratio, 1.0);
@@ -337,7 +368,10 @@ fn a_run_inside_the_window_accumulates_normally() {
assert!(!focus.update(1, 4, 4));
assert!(!focus.update(1, 4, 4));
assert!(focus.update(1, 4, 4), "three rounds inside the window did not decide");
assert!(
focus.update(1, 4, 4),
"three rounds inside the window did not decide"
);
assert!(focus.is_decided(&1));
assert_eq!(focus.decision(&1), Decision::Accept);
}
+10 -2
View File
@@ -186,7 +186,11 @@ fn a_sample_is_distinct_and_never_larger_than_the_set() {
let mut distinct = drawn.clone();
distinct.sort();
distinct.dedup();
assert_eq!(distinct.len(), drawn.len(), "a peer was sampled twice at k={k}");
assert_eq!(
distinct.len(),
drawn.len(),
"a peer was sampled twice at k={k}"
);
for d in &drawn {
assert!(peers.contains(d), "a peer nobody registered was sampled");
@@ -217,7 +221,11 @@ fn the_same_state_draws_the_same_committee() {
let first = sampler.sample(4);
for _ in 0..8 {
assert_eq!(sampler.sample(4), first, "two draws from one state disagreed");
assert_eq!(
sampler.sample(4),
first,
"two draws from one state disagreed"
);
}
}
+6 -1
View File
@@ -377,7 +377,12 @@ fn a_keyed_seat_with_no_stake_is_refused_at_either_door() {
.as_array()
.unwrap_or_else(|| panic!("{name}: no weights"))
.iter()
.map(|w| w.as_str().expect("weight is not a decimal").parse().unwrap())
.map(|w| {
w.as_str()
.expect("weight is not a decimal")
.parse()
.unwrap()
})
.collect();
assert!(
weights.contains(&0),