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}