manul/protocol/round.rs
1use alloc::{
2 boxed::Box,
3 collections::{BTreeMap, BTreeSet},
4 format,
5};
6use core::{
7 any::Any,
8 fmt::{Debug, Display},
9};
10
11use rand_core::CryptoRngCore;
12use serde::{Deserialize, Serialize};
13
14use super::{
15 boxed_format::BoxedFormat,
16 boxed_round::BoxedRound,
17 errors::{LocalError, MessageValidationError, ProtocolValidationError, ReceiveError},
18 message::{DirectMessage, EchoBroadcast, NormalBroadcast, ProtocolMessage, ProtocolMessagePart},
19 round_id::{RoundId, TransitionInfo},
20};
21
22/// Describes what other parties this rounds sends messages to, and what other parties it expects messages from.
23#[derive(Debug, Clone)]
24pub struct CommunicationInfo<Id> {
25 /// The destinations of the messages to be sent out by this round.
26 ///
27 /// The way it is interpreted by the execution layer is
28 /// - An echo broadcast (if any) is sent to all of these destinations;
29 /// - A direct message is sent to each of these destinations,
30 /// which means [`make_direct_message`](`Round::make_direct_message`) may be called
31 /// for each element of the returned set.
32 pub message_destinations: BTreeSet<Id>,
33
34 /// Returns the set of node IDs from which this round expects messages.
35 ///
36 /// The execution layer will not call [`finalize`](`Round::finalize`) until all these nodes have responded
37 /// (and the corresponding [`receive_message`](`Round::receive_message`) finished successfully).
38 pub expecting_messages_from: BTreeSet<Id>,
39
40 /// Returns the specific way the node participates in the echo round following this round.
41 ///
42 /// Returns [`EchoRoundParticipation::Default`] by default; this works fine when every node
43 /// sends messages to every other one, or do not send or receive any echo broadcasts.
44 /// Otherwise, review the options in [`EchoRoundParticipation`] and pick the appropriate one.
45 pub echo_round_participation: EchoRoundParticipation<Id>,
46}
47
48impl<Id: PartyId> CommunicationInfo<Id> {
49 /// A regular round that sends messages to all `other_parties`, and expects messages back from them.
50 pub fn regular(other_parties: &BTreeSet<Id>) -> Self {
51 Self {
52 message_destinations: other_parties.clone(),
53 expecting_messages_from: other_parties.clone(),
54 echo_round_participation: EchoRoundParticipation::Default,
55 }
56 }
57}
58
59/// Possible successful outcomes of [`Round::finalize`].
60#[derive(Debug)]
61pub enum FinalizeOutcome<Id: PartyId, P: Protocol<Id>> {
62 /// Transition to a new round.
63 AnotherRound(BoxedRound<Id, P>),
64 /// The protocol reached a result.
65 Result(P::Result),
66}
67
68/// A distributed protocol.
69pub trait Protocol<Id>: 'static {
70 /// The successful result of an execution of this protocol.
71 type Result: Debug;
72
73 /// An object of this type will be returned when a provable error happens during [`Round::receive_message`].
74 type ProtocolError: ProtocolError<Id>;
75
76 /// Returns `Ok(())` if the given direct message cannot be deserialized
77 /// assuming it is a direct message from the round `round_id`.
78 ///
79 /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
80 /// when implementing this.
81 fn verify_direct_message_is_invalid(
82 format: &BoxedFormat,
83 round_id: &RoundId,
84 message: &DirectMessage,
85 ) -> Result<(), MessageValidationError>;
86
87 /// Returns `Ok(())` if the given echo broadcast cannot be deserialized
88 /// assuming it is an echo broadcast from the round `round_id`.
89 ///
90 /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
91 /// when implementing this.
92 fn verify_echo_broadcast_is_invalid(
93 format: &BoxedFormat,
94 round_id: &RoundId,
95 message: &EchoBroadcast,
96 ) -> Result<(), MessageValidationError>;
97
98 /// Returns `Ok(())` if the given echo broadcast cannot be deserialized
99 /// assuming it is an echo broadcast from the round `round_id`.
100 ///
101 /// Normally one would use [`ProtocolMessagePart::verify_is_not`] and [`ProtocolMessagePart::verify_is_some`]
102 /// when implementing this.
103 fn verify_normal_broadcast_is_invalid(
104 format: &BoxedFormat,
105 round_id: &RoundId,
106 message: &NormalBroadcast,
107 ) -> Result<(), MessageValidationError>;
108}
109
110/// Declares which parts of the message from a round have to be stored to serve as the evidence of malicious behavior.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct RequiredMessageParts {
113 pub(crate) echo_broadcast: bool,
114 pub(crate) normal_broadcast: bool,
115 pub(crate) direct_message: bool,
116}
117
118impl RequiredMessageParts {
119 fn new(echo_broadcast: bool, normal_broadcast: bool, direct_message: bool) -> Self {
120 // We must require at least one part, otherwise this struct doesn't need to be created.
121 debug_assert!(echo_broadcast || normal_broadcast || direct_message);
122 Self {
123 echo_broadcast,
124 normal_broadcast,
125 direct_message,
126 }
127 }
128
129 /// Store echo broadcast
130 pub fn echo_broadcast() -> Self {
131 Self::new(true, false, false)
132 }
133
134 /// Store normal broadcast
135 pub fn normal_broadcast() -> Self {
136 Self::new(false, true, false)
137 }
138
139 /// Store direct message
140 pub fn direct_message() -> Self {
141 Self::new(false, false, true)
142 }
143
144 /// Store echo broadcast in addition to what is already stored.
145 pub fn and_echo_broadcast(&self) -> Self {
146 Self::new(true, self.normal_broadcast, self.direct_message)
147 }
148
149 /// Store normal broadcast in addition to what is already stored.
150 pub fn and_normal_broadcast(&self) -> Self {
151 Self::new(self.echo_broadcast, true, self.direct_message)
152 }
153
154 /// Store direct message in addition to what is already stored.
155 pub fn and_direct_message(&self) -> Self {
156 Self::new(self.echo_broadcast, self.normal_broadcast, true)
157 }
158}
159
160/// Declares which messages from this and previous rounds
161/// have to be stored to serve as the evidence of malicious behavior.
162#[derive(Debug, Clone, PartialEq, Eq)]
163pub struct RequiredMessages {
164 pub(crate) this_round: RequiredMessageParts,
165 pub(crate) previous_rounds: Option<BTreeMap<RoundId, RequiredMessageParts>>,
166 pub(crate) combined_echos: Option<BTreeSet<RoundId>>,
167}
168
169impl RequiredMessages {
170 /// The general case constructor.
171 ///
172 /// `this_round` specifies the message parts to be stored from the message that triggered the error.
173 ///
174 /// `previous_rounds` specifies, optionally, if any message parts from the previous rounds need to be included.
175 ///
176 /// `combined_echos` specifies, optionally, if any echoed broadcasts need to be included.
177 /// The combined echos are echo broadcasts sent by a party during the echo round,
178 /// where it bundles all the received broadcasts and sends them back to everyone.
179 /// That is, they will include the echo broadcasts from all other nodes signed by the guilty party.
180 pub fn new(
181 this_round: RequiredMessageParts,
182 previous_rounds: Option<BTreeMap<RoundId, RequiredMessageParts>>,
183 combined_echos: Option<BTreeSet<RoundId>>,
184 ) -> Self {
185 Self {
186 this_round,
187 previous_rounds,
188 combined_echos,
189 }
190 }
191}
192
193/// Describes provable errors originating during protocol execution.
194///
195/// Provable here means that we can create an evidence object entirely of messages signed by some party,
196/// which, in combination, prove the party's malicious actions.
197pub trait ProtocolError<Id>: Display + Debug + Clone + Serialize + for<'de> Deserialize<'de> {
198 /// Additional data that cannot be derived from the node's messages alone
199 /// and therefore has to be supplied externally during evidence verification.
200 type AssociatedData: Debug;
201
202 /// Specifies the messages of the guilty party that need to be stored as the evidence
203 /// to prove its malicious behavior.
204 fn required_messages(&self) -> RequiredMessages;
205
206 /// Returns `Ok(())` if the attached messages indeed prove that a malicious action happened.
207 ///
208 /// The signatures and metadata of the messages will be checked by the calling code,
209 /// the responsibility of this method is just to check the message contents.
210 ///
211 /// `message` contain the message parts that triggered the error
212 /// during [`Round::receive_message`].
213 ///
214 /// `previous_messages` are message parts from the previous rounds, as requested by
215 /// [`required_messages`](Self::required_messages).
216 ///
217 /// Note that if some message part was not requested by above methods, it will be set to an empty one
218 /// in the [`ProtocolMessage`], even if it was present originally.
219 ///
220 /// `combined_echos` are bundled echos from other parties from the previous rounds,
221 /// as requested by [`required_messages`](Self::required_messages).
222 #[allow(clippy::too_many_arguments)]
223 fn verify_messages_constitute_error(
224 &self,
225 format: &BoxedFormat,
226 guilty_party: &Id,
227 shared_randomness: &[u8],
228 associated_data: &Self::AssociatedData,
229 message: ProtocolMessage,
230 previous_messages: BTreeMap<RoundId, ProtocolMessage>,
231 combined_echos: BTreeMap<RoundId, BTreeMap<Id, EchoBroadcast>>,
232 ) -> Result<(), ProtocolValidationError>;
233}
234
235#[derive(displaydoc::Display, Debug, Clone, Copy, Serialize, Deserialize)]
236/// A stub type indicating that this protocol does not generate any provable errors.
237pub struct NoProtocolErrors;
238
239impl<Id> ProtocolError<Id> for NoProtocolErrors {
240 type AssociatedData = ();
241
242 fn required_messages(&self) -> RequiredMessages {
243 panic!("Attempt to use an empty error type in an evidence. This is a bug in the protocol implementation.")
244 }
245
246 fn verify_messages_constitute_error(
247 &self,
248 _format: &BoxedFormat,
249 _guilty_party: &Id,
250 _shared_randomness: &[u8],
251 _associated_data: &Self::AssociatedData,
252 _message: ProtocolMessage,
253 _previous_messages: BTreeMap<RoundId, ProtocolMessage>,
254 _combined_echos: BTreeMap<RoundId, BTreeMap<Id, EchoBroadcast>>,
255 ) -> Result<(), ProtocolValidationError> {
256 panic!("Attempt to use an empty error type in an evidence. This is a bug in the protocol implementation.")
257 }
258}
259
260/// Message payload created in [`Round::receive_message`].
261#[derive(Debug)]
262pub struct Payload(pub Box<dyn Any + Send + Sync>);
263
264impl Payload {
265 /// Creates a new payload.
266 ///
267 /// Would be normally called in [`Round::receive_message`].
268 pub fn new<T: 'static + Send + Sync>(payload: T) -> Self {
269 Self(Box::new(payload))
270 }
271
272 /// Creates an empty payload.
273 ///
274 /// Use it in [`Round::receive_message`] if it does not need to create payloads.
275 pub fn empty() -> Self {
276 Self::new(())
277 }
278
279 /// Attempts to downcast back to the concrete type.
280 ///
281 /// Would be normally called in [`Round::finalize`].
282 pub fn downcast<T: 'static>(self) -> Result<T, LocalError> {
283 Ok(*(self
284 .0
285 .downcast::<T>()
286 .map_err(|_| LocalError::new(format!("Failed to downcast into {}", core::any::type_name::<T>())))?))
287 }
288}
289
290/// Associated data created alongside a message in [`Round::make_direct_message`].
291#[derive(Debug)]
292pub struct Artifact(pub Box<dyn Any + Send + Sync>);
293
294impl Artifact {
295 /// Creates a new artifact.
296 ///
297 /// Would be normally called in [`Round::make_direct_message`].
298 pub fn new<T: 'static + Send + Sync>(artifact: T) -> Self {
299 Self(Box::new(artifact))
300 }
301
302 /// Attempts to downcast back to the concrete type.
303 ///
304 /// Would be normally called in [`Round::finalize`].
305 pub fn downcast<T: 'static>(self) -> Result<T, LocalError> {
306 Ok(*(self
307 .0
308 .downcast::<T>()
309 .map_err(|_| LocalError::new(format!("Failed to downcast into {}", core::any::type_name::<T>())))?))
310 }
311}
312
313/// A round that initiates a protocol.
314///
315/// This is a round that can be created directly;
316/// all the others are only reachable throud [`Round::finalize`] by the execution layer.
317pub trait EntryPoint<Id: PartyId> {
318 /// The protocol implemented by the round this entry points returns.
319 type Protocol: Protocol<Id>;
320
321 /// Returns the ID of the round returned by [`Self::make_round`].
322 fn entry_round_id() -> RoundId;
323
324 /// Creates the round.
325 ///
326 /// `session_id` can be assumed to be the same for each node participating in a session.
327 /// `id` is the ID of this node.
328 fn make_round(
329 self,
330 rng: &mut dyn CryptoRngCore,
331 shared_randomness: &[u8],
332 id: &Id,
333 ) -> Result<BoxedRound<Id, Self::Protocol>, LocalError>;
334}
335
336/// A trait alias for the combination of traits needed for a party identifier.
337pub trait PartyId: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> {}
338
339impl<T> PartyId for T where T: 'static + Debug + Clone + Ord + Send + Sync + Serialize + for<'de> Deserialize<'de> {}
340
341/// The specific way the node participates in the echo round (if any).
342#[derive(Debug, Clone)]
343pub enum EchoRoundParticipation<Id> {
344 /// The default behavior: sends broadcasts and receives echoed messages, or does neither.
345 ///
346 /// That is, this node will be a part of the echo round if [`Round::make_echo_broadcast`] generates a message.
347 Default,
348
349 /// This node sends broadcasts that will be echoed, but does not receive any.
350 Send,
351
352 /// This node receives broadcasts that it needs to echo, but does not send any itself.
353 Receive {
354 /// The other participants of the echo round
355 /// (that is, the nodes to which echoed messages will be sent).
356 echo_targets: BTreeSet<Id>,
357 },
358}
359
360mod sealed {
361 /// A dyn safe trait to get the type's ID.
362 pub trait DynTypeId: 'static {
363 /// Returns the type ID of the implementing type.
364 fn get_type_id(&self) -> core::any::TypeId {
365 core::any::TypeId::of::<Self>()
366 }
367 }
368
369 impl<T: 'static> DynTypeId for T {}
370}
371
372use sealed::DynTypeId;
373
374/**
375A type representing a single round of a protocol.
376
377The way a round will be used by an external caller:
378- create messages to send out (by calling [`make_direct_message`](`Self::make_direct_message`)
379 and [`make_echo_broadcast`](`Self::make_echo_broadcast`));
380- process received messages from other nodes (by calling [`receive_message`](`Self::receive_message`));
381- attempt to finalize (by calling [`finalize`](`Self::finalize`)) to produce the next round, or return a result.
382*/
383pub trait Round<Id: PartyId>: 'static + Debug + Send + Sync + DynTypeId {
384 /// The protocol this round is a part of.
385 type Protocol: Protocol<Id>;
386
387 /// Returns the information about the position of this round in the state transition graph.
388 ///
389 /// See [`TransitionInfo`] documentation for more details.
390 fn transition_info(&self) -> TransitionInfo;
391
392 /// Returns the information about the communication this rounds engages in with other nodes.
393 ///
394 /// See [`CommunicationInfo`] documentation for more details.
395 fn communication_info(&self) -> CommunicationInfo<Id>;
396
397 /// Returns the direct message to the given destination and (maybe) an accompanying artifact.
398 ///
399 /// Return [`DirectMessage::none`] if this round does not send direct messages.
400 ///
401 /// In some protocols, when a message to another node is created, there is some associated information
402 /// that needs to be retained for later (randomness, proofs of knowledge, and so on).
403 /// These should be put in an [`Artifact`] and will be available at the time of [`finalize`](`Self::finalize`).
404 fn make_direct_message(
405 &self,
406 #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
407 #[allow(unused_variables)] format: &BoxedFormat,
408 #[allow(unused_variables)] destination: &Id,
409 ) -> Result<(DirectMessage, Option<Artifact>), LocalError> {
410 Ok((DirectMessage::none(), None))
411 }
412
413 /// Returns the echo broadcast for this round.
414 ///
415 /// Return [`EchoBroadcast::none`] if this round does not send echo-broadcast messages.
416 /// This is also the blanket implementation.
417 ///
418 /// The execution layer will guarantee that all the destinations are sure they all received the same broadcast.
419 /// This also means that a message with the broadcasts from all nodes signed by each node is available
420 /// if an evidence of malicious behavior has to be constructed.
421 fn make_echo_broadcast(
422 &self,
423 #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
424 #[allow(unused_variables)] format: &BoxedFormat,
425 ) -> Result<EchoBroadcast, LocalError> {
426 Ok(EchoBroadcast::none())
427 }
428
429 /// Returns the normal broadcast for this round.
430 ///
431 /// Return [`NormalBroadcast::none`] if this round does not send normal broadcast messages.
432 /// This is also the blanket implementation.
433 ///
434 /// Unlike the echo broadcasts, these will be just sent to every node defined in [`Self::communication_info`]
435 /// without any confirmation required.
436 fn make_normal_broadcast(
437 &self,
438 #[allow(unused_variables)] rng: &mut dyn CryptoRngCore,
439 #[allow(unused_variables)] format: &BoxedFormat,
440 ) -> Result<NormalBroadcast, LocalError> {
441 Ok(NormalBroadcast::none())
442 }
443
444 /// Processes the received message and generates the payload that will be used in [`finalize`](`Self::finalize`).
445 ///
446 /// Note that there is no need to authenticate the message at this point;
447 /// it has already been done by the execution layer.
448 fn receive_message(
449 &self,
450 format: &BoxedFormat,
451 from: &Id,
452 message: ProtocolMessage,
453 ) -> Result<Payload, ReceiveError<Id, Self::Protocol>>;
454
455 /// Attempts to finalize the round, producing the next round or the result.
456 ///
457 /// `payloads` here are the ones previously generated by [`receive_message`](`Self::receive_message`),
458 /// and `artifacts` are the ones previously generated by
459 /// [`make_direct_message`](`Self::make_direct_message`).
460 fn finalize(
461 self: Box<Self>,
462 rng: &mut dyn CryptoRngCore,
463 payloads: BTreeMap<Id, Payload>,
464 artifacts: BTreeMap<Id, Artifact>,
465 ) -> Result<FinalizeOutcome<Id, Self::Protocol>, LocalError>;
466}