Skip to main content

hotshot_types/
event.rs

1// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
2// This file is part of the HotShot repository.
3
4// You should have received a copy of the MIT License
5// along with the HotShot repository. If not, see <https://mit-license.org/>.
6
7//! Events that a `HotShot` instance can emit
8
9use std::sync::Arc;
10
11use hotshot_utils::anytrace::*;
12use serde::{Deserialize, Serialize};
13
14use crate::{
15    data::{
16        DaProposal, DaProposal2, Leaf, Leaf2, QuorumProposal, QuorumProposalWrapper,
17        UpgradeProposal, VidDisperseShare, VidDisperseShare0, ViewNumber,
18    },
19    error::HotShotError,
20    message::{Proposal, convert_proposal},
21    simple_certificate::{
22        CertificatePair, LightClientStateUpdateCertificateV2, QuorumCertificate, QuorumCertificate2,
23    },
24    simple_vote::TimeoutVote2,
25    traits::{ValidatedState, node_implementation::NodeType},
26    vote::HasViewNumber,
27};
28
29/// A status event emitted by a `HotShot` instance
30///
31/// This includes some metadata, such as the stage and view number that the event was generated in,
32/// as well as an inner [`EventType`] describing the event proper.
33#[derive(Clone, Debug, Serialize, Deserialize)]
34#[serde(bound(deserialize = "TYPES: NodeType"))]
35pub struct Event<TYPES: NodeType> {
36    /// The view number that this event originates from
37    pub view_number: ViewNumber,
38    /// The underlying event
39    pub event: EventType<TYPES>,
40}
41
42impl<TYPES: NodeType> Event<TYPES> {
43    pub fn to_legacy(self) -> anyhow::Result<LegacyEvent<TYPES>> {
44        Ok(LegacyEvent {
45            view_number: self.view_number,
46            event: self.event.to_legacy()?,
47        })
48    }
49}
50
51/// The pre-epoch version of an Event
52#[derive(Clone, Debug, Serialize, Deserialize)]
53#[serde(bound(deserialize = "TYPES: NodeType"))]
54pub struct LegacyEvent<TYPES: NodeType> {
55    /// The view number that this event originates from
56    pub view_number: ViewNumber,
57    /// The underlying event
58    pub event: LegacyEventType<TYPES>,
59}
60
61/// Decided leaf with the corresponding state and VID info.
62#[derive(Clone, Debug, Serialize, Deserialize)]
63#[serde(bound(deserialize = "TYPES: NodeType"))]
64pub struct LeafInfo<TYPES: NodeType> {
65    /// Decided leaf.
66    pub leaf: Leaf2<TYPES>,
67    /// Validated state.
68    pub state: Arc<<TYPES as NodeType>::ValidatedState>,
69    /// Optional application-specific state delta.
70    pub delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
71    /// Optional VID share data.
72    pub vid_share: Option<VidDisperseShare<TYPES>>,
73    /// Optional light client state update certificate.
74    pub state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
75}
76
77impl<TYPES: NodeType> LeafInfo<TYPES> {
78    /// Constructor.
79    pub fn new(
80        leaf: Leaf2<TYPES>,
81        state: Arc<<TYPES as NodeType>::ValidatedState>,
82        delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
83        vid_share: Option<VidDisperseShare<TYPES>>,
84        state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
85    ) -> Self {
86        Self {
87            leaf,
88            state,
89            delta,
90            vid_share,
91            state_cert,
92        }
93    }
94
95    pub fn to_legacy_unsafe(self) -> anyhow::Result<LegacyLeafInfo<TYPES>> {
96        Ok(LegacyLeafInfo {
97            leaf: self.leaf.to_leaf_unsafe(),
98            state: self.state,
99            delta: self.delta,
100            vid_share: self
101                .vid_share
102                .map(|share| match share {
103                    VidDisperseShare::V0(share) => Ok(share),
104                    _ => Err(error!("VID share is post-epoch")),
105                })
106                .transpose()?,
107        })
108    }
109}
110
111/// Pre-epoch version of `LeafInfo`
112#[derive(Clone, Debug, Serialize, Deserialize)]
113#[serde(bound(deserialize = "TYPES: NodeType"))]
114pub struct LegacyLeafInfo<TYPES: NodeType> {
115    /// Decided leaf.
116    pub leaf: Leaf<TYPES>,
117    /// Validated state.
118    pub state: Arc<<TYPES as NodeType>::ValidatedState>,
119    /// Optional application-specific state delta.
120    pub delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
121    /// Optional VID share data.
122    pub vid_share: Option<VidDisperseShare0<TYPES>>,
123}
124
125impl<TYPES: NodeType> LegacyLeafInfo<TYPES> {
126    /// Constructor.
127    pub fn new(
128        leaf: Leaf<TYPES>,
129        state: Arc<<TYPES as NodeType>::ValidatedState>,
130        delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
131        vid_share: Option<VidDisperseShare0<TYPES>>,
132    ) -> Self {
133        Self {
134            leaf,
135            state,
136            delta,
137            vid_share,
138        }
139    }
140}
141
142/// The chain of decided leaves with its corresponding state and VID info.
143pub type LeafChain<TYPES> = Vec<LeafInfo<TYPES>>;
144
145/// Pre-epoch version of `LeafChain`
146pub type LegacyLeafChain<TYPES> = Vec<LegacyLeafInfo<TYPES>>;
147
148/// Utilities for converting between HotShotError and a string.
149pub mod error_adaptor {
150    use serde::{de::Deserializer, ser::Serializer};
151
152    use super::{Arc, Deserialize, HotShotError, NodeType};
153
154    /// Convert a HotShotError into a string
155    ///
156    /// # Errors
157    /// Returns `Err` if the serializer fails.
158    pub fn serialize<S: Serializer, TYPES: NodeType>(
159        elem: &Arc<HotShotError<TYPES>>,
160        serializer: S,
161    ) -> Result<S::Ok, S::Error> {
162        serializer.serialize_str(&format!("{elem}"))
163    }
164
165    /// Convert a string into a HotShotError
166    ///
167    /// # Errors
168    /// Returns `Err` if the string cannot be deserialized.
169    pub fn deserialize<'de, D: Deserializer<'de>, TYPES: NodeType>(
170        deserializer: D,
171    ) -> Result<Arc<HotShotError<TYPES>>, D::Error> {
172        let str = String::deserialize(deserializer)?;
173        Ok(Arc::new(HotShotError::FailedToDeserialize(str)))
174    }
175}
176
177/// The type and contents of a status event emitted by a `HotShot` instance
178///
179/// This enum does not include metadata shared among all variants, such as the stage and view
180/// number, and is thus always returned wrapped in an [`Event`].
181#[non_exhaustive]
182#[derive(Clone, Debug, Serialize, Deserialize)]
183#[serde(bound(deserialize = "TYPES: NodeType"))]
184#[allow(clippy::large_enum_variant)]
185pub enum EventType<TYPES: NodeType> {
186    /// A view encountered an error and was interrupted
187    Error {
188        /// The underlying error
189        #[serde(with = "error_adaptor")]
190        error: Arc<HotShotError<TYPES>>,
191    },
192    /// A new decision event was issued
193    Decide {
194        /// The chain of Leaves that were committed by this decision
195        ///
196        /// This list is sorted in reverse view number order, with the newest (highest view number)
197        /// block first in the list.
198        ///
199        /// This list may be incomplete if the node is currently performing catchup.
200        /// Vid Info for a decided view may be missing if this node never saw it's share.
201        leaf_chain: Arc<LeafChain<TYPES>>,
202        /// The QC signing the most recent leaf in `leaf_chain`.
203        ///
204        /// Note that the QC for each additional leaf in the chain can be obtained from the leaf
205        /// before it using
206        committing_qc: Arc<CertificatePair<TYPES>>,
207        /// A QC signing the leaf corresponding to `qc`.
208        ///
209        /// Together with `qc`, this forms a 2-chain, which is sufficient for a light client to
210        /// verify that the leaf chain contained in this event is in fact decided.
211        deciding_qc: Option<Arc<CertificatePair<TYPES>>>,
212        /// Optional information of the number of transactions in the block, for logging purposes.
213        block_size: Option<u64>,
214    },
215    /// A replica task was canceled by a timeout interrupt
216    ReplicaViewTimeout {
217        /// The view that timed out
218        view_number: ViewNumber,
219    },
220    /// The view has finished.  If values were decided on, a `Decide` event will also be emitted.
221    ViewFinished {
222        /// The view number that has just finished
223        view_number: ViewNumber,
224    },
225    /// The view timed out
226    ViewTimeout {
227        /// The view that timed out
228        view_number: ViewNumber,
229    },
230    /// New transactions were received from the network
231    /// or submitted to the network by us
232    Transactions {
233        /// The list of transactions
234        transactions: Vec<TYPES::Transaction>,
235    },
236    /// DA proposal was received from the network
237    /// or submitted to the network by us
238    DaProposal {
239        /// Contents of the proposal
240        proposal: Proposal<TYPES, DaProposal2<TYPES>>,
241        /// Public key of the leader submitting the proposal
242        sender: TYPES::SignatureKey,
243    },
244    /// Quorum proposal was received from the network
245    /// or submitted to the network by us
246    QuorumProposal {
247        /// Contents of the proposal
248        proposal: Proposal<TYPES, QuorumProposalWrapper<TYPES>>,
249        /// Public key of the leader submitting the proposal
250        sender: TYPES::SignatureKey,
251    },
252    /// Upgrade proposal was received from the network
253    /// or submitted to the network by us
254    UpgradeProposal {
255        /// Contents of the proposal
256        proposal: Proposal<TYPES, UpgradeProposal>,
257        /// Public key of the leader submitting the proposal
258        sender: TYPES::SignatureKey,
259    },
260
261    /// A message destined for external listeners was received
262    ExternalMessageReceived {
263        /// Public Key of the message sender
264        sender: TYPES::SignatureKey,
265        /// Serialized data of the message
266        data: Vec<u8>,
267    },
268
269    /// Emitted by the legacy consensus task whenever it signs and broadcasts a
270    /// `TimeoutVote2`. Used at the legacy → new-protocol upgrade boundary so
271    /// the espresso bridge can forward the same vote into the new-protocol
272    /// coordinator's vote collectors. The wire-level protocols differ but the
273    /// underlying `TimeoutVote2` type and its version-tagged signature
274    /// commitment are shared, so the same vote is valid in both systems.
275    LegacyTimeoutVoteEmitted {
276        /// The vote that was signed and broadcast on the legacy wire.
277        vote: TimeoutVote2<TYPES>,
278    },
279
280    /// QC for the last legacy view, formed by the cutover-view leader at the
281    /// legacy -> new-protocol boundary. Lets the espresso bridge forward it to the
282    /// new-protocol coordinator if the cutover seed was snapshotted before this QC
283    /// finished assembling.
284    LegacyHighQcFormed {
285        /// The QC for the last legacy view (`cutover_view - 1`).
286        qc: QuorumCertificate2<TYPES>,
287    },
288}
289
290impl<TYPES: NodeType> std::fmt::Display for Event<TYPES> {
291    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
292        write!(f, "{} (view {})", self.event, self.view_number)
293    }
294}
295
296impl<TYPES: NodeType> std::fmt::Display for EventType<TYPES> {
297    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
298        match self {
299            Self::Error { error } => write!(f, "Error: {error}"),
300            Self::Decide { leaf_chain, .. } => {
301                let newest = leaf_chain.first().map(|l| l.leaf.view_number());
302                let oldest = leaf_chain.last().map(|l| l.leaf.view_number());
303                write!(
304                    f,
305                    "Decide: leaves={} oldest={:?} newest={:?}",
306                    leaf_chain.len(),
307                    oldest,
308                    newest,
309                )
310            },
311            Self::ReplicaViewTimeout { view_number } => {
312                write!(f, "ReplicaViewTimeout: view={view_number}")
313            },
314            Self::ViewFinished { view_number } => {
315                write!(f, "ViewFinished: view={view_number}")
316            },
317            Self::ViewTimeout { view_number } => {
318                write!(f, "ViewTimeout: view={view_number}")
319            },
320            Self::Transactions { transactions } => {
321                write!(f, "Transactions: count={}", transactions.len())
322            },
323            Self::DaProposal { proposal, .. } => {
324                write!(f, "DaProposal: view={}", proposal.data.view_number())
325            },
326            Self::QuorumProposal { proposal, .. } => {
327                write!(f, "QuorumProposal: view={}", proposal.data.view_number())
328            },
329            Self::UpgradeProposal { proposal, .. } => {
330                write!(
331                    f,
332                    "UpgradeProposal: view={} old_version={} new_version={}",
333                    proposal.data.view_number,
334                    proposal.data.upgrade_proposal.old_version,
335                    proposal.data.upgrade_proposal.new_version,
336                )
337            },
338            Self::ExternalMessageReceived { .. } => {
339                write!(f, "ExternalMessageReceived")
340            },
341            Self::LegacyTimeoutVoteEmitted { vote } => {
342                write!(f, "LegacyTimeoutVoteEmitted: view={}", vote.view_number())
343            },
344            Self::LegacyHighQcFormed { qc } => {
345                write!(f, "LegacyHighQcFormed: view={}", qc.view_number())
346            },
347        }
348    }
349}
350
351impl<TYPES: NodeType> EventType<TYPES> {
352    pub fn to_legacy(self) -> anyhow::Result<LegacyEventType<TYPES>> {
353        Ok(match self {
354            EventType::Error { error } => LegacyEventType::Error { error },
355            EventType::Decide {
356                leaf_chain,
357                committing_qc: qc,
358                block_size,
359                ..
360            } => LegacyEventType::Decide {
361                leaf_chain: Arc::new(
362                    leaf_chain
363                        .iter()
364                        .cloned()
365                        .map(LeafInfo::to_legacy_unsafe)
366                        .collect::<anyhow::Result<_, _>>()?,
367                ),
368                qc: Arc::new(qc.qc().clone().to_qc()),
369                block_size,
370            },
371            EventType::ReplicaViewTimeout { view_number } => {
372                LegacyEventType::ReplicaViewTimeout { view_number }
373            },
374            EventType::ViewFinished { view_number } => {
375                LegacyEventType::ViewFinished { view_number }
376            },
377            EventType::ViewTimeout { view_number } => LegacyEventType::ViewTimeout { view_number },
378            EventType::Transactions { transactions } => {
379                LegacyEventType::Transactions { transactions }
380            },
381            EventType::DaProposal { proposal, sender } => LegacyEventType::DaProposal {
382                proposal: convert_proposal(proposal),
383                sender,
384            },
385            EventType::QuorumProposal { proposal, sender } => LegacyEventType::QuorumProposal {
386                proposal: convert_proposal(proposal),
387                sender,
388            },
389            EventType::UpgradeProposal { proposal, sender } => {
390                LegacyEventType::UpgradeProposal { proposal, sender }
391            },
392            EventType::ExternalMessageReceived { sender, data } => {
393                LegacyEventType::ExternalMessageReceived { sender, data }
394            },
395            // Upgrade-bridging event: doesn't exist in the pre-epoch event
396            // surface. Convert to a no-op equivalent (drop) since legacy
397            // consumers wouldn't know what to do with it.
398            EventType::LegacyTimeoutVoteEmitted { .. } => {
399                anyhow::bail!(
400                    "LegacyTimeoutVoteEmitted is upgrade-bridging only and has no legacy \
401                     equivalent"
402                )
403            },
404            EventType::LegacyHighQcFormed { .. } => {
405                anyhow::bail!(
406                    "LegacyHighQcFormed is upgrade-bridging only and has no legacy equivalent"
407                )
408            },
409        })
410    }
411}
412
413/// Pre-epoch version of the `EventType` enum.
414#[non_exhaustive]
415#[derive(Clone, Debug, Serialize, Deserialize)]
416#[serde(bound(deserialize = "TYPES: NodeType"))]
417#[allow(clippy::large_enum_variant)]
418pub enum LegacyEventType<TYPES: NodeType> {
419    /// A view encountered an error and was interrupted
420    Error {
421        /// The underlying error
422        #[serde(with = "error_adaptor")]
423        error: Arc<HotShotError<TYPES>>,
424    },
425    /// A new decision event was issued
426    Decide {
427        /// The chain of Leaves that were committed by this decision
428        ///
429        /// This list is sorted in reverse view number order, with the newest (highest view number)
430        /// block first in the list.
431        ///
432        /// This list may be incomplete if the node is currently performing catchup.
433        /// Vid Info for a decided view may be missing if this node never saw it's share.
434        leaf_chain: Arc<LegacyLeafChain<TYPES>>,
435        /// The QC signing the most recent leaf in `leaf_chain`.
436        ///
437        /// Note that the QC for each additional leaf in the chain can be obtained from the leaf
438        /// before it using
439        qc: Arc<QuorumCertificate<TYPES>>,
440        /// Optional information of the number of transactions in the block, for logging purposes.
441        block_size: Option<u64>,
442    },
443    /// A replica task was canceled by a timeout interrupt
444    ReplicaViewTimeout {
445        /// The view that timed out
446        view_number: ViewNumber,
447    },
448    /// The view has finished.  If values were decided on, a `Decide` event will also be emitted.
449    ViewFinished {
450        /// The view number that has just finished
451        view_number: ViewNumber,
452    },
453    /// The view timed out
454    ViewTimeout {
455        /// The view that timed out
456        view_number: ViewNumber,
457    },
458    /// New transactions were received from the network
459    /// or submitted to the network by us
460    Transactions {
461        /// The list of transactions
462        transactions: Vec<TYPES::Transaction>,
463    },
464    /// DA proposal was received from the network
465    /// or submitted to the network by us
466    DaProposal {
467        /// Contents of the proposal
468        proposal: Proposal<TYPES, DaProposal<TYPES>>,
469        /// Public key of the leader submitting the proposal
470        sender: TYPES::SignatureKey,
471    },
472    /// Quorum proposal was received from the network
473    /// or submitted to the network by us
474    QuorumProposal {
475        /// Contents of the proposal
476        proposal: Proposal<TYPES, QuorumProposal<TYPES>>,
477        /// Public key of the leader submitting the proposal
478        sender: TYPES::SignatureKey,
479    },
480    /// Upgrade proposal was received from the network
481    /// or submitted to the network by us
482    UpgradeProposal {
483        /// Contents of the proposal
484        proposal: Proposal<TYPES, UpgradeProposal>,
485        /// Public key of the leader submitting the proposal
486        sender: TYPES::SignatureKey,
487    },
488
489    /// A message destined for external listeners was received
490    ExternalMessageReceived {
491        /// Public Key of the message sender
492        sender: TYPES::SignatureKey,
493        /// Serialized data of the message
494        data: Vec<u8>,
495    },
496}
497
498#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)]
499/// A list of actions that we track for nodes
500pub enum HotShotAction {
501    /// A quorum vote was sent
502    Vote,
503    /// A timeout vote was sent
504    TimeoutVote,
505    /// View Sync Vote
506    ViewSyncVote,
507    /// A quorum proposal was sent
508    Propose,
509    /// DA proposal was sent
510    DaPropose,
511    /// DA vote was sent
512    DaVote,
513    /// DA certificate was sent
514    DaCert,
515    /// VID shares were sent
516    VidDisperse,
517    /// An upgrade vote was sent
518    UpgradeVote,
519    /// An upgrade proposal was sent
520    UpgradePropose,
521}