diff --git a/crates/hotshot/src/tasks/mod.rs b/crates/hotshot/src/tasks/mod.rs index dbaa964bc8..a28d32d260 100644 --- a/crates/hotshot/src/tasks/mod.rs +++ b/crates/hotshot/src/tasks/mod.rs @@ -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, @@ -364,6 +366,83 @@ impl> EventTransformerState> { + /// store events from previous views + cached_events: Vec>, + /// phantom + _phantom: std::marker::PhantomData, +} + +/// add trait to handle proposal events for a dishonest leader +trait DishonestProposal> { + /// send dishonest proposal + fn handle_proposal_send_event( + &self, + event: &HotShotEvent, + proposal: &Proposal>, + sender: &TYPES::SignatureKey, + ) -> Vec>; +} + +impl> Default for DishonestLeader { + /// 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> DishonestProposal + for DishonestLeader +{ + fn handle_proposal_send_event( + &self, + event: &HotShotEvent, + proposal: &Proposal>, + sender: &TYPES::SignatureKey, + ) -> Vec> { + 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 + std::fmt::Debug> + EventTransformerState for DishonestLeader +{ + async fn recv_handler(&mut self, event: &HotShotEvent) -> Vec> { + vec![event.clone()] + } + + async fn send_handler(&mut self, event: &HotShotEvent) -> Vec> { + 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>( handle: &mut SystemContextHandle, diff --git a/crates/testing/tests/tests_1/test_with_failures_2.rs b/crates/testing/tests/tests_1/test_with_failures_2.rs index cb9acd9dc5..66251e1c92 100644 --- a/crates/testing/tests/tests_1/test_with_failures_2.rs +++ b/crates/testing/tests/tests_1/test_with_failures_2.rs @@ -1,6 +1,5 @@ // TODO: Remove this after integration #![allow(unused_imports)] - use hotshot_example_types::{ node_types::{Libp2pImpl, MemoryImpl, PushCdnImpl}, state_types::TestTypes, @@ -8,9 +7,14 @@ use hotshot_example_types::{ 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!( @@ -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::::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 + }, +);