1use 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
55pub type CommitmentMap<T> = HashMap<Commitment<T>, T>;
57
58pub type VidShares<TYPES> = BTreeMap<
60 ViewNumber,
61 HashMap<
62 <TYPES as NodeType>::SignatureKey,
63 BTreeMap<Option<EpochNumber>, Proposal<TYPES, VidDisperseShare<TYPES>>>,
64 >,
65>;
66
67pub type LockedConsensusState<TYPES> = Arc<RwLock<Consensus<TYPES>>>;
69
70#[derive(Clone, Debug)]
72pub struct OuterConsensus<TYPES: NodeType> {
73 pub inner_consensus: LockedConsensusState<TYPES>,
75}
76
77impl<TYPES: NodeType> OuterConsensus<TYPES> {
78 pub fn new(consensus: LockedConsensusState<TYPES>) -> Self {
80 Self {
81 inner_consensus: consensus,
82 }
83 }
84
85 #[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 #[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 #[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 #[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 #[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
141pub struct ConsensusReadLockGuard<'a, TYPES: NodeType> {
143 lock_guard: RwLockReadGuard<'a, Consensus<TYPES>>,
145}
146
147impl<'a, TYPES: NodeType> ConsensusReadLockGuard<'a, TYPES> {
148 #[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
169pub struct ConsensusWriteLockGuard<'a, TYPES: NodeType> {
171 lock_guard: RwLockWriteGuard<'a, Consensus<TYPES>>,
173}
174
175impl<'a, TYPES: NodeType> ConsensusWriteLockGuard<'a, TYPES> {
176 #[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
203pub struct ConsensusUpgradableReadLockGuard<'a, TYPES: NodeType> {
205 lock_guard: ManuallyDrop<RwLockUpgradableReadGuard<'a, Consensus<TYPES>>>,
207 taken: bool,
209}
210
211impl<'a, TYPES: NodeType> ConsensusUpgradableReadLockGuard<'a, TYPES> {
212 #[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 #[instrument(skip_all, target = "ConsensusUpgradableReadLockGuard")]
223 #[allow(unused_assignments)] 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#[derive(Debug, Clone, Copy)]
254struct HotShotActionViews {
255 proposed: ViewNumber,
257 voted: ViewNumber,
259 da_proposed: ViewNumber,
261 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 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: ValidatorParticipationMap<TYPES>,
295
296 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 epoch: Option<EpochNumber>,
395
396 stake_table: HSStakeTable<TYPES>,
398
399 success_threshold: U256,
401
402 view_set: HashSet<ViewNumber>,
404
405 current_epoch_num_views: u64,
407
408 current_epoch_participation:
410 HashMap<<TYPES::SignatureKey as SignatureKey>::VerificationKeyType, u64>,
411
412 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#[derive(derive_more::Debug, Clone)]
590pub struct Consensus<TYPES: NodeType> {
591 validated_state_map: BTreeMap<ViewNumber, View<TYPES>>,
593
594 vid_shares: VidShares<TYPES>,
596
597 saved_da_certs: HashMap<ViewNumber, DaCertificate2<TYPES>>,
600
601 cur_view: ViewNumber,
603
604 cur_epoch: Option<EpochNumber>,
606
607 last_proposals: BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>>,
610
611 last_decided_view: ViewNumber,
613
614 locked_view: ViewNumber,
616
617 saved_leaves: CommitmentMap<Leaf2<TYPES>>,
621
622 last_actions: HotShotActionViews,
626
627 saved_payloads: BTreeMap<ViewNumber, Arc<PayloadWithMetadata<TYPES>>>,
631
632 high_qc: QuorumCertificate2<TYPES>,
634
635 next_epoch_high_qc: Option<NextEpochQuorumCertificate2<TYPES>>,
637
638 validator_participation: ValidatorParticipation<TYPES>,
640
641 vote_participation: VoteParticipation<TYPES>,
643
644 pub metrics: Arc<ConsensusMetricsValue>,
646
647 pub epoch_height: u64,
649
650 pub drb_difficulty: u64,
652
653 pub drb_upgrade_difficulty: u64,
655
656 transition_qc: Option<(
658 QuorumCertificate2<TYPES>,
659 NextEpochQuorumCertificate2<TYPES>,
660 )>,
661
662 pub highest_block: u64,
664 pub state_cert: Option<LightClientStateUpdateCertificateV2<TYPES>>,
666}
667
668#[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#[derive(Clone, Debug)]
677pub struct ConsensusMetricsValue {
678 pub last_synced_block_height: Box<dyn Gauge>,
680 pub last_decided_view: Box<dyn Gauge>,
682 pub last_voted_view: Box<dyn Gauge>,
684 pub last_decided_time: Box<dyn Gauge>,
686 pub current_view: Box<dyn Gauge>,
688 pub number_of_views_since_last_decide: Box<dyn Gauge>,
690 pub number_of_views_per_decide_event: Box<dyn Histogram>,
692 pub view_duration_as_leader: Box<dyn Histogram>,
694 pub invalid_qc: Box<dyn Gauge>,
696 pub outstanding_transactions: Box<dyn Gauge>,
698 pub outstanding_transactions_memory_size: Box<dyn Gauge>,
700 pub number_of_timeouts: Box<dyn Counter>,
702 pub number_of_timeouts_as_leader: Box<dyn Counter>,
704 pub number_of_empty_blocks_proposed: Box<dyn Counter>,
706 pub internal_event_queue_len: Box<dyn Gauge>,
708 pub proposal_to_decide_time: Box<dyn Histogram>,
710 pub previous_proposal_to_proposal_time: Box<dyn Histogram>,
712 pub finalized_bytes: Box<dyn Histogram>,
714 pub validate_and_apply_header_duration: Box<dyn Histogram>,
716 pub update_leaf_duration: Box<dyn Histogram>,
718 pub vid_disperse_duration: Box<dyn Histogram>,
720}
721
722impl ConsensusMetricsValue {
723 #[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 #[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 pub fn cur_view(&self) -> ViewNumber {
848 self.cur_view
849 }
850
851 pub fn cur_epoch(&self) -> Option<EpochNumber> {
853 self.cur_epoch
854 }
855
856 pub fn last_decided_view(&self) -> ViewNumber {
858 self.last_decided_view
859 }
860
861 pub fn locked_view(&self) -> ViewNumber {
863 self.locked_view
864 }
865
866 pub fn high_qc(&self) -> &QuorumCertificate2<TYPES> {
868 &self.high_qc
869 }
870
871 pub fn transition_qc(
873 &self,
874 ) -> Option<&(
875 QuorumCertificate2<TYPES>,
876 NextEpochQuorumCertificate2<TYPES>,
877 )> {
878 self.transition_qc.as_ref()
879 }
880
881 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 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 pub fn state_cert(&self) -> Option<&LightClientStateUpdateCertificateV2<TYPES>> {
922 self.state_cert.as_ref()
923 }
924
925 pub fn next_epoch_high_qc(&self) -> Option<&NextEpochQuorumCertificate2<TYPES>> {
927 self.next_epoch_high_qc.as_ref()
928 }
929
930 pub fn validated_state_map(&self) -> &BTreeMap<ViewNumber, View<TYPES>> {
932 &self.validated_state_map
933 }
934
935 pub fn saved_leaves(&self) -> &CommitmentMap<Leaf2<TYPES>> {
937 &self.saved_leaves
938 }
939
940 pub fn saved_payloads(&self) -> &BTreeMap<ViewNumber, Arc<PayloadWithMetadata<TYPES>>> {
942 &self.saved_payloads
943 }
944
945 pub fn vid_shares(&self) -> &VidShares<TYPES> {
947 &self.vid_shares
948 }
949
950 pub fn saved_da_certs(&self) -> &HashMap<ViewNumber, DaCertificate2<TYPES>> {
952 &self.saved_da_certs
953 }
954
955 pub fn last_proposals(
957 &self,
958 ) -> &BTreeMap<ViewNumber, Proposal<TYPES, QuorumProposalWrapper<TYPES>>> {
959 &self.last_proposals
960 }
961
962 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 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 pub fn update_validator_participation_epoch(&mut self, epoch: EpochNumber) {
987 self.validator_participation
988 .update_participation_epoch(epoch);
989 }
990
991 pub fn current_proposal_participation(&self) -> HashMap<TYPES::SignatureKey, f64> {
993 self.validator_participation
994 .current_proposal_participation()
995 }
996
997 pub fn proposal_participation(&self, epoch: EpochNumber) -> HashMap<TYPES::SignatureKey, f64> {
999 self.validator_participation.proposal_participation(epoch)
1000 }
1001
1002 pub fn update_vote_participation(&mut self, qc: QuorumCertificate2<TYPES>) -> Result<()> {
1004 self.vote_participation.update_participation(qc)
1005 }
1006
1007 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 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 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 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 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 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 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 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 pub fn reset_actions(&mut self) {
1128 self.last_actions = HotShotActionViews::default();
1129 }
1130
1131 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 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 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 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 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 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 fn update_saved_leaves(&mut self, leaf: Leaf2<TYPES>) {
1266 self.saved_leaves.insert(leaf.commit(), leaf);
1267 }
1268
1269 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 pub fn update_high_qc(&mut self, high_qc: QuorumCertificate2<TYPES>) -> Result<()> {
1290 if self.high_qc == high_qc {
1291 return Ok(());
1292 }
1293 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 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 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 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 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 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 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 pub fn collect_garbage(&mut self, old_anchor_view: ViewNumber, new_anchor_view: ViewNumber) {
1472 if new_anchor_view <= old_anchor_view {
1474 return;
1475 }
1476 let gc_view = ViewNumber::new(new_anchor_view.saturating_sub(1));
1477 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 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 #[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 #[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 #[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 #[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 #[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 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 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 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#[derive(Eq, PartialEq, Debug, Clone)]
1637pub struct CommitmentAndMetadata<TYPES: NodeType> {
1638 pub commitment: VidCommitment,
1640 pub builder_commitment: BuilderCommitment,
1642 pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
1644 pub fees: Vec1<BuilderFee<TYPES>>,
1646 pub block_view: ViewNumber,
1648}