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::{CertificatePair, LightClientStateUpdateCertificateV2, QuorumCertificate},
22 simple_vote::TimeoutVote2,
23 traits::{ValidatedState, node_implementation::NodeType},
24 vote::HasViewNumber,
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: ViewNumber,
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: ViewNumber,
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<LightClientStateUpdateCertificateV2<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<LightClientStateUpdateCertificateV2<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 _ => 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<VidDisperseShare0<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<VidDisperseShare0<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 committing_qc: Arc<CertificatePair<TYPES>>,
205 /// A QC signing the leaf corresponding to `qc`.
206 ///
207 /// Together with `qc`, this forms a 2-chain, which is sufficient for a light client to
208 /// verify that the leaf chain contained in this event is in fact decided.
209 deciding_qc: Option<Arc<CertificatePair<TYPES>>>,
210 /// Optional information of the number of transactions in the block, for logging purposes.
211 block_size: Option<u64>,
212 },
213 /// A replica task was canceled by a timeout interrupt
214 ReplicaViewTimeout {
215 /// The view that timed out
216 view_number: ViewNumber,
217 },
218 /// The view has finished. If values were decided on, a `Decide` event will also be emitted.
219 ViewFinished {
220 /// The view number that has just finished
221 view_number: ViewNumber,
222 },
223 /// The view timed out
224 ViewTimeout {
225 /// The view that timed out
226 view_number: ViewNumber,
227 },
228 /// New transactions were received from the network
229 /// or submitted to the network by us
230 Transactions {
231 /// The list of transactions
232 transactions: Vec<TYPES::Transaction>,
233 },
234 /// DA proposal was received from the network
235 /// or submitted to the network by us
236 DaProposal {
237 /// Contents of the proposal
238 proposal: Proposal<TYPES, DaProposal2<TYPES>>,
239 /// Public key of the leader submitting the proposal
240 sender: TYPES::SignatureKey,
241 },
242 /// Quorum proposal was received from the network
243 /// or submitted to the network by us
244 QuorumProposal {
245 /// Contents of the proposal
246 proposal: Proposal<TYPES, QuorumProposalWrapper<TYPES>>,
247 /// Public key of the leader submitting the proposal
248 sender: TYPES::SignatureKey,
249 },
250 /// Upgrade proposal was received from the network
251 /// or submitted to the network by us
252 UpgradeProposal {
253 /// Contents of the proposal
254 proposal: Proposal<TYPES, UpgradeProposal>,
255 /// Public key of the leader submitting the proposal
256 sender: TYPES::SignatureKey,
257 },
258
259 /// A message destined for external listeners was received
260 ExternalMessageReceived {
261 /// Public Key of the message sender
262 sender: TYPES::SignatureKey,
263 /// Serialized data of the message
264 data: Vec<u8>,
265 },
266
267 /// Emitted by the legacy consensus task whenever it signs and broadcasts a
268 /// `TimeoutVote2`. Used at the legacy → new-protocol upgrade boundary so
269 /// the espresso bridge can forward the same vote into the new-protocol
270 /// coordinator's vote collectors. The wire-level protocols differ but the
271 /// underlying `TimeoutVote2` type and its version-tagged signature
272 /// commitment are shared, so the same vote is valid in both systems.
273 LegacyTimeoutVoteEmitted {
274 /// The vote that was signed and broadcast on the legacy wire.
275 vote: TimeoutVote2<TYPES>,
276 },
277}
278
279impl<TYPES: NodeType> std::fmt::Display for EventType<TYPES> {
280 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
281 match self {
282 Self::Error { error } => write!(f, "Error: {error}"),
283 Self::Decide { leaf_chain, .. } => {
284 let newest = leaf_chain.first().map(|l| l.leaf.view_number());
285 let oldest = leaf_chain.last().map(|l| l.leaf.view_number());
286 write!(
287 f,
288 "Decide: leaves={} oldest={:?} newest={:?}",
289 leaf_chain.len(),
290 oldest,
291 newest,
292 )
293 },
294 Self::ReplicaViewTimeout { view_number } => {
295 write!(f, "ReplicaViewTimeout: view={view_number}")
296 },
297 Self::ViewFinished { view_number } => {
298 write!(f, "ViewFinished: view={view_number}")
299 },
300 Self::ViewTimeout { view_number } => {
301 write!(f, "ViewTimeout: view={view_number}")
302 },
303 Self::Transactions { transactions } => {
304 write!(f, "Transactions: count={}", transactions.len())
305 },
306 Self::DaProposal { proposal, .. } => {
307 write!(f, "DaProposal: view={}", proposal.data.view_number())
308 },
309 Self::QuorumProposal { proposal, .. } => {
310 write!(f, "QuorumProposal: view={}", proposal.data.view_number())
311 },
312 Self::UpgradeProposal { proposal, .. } => {
313 write!(
314 f,
315 "UpgradeProposal: view={} old_version={} new_version={}",
316 proposal.data.view_number,
317 proposal.data.upgrade_proposal.old_version,
318 proposal.data.upgrade_proposal.new_version,
319 )
320 },
321 Self::ExternalMessageReceived { .. } => {
322 write!(f, "ExternalMessageReceived")
323 },
324 Self::LegacyTimeoutVoteEmitted { vote } => {
325 write!(f, "LegacyTimeoutVoteEmitted: view={}", vote.view_number())
326 },
327 }
328 }
329}
330
331impl<TYPES: NodeType> EventType<TYPES> {
332 pub fn to_legacy(self) -> anyhow::Result<LegacyEventType<TYPES>> {
333 Ok(match self {
334 EventType::Error { error } => LegacyEventType::Error { error },
335 EventType::Decide {
336 leaf_chain,
337 committing_qc: qc,
338 block_size,
339 ..
340 } => LegacyEventType::Decide {
341 leaf_chain: Arc::new(
342 leaf_chain
343 .iter()
344 .cloned()
345 .map(LeafInfo::to_legacy_unsafe)
346 .collect::<anyhow::Result<_, _>>()?,
347 ),
348 qc: Arc::new(qc.qc().clone().to_qc()),
349 block_size,
350 },
351 EventType::ReplicaViewTimeout { view_number } => {
352 LegacyEventType::ReplicaViewTimeout { view_number }
353 },
354 EventType::ViewFinished { view_number } => {
355 LegacyEventType::ViewFinished { view_number }
356 },
357 EventType::ViewTimeout { view_number } => LegacyEventType::ViewTimeout { view_number },
358 EventType::Transactions { transactions } => {
359 LegacyEventType::Transactions { transactions }
360 },
361 EventType::DaProposal { proposal, sender } => LegacyEventType::DaProposal {
362 proposal: convert_proposal(proposal),
363 sender,
364 },
365 EventType::QuorumProposal { proposal, sender } => LegacyEventType::QuorumProposal {
366 proposal: convert_proposal(proposal),
367 sender,
368 },
369 EventType::UpgradeProposal { proposal, sender } => {
370 LegacyEventType::UpgradeProposal { proposal, sender }
371 },
372 EventType::ExternalMessageReceived { sender, data } => {
373 LegacyEventType::ExternalMessageReceived { sender, data }
374 },
375 // Upgrade-bridging event: doesn't exist in the pre-epoch event
376 // surface. Convert to a no-op equivalent (drop) since legacy
377 // consumers wouldn't know what to do with it.
378 EventType::LegacyTimeoutVoteEmitted { .. } => {
379 anyhow::bail!(
380 "LegacyTimeoutVoteEmitted is upgrade-bridging only and has no legacy \
381 equivalent"
382 )
383 },
384 })
385 }
386}
387
388/// Pre-epoch version of the `EventType` enum.
389#[non_exhaustive]
390#[derive(Clone, Debug, Serialize, Deserialize)]
391#[serde(bound(deserialize = "TYPES: NodeType"))]
392#[allow(clippy::large_enum_variant)]
393pub enum LegacyEventType<TYPES: NodeType> {
394 /// A view encountered an error and was interrupted
395 Error {
396 /// The underlying error
397 #[serde(with = "error_adaptor")]
398 error: Arc<HotShotError<TYPES>>,
399 },
400 /// A new decision event was issued
401 Decide {
402 /// The chain of Leaves that were committed by this decision
403 ///
404 /// This list is sorted in reverse view number order, with the newest (highest view number)
405 /// block first in the list.
406 ///
407 /// This list may be incomplete if the node is currently performing catchup.
408 /// Vid Info for a decided view may be missing if this node never saw it's share.
409 leaf_chain: Arc<LegacyLeafChain<TYPES>>,
410 /// The QC signing the most recent leaf in `leaf_chain`.
411 ///
412 /// Note that the QC for each additional leaf in the chain can be obtained from the leaf
413 /// before it using
414 qc: Arc<QuorumCertificate<TYPES>>,
415 /// Optional information of the number of transactions in the block, for logging purposes.
416 block_size: Option<u64>,
417 },
418 /// A replica task was canceled by a timeout interrupt
419 ReplicaViewTimeout {
420 /// The view that timed out
421 view_number: ViewNumber,
422 },
423 /// The view has finished. If values were decided on, a `Decide` event will also be emitted.
424 ViewFinished {
425 /// The view number that has just finished
426 view_number: ViewNumber,
427 },
428 /// The view timed out
429 ViewTimeout {
430 /// The view that timed out
431 view_number: ViewNumber,
432 },
433 /// New transactions were received from the network
434 /// or submitted to the network by us
435 Transactions {
436 /// The list of transactions
437 transactions: Vec<TYPES::Transaction>,
438 },
439 /// DA proposal was received from the network
440 /// or submitted to the network by us
441 DaProposal {
442 /// Contents of the proposal
443 proposal: Proposal<TYPES, DaProposal<TYPES>>,
444 /// Public key of the leader submitting the proposal
445 sender: TYPES::SignatureKey,
446 },
447 /// Quorum proposal was received from the network
448 /// or submitted to the network by us
449 QuorumProposal {
450 /// Contents of the proposal
451 proposal: Proposal<TYPES, QuorumProposal<TYPES>>,
452 /// Public key of the leader submitting the proposal
453 sender: TYPES::SignatureKey,
454 },
455 /// Upgrade proposal was received from the network
456 /// or submitted to the network by us
457 UpgradeProposal {
458 /// Contents of the proposal
459 proposal: Proposal<TYPES, UpgradeProposal>,
460 /// Public key of the leader submitting the proposal
461 sender: TYPES::SignatureKey,
462 },
463
464 /// A message destined for external listeners was received
465 ExternalMessageReceived {
466 /// Public Key of the message sender
467 sender: TYPES::SignatureKey,
468 /// Serialized data of the message
469 data: Vec<u8>,
470 },
471}
472
473#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
474/// A list of actions that we track for nodes
475pub enum HotShotAction {
476 /// A quorum vote was sent
477 Vote,
478 /// A timeout vote was sent
479 TimeoutVote,
480 /// View Sync Vote
481 ViewSyncVote,
482 /// A quorum proposal was sent
483 Propose,
484 /// DA proposal was sent
485 DaPropose,
486 /// DA vote was sent
487 DaVote,
488 /// DA certificate was sent
489 DaCert,
490 /// VID shares were sent
491 VidDisperse,
492 /// An upgrade vote was sent
493 UpgradeVote,
494 /// An upgrade proposal was sent
495 UpgradePropose,
496}