diff --git a/code/crates/core-types/src/proposal.rs b/code/crates/core-types/src/proposal.rs index 0dfa853c1..024cdf379 100644 --- a/code/crates/core-types/src/proposal.rs +++ b/code/crates/core-types/src/proposal.rs @@ -25,6 +25,9 @@ where /// Address of the validator who issued this proposal fn validator_address(&self) -> &Ctx::Address; + + /// Return a copy of this proposal with a different value. + fn with_value(self, value: Ctx::Value) -> Self; } /// Whether or not a proposal is valid. diff --git a/code/crates/core-types/src/value.rs b/code/crates/core-types/src/value.rs index cc1363c35..ce195d58b 100644 --- a/code/crates/core-types/src/value.rs +++ b/code/crates/core-types/src/value.rs @@ -89,6 +89,10 @@ where /// The ID of the value. fn id(&self) -> Self::Id; + + /// Return a dummy/sentinel value for testing purposes. + /// Must differ from any real value produced by the system. + fn dummy() -> Self; } /// The possible messages used to deliver proposals diff --git a/code/crates/core-types/src/vote.rs b/code/crates/core-types/src/vote.rs index 356acefe2..255d585de 100644 --- a/code/crates/core-types/src/vote.rs +++ b/code/crates/core-types/src/vote.rs @@ -53,4 +53,7 @@ where /// Extend this vote with an extension, overriding any existing extension. fn extend(self, extension: SignedExtension) -> Self; + + /// Return a copy of this vote with a different value. + fn with_value(self, value: NilOrVal<::Id>) -> Self; } diff --git a/code/crates/engine/src/consensus.rs b/code/crates/engine/src/consensus.rs index 0b6a08b6e..2a46d0bdf 100644 --- a/code/crates/engine/src/consensus.rs +++ b/code/crates/engine/src/consensus.rs @@ -19,8 +19,8 @@ use malachitebft_core_consensus::{ Effect, LivenessMsg, PeerId, Resumable, Resume, SignedConsensusMsg, VoteExtensionError, }; use malachitebft_core_types::{ - Context, Proposal, Round, Timeout, TimeoutKind, Timeouts, ValidatorSet, Validity, Value, - ValueId, ValueOrigin, ValueResponse as CoreValueResponse, Vote, + Context, NilOrVal, Proposal, Round, Timeout, TimeoutKind, Timeouts, ValidatorSet, Validity, + Value, ValueId, ValueOrigin, ValueResponse as CoreValueResponse, Vote, }; use malachitebft_metrics::Metrics; use malachitebft_signing::{SigningProvider, SigningProviderExt}; @@ -1140,9 +1140,37 @@ where self.tx_event.send(|| Event::Published(msg.clone())); self.network - .cast(NetworkMsg::PublishConsensusMsg(msg)) + .cast(NetworkMsg::PublishConsensusMsg(msg.clone())) .map_err(|e| eyre!("Error when broadcasting consensus message: {e:?}"))?; + // TESTING ONLY: send conflicting messages to trigger equivocation detection + if std::env::var("TEST_ONLY_MALICIOUS_NODE").as_deref() == Ok("true") { + match &msg { + SignedConsensusMsg::Vote(signed_vote) => { + // Send a conflicting nil vote to trigger vote equivocation + if signed_vote.message.value().is_val() { + let conflicting = signed_vote.message.clone().with_value(NilOrVal::Nil); + if let Ok(signed) = self.signing_provider.sign_vote(conflicting).await { + let _ = self.network.cast(NetworkMsg::PublishConsensusMsg( + SignedConsensusMsg::Vote(signed), + )); + } + } + } + SignedConsensusMsg::Proposal(signed_proposal) => { + // Send a conflicting proposal with a dummy value to trigger + // double proposal detection on honest nodes. + let bad_proposal = signed_proposal.message.clone() + .with_value(<::Value as Value>::dummy()); + if let Ok(signed) = self.signing_provider.sign_proposal(bad_proposal).await { + let _ = self.network.cast(NetworkMsg::PublishConsensusMsg( + SignedConsensusMsg::Proposal(signed), + )); + } + } + } + } + Ok(r.resume_with(())) } diff --git a/code/crates/starknet/p2p-types/src/context/impls.rs b/code/crates/starknet/p2p-types/src/context/impls.rs index f7b0bdb1e..e7f19997e 100644 --- a/code/crates/starknet/p2p-types/src/context/impls.rs +++ b/code/crates/starknet/p2p-types/src/context/impls.rs @@ -41,6 +41,11 @@ impl common::Proposal for Proposal { fn validator_address(&self) -> &Address { &self.proposer } + + fn with_value(mut self, value: Hash) -> Self { + self.value_id = value; + self + } } impl common::Vote for Vote { @@ -79,6 +84,11 @@ impl common::Vote for Vote { fn take_extension(&mut self) -> Option> { None } + + fn with_value(mut self, value: NilOrVal) -> Self { + self.block_hash = value; + self + } } impl common::ValidatorSet for ValidatorSet { diff --git a/code/crates/starknet/p2p-types/src/hash.rs b/code/crates/starknet/p2p-types/src/hash.rs index 79e0d6e6b..55d225cca 100644 --- a/code/crates/starknet/p2p-types/src/hash.rs +++ b/code/crates/starknet/p2p-types/src/hash.rs @@ -18,6 +18,10 @@ impl malachitebft_core_types::Value for BlockHash { fn id(&self) -> Self::Id { *self } + + fn dummy() -> Self { + Self::new([0xBA; 32]) + } } #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/code/crates/test/src/proposal.rs b/code/crates/test/src/proposal.rs index 3079786b9..745436ecd 100644 --- a/code/crates/test/src/proposal.rs +++ b/code/crates/test/src/proposal.rs @@ -64,6 +64,11 @@ impl malachitebft_core_types::Proposal for Proposal { fn validator_address(&self) -> &Address { &self.validator_address } + + fn with_value(mut self, value: Value) -> Self { + self.value = value; + self + } } impl Protobuf for Proposal { diff --git a/code/crates/test/src/value.rs b/code/crates/test/src/value.rs index 8d75a3b18..0c837b033 100644 --- a/code/crates/test/src/value.rs +++ b/code/crates/test/src/value.rs @@ -88,6 +88,10 @@ impl malachitebft_core_types::Value for Value { fn id(&self) -> ValueId { self.id() } + + fn dummy() -> Self { + Self::new(u64::MAX) + } } impl Protobuf for Value { diff --git a/code/crates/test/src/vote.rs b/code/crates/test/src/vote.rs index 0341c28e2..2cbadf8f0 100644 --- a/code/crates/test/src/vote.rs +++ b/code/crates/test/src/vote.rs @@ -104,6 +104,11 @@ impl malachitebft_core_types::Vote for Vote { ..self } } + + fn with_value(mut self, value: NilOrVal) -> Self { + self.value = value; + self + } } impl Protobuf for Vote {