use node_data::StepName;
use node_data::bls::PublicKeyBytes;
use node_data::ledger::Seed;
use num_bigint::BigInt;
use num_bigint::Sign::Plus;
use sha3::{Digest, Sha3_256};
use crate::config::{
PROPOSAL_COMMITTEE_CREDITS, RATIFICATION_COMMITTEE_CREDITS,
VALIDATION_COMMITTEE_CREDITS,
};
#[derive(Debug, Clone, Default, Eq, Hash, PartialEq)]
pub struct Config {
seed: Seed,
round: u64,
pub step: u8,
committee_credits: usize,
exclusion: Vec<PublicKeyBytes>,
}
impl Config {
pub fn new(
seed: Seed,
round: u64,
iteration: u8,
step: StepName,
exclusion: Vec<PublicKeyBytes>,
) -> Config {
let committee_credits = match step {
StepName::Proposal => PROPOSAL_COMMITTEE_CREDITS,
StepName::Ratification => RATIFICATION_COMMITTEE_CREDITS,
StepName::Validation => VALIDATION_COMMITTEE_CREDITS,
};
let step = step.to_step(iteration);
Self {
seed,
round,
step,
committee_credits,
exclusion,
}
}
pub fn committee_credits(&self) -> usize {
self.committee_credits
}
pub fn step(&self) -> u8 {
self.step
}
pub fn round(&self) -> u64 {
self.round
}
pub fn exclusion(&self) -> &Vec<PublicKeyBytes> {
&self.exclusion
}
}
pub fn create_sortition_hash(cfg: &Config, counter: u32) -> [u8; 32] {
let mut hasher = Sha3_256::new();
hasher.update(&cfg.seed.inner()[..]);
hasher.update(cfg.step.to_le_bytes());
hasher.update(counter.to_le_bytes());
let reader = hasher.finalize();
reader.as_slice().try_into().expect("Wrong length")
}
pub fn generate_sortition_score(
hash: [u8; 32],
total_weight: &BigInt,
) -> BigInt {
let num = BigInt::from_bytes_be(Plus, hash.as_slice());
num % total_weight
}
#[cfg(test)]
mod tests {
use dusk_bytes::DeserializableSlice;
use dusk_core::signatures::bls::{
PublicKey as BlsPublicKey, SecretKey as BlsSecretKey,
};
use node_data::ledger::Seed;
use super::*;
use crate::user::committee::Committee;
use crate::user::provisioners::{DUSK, Provisioners};
use crate::user::sortition::Config;
impl Config {
pub fn raw(
seed: Seed,
round: u64,
step: u8,
committee_credits: usize,
exclusion: Vec<PublicKeyBytes>,
) -> Config {
Self {
seed,
round,
step,
committee_credits,
exclusion,
}
}
}
#[test]
pub fn test_sortition_hash() {
let hash = [
74, 64, 238, 174, 226, 52, 11, 105, 93, 251, 204, 6, 137, 176, 14,
96, 77, 139, 92, 76, 7, 178, 38, 16, 132, 233, 13, 180, 78, 206,
204, 31,
];
assert_eq!(
create_sortition_hash(
&Config::raw(Seed::from([3; 48]), 10, 3, 0, vec![]),
1
)[..],
hash[..],
);
}
#[test]
pub fn test_generate_sortition_score() {
let dataset = vec![
([3; 48], 123342342, 80689917),
([4; 48], 44443333, 20330495),
];
for (seed, total_weight, expected_score) in dataset {
let hash = create_sortition_hash(
&Config::raw(Seed::from(seed), 10, 3, 0, vec![]),
1,
);
let total_weight = BigInt::from(total_weight);
let res = generate_sortition_score(hash, &total_weight);
assert_eq!(res, BigInt::from(expected_score));
}
}
#[test]
fn test_deterministic_sortition_1() {
let p = generate_provisioners(5);
let committee_credits = 64;
let cfg = Config::raw(Seed::default(), 1, 1, 64, vec![]);
let committee = Committee::new(&p, &cfg);
assert_eq!(
committee_credits,
committee.get_occurrences().iter().sum::<usize>()
);
assert_eq!(vec![4, 29, 9, 22], committee.get_occurrences());
}
#[test]
fn test_deterministic_sortition_2() {
let p = generate_provisioners(5);
let committee_credits = 45;
let cfg = Config::raw(
Seed::from([3u8; 48]),
7777,
8,
committee_credits,
vec![],
);
let committee = Committee::new(&p, &cfg);
assert_eq!(
committee_credits,
committee.get_occurrences().iter().sum::<usize>()
);
assert_eq!(vec![6, 13, 11, 15], committee.get_occurrences());
}
#[test]
fn test_deterministic_sortition_2_exclusion() {
let p = generate_provisioners(5);
let seed = Seed::from([3u8; 48]);
let round = 7777;
let committee_credits = 45;
let iteration = 2;
let relative_step = 2;
let step = iteration * 3 + relative_step;
let cfg = Config::raw(seed, round, step, committee_credits, vec![]);
let generator = p.get_generator(iteration, seed, round);
let committee = Committee::new(&p, &cfg);
committee
.iter()
.find(|&p| p.bytes() == &generator)
.expect("Generator to be included");
assert_eq!(
committee_credits,
committee.get_occurrences().iter().sum::<usize>()
);
assert_eq!(vec![6, 13, 11, 15], committee.get_occurrences());
let cfg =
Config::raw(seed, round, step, committee_credits, vec![generator]);
let committee = Committee::new(&p, &cfg);
assert!(
committee
.iter()
.find(|&p| p.bytes() == &generator)
.is_none(),
"Generator to be excluded"
);
assert_eq!(
committee_credits,
committee.get_occurrences().iter().sum::<usize>()
);
assert_eq!(vec![8, 13, 24], committee.get_occurrences());
}
#[test]
fn test_quorum() {
let p = generate_provisioners(5);
let cfg = Config::raw(Seed::default(), 7777, 8, 64, vec![]);
let c = Committee::new(&p, &cfg);
assert_eq!(c.super_majority_quorum(), 43);
}
#[test]
fn test_intersect() {
let p = generate_provisioners(10);
let cfg = Config::raw(Seed::default(), 1, 3, 200, vec![]);
let c = Committee::new(&p, &cfg);
let max_bitset = (2_i32.pow((c.size()) as u32) - 1) as u64;
println!("max_bitset: {} / {:#064b} ", max_bitset, max_bitset);
for bitset in 0..max_bitset {
let result = c.intersect(bitset);
assert_eq!(
c.bits(&result),
bitset,
"testing with bitset:{}",
bitset
);
}
}
fn generate_provisioners(n: usize) -> Provisioners {
let sks = [
"7f6f2ccdb23f2abb7b69278e947c01c6160a31cf02c19d06d0f6e5ab1d768b15",
"611830d3641a68f94a690dcc25d1f4b0dac948325ac18f6dd32564371735f32c",
"1fbec814b18b1d4c3eaa7cec41007e04bf0a98453b06ec7582aa29882c52eb3e",
"ecd9c4a53ea15f18447b08fb96a13c5ab7dc7d24067b102fcbaaf7b39ca52e2d",
"e463bcb1a6e57288ffd4671503082fa8656e3eacb78fb1925f8a7c76400e8e15",
"7a19fb2d099a9557f7c10c2efbb8b101d9e0ec85610d5c74a887d1d4fb8d2827",
"4dbad51eb408af559dd91bbbed8dbeae0a2c89e0e05f0cce87c98652a8437f1f",
"befba86ae9e0c207865f7e24e8349d4ecdbc8b0f4632842499a0dfa60568e20a",
"b260b8a10343bf5a5dacb4f1d32d06c4fdddc9981a3619fbc0a5cd9eb30f3334",
"87a9779748888da5d96bbbce041b5109c6ffc0c4f30561c0170384a5922d9e21",
];
let sks: Vec<_> = sks
.iter()
.take(n)
.map(|hex| hex::decode(hex).expect("valid hex"))
.map(|data| {
BlsSecretKey::from_slice(&data[..]).expect("valid secret key")
})
.collect();
let mut p = Provisioners::empty();
for (i, sk) in sks.iter().enumerate().skip(1) {
let stake_value = 1000 * (i) as u64 * DUSK;
let stake_pk =
node_data::bls::PublicKey::new(BlsPublicKey::from(sk));
p.add_provisioner_with_value(stake_pk, stake_value);
}
p
}
}