-
Notifications
You must be signed in to change notification settings - Fork 1
Proper threshold support #95
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
base: master
Are you sure you want to change the base?
Changes from all commits
5790889
876cd19
d55f255
246dae7
af6c76e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -19,9 +19,79 @@ use super::{ | |||||
round_id::{RoundId, TransitionInfo}, | ||||||
}; | ||||||
|
||||||
/// Describes what other parties this rounds sends messages to, and what other parties it expects messages from. | ||||||
// TODO: make a trait to implement custom threshold strategies | ||||||
/// A set of IDs with an associated quorum condition. | ||||||
#[derive(Debug, Clone)] | ||||||
pub struct IdSet<Id> { | ||||||
ids: BTreeSet<Id>, | ||||||
threshold: usize, | ||||||
} | ||||||
|
||||||
impl<Id: Ord> IdSet<Id> { | ||||||
/// Creates a non-threshold ID set (that is, messages from all `ids` must be present for the quorum). | ||||||
pub fn new_non_threshold(ids: BTreeSet<Id>) -> Self { | ||||||
let threshold = ids.len(); | ||||||
Self { ids, threshold } | ||||||
} | ||||||
|
||||||
/// Creates an empty ID set. | ||||||
pub fn empty() -> Self { | ||||||
Self { | ||||||
ids: BTreeSet::new(), | ||||||
threshold: 0, | ||||||
} | ||||||
} | ||||||
|
||||||
pub(crate) fn all(&self) -> &BTreeSet<Id> { | ||||||
&self.ids | ||||||
} | ||||||
|
||||||
pub(crate) fn is_quorum(&self, ids: &BTreeSet<Id>) -> bool { | ||||||
// TODO: assuming `ids` are a subset of `self.ids`. Can we? | ||||||
ids.len() >= self.threshold | ||||||
} | ||||||
|
||||||
pub(crate) fn is_quorum_possible(&self, banned_ids: &BTreeSet<Id>) -> bool { | ||||||
let ids = self.ids.intersection(banned_ids).collect::<BTreeSet<_>>(); | ||||||
self.ids.len() - ids.len() >= self.threshold | ||||||
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this work here?
|
||||||
} | ||||||
} | ||||||
|
||||||
/// Encapsulates the communication info for the main round and the possible echo round. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it'd be better to beef up this doc comment and thin out the lower-level ones a bit, so that the doc reader can learn about the |
||||||
#[derive(Debug, Clone)] | ||||||
pub struct CommunicationInfo<Id> { | ||||||
/// Communication info for the main part of the round (that is, not considering the echo round). | ||||||
pub main_round: RoundCommunicationInfo<Id>, | ||||||
|
||||||
/// The specific way the node participates in the echo round following this round. | ||||||
/// | ||||||
/// If `None`, and there is an echo round, will be set to `main_round`. | ||||||
pub echo_round: Option<RoundCommunicationInfo<Id>>, | ||||||
|
||||||
/// The parties whose echoed messages this node expects to receive in the echo round (if any). | ||||||
/// | ||||||
/// If `None`, and there is an echo round, will be set to `echo_round.expecting_messages_from`. | ||||||
pub expected_echos: Option<IdSet<Id>>, | ||||||
} | ||||||
|
||||||
impl<Id: PartyId> CommunicationInfo<Id> { | ||||||
/// A regular round that sends messages to all `other_parties`, and expects messages back from them. | ||||||
pub fn all_to_all(id_set: &IdSet<Id>) -> Self { | ||||||
Self { | ||||||
main_round: RoundCommunicationInfo::all_to_all(id_set), | ||||||
echo_round: None, | ||||||
expected_echos: None, | ||||||
} | ||||||
} | ||||||
|
||||||
pub fn set_to_set(senders: &IdSet<Id>, receivers: &IdSet<Id>, my_id: &Id) -> Self { | ||||||
todo!() | ||||||
} | ||||||
} | ||||||
|
||||||
/// Describes what other parties this rounds sends messages to, and what other parties it expects messages from. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
#[derive(Debug, Clone)] | ||||||
pub struct RoundCommunicationInfo<Id> { | ||||||
/// The destinations of the messages to be sent out by this round. | ||||||
/// | ||||||
/// The way it is interpreted by the execution layer is | ||||||
|
@@ -31,27 +101,26 @@ pub struct CommunicationInfo<Id> { | |||||
/// for each element of the returned set. | ||||||
pub message_destinations: BTreeSet<Id>, | ||||||
|
||||||
/// Returns the set of node IDs from which this round expects messages. | ||||||
/// The set of node IDs from which this round expects messages. | ||||||
/// | ||||||
/// The execution layer will not call [`finalize`](`Round::finalize`) until all these nodes have responded | ||||||
/// (and the corresponding [`receive_message`](`Round::receive_message`) finished successfully). | ||||||
pub expecting_messages_from: 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. | ||||||
pub echo_round_participation: EchoRoundParticipation<Id>, | ||||||
/// The execution layer will not call [`finalize`](`Round::finalize`) until enough nodes to constitute the quorum | ||||||
/// have responded (and the corresponding [`receive_message`](`Round::receive_message`) finished successfully). | ||||||
pub expecting_messages_from: IdSet<Id>, | ||||||
} | ||||||
|
||||||
impl<Id: PartyId> CommunicationInfo<Id> { | ||||||
impl<Id: PartyId> RoundCommunicationInfo<Id> { | ||||||
/// A regular round that sends messages to all `other_parties`, and expects messages back from them. | ||||||
pub fn regular(other_parties: &BTreeSet<Id>) -> Self { | ||||||
pub fn all_to_all(id_set: &IdSet<Id>) -> Self { | ||||||
Self { | ||||||
message_destinations: other_parties.clone(), | ||||||
expecting_messages_from: other_parties.clone(), | ||||||
echo_round_participation: EchoRoundParticipation::Default, | ||||||
message_destinations: id_set.all().clone(), | ||||||
expecting_messages_from: id_set.clone(), | ||||||
} | ||||||
} | ||||||
|
||||||
pub fn none() -> Self { | ||||||
Self { | ||||||
message_destinations: BTreeSet::new(), | ||||||
expecting_messages_from: IdSet::empty(), | ||||||
} | ||||||
} | ||||||
} | ||||||
|
@@ -338,25 +407,6 @@ 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, | ||||||
|
||||||
/// 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>, | ||||||
}, | ||||||
} | ||||||
|
||||||
mod sealed { | ||||||
/// A dyn safe trait to get the type's ID. | ||||||
pub trait DynTypeId: 'static { | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,18 +164,20 @@ where | |
} | ||
|
||
pub(crate) fn new_echo_round_error( | ||
verifier: &SP::Verifier, | ||
normal_broadcast: SignedMessagePart<NormalBroadcast>, | ||
error: EchoRoundError<SP::Verifier>, | ||
) -> Result<Self, LocalError> { | ||
let description = format!("Echo round error: {}", error.description()); | ||
match error { | ||
EchoRoundError::InvalidEcho(from) => Ok(Self { | ||
guilty_party: verifier.clone(), | ||
EchoRoundError::InvalidEcho { | ||
guilty_party, | ||
failed_for, | ||
} => Ok(Self { | ||
guilty_party, | ||
description, | ||
evidence: EvidenceEnum::InvalidEchoPack(InvalidEchoPackEvidence { | ||
normal_broadcast, | ||
invalid_echo_sender: from, | ||
invalid_echo_sender: failed_for, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be renamed to |
||
}), | ||
}), | ||
EchoRoundError::MismatchedBroadcasts { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth making sure I think:
ids.is_subset(&self.ids) && ids.len() >= self.threshold