use polkadot_primitives::{CandidateHash, CompactStatement, Hash, ValidatorIndex};
use crate::LOG_TARGET;
use std::collections::{HashMap, HashSet};
#[derive(Hash, Clone, PartialEq, Eq)]
enum Knowledge {
General(CandidateHash),
Specific(CompactStatement, ValidatorIndex),
}
#[derive(Hash, Clone, PartialEq, Eq)]
enum TaggedKnowledge {
IncomingP2P(Knowledge),
OutgoingP2P(Knowledge),
Seconded(CandidateHash),
}
pub struct ClusterTracker {
validators: Vec<ValidatorIndex>,
seconding_limit: usize,
knowledge: HashMap<ValidatorIndex, HashSet<TaggedKnowledge>>,
pending: HashMap<ValidatorIndex, HashSet<(ValidatorIndex, CompactStatement)>>,
}
impl ClusterTracker {
pub fn new(cluster_validators: Vec<ValidatorIndex>, seconding_limit: usize) -> Option<Self> {
if cluster_validators.is_empty() {
return None
}
Some(ClusterTracker {
validators: cluster_validators,
seconding_limit,
knowledge: HashMap::new(),
pending: HashMap::new(),
})
}
pub fn can_receive(
&self,
sender: ValidatorIndex,
originator: ValidatorIndex,
statement: CompactStatement,
) -> Result<Accept, RejectIncoming> {
if !self.is_in_group(sender) || !self.is_in_group(originator) {
return Err(RejectIncoming::NotInGroup)
}
if self.they_sent(sender, Knowledge::Specific(statement.clone(), originator)) {
return Err(RejectIncoming::Duplicate)
}
match statement {
CompactStatement::Seconded(candidate_hash) => {
let other_seconded_for_orig_from_remote = self
.knowledge
.get(&sender)
.into_iter()
.flat_map(|v_knowledge| v_knowledge.iter())
.filter(|k| match k {
TaggedKnowledge::IncomingP2P(Knowledge::Specific(
CompactStatement::Seconded(_),
orig,
)) if orig == &originator => true,
_ => false,
})
.count();
if other_seconded_for_orig_from_remote == self.seconding_limit {
return Err(RejectIncoming::ExcessiveSeconded)
}
if self.seconded_already_or_within_limit(originator, candidate_hash) {
Ok(Accept::Ok)
} else {
Ok(Accept::WithPrejudice)
}
},
CompactStatement::Valid(candidate_hash) => {
if !self.knows_candidate(sender, candidate_hash) {
return Err(RejectIncoming::CandidateUnknown)
}
Ok(Accept::Ok)
},
}
}
pub fn note_issued(&mut self, originator: ValidatorIndex, statement: CompactStatement) {
for cluster_member in &self.validators {
if !self.they_know_statement(*cluster_member, originator, statement.clone()) {
self.pending
.entry(*cluster_member)
.or_default()
.insert((originator, statement.clone()));
}
}
}
pub fn note_received(
&mut self,
sender: ValidatorIndex,
originator: ValidatorIndex,
statement: CompactStatement,
) {
for cluster_member in &self.validators {
if cluster_member == &sender {
if let Some(pending) = self.pending.get_mut(&sender) {
pending.remove(&(originator, statement.clone()));
}
} else if !self.they_know_statement(*cluster_member, originator, statement.clone()) {
self.pending
.entry(*cluster_member)
.or_default()
.insert((originator, statement.clone()));
}
}
{
let sender_knowledge = self.knowledge.entry(sender).or_default();
sender_knowledge.insert(TaggedKnowledge::IncomingP2P(Knowledge::Specific(
statement.clone(),
originator,
)));
if let CompactStatement::Seconded(candidate_hash) = statement.clone() {
sender_knowledge
.insert(TaggedKnowledge::IncomingP2P(Knowledge::General(candidate_hash)));
}
}
if let CompactStatement::Seconded(candidate_hash) = statement {
if self.seconded_already_or_within_limit(originator, candidate_hash) {
let originator_knowledge = self.knowledge.entry(originator).or_default();
originator_knowledge.insert(TaggedKnowledge::Seconded(candidate_hash));
}
}
}
pub fn can_send(
&self,
target: ValidatorIndex,
originator: ValidatorIndex,
statement: CompactStatement,
) -> Result<(), RejectOutgoing> {
if !self.is_in_group(target) || !self.is_in_group(originator) {
return Err(RejectOutgoing::NotInGroup)
}
if self.they_know_statement(target, originator, statement.clone()) {
return Err(RejectOutgoing::Known)
}
match statement {
CompactStatement::Seconded(candidate_hash) => {
if !self.seconded_already_or_within_limit(originator, candidate_hash) {
return Err(RejectOutgoing::ExcessiveSeconded)
}
Ok(())
},
CompactStatement::Valid(candidate_hash) => {
if !self.knows_candidate(target, candidate_hash) {
return Err(RejectOutgoing::CandidateUnknown)
}
Ok(())
},
}
}
pub fn note_sent(
&mut self,
target: ValidatorIndex,
originator: ValidatorIndex,
statement: CompactStatement,
) {
{
let target_knowledge = self.knowledge.entry(target).or_default();
target_knowledge.insert(TaggedKnowledge::OutgoingP2P(Knowledge::Specific(
statement.clone(),
originator,
)));
if let CompactStatement::Seconded(candidate_hash) = statement.clone() {
target_knowledge
.insert(TaggedKnowledge::OutgoingP2P(Knowledge::General(candidate_hash)));
}
}
if let CompactStatement::Seconded(candidate_hash) = statement {
let originator_knowledge = self.knowledge.entry(originator).or_default();
originator_knowledge.insert(TaggedKnowledge::Seconded(candidate_hash));
}
if let Some(pending) = self.pending.get_mut(&target) {
pending.remove(&(originator, statement));
}
}
pub fn targets(&self) -> &[ValidatorIndex] {
&self.validators
}
pub fn senders_for_originator(&self, originator: ValidatorIndex) -> &[ValidatorIndex] {
if self.validators.contains(&originator) {
&self.validators[..]
} else {
&[]
}
}
pub fn knows_candidate(
&self,
validator: ValidatorIndex,
candidate_hash: CandidateHash,
) -> bool {
self.we_sent_seconded(validator, candidate_hash) ||
self.they_sent_seconded(validator, candidate_hash) ||
self.validator_seconded(validator, candidate_hash)
}
pub fn can_request(&self, target: ValidatorIndex, candidate_hash: CandidateHash) -> bool {
self.validators.contains(&target) &&
self.we_sent_seconded(target, candidate_hash) &&
!self.they_sent_seconded(target, candidate_hash)
}
pub fn pending_statements_for(
&self,
target: ValidatorIndex,
) -> Vec<(ValidatorIndex, CompactStatement)> {
let mut v = self
.pending
.get(&target)
.map(|x| x.iter().cloned().collect::<Vec<_>>())
.unwrap_or_default();
v.sort_by_key(|(_, s)| match s {
CompactStatement::Seconded(_) => 0u8,
CompactStatement::Valid(_) => 1u8,
});
v
}
fn seconded_already_or_within_limit(
&self,
validator: ValidatorIndex,
candidate_hash: CandidateHash,
) -> bool {
let seconded_other_candidates = self
.knowledge
.get(&validator)
.into_iter()
.flat_map(|v_knowledge| v_knowledge.iter())
.filter(|k| match k {
TaggedKnowledge::Seconded(c) if c != &candidate_hash => true,
_ => false,
})
.count();
seconded_other_candidates < self.seconding_limit
}
fn they_know_statement(
&self,
validator: ValidatorIndex,
originator: ValidatorIndex,
statement: CompactStatement,
) -> bool {
let knowledge = Knowledge::Specific(statement, originator);
self.we_sent(validator, knowledge.clone()) || self.they_sent(validator, knowledge)
}
fn they_sent(&self, validator: ValidatorIndex, knowledge: Knowledge) -> bool {
self.knowledge
.get(&validator)
.map_or(false, |k| k.contains(&TaggedKnowledge::IncomingP2P(knowledge)))
}
fn we_sent(&self, validator: ValidatorIndex, knowledge: Knowledge) -> bool {
self.knowledge
.get(&validator)
.map_or(false, |k| k.contains(&TaggedKnowledge::OutgoingP2P(knowledge)))
}
fn we_sent_seconded(&self, validator: ValidatorIndex, candidate_hash: CandidateHash) -> bool {
self.we_sent(validator, Knowledge::General(candidate_hash))
}
fn they_sent_seconded(&self, validator: ValidatorIndex, candidate_hash: CandidateHash) -> bool {
self.they_sent(validator, Knowledge::General(candidate_hash))
}
fn validator_seconded(&self, validator: ValidatorIndex, candidate_hash: CandidateHash) -> bool {
self.knowledge
.get(&validator)
.map_or(false, |k| k.contains(&TaggedKnowledge::Seconded(candidate_hash)))
}
fn is_in_group(&self, validator: ValidatorIndex) -> bool {
self.validators.contains(&validator)
}
pub fn warn_if_too_many_pending_statements(&self, parent_hash: Hash) {
if self.pending.iter().filter(|pending| !pending.1.is_empty()).count() >=
self.validators.len() &&
self.validators.len() > 1
{
gum::warn!(
target: LOG_TARGET,
pending_statements = ?self.pending,
?parent_hash,
"Cluster has too many pending statements, something wrong with our connection to our group peers
Restart might be needed if validator gets 0 backing rewards for more than 3-4 consecutive sessions"
);
}
}
}
#[derive(Debug, PartialEq)]
pub enum Accept {
Ok,
WithPrejudice,
}
#[derive(Debug, PartialEq)]
pub enum RejectIncoming {
ExcessiveSeconded,
NotInGroup,
CandidateUnknown,
Duplicate,
}
#[derive(Debug, PartialEq)]
pub enum RejectOutgoing {
CandidateUnknown,
ExcessiveSeconded,
Known,
NotInGroup,
}
#[cfg(test)]
mod tests {
use super::*;
use polkadot_primitives::Hash;
#[test]
fn rejects_incoming_outside_of_group() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
assert_eq!(
tracker.can_receive(
ValidatorIndex(100),
ValidatorIndex(5),
CompactStatement::Seconded(CandidateHash(Hash::repeat_byte(1))),
),
Err(RejectIncoming::NotInGroup),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(100),
CompactStatement::Seconded(CandidateHash(Hash::repeat_byte(1))),
),
Err(RejectIncoming::NotInGroup),
);
}
#[test]
fn begrudgingly_accepts_too_many_seconded_from_multiple_peers() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
let hash_c = CandidateHash(Hash::repeat_byte(3));
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_c),
),
Err(RejectIncoming::ExcessiveSeconded),
);
}
#[test]
fn rejects_too_many_seconded_from_sender() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
let hash_c = CandidateHash(Hash::repeat_byte(3));
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_c),
),
Ok(Accept::WithPrejudice),
);
}
#[test]
fn rejects_duplicates() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let hash_a = CandidateHash(Hash::repeat_byte(1));
let mut tracker = ClusterTracker::new(group, seconding_limit).expect("not empty");
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Valid(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Err(RejectIncoming::Duplicate),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Valid(hash_a),
),
Err(RejectIncoming::Duplicate),
);
}
#[test]
fn rejects_incoming_valid_without_seconded() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let tracker = ClusterTracker::new(group, seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Valid(hash_a),
),
Err(RejectIncoming::CandidateUnknown),
);
}
#[test]
fn accepts_incoming_valid_after_receiving_seconded() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Valid(hash_a),
),
Ok(Accept::Ok)
);
}
#[test]
fn accepts_incoming_valid_after_outgoing_seconded() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
tracker.note_sent(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Valid(hash_a),
),
Ok(Accept::Ok)
);
}
#[test]
fn cannot_send_too_many_seconded_even_to_multiple_peers() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
let hash_c = CandidateHash(Hash::repeat_byte(3));
tracker.note_sent(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
tracker.note_sent(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_c),
),
Err(RejectOutgoing::ExcessiveSeconded),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(24),
ValidatorIndex(5),
CompactStatement::Seconded(hash_c),
),
Err(RejectOutgoing::ExcessiveSeconded),
);
}
#[test]
fn cannot_send_duplicate() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
tracker.note_sent(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Err(RejectOutgoing::Known),
);
}
#[test]
fn cannot_send_what_was_received() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 2;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
tracker.note_received(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Err(RejectOutgoing::Known),
);
}
#[test]
fn can_send_statements_received_with_prejudice() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 1;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
assert_eq!(
tracker.can_receive(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(24),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
),
Ok(Accept::WithPrejudice),
);
tracker.note_received(
ValidatorIndex(24),
ValidatorIndex(5),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(24),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(()),
);
}
#[test]
fn pending_statements_set_when_receiving_fresh_statements() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 1;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
{
assert_eq!(
tracker.can_receive(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(tracker.pending_statements_for(ValidatorIndex(200)), vec![]);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
}
{
assert_eq!(
tracker.can_send(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Seconded(hash_a)
),
Ok(())
);
tracker.note_sent(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Valid(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Valid(hash_a),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_a))
]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(200)),
vec![(ValidatorIndex(200), CompactStatement::Valid(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_a))
]
);
}
{
assert_eq!(
tracker.can_receive(
ValidatorIndex(5),
ValidatorIndex(146),
CompactStatement::Seconded(hash_b),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(5),
ValidatorIndex(146),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_a))
]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(200)),
vec![
(ValidatorIndex(146), CompactStatement::Seconded(hash_b)),
(ValidatorIndex(200), CompactStatement::Valid(hash_a)),
]
);
{
let mut pending_statements = tracker.pending_statements_for(ValidatorIndex(24));
pending_statements.sort();
assert_eq!(
pending_statements,
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(146), CompactStatement::Seconded(hash_b))
],
);
}
{
let mut pending_statements = tracker.pending_statements_for(ValidatorIndex(146));
pending_statements.sort();
assert_eq!(
pending_statements,
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(146), CompactStatement::Seconded(hash_b)),
(ValidatorIndex(200), CompactStatement::Valid(hash_a)),
]
);
}
}
}
#[test]
fn pending_statements_updated_when_sending_statements() {
let group =
vec![ValidatorIndex(5), ValidatorIndex(200), ValidatorIndex(24), ValidatorIndex(146)];
let seconding_limit = 1;
let mut tracker = ClusterTracker::new(group.clone(), seconding_limit).expect("not empty");
let hash_a = CandidateHash(Hash::repeat_byte(1));
let hash_b = CandidateHash(Hash::repeat_byte(2));
{
assert_eq!(
tracker.can_receive(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(200),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(tracker.pending_statements_for(ValidatorIndex(200)), vec![]);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
}
{
assert_eq!(
tracker.can_send(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Seconded(hash_b)
),
Ok(())
);
tracker.note_sent(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_receive(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Valid(hash_b),
),
Ok(Accept::Ok),
);
tracker.note_received(
ValidatorIndex(24),
ValidatorIndex(200),
CompactStatement::Valid(hash_b),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_b))
]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(200)),
vec![(ValidatorIndex(200), CompactStatement::Valid(hash_b))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_b))
]
);
}
{
assert_eq!(
tracker.can_send(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a)
),
Ok(())
);
tracker.note_sent(
ValidatorIndex(5),
ValidatorIndex(5),
CompactStatement::Seconded(hash_a),
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(5)),
vec![(ValidatorIndex(200), CompactStatement::Valid(hash_b))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(200)),
vec![(ValidatorIndex(200), CompactStatement::Valid(hash_b))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_b))
]
);
}
{
assert_eq!(
tracker.can_send(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Seconded(hash_b)
),
Ok(())
);
tracker.note_sent(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Seconded(hash_b),
);
assert_eq!(
tracker.can_send(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Valid(hash_b)
),
Ok(())
);
tracker.note_sent(
ValidatorIndex(5),
ValidatorIndex(200),
CompactStatement::Valid(hash_b),
);
assert_eq!(tracker.pending_statements_for(ValidatorIndex(5)), vec![]);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(200)),
vec![(ValidatorIndex(200), CompactStatement::Valid(hash_b))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(24)),
vec![(ValidatorIndex(5), CompactStatement::Seconded(hash_a))]
);
assert_eq!(
tracker.pending_statements_for(ValidatorIndex(146)),
vec![
(ValidatorIndex(5), CompactStatement::Seconded(hash_a)),
(ValidatorIndex(200), CompactStatement::Valid(hash_b))
]
);
}
}
}