use std::{
collections::{HashMap, BTreeSet, VecDeque},
fmt,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use common_types::{
BlockNumber,
header::Header,
errors::{BlockError, VapcoreError as Error, EngineError},
};
use vapory_types::{Address, H64};
use log::{debug, trace};
use rand::Rng;
use time_utils::CheckedSystemTime;
use tetsy_unexpected::Mismatch;
use crate::{
util::{extract_signers, recover_creator},
{VoteType, DIFF_INTURN, DIFF_NOTURN, NULL_AUTHOR, SIGNING_DELAY_NOTURN_MS},
};
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct VoteState {
kind: VoteType,
votes: u64,
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Vote {
block_number: BlockNumber,
beneficiary: Address,
kind: VoteType,
signer: Address,
reverted: bool,
}
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd)]
pub struct PendingVote {
signer: Address,
beneficiary: Address,
}
#[cfg(not(test))]
#[derive(Clone, Debug, Default)]
pub struct CliqueBlockState {
votes: HashMap<PendingVote, VoteState>,
votes_history: Vec<Vote>,
signers: BTreeSet<Address>,
recent_signers: VecDeque<Address>,
pub next_timestamp_inturn: Option<SystemTime>,
pub next_timestamp_noturn: Option<SystemTime>,
}
#[cfg(test)]
#[derive(Clone, Debug, Default)]
pub struct CliqueBlockState {
pub votes: HashMap<PendingVote, VoteState>,
pub votes_history: Vec<Vote>,
pub signers: BTreeSet<Address>,
pub recent_signers: VecDeque<Address>,
pub next_timestamp_inturn: Option<SystemTime>,
pub next_timestamp_noturn: Option<SystemTime>,
}
impl fmt::Display for CliqueBlockState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let signers: Vec<String> = self.signers.iter()
.map(|s|
format!("{} {:?}",
s,
self.votes.iter().map(|(v, s)| format!("[beneficiary {}, votes: {}]", v.beneficiary, s.votes))
.collect::<Vec<_>>()
)
)
.collect();
let recent_signers: Vec<String> = self.recent_signers.iter().map(|s| format!("{}", s)).collect();
let num_votes = self.votes_history.len();
let add_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Add).count();
let rm_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Remove).count();
let reverted_votes = self.votes_history.iter().filter(|v| v.reverted).count();
write!(f,
"Votes {{ \n signers: {:?} \n recent_signers: {:?} \n number of votes: {} \n number of add votes {}
\r number of remove votes {} \n number of reverted votes: {}}}",
signers, recent_signers, num_votes, add_votes, rm_votes, reverted_votes)
}
}
impl CliqueBlockState {
pub fn new(signers: BTreeSet<Address>) -> Self {
CliqueBlockState {
signers,
..Default::default()
}
}
fn verify(&self, header: &Header) -> Result<Address, Error> {
let creator = recover_creator(header)?.clone();
if !self.signers.contains(&creator) {
trace!(target: "engine", "current state: {}", self);
Err(EngineError::NotAuthorized(creator))?
}
if self.recent_signers.contains(&creator) {
trace!(target: "engine", "current state: {}", self);
Err(EngineError::CliqueTooRecentlySigned(creator))?
}
let inturn = self.is_inturn(header.number(), &creator);
if inturn && *header.difficulty() != DIFF_INTURN {
Err(BlockError::InvalidDifficulty(Mismatch {
expected: DIFF_INTURN,
found: *header.difficulty(),
}))?
}
if !inturn && *header.difficulty() != DIFF_NOTURN {
Err(BlockError::InvalidDifficulty(Mismatch {
expected: DIFF_NOTURN,
found: *header.difficulty(),
}))?
}
Ok(creator)
}
pub fn apply(&mut self, header: &Header, is_checkpoint: bool) -> Result<Address, Error> {
let creator = self.verify(header)?;
self.recent_signers.push_front(creator);
self.rotate_recent_signers();
if is_checkpoint {
let signers = extract_signers(header)?;
if self.signers != signers {
let invalid_signers: Vec<String> = signers.into_iter()
.filter(|s| !self.signers.contains(s))
.map(|s| format!("{}", s))
.collect();
Err(EngineError::CliqueFaultyRecoveredSigners(invalid_signers))?
};
self.votes.clear();
self.votes_history.clear();
self.votes.shrink_to_fit();
self.votes_history.shrink_to_fit();
}
if *header.author() != NULL_AUTHOR {
let decoded_seal = header.decode_seal::<Vec<_>>()?;
if decoded_seal.len() != 2 {
Err(BlockError::InvalidSealArity(Mismatch { expected: 2, found: decoded_seal.len() }))?
}
let nonce = H64::from_slice(decoded_seal[1]);
self.update_signers_on_vote(VoteType::from_nonce(nonce)?, creator, *header.author(), header.number())?;
}
Ok(creator)
}
fn update_signers_on_vote(
&mut self,
kind: VoteType,
signer: Address,
beneficiary: Address,
block_number: u64
) -> Result<(), Error> {
trace!(target: "engine", "Attempt vote {:?} {:?}", kind, beneficiary);
let pending_vote = PendingVote { signer, beneficiary };
let reverted = if self.is_valid_vote(&beneficiary, kind) {
self.add_vote(pending_vote, kind)
} else {
self.revert_vote(pending_vote)
};
self.votes_history.push(
Vote {
block_number,
beneficiary,
kind,
signer,
reverted,
});
let (votes, vote_kind) = match self.get_current_votes_and_kind(beneficiary) {
Some((v, k)) => (v, k),
None => return Ok(()),
};
let threshold = self.signers.len() / 2;
debug!(target: "engine", "{}/{} votes to have consensus", votes, threshold + 1);
trace!(target: "engine", "votes: {:?}", votes);
if votes > threshold {
match vote_kind {
VoteType::Add => {
if self.signers.insert(beneficiary) {
debug!(target: "engine", "added new signer: {}", beneficiary);
}
}
VoteType::Remove => {
if self.signers.remove(&beneficiary) {
debug!(target: "engine", "removed signer: {}", beneficiary);
}
}
}
self.rotate_recent_signers();
self.remove_all_votes_from(beneficiary);
}
Ok(())
}
pub fn calc_next_timestamp(&mut self, timestamp: u64, period: u64) -> Result<(), Error> {
let inturn = CheckedSystemTime::checked_add(UNIX_EPOCH, Duration::from_secs(timestamp.saturating_add(period)));
self.next_timestamp_inturn = inturn;
let delay = Duration::from_millis(
rand::thread_rng().gen_range(0u64, (self.signers.len() as u64 / 2 + 1) * SIGNING_DELAY_NOTURN_MS));
self.next_timestamp_noturn = inturn.map(|inturn| {
inturn + delay
});
if self.next_timestamp_inturn.is_some() && self.next_timestamp_noturn.is_some() {
Ok(())
} else {
Err(BlockError::TimestampOverflow)?
}
}
pub fn is_inturn(&self, current_block_number: u64, author: &Address) -> bool {
if let Some(pos) = self.signers.iter().position(|x| *author == *x) {
return current_block_number % self.signers.len() as u64 == pos as u64;
}
false
}
pub fn is_authorized(&self, author: &Address) -> bool {
self.signers.contains(author) && !self.recent_signers.contains(author)
}
pub fn is_valid_vote(&self, address: &Address, vote_type: VoteType) -> bool {
let in_signer = self.signers.contains(address);
match vote_type {
VoteType::Add => !in_signer,
VoteType::Remove => in_signer,
}
}
pub fn signers(&self) -> &BTreeSet<Address> {
&self.signers
}
fn add_vote(&mut self, pending_vote: PendingVote, kind: VoteType) -> bool {
self.votes.entry(pending_vote)
.and_modify(|state| {
state.votes = state.votes.saturating_add(1);
})
.or_insert_with(|| VoteState { kind, votes: 1 });
true
}
fn revert_vote(&mut self, pending_vote: PendingVote) -> bool {
let mut revert = false;
let mut remove = false;
self.votes.entry(pending_vote).and_modify(|state| {
if state.votes.saturating_sub(1) == 0 {
remove = true;
}
revert = true;
});
if remove {
self.votes.remove(&pending_vote);
}
revert
}
fn get_current_votes_and_kind(&self, beneficiary: Address) -> Option<(usize, VoteType)> {
let kind = self.votes.iter()
.find(|(v, _t)| v.beneficiary == beneficiary)
.map(|(_v, t)| t.kind)?;
let votes = self.votes.keys()
.filter(|vote| vote.beneficiary == beneficiary)
.count();
Some((votes, kind))
}
fn rotate_recent_signers(&mut self) {
if self.recent_signers.len() >= ( self.signers.len() / 2 ) + 1 {
self.recent_signers.pop_back();
}
}
fn remove_all_votes_from(&mut self, beneficiary: Address) {
self.votes = std::mem::replace(&mut self.votes, HashMap::new())
.into_iter()
.filter(|(v, _t)| v.signer != beneficiary && v.beneficiary != beneficiary)
.collect();
}
}