Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions code/crates/test/framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,10 +362,6 @@ where
Ok(HandlerResult::ContinueTest) => {
break 'inner;
}
Ok(HandlerResult::SleepAndContinueTest(duration)) => {
sleep(duration).await;
break 'inner;
}
Err(e) => {
error!("Event handler returned an error: {e}");

Expand Down
1 change: 0 additions & 1 deletion code/crates/test/framework/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ where
pub enum HandlerResult {
WaitForNextEvent,
ContinueTest,
SleepAndContinueTest(Duration),
}

pub type EventHandler<Ctx, S> =
Expand Down
74 changes: 36 additions & 38 deletions code/crates/test/tests/it/equivocation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ use malachitebft_core_consensus::MisbehaviorEvidence;
use malachitebft_core_types::{Context, Proposal, Vote};
use malachitebft_test_framework::{HandlerResult, TestParams};

use crate::middlewares::PrevoteRandom;
use crate::TestBuilder;

const VOTE_DURATION: Duration = Duration::from_millis(300);
const TARGET_TIME: Duration = Duration::from_secs(1);

#[allow(clippy::never_loop)]
fn check_decided_impl<Ctx: Context>(evidence: &MisbehaviorEvidence<Ctx>) {
Expand All @@ -27,36 +28,28 @@ fn check_decided_impl<Ctx: Context>(evidence: &MisbehaviorEvidence<Ctx>) {
}
}

// Verifies that sharing a validator key across two nodes
// induces equivocation and that decide-time `MisbehaviorEvidence`
// contains double proposals and double prevotes for the equivocator.
// Node 3 checks for proposal equivocation evidence.
#[tokio::test]
#[ignore] // Flaky test
pub async fn equivocation_two_vals_same_pk_proposal() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn equivocation_two_vals_same_pk_proposal() {
pub async fn equivocation_two_vals_same_key_proposal() {

// Nodes 1 and 2 share a validator key to induce proposal equivocation
// Nodes 1 and 2 share a validator key to induce proposal equivocation.
let params = TestParams {
shared_key_group: HashSet::from([1, 2]),
target_time: Some(TARGET_TIME),
..Default::default()
};
let mut test = TestBuilder::<()>::new();

// Node 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Node 1
// Node 1 - Byzantine

test.add_node()
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.success();
test.add_node().start().success();

// Node 2 (same validator key as node 1)
test.add_node()
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.success();
// Node 2 (same validator key as node 1).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Node 2 (same validator key as node 1).
// Node 2 - Byzantine: same validator key as node 1

test.add_node().start().success();

// Node 3 -- checking proposal equivocation evidence
// Node 3 -- checking proposal equivocation evidence.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Node 3 -- checking proposal equivocation evidence.
// Node 3: correct, with >2/3 of the total voting power.
// Checks for proposal equivocation.

// Voting power is set to hold >2/3 worth of VP so consensus always progresses.
// Proposals are processed regardless of consensus state.
test.add_node()
.with_voting_power(5)
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.on_finalized(|_c, evidence, _s| {
check_decided_impl(&evidence);
let result = if evidence.proposals.is_empty() {
Expand All @@ -73,48 +66,53 @@ pub async fn equivocation_two_vals_same_pk_proposal() {
.await;
}

// Verifies that sharing a validator key across two nodes
// induces equivocation and that decide-time `MisbehaviorEvidence`
// contains double proposals and double prevotes for the equivocator.
// Node 3 checks for vote equivocation evidence.
/// Vote equivocation test with 7 nodes.
///
/// Nodes 1 and 2 share validator key. Node 2 uses `PrevoteRandom`, node 1 votes normally.
///
/// Need five honest nodes (3-7) so that
/// * equivocator holds < 1/3 of total VP
/// * no single honest node has >2/3 of total VP, so needs to collect votes from others
#[tokio::test]
#[ignore] // Flaky test
pub async fn equivocation_two_vals_same_pk_vote() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub async fn equivocation_two_vals_same_pk_vote() {
pub async fn equivocation_two_vals_same_key_vote() {

// Nodes 1 and 2 share a validator key to induce vote equivocation
// Nodes 1 and 2 share a validator key to induce vote equivocation.
let params = TestParams {
shared_key_group: HashSet::from([1, 2]),
target_time: Some(TARGET_TIME),
..Default::default()
};
let mut test = TestBuilder::<()>::new();

// Node 1
test.add_node()
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.success();
// Node 1 votes normally.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Node 1 votes normally.
// Node 1 - Byzantine

test.add_node().start().success();

// Node 2 (same validator key as node 1)
// Node 2 (same validator key as node 1) prevotes for random values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Node 2 (same validator key as node 1) prevotes for random values.
// Node 2 - Byzantine: same validator key as node 1 and prevotes for random values.

test.add_node()
.with_middleware(PrevoteRandom)
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.success();

// Node 3 -- checking vote equivocation evidence
// Nodes 3 to 6 (honest)
for _ in 0..4 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for _ in 0..4 {
for _ in 3..=6 {

test.add_node().start().success();
}

// Node 7 (honest) checks vote equivocation evidence.
test.add_node()
.start()
.on_vote(|_v, _s| Ok(HandlerResult::SleepAndContinueTest(VOTE_DURATION)))
.on_finalized(|_c, evidence, _s| {
.on_finalized(move |_c, evidence, _s| {
check_decided_impl(&evidence);
let result = if evidence.votes.is_empty() {
HandlerResult::WaitForNextEvent
} else {
let has_vote_equivocation = !evidence.votes.is_empty();
let result = if has_vote_equivocation {
HandlerResult::ContinueTest
} else {
HandlerResult::WaitForNextEvent
};
Ok(result)
})
.success();

test.build()
.run_with_params(Duration::from_secs(15), params)
.run_with_params(Duration::from_secs(30), params)
.await;
}
20 changes: 20 additions & 0 deletions code/crates/test/tests/it/middlewares.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,23 @@ impl Middleware for PrevoteNil {
}
}
}

#[derive(Copy, Clone, Debug)]
pub struct PrevoteRandom;

impl Middleware for PrevoteRandom {
fn new_prevote(
&self,
_ctx: &TestContext,
height: Height,
round: Round,
_value_id: NilOrVal<ValueId>,
address: Address,
) -> Vote {
use rand::Rng;
let random = NilOrVal::Val(ValueId::new(
rand::thread_rng().gen_range(100_000..=999_999),
));
Vote::new_prevote(height, round, random, address)
}
}