mirror of https://github.com/poanetwork/hbbft.git
fixed broadcast failure with malicious value proposal
This commit is contained in:
parent
0f3377c8e9
commit
babbd2f36a
|
@ -59,7 +59,9 @@ struct BroadcastState {
|
|||
/// Reliable Broadcast algorithm instance.
|
||||
pub struct Broadcast<NodeUid: Eq + Hash> {
|
||||
/// The UID of this node.
|
||||
uid: NodeUid,
|
||||
our_id: NodeUid,
|
||||
/// The UID of the sending node.
|
||||
proposer_id: NodeUid,
|
||||
/// UIDs of all nodes for iteration purposes.
|
||||
all_uids: HashSet<NodeUid>,
|
||||
num_nodes: usize,
|
||||
|
@ -112,7 +114,7 @@ impl Broadcast<messaging::NodeUid> {
|
|||
if let Some(value) = output {
|
||||
tx.send(QMessage::Local(LocalMessage {
|
||||
dst: Algorithm::CommonSubset,
|
||||
message: AlgoMessage::BroadcastOutput(self.uid, value),
|
||||
message: AlgoMessage::BroadcastOutput(self.our_id, value),
|
||||
})).map_err(Error::from)?;
|
||||
}
|
||||
Ok(MessageLoopState::Processing(
|
||||
|
@ -162,14 +164,22 @@ where
|
|||
}
|
||||
|
||||
impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
||||
pub fn new(uid: NodeUid, all_uids: HashSet<NodeUid>, num_nodes: usize) -> Result<Self, Error> {
|
||||
/// Creates a new broadcast instance to be used by node `our_id` which expects a value proposal
|
||||
/// from node `proposer_id`.
|
||||
pub fn new(
|
||||
our_id: NodeUid,
|
||||
proposer_id: NodeUid,
|
||||
all_uids: HashSet<NodeUid>,
|
||||
) -> Result<Self, Error> {
|
||||
let num_nodes = all_uids.len();
|
||||
let num_faulty_nodes = (num_nodes - 1) / 3;
|
||||
let parity_shard_num = 2 * num_faulty_nodes;
|
||||
let data_shard_num = num_nodes - parity_shard_num;
|
||||
let coding = ReedSolomon::new(data_shard_num, parity_shard_num)?;
|
||||
|
||||
Ok(Broadcast {
|
||||
uid,
|
||||
our_id,
|
||||
proposer_id,
|
||||
all_uids,
|
||||
num_nodes,
|
||||
num_faulty_nodes,
|
||||
|
@ -190,6 +200,9 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
|
||||
/// Processes the proposed value input by broadcasting it.
|
||||
pub fn propose_value(&self, value: ProposedValue) -> Result<MessageQueue<NodeUid>, Error> {
|
||||
if self.our_id != self.proposer_id {
|
||||
return Err(Error::UnexpectedMessage);
|
||||
}
|
||||
let mut state = self.state.write().unwrap();
|
||||
// Split the value into chunks/shards, encode them with erasure codes.
|
||||
// Assemble a Merkle tree from data and parity shards. Take all proofs
|
||||
|
@ -212,8 +225,8 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn uid(&self) -> &NodeUid {
|
||||
&self.uid
|
||||
pub fn our_id(&self) -> &NodeUid {
|
||||
&self.our_id
|
||||
}
|
||||
|
||||
/// Breaks the input value into shards of equal length and encodes them --
|
||||
|
@ -280,7 +293,7 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
for (leaf_value, uid) in mtree.iter().zip(self.all_uids.clone()) {
|
||||
let proof = mtree.gen_proof(leaf_value.to_vec());
|
||||
if let Some(proof) = proof {
|
||||
if uid == self.uid {
|
||||
if uid == self.our_id {
|
||||
// The proof is addressed to this node.
|
||||
result = Ok(proof);
|
||||
} else {
|
||||
|
@ -299,12 +312,12 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
/// Handler of messages received from remote nodes.
|
||||
pub fn handle_broadcast_message(
|
||||
&self,
|
||||
_uid: &NodeUid, // TODO: Remove or clarify: this should probably be the sender's ID.
|
||||
sender_id: &NodeUid,
|
||||
message: &BroadcastMessage<ProposedValue>,
|
||||
) -> Result<(Option<ProposedValue>, MessageQueue<NodeUid>), Error> {
|
||||
let state = self.state.write().unwrap();
|
||||
match message {
|
||||
BroadcastMessage::Value(p) => self.handle_value(p, state),
|
||||
BroadcastMessage::Value(p) => self.handle_value(sender_id, p, state),
|
||||
BroadcastMessage::Echo(p) => self.handle_echo(p, state),
|
||||
BroadcastMessage::Ready(ref hash) => self.handle_ready(hash, state),
|
||||
}
|
||||
|
@ -313,15 +326,19 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
/// Handles a received echo and verifies the proof it contains.
|
||||
fn handle_value(
|
||||
&self,
|
||||
sender_id: &NodeUid,
|
||||
p: &Proof<ProposedValue>,
|
||||
mut state: RwLockWriteGuard<BroadcastState>,
|
||||
) -> Result<(Option<ProposedValue>, MessageQueue<NodeUid>), Error> {
|
||||
if *sender_id != self.proposer_id {
|
||||
return Ok((None, VecDeque::new()));
|
||||
}
|
||||
// Initialize the root hash if not already initialised.
|
||||
if state.root_hash.is_none() {
|
||||
state.root_hash = Some(p.root_hash.clone());
|
||||
debug!(
|
||||
"Node {:?} Value root hash {:?}",
|
||||
self.uid,
|
||||
self.our_id,
|
||||
HexBytes(&p.root_hash)
|
||||
);
|
||||
}
|
||||
|
@ -352,19 +369,22 @@ impl<NodeUid: Eq + Hash + Debug + Clone> Broadcast<NodeUid> {
|
|||
) -> Result<(Option<ProposedValue>, MessageQueue<NodeUid>), Error> {
|
||||
if state.root_hash.is_none() {
|
||||
state.root_hash = Some(p.root_hash.clone());
|
||||
debug!("Node {:?} Echo root hash {:?}", self.uid, state.root_hash);
|
||||
debug!(
|
||||
"Node {:?} Echo root hash {:?}",
|
||||
self.our_id, state.root_hash
|
||||
);
|
||||
}
|
||||
|
||||
// Call validate with the root hash as argument.
|
||||
let h = if let Some(h) = state.root_hash.clone() {
|
||||
h
|
||||
} else {
|
||||
error!("Broadcast/{:?} root hash not initialised", self.uid);
|
||||
error!("Broadcast/{:?} root hash not initialised", self.our_id);
|
||||
return Ok((None, VecDeque::new()));
|
||||
};
|
||||
|
||||
if !p.validate(h.as_slice()) {
|
||||
debug!("Broadcast/{:?} cannot validate Echo {:?}", self.uid, p);
|
||||
debug!("Broadcast/{:?} cannot validate Echo {:?}", self.our_id, p);
|
||||
return Ok((None, VecDeque::new()));
|
||||
}
|
||||
|
||||
|
|
|
@ -48,10 +48,10 @@ impl<NodeUid: Clone + Debug + Display + Eq + Hash + Ord> CommonSubset<NodeUid> {
|
|||
let mut broadcast_instances: HashMap<NodeUid, Broadcast<NodeUid>> =
|
||||
HashMap::new();
|
||||
for uid0 in all_uids {
|
||||
broadcast_instances.insert(uid0.clone(),
|
||||
Broadcast::new(uid0.clone(),
|
||||
all_uids.clone(),
|
||||
num_nodes)?);
|
||||
broadcast_instances.insert(
|
||||
uid0.clone(),
|
||||
Broadcast::new(uid.clone(), uid0.clone(), all_uids.clone())?,
|
||||
);
|
||||
}
|
||||
|
||||
// Create all agreement instances.
|
||||
|
|
|
@ -11,6 +11,7 @@ extern crate simple_logger;
|
|||
|
||||
use rand::Rng;
|
||||
use std::collections::{BTreeMap, BTreeSet, HashSet, VecDeque};
|
||||
use std::fmt;
|
||||
|
||||
use hbbft::broadcast::{Broadcast, BroadcastTarget, TargetedBroadcastMessage};
|
||||
use hbbft::messaging::ProposedValue;
|
||||
|
@ -37,7 +38,7 @@ impl TestNode {
|
|||
/// Creates a new test node with the given broadcast instance.
|
||||
fn new(broadcast: Broadcast<NodeId>) -> TestNode {
|
||||
TestNode {
|
||||
id: *broadcast.uid(),
|
||||
id: *broadcast.our_id(),
|
||||
broadcast,
|
||||
queue: VecDeque::new(),
|
||||
outputs: Vec::new(),
|
||||
|
@ -49,7 +50,7 @@ impl TestNode {
|
|||
let (from_id, msg) = self.queue.pop_front().expect("message not found");
|
||||
debug!("Handling {:?} -> {:?}: {:?}", from_id, self.id, msg);
|
||||
let (output, msgs) = self.broadcast
|
||||
.handle_broadcast_message(&self.id, &msg)
|
||||
.handle_broadcast_message(&from_id, &msg)
|
||||
.expect("handling message");
|
||||
if let Some(output) = output.clone() {
|
||||
self.outputs.push(output);
|
||||
|
@ -127,6 +128,57 @@ impl Adversary for SilentAdversary {
|
|||
}
|
||||
}
|
||||
|
||||
/// An adversary that proposes an alternate value.
|
||||
struct ProposeAdversary {
|
||||
scheduler: MessageScheduler,
|
||||
good_nodes: BTreeSet<NodeId>,
|
||||
adv_nodes: BTreeSet<NodeId>,
|
||||
has_sent: bool,
|
||||
}
|
||||
|
||||
impl ProposeAdversary {
|
||||
/// Creates a new replay adversary with the given message scheduler.
|
||||
fn new(
|
||||
scheduler: MessageScheduler,
|
||||
good_nodes: BTreeSet<NodeId>,
|
||||
adv_nodes: BTreeSet<NodeId>,
|
||||
) -> ProposeAdversary {
|
||||
ProposeAdversary {
|
||||
scheduler,
|
||||
good_nodes,
|
||||
adv_nodes,
|
||||
has_sent: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Adversary for ProposeAdversary {
|
||||
fn pick_node(&self, nodes: &BTreeMap<NodeId, TestNode>) -> NodeId {
|
||||
self.scheduler.pick_node(nodes)
|
||||
}
|
||||
|
||||
fn push_message(&mut self, _: NodeId, _: TargetedBroadcastMessage<NodeId>) {
|
||||
// All messages are ignored.
|
||||
}
|
||||
|
||||
fn step(&mut self) -> Vec<(NodeId, TargetedBroadcastMessage<NodeId>)> {
|
||||
if self.has_sent {
|
||||
return vec![];
|
||||
}
|
||||
self.has_sent = true;
|
||||
let value = b"Fake news";
|
||||
let node_ids: HashSet<NodeId> = self.adv_nodes
|
||||
.iter()
|
||||
.cloned()
|
||||
.chain(self.good_nodes.iter().cloned())
|
||||
.collect();
|
||||
let id = *self.adv_nodes.iter().next().unwrap();
|
||||
let bc = Broadcast::new(id, id, node_ids).expect("broadcast instance");
|
||||
let msgs = bc.propose_value(value.to_vec()).expect("propose");
|
||||
msgs.into_iter().map(|msg| (id, msg)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of `TestNode`s representing a network.
|
||||
struct TestNetwork<A: Adversary> {
|
||||
nodes: BTreeMap<NodeId, TestNode>,
|
||||
|
@ -140,19 +192,27 @@ impl<A: Adversary> TestNetwork<A> {
|
|||
fn new(good_num: usize, adv_num: usize, adversary: A) -> TestNetwork<A> {
|
||||
let node_ids: HashSet<NodeId> = (0..(good_num + adv_num)).map(NodeId).collect();
|
||||
let new_broadcast = |id: NodeId| {
|
||||
let bc = Broadcast::new(id, node_ids.clone(), node_ids.len())
|
||||
.expect("Instantiate broadcast");
|
||||
let bc =
|
||||
Broadcast::new(id, NodeId(0), node_ids.clone()).expect("Instantiate broadcast");
|
||||
(id, TestNode::new(bc))
|
||||
};
|
||||
TestNetwork {
|
||||
let mut network = TestNetwork {
|
||||
nodes: (0..good_num).map(NodeId).map(new_broadcast).collect(),
|
||||
adversary,
|
||||
adv_nodes: (good_num..(good_num + adv_num)).map(NodeId).collect(),
|
||||
};
|
||||
let msgs = network.adversary.step();
|
||||
for (sender_id, msg) in msgs {
|
||||
network.dispatch_messages(sender_id, vec![msg]);
|
||||
}
|
||||
network
|
||||
}
|
||||
|
||||
/// Pushes the messages into the queues of the corresponding recipients.
|
||||
fn dispatch_messages(&mut self, sender_id: NodeId, msgs: MessageQueue) {
|
||||
fn dispatch_messages<Q>(&mut self, sender_id: NodeId, msgs: Q)
|
||||
where
|
||||
Q: IntoIterator<Item = TargetedBroadcastMessage<NodeId>> + fmt::Debug,
|
||||
{
|
||||
debug!("Sending: {:?}", msgs);
|
||||
for msg in msgs {
|
||||
match msg {
|
||||
|
@ -188,7 +248,10 @@ impl<A: Adversary> TestNetwork<A> {
|
|||
/// Handles a queued message in a randomly selected node and returns the selected node's ID and
|
||||
/// its output value, if any.
|
||||
fn step(&mut self) -> (NodeId, Option<ProposedValue>) {
|
||||
// TODO: `self.adversary.step()`
|
||||
let msgs = self.adversary.step();
|
||||
for (sender_id, msg) in msgs {
|
||||
self.dispatch_messages(sender_id, Some(msg));
|
||||
}
|
||||
// Pick a random non-idle node..
|
||||
let id = self.adversary.pick_node(&self.nodes);
|
||||
let (output, msgs) = self.nodes.get_mut(&id).unwrap().handle_message();
|
||||
|
@ -237,3 +300,19 @@ fn test_11_5_broadcast_nodes_first_delivery() {
|
|||
let adversary = SilentAdversary::new(MessageScheduler::First);
|
||||
test_broadcast(TestNetwork::new(11, 5, adversary));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_11_5_broadcast_nodes_random_delivery_adv_propose() {
|
||||
let good_nodes: BTreeSet<NodeId> = (0..11).map(NodeId).collect();
|
||||
let adv_nodes: BTreeSet<NodeId> = (11..16).map(NodeId).collect();
|
||||
let adversary = ProposeAdversary::new(MessageScheduler::Random, good_nodes, adv_nodes);
|
||||
test_broadcast(TestNetwork::new(11, 5, adversary));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_11_5_broadcast_nodes_first_delivery_adv_propose() {
|
||||
let good_nodes: BTreeSet<NodeId> = (0..11).map(NodeId).collect();
|
||||
let adv_nodes: BTreeSet<NodeId> = (11..16).map(NodeId).collect();
|
||||
let adversary = ProposeAdversary::new(MessageScheduler::First, good_nodes, adv_nodes);
|
||||
test_broadcast(TestNetwork::new(11, 5, adversary));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue