use beefy_primitives::{
crypto::{Public, Signature},
ValidatorSet, ValidatorSetId,
};
use codec::{Decode, Encode};
use log::{debug, trace};
use sp_runtime::traits::{Block, NumberFor};
use std::{collections::BTreeMap, hash::Hash};
#[derive(Debug, Decode, Default, Encode, PartialEq)]
struct RoundTracker {
self_vote: bool,
votes: BTreeMap<Public, Signature>,
}
impl RoundTracker {
fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool {
if self.votes.contains_key(&vote.0) {
return false
}
self.self_vote = self.self_vote || self_vote;
self.votes.insert(vote.0, vote.1);
true
}
fn has_self_vote(&self) -> bool {
self.self_vote
}
fn is_done(&self, threshold: usize) -> bool {
self.votes.len() >= threshold
}
}
pub fn threshold(authorities: usize) -> usize {
let faulty = authorities.saturating_sub(1) / 3;
authorities - faulty
}
#[derive(Debug, Decode, Encode, PartialEq)]
pub(crate) struct Rounds<Payload, B: Block> {
rounds: BTreeMap<(Payload, NumberFor<B>), RoundTracker>,
session_start: NumberFor<B>,
validator_set: ValidatorSet<Public>,
mandatory_done: bool,
best_done: Option<NumberFor<B>>,
}
impl<P, B> Rounds<P, B>
where
P: Ord + Hash + Clone,
B: Block,
{
pub(crate) fn new(session_start: NumberFor<B>, validator_set: ValidatorSet<Public>) -> Self {
Rounds {
rounds: BTreeMap::new(),
session_start,
validator_set,
mandatory_done: false,
best_done: None,
}
}
pub(crate) fn validator_set(&self) -> &ValidatorSet<Public> {
&self.validator_set
}
pub(crate) fn validator_set_id(&self) -> ValidatorSetId {
self.validator_set.id()
}
pub(crate) fn validators(&self) -> &[Public] {
self.validator_set.validators()
}
pub(crate) fn session_start(&self) -> NumberFor<B> {
self.session_start
}
pub(crate) fn mandatory_done(&self) -> bool {
self.mandatory_done
}
pub(crate) fn should_self_vote(&self, round: &(P, NumberFor<B>)) -> bool {
Some(round.1) > self.best_done &&
self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true)
}
pub(crate) fn add_vote(
&mut self,
round: &(P, NumberFor<B>),
vote: (Public, Signature),
self_vote: bool,
) -> bool {
let num = round.1;
if num < self.session_start || Some(num) <= self.best_done {
debug!(target: "beefy", "🥩 received vote for old stale round {:?}, ignoring", num);
false
} else if !self.validators().iter().any(|id| vote.0 == *id) {
debug!(
target: "beefy",
"🥩 received vote {:?} from validator that is not in the validator set, ignoring",
vote
);
false
} else {
self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote)
}
}
pub(crate) fn should_conclude(
&mut self,
round: &(P, NumberFor<B>),
) -> Option<Vec<Option<Signature>>> {
let done = self
.rounds
.get(round)
.map(|tracker| tracker.is_done(threshold(self.validator_set.len())))
.unwrap_or(false);
trace!(target: "beefy", "🥩 Round #{} done: {}", round.1, done);
if done {
let signatures = self.rounds.remove(round)?.votes;
Some(
self.validators()
.iter()
.map(|authority_id| signatures.get(authority_id).cloned())
.collect(),
)
} else {
None
}
}
pub(crate) fn conclude(&mut self, round_num: NumberFor<B>) {
self.rounds.retain(|&(_, number), _| number > round_num);
self.mandatory_done = self.mandatory_done || round_num == self.session_start;
self.best_done = self.best_done.max(Some(round_num));
debug!(target: "beefy", "🥩 Concluded round #{}", round_num);
}
}
#[cfg(test)]
mod tests {
use sc_network_test::Block;
use sp_core::H256;
use beefy_primitives::{crypto::Public, ValidatorSet};
use super::{threshold, Block as BlockT, Hash, RoundTracker, Rounds};
use crate::keystore::tests::Keyring;
impl<P, B> Rounds<P, B>
where
P: Ord + Hash + Clone,
B: BlockT,
{
pub(crate) fn test_set_mandatory_done(&mut self, done: bool) {
self.mandatory_done = done;
}
}
#[test]
fn round_tracker() {
let mut rt = RoundTracker::default();
let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed"));
let threshold = 2;
assert!(!rt.has_self_vote());
assert!(rt.add_vote(bob_vote.clone(), false));
assert!(!rt.add_vote(bob_vote, false));
assert!(!rt.has_self_vote());
assert!(!rt.is_done(threshold));
let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed"));
assert!(rt.add_vote(alice_vote, true));
assert!(rt.has_self_vote());
assert!(rt.is_done(threshold));
}
#[test]
fn vote_threshold() {
assert_eq!(threshold(1), 1);
assert_eq!(threshold(2), 2);
assert_eq!(threshold(3), 3);
assert_eq!(threshold(4), 3);
assert_eq!(threshold(100), 67);
assert_eq!(threshold(300), 201);
}
#[test]
fn new_rounds() {
sp_tracing::try_init_simple();
let validators = ValidatorSet::<Public>::new(
vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
42,
)
.unwrap();
let session_start = 1u64.into();
let rounds = Rounds::<H256, Block>::new(session_start, validators);
assert_eq!(42, rounds.validator_set_id());
assert_eq!(1, rounds.session_start());
assert_eq!(
&vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
rounds.validators()
);
}
#[test]
fn add_and_conclude_votes() {
sp_tracing::try_init_simple();
let validators = ValidatorSet::<Public>::new(
vec![
Keyring::Alice.public(),
Keyring::Bob.public(),
Keyring::Charlie.public(),
Keyring::Eve.public(),
],
Default::default(),
)
.unwrap();
let round = (H256::from_low_u64_le(1), 1);
let session_start = 1u64.into();
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
assert!(rounds.should_self_vote(&round));
assert!(rounds.add_vote(
&round,
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
true
));
assert!(rounds.should_conclude(&round).is_none());
assert!(!rounds.should_self_vote(&round));
assert!(!rounds.add_vote(
&round,
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
true
));
assert!(!rounds.add_vote(
&round,
(Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")),
false
));
assert!(rounds.should_conclude(&round).is_none());
assert!(rounds.add_vote(
&round,
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")),
false
));
assert!(rounds.should_conclude(&round).is_none());
assert!(rounds.add_vote(
&round,
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")),
false
));
assert!(rounds.should_conclude(&round).is_some());
rounds.conclude(round.1);
assert!(!rounds.add_vote(
&round,
(Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")),
false
));
}
#[test]
fn old_rounds_not_accepted() {
sp_tracing::try_init_simple();
let validators = ValidatorSet::<Public>::new(
vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
42,
)
.unwrap();
let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed"));
let session_start = 10u64.into();
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
let mut vote = (H256::from_low_u64_le(1), 9);
assert!(!rounds.add_vote(&vote, alice.clone(), true));
assert!(rounds.rounds.is_empty());
rounds.best_done = Some(11);
vote.1 = 10;
assert!(!rounds.add_vote(&vote, alice.clone(), true));
vote.1 = 11;
assert!(!rounds.add_vote(&vote, alice.clone(), true));
assert!(rounds.rounds.is_empty());
vote.1 = 12;
assert!(rounds.add_vote(&vote, alice, true));
assert_eq!(rounds.rounds.len(), 1);
}
#[test]
fn multiple_rounds() {
sp_tracing::try_init_simple();
let validators = ValidatorSet::<Public>::new(
vec![
Keyring::Alice.public(),
Keyring::Bob.public(),
Keyring::Charlie.public(),
Keyring::Dave.public(),
],
Default::default(),
)
.unwrap();
let session_start = 1u64.into();
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
assert!(rounds.add_vote(
&(H256::from_low_u64_le(1), 1),
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
true,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(1), 1),
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")),
false,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(1), 1),
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")),
false,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(2), 2),
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")),
true,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(2), 2),
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")),
false,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(2), 2),
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")),
false,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(3), 3),
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")),
true,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(3), 3),
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")),
false,
));
assert!(rounds.add_vote(
&(H256::from_low_u64_le(3), 3),
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")),
false,
));
assert_eq!(3, rounds.rounds.len());
assert!(rounds.should_conclude(&(H256::from_low_u64_le(5), 5)).is_none());
assert_eq!(3, rounds.rounds.len());
let signatures = rounds.should_conclude(&(H256::from_low_u64_le(2), 2)).unwrap();
rounds.conclude(2);
assert_eq!(1, rounds.rounds.len());
assert_eq!(
signatures,
vec![
Some(Keyring::Alice.sign(b"I am again committed")),
Some(Keyring::Bob.sign(b"I am again committed")),
Some(Keyring::Charlie.sign(b"I am again committed")),
None
]
);
}
}