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        vid_disperse::ADVZDisperseShare, DaProposal, DaProposal2, Leaf, Leaf2, QuorumProposal,
17        QuorumProposalWrapper, UpgradeProposal, VidDisperseShare,
18    },
19    error::HotShotError,
20    message::{convert_proposal, Proposal},
21    simple_certificate::{CertificatePair, LightClientStateUpdateCertificateV2, QuorumCertificate},
22    traits::{node_implementation::NodeType, ValidatedState},
23};
24
25/// A status event emitted by a `HotShot` instance
26///
27/// This includes some metadata, such as the stage and view number that the event was generated in,
28/// as well as an inner [`EventType`] describing the event proper.
29#[derive(Clone, Debug, Serialize, Deserialize)]
30#[serde(bound(deserialize = "TYPES: NodeType"))]
31pub struct Event<TYPES: NodeType> {
32    /// The view number that this event originates from
33    pub view_number: TYPES::View,
34    /// The underlying event
35    pub event: EventType<TYPES>,
36}
37
38impl<TYPES: NodeType> Event<TYPES> {
39    pub fn to_legacy(self) -> anyhow::Result<LegacyEvent<TYPES>> {
40        Ok(LegacyEvent {
41            view_number: self.view_number,
42            event: self.event.to_legacy()?,
43        })
44    }
45}
46
47/// The pre-epoch version of an Event
48#[derive(Clone, Debug, Serialize, Deserialize)]
49#[serde(bound(deserialize = "TYPES: NodeType"))]
50pub struct LegacyEvent<TYPES: NodeType> {
51    /// The view number that this event originates from
52    pub view_number: TYPES::View,
53    /// The underlying event
54    pub event: LegacyEventType<TYPES>,
55}
56
57/// Decided leaf with the corresponding state and VID info.
58#[derive(Clone, Debug, Serialize, Deserialize)]
59#[serde(bound(deserialize = "TYPES: NodeType"))]
60pub struct LeafInfo<TYPES: NodeType> {
61    /// Decided leaf.
62    pub leaf: Leaf2<TYPES>,
63    /// Validated state.
64    pub state: Arc<<TYPES as NodeType>::ValidatedState>,
65    /// Optional application-specific state delta.
66    pub delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
67    /// Optional VID share data.
68    pub vid_share: Option<VidDisperseShare<TYPES>>,
69    /// Optional light client state update certificate.
70    pub state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
71}
72
73impl<TYPES: NodeType> LeafInfo<TYPES> {
74    /// Constructor.
75    pub fn new(
76        leaf: Leaf2<TYPES>,
77        state: Arc<<TYPES as NodeType>::ValidatedState>,
78        delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
79        vid_share: Option<VidDisperseShare<TYPES>>,
80        state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
81    ) -> Self {
82        Self {
83            leaf,
84            state,
85            delta,
86            vid_share,
87            state_cert,
88        }
89    }
90
91    pub fn to_legacy_unsafe(self) -> anyhow::Result<LegacyLeafInfo<TYPES>> {
92        Ok(LegacyLeafInfo {
93            leaf: self.leaf.to_leaf_unsafe(),
94            state: self.state,
95            delta: self.delta,
96            vid_share: self
97                .vid_share
98                .map(|share| match share {
99                    VidDisperseShare::V0(share) => Ok(share),
100                    VidDisperseShare::V1(_) => Err(error!("VID share is post-epoch")),
101                })
102                .transpose()?,
103        })
104    }
105}
106
107/// Pre-epoch version of `LeafInfo`
108#[derive(Clone, Debug, Serialize, Deserialize)]
109#[serde(bound(deserialize = "TYPES: NodeType"))]
110pub struct LegacyLeafInfo<TYPES: NodeType> {
111    /// Decided leaf.
112    pub leaf: Leaf<TYPES>,
113    /// Validated state.
114    pub state: Arc<<TYPES as NodeType>::ValidatedState>,
115    /// Optional application-specific state delta.
116    pub delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
117    /// Optional VID share data.
118    pub vid_share: Option<ADVZDisperseShare<TYPES>>,
119}
120
121impl<TYPES: NodeType> LegacyLeafInfo<TYPES> {
122    /// Constructor.
123    pub fn new(
124        leaf: Leaf<TYPES>,
125        state: Arc<<TYPES as NodeType>::ValidatedState>,
126        delta: Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
127        vid_share: Option<ADVZDisperseShare<TYPES>>,
128    ) -> Self {
129        Self {
130            leaf,
131            state,
132            delta,
133            vid_share,
134        }
135    }
136}
137
138/// The chain of decided leaves with its corresponding state and VID info.
139pub type LeafChain<TYPES> = Vec<LeafInfo<TYPES>>;
140
141/// Pre-epoch version of `LeafChain`
142pub type LegacyLeafChain<TYPES> = Vec<LegacyLeafInfo<TYPES>>;
143
144/// Utilities for converting between HotShotError and a string.
145pub mod error_adaptor {
146    use serde::{de::Deserializer, ser::Serializer};
147
148    use super::{Arc, Deserialize, HotShotError, NodeType};
149
150    /// Convert a HotShotError into a string
151    ///
152    /// # Errors
153    /// Returns `Err` if the serializer fails.
154    pub fn serialize<S: Serializer, TYPES: NodeType>(
155        elem: &Arc<HotShotError<TYPES>>,
156        serializer: S,
157    ) -> Result<S::Ok, S::Error> {
158        serializer.serialize_str(&format!("{elem}"))
159    }
160
161    /// Convert a string into a HotShotError
162    ///
163    /// # Errors
164    /// Returns `Err` if the string cannot be deserialized.
165    pub fn deserialize<'de, D: Deserializer<'de>, TYPES: NodeType>(
166        deserializer: D,
167    ) -> Result<Arc<HotShotError<TYPES>>, D::Error> {
168        let str = String::deserialize(deserializer)?;
169        Ok(Arc::new(HotShotError::FailedToDeserialize(str)))
170    }
171}
172
173/// The type and contents of a status event emitted by a `HotShot` instance
174///
175/// This enum does not include metadata shared among all variants, such as the stage and view
176/// number, and is thus always returned wrapped in an [`Event`].
177#[non_exhaustive]
178#[derive(Clone, Debug, Serialize, Deserialize)]
179#[serde(bound(deserialize = "TYPES: NodeType"))]
180#[allow(clippy::large_enum_variant)]
181pub enum EventType<TYPES: NodeType> {
182    /// A view encountered an error and was interrupted
183    Error {
184        /// The underlying error
185        #[serde(with = "error_adaptor")]
186        error: Arc<HotShotError<TYPES>>,
187    },
188    /// A new decision event was issued
189    Decide {
190        /// The chain of Leaves that were committed by this decision
191        ///
192        /// This list is sorted in reverse view number order, with the newest (highest view number)
193        /// block first in the list.
194        ///
195        /// This list may be incomplete if the node is currently performing catchup.
196        /// Vid Info for a decided view may be missing if this node never saw it's share.
197        leaf_chain: Arc<LeafChain<TYPES>>,
198        /// The QC signing the most recent leaf in `leaf_chain`.
199        ///
200        /// Note that the QC for each additional leaf in the chain can be obtained from the leaf
201        /// before it using
202        committing_qc: Arc<CertificatePair<TYPES>>,
203        /// A QC signing the leaf corresponding to `qc`.
204        ///
205        /// Together with `qc`, this forms a 2-chain, which is sufficient for a light client to
206        /// verify that the leaf chain contained in this event is in fact decided.
207        deciding_qc: Option<Arc<CertificatePair<TYPES>>>,
208        /// Optional information of the number of transactions in the block, for logging purposes.
209        block_size: Option<u64>,
210    },
211    /// A replica task was canceled by a timeout interrupt
212    ReplicaViewTimeout {
213        /// The view that timed out
214        view_number: TYPES::View,
215    },
216    /// The view has finished.  If values were decided on, a `Decide` event will also be emitted.
217    ViewFinished {
218        /// The view number that has just finished
219        view_number: TYPES::View,
220    },
221    /// The view timed out
222    ViewTimeout {
223        /// The view that timed out
224        view_number: TYPES::View,
225    },
226    /// New transactions were received from the network
227    /// or submitted to the network by us
228    Transactions {
229        /// The list of transactions
230        transactions: Vec<TYPES::Transaction>,
231    },
232    /// DA proposal was received from the network
233    /// or submitted to the network by us
234    DaProposal {
235        /// Contents of the proposal
236        proposal: Proposal<TYPES, DaProposal2<TYPES>>,
237        /// Public key of the leader submitting the proposal
238        sender: TYPES::SignatureKey,
239    },
240    /// Quorum proposal was received from the network
241    /// or submitted to the network by us
242    QuorumProposal {
243        /// Contents of the proposal
244        proposal: Proposal<TYPES, QuorumProposalWrapper<TYPES>>,
245        /// Public key of the leader submitting the proposal
246        sender: TYPES::SignatureKey,
247    },
248    /// Upgrade proposal was received from the network
249    /// or submitted to the network by us
250    UpgradeProposal {
251        /// Contents of the proposal
252        proposal: Proposal<TYPES, UpgradeProposal<TYPES>>,
253        /// Public key of the leader submitting the proposal
254        sender: TYPES::SignatureKey,
255    },
256
257    /// A message destined for external listeners was received
258    ExternalMessageReceived {
259        /// Public Key of the message sender
260        sender: TYPES::SignatureKey,
261        /// Serialized data of the message
262        data: Vec<u8>,
263    },
264}
265
266impl<TYPES: NodeType> EventType<TYPES> {
267    pub fn to_legacy(self) -> anyhow::Result<LegacyEventType<TYPES>> {
268        Ok(match self {
269            EventType::Error { error } => LegacyEventType::Error { error },
270            EventType::Decide {
271                leaf_chain,
272                committing_qc: qc,
273                block_size,
274                ..
275            } => LegacyEventType::Decide {
276                leaf_chain: Arc::new(
277                    leaf_chain
278                        .iter()
279                        .cloned()
280                        .map(LeafInfo::to_legacy_unsafe)
281                        .collect::<anyhow::Result<_, _>>()?,
282                ),
283                qc: Arc::new(qc.qc().clone().to_qc()),
284                block_size,
285            },
286            EventType::ReplicaViewTimeout { view_number } => {
287                LegacyEventType::ReplicaViewTimeout { view_number }
288            },
289            EventType::ViewFinished { view_number } => {
290                LegacyEventType::ViewFinished { view_number }
291            },
292            EventType::ViewTimeout { view_number } => LegacyEventType::ViewTimeout { view_number },
293            EventType::Transactions { transactions } => {
294                LegacyEventType::Transactions { transactions }
295            },
296            EventType::DaProposal { proposal, sender } => LegacyEventType::DaProposal {
297                proposal: convert_proposal(proposal),
298                sender,
299            },
300            EventType::QuorumProposal { proposal, sender } => LegacyEventType::QuorumProposal {
301                proposal: convert_proposal(proposal),
302                sender,
303            },
304            EventType::UpgradeProposal { proposal, sender } => {
305                LegacyEventType::UpgradeProposal { proposal, sender }
306            },
307            EventType::ExternalMessageReceived { sender, data } => {
308                LegacyEventType::ExternalMessageReceived { sender, data }
309            },
310        })
311    }
312}
313
314/// Pre-epoch version of the `EventType` enum.
315#[non_exhaustive]
316#[derive(Clone, Debug, Serialize, Deserialize)]
317#[serde(bound(deserialize = "TYPES: NodeType"))]
318#[allow(clippy::large_enum_variant)]
319pub enum LegacyEventType<TYPES: NodeType> {
320    /// A view encountered an error and was interrupted
321    Error {
322        /// The underlying error
323        #[serde(with = "error_adaptor")]
324        error: Arc<HotShotError<TYPES>>,
325    },
326    /// A new decision event was issued
327    Decide {
328        /// The chain of Leaves that were committed by this decision
329        ///
330        /// This list is sorted in reverse view number order, with the newest (highest view number)
331        /// block first in the list.
332        ///
333        /// This list may be incomplete if the node is currently performing catchup.
334        /// Vid Info for a decided view may be missing if this node never saw it's share.
335        leaf_chain: Arc<LegacyLeafChain<TYPES>>,
336        /// The QC signing the most recent leaf in `leaf_chain`.
337        ///
338        /// Note that the QC for each additional leaf in the chain can be obtained from the leaf
339        /// before it using
340        qc: Arc<QuorumCertificate<TYPES>>,
341        /// Optional information of the number of transactions in the block, for logging purposes.
342        block_size: Option<u64>,
343    },
344    /// A replica task was canceled by a timeout interrupt
345    ReplicaViewTimeout {
346        /// The view that timed out
347        view_number: TYPES::View,
348    },
349    /// The view has finished.  If values were decided on, a `Decide` event will also be emitted.
350    ViewFinished {
351        /// The view number that has just finished
352        view_number: TYPES::View,
353    },
354    /// The view timed out
355    ViewTimeout {
356        /// The view that timed out
357        view_number: TYPES::View,
358    },
359    /// New transactions were received from the network
360    /// or submitted to the network by us
361    Transactions {
362        /// The list of transactions
363        transactions: Vec<TYPES::Transaction>,
364    },
365    /// DA proposal was received from the network
366    /// or submitted to the network by us
367    DaProposal {
368        /// Contents of the proposal
369        proposal: Proposal<TYPES, DaProposal<TYPES>>,
370        /// Public key of the leader submitting the proposal
371        sender: TYPES::SignatureKey,
372    },
373    /// Quorum proposal was received from the network
374    /// or submitted to the network by us
375    QuorumProposal {
376        /// Contents of the proposal
377        proposal: Proposal<TYPES, QuorumProposal<TYPES>>,
378        /// Public key of the leader submitting the proposal
379        sender: TYPES::SignatureKey,
380    },
381    /// Upgrade proposal was received from the network
382    /// or submitted to the network by us
383    UpgradeProposal {
384        /// Contents of the proposal
385        proposal: Proposal<TYPES, UpgradeProposal<TYPES>>,
386        /// Public key of the leader submitting the proposal
387        sender: TYPES::SignatureKey,
388    },
389
390    /// A message destined for external listeners was received
391    ExternalMessageReceived {
392        /// Public Key of the message sender
393        sender: TYPES::SignatureKey,
394        /// Serialized data of the message
395        data: Vec<u8>,
396    },
397}
398
399#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
400/// A list of actions that we track for nodes
401pub enum HotShotAction {
402    /// A quorum vote was sent
403    Vote,
404    /// A timeout vote was sent
405    TimeoutVote,
406    /// View Sync Vote
407    ViewSyncVote,
408    /// A quorum proposal was sent
409    Propose,
410    /// DA proposal was sent
411    DaPropose,
412    /// DA vote was sent
413    DaVote,
414    /// DA certificate was sent
415    DaCert,
416    /// VID shares were sent
417    VidDisperse,
418    /// An upgrade vote was sent
419    UpgradeVote,
420    /// An upgrade proposal was sent
421    UpgradePropose,
422}