mod context;
use context::{Context, Vote, VoteNode};
#[cfg(feature = "derive-codec")]
use parity_scale_codec::{Decode, Encode};
use crate::{
std::{
self,
collections::btree_map::{BTreeMap, Entry},
fmt,
vec::Vec,
},
vote_graph::VoteGraph,
voter_set::{VoterInfo, VoterSet},
weights::{VoteWeight, VoterWeight},
};
use super::{
BlockNumberOps, Chain, Equivocation, HistoricalVotes, Message, Precommit, Prevote,
SignedMessage,
};
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
pub enum Phase {
Prevote,
Precommit,
}
enum VoteMultiplicity<Vote, Signature> {
Single(Vote, Signature),
Equivocated((Vote, Signature), (Vote, Signature)),
}
impl<Vote: Eq, Signature: Eq> VoteMultiplicity<Vote, Signature> {
fn contains(&self, vote: &Vote, signature: &Signature) -> bool {
match self {
VoteMultiplicity::Single(v, s) => v == vote && s == signature,
VoteMultiplicity::Equivocated((v1, s1), (v2, s2)) =>
v1 == vote && s1 == signature || v2 == vote && s2 == signature,
}
}
}
struct VoteTracker<Id: Ord + Eq, Vote, Signature> {
votes: BTreeMap<Id, VoteMultiplicity<Vote, Signature>>,
current_weight: VoteWeight,
}
pub(crate) struct AddVoteResult<'a, Vote, Signature> {
multiplicity: Option<&'a VoteMultiplicity<Vote, Signature>>,
duplicated: bool,
}
impl<Id: Ord + Eq + Clone, Vote: Clone + Eq, Signature: Clone + Eq>
VoteTracker<Id, Vote, Signature>
{
fn new() -> Self {
VoteTracker { votes: BTreeMap::new(), current_weight: VoteWeight(0) }
}
fn add_vote(
&mut self,
id: Id,
vote: Vote,
signature: Signature,
weight: VoterWeight,
) -> AddVoteResult<Vote, Signature> {
match self.votes.entry(id) {
Entry::Vacant(vacant) => {
self.current_weight = self.current_weight + weight;
let multiplicity = vacant.insert(VoteMultiplicity::Single(vote, signature));
AddVoteResult { multiplicity: Some(multiplicity), duplicated: false }
},
Entry::Occupied(mut occupied) => {
if occupied.get().contains(&vote, &signature) {
return AddVoteResult { multiplicity: None, duplicated: true }
}
let new_val = match *occupied.get_mut() {
VoteMultiplicity::Single(ref v, ref s) =>
VoteMultiplicity::Equivocated((v.clone(), s.clone()), (vote, signature)),
VoteMultiplicity::Equivocated(_, _) =>
return AddVoteResult { multiplicity: None, duplicated: false },
};
*occupied.get_mut() = new_val;
AddVoteResult { multiplicity: Some(&*occupied.into_mut()), duplicated: false }
},
}
}
fn votes(&self) -> Vec<(Id, Vote, Signature)> {
let mut votes = Vec::new();
for (id, vote) in &self.votes {
match vote {
VoteMultiplicity::Single(v, s) => votes.push((id.clone(), v.clone(), s.clone())),
VoteMultiplicity::Equivocated((v1, s1), (v2, s2)) => {
votes.push((id.clone(), v1.clone(), s1.clone()));
votes.push((id.clone(), v2.clone(), s2.clone()));
},
}
}
votes
}
fn participation(&self) -> (VoteWeight, usize) {
(self.current_weight, self.votes.len())
}
}
#[derive(PartialEq, Clone)]
#[cfg_attr(any(feature = "std", test), derive(Debug))]
#[cfg_attr(feature = "derive-codec", derive(Encode, Decode, scale_info::TypeInfo))]
pub struct State<H, N> {
pub prevote_ghost: Option<(H, N)>,
pub finalized: Option<(H, N)>,
pub estimate: Option<(H, N)>,
pub completable: bool,
}
impl<H: Clone, N: Clone> State<H, N> {
pub fn genesis(genesis: (H, N)) -> Self {
State {
prevote_ghost: Some(genesis.clone()),
finalized: Some(genesis.clone()),
estimate: Some(genesis),
completable: true,
}
}
}
pub struct RoundParams<Id: Ord + Eq, H, N> {
pub round_number: u64,
pub voters: VoterSet<Id>,
pub base: (H, N),
}
pub struct Round<Id: Ord + Eq, H: Ord + Eq, N, Signature> {
round_number: u64,
context: Context<Id>,
graph: VoteGraph<H, N, VoteNode>, prevote: VoteTracker<Id, Prevote<H, N>, Signature>, precommit: VoteTracker<Id, Precommit<H, N>, Signature>, historical_votes: HistoricalVotes<H, N, Signature, Id>,
prevote_ghost: Option<(H, N)>, precommit_ghost: Option<(H, N)>, finalized: Option<(H, N)>, estimate: Option<(H, N)>, completable: bool, }
pub(crate) struct ImportResult<Id, P, Signature> {
pub(crate) valid_voter: bool,
pub(crate) duplicated: bool,
pub(crate) equivocation: Option<Equivocation<Id, P, Signature>>,
}
impl<Id, P, Signature> Default for ImportResult<Id, P, Signature> {
fn default() -> Self {
ImportResult { valid_voter: false, duplicated: false, equivocation: None }
}
}
impl<Id, H, N, Signature> Round<Id, H, N, Signature>
where
Id: Ord + Clone + Eq + fmt::Debug,
H: Ord + Clone + Eq + Ord + fmt::Debug,
N: Copy + fmt::Debug + BlockNumberOps,
Signature: Eq + Clone,
{
pub fn new(round_params: RoundParams<Id, H, N>) -> Self {
let (base_hash, base_number) = round_params.base;
Round {
round_number: round_params.round_number,
context: Context::new(round_params.voters),
graph: VoteGraph::new(base_hash, base_number, VoteNode::default()),
prevote: VoteTracker::new(),
precommit: VoteTracker::new(),
historical_votes: HistoricalVotes::new(),
prevote_ghost: None,
precommit_ghost: None,
finalized: None,
estimate: None,
completable: false,
}
}
pub fn number(&self) -> u64 {
self.round_number
}
#[cfg_attr(not(feature = "std"), allow(unused))]
pub(crate) fn import_prevote<C: Chain<H, N>>(
&mut self,
chain: &C,
prevote: Prevote<H, N>,
signer: Id,
signature: Signature,
) -> Result<ImportResult<Id, Prevote<H, N>, Signature>, crate::Error> {
let mut import_result = ImportResult::default();
let info = match self.context.voters().get(&signer) {
Some(info) => info.clone(),
None => return Ok(import_result),
};
import_result.valid_voter = true;
let weight = info.weight();
let equivocation = {
let multiplicity = match self.prevote.add_vote(
signer.clone(),
prevote.clone(),
signature.clone(),
weight,
) {
AddVoteResult { multiplicity: Some(m), .. } => m,
AddVoteResult { duplicated, .. } => {
import_result.duplicated = duplicated;
return Ok(import_result)
},
};
let round_number = self.round_number;
match multiplicity {
VoteMultiplicity::Single(single_vote, _) => {
let vote = Vote::new(&info, Phase::Prevote);
self.graph.insert(
single_vote.target_hash.clone(),
single_vote.target_number,
vote,
chain,
)?;
let message = Message::Prevote(prevote);
let signed_message = SignedMessage { id: signer, signature, message };
self.historical_votes.push_vote(signed_message);
None
},
VoteMultiplicity::Equivocated(ref first, ref second) => {
self.context.equivocated(&info, Phase::Prevote);
let message = Message::Prevote(prevote);
let signed_message = SignedMessage { id: signer.clone(), signature, message };
self.historical_votes.push_vote(signed_message);
Some(Equivocation {
round_number,
identity: signer,
first: first.clone(),
second: second.clone(),
})
},
}
};
let threshold = self.threshold();
if self.prevote.current_weight >= threshold {
self.prevote_ghost = self.graph.find_ghost(self.prevote_ghost.take(), |v| {
self.context.weight(v, Phase::Prevote) >= threshold
});
}
self.update();
import_result.equivocation = equivocation;
Ok(import_result)
}
pub(crate) fn import_precommit<C: Chain<H, N>>(
&mut self,
chain: &C,
precommit: Precommit<H, N>,
signer: Id,
signature: Signature,
) -> Result<ImportResult<Id, Precommit<H, N>, Signature>, crate::Error> {
let mut import_result = ImportResult::default();
let info = match self.context.voters().get(&signer) {
Some(info) => info.clone(),
None => return Ok(import_result),
};
import_result.valid_voter = true;
let weight = info.weight();
let equivocation = {
let multiplicity = match self.precommit.add_vote(
signer.clone(),
precommit.clone(),
signature.clone(),
weight,
) {
AddVoteResult { multiplicity: Some(m), .. } => m,
AddVoteResult { duplicated, .. } => {
import_result.duplicated = duplicated;
return Ok(import_result)
},
};
let round_number = self.round_number;
match multiplicity {
VoteMultiplicity::Single(single_vote, _) => {
let vote = Vote::new(&info, Phase::Precommit);
self.graph.insert(
single_vote.target_hash.clone(),
single_vote.target_number,
vote,
chain,
)?;
let message = Message::Precommit(precommit);
let signed_message = SignedMessage { id: signer, signature, message };
self.historical_votes.push_vote(signed_message);
None
},
VoteMultiplicity::Equivocated(ref first, ref second) => {
self.context.equivocated(&info, Phase::Precommit);
let message = Message::Precommit(precommit);
let signed_message = SignedMessage { id: signer.clone(), signature, message };
self.historical_votes.push_vote(signed_message);
Some(Equivocation {
round_number,
identity: signer,
first: first.clone(),
second: second.clone(),
})
},
}
};
self.update();
import_result.equivocation = equivocation;
Ok(import_result)
}
pub fn state(&self) -> State<H, N> {
State {
prevote_ghost: self.prevote_ghost.clone(),
finalized: self.finalized.clone(),
estimate: self.estimate.clone(),
completable: self.completable,
}
}
pub fn precommit_ghost(&mut self) -> Option<(H, N)> {
let threshold = self.threshold();
if self.precommit.current_weight >= threshold {
self.precommit_ghost = self.graph.find_ghost(self.precommit_ghost.take(), |v| {
self.context.weight(v, Phase::Precommit) >= threshold
});
}
self.precommit_ghost.clone()
}
pub fn finalizing_precommits<'a, C: 'a + Chain<H, N>>(
&'a mut self,
chain: &'a C,
) -> Option<impl Iterator<Item = crate::SignedPrecommit<H, N, Signature, Id>> + 'a> {
struct YieldVotes<'b, V: 'b, S: 'b> {
yielded: usize,
multiplicity: &'b VoteMultiplicity<V, S>,
}
impl<'b, V: 'b + Clone, S: 'b + Clone> Iterator for YieldVotes<'b, V, S> {
type Item = (V, S);
fn next(&mut self) -> Option<(V, S)> {
match self.multiplicity {
VoteMultiplicity::Single(ref v, ref s) =>
if self.yielded == 0 {
self.yielded += 1;
Some((v.clone(), s.clone()))
} else {
None
},
VoteMultiplicity::Equivocated(ref a, ref b) => {
let res = match self.yielded {
0 => Some(a.clone()),
1 => Some(b.clone()),
_ => None,
};
self.yielded += 1;
res
},
}
}
}
let (f_hash, _f_num) = self.finalized.clone()?;
let find_valid_precommits = self
.precommit
.votes
.iter()
.filter(move |&(_id, multiplicity)| {
if let VoteMultiplicity::Single(ref v, _) = *multiplicity {
chain.is_equal_or_descendent_of(f_hash.clone(), v.target_hash.clone())
} else {
true
}
})
.flat_map(|(id, multiplicity)| {
let yield_votes = YieldVotes { yielded: 0, multiplicity };
yield_votes.map(move |(v, s)| crate::SignedPrecommit {
precommit: v,
signature: s,
id: id.clone(),
})
});
Some(find_valid_precommits)
}
fn update(&mut self) {
let threshold = self.threshold();
if self.prevote.current_weight < threshold {
return
}
let (g_hash, g_num) = match self.prevote_ghost.clone() {
None => return,
Some(x) => x,
};
let ctx = &self.context;
let current_precommits = self.precommit.current_weight;
if current_precommits >= self.threshold() {
self.finalized = self.graph.find_ancestor(g_hash.clone(), g_num, |v| {
ctx.weight(v, Phase::Precommit) >= threshold
});
};
let possible_to_precommit = {
let tolerated_equivocations = ctx.voters().total_weight() - threshold;
let current_equivocations = ctx.equivocation_weight(Phase::Precommit);
let additional_equiv = tolerated_equivocations - current_equivocations;
let remaining_commit_votes =
ctx.voters().total_weight() - self.precommit.current_weight;
move |node: &VoteNode| {
let precommitted_for = ctx.weight(node, Phase::Precommit);
let possible_equivocations =
std::cmp::min(current_precommits - precommitted_for, additional_equiv);
let full_possible_weight =
precommitted_for + remaining_commit_votes + possible_equivocations;
full_possible_weight >= threshold
}
};
if self.precommit.current_weight >= threshold {
self.estimate = self.graph.find_ancestor(g_hash.clone(), g_num, possible_to_precommit);
} else {
self.estimate = Some((g_hash, g_num));
return
}
self.completable = self.estimate.clone().map_or(false, |(b_hash, b_num)| {
b_hash != g_hash || {
self.graph
.find_ghost(Some((b_hash, b_num)), possible_to_precommit)
.map_or(true, |x| x == (g_hash, g_num))
}
})
}
pub fn estimate(&self) -> Option<&(H, N)> {
self.estimate.as_ref()
}
pub fn finalized(&self) -> Option<&(H, N)> {
self.finalized.as_ref()
}
pub fn completable(&self) -> bool {
self.completable
}
pub fn threshold(&self) -> VoterWeight {
self.context.voters().threshold()
}
pub fn base(&self) -> (H, N) {
self.graph.base()
}
pub fn voters(&self) -> &VoterSet<Id> {
self.context.voters()
}
pub fn primary_voter(&self) -> (&Id, &VoterInfo) {
self.context.voters().nth_mod(self.round_number as usize)
}
pub fn prevote_participation(&self) -> (VoteWeight, usize) {
self.prevote.participation()
}
pub fn precommit_participation(&self) -> (VoteWeight, usize) {
self.precommit.participation()
}
pub fn prevotes(&self) -> Vec<(Id, Prevote<H, N>, Signature)> {
self.prevote.votes()
}
pub fn precommits(&self) -> Vec<(Id, Precommit<H, N>, Signature)> {
self.precommit.votes()
}
pub fn historical_votes(&self) -> &HistoricalVotes<H, N, Signature, Id> {
&self.historical_votes
}
pub fn set_prevoted_index(&mut self) {
self.historical_votes.set_prevoted_idx()
}
pub fn set_precommitted_index(&mut self) {
self.historical_votes.set_precommitted_idx()
}
pub fn prevoted_index(&self) -> Option<u64> {
self.historical_votes.prevote_idx
}
pub fn precommitted_index(&self) -> Option<u64> {
self.historical_votes.precommit_idx
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::chain::{DummyChain, GENESIS_HASH};
fn voters() -> VoterSet<&'static str> {
VoterSet::new([("Alice", 4), ("Bob", 7), ("Eve", 3)].iter().cloned()).expect("nonempty")
}
#[derive(PartialEq, Eq, Clone, Debug)]
struct Signature(&'static str);
#[test]
fn estimate_is_valid() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C", "D", "E", "F"]);
chain.push_blocks("E", &["EA", "EB", "EC", "ED"]);
chain.push_blocks("F", &["FA", "FB", "FC"]);
let mut round =
Round::new(RoundParams { round_number: 1, voters: voters(), base: ("C", 4) });
round
.import_prevote(&chain, Prevote::new("FC", 10), "Alice", Signature("Alice"))
.unwrap();
round
.import_prevote(&chain, Prevote::new("ED", 10), "Bob", Signature("Bob"))
.unwrap();
assert_eq!(round.prevote_ghost, Some(("E", 6)));
assert_eq!(round.estimate(), Some(&("E", 6)));
assert!(!round.completable());
round
.import_prevote(&chain, Prevote::new("F", 7), "Eve", Signature("Eve"))
.unwrap();
assert_eq!(round.prevote_ghost, Some(("E", 6)));
assert_eq!(round.estimate(), Some(&("E", 6)));
}
#[test]
fn finalization() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C", "D", "E", "F"]);
chain.push_blocks("E", &["EA", "EB", "EC", "ED"]);
chain.push_blocks("F", &["FA", "FB", "FC"]);
let mut round =
Round::new(RoundParams { round_number: 1, voters: voters(), base: ("C", 4) });
round
.import_precommit(&chain, Precommit::new("FC", 10), "Alice", Signature("Alice"))
.unwrap();
round
.import_precommit(&chain, Precommit::new("ED", 10), "Bob", Signature("Bob"))
.unwrap();
assert_eq!(round.finalized, None);
{
round
.import_prevote(&chain, Prevote::new("FC", 10), "Alice", Signature("Alice"))
.unwrap();
round
.import_prevote(&chain, Prevote::new("ED", 10), "Bob", Signature("Bob"))
.unwrap();
round
.import_prevote(&chain, Prevote::new("EA", 7), "Eve", Signature("Eve"))
.unwrap();
assert_eq!(round.finalized, Some(("E", 6)));
}
round
.import_precommit(&chain, Precommit::new("EA", 7), "Eve", Signature("Eve"))
.unwrap();
assert_eq!(round.finalized, Some(("EA", 7)));
}
#[test]
fn equivocate_does_not_double_count() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C", "D", "E", "F"]);
chain.push_blocks("E", &["EA", "EB", "EC", "ED"]);
chain.push_blocks("F", &["FA", "FB", "FC"]);
let mut round =
Round::new(RoundParams { round_number: 1, voters: voters(), base: ("C", 4) });
assert!(round
.import_prevote(
&chain,
Prevote::new("FC", 10),
"Eve", Signature("Eve-1"),
)
.unwrap()
.equivocation
.is_none());
assert!(round.prevote_ghost.is_none());
assert!(round
.import_prevote(
&chain,
Prevote::new("ED", 10),
"Eve", Signature("Eve-2"),
)
.unwrap()
.equivocation
.is_some());
assert!(round
.import_prevote(
&chain,
Prevote::new("F", 7),
"Eve", Signature("Eve-2"),
)
.unwrap()
.equivocation
.is_none());
assert!(round.prevote_ghost.is_none());
assert!(round
.import_prevote(
&chain,
Prevote::new("FA", 8),
"Bob", Signature("Bob-1"),
)
.unwrap()
.equivocation
.is_none());
assert_eq!(round.prevote_ghost, Some(("FA", 8)));
}
#[test]
fn historical_votes_works() {
let mut chain = DummyChain::new();
chain.push_blocks(GENESIS_HASH, &["A", "B", "C", "D", "E", "F"]);
chain.push_blocks("E", &["EA", "EB", "EC", "ED"]);
chain.push_blocks("F", &["FA", "FB", "FC"]);
let mut round =
Round::new(RoundParams { round_number: 1, voters: voters(), base: ("C", 4) });
round
.import_prevote(&chain, Prevote::new("FC", 10), "Alice", Signature("Alice"))
.unwrap();
round.set_prevoted_index();
round
.import_prevote(&chain, Prevote::new("EA", 7), "Eve", Signature("Eve"))
.unwrap();
round
.import_precommit(&chain, Precommit::new("EA", 7), "Eve", Signature("Eve"))
.unwrap();
round
.import_prevote(&chain, Prevote::new("EC", 10), "Alice", Signature("Alice"))
.unwrap();
round.set_precommitted_index();
assert_eq!(
round.historical_votes(),
&HistoricalVotes::new_with(
vec![
SignedMessage {
message: Message::Prevote(Prevote { target_hash: "FC", target_number: 10 }),
signature: Signature("Alice"),
id: "Alice"
},
SignedMessage {
message: Message::Prevote(Prevote { target_hash: "EA", target_number: 7 }),
signature: Signature("Eve"),
id: "Eve"
},
SignedMessage {
message: Message::Precommit(Precommit {
target_hash: "EA",
target_number: 7
}),
signature: Signature("Eve"),
id: "Eve"
},
SignedMessage {
message: Message::Prevote(Prevote { target_hash: "EC", target_number: 10 }),
signature: Signature("Alice"),
id: "Alice"
},
],
Some(1),
Some(4),
)
);
}
}