hotshot_types/
consensus.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//! Provides the core consensus types
8
9use std::{
10    collections::{BTreeMap, HashMap, HashSet},
11    mem::ManuallyDrop,
12    ops::{Deref, DerefMut},
13    sync::Arc,
14};
15
16use alloy::primitives::U256;
17use async_lock::{RwLock, RwLockReadGuard, RwLockUpgradableReadGuard, RwLockWriteGuard};
18use committable::{Commitment, Committable};
19use hotshot_utils::anytrace::*;
20use tracing::instrument;
21use vec1::Vec1;
22
23pub use crate::utils::{View, ViewInner};
24use crate::{
25    constants::EPOCH_PARTICIPATION_HISTORY,
26    data::{
27        EpochNumber, Leaf2, QuorumProposalWrapper, VidCommitment, VidDisperse,
28        VidDisperseAndDuration, VidDisperseShare, ViewNumber,
29    },
30    epoch_membership::EpochMembershipCoordinator,
31    error::HotShotError,
32    event::{HotShotAction, LeafInfo},
33    message::{Proposal, UpgradeLock},
34    simple_certificate::{
35        DaCertificate2, LightClientStateUpdateCertificateV2, NextEpochQuorumCertificate2,
36        QuorumCertificate2,
37    },
38    simple_vote::HasEpoch,
39    stake_table::{HSStakeTable, StakeTableEntries},
40    traits::{
41        BlockPayload, ValidatedState,
42        block_contents::{BlockHeader, BuilderFee},
43        metrics::{Counter, Gauge, Histogram, Metrics, NoMetrics},
44        node_implementation::NodeType,
45        signature_key::{SignatureKey, StakeTableEntryType},
46    },
47    utils::{
48        BuilderCommitment, LeafCommitment, StateAndDelta, Terminator, epoch_from_block_number,
49        is_epoch_root, is_epoch_transition, is_last_block, is_transition_block,
50        option_epoch_from_block_number,
51    },
52    vote::{Certificate, HasViewNumber},
53};
54
55/// A type alias for `HashMap<Commitment<T>, T>`
56pub type CommitmentMap<T> = HashMap<Commitment<T>, T>;
57
58/// A type alias for `BTreeMap<T::Time, HashMap<T::SignatureKey, BTreeMap<T::Epoch, Proposal<T, VidDisperseShare<T>>>>>`
59pub type VidShares<TYPES> = BTreeMap<
60    ViewNumber,
61    HashMap<
62        <TYPES as NodeType>::SignatureKey,
63        BTreeMap<Option<EpochNumber>, Proposal<TYPES, VidDisperseShare<TYPES>>>,
64    >,
65>;
66
67/// Type alias for consensus state wrapped in a lock.
68pub type LockedConsensusState<TYPES> = Arc<RwLock<Consensus<TYPES>>>;
69
70/// A thin wrapper around `LockedConsensusState` that helps debugging locks
71#[derive(Clone, Debug)]
72pub struct OuterConsensus<TYPES: NodeType> {
73    /// Inner `LockedConsensusState`
74    pub inner_consensus: LockedConsensusState<TYPES>,
75}
76
77impl<TYPES: NodeType> OuterConsensus<TYPES> {
78    /// Create a new instance of `OuterConsensus`, hopefully uniquely named
79    pub fn new(consensus: LockedConsensusState<TYPES>) -> Self {
80        Self {
81            inner_consensus: consensus,
82        }
83    }
84
85    /// Locks inner consensus for reading and leaves debug traces
86    #[instrument(skip_all, target = "OuterConsensus")]
87    pub async fn read(&self) -> ConsensusReadLockGuard<'_, TYPES> {
88        tracing::trace!("Trying to acquire read lock on consensus");
89        let ret = self.inner_consensus.read().await;
90        tracing::trace!("Acquired read lock on consensus");
91        ConsensusReadLockGuard::new(ret)
92    }
93
94    /// Locks inner consensus for writing and leaves debug traces
95    #[instrument(skip_all, target = "OuterConsensus")]
96    pub async fn write(&self) -> ConsensusWriteLockGuard<'_, TYPES> {
97        tracing::trace!("Trying to acquire write lock on consensus");
98        let ret = self.inner_consensus.write().await;
99        tracing::trace!("Acquired write lock on consensus");
100        ConsensusWriteLockGuard::new(ret)
101    }
102
103    /// Tries to acquire write lock on inner consensus and leaves debug traces
104    #[instrument(skip_all, target = "OuterConsensus")]
105    pub fn try_write(&self) -> Option<ConsensusWriteLockGuard<'_, TYPES>> {
106        tracing::trace!("Trying to acquire write lock on consensus");
107        let ret = self.inner_consensus.try_write();
108        if let Some(guard) = ret {
109            tracing::trace!("Acquired write lock on consensus");
110            Some(ConsensusWriteLockGuard::new(guard))
111        } else {
112            tracing::trace!("Failed to acquire write lock");
113            None
114        }
115    }
116
117    /// Acquires upgradable read lock on inner consensus and leaves debug traces
118    #[instrument(skip_all, target = "OuterConsensus")]
119    pub async fn upgradable_read(&self) -> ConsensusUpgradableReadLockGuard<'_, TYPES> {
120        tracing::trace!("Trying to acquire upgradable read lock on consensus");
121        let ret = self.inner_consensus.upgradable_read().await;
122        tracing::trace!("Acquired upgradable read lock on consensus");
123        ConsensusUpgradableReadLockGuard::new(ret)
124    }
125
126    /// Tries to acquire read lock on inner consensus and leaves debug traces
127    #[instrument(skip_all, target = "OuterConsensus")]
128    pub fn try_read(&self) -> Option<ConsensusReadLockGuard<'_, TYPES>> {
129        tracing::trace!("Trying to acquire read lock on consensus");
130        let ret = self.inner_consensus.try_read();
131        if let Some(guard) = ret {
132            tracing::trace!("Acquired read lock on consensus");
133            Some(ConsensusReadLockGuard::new(guard))
134        } else {
135            tracing::trace!("Failed to acquire read lock");
136            None
137        }
138    }
139}
140
141/// A thin wrapper around `RwLockReadGuard` for `Consensus` that leaves debug traces when the lock is freed
142pub struct ConsensusReadLockGuard<'a, TYPES: NodeType> {
143    /// Inner `RwLockReadGuard`
144    lock_guard: RwLockReadGuard<'a, Consensus<TYPES>>,
145}
146
147impl<'a, TYPES: NodeType> ConsensusReadLockGuard<'a, TYPES> {
148    /// Creates a new instance of `ConsensusReadLockGuard` with the same name as parent `OuterConsensus`
149    #[must_use]
150    pub fn new(lock_guard: RwLockReadGuard<'a, Consensus<TYPES>>) -> Self {
151        Self { lock_guard }
152    }
153}
154
155impl<TYPES: NodeType> Deref for ConsensusReadLockGuard<'_, TYPES> {
156    type Target = Consensus<TYPES>;
157    fn deref(&self) -> &Self::Target {
158        &self.lock_guard
159    }
160}
161
162impl<TYPES: NodeType> Drop for ConsensusReadLockGuard<'_, TYPES> {
163    #[instrument(skip_all, target = "ConsensusReadLockGuard")]
164    fn drop(&mut self) {
165        tracing::trace!("Read lock on consensus dropped");
166    }
167}
168
169/// A thin wrapper around `RwLockWriteGuard` for `Consensus` that leaves debug traces when the lock is freed
170pub struct ConsensusWriteLockGuard<'a, TYPES: NodeType> {
171    /// Inner `RwLockWriteGuard`
172    lock_guard: RwLockWriteGuard<'a, Consensus<TYPES>>,
173}
174
175impl<'a, TYPES: NodeType> ConsensusWriteLockGuard<'a, TYPES> {
176    /// Creates a new instance of `ConsensusWriteLockGuard` with the same name as parent `OuterConsensus`
177    #[must_use]
178    pub fn new(lock_guard: RwLockWriteGuard<'a, Consensus<TYPES>>) -> Self {
179        Self { lock_guard }
180    }
181}
182
183impl<TYPES: NodeType> Deref for ConsensusWriteLockGuard<'_, TYPES> {
184    type Target = Consensus<TYPES>;
185    fn deref(&self) -> &Self::Target {
186        &self.lock_guard
187    }
188}
189
190impl<TYPES: NodeType> DerefMut for ConsensusWriteLockGuard<'_, TYPES> {
191    fn deref_mut(&mut self) -> &mut Self::Target {
192        &mut self.lock_guard
193    }
194}
195
196impl<TYPES: NodeType> Drop for ConsensusWriteLockGuard<'_, TYPES> {
197    #[instrument(skip_all, target = "ConsensusWriteLockGuard")]
198    fn drop(&mut self) {
199        tracing::debug!("Write lock on consensus dropped");
200    }
201}
202
203/// A thin wrapper around `RwLockUpgradableReadGuard` for `Consensus` that leaves debug traces when the lock is freed or upgraded
204pub struct ConsensusUpgradableReadLockGuard<'a, TYPES: NodeType> {
205    /// Inner `RwLockUpgradableReadGuard`
206    lock_guard: ManuallyDrop<RwLockUpgradableReadGuard<'a, Consensus<TYPES>>>,
207    /// A helper bool to indicate whether inner lock has been unsafely taken or not
208    taken: bool,
209}
210
211impl<'a, TYPES: NodeType> ConsensusUpgradableReadLockGuard<'a, TYPES> {
212    /// Creates a new instance of `ConsensusUpgradableReadLockGuard` with the same name as parent `OuterConsensus`
213    #[must_use]
214    pub fn new(lock_guard: RwLockUpgradableReadGuard<'a, Consensus<TYPES>>) -> Self {
215        Self {
216            lock_guard: ManuallyDrop::new(lock_guard),
217            taken: false,
218        }
219    }
220
221    /// Upgrades the inner `RwLockUpgradableReadGuard` and leaves debug traces
222    #[instrument(skip_all, target = "ConsensusUpgradableReadLockGuard")]
223    #[allow(unused_assignments)] // `taken` is read in Drop impl
224    pub async fn upgrade(mut guard: Self) -> ConsensusWriteLockGuard<'a, TYPES> {
225        let inner_guard = unsafe { ManuallyDrop::take(&mut guard.lock_guard) };
226        guard.taken = true;
227        tracing::debug!("Trying to upgrade upgradable read lock on consensus");
228        let ret = RwLockUpgradableReadGuard::upgrade(inner_guard).await;
229        tracing::debug!("Upgraded upgradable read lock on consensus");
230        ConsensusWriteLockGuard::new(ret)
231    }
232}
233
234impl<TYPES: NodeType> Deref for ConsensusUpgradableReadLockGuard<'_, TYPES> {
235    type Target = Consensus<TYPES>;
236
237    fn deref(&self) -> &Self::Target {
238        &self.lock_guard
239    }
240}
241
242impl<TYPES: NodeType> Drop for ConsensusUpgradableReadLockGuard<'_, TYPES> {
243    #[instrument(skip_all, target = "ConsensusUpgradableReadLockGuard")]
244    fn drop(&mut self) {
245        if !self.taken {
246            unsafe { ManuallyDrop::drop(&mut self.lock_guard) }
247            tracing::debug!("Upgradable read lock on consensus dropped");
248        }
249    }
250}
251
252/// A bundle of views that we have most recently performed some action
253#[derive(Debug, Clone, Copy)]
254struct HotShotActionViews {
255    /// View we last proposed in to the Quorum
256    proposed: ViewNumber,
257    /// View we last voted in for a QuorumProposal
258    voted: ViewNumber,
259    /// View we last proposed to the DA committee
260    da_proposed: ViewNumber,
261    /// View we lasted voted for DA proposal
262    da_vote: ViewNumber,
263}
264
265impl Default for HotShotActionViews {
266    fn default() -> Self {
267        let genesis = ViewNumber::genesis();
268        Self {
269            proposed: genesis,
270            voted: genesis,
271            da_proposed: genesis,
272            da_vote: genesis,
273        }
274    }
275}
276impl HotShotActionViews {
277    /// Create HotShotActionViews from a view number
278    fn from_view(view: ViewNumber) -> Self {
279        Self {
280            proposed: view,
281            voted: view,
282            da_proposed: view,
283            da_vote: view,
284        }
285    }
286}
287
288type ValidatorParticipationMap<TYPES> = HashMap<<TYPES as NodeType>::SignatureKey, (u64, u64)>;
289
290#[derive(Debug, Clone)]
291struct ValidatorParticipation<TYPES: NodeType> {
292    epoch: EpochNumber,
293    /// Current epoch participation by key maps key -> (num leader, num times proposed)
294    current_epoch_participation: ValidatorParticipationMap<TYPES>,
295
296    /// Last epoch participation by key maps key -> (num leader, num times proposed)
297    previous_epoch_participation: BTreeMap<EpochNumber, ValidatorParticipationMap<TYPES>>,
298}
299
300impl<TYPES: NodeType> ValidatorParticipation<TYPES> {
301    fn new() -> Self {
302        Self {
303            epoch: EpochNumber::genesis(),
304            current_epoch_participation: HashMap::new(),
305            previous_epoch_participation: BTreeMap::new(),
306        }
307    }
308
309    fn update_participation(
310        &mut self,
311        key: TYPES::SignatureKey,
312        epoch: EpochNumber,
313        proposed: bool,
314    ) {
315        if epoch != self.epoch {
316            return;
317        }
318        let entry = self
319            .current_epoch_participation
320            .entry(key)
321            .or_insert((0, 0));
322        if proposed {
323            entry.1 += 1;
324        }
325        entry.0 += 1;
326    }
327
328    fn update_participation_epoch(&mut self, epoch: EpochNumber) {
329        if epoch <= self.epoch {
330            return;
331        }
332        self.previous_epoch_participation
333            .insert(self.epoch, self.current_epoch_participation.clone());
334
335        self.previous_epoch_participation =
336            self.previous_epoch_participation
337                .split_off(&EpochNumber::new(
338                    self.epoch.saturating_sub(EPOCH_PARTICIPATION_HISTORY),
339                ));
340
341        self.epoch = epoch;
342        self.current_epoch_participation = HashMap::new();
343    }
344
345    fn current_proposal_participation(&self) -> HashMap<TYPES::SignatureKey, f64> {
346        self.current_epoch_participation
347            .iter()
348            .map(|(key, (leader, proposed))| {
349                (
350                    key.clone(),
351                    if *leader == 0 {
352                        0.0
353                    } else {
354                        *proposed as f64 / *leader as f64
355                    },
356                )
357            })
358            .collect()
359    }
360    fn proposal_participation(&self, epoch: EpochNumber) -> HashMap<TYPES::SignatureKey, f64> {
361        let tracked_participation = if epoch == self.epoch {
362            self.current_epoch_participation.clone()
363        } else {
364            self.previous_epoch_participation
365                .get(&epoch)
366                .unwrap_or(&HashMap::new())
367                .clone()
368        };
369
370        tracked_participation
371            .iter()
372            .map(|(key, (leader, proposed))| {
373                (
374                    key.clone(),
375                    if *leader == 0 {
376                        0.0
377                    } else {
378                        *proposed as f64 / *leader as f64
379                    },
380                )
381            })
382            .collect()
383    }
384}
385
386type VoteParticipationMap<TYPES> = (
387    HashMap<<<TYPES as NodeType>::SignatureKey as SignatureKey>::VerificationKeyType, u64>,
388    u64,
389);
390
391#[derive(Clone, Debug)]
392struct VoteParticipation<TYPES: NodeType> {
393    /// Current epoch
394    epoch: Option<EpochNumber>,
395
396    /// Current stake_table
397    stake_table: HSStakeTable<TYPES>,
398
399    /// Success threshold
400    success_threshold: U256,
401
402    /// Set of views in the current epoch
403    view_set: HashSet<ViewNumber>,
404
405    /// Number of views in the current epoch
406    current_epoch_num_views: u64,
407
408    /// Current epoch participation by key maps key -> num times voted
409    current_epoch_participation:
410        HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, u64>,
411
412    /// Last epoch participation by key maps key -> num times voted
413    previous_epoch_participation: BTreeMap<Option<EpochNumber>, VoteParticipationMap<TYPES>>,
414}
415
416impl<TYPES: NodeType> VoteParticipation<TYPES> {
417    fn new(
418        stake_table: HSStakeTable<TYPES>,
419        success_threshold: U256,
420        epoch: Option<EpochNumber>,
421    ) -> Self {
422        let current_epoch_participation: HashMap<_, _> = stake_table
423            .iter()
424            .map({
425                |peer_config| {
426                    (
427                        peer_config
428                            .stake_table_entry
429                            .public_key()
430                            .to_verification_key(),
431                        0u64,
432                    )
433                }
434            })
435            .collect();
436        Self {
437            epoch,
438            stake_table,
439            success_threshold,
440            view_set: HashSet::new(),
441            current_epoch_num_views: 0u64,
442            current_epoch_participation,
443            previous_epoch_participation: BTreeMap::new(),
444        }
445    }
446
447    fn update_participation(&mut self, qc: QuorumCertificate2<TYPES>) -> Result<()> {
448        ensure!(
449            qc.epoch() == self.epoch,
450            warn!(
451                "Incorrect epoch while updating vote participation, current epoch: {:?}, QC epoch \
452                 {:?}",
453                self.epoch,
454                qc.epoch()
455            )
456        );
457        ensure!(
458            !self.view_set.contains(&qc.view_number()),
459            info!(
460                "Participation for view {} already updated",
461                qc.view_number()
462            )
463        );
464        let signers = qc
465            .signers(
466                &StakeTableEntries::<TYPES>::from(self.stake_table.clone()).0,
467                self.success_threshold,
468            )
469            .context(|e| warn!("Tracing signers: {e}"))?;
470        for vk in signers {
471            let Some(votes) = self.current_epoch_participation.get_mut(&vk) else {
472                bail!(warn!(
473                    "Trying to update vote participation for unknown key: {:?}",
474                    vk
475                ));
476            };
477            *votes += 1;
478        }
479        self.view_set.insert(qc.view_number());
480        self.current_epoch_num_views += 1;
481        Ok(())
482    }
483
484    fn update_participation_epoch(
485        &mut self,
486        stake_table: HSStakeTable<TYPES>,
487        success_threshold: U256,
488        epoch: Option<EpochNumber>,
489    ) -> Result<()> {
490        ensure!(
491            epoch > self.epoch,
492            info!(
493                "New epoch not greater than current epoch while updating vote participation \
494                 epoch, current epoch: {:?}, new epoch {:?}",
495                self.epoch, epoch
496            )
497        );
498
499        self.previous_epoch_participation.insert(
500            self.epoch,
501            (
502                self.current_epoch_participation.clone(),
503                self.current_epoch_num_views,
504            ),
505        );
506
507        self.previous_epoch_participation = self.previous_epoch_participation.split_off(
508            &self
509                .epoch
510                .map(|e| EpochNumber::new(e.saturating_sub(EPOCH_PARTICIPATION_HISTORY))),
511        );
512
513        self.epoch = epoch;
514        self.current_epoch_num_views = 0;
515        self.view_set = HashSet::new();
516        let current_epoch_participation: HashMap<_, _> = stake_table
517            .iter()
518            .map({
519                |peer_config| {
520                    (
521                        peer_config
522                            .stake_table_entry
523                            .public_key()
524                            .to_verification_key(),
525                        0u64,
526                    )
527                }
528            })
529            .collect();
530        self.current_epoch_participation = current_epoch_participation;
531        self.stake_table = stake_table;
532        self.success_threshold = success_threshold;
533        Ok(())
534    }
535
536    fn current_vote_participation(
537        &self,
538    ) -> HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, f64> {
539        self.current_epoch_participation
540            .iter()
541            .map(|(key, votes)| {
542                (
543                    key.clone(),
544                    Self::calculate_ratio(votes, self.current_epoch_num_views),
545                )
546            })
547            .collect()
548    }
549    fn vote_participation(
550        &self,
551        epoch: Option<EpochNumber>,
552    ) -> HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, f64> {
553        let tracked_participation = if epoch == self.epoch {
554            (
555                self.current_epoch_participation.clone(),
556                self.current_epoch_num_views,
557            )
558        } else {
559            self.previous_epoch_participation
560                .get(&epoch)
561                .unwrap_or(&(HashMap::new(), 0))
562                .clone()
563        };
564
565        tracked_participation
566            .0
567            .iter()
568            .map(|(key, votes)| {
569                (
570                    key.clone(),
571                    Self::calculate_ratio(votes, tracked_participation.1),
572                )
573            })
574            .collect()
575    }
576
577    fn calculate_ratio(num_votes: &u64, total_views: u64) -> f64 {
578        if total_views == 0 {
579            0.0
580        } else {
581            *num_votes as f64 / total_views as f64
582        }
583    }
584}
585
586/// A reference to the consensus algorithm
587///
588/// This will contain the state of all rounds.
589#[derive(derive_more::Debug, Clone)]
590pub struct Consensus<TYPES: NodeType> {
591    /// The validated states that are currently loaded in memory.
592    validated_state_map: BTreeMap<ViewNumber, View<TYPES>>,
593
594    /// All the VID shares we've received for current and future views.
595    vid_shares: VidShares<TYPES>,
596
597    /// All the DA certs we've received for current and future views.
598    /// view -> DA cert
599    saved_da_certs: HashMap<ViewNumber, DaCertificate2<TYPES>>,
600
601    /// View number that is currently on.
602    cur_view: ViewNumber,
603
604    /// Epoch number that is currently on.
605    cur_epoch: Option<EpochNumber>,
606
607    /// Last proposals we sent out, None if we haven't proposed yet.
608    /// Prevents duplicate proposals, and can be served to those trying to catchup
609    last_proposals: BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>>,
610
611    /// last view had a successful decide event
612    last_decided_view: ViewNumber,
613
614    /// The `locked_qc` view number
615    locked_view: ViewNumber,
616
617    /// Map of leaf hash -> leaf
618    /// - contains undecided leaves
619    /// - includes the MOST RECENT decided leaf
620    saved_leaves: CommitmentMap<Leaf2<TYPES>>,
621
622    /// Bundle of views which we performed the most recent action
623    /// visibible to the network.  Actions are votes and proposals
624    /// for DA and Quorum
625    last_actions: HotShotActionViews,
626
627    /// Saved payloads.
628    ///
629    /// Encoded transactions for every view if we got a payload for that view.
630    saved_payloads: BTreeMap<ViewNumber, Arc<PayloadWithMetadata<TYPES>>>,
631
632    /// the highqc per spec
633    high_qc: QuorumCertificate2<TYPES>,
634
635    /// The high QC for the next epoch
636    next_epoch_high_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
637
638    /// Track the participation of each validator in the current epoch and previous epoch
639    validator_participation: ValidatorParticipation<TYPES>,
640
641    /// Track the vote participation of each node in the current epoch and previous epoch
642    vote_participation: VoteParticipation<TYPES>,
643
644    /// A reference to the metrics trait
645    pub metrics: Arc<ConsensusMetricsValue>,
646
647    /// Number of blocks in an epoch, zero means there are no epochs
648    pub epoch_height: u64,
649
650    /// Number of iterations for the DRB calculation, taken from HotShotConfig
651    pub drb_difficulty: u64,
652
653    /// Number of iterations for the DRB calculation post-difficulty upgrade, taken from HotShotConfig
654    pub drb_upgrade_difficulty: u64,
655
656    /// The transition QC for the current epoch
657    transition_qc: Option<(
658        QuorumCertificate2<TYPES>,
659        NextEpochQuorumCertificate2<TYPES>,
660    )>,
661
662    /// The highest block number that we have seen
663    pub highest_block: u64,
664    /// The light client state update certificate
665    pub state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
666}
667
668/// This struct holds a payload and its metadata
669#[derive(Debug, Clone, Hash, Eq, PartialEq)]
670pub struct PayloadWithMetadata<TYPES: NodeType> {
671    pub payload: TYPES::BlockPayload,
672    pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
673}
674
675/// Contains several `ConsensusMetrics` that we're interested in from the consensus interfaces
676#[derive(Clone, Debug)]
677pub struct ConsensusMetricsValue {
678    /// The number of last synced block height
679    pub last_synced_block_height: Box<dyn Gauge>,
680    /// The number of last decided view
681    pub last_decided_view: Box<dyn Gauge>,
682    /// The number of the last voted view
683    pub last_voted_view: Box<dyn Gauge>,
684    /// Number of timestamp for the last decided time
685    pub last_decided_time: Box<dyn Gauge>,
686    /// The current view
687    pub current_view: Box<dyn Gauge>,
688    /// Number of views that are in-flight since the last decided view
689    pub number_of_views_since_last_decide: Box<dyn Gauge>,
690    /// Number of views that are in-flight since the last anchor view
691    pub number_of_views_per_decide_event: Box<dyn Histogram>,
692    /// Duration of views as leader
693    pub view_duration_as_leader: Box<dyn Histogram>,
694    /// Number of invalid QCs we've seen since the last commit.
695    pub invalid_qc: Box<dyn Gauge>,
696    /// Number of outstanding transactions
697    pub outstanding_transactions: Box<dyn Gauge>,
698    /// Memory size in bytes of the serialized transactions still outstanding
699    pub outstanding_transactions_memory_size: Box<dyn Gauge>,
700    /// Number of views that timed out
701    pub number_of_timeouts: Box<dyn Counter>,
702    /// Number of views that timed out as leader
703    pub number_of_timeouts_as_leader: Box<dyn Counter>,
704    /// The number of empty blocks that have been proposed
705    pub number_of_empty_blocks_proposed: Box<dyn Counter>,
706    /// Number of events in the hotshot event queue
707    pub internal_event_queue_len: Box<dyn Gauge>,
708    /// Time from proposal creation to decide time
709    pub proposal_to_decide_time: Box<dyn Histogram>,
710    /// Time from proposal received to proposal creation
711    pub previous_proposal_to_proposal_time: Box<dyn Histogram>,
712    /// Finalized bytes per view
713    pub finalized_bytes: Box<dyn Histogram>,
714    /// The duration of the validate and apply header
715    pub validate_and_apply_header_duration: Box<dyn Histogram>,
716    /// The duration of update leaf
717    pub update_leaf_duration: Box<dyn Histogram>,
718    /// The time it took to calculate the disperse
719    pub vid_disperse_duration: Box<dyn Histogram>,
720}
721
722impl ConsensusMetricsValue {
723    /// Create a new instance of this [`ConsensusMetricsValue`] struct, setting all the counters and gauges
724    #[must_use]
725    pub fn new(metrics: &dyn Metrics) -> Self {
726        Self {
727            last_synced_block_height: metrics
728                .create_gauge(String::from("last_synced_block_height"), None),
729            last_decided_view: metrics.create_gauge(String::from("last_decided_view"), None),
730            last_voted_view: metrics.create_gauge(String::from("last_voted_view"), None),
731            last_decided_time: metrics.create_gauge(String::from("last_decided_time"), None),
732            current_view: metrics.create_gauge(String::from("current_view"), None),
733            number_of_views_since_last_decide: metrics
734                .create_gauge(String::from("number_of_views_since_last_decide"), None),
735            number_of_views_per_decide_event: metrics
736                .create_histogram(String::from("number_of_views_per_decide_event"), None),
737            view_duration_as_leader: metrics
738                .create_histogram(String::from("view_duration_as_leader"), None),
739            invalid_qc: metrics.create_gauge(String::from("invalid_qc"), None),
740            outstanding_transactions: metrics
741                .create_gauge(String::from("outstanding_transactions"), None),
742            outstanding_transactions_memory_size: metrics
743                .create_gauge(String::from("outstanding_transactions_memory_size"), None),
744            number_of_timeouts: metrics.create_counter(String::from("number_of_timeouts"), None),
745            number_of_timeouts_as_leader: metrics
746                .create_counter(String::from("number_of_timeouts_as_leader"), None),
747            number_of_empty_blocks_proposed: metrics
748                .create_counter(String::from("number_of_empty_blocks_proposed"), None),
749            internal_event_queue_len: metrics
750                .create_gauge(String::from("internal_event_queue_len"), None),
751            proposal_to_decide_time: metrics
752                .create_histogram(String::from("proposal_to_decide_time"), None),
753            previous_proposal_to_proposal_time: metrics
754                .create_histogram(String::from("previous_proposal_to_proposal_time"), None),
755            finalized_bytes: metrics.create_histogram(String::from("finalized_bytes"), None),
756            validate_and_apply_header_duration: metrics.create_histogram(
757                String::from("validate_and_apply_header_duration"),
758                Some("seconds".to_string()),
759            ),
760            update_leaf_duration: metrics.create_histogram(
761                String::from("update_leaf_duration"),
762                Some("seconds".to_string()),
763            ),
764            vid_disperse_duration: metrics.create_histogram(
765                String::from("vid_disperse_duration"),
766                Some("seconds".to_string()),
767            ),
768        }
769    }
770}
771
772impl Default for ConsensusMetricsValue {
773    fn default() -> Self {
774        Self::new(&*NoMetrics::boxed())
775    }
776}
777
778impl<TYPES: NodeType> Consensus<TYPES> {
779    /// Constructor.
780    #[allow(clippy::too_many_arguments)]
781    pub fn new(
782        validated_state_map: BTreeMap<ViewNumber, View<TYPES>>,
783        vid_shares: Option<VidShares<TYPES>>,
784        cur_view: ViewNumber,
785        cur_epoch: Option<EpochNumber>,
786        locked_view: ViewNumber,
787        last_decided_view: ViewNumber,
788        last_actioned_view: ViewNumber,
789        last_proposals: BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>>,
790        saved_leaves: CommitmentMap<Leaf2<TYPES>>,
791        saved_payloads: BTreeMap<ViewNumber, Arc<PayloadWithMetadata<TYPES>>>,
792        high_qc: QuorumCertificate2<TYPES>,
793        next_epoch_high_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
794        metrics: Arc<ConsensusMetricsValue>,
795        epoch_height: u64,
796        state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
797        drb_difficulty: u64,
798        drb_upgrade_difficulty: u64,
799        stake_table: HSStakeTable<TYPES>,
800        success_threshold: U256,
801    ) -> Self {
802        let transition_qc = if let Some(ref next_epoch_high_qc) = next_epoch_high_qc {
803            if high_qc
804                .data
805                .block_number
806                .is_some_and(|bn| is_transition_block(bn, epoch_height))
807            {
808                if high_qc.data.leaf_commit == next_epoch_high_qc.data.leaf_commit {
809                    Some((high_qc.clone(), next_epoch_high_qc.clone()))
810                } else {
811                    tracing::error!("Next epoch high QC has different leaf commit to high QC");
812                    None
813                }
814            } else {
815                None
816            }
817        } else {
818            None
819        };
820        Consensus {
821            validated_state_map,
822            vid_shares: vid_shares.unwrap_or_default(),
823            saved_da_certs: HashMap::new(),
824            cur_view,
825            cur_epoch,
826            last_decided_view,
827            last_proposals,
828            last_actions: HotShotActionViews::from_view(last_actioned_view),
829            locked_view,
830            saved_leaves,
831            saved_payloads,
832            high_qc,
833            next_epoch_high_qc,
834            metrics,
835            epoch_height,
836            transition_qc,
837            highest_block: 0,
838            state_cert,
839            drb_difficulty,
840            validator_participation: ValidatorParticipation::new(),
841            vote_participation: VoteParticipation::new(stake_table, success_threshold, cur_epoch),
842            drb_upgrade_difficulty,
843        }
844    }
845
846    /// Get the current view.
847    pub fn cur_view(&self) -> ViewNumber {
848        self.cur_view
849    }
850
851    /// Get the current epoch.
852    pub fn cur_epoch(&self) -> Option<EpochNumber> {
853        self.cur_epoch
854    }
855
856    /// Get the last decided view.
857    pub fn last_decided_view(&self) -> ViewNumber {
858        self.last_decided_view
859    }
860
861    /// Get the locked view.
862    pub fn locked_view(&self) -> ViewNumber {
863        self.locked_view
864    }
865
866    /// Get the high QC.
867    pub fn high_qc(&self) -> &QuorumCertificate2<TYPES> {
868        &self.high_qc
869    }
870
871    /// Get the transition QC.
872    pub fn transition_qc(
873        &self,
874    ) -> Option<&(
875        QuorumCertificate2<TYPES>,
876        NextEpochQuorumCertificate2<TYPES>,
877    )> {
878        self.transition_qc.as_ref()
879    }
880
881    ///Update the highest block number
882    pub fn update_highest_block(&mut self, block_number: u64) {
883        if block_number > self.highest_block {
884            self.highest_block = block_number;
885            return;
886        }
887
888        if is_epoch_transition(block_number, self.epoch_height) {
889            let new_epoch = epoch_from_block_number(block_number, self.epoch_height);
890            let high_epoch = epoch_from_block_number(self.highest_block, self.epoch_height);
891            if new_epoch >= high_epoch {
892                self.highest_block = block_number;
893            }
894        }
895    }
896
897    /// Update the transition QC.
898    pub fn update_transition_qc(
899        &mut self,
900        qc: QuorumCertificate2<TYPES>,
901        next_epoch_qc: NextEpochQuorumCertificate2<TYPES>,
902    ) {
903        if next_epoch_qc.data.leaf_commit != qc.data().leaf_commit {
904            tracing::error!(
905                "Next epoch QC for view {} has different leaf commit {:?} to {:?}",
906                qc.view_number(),
907                next_epoch_qc.data.leaf_commit,
908                qc.data().leaf_commit
909            );
910            return;
911        }
912        if let Some((transition_qc, _)) = &self.transition_qc
913            && transition_qc.view_number() >= qc.view_number()
914        {
915            return;
916        }
917        self.transition_qc = Some((qc, next_epoch_qc));
918    }
919
920    /// Get the current light client state certificate
921    pub fn state_cert(&self) -> Option<&LightClientStateUpdateCertificateV2<TYPES>> {
922        self.state_cert.as_ref()
923    }
924
925    /// Get the next epoch high QC.
926    pub fn next_epoch_high_qc(&self) -> Option<&NextEpochQuorumCertificate2<TYPES>> {
927        self.next_epoch_high_qc.as_ref()
928    }
929
930    /// Get the validated state map.
931    pub fn validated_state_map(&self) -> &BTreeMap<ViewNumber, View<TYPES>> {
932        &self.validated_state_map
933    }
934
935    /// Get the saved leaves.
936    pub fn saved_leaves(&self) -> &CommitmentMap<Leaf2<TYPES>> {
937        &self.saved_leaves
938    }
939
940    /// Get the saved payloads.
941    pub fn saved_payloads(&self) -> &BTreeMap<ViewNumber, Arc<PayloadWithMetadata<TYPES>>> {
942        &self.saved_payloads
943    }
944
945    /// Get the vid shares.
946    pub fn vid_shares(&self) -> &VidShares<TYPES> {
947        &self.vid_shares
948    }
949
950    /// Get the saved DA certs.
951    pub fn saved_da_certs(&self) -> &HashMap<ViewNumber, DaCertificate2<TYPES>> {
952        &self.saved_da_certs
953    }
954
955    /// Get the map of our recent proposals
956    pub fn last_proposals(
957        &self,
958    ) -> &BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>> {
959        &self.last_proposals
960    }
961
962    /// Update the current view.
963    /// # Errors
964    /// Can return an error when the new view_number is not higher than the existing view number.
965    pub fn update_view(&mut self, view_number: ViewNumber) -> Result<()> {
966        ensure!(
967            view_number > self.cur_view,
968            debug!("New view isn't newer than the current view.")
969        );
970        self.cur_view = view_number;
971        Ok(())
972    }
973
974    /// Update the validator participation
975    pub fn update_validator_participation(
976        &mut self,
977        key: TYPES::SignatureKey,
978        epoch: EpochNumber,
979        proposed: bool,
980    ) {
981        self.validator_participation
982            .update_participation(key, epoch, proposed);
983    }
984
985    /// Update the validator participation epoch
986    pub fn update_validator_participation_epoch(&mut self, epoch: EpochNumber) {
987        self.validator_participation
988            .update_participation_epoch(epoch);
989    }
990
991    /// Get the current proposal participation
992    pub fn current_proposal_participation(&self) -> HashMap<TYPES::SignatureKey, f64> {
993        self.validator_participation
994            .current_proposal_participation()
995    }
996
997    /// Get the proposal participation for a given epoch
998    pub fn proposal_participation(&self, epoch: EpochNumber) -> HashMap<TYPES::SignatureKey, f64> {
999        self.validator_participation.proposal_participation(epoch)
1000    }
1001
1002    /// Update the vote participation
1003    pub fn update_vote_participation(&mut self, qc: QuorumCertificate2<TYPES>) -> Result<()> {
1004        self.vote_participation.update_participation(qc)
1005    }
1006
1007    /// Update the vote participation epoch
1008    pub fn update_vote_participation_epoch(
1009        &mut self,
1010        stake_table: HSStakeTable<TYPES>,
1011        success_threshold: U256,
1012        epoch: Option<EpochNumber>,
1013    ) -> Result<()> {
1014        self.vote_participation
1015            .update_participation_epoch(stake_table, success_threshold, epoch)
1016    }
1017
1018    /// Get the current vote participation
1019    pub fn current_vote_participation(
1020        &self,
1021    ) -> HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, f64> {
1022        self.vote_participation.current_vote_participation()
1023    }
1024
1025    /// Get the previous vote participation
1026    pub fn vote_participation(
1027        &self,
1028        epoch: Option<EpochNumber>,
1029    ) -> HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, f64> {
1030        self.vote_participation.vote_participation(epoch)
1031    }
1032
1033    /// Get the parent Leaf Info from a given leaf and our public key.
1034    /// Returns None if we don't have the data in out state
1035    pub async fn parent_leaf_info(
1036        &self,
1037        leaf: &Leaf2<TYPES>,
1038        public_key: &TYPES::SignatureKey,
1039    ) -> Option<LeafInfo<TYPES>> {
1040        let parent_view_number = leaf.justify_qc().view_number();
1041        let parent_epoch = leaf.justify_qc().epoch();
1042        let parent_leaf = self
1043            .saved_leaves
1044            .get(&leaf.justify_qc().data().leaf_commit)?;
1045        let parent_state_and_delta = self.state_and_delta(parent_view_number);
1046        let (Some(state), delta) = parent_state_and_delta else {
1047            return None;
1048        };
1049
1050        let parent_vid = self
1051            .vid_shares()
1052            .get(&parent_view_number)
1053            .and_then(|key_map| key_map.get(public_key))
1054            .and_then(|epoch_map| epoch_map.get(&parent_epoch))
1055            .map(|prop| prop.data.clone());
1056
1057        let state_cert = if parent_leaf.with_epoch
1058            && is_epoch_root(parent_leaf.block_header().block_number(), self.epoch_height)
1059        {
1060            match self.state_cert() {
1061                // Sanity check that the state cert is for the same view as the parent leaf
1062                Some(state_cert)
1063                    if state_cert.light_client_state.view_number == parent_view_number.u64() =>
1064                {
1065                    Some(state_cert.clone())
1066                },
1067                _ => None,
1068            }
1069        } else {
1070            None
1071        };
1072
1073        Some(LeafInfo {
1074            leaf: parent_leaf.clone(),
1075            state,
1076            delta,
1077            vid_share: parent_vid,
1078            state_cert,
1079        })
1080    }
1081
1082    /// Update the current epoch.
1083    /// # Errors
1084    /// Can return an error when the new epoch_number is not higher than the existing epoch number.
1085    pub fn update_epoch(&mut self, epoch_number: EpochNumber) -> Result<()> {
1086        ensure!(
1087            self.cur_epoch.is_none() || Some(epoch_number) > self.cur_epoch,
1088            debug!("New epoch isn't newer than the current epoch.")
1089        );
1090        tracing::trace!(
1091            "Updating epoch from {:?} to {}",
1092            self.cur_epoch,
1093            epoch_number
1094        );
1095        self.cur_epoch = Some(epoch_number);
1096        Ok(())
1097    }
1098
1099    /// Update the last actioned view internally for votes and proposals
1100    ///
1101    /// Returns true if the action is for a newer view than the last action of that type
1102    pub fn update_action(&mut self, action: HotShotAction, view: ViewNumber) -> bool {
1103        let old_view = match action {
1104            HotShotAction::Vote => &mut self.last_actions.voted,
1105            HotShotAction::Propose => &mut self.last_actions.proposed,
1106            HotShotAction::DaPropose => &mut self.last_actions.da_proposed,
1107            HotShotAction::DaVote => {
1108                if view > self.last_actions.da_vote {
1109                    self.last_actions.da_vote = view;
1110                }
1111                // TODO Add logic to prevent double voting.  For now the simple check if
1112                // the last voted view is less than the view we are trying to vote doesn't work
1113                // because the leader of view n + 1 may propose to the DA (and we would vote)
1114                // before the leader of view n.
1115                return true;
1116            },
1117            _ => return true,
1118        };
1119        if view > *old_view {
1120            *old_view = view;
1121            return true;
1122        }
1123        false
1124    }
1125
1126    /// reset last actions to genesis so we can resend events in tests
1127    pub fn reset_actions(&mut self) {
1128        self.last_actions = HotShotActionViews::default();
1129    }
1130
1131    /// Update the last proposal.
1132    ///
1133    /// # Errors
1134    /// Can return an error when the new view_number is not higher than the existing proposed view number.
1135    pub fn update_proposed_view(
1136        &mut self,
1137        proposal: Proposal<TYPES, QuorumProposalWrapper<TYPES>>,
1138    ) -> Result<()> {
1139        ensure!(
1140            proposal.data.view_number()
1141                > self
1142                    .last_proposals
1143                    .last_key_value()
1144                    .map_or(ViewNumber::genesis(), |(k, _)| { *k }),
1145            debug!("New view isn't newer than the previously proposed view.")
1146        );
1147        self.last_proposals
1148            .insert(proposal.data.view_number(), proposal);
1149        Ok(())
1150    }
1151
1152    /// Update the last decided view.
1153    ///
1154    /// # Errors
1155    /// Can return an error when the new view_number is not higher than the existing decided view number.
1156    pub fn update_last_decided_view(&mut self, view_number: ViewNumber) -> Result<()> {
1157        ensure!(
1158            view_number > self.last_decided_view,
1159            debug!("New view isn't newer than the previously decided view.")
1160        );
1161        self.last_decided_view = view_number;
1162        Ok(())
1163    }
1164
1165    /// Update the locked view.
1166    ///
1167    /// # Errors
1168    /// Can return an error when the new view_number is not higher than the existing locked view number.
1169    pub fn update_locked_view(&mut self, view_number: ViewNumber) -> Result<()> {
1170        ensure!(
1171            view_number > self.locked_view,
1172            debug!("New view isn't newer than the previously locked view.")
1173        );
1174        self.locked_view = view_number;
1175        Ok(())
1176    }
1177
1178    /// Update the validated state map with a new view_number/view combo.
1179    ///
1180    /// # Errors
1181    /// Can return an error when the new view contains less information than the existing view
1182    /// with the same view number.
1183    pub fn update_da_view(
1184        &mut self,
1185        view_number: ViewNumber,
1186        epoch: Option<EpochNumber>,
1187        payload_commitment: VidCommitment,
1188    ) -> Result<()> {
1189        let view = View {
1190            view_inner: ViewInner::Da {
1191                payload_commitment,
1192                epoch,
1193            },
1194        };
1195        self.update_validated_state_map(view_number, view)
1196    }
1197
1198    /// Update the validated state map with a new view_number/view combo.
1199    ///
1200    /// # Errors
1201    /// Can return an error when the new view contains less information than the existing view
1202    /// with the same view number.
1203    pub fn update_leaf(
1204        &mut self,
1205        leaf: Leaf2<TYPES>,
1206        state: Arc<TYPES::ValidatedState>,
1207        delta: Option<Arc<<TYPES::ValidatedState as ValidatedState<TYPES>>::Delta>>,
1208    ) -> Result<()> {
1209        let view_number = leaf.view_number();
1210        let epoch =
1211            option_epoch_from_block_number(leaf.with_epoch, leaf.height(), self.epoch_height);
1212        let view = View {
1213            view_inner: ViewInner::Leaf {
1214                leaf: leaf.commit(),
1215                state,
1216                delta,
1217                epoch,
1218            },
1219        };
1220        self.update_validated_state_map(view_number, view)?;
1221        self.update_saved_leaves(leaf);
1222        Ok(())
1223    }
1224
1225    /// Update the validated state map with a new view_number/view combo.
1226    ///
1227    /// # Errors
1228    /// Can return an error when the new view contains less information than the existing view
1229    /// with the same view number.
1230    fn update_validated_state_map(
1231        &mut self,
1232        view_number: ViewNumber,
1233        new_view: View<TYPES>,
1234    ) -> Result<()> {
1235        if let Some(existing_view) = self.validated_state_map().get(&view_number)
1236            && let ViewInner::Leaf {
1237                delta: ref existing_delta,
1238                ..
1239            } = existing_view.view_inner
1240        {
1241            if let ViewInner::Leaf {
1242                delta: ref new_delta,
1243                ..
1244            } = new_view.view_inner
1245            {
1246                ensure!(
1247                    new_delta.is_some() || existing_delta.is_none(),
1248                    debug!(
1249                        "Skipping the state update to not override a `Leaf` view with `Some` \
1250                         state delta."
1251                    )
1252                );
1253            } else {
1254                bail!(
1255                    "Skipping the state update to not override a `Leaf` view with a non-`Leaf` \
1256                     view."
1257                );
1258            }
1259        }
1260        self.validated_state_map.insert(view_number, new_view);
1261        Ok(())
1262    }
1263
1264    /// Update the saved leaves with a new leaf.
1265    fn update_saved_leaves(&mut self, leaf: Leaf2<TYPES>) {
1266        self.saved_leaves.insert(leaf.commit(), leaf);
1267    }
1268
1269    /// Update the saved payloads with a new encoded transaction.
1270    ///
1271    /// # Errors
1272    /// Can return an error when there's an existing payload corresponding to the same view number.
1273    pub fn update_saved_payloads(
1274        &mut self,
1275        view_number: ViewNumber,
1276        payload: Arc<PayloadWithMetadata<TYPES>>,
1277    ) -> Result<()> {
1278        ensure!(
1279            !self.saved_payloads.contains_key(&view_number),
1280            "Payload with the same view already exists."
1281        );
1282        self.saved_payloads.insert(view_number, payload);
1283        Ok(())
1284    }
1285
1286    /// Update the high QC if given a newer one.
1287    /// # Errors
1288    /// Can return an error when the provided high_qc is not newer than the existing entry.
1289    pub fn update_high_qc(&mut self, high_qc: QuorumCertificate2<TYPES>) -> Result<()> {
1290        if self.high_qc == high_qc {
1291            return Ok(());
1292        }
1293        // make sure the we don't update the high QC unless is't a higher view
1294        ensure!(
1295            high_qc.view_number > self.high_qc.view_number,
1296            debug!("High QC with an equal or higher view exists.")
1297        );
1298        tracing::debug!("Updating high QC");
1299        self.high_qc = high_qc;
1300
1301        Ok(())
1302    }
1303
1304    /// Update the next epoch high QC if given a newer one.
1305    /// # Errors
1306    /// Can return an error when the provided high_qc is not newer than the existing entry.
1307    /// # Panics
1308    /// It can't actually panic. If the option is None, we will not call unwrap on it.
1309    pub fn update_next_epoch_high_qc(
1310        &mut self,
1311        high_qc: NextEpochQuorumCertificate2<TYPES>,
1312    ) -> Result<()> {
1313        if self.next_epoch_high_qc.as_ref() == Some(&high_qc) {
1314            return Ok(());
1315        }
1316        if let Some(next_epoch_high_qc) = self.next_epoch_high_qc() {
1317            ensure!(
1318                high_qc.view_number > next_epoch_high_qc.view_number,
1319                debug!("Next epoch high QC with an equal or higher view exists.")
1320            );
1321        }
1322        tracing::debug!("Updating next epoch high QC");
1323        self.next_epoch_high_qc = Some(high_qc);
1324
1325        Ok(())
1326    }
1327
1328    /// Resets high qc and next epoch qc to the provided transition qc.
1329    /// # Errors
1330    /// Can return an error when the provided high_qc is not newer than the existing entry.
1331    pub fn reset_high_qc(
1332        &mut self,
1333        high_qc: QuorumCertificate2<TYPES>,
1334        next_epoch_qc: NextEpochQuorumCertificate2<TYPES>,
1335    ) -> Result<()> {
1336        ensure!(
1337            high_qc.data.leaf_commit == next_epoch_qc.data.leaf_commit,
1338            error!("High QC's and next epoch QC's leaf commits do not match.")
1339        );
1340        if self.high_qc == high_qc {
1341            return Ok(());
1342        }
1343        let same_epoch = high_qc.data.block_number.is_some_and(|bn| {
1344            let current_qc = self.high_qc();
1345            let Some(high_bn) = current_qc.data.block_number else {
1346                return false;
1347            };
1348            epoch_from_block_number(bn + 1, self.epoch_height)
1349                == epoch_from_block_number(high_bn + 1, self.epoch_height)
1350        });
1351        ensure!(
1352            high_qc
1353                .data
1354                .block_number
1355                .is_some_and(|bn| is_transition_block(bn, self.epoch_height))
1356                && same_epoch,
1357            error!("Provided QC is not a transition QC.")
1358        );
1359        tracing::debug!("Resetting high QC and next epoch high QC");
1360        self.high_qc = high_qc;
1361        self.next_epoch_high_qc = Some(next_epoch_qc);
1362
1363        Ok(())
1364    }
1365
1366    /// Update the light client state update certificate if given a newer one.
1367    /// # Errors
1368    /// Can return an error when the provided state_cert is not newer than the existing entry.
1369    pub fn update_state_cert(
1370        &mut self,
1371        state_cert: LightClientStateUpdateCertificateV2<TYPES>,
1372    ) -> Result<()> {
1373        if let Some(existing_state_cert) = &self.state_cert {
1374            ensure!(
1375                state_cert.epoch > existing_state_cert.epoch,
1376                debug!(
1377                    "Light client state update certification with an equal or higher epoch exists."
1378                )
1379            );
1380        }
1381        tracing::debug!("Updating light client state update certification");
1382        self.state_cert = Some(state_cert);
1383
1384        Ok(())
1385    }
1386
1387    /// Add a new entry to the vid_shares map.
1388    pub fn update_vid_shares(
1389        &mut self,
1390        view_number: ViewNumber,
1391        disperse: Proposal<TYPES, VidDisperseShare<TYPES>>,
1392    ) {
1393        self.vid_shares
1394            .entry(view_number)
1395            .or_default()
1396            .entry(disperse.data.recipient_key().clone())
1397            .or_default()
1398            .insert(disperse.data.target_epoch(), disperse);
1399    }
1400
1401    /// Add a new entry to the da_certs map.
1402    pub fn update_saved_da_certs(&mut self, view_number: ViewNumber, cert: DaCertificate2<TYPES>) {
1403        self.saved_da_certs.insert(view_number, cert);
1404    }
1405
1406    /// gather information from the parent chain of leaves
1407    /// # Errors
1408    /// If the leaf or its ancestors are not found in storage
1409    pub fn visit_leaf_ancestors<F>(
1410        &self,
1411        start_from: ViewNumber,
1412        terminator: Terminator<ViewNumber>,
1413        ok_when_finished: bool,
1414        mut f: F,
1415    ) -> std::result::Result<(), HotShotError<TYPES>>
1416    where
1417        F: FnMut(
1418            &Leaf2<TYPES>,
1419            Arc<<TYPES as NodeType>::ValidatedState>,
1420            Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
1421        ) -> bool,
1422    {
1423        let mut next_leaf = if let Some(view) = self.validated_state_map.get(&start_from) {
1424            view.leaf_commitment().ok_or_else(|| {
1425                HotShotError::InvalidState(format!(
1426                    "Visited failed view {start_from} leaf. Expected successful leaf"
1427                ))
1428            })?
1429        } else {
1430            return Err(HotShotError::InvalidState(format!(
1431                "View {start_from} leaf does not exist in state map "
1432            )));
1433        };
1434
1435        while let Some(leaf) = self.saved_leaves.get(&next_leaf) {
1436            let view = leaf.view_number();
1437            if let (Some(state), delta) = self.state_and_delta(view) {
1438                if let Terminator::Exclusive(stop_before) = terminator
1439                    && stop_before == view
1440                {
1441                    if ok_when_finished {
1442                        return Ok(());
1443                    }
1444                    break;
1445                }
1446                next_leaf = leaf.parent_commitment();
1447                if !f(leaf, state, delta) {
1448                    return Ok(());
1449                }
1450                if let Terminator::Inclusive(stop_after) = terminator
1451                    && stop_after == view
1452                {
1453                    if ok_when_finished {
1454                        return Ok(());
1455                    }
1456                    break;
1457                }
1458            } else {
1459                return Err(HotShotError::InvalidState(format!(
1460                    "View {view} state does not exist in state map"
1461                )));
1462            }
1463        }
1464        Err(HotShotError::MissingLeaf(next_leaf))
1465    }
1466
1467    /// Garbage collects based on state change right now, this removes from both the
1468    /// `saved_payloads` and `validated_state_map` fields of `Consensus`.
1469    /// # Panics
1470    /// On inconsistent stored entries
1471    pub fn collect_garbage(&mut self, old_anchor_view: ViewNumber, new_anchor_view: ViewNumber) {
1472        // Nothing to collect
1473        if new_anchor_view <= old_anchor_view {
1474            return;
1475        }
1476        let gc_view = ViewNumber::new(new_anchor_view.saturating_sub(1));
1477        // state check
1478        let anchor_entry = self
1479            .validated_state_map
1480            .iter()
1481            .next()
1482            .expect("INCONSISTENT STATE: anchor leaf not in state map!");
1483        if **anchor_entry.0 != old_anchor_view.saturating_sub(1) {
1484            tracing::info!(
1485                "Something about GC has failed. Older leaf exists than the previous anchor leaf."
1486            );
1487        }
1488        // perform gc
1489        self.saved_da_certs
1490            .retain(|view_number, _| *view_number >= old_anchor_view);
1491        self.validated_state_map
1492            .range(..gc_view)
1493            .filter_map(|(_view_number, view)| view.leaf_commitment())
1494            .for_each(|leaf| {
1495                self.saved_leaves.remove(&leaf);
1496            });
1497        self.validated_state_map = self.validated_state_map.split_off(&gc_view);
1498        self.saved_payloads = self.saved_payloads.split_off(&gc_view);
1499        self.vid_shares = self.vid_shares.split_off(&gc_view);
1500        self.last_proposals = self.last_proposals.split_off(&gc_view);
1501    }
1502
1503    /// Gets the last decided leaf.
1504    ///
1505    /// # Panics
1506    /// if the last decided view's leaf does not exist in the state map or saved leaves, which
1507    /// should never happen.
1508    #[must_use]
1509    pub fn decided_leaf(&self) -> Leaf2<TYPES> {
1510        let decided_view_num = self.last_decided_view;
1511        let view = self.validated_state_map.get(&decided_view_num).unwrap();
1512        let leaf = view
1513            .leaf_commitment()
1514            .expect("Decided leaf not found! Consensus internally inconsistent");
1515        self.saved_leaves.get(&leaf).unwrap().clone()
1516    }
1517
1518    pub fn undecided_leaves(&self) -> Vec<Leaf2<TYPES>> {
1519        self.saved_leaves.values().cloned().collect::<Vec<_>>()
1520    }
1521
1522    /// Gets the validated state with the given view number, if in the state map.
1523    #[must_use]
1524    pub fn state(&self, view_number: ViewNumber) -> Option<&Arc<TYPES::ValidatedState>> {
1525        match self.validated_state_map.get(&view_number) {
1526            Some(view) => view.state(),
1527            None => None,
1528        }
1529    }
1530
1531    /// Gets the validated state and state delta with the given view number, if in the state map.
1532    #[must_use]
1533    pub fn state_and_delta(&self, view_number: ViewNumber) -> StateAndDelta<TYPES> {
1534        match self.validated_state_map.get(&view_number) {
1535            Some(view) => view.state_and_delta(),
1536            None => (None, None),
1537        }
1538    }
1539
1540    /// Gets the last decided validated state.
1541    ///
1542    /// # Panics
1543    /// If the last decided view's state does not exist in the state map, which should never
1544    /// happen.
1545    #[must_use]
1546    pub fn decided_state(&self) -> Arc<TYPES::ValidatedState> {
1547        let decided_view_num = self.last_decided_view;
1548        self.state_and_delta(decided_view_num)
1549            .0
1550            .expect("Decided state not found! Consensus internally inconsistent")
1551    }
1552
1553    /// Associated helper function:
1554    /// Takes `LockedConsensusState` which will be updated; locks it for read and write accordingly.
1555    /// Calculates `VidDisperse` based on the view, the txns and the membership,
1556    /// and updates `vid_shares` map with the signed `VidDisperseShare` proposals.
1557    /// Returned `Option` indicates whether the update has actually happened or not.
1558    #[instrument(skip_all, target = "Consensus", fields(view = *view))]
1559    pub async fn calculate_and_update_vid(
1560        consensus: OuterConsensus<TYPES>,
1561        view: ViewNumber,
1562        target_epoch: Option<EpochNumber>,
1563        membership_coordinator: EpochMembershipCoordinator<TYPES>,
1564        private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
1565        upgrade_lock: &UpgradeLock<TYPES>,
1566    ) -> Option<()> {
1567        let payload_with_metadata = Arc::clone(consensus.read().await.saved_payloads().get(&view)?);
1568        let epoch = consensus
1569            .read()
1570            .await
1571            .validated_state_map()
1572            .get(&view)?
1573            .view_inner
1574            .epoch()?;
1575
1576        let VidDisperseAndDuration {
1577            disperse: vid,
1578            duration: disperse_duration,
1579        } = VidDisperse::calculate_vid_disperse(
1580            &payload_with_metadata.payload,
1581            &membership_coordinator,
1582            view,
1583            target_epoch,
1584            epoch,
1585            &payload_with_metadata.metadata,
1586            upgrade_lock,
1587        )
1588        .await
1589        .ok()?;
1590
1591        let mut consensus_writer = consensus.write().await;
1592        consensus_writer
1593            .metrics
1594            .vid_disperse_duration
1595            .add_point(disperse_duration.as_secs_f64());
1596        for share in vid.to_shares() {
1597            if let Some(prop) = share.to_proposal(private_key) {
1598                consensus_writer.update_vid_shares(view, prop);
1599            }
1600        }
1601
1602        Some(())
1603    }
1604    /// Returns true if a given leaf is for the epoch transition
1605    pub fn is_epoch_transition(&self, leaf_commit: LeafCommitment<TYPES>) -> bool {
1606        let Some(leaf) = self.saved_leaves.get(&leaf_commit) else {
1607            tracing::trace!("We don't have a leaf corresponding to the leaf commit");
1608            return false;
1609        };
1610        let block_height = leaf.height();
1611        is_epoch_transition(block_height, self.epoch_height)
1612    }
1613
1614    /// Returns true if our high QC is for one of the epoch transition blocks
1615    pub fn is_high_qc_for_epoch_transition(&self) -> bool {
1616        let Some(block_height) = self.high_qc().data.block_number else {
1617            return false;
1618        };
1619        is_epoch_transition(block_height, self.epoch_height)
1620    }
1621
1622    /// Returns true if the `parent_leaf` formed an eQC for the previous epoch to the `proposed_leaf`
1623    pub fn check_eqc(&self, proposed_leaf: &Leaf2<TYPES>, parent_leaf: &Leaf2<TYPES>) -> bool {
1624        if parent_leaf.view_number() == ViewNumber::genesis() {
1625            return true;
1626        }
1627        let new_epoch = epoch_from_block_number(proposed_leaf.height(), self.epoch_height);
1628        let old_epoch = epoch_from_block_number(parent_leaf.height(), self.epoch_height);
1629
1630        new_epoch - 1 == old_epoch && is_last_block(parent_leaf.height(), self.epoch_height)
1631    }
1632}
1633
1634/// Alias for the block payload commitment and the associated metadata. The primary data
1635/// needed in order to submit a proposal.
1636#[derive(Eq, PartialEq, Debug, Clone)]
1637pub struct CommitmentAndMetadata<TYPES: NodeType> {
1638    /// Vid Commitment
1639    pub commitment: VidCommitment,
1640    /// Builder Commitment
1641    pub builder_commitment: BuilderCommitment,
1642    /// Metadata for the block payload
1643    pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
1644    /// Builder fee data
1645    pub fees: Vec1<BuilderFee<TYPES>>,
1646    /// View number this block is for
1647    pub block_view: ViewNumber,
1648}