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