use std::collections::BTreeMap;
use std::mem;
use dusk_core::dusk;
use dusk_core::stake::DEFAULT_MINIMUM_STAKE;
use node_data::StepName;
use node_data::bls::{PublicKey, PublicKeyBytes};
use node_data::ledger::Seed;
use num_bigint::BigInt;
use super::committee::Committee;
use crate::user::sortition;
use crate::user::stake::Stake;
pub const DUSK: u64 = dusk(1.0);
#[derive(Clone, Debug)]
pub struct Provisioners(BTreeMap<PublicKey, Stake>);
impl Provisioners {
pub fn iter(&self) -> impl Iterator<Item = (&PublicKey, &Stake)> {
self.0.iter()
}
}
#[derive(Clone, Debug)]
pub struct ContextProvisioners {
current: Provisioners,
prev: Option<Provisioners>,
}
impl ContextProvisioners {
pub fn new(current: Provisioners) -> Self {
Self {
current,
prev: None,
}
}
pub fn current(&self) -> &Provisioners {
&self.current
}
pub fn to_current(&self) -> Provisioners {
self.current.clone()
}
pub fn prev(&self) -> &Provisioners {
self.prev.as_ref().unwrap_or(&self.current)
}
pub fn update_and_swap(&mut self, mut new: Provisioners) {
mem::swap(&mut self.current, &mut new);
self.prev = Some(new);
}
pub fn remove_previous(&mut self) {
self.prev = None;
}
pub fn set_previous(&mut self, prev: Provisioners) {
self.prev = Some(prev);
}
pub fn update(&mut self, new: Provisioners) {
self.current = new;
self.prev = None;
}
pub fn apply_changes(&mut self, changes: Vec<(PublicKey, Option<Stake>)>) {
if !changes.is_empty() {
let mut prev = self.to_current();
for change in changes {
match change {
(pk, None) => prev.remove_stake(&pk),
(pk, Some(stake)) => prev.replace_stake(pk, stake),
};
}
self.set_previous(prev)
} else {
self.remove_previous()
}
}
}
impl Provisioners {
pub fn empty() -> Self {
Self(BTreeMap::default())
}
pub fn add_provisioner(&mut self, pubkey_bls: PublicKey, stake: Stake) {
self.0.entry(pubkey_bls).or_insert_with(|| stake);
}
pub fn get_provisioner_mut(
&mut self,
pubkey_bls: &PublicKey,
) -> Option<&mut Stake> {
self.0.get_mut(pubkey_bls)
}
pub fn replace_stake(
&mut self,
pubkey_bls: PublicKey,
stake: Stake,
) -> Option<Stake> {
self.0.insert(pubkey_bls, stake)
}
pub fn sub_stake(
&mut self,
pubkey_bls: &PublicKey,
amount: u64,
) -> Option<u64> {
let stake = self.0.get_mut(pubkey_bls)?;
if stake.value() < amount {
None
} else {
stake.subtract(amount);
let left = stake.value();
if left == 0 {
self.0.remove(pubkey_bls);
}
Some(left)
}
}
pub fn remove_stake(&mut self, pubkey_bls: &PublicKey) -> Option<Stake> {
self.0.remove(pubkey_bls)
}
pub fn add_provisioner_with_value(
&mut self,
pubkey_bls: PublicKey,
value: u64,
) {
self.add_provisioner(pubkey_bls, Stake::from_value(value));
}
pub fn get_provisioners_info(&self, round: u64) -> (usize, usize) {
let eligible_len = self.eligibles(round).count();
(self.0.len(), eligible_len)
}
pub fn eligibles(
&self,
round: u64,
) -> impl Iterator<Item = (&PublicKey, &Stake)> {
self.0.iter().filter(move |(_, m)| {
m.is_eligible(round) && m.value() >= DEFAULT_MINIMUM_STAKE
})
}
pub(crate) fn create_committee(
&self,
cfg: &sortition::Config,
) -> Vec<PublicKey> {
let committee_credits = cfg.committee_credits();
let mut committee: Vec<PublicKey> =
Vec::with_capacity(committee_credits);
let mut comm_gen =
CommitteeGenerator::new(self, cfg.round(), cfg.exclusion());
let mut eligible_weight = comm_gen.eligible_weight().into();
if eligible_weight == BigInt::ZERO {
panic!("No stakes available. Cannot run consensus");
}
while committee.len() != committee_credits {
let credit_index = committee.len() as u32;
let hash = sortition::create_sortition_hash(cfg, credit_index);
let score =
sortition::generate_sortition_score(hash, &eligible_weight);
let (prov_pk, prov_weight) = comm_gen.extract_member(score);
committee.push(prov_pk);
if eligible_weight > prov_weight {
eligible_weight -= prov_weight;
} else {
break;
}
}
committee
}
pub fn get_generator(
&self,
iteration: u8,
seed: Seed,
round: u64,
) -> PublicKeyBytes {
let cfg = sortition::Config::new(
seed,
round,
iteration,
StepName::Proposal,
vec![],
);
let committee_keys = Committee::new(self, &cfg);
*committee_keys
.iter()
.next()
.expect("committee to have 1 entry")
.bytes()
}
}
#[derive(Default)]
struct CommitteeGenerator<'a> {
eligibles: BTreeMap<&'a PublicKey, Stake>,
}
impl<'a> CommitteeGenerator<'a> {
fn new(
provisioners: &'a Provisioners,
round: u64,
exclusion_list: &[PublicKeyBytes],
) -> Self {
let eligible_set: Vec<_> = provisioners
.eligibles(round)
.map(|(pk, stake)| (pk, stake.clone()))
.collect();
let num_eligibles = eligible_set.len();
let eligible_set = eligible_set.into_iter();
let eligibles = if num_eligibles > 1 {
let eligible_iter = eligible_set;
match exclusion_list.len() {
0 => BTreeMap::from_iter(eligible_iter),
_ => {
let filtered_eligibles = eligible_iter.filter(|(p, _)| {
!exclusion_list
.iter()
.any(|excluded| excluded == p.bytes())
});
BTreeMap::from_iter(filtered_eligibles)
}
}
} else {
BTreeMap::from_iter(eligible_set)
};
Self { eligibles }
}
fn eligible_weight(&self) -> u64 {
self.eligibles.values().map(|m| m.value()).sum()
}
fn extract_member(&mut self, mut score: BigInt) -> (PublicKey, BigInt) {
if self.eligibles.is_empty() {
panic!("No eligible provisioners to extract for the committee");
}
loop {
for (&provisioner, provisioner_weight) in self.eligibles.iter_mut()
{
let weight = BigInt::from(provisioner_weight.value());
if weight >= score {
let new_weight =
BigInt::from(provisioner_weight.subtract(DUSK));
return (provisioner.clone(), new_weight);
}
score -= weight;
}
}
}
}