Skip to content

Commit

Permalink
Add a test for the reordering attack for BinaryAgreement
Browse files Browse the repository at this point in the history
  • Loading branch information
d33a94975ba60d59 committed Oct 16, 2018
1 parent 6df72d8 commit 3ad7a8d
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/binary_agreement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
mod binary_agreement;
mod bool_multimap;
pub mod bool_set;
mod sbv_broadcast;
pub mod sbv_broadcast;

use rand;

Expand Down
135 changes: 127 additions & 8 deletions tests/binary_agreement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,139 @@ extern crate threshold_crypto as crypto;

mod network;

use std::collections::BTreeMap;
use std::iter::once;
use std::sync::Arc;

use rand::Rng;

use hbbft::binary_agreement::BinaryAgreement;
use hbbft::NetworkInfo;
use hbbft::binary_agreement::sbv_broadcast;
use hbbft::binary_agreement::{BinaryAgreement, Message, MessageContent};
use hbbft::{NetworkInfo, Target, TargetedMessage};

use network::{Adversary, MessageScheduler, NodeId, SilentAdversary, TestNetwork, TestNode};
use network::{
Adversary, MessageScheduler, MessageWithSender, NodeId, SilentAdversary, TestNetwork, TestNode,
};

/// 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)]
pub struct ReorderingAdversary {
/// Node ids seen by the adversary, but not under control of the adversary.
known_node_ids: Vec<NodeId>,
/// Node ids under control of adversary.
known_adversarial_ids: Vec<NodeId>,
/// The most recent epoch we've seen.
current_epoch: u32,
/// If we've sent the one-time messages for the attack.
sent_attack: bool,
}

impl Adversary<BinaryAgreement<NodeId>> for ReorderingAdversary {
fn init(
&mut self,
all_nodes: &BTreeMap<NodeId, TestNode<BinaryAgreement<NodeId>>>,
nodes: &BTreeMap<NodeId, Arc<NetworkInfo<NodeId>>>,
) {
self.known_adversarial_ids = nodes.keys().cloned().collect();
self.known_node_ids = all_nodes.keys().cloned().collect();
}

fn pick_node(&self, nodes: &BTreeMap<NodeId, TestNode<BinaryAgreement<NodeId>>>) -> NodeId {
// Prioritizes A0, as required by the attack.
MessageScheduler::First.pick_node(nodes)
}

fn push_message(&mut self, _: NodeId, msg: TargetedMessage<Message, NodeId>) {
if self.current_epoch < msg.message.epoch {
self.current_epoch = msg.message.epoch;
// Reset for the next epoch.
self.sent_attack = false;
}
}

fn step(&mut self) -> Vec<MessageWithSender<BinaryAgreement<NodeId>>> {
let mut messages = Vec::new();
if !self.sent_attack {
self.sent_attack = true;
for &our_node_id in &self.known_adversarial_ids {
let f = self.known_node_ids.len() / 3;
for &target in &self.known_node_ids[0..f] {
// Disagree with the first third.
messages.push(MessageWithSender::new(
our_node_id,
TargetedMessage {
target: Target::Node(target),
message: Message {
epoch: self.current_epoch,
content: MessageContent::SbvBroadcast(
sbv_broadcast::Message::BVal(true),
),
},
},
));
}
for &target in &self.known_node_ids[f..(f * 2)] {
// Affirm the second third.
messages.push(MessageWithSender::new(
our_node_id,
TargetedMessage {
target: Target::Node(target),
message: Message {
epoch: self.current_epoch,
content: MessageContent::SbvBroadcast(
sbv_broadcast::Message::BVal(false),
),
},
},
));
}
}
}
messages
}
}

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum InputConfig {
/// Always input this bool.
Fixed(bool),
/// Input a random bool.
Random,
/// Deterministically split the network.
/// Input false to the first 2/3, and true to the rest.
/// This is used to test the reordering attack.
Split,
}

fn test_binary_agreement<A: Adversary<BinaryAgreement<NodeId>>>(
mut network: TestNetwork<A, BinaryAgreement<NodeId>>,
input: Option<bool>,
input: InputConfig,
) {
// The borrow checker gets in our way here, so we clone the node ids.
let ids: Vec<NodeId> = network.nodes.keys().cloned().collect();
for id in ids {
network.input(id, input.unwrap_or_else(rand::random));
for (i, id) in ids.into_iter().enumerate() {
let value = match input {
InputConfig::Fixed(b) => b,
InputConfig::Random => rand::random(),
InputConfig::Split => i < (2 * network.nodes.len() / 3),
};
network.input(id, value);
}

// Handle messages in random order until all nodes have output the proposed value.
let mut steps: usize = 0;
while !network.nodes.values().all(TestNode::terminated) {
network.step();
steps += 1;
assert!(steps < 20000);
}
// Verify that all instances output the same value.
let mut expected = input;
let mut expected = match input {
InputConfig::Fixed(b) => Some(b),
_ => None,
};
for node in network.nodes.values() {
if let Some(b) = expected {
assert!(once(&b).eq(node.outputs()));
Expand All @@ -78,7 +186,12 @@ where
for size in sizes {
let num_faulty_nodes = (size - 1) / 3;
let num_good_nodes = size - num_faulty_nodes;
for &input in &[None, Some(false), Some(true)] {
for &input in &[
InputConfig::Fixed(false),
InputConfig::Fixed(true),
InputConfig::Random,
InputConfig::Split,
] {
info!(
"Test start: {} good nodes and {} faulty nodes, input: {:?}",
num_good_nodes, num_faulty_nodes, input
Expand Down Expand Up @@ -108,3 +221,9 @@ fn test_binary_agreement_first_silent() {
let new_adversary = |_: usize, _: usize| SilentAdversary::new(MessageScheduler::First);
test_binary_agreement_different_sizes(new_adversary);
}

#[test]
fn test_binary_agreement_reordering() {
let new_adversary = |_: usize, _: usize| ReorderingAdversary::default();
test_binary_agreement_different_sizes(new_adversary);
}

0 comments on commit 3ad7a8d

Please sign in to comment.