Skip to content

Commit

Permalink
Test the reordering attack on BinaryAgreement using net framework
Browse files Browse the repository at this point in the history
  • Loading branch information
d33a94975ba60d59 committed Oct 16, 2018
1 parent 26e6590 commit 12d2436
Show file tree
Hide file tree
Showing 4 changed files with 241 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/binary_agreement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ use self::bool_set::BoolSet;
use coin::{self, CoinMessage};

pub use self::binary_agreement::BinaryAgreement;
pub use self::sbv_broadcast::Message as SbvMessage;

/// An Binary Agreement error.
#[derive(Clone, Eq, PartialEq, Debug, Fail)]
Expand Down
213 changes: 213 additions & 0 deletions tests/binary_agreement_mitm.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#![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]);
}
}
}
9 changes: 8 additions & 1 deletion tests/net/adversary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
//! `NodeHandle::node()` and `NodeHandle::node_mut()`).
use std::cmp;
use std::collections::VecDeque;

use hbbft::{DistAlgorithm, Step};

Expand Down Expand Up @@ -154,7 +155,7 @@ where
/// Panics if `position` is equal to `Before(idx)`, with `idx` being out of bounds.
#[inline]
pub fn inject_message(&mut self, position: QueuePosition, msg: NetMessage<D>) {
// Ensure the node is not faulty.
// Ensure the source node is faulty.
assert!(
self.0
.get(msg.from.clone())
Expand Down Expand Up @@ -199,6 +200,12 @@ where
{
self.0.sort_messages_by(f)
}

/// Returns a reference to the queue of messages
#[inline]
pub fn get_messages(&self) -> &VecDeque<NetMessage<D>> {
&self.0.messages
}
}

// Downgrade-conversion.
Expand Down
20 changes: 19 additions & 1 deletion tests/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,27 @@ pub struct NetworkMessage<M, N> {
impl<M, N> NetworkMessage<M, N> {
/// Create a new network message.
#[inline]
fn new(from: N, payload: M, to: N) -> NetworkMessage<M, N> {
pub fn new(from: N, payload: M, to: N) -> NetworkMessage<M, N> {
NetworkMessage { from, to, payload }
}

/// Returns the source of the message
#[inline]
pub fn from(&self) -> &N {
&self.from
}

/// Returns the destination of the message
#[inline]
pub fn to(&self) -> &N {
&self.to
}

/// Returns the contents of the message
#[inline]
pub fn payload(&self) -> &M {
&self.payload
}
}

/// Mapping from node IDs to actual node instances.
Expand Down

0 comments on commit 12d2436

Please sign in to comment.