From 842e77283d80658e0c7c612aa5ba22d5cabdccb5 Mon Sep 17 00:00:00 2001 From: Jeongseup Date: Fri, 28 Jul 2023 01:21:12 +0900 Subject: [PATCH 1/4] add test-case for double_vote_1 --- vetomint/tests/test_suite1.rs | 145 +++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 2 deletions(-) diff --git a/vetomint/tests/test_suite1.rs b/vetomint/tests/test_suite1.rs index b9b32c86..882181f7 100644 --- a/vetomint/tests/test_suite1.rs +++ b/vetomint/tests/test_suite1.rs @@ -133,9 +133,150 @@ fn normal_1() { fn lock_1() {} /// A byzantine node broadcasts both nil and non-nil prevotes but fails to break the safety. -#[ignore] #[test] -fn double_votes_1() {} +fn double_votes_1() { + let mut height_info = HeightInfo { + validators: vec![1, 1, 1, 1], + this_node_index: Some(0), + timestamp: 0, + consensus_params: ConsensusParams { + timeout_ms: 100, + repeat_round_for_first_leader: 1, + }, + initial_block_candidate: 0, + }; + + let mut proposer = Vetomint::new(height_info.clone()); + + let mut nodes = Vec::new(); + for i in 1..=2 { + height_info.this_node_index = Some(i); + nodes.push(Vetomint::new(height_info.clone())); + } + + let response = proposer.progress(ConsensusEvent::Start, 0); + assert_eq!( + response, + vec![ + ConsensusResponse::BroadcastProposal { + proposal: 0, + valid_round: None, + round: 0, + }, + ConsensusResponse::BroadcastPrevote { + proposal: Some(0), + round: 0 + } + ] + ); + + for node in nodes.iter_mut() { + let response = node.progress(ConsensusEvent::Start, 0); + assert_eq!(response, vec![]); + } + + for node in nodes.iter_mut() { + let response = node.progress( + ConsensusEvent::BlockProposalReceived { + proposal: 0, + valid: true, + valid_round: None, + proposer: 0, + round: 0, + favor: true, + }, + 1, + ); + assert_eq!( + response, + vec![ConsensusResponse::BroadcastPrevote { + proposal: Some(0), + round: 0, + }] + ); + } + + let mut nodes = vec![vec![proposer], nodes].concat(); + + for (i, node) in nodes.iter_mut().enumerate() { + // byzantine node's will send nil-prevote to node + let response = node.progress( + ConsensusEvent::Prevote { + proposal: None, + signer: 3, // byzantine node index + round: 0, + }, + 2, + ); + assert_eq!(response, Vec::new()); + + // byzantine node's will send prevote to node agiain to break the consensus + let response = node.progress( + ConsensusEvent::Prevote { + proposal: Some(0), + signer: 3, // byzantine node index + round: 0, + }, + 2, + ); + assert_eq!(response, Vec::new()); + + let response = node.progress( + ConsensusEvent::Prevote { + proposal: Some(0), + signer: (i + 1) % 3, + round: 0, + }, + 2, + ); + assert_eq!( + response, + vec![ConsensusResponse::BroadcastPrecommit { + proposal: Some(0), + round: 0, + }] + ); + + let response = node.progress( + ConsensusEvent::Prevote { + proposal: Some(0), + signer: (i + 2) % 3, + round: 0, + }, + 2, + ); + assert_eq!(response, Vec::new()); + } + + for (i, node) in nodes.iter_mut().enumerate() { + let response = node.progress( + ConsensusEvent::Precommit { + proposal: Some(0), + signer: (i + 1) % 3, + round: 0, + }, + 3, + ); + assert_eq!(response, Vec::new()); + let response = node.progress( + ConsensusEvent::Precommit { + proposal: Some(0), + signer: (i + 2) % 3, + round: 0, + }, + 3, + ); + + assert_eq!( + response, + vec![ConsensusResponse::FinalizeBlock { + proposal: 0, + proof: (0..3).collect(), + round: 0 + }] + ); + } +} /// Timeout occurs in the prevote stage, skipping the first round but eventually reaching consensus. #[ignore] From 95bb052d94f3ffe895ca70261410d82206c8e768 Mon Sep 17 00:00:00 2001 From: Junha Yang Date: Wed, 2 Aug 2023 23:24:13 +0900 Subject: [PATCH 2/4] Introduce concrete misbehavior enum --- consensus/src/state.rs | 9 +++++-- vetomint/src/lib.rs | 55 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/consensus/src/state.rs b/consensus/src/state.rs index d2b8816b..4b7ae4a8 100644 --- a/consensus/src/state.rs +++ b/consensus/src/state.rs @@ -358,7 +358,7 @@ impl State { } ConsensusResponse::ViolationReport { violator, - description, + misbehavior, } => { let pubkey = self .block_header @@ -368,7 +368,12 @@ impl State { .0 .clone(); ( - ProgressResult::ViolationReported(pubkey, description, timestamp), + // TODO: add misbehavior handling + ProgressResult::ViolationReported( + pubkey, + format!("{misbehavior:?}"), + timestamp, + ), None, ) } diff --git a/vetomint/src/lib.rs b/vetomint/src/lib.rs index 4b406779..f1d89a41 100644 --- a/vetomint/src/lib.rs +++ b/vetomint/src/lib.rs @@ -61,6 +61,59 @@ pub enum ConsensusEvent { Timer, } +/// The report and trace of a misbehavior committed by a malicious node. +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] +pub enum Misbehavior { + DoubleProposal { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The two conflicting proposals. + proposals: (BlockIdentifier, BlockIdentifier), + }, + DoublePrevote { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The two conflicting proposals that the node has prevoted. + proposals: (Option, Option), + }, + DoublePrecommit { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The two conflicting proposals that the node has precommitted. + proposals: (Option, Option), + }, + InvalidProposal { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The proposal that the node has proposed. + proposal: BlockIdentifier, + }, + InvalidPrevote { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The proposal that the node has prevoted. + proposal: BlockIdentifier, + }, + InvalidPrecommit { + /// The malicious node. + byzantine_node: ValidatorIndex, + /// The round in which the misbehavior is committed. + round: Round, + /// The proposal that the node has precommitted. + proposal: BlockIdentifier, + }, +} + /// A response that the consensus might emit for a given event, which must be properly handled by the lower layer. #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub enum ConsensusResponse { @@ -84,7 +137,7 @@ pub enum ConsensusResponse { }, ViolationReport { violator: ValidatorIndex, - description: String, + misbehavior: Misbehavior, }, } From faf9f11e038c926a94e137913b21556f8afb3b18 Mon Sep 17 00:00:00 2001 From: Jeongseup Date: Sun, 6 Aug 2023 17:55:09 +0900 Subject: [PATCH 3/4] add misbehavior.rs --- vetomint/src/misbehavior.rs | 98 +++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 vetomint/src/misbehavior.rs diff --git a/vetomint/src/misbehavior.rs b/vetomint/src/misbehavior.rs new file mode 100644 index 00000000..20384811 --- /dev/null +++ b/vetomint/src/misbehavior.rs @@ -0,0 +1,98 @@ +use super::*; +use state::*; + +use std::collections::HashMap; + +pub(crate) fn check_misbehavior( + state: &ConsensusState, + check_round: Round, + check_proposal: BlockIdentifier, +) -> Vec { + let mut misbehavior: Vec = vec![]; + + if let Some(m) = check_double_proposals(state, check_round) { + misbehavior.push(m); + } else { + println!("not found double proposals in this round"); + } + + if let Some(m) = check_double_prevote(state, check_round, check_proposal) { + misbehavior.push(m); + } else { + println!("not found double prevote in this round"); + } + + if let Some(m) = check_double_precommit(state, check_round, check_proposal) { + misbehavior.push(m); + } else { + println!("not found double precommit in this round"); + } + + misbehavior +} + +fn check_double_proposals(state: &ConsensusState, check_round: Round) -> Option { + let proposals: Vec<_> = state + .proposals + .iter() + .filter(|(_, proposal)| proposal.round == check_round) + .map(|(_, proposal)| proposal) + .collect(); + + if proposals.len() > 1 { + // returnSome(result[0].1.proposer) + return Some(Misbehavior::DoubleProposal { + byzantine_node: proposals[0].proposer, + round: check_round, + proposals: (proposals[0].proposal, proposals[1].proposal), + }); + } + + None +} + +fn check_double_prevote( + state: &ConsensusState, + check_round: Round, + check_proposal: BlockIdentifier, +) -> Option { + let mut validators_map = HashMap::new(); + + for vote in state.prevotes.iter() { + let count = validators_map.entry(vote.signer).or_insert(0); + *count += 1; + + if *count == 2 { + return Some(Misbehavior::DoublePrevote { + byzantine_node: vote.signer, + round: check_round, + proposals: (Some(check_proposal), Some(check_proposal)), + }); + } + } + + None +} + +fn check_double_precommit( + state: &ConsensusState, + check_round: Round, + check_proposal: BlockIdentifier, +) -> Option { + let mut validators_map = HashMap::new(); + + for vote in state.precommits.iter() { + let count = validators_map.entry(vote.signer).or_insert(0); + *count += 1; + + if *count == 2 { + return Some(Misbehavior::DoublePrecommit { + byzantine_node: vote.signer, + round: check_round, + proposals: (Some(check_proposal), Some(check_proposal)), + }); + } + } + + None +} From 4804cbd1dab1fc4ced15d1ea6da0c29faf70dd64 Mon Sep 17 00:00:00 2001 From: Jeongseup Date: Sat, 12 Aug 2023 00:50:51 +0900 Subject: [PATCH 4/4] update misbehavior --- vetomint/src/misbehavior.rs | 87 +++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/vetomint/src/misbehavior.rs b/vetomint/src/misbehavior.rs index 20384811..a7a59f41 100644 --- a/vetomint/src/misbehavior.rs +++ b/vetomint/src/misbehavior.rs @@ -28,6 +28,24 @@ pub(crate) fn check_misbehavior( println!("not found double precommit in this round"); } + if let Some(m) = check_invalid_proposal(state, check_proposal) { + misbehavior.push(m); + } else { + println!("not found invalid proposal in this round"); + } + + if let Some(m) = check_invalid_prevote(state, check_round, check_proposal) { + misbehavior.push(m); + } else { + println!("not found double precommit in this round"); + } + + if let Some(m) = check_invalid_precommit(state, check_round, check_proposal) { + misbehavior.push(m); + } else { + println!("not found double precommit in this round"); + } + misbehavior } @@ -96,3 +114,72 @@ fn check_double_precommit( None } + +fn check_invalid_proposal( + state: &ConsensusState, + check_proposal: BlockIdentifier, +) -> Option { + if let Some(proposal) = state.proposals.get(&check_proposal) { + if proposal.valid == false { + return Some(Misbehavior::InvalidProposal { + byzantine_node: proposal.proposer, + round: proposal.round, + proposal: proposal.proposal, + }); + } + } + + None +} + +fn check_invalid_prevote( + state: &ConsensusState, + check_round: Round, + check_proposal: BlockIdentifier, +) -> Option { + let valid_prevotes: Vec<_> = state + .prevotes + .iter() + .filter(|prevote| prevote.round == check_round) + .collect(); + + for prevote in valid_prevotes.iter() { + if let Some(proposal) = prevote.proposal { + if proposal == check_proposal { + return Some(Misbehavior::InvalidPrevote { + byzantine_node: prevote.signer, + round: prevote.round, + proposal: proposal, + }); + } + } + } + + None +} + +fn check_invalid_precommit( + state: &ConsensusState, + check_round: Round, + check_proposal: BlockIdentifier, +) -> Option { + let valid_precommits: Vec<_> = state + .precommits + .iter() + .filter(|prevote| prevote.round == check_round) + .collect(); + + for precommit in valid_precommits.iter() { + if let Some(proposal) = precommit.proposal { + if proposal == check_proposal { + return Some(Misbehavior::InvalidPrecommit { + byzantine_node: precommit.signer, + round: precommit.round, + proposal: proposal, + }); + } + } + } + + None +}