-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test the reordering attack on BinaryAgreement using net framework
- Loading branch information
1 parent
26e6590
commit 26352ef
Showing
4 changed files
with
237 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
#![deny(unused_must_use)] | ||
//! Tests the BinaryAgreement protocol with a MTIM adversary. | ||
extern crate env_logger; | ||
extern crate failure; | ||
extern crate hbbft; | ||
extern crate integer_sqrt; | ||
extern crate proptest; | ||
extern crate rand; | ||
extern crate threshold_crypto; | ||
|
||
pub mod net; | ||
|
||
use std::iter; | ||
use std::sync::Arc; | ||
|
||
use hbbft::binary_agreement::{BinaryAgreement, Message, MessageContent, SbvMessage}; | ||
use hbbft::{DistAlgorithm, Step}; | ||
|
||
use net::adversary::{NetMutHandle, QueuePosition}; | ||
use net::err::CrankError; | ||
use net::{Adversary, NetBuilder, NetMessage}; | ||
|
||
type NodeId = usize; | ||
|
||
#[derive(Debug, PartialEq, Eq, Clone, Copy)] | ||
enum ReorderingStage { | ||
DeliverBBVal, | ||
DeliverA0BVal, | ||
DeliverA0Aux, | ||
Finished, | ||
} | ||
|
||
impl Default for ReorderingStage { | ||
fn default() -> Self { | ||
ReorderingStage::DeliverBBVal | ||
} | ||
} | ||
|
||
/// An adversary for the reordering attack. | ||
/// Described here: https://github.com/amiller/HoneyBadgerBFT/issues/59#issue-310368284 | ||
/// A0 is the first third of nodes, A1 is the second third, and the rest are B. | ||
#[derive(Default)] | ||
struct ReorderingAdversary { | ||
stage: ReorderingStage, | ||
stage_progress: usize, | ||
sent_initial_messages: bool, | ||
sent_final_messages: bool, | ||
} | ||
|
||
const NODES_PER_GROUP: usize = 10; | ||
const NUM_NODES: usize = (NODES_PER_GROUP * 3 + 1); | ||
|
||
impl ReorderingAdversary { | ||
fn message_priority(&self, message: &NetMessage<BinaryAgreement<NodeId>>) -> i16 { | ||
let src = *message.from(); | ||
let src_group = if src == 0 { | ||
// This is the faulty node. | ||
// We always prioritize our own messages. | ||
return 10; | ||
} else { | ||
(src - 1) / NODES_PER_GROUP | ||
}; | ||
let dst = *message.to(); | ||
let dst_group = if dst == 0 { | ||
3 | ||
} else { | ||
(src - 1) / NODES_PER_GROUP | ||
}; | ||
match message.payload().content { | ||
MessageContent::SbvBroadcast(ref message) => match message { | ||
SbvMessage::BVal(v) => match self.stage { | ||
ReorderingStage::DeliverBBVal if src_group == 2 => 1, | ||
ReorderingStage::DeliverA0BVal if src_group == 0 => { | ||
let wanted_dst_group = if *v { 0 } else { 1 }; | ||
if wanted_dst_group == dst_group { | ||
1 | ||
} else { | ||
-1 | ||
} | ||
} | ||
_ => 0, | ||
}, | ||
SbvMessage::Aux(_) => match self.stage { | ||
ReorderingStage::DeliverA0Aux if src_group == 0 => 1, | ||
_ => 0, | ||
}, | ||
}, | ||
_ => 0, | ||
} | ||
} | ||
} | ||
|
||
impl Adversary<BinaryAgreement<NodeId>> for ReorderingAdversary { | ||
fn pre_crank(&mut self, mut net: NetMutHandle<BinaryAgreement<NodeId>>) { | ||
if self.stage != ReorderingStage::Finished { | ||
net.sort_messages_by(|a, b| self.message_priority(b).cmp(&self.message_priority(a))); | ||
if let Some(msg) = net.get_messages().front() { | ||
if self.message_priority(msg) == 1 { | ||
self.stage_progress += 1; | ||
// Each message should be sent by each member of the group to every other node. | ||
let stage_messages = match self.stage { | ||
ReorderingStage::DeliverBBVal => NODES_PER_GROUP * (NUM_NODES - 1), | ||
ReorderingStage::DeliverA0BVal => NODES_PER_GROUP * NODES_PER_GROUP, | ||
ReorderingStage::DeliverA0Aux => NODES_PER_GROUP * (NUM_NODES - 1), | ||
ReorderingStage::Finished => unreachable!(), | ||
}; | ||
if self.stage_progress >= stage_messages { | ||
self.stage = match self.stage { | ||
ReorderingStage::DeliverBBVal => ReorderingStage::DeliverA0BVal, | ||
ReorderingStage::DeliverA0BVal => ReorderingStage::DeliverA0Aux, | ||
ReorderingStage::DeliverA0Aux => ReorderingStage::Finished, | ||
ReorderingStage::Finished => unreachable!(), | ||
}; | ||
self.stage_progress = 0; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn tamper( | ||
&mut self, | ||
mut net: NetMutHandle<BinaryAgreement<NodeId>>, | ||
_: NetMessage<BinaryAgreement<NodeId>>, | ||
) -> Result<Step<BinaryAgreement<NodeId>>, CrankError<BinaryAgreement<NodeId>>> { | ||
const BVAL_FALSE_MSG: Message = Message { | ||
epoch: 0, | ||
content: MessageContent::SbvBroadcast(SbvMessage::BVal(false)), | ||
}; | ||
const BVAL_TRUE_MSG: Message = Message { | ||
epoch: 0, | ||
content: MessageContent::SbvBroadcast(SbvMessage::BVal(true)), | ||
}; | ||
if !self.sent_initial_messages { | ||
self.sent_initial_messages = true; | ||
for target in 1..(1 + NODES_PER_GROUP) { | ||
// Disagree with the nodes in A0. | ||
net.inject_message( | ||
QueuePosition::Front, | ||
NetMessage::<BinaryAgreement<NodeId>>::new(0, BVAL_TRUE_MSG.clone(), target), | ||
); | ||
} | ||
for target in (1 + NODES_PER_GROUP)..(1 + NODES_PER_GROUP * 2) { | ||
// Agree with the nodes in A1. | ||
net.inject_message( | ||
QueuePosition::Front, | ||
NetMessage::<BinaryAgreement<NodeId>>::new(0, BVAL_FALSE_MSG.clone(), target), | ||
); | ||
} | ||
} else if self.stage == ReorderingStage::Finished && !self.sent_final_messages { | ||
self.sent_final_messages = true; | ||
for target in 1..(1 + NODES_PER_GROUP * 2) { | ||
// Both agree and disgree with nodes in A. | ||
net.inject_message( | ||
QueuePosition::Front, | ||
NetMessage::<BinaryAgreement<NodeId>>::new(0, BVAL_FALSE_MSG.clone(), target), | ||
); | ||
net.inject_message( | ||
QueuePosition::Front, | ||
NetMessage::<BinaryAgreement<NodeId>>::new(0, BVAL_TRUE_MSG.clone(), target), | ||
); | ||
} | ||
} | ||
Ok(Step::default()) | ||
} | ||
} | ||
|
||
#[test] | ||
fn reordering_attack() { | ||
let _ = env_logger::try_init(); | ||
let ids: Vec<NodeId> = (0..NUM_NODES).collect(); | ||
let mut net = NetBuilder::new(ids.iter().cloned()) | ||
.adversary(ReorderingAdversary::default()) | ||
.crank_limit(10000) | ||
.using(|info| { | ||
BinaryAgreement::new(Arc::new(info.netinfo), 0, info.id) | ||
.expect("failed to create BinaryAgreement instance") | ||
}).num_faulty(1) | ||
.build() | ||
.unwrap(); | ||
|
||
for id in ids { | ||
if id == 0 { | ||
// This is the faulty node. | ||
} else if id < (1 + NODES_PER_GROUP * 2) { | ||
// Group A | ||
let _ = net.send_input(id, false).unwrap(); | ||
} else { | ||
// Group B | ||
let _ = net.send_input(id, true).unwrap(); | ||
} | ||
} | ||
|
||
while !net.nodes().skip(1).all(|n| n.algorithm().terminated()) { | ||
net.crank_expect(); | ||
} | ||
|
||
// Verify that all instances output the same value. | ||
let mut expected = None; | ||
for node in net.nodes().skip(1) { | ||
if let Some(b) = expected { | ||
assert!(iter::once(&b).eq(node.outputs())); | ||
} else { | ||
assert_eq!(1, node.outputs().len()); | ||
expected = Some(node.outputs()[0]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters