From 8e075e996381b72c72bfa929898f447467e55697 Mon Sep 17 00:00:00 2001 From: Robert Habermeier Date: Mon, 18 Dec 2017 16:07:55 +0100 Subject: [PATCH] check justification set for BFT --- substrate/candidate-agreement/src/bft.rs | 69 +++++++++++++++++++++--- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/substrate/candidate-agreement/src/bft.rs b/substrate/candidate-agreement/src/bft.rs index 1cc9158127..ae240d524f 100644 --- a/substrate/candidate-agreement/src/bft.rs +++ b/substrate/candidate-agreement/src/bft.rs @@ -20,14 +20,14 @@ //! known by each node. The proposals they have may differ, so the agreement //! may never complete. -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::hash::Hash; use futures::{Future, Stream, Sink}; use futures::future::{ok, loop_fn, Loop}; /// Messages over the proposal. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Message

{ /// Prepare to vote for proposal P. Prepare(P), @@ -53,6 +53,51 @@ pub struct Agreed { pub justification: Vec>, } +/// Check validity and compactness justification set for a proposal. +/// +/// Validity checks whether the set of signed messages is enough to justify +/// the agreement of the proposal by the validators. +/// +/// Compactness enforces that no extraneous messages are included. +/// +/// Provide the proposal, the justification set to check, and a closure for +/// extracting validator IDs from signatures. Should return true only if the +/// signature is valid and the signer was a validator at that time. +pub fn check_justification( + proposal: P, + justification: &[LocalizedMessage], + max_faulty: usize, + check_sig: C, +) -> bool + where + P: Eq, + V: Hash + Eq, + C: Fn(&Message

, &S) -> Option +{ + let mut prepared = HashSet::new(); + + for message in justification { + let signer = match check_sig(&message.message, &message.signature) { + Some(signer) => signer, + None => return false, // compactness. + }; + + if signer != message.sender { return false } + + match message.message { + Message::Prepare(ref p) if p == &proposal => {}, + _ => return false, + }; + + // compactness + if !prepared.insert(signer) { return false } + + if prepared.len() > max_faulty * 2 { return true } + } + + false +} + /// Reach BFT agreement. Input the local proposal, message input stream, message output stream, /// and maximum number of faulty participants. /// @@ -198,7 +243,7 @@ mod tests { let agreement = agree( 100_000, 255, - |_msg| true, + |msg| (msg.clone(), 255), i_rx.map_err(|_| ()), o_tx.sink_map_err(|_| ()), max_faulty, @@ -208,7 +253,7 @@ mod tests { LocalizedMessage { message: Message::Prepare(100_000), sender: i, - signature: true, + signature: (Message::Prepare(100_000), i), } }); @@ -227,6 +272,12 @@ mod tests { .expect("not to fail to agree"); assert_eq!(agreed_value.proposal, 100_000); + assert!(check_justification( + agreed_value.proposal, + &agreed_value.justification, + max_faulty, + |msg, sig| if msg == &sig.0 { Some(sig.1) } else { None } + )); } #[test] @@ -239,7 +290,7 @@ mod tests { let agreement = agree( 100_000, 255, - |_msg| true, + |msg| (msg.clone(), 255), i_rx.map_err(|_| ()), o_tx.sink_map_err(|_| ()), max_faulty, @@ -249,7 +300,7 @@ mod tests { LocalizedMessage { message: Message::Prepare(100_001), sender: i, - signature: true, + signature: (Message::Prepare(100_001), i), } }); @@ -268,6 +319,12 @@ mod tests { .expect("not to fail to agree"); assert_eq!(agreed_value.proposal, 100_001); + assert!(check_justification( + agreed_value.proposal, + &agreed_value.justification, + max_faulty, + |msg, sig| if msg == &sig.0 { Some(sig.1) } else { None } + )); } #[test]