Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Logic fixes for rounds with partial echo #67

Merged
merged 2 commits into from
Nov 18, 2024
Merged
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion clippy.toml

This file was deleted.

2 changes: 2 additions & 0 deletions manul/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ serde_json = { version = "1", default-features = false, features = ["alloc"], op

[dev-dependencies]
impls = "1"
rand_core = { version = "0.6.4", default-features = false, features = ["getrandom"] }
rand = { version = "0.8", default-features = false }
serde_asn1_der = "0.8"
criterion = "0.5"
serde-persistent-deserializer = "0.3"
postcard = { version = "1", default-features = false, features = ["alloc"] }
serde_json = { version = "1", default-features = false, features = ["alloc"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

[features]
testing = ["rand", "postcard", "serde_json", "serde-persistent-deserializer"]
Expand Down
21 changes: 14 additions & 7 deletions manul/src/combinators/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,20 @@ where
}
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
match &self.state {
ChainState::Protocol1 { round, .. } => round.as_ref().expecting_messages_from(),
ChainState::Protocol2(round) => round.as_ref().expecting_messages_from(),
}
}

fn echo_round_participation(&self) -> EchoRoundParticipation<Id> {
match &self.state {
ChainState::Protocol1 { round, .. } => round.as_ref().echo_round_participation(),
ChainState::Protocol2(round) => round.as_ref().echo_round_participation(),
}
}

fn make_direct_message(
&self,
rng: &mut dyn CryptoRngCore,
Expand Down Expand Up @@ -514,11 +528,4 @@ where
},
}
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
match &self.state {
ChainState::Protocol1 { round, .. } => round.as_ref().expecting_messages_from(),
ChainState::Protocol2(round) => round.as_ref().expecting_messages_from(),
}
}
}
17 changes: 11 additions & 6 deletions manul/src/combinators/misbehave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ use core::fmt::Debug;
use rand_core::CryptoRngCore;

use crate::protocol::{
Artifact, BoxedRng, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EntryPoint, FinalizeError,
FinalizeOutcome, LocalError, NormalBroadcast, ObjectSafeRound, PartyId, Payload, ReceiveError, RoundId, Serializer,
Artifact, BoxedRng, BoxedRound, Deserializer, DirectMessage, EchoBroadcast, EchoRoundParticipation, EntryPoint,
FinalizeError, FinalizeOutcome, LocalError, NormalBroadcast, ObjectSafeRound, PartyId, Payload, ReceiveError,
RoundId, Serializer,
};

/// A trait describing required properties for a behavior type.
Expand Down Expand Up @@ -173,6 +174,14 @@ where
self.round.as_ref().message_destinations()
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
self.round.as_ref().expecting_messages_from()
}

fn echo_round_participation(&self) -> EchoRoundParticipation<Id> {
self.round.as_ref().echo_round_participation()
}

fn make_direct_message(
&self,
rng: &mut dyn CryptoRngCore,
Expand Down Expand Up @@ -284,8 +293,4 @@ where
Err(err) => Err(err),
}
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
self.round.as_ref().expecting_messages_from()
}
}
6 changes: 4 additions & 2 deletions manul/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
#![doc = include_str!("../../README.md")]
#![warn(
clippy::mod_module_files,
clippy::unwrap_used,
clippy::indexing_slicing,
missing_docs,
missing_copy_implementations,
rust_2018_idioms,
Expand All @@ -13,6 +11,7 @@
unused_qualifications,
missing_debug_implementations
)]
#![cfg_attr(not(test), warn(clippy::unwrap_used, clippy::indexing_slicing,))]

extern crate alloc;

Expand All @@ -23,3 +22,6 @@ pub(crate) mod utils;

#[cfg(any(test, feature = "testing"))]
pub mod testing;

#[cfg(test)]
mod tests;
3 changes: 2 additions & 1 deletion manul/src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ pub use errors::{
pub use message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessagePart};
pub use object_safe::BoxedRound;
pub use round::{
Artifact, CorrectnessProof, EntryPoint, FinalizeOutcome, PartyId, Payload, Protocol, ProtocolError, Round, RoundId,
Artifact, CorrectnessProof, EchoRoundParticipation, EntryPoint, FinalizeOutcome, PartyId, Payload, Protocol,
ProtocolError, Round, RoundId,
};
pub use serialization::{Deserializer, Serializer};

Expand Down
20 changes: 13 additions & 7 deletions manul/src/protocol/object_safe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use rand_core::{CryptoRng, CryptoRngCore, RngCore};
use super::{
errors::{FinalizeError, LocalError, ReceiveError},
message::{DirectMessage, EchoBroadcast, NormalBroadcast},
round::{Artifact, FinalizeOutcome, PartyId, Payload, Protocol, Round, RoundId},
round::{Artifact, EchoRoundParticipation, FinalizeOutcome, PartyId, Payload, Protocol, Round, RoundId},
serialization::{Deserializer, Serializer},
};

Expand Down Expand Up @@ -48,6 +48,10 @@ pub(crate) trait ObjectSafeRound<Id: PartyId>: 'static + Debug + Send + Sync {

fn message_destinations(&self) -> &BTreeSet<Id>;

fn expecting_messages_from(&self) -> &BTreeSet<Id>;

fn echo_round_participation(&self) -> EchoRoundParticipation<Id>;

fn make_direct_message(
&self,
rng: &mut dyn CryptoRngCore,
Expand Down Expand Up @@ -87,8 +91,6 @@ pub(crate) trait ObjectSafeRound<Id: PartyId>: 'static + Debug + Send + Sync {
artifacts: BTreeMap<Id, Artifact>,
) -> Result<FinalizeOutcome<Id, Self::Protocol>, FinalizeError<Self::Protocol>>;

fn expecting_messages_from(&self) -> &BTreeSet<Id>;

/// Returns the type ID of the implementing type.
fn get_type_id(&self) -> core::any::TypeId {
core::any::TypeId::of::<Self>()
Expand Down Expand Up @@ -135,6 +137,14 @@ where
self.round.message_destinations()
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
self.round.expecting_messages_from()
}

fn echo_round_participation(&self) -> EchoRoundParticipation<Id> {
self.round.echo_round_participation()
}

fn make_direct_message(
&self,
rng: &mut dyn CryptoRngCore,
Expand Down Expand Up @@ -195,10 +205,6 @@ where
let mut boxed_rng = BoxedRng(rng);
self.round.finalize(&mut boxed_rng, payloads, artifacts)
}

fn expecting_messages_from(&self) -> &BTreeSet<Id> {
self.round.expecting_messages_from()
}
}

// We do not want to expose `ObjectSafeRound` to the user, so it is hidden in a struct.
Expand Down
40 changes: 34 additions & 6 deletions manul/src/protocol/round.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,25 @@ pub trait PartyId: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for

impl<T> PartyId for T where T: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> {}

/// The specific way the node participates in the echo round (if any).
#[derive(Debug, Clone)]
pub enum EchoRoundParticipation<Id> {
/// The default behavior: sends broadcasts and receives echoed messages, or does neither.
///
/// That is, this node will be a part of the echo round if [`Round::make_echo_broadcast`] generates a message.
Default,
Copy link
Contributor

Choose a reason for hiding this comment

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

Would Full be a good name here? It doesn't fully capture the semantics but is at least a hint in the right direction whereas "default" doesn't mean anything unless the reader knows what the default is.

Copy link
Member Author

Choose a reason for hiding this comment

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

Funny you should say that, Full was the original name I had here :) But I changed it to Default because it also covers the nodes which don't send or receive anything (node 4 in the test), where I wasn't sure Full was intuitive.

Copy link
Contributor

Choose a reason for hiding this comment

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

Full isn't perfect, but gives a hint to the right understanding. I like it better than Default.

We could go with a FullOrAbsent but that reads very confusingly. :)


/// This node sends broadcasts that will be echoed, but does not receive any.
Send,

/// This node receives broadcasts that it needs to echo, but does not send any itself.
Receive {
/// The other participants of the echo round
/// (that is, the nodes to which echoed messages will be sent).
echo_targets: BTreeSet<Id>,
},
}

/**
A type representing a single round of a protocol.

Expand Down Expand Up @@ -401,6 +420,21 @@ pub trait Round<Id: PartyId>: 'static + Debug + Send + Sync {
/// for each element of the returned set.
fn message_destinations(&self) -> &BTreeSet<Id>;

/// Returns the set of node IDs from which this round expects messages.
///
/// The execution layer will not call [`finalize`](`Self::finalize`) until all these nodes have responded
/// (and the corresponding [`receive_message`](`Self::receive_message`) finished successfully).
fn expecting_messages_from(&self) -> &BTreeSet<Id>;

/// Returns the specific way the node participates in the echo round following this round.
///
/// Returns [`EchoRoundParticipation::Default`] by default; this works fine when every node
/// sends messages to every other one, or do not send or receive any echo broadcasts.
/// Otherwise, review the options in [`EchoRoundParticipation`] and pick the appropriate one.
fn echo_round_participation(&self) -> EchoRoundParticipation<Id> {
EchoRoundParticipation::Default
}

/// Returns the direct message to the given destination and (maybe) an accompanying artifact.
///
/// Return [`DirectMessage::none`] if this round does not send direct messages.
Expand Down Expand Up @@ -473,10 +507,4 @@ pub trait Round<Id: PartyId>: 'static + Debug + Send + Sync {
payloads: BTreeMap<Id, Payload>,
artifacts: BTreeMap<Id, Artifact>,
) -> Result<FinalizeOutcome<Id, Self::Protocol>, FinalizeError<Self::Protocol>>;

/// Returns the set of node IDs from which this round expects messages.
///
/// The execution layer will not call [`finalize`](`Self::finalize`) until all these nodes have responded
/// (and the corresponding [`receive_message`](`Self::receive_message`) finished successfully).
fn expecting_messages_from(&self) -> &BTreeSet<Id>;
}
35 changes: 13 additions & 22 deletions manul/src/session/echo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use tracing::debug;

use super::{
message::{MessageVerificationError, SignedMessagePart},
session::SessionParameters,
session::{EchoRoundInfo, SessionParameters},
LocalError,
};
use crate::{
Expand Down Expand Up @@ -77,8 +77,7 @@ pub(crate) struct EchoRoundMessage<SP: SessionParameters> {
pub struct EchoRound<P: Protocol, SP: SessionParameters> {
verifier: SP::Verifier,
echo_broadcasts: BTreeMap<SP::Verifier, SignedMessagePart<EchoBroadcast>>,
destinations: BTreeSet<SP::Verifier>,
expected_echos: BTreeSet<SP::Verifier>,
echo_round_info: EchoRoundInfo<SP::Verifier>,
main_round: BoxedRound<SP::Verifier, P>,
payloads: BTreeMap<SP::Verifier, Payload>,
artifacts: BTreeMap<SP::Verifier, Artifact>,
Expand All @@ -93,25 +92,19 @@ where
verifier: SP::Verifier,
my_echo_broadcast: SignedMessagePart<EchoBroadcast>,
echo_broadcasts: BTreeMap<SP::Verifier, SignedMessagePart<EchoBroadcast>>,
echo_round_info: EchoRoundInfo<SP::Verifier>,
main_round: BoxedRound<SP::Verifier, P>,
payloads: BTreeMap<SP::Verifier, Payload>,
artifacts: BTreeMap<SP::Verifier, Artifact>,
) -> Self {
let destinations = echo_broadcasts.keys().cloned().collect::<BTreeSet<_>>();

// Add our own echo message because we expect it to be sent back from other nodes.
let mut expected_echos = destinations.clone();
expected_echos.insert(verifier.clone());

let mut echo_broadcasts = echo_broadcasts;
echo_broadcasts.insert(verifier.clone(), my_echo_broadcast);

debug!("{:?}: initialized echo round with {:?}", verifier, destinations);
debug!("{:?}: initialized echo round with {:?}", verifier, echo_round_info);
Self {
verifier,
echo_broadcasts,
destinations,
expected_echos,
echo_round_info,
main_round,
payloads,
artifacts,
Expand Down Expand Up @@ -154,7 +147,7 @@ where
}

fn message_destinations(&self) -> &BTreeSet<SP::Verifier> {
&self.destinations
&self.echo_round_info.message_destinations
}

fn make_normal_broadcast(
Expand All @@ -180,7 +173,7 @@ where
}

fn expecting_messages_from(&self) -> &BTreeSet<SP::Verifier> {
&self.destinations
&self.echo_round_info.expecting_messages_from
}

fn receive_message(
Expand All @@ -199,16 +192,14 @@ where

let message = normal_broadcast.deserialize::<EchoRoundMessage<SP>>(deserializer)?;

// Check that the received message contains entries from `destinations` sans `from`
// Check that the received message contains entries from `expected_echos`.
// It is an unprovable fault.

let mut expected_keys = self.expected_echos.clone();
if !expected_keys.remove(from) {
return Err(ReceiveError::local(format!(
"The message sender {from:?} is missing from the expected senders {:?}",
self.destinations
)));
}
let mut expected_keys = self.echo_round_info.expected_echos.clone();

// We don't expect the node to send its echo the second time.
expected_keys.remove(from);

let message_keys = message.echo_broadcasts.keys().cloned().collect::<BTreeSet<_>>();

let missing_keys = expected_keys.difference(&message_keys).collect::<Vec<_>>();
Expand Down
Loading
Loading