Skip to content

Commit

Permalink
add a test case where we have dishonest leader
Browse files Browse the repository at this point in the history
  • Loading branch information
lukeiannucci committed Jul 30, 2024
1 parent 50decdc commit 19a2ef3
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 1 deletion.
79 changes: 79 additions & 0 deletions crates/hotshot/src/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ use hotshot_task_impls::{
};
use hotshot_types::{
constants::EVENT_CHANNEL_SIZE,
data::QuorumProposal,
message::Proposal,
message::{Messages, VersionedMessage},
traits::{
network::ConnectedNetwork,
Expand Down Expand Up @@ -364,6 +366,83 @@ impl<TYPES: NodeType, I: NodeImplementation<TYPES>> EventTransformerState<TYPES,
}
}

#[derive(Debug)]
/// An `EventHandlerState` that modifies justify_qc to that of previous view to mock dishonest leader
pub struct DishonestLeader<TYPES: NodeType, I: NodeImplementation<TYPES>> {
/// store events from previous views
cached_events: Vec<HotShotEvent<TYPES>>,
/// phantom
_phantom: std::marker::PhantomData<I>,
}

/// add trait to handle proposal events for a dishonest leader
trait DishonestProposal<TYPES: NodeType, I: NodeImplementation<TYPES>> {
/// send dishonest proposal
fn handle_proposal_send_event(
&self,
event: &HotShotEvent<TYPES>,
proposal: &Proposal<TYPES, QuorumProposal<TYPES>>,
sender: &TYPES::SignatureKey,
) -> Vec<HotShotEvent<TYPES>>;
}

impl<TYPES: NodeType, I: NodeImplementation<TYPES>> Default for DishonestLeader<TYPES, I> {
/// create a dishonest leader that stores past events to try a modify a view when they are the leader
fn default() -> Self {
DishonestLeader {
cached_events: Vec::new(),
_phantom: std::marker::PhantomData,
}
}
}

impl<TYPES: NodeType, I: NodeImplementation<TYPES>> DishonestProposal<TYPES, I>
for DishonestLeader<TYPES, I>
{
fn handle_proposal_send_event(
&self,
event: &HotShotEvent<TYPES>,
proposal: &Proposal<TYPES, QuorumProposal<TYPES>>,
sender: &TYPES::SignatureKey,
) -> Vec<HotShotEvent<TYPES>> {
let dishonest = self.cached_events.len() == 2;
if !dishonest {
return vec![event.clone()];
}

match self.cached_events.first().unwrap().clone() {
HotShotEvent::QuorumProposalSend(cached_proposal, _cached_sender) => {
let mut dishonest_proposal = proposal.clone();
dishonest_proposal.data.justify_qc = cached_proposal.data.justify_qc;

let dishonest_proposal_event =
HotShotEvent::QuorumProposalSend(dishonest_proposal, sender.clone());
vec![dishonest_proposal_event]
}
_ => vec![event.clone()],
}
}
}

#[async_trait]
impl<TYPES: NodeType, I: NodeImplementation<TYPES> + std::fmt::Debug>
EventTransformerState<TYPES, I> for DishonestLeader<TYPES, I>
{
async fn recv_handler(&mut self, event: &HotShotEvent<TYPES>) -> Vec<HotShotEvent<TYPES>> {
vec![event.clone()]
}

async fn send_handler(&mut self, event: &HotShotEvent<TYPES>) -> Vec<HotShotEvent<TYPES>> {
match event {
HotShotEvent::QuorumProposalSend(proposal, sender) => {
self.cached_events.push(event.clone());
self.handle_proposal_send_event(event, proposal, sender)
}
_ => vec![event.clone()],
}
}
}

/// adds tasks for sending/receiving messages to/from the network.
pub async fn add_network_tasks<TYPES: NodeType, I: NodeImplementation<TYPES>>(
handle: &mut SystemContextHandle<TYPES, I>,
Expand Down
37 changes: 36 additions & 1 deletion crates/testing/tests/tests_1/test_with_failures_2.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
// TODO: Remove this after integration
#![allow(unused_imports)]

use hotshot_example_types::{
node_types::{Libp2pImpl, MemoryImpl, PushCdnImpl},
state_types::TestTypes,
};
use hotshot_macros::cross_tests;
use hotshot_testing::{
block_builder::SimpleBuilderImplementation,
completion_task::{CompletionTaskDescription, TimeBasedCompletionTaskDescription},
spinning_task::{ChangeNode, SpinningTaskDescription, UpDown},
test_builder::TestDescription,
};
use std::time::Duration;

#[cfg(async_executor_impl = "async-std")]
use {hotshot::tasks::DishonestLeader, hotshot_testing::test_builder::Behaviour, std::rc::Rc};
// Test that a good leader can succeed in the view directly after view sync
#[cfg(not(feature = "dependency-tasks"))]
cross_tests!(
Expand Down Expand Up @@ -52,3 +56,34 @@ cross_tests!(
metadata
}
);

#[cfg(async_executor_impl = "async-std")]
cross_tests!(
TestName: dishonest_leader,
Impls: [MemoryImpl],
Types: [TestTypes],
Ignore: false,
Metadata: {
let behaviour = Rc::new(|node_id| {
let dishonest_leader = DishonestLeader::<TestTypes, MemoryImpl>::default();
match node_id {
2 => Behaviour::Byzantine(Box::new(dishonest_leader)),
_ => Behaviour::Standard,
}
});

let mut metadata = TestDescription {
// allow more time to pass in CI
completion_task_description: CompletionTaskDescription::TimeBasedCompletionTaskBuilder(
TimeBasedCompletionTaskDescription {
duration: Duration::from_secs(60),
},
),
behaviour,
..TestDescription::default()
};

metadata.overall_safety_properties.num_failed_views = 1;
metadata
},
);

0 comments on commit 19a2ef3

Please sign in to comment.