#![allow(unused_qualifications)]
use std::{collections::hash_map::DefaultHasher, hash::Hasher};
use rand::{Rng, RngCore};
use super::*;
use crate::{
components::consensus::{
highway_core::{highway::Dependency, highway_testing::TEST_BLOCK_REWARD},
traits::ValidatorSecret,
},
testing::TestRng,
types::CryptoRngCore,
};
pub(crate) const WEIGHTS: &[Weight] = &[Weight(3), Weight(4), Weight(5)];
pub(crate) const ALICE: ValidatorIndex = ValidatorIndex(0);
pub(crate) const BOB: ValidatorIndex = ValidatorIndex(1);
pub(crate) const CAROL: ValidatorIndex = ValidatorIndex(2);
pub(crate) const N: Observation<TestContext> = Observation::None;
pub(crate) const F: Observation<TestContext> = Observation::Faulty;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct TestContext;
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct TestSecret(pub(crate) u32);
impl ValidatorSecret for TestSecret {
type Hash = u64;
type Signature = u64;
fn sign(&self, data: &Self::Hash, _rng: &mut dyn CryptoRngCore) -> Self::Signature {
data + u64::from(self.0)
}
}
pub(crate) const ALICE_SEC: TestSecret = TestSecret(0);
pub(crate) const BOB_SEC: TestSecret = TestSecret(1);
pub(crate) const CAROL_SEC: TestSecret = TestSecret(2);
impl Context for TestContext {
type ConsensusValue = u32;
type ValidatorId = u32;
type ValidatorSecret = TestSecret;
type Signature = u64;
type Hash = u64;
type InstanceId = u64;
fn hash(data: &[u8]) -> Self::Hash {
let mut hasher = DefaultHasher::new();
hasher.write(data);
hasher.finish()
}
fn verify_signature(
hash: &Self::Hash,
public_key: &Self::ValidatorId,
signature: &<Self::ValidatorSecret as ValidatorSecret>::Signature,
) -> bool {
let computed_signature = hash + u64::from(*public_key);
computed_signature == *signature
}
}
impl From<<TestContext as Context>::Hash> for Observation<TestContext> {
fn from(vhash: <TestContext as Context>::Hash) -> Self {
Observation::Correct(vhash)
}
}
fn vote_err(err: AddVoteError<TestContext>) -> VoteError {
err.cause
}
#[derive(Debug, Error)]
#[error("{:?}", .cause)]
pub(crate) struct AddVoteError<C: Context> {
pub(crate) swvote: SignedWireVote<C>,
#[source]
pub(crate) cause: VoteError,
}
impl<C: Context> SignedWireVote<C> {
fn with_error(self, cause: VoteError) -> AddVoteError<C> {
AddVoteError {
swvote: self,
cause,
}
}
}
impl State<TestContext> {
pub(crate) fn new_test(weights: &[Weight], seed: u64) -> Self {
let params = Params::new(
seed,
TEST_BLOCK_REWARD,
TEST_BLOCK_REWARD / 5,
4,
4,
u64::MAX,
Timestamp::from(u64::MAX),
Timestamp::from(u64::MAX),
);
State::new(weights, params, vec![])
}
pub(crate) fn add_vote(
&mut self,
swvote: SignedWireVote<TestContext>,
) -> Result<(), AddVoteError<TestContext>> {
if let Err(err) = self
.pre_validate_vote(&swvote)
.and_then(|()| self.validate_vote(&swvote))
{
return Err(swvote.with_error(err));
}
self.add_valid_vote(swvote);
Ok(())
}
}
#[test]
fn add_vote() -> Result<(), AddVoteError<TestContext>> {
let mut state = State::new_test(WEIGHTS, 0);
let mut rng = TestRng::new();
let a0 = add_vote!(state, rng, ALICE, 0xA; N, N, N)?;
let b0 = add_vote!(state, rng, BOB, 48, 4u8, 0xB; N, N, N)?;
let c0 = add_vote!(state, rng, CAROL, 49, 4u8, None; N, b0, N)?;
let b1 = add_vote!(state, rng, BOB, None; N, b0, c0)?;
let _a1 = add_vote!(state, rng, ALICE, None; a0, b1, c0)?;
let mut wvote = WireVote {
panorama: panorama!(N, b1, c0),
creator: BOB,
instance_id: 1u64,
value: None,
seq_number: 3,
timestamp: 51.into(),
round_exp: 4u8,
endorsed: vec![],
};
let vote = SignedWireVote::new(wvote.clone(), &BOB_SEC, &mut rng);
let opt_err = state.add_vote(vote).err().map(vote_err);
assert_eq!(Some(VoteError::SequenceNumber), opt_err);
wvote.seq_number = 2;
let vote = SignedWireVote::new(wvote, &BOB_SEC, &mut rng);
let opt_err = state.add_vote(vote).err().map(vote_err);
assert_eq!(Some(VoteError::ThreeVotesInRound), opt_err);
let opt_err = add_vote!(state, rng, CAROL, None; N, b1, N)
.err()
.map(vote_err);
assert_eq!(Some(VoteError::InconsistentPanorama(BOB)), opt_err);
let opt_err = add_vote!(state, rng, CAROL, 50, 5u8, None; N, b1, c0)
.err()
.map(vote_err);
assert_eq!(Some(VoteError::RoundLength), opt_err);
let c1 = add_vote!(state, rng, CAROL, 65, 5u8, None; N, b1, c0)?;
let missing = panorama!(F, b1, c0).missing_dependency(&state);
assert_eq!(Some(Dependency::Evidence(ALICE)), missing);
let missing = panorama!(42, b1, c0).missing_dependency(&state);
assert_eq!(Some(Dependency::Vote(42)), missing);
let ae1 = add_vote!(state, rng, ALICE, None; a0, b1, c0)?;
assert!(state.has_evidence(ALICE));
assert_eq!(panorama![F, b1, c1], *state.panorama());
let missing = panorama!(F, b1, c0).missing_dependency(&state);
assert_eq!(None, missing);
let missing = panorama!(ae1, b1, c0).missing_dependency(&state);
assert_eq!(None, missing);
let b2 = add_vote!(state, rng, BOB, None; F, b1, c0)?;
assert_eq!(state.panorama, panorama!(F, b2, c1));
Ok(())
}
#[test]
fn ban_and_mark_faulty() -> Result<(), AddVoteError<TestContext>> {
let mut rng = TestRng::new();
let params = Params::new(
0,
TEST_BLOCK_REWARD,
TEST_BLOCK_REWARD / 5,
4,
4,
u64::MAX,
Timestamp::from(u64::MAX),
Timestamp::from(u64::MAX),
);
let mut state = State::new(WEIGHTS, params, vec![ALICE]);
assert_eq!(panorama![F, N, N], *state.panorama());
assert_eq!(Some(&Fault::Banned), state.opt_fault(ALICE));
let err = vote_err(add_vote!(state, rng, ALICE, 0xA; N, N, N).err().unwrap());
assert_eq!(VoteError::Banned, err);
state.mark_faulty(ALICE); assert_eq!(panorama![F, N, N], *state.panorama());
assert_eq!(Some(&Fault::Banned), state.opt_fault(ALICE));
let err = vote_err(add_vote!(state, rng, ALICE, 0xA; N, N, N).err().unwrap());
assert_eq!(VoteError::Banned, err);
state.mark_faulty(BOB);
assert_eq!(panorama![F, F, N], *state.panorama());
assert_eq!(Some(&Fault::Indirect), state.opt_fault(BOB));
add_vote!(state, rng, BOB, 0xB; F, N, N)?;
Ok(())
}
#[test]
fn find_in_swimlane() -> Result<(), AddVoteError<TestContext>> {
let mut state = State::new_test(WEIGHTS, 0);
let mut rng = TestRng::new();
let a0 = add_vote!(state, rng, ALICE, 0xA; N, N, N)?;
let mut a = vec![a0];
for i in 1..10 {
let ai = add_vote!(state, rng, ALICE, None; a[i - 1], N, N)?;
a.push(ai);
}
for j in (a.len() - 2)..a.len() {
for i in 0..j {
assert_eq!(Some(&a[i]), state.find_in_swimlane(&a[j], i as u64));
}
}
assert_eq!(&[a[8]], &state.vote(&a[9]).skip_idx.as_ref());
assert_eq!(
&[a[7], a[6], a[4], a[0]],
&state.vote(&a[8]).skip_idx.as_ref()
);
Ok(())
}
#[test]
fn fork_choice() -> Result<(), AddVoteError<TestContext>> {
let mut state = State::new_test(WEIGHTS, 0);
let mut rng = TestRng::new();
let b0 = add_vote!(state, rng, BOB, 0xB0; N, N, N)?;
let c0 = add_vote!(state, rng, CAROL, 0xC0; N, b0, N)?;
let c1 = add_vote!(state, rng, CAROL, 0xC1; N, b0, c0)?;
let a0 = add_vote!(state, rng, ALICE, 0xA0; N, b0, N)?;
let b1 = add_vote!(state, rng, BOB, None; a0, b0, N)?; let a1 = add_vote!(state, rng, ALICE, 0xA1; a0, b1, c1)?;
let b2 = add_vote!(state, rng, BOB, 0xB2; a0, b1, N)?;
assert_eq!(Some(&a0), state.block(&state.vote(&a1).block).parent());
assert_eq!(Some(&b2), state.fork_choice(&state.panorama));
Ok(())
}
#[test]
fn test_log2() {
assert_eq!(2, log2(0b100));
assert_eq!(2, log2(0b101));
assert_eq!(2, log2(0b111));
assert_eq!(3, log2(0b1000));
}
#[test]
fn test_leader_prng() {
let mut rng = TestRng::new();
for _ in 0..10 {
let upper = rng.gen_range(1, u64::MAX);
let seed = rng.next_u64();
let mut prng = ChaCha8Rng::seed_from_u64(seed);
let zone = upper << upper.leading_zeros(); let expected = loop {
let prod = (prng.next_u64() as u128) * (upper as u128);
if (prod as u64) < zone {
break (prod >> 64) as u64 + 1;
}
};
assert_eq!(expected, leader_prng(upper, seed));
}
}
#[test]
fn test_leader_prng_values() {
assert_eq!(12578764544318200737, leader_prng(u64::MAX, 42));
assert_eq!(12358540700710939054, leader_prng(u64::MAX, 1337));
assert_eq!(4134160578770126600, leader_prng(u64::MAX, 0x1020304050607));
}