use std::{collections::BTreeMap, time::Duration};
use arrayref::array_ref;
use async_trait::async_trait;
use bytes::Bytes;
use futures_lite::FutureExt;
use tmelcrypt::{Ed25519PK, Ed25519SK};
use crate::core::Core;
pub struct Decider {
config: Box<dyn DeciderConfig>,
core: Core,
tick: u64,
decision: Option<Bytes>,
}
impl Decider {
pub fn new(config: impl DeciderConfig) -> Self {
let seed = config.seed();
let total_votes: u64 = config.vote_weights().values().sum();
let weights = config.vote_weights();
let core = Core::new(config.seed(), config.vote_weights(), move |tick| {
let random_point = {
let mut state = seed.wrapping_add(tick as u128);
let mut point = u64::MAX;
while point >= total_votes {
let v = tmelcrypt::hash_single(&state.to_be_bytes());
state = u128::from_be_bytes(*array_ref![v, 0, 16]);
point = (state >> (total_votes as u128).leading_zeros()) as u64;
}
point
};
let mut sum = 0;
for (&pk, &weight) in weights.iter() {
sum += weight;
if sum > random_point {
return pk;
}
}
unreachable!()
});
Self {
config: Box::new(config),
core,
tick: 0,
decision: None,
}
}
pub fn debug_graphviz(&self) -> String {
self.core.debug_graphviz()
}
pub fn pre_tick(&mut self) -> Option<Bytes> {
self.core.set_max_tick(self.tick + 1);
if let Some(v) = self.core.get_finalized() {
self.decision = Some(v.body.clone());
}
if self.decision.is_some() {
return self.decision.clone();
}
self.core
.insert_my_prop_or_solicit(self.tick, self.config.my_secret(), || {
self.config.generate_proposal()
});
None
}
pub fn post_tick(&mut self) -> Option<Bytes> {
if let Some(v) = self.core.get_finalized() {
self.decision = Some(v.body.clone());
}
if self.decision.is_some() {
return self.decision.clone();
}
self.core.insert_my_votes(self.config.my_secret());
self.tick += 1;
None
}
pub async fn sync_state(&mut self, timeout: Option<Duration>) {
if let Some(timeout) = timeout {
self.config
.sync_core(&mut self.core)
.or(async {
async_io::Timer::after(timeout).await;
})
.await
} else {
self.config.sync_core(&mut self.core).await;
}
}
pub async fn tick_to_end(&mut self) -> Bytes {
let mut interval = 1.0f64;
loop {
if let Some(result) = self.pre_tick() {
return result;
}
self.sync_state(Duration::from_secs_f64(interval / 2.0).into())
.await;
if let Some(result) = self.post_tick() {
return result;
}
self.sync_state(Duration::from_secs_f64(interval / 2.0).into())
.await;
interval *= 1.05;
}
}
}
#[async_trait]
pub trait DeciderConfig: Sync + Send + 'static {
fn generate_proposal(&self) -> Bytes;
fn verify_proposal(&self, prop: &[u8]) -> bool;
async fn sync_core(&self, core: &mut Core);
fn vote_weights(&self) -> BTreeMap<Ed25519PK, u64>;
fn seed(&self) -> u128;
fn my_secret(&self) -> Ed25519SK;
}