Files
zeekayandHanzo Dev 33e6b708c3 cargo fmt
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 09:53:52 -07:00

193 lines
5.5 KiB
Rust

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
use lux_consensus::*;
/// Helper to create a 32-byte ID from a single byte
fn make_id(b: u8) -> ID {
let mut arr = [0u8; 32];
arr[0] = b;
ID::from(arr)
}
/// Helper to create a 20-byte NodeID from a single byte — a validator identity
/// is 20 bytes, the width Go names a node by, and never a block id.
fn make_node_id(b: u8) -> NodeID {
let mut arr = [0u8; 20];
arr[0] = b;
NodeID::from(arr)
}
#[test]
fn test_initialization() {
let config = QuasarConfig::testnet();
let mut chain = QuasarEngine::new(config);
assert!(chain.start().is_ok());
chain.stop().unwrap();
}
#[test]
fn test_block_operations() {
let config = QuasarConfig::testnet();
let mut chain = QuasarEngine::new(config);
chain.start().unwrap();
// Create and add a block
let block = Block::new(
make_id(1),
ID::zero(), // Genesis
1,
b"Test Block".to_vec(),
);
assert!(chain.add(block.clone()).is_ok());
// Check status
let status = chain.get_status(&block.id);
assert_eq!(status, Status::Processing);
chain.stop().unwrap();
}
#[test]
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
let mut chain = QuasarEngine::new(config);
chain.start().unwrap();
// The committee whose ballots count. Without this the votes below are from
// nobody, and an engine that counts them is sampling the network rather
// than the validator set.
for i in 0..3 {
chain.add_validator(make_node_id(i), 1).unwrap();
}
// Create and add a block
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));
chain.record_vote(vote).unwrap();
}
// Check status (should be at least processing)
let status = chain.get_status(&block.id);
assert!(status == Status::Processing || status == Status::Accepted);
chain.stop().unwrap();
}
/// A ballot from outside the validator set is refused, and refused whether or
/// not the set is empty. This is the hole that let nine unregistered node ids
/// drive a block to Accepted.
#[test]
fn votes_from_outside_the_committee_are_refused() {
let mut config = QuasarConfig::testnet();
config.k = 3;
config.beta = 1;
let mut chain = QuasarEngine::new(config);
chain.start().unwrap();
let block = Block::new(make_id(1), ID::zero(), 1, b"Test Block".to_vec());
chain.add(block.clone()).unwrap();
// No validators registered: nobody may vote.
for i in 0..9 {
let vote = Vote::new(block.id.clone(), VoteType::Preference, make_node_id(i));
assert!(chain.record_vote(vote).is_err(), "voter {i} was counted");
}
assert!(!chain.is_accepted(&block.id));
// With a committee, its members are counted and a stranger still is not.
for i in 0..3 {
chain.add_validator(make_node_id(i), 1).unwrap();
}
for i in 0..3 {
let vote = Vote::new(block.id.clone(), VoteType::Preference, make_node_id(i));
chain.record_vote(vote).unwrap();
}
let stranger = Vote::new(block.id.clone(), VoteType::Preference, make_node_id(200));
assert!(chain.record_vote(stranger).is_err());
chain.stop().unwrap();
}
#[test]
fn test_error_handling() {
let config = QuasarConfig::testnet();
let mut chain = QuasarEngine::new(config);
chain.start().unwrap();
// Try to get non-existent block status (should return Unknown)
let non_existent = make_id(255);
let status = chain.get_status(&non_existent);
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));
assert!(chain.record_vote(vote).is_err());
chain.stop().unwrap();
}
#[test]
fn test_full_quasar_flow() {
let config = QuasarConfig::testnet();
let mut engine = QuasarEngine::new(config.clone());
engine.start().unwrap();
// Add validators
for i in 0..10 {
engine.add_validator(make_node_id(i), 1).unwrap();
}
// 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();
// Add blocks
for block in &blocks {
engine.add(block.clone()).unwrap();
}
// 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));
engine.record_vote(vote).unwrap();
}
}
// All blocks should be accepted or processing
for block in &blocks {
let status = engine.get_status(&block.id);
assert!(
status == Status::Accepted || status == Status::Processing,
"Block {} has unexpected status {:?}",
block.height,
status
);
}
engine.stop().unwrap();
}