1use std::{
2 collections::{HashMap, HashSet, hash_map::Entry},
3 sync::Arc,
4};
5
6use alloy_primitives::U256;
7use async_broadcast::{InactiveReceiver, Sender, broadcast};
8use committable::Commitment;
9use either::Either;
10use hotshot_utils::{anytrace::*, *};
11use parking_lot::{Mutex, RwLock};
12use sha2::{Digest, Sha256};
13use tokio_util::sync::CancellationToken;
14use versions::DRB_FIX_VERSION;
15
16use crate::{
17 PeerConfig, PeerConnectInfo,
18 data::{BlockNumber, EpochNumber, Leaf2, ViewNumber},
19 drb::{DrbDifficultySelectorFn, DrbInput, DrbResult, compute_drb_result},
20 traits::{
21 block_contents::BlockHeader,
22 election::{Membership, MembershipSnapshot, NonEpochMembershipSnapshot},
23 node_implementation::NodeType,
24 signature_key::StakeTableEntryType,
25 storage::{
26 LoadDrbProgressFn, Storage, StoreDrbProgressFn, StoreDrbResultFn, load_drb_progress_fn,
27 store_drb_progress_fn, store_drb_result_fn,
28 },
29 },
30};
31
32type EpochMap<TYPES> = HashMap<EpochNumber, InactiveReceiver<Result<EpochMembership<TYPES>>>>;
33
34type DrbMap = HashSet<EpochNumber>;
35
36type DrbCancelMap = HashMap<EpochNumber, CancellationToken>;
41
42type EpochSender<TYPES> = (EpochNumber, Sender<Result<EpochMembership<TYPES>>>);
43
44type Snapshot<T> = <<T as NodeType>::Membership as Membership<T>>::Snapshot;
46
47type SnapshotStakeTableHash<T> = <Snapshot<T> as MembershipSnapshot<T>>::StakeTableHash;
50
51pub struct EpochMembershipCoordinator<TYPES: NodeType> {
53 membership: Arc<TYPES::Membership>,
54 catchup_map: Arc<Mutex<EpochMap<TYPES>>>,
55 drb_calculation_map: Arc<Mutex<DrbMap>>,
56 drb_cancel_map: Arc<Mutex<DrbCancelMap>>,
57 epoch_height: BlockNumber,
58 store_drb_progress_fn: StoreDrbProgressFn,
59 load_drb_progress_fn: LoadDrbProgressFn,
60 store_drb_result_fn: StoreDrbResultFn,
61 drb_difficulty_selector: Arc<RwLock<Option<DrbDifficultySelectorFn>>>,
62}
63
64impl<TYPES: NodeType> Clone for EpochMembershipCoordinator<TYPES> {
65 fn clone(&self) -> Self {
66 Self {
67 membership: Arc::clone(&self.membership),
68 catchup_map: Arc::clone(&self.catchup_map),
69 drb_calculation_map: Arc::clone(&self.drb_calculation_map),
70 drb_cancel_map: Arc::clone(&self.drb_cancel_map),
71 epoch_height: self.epoch_height,
72 store_drb_progress_fn: Arc::clone(&self.store_drb_progress_fn),
73 load_drb_progress_fn: Arc::clone(&self.load_drb_progress_fn),
74 store_drb_result_fn: self.store_drb_result_fn.clone(),
75 drb_difficulty_selector: Arc::clone(&self.drb_difficulty_selector),
76 }
77 }
78}
79
80impl<TYPES: NodeType> EpochMembershipCoordinator<TYPES> {
81 pub fn new<M, S, B>(membership: M, epoch_height: B, storage: &S) -> Self
82 where
83 M: Into<Arc<TYPES::Membership>>,
84 B: Into<BlockNumber>,
85 S: Storage<TYPES>,
86 {
87 Self {
88 membership: membership.into(),
89 catchup_map: Arc::default(),
90 drb_calculation_map: Arc::default(),
91 drb_cancel_map: Arc::default(),
92 epoch_height: epoch_height.into(),
93 store_drb_progress_fn: store_drb_progress_fn(storage.clone()),
94 load_drb_progress_fn: load_drb_progress_fn(storage.clone()),
95 store_drb_result_fn: store_drb_result_fn(storage.clone()),
96 drb_difficulty_selector: Arc::new(RwLock::new(None)),
97 }
98 }
99
100 pub fn epoch_height(&self) -> BlockNumber {
101 self.epoch_height
102 }
103
104 pub fn membership(&self) -> &TYPES::Membership {
106 &self.membership
107 }
108
109 pub async fn add_epoch_root(
111 &self,
112 header: TYPES::BlockHeader,
113 ) -> std::result::Result<(), <TYPES::Membership as Membership<TYPES>>::Error> {
114 self.membership.add_epoch_root(header, self).await
115 }
116
117 pub async fn get_epoch_root(
120 &self,
121 epoch: EpochNumber,
122 ) -> std::result::Result<Leaf2<TYPES>, <TYPES::Membership as Membership<TYPES>>::Error> {
123 self.membership.get_epoch_root(epoch, self).await
124 }
125
126 pub async fn get_epoch_drb(
128 &self,
129 epoch: EpochNumber,
130 ) -> std::result::Result<DrbResult, <TYPES::Membership as Membership<TYPES>>::Error> {
131 self.membership.get_epoch_drb(epoch, self).await
132 }
133
134 pub fn set_drb_difficulty_selector(&self, f: DrbDifficultySelectorFn) {
136 let mut drb_difficulty_selector_writer = self.drb_difficulty_selector.write();
137 *drb_difficulty_selector_writer = Some(f);
138 }
139
140 pub fn membership_for_epoch(
143 &self,
144 maybe_epoch: Option<EpochNumber>,
145 ) -> Result<EpochMembership<TYPES>> {
146 let Some(epoch) = maybe_epoch else {
147 return Ok(EpochMembership {
148 coordinator: self.clone(),
149 snapshot: EpochMembershipSnapshot::NonEpoch(self.membership.non_epoch_snapshot()),
150 });
151 };
152 let Some(first_epoch) = self.membership.first_epoch() else {
153 return Err(error!(
154 "membership_for_epoch called with epoch {epoch:?} but first_epoch is unset"
155 ));
156 };
157 if epoch < first_epoch {
158 return Err(error!(
159 "membership_for_epoch called with epoch {epoch:?} before first_epoch {first_epoch}"
160 ));
161 }
162 if let Some(snapshot) = self.membership.snapshot(epoch)
163 && snapshot.has_drb()
164 {
165 return Ok(EpochMembership {
166 coordinator: self.clone(),
167 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
168 });
169 }
170 let mut catchup_map = self.catchup_map.lock();
171 match catchup_map.entry(epoch) {
172 Entry::Occupied(_) => Err(warn!(
173 "Randomized stake table for epoch {epoch:?} unavailable. Catchup already in \
174 progress"
175 )),
176 Entry::Vacant(e) => {
177 let coordinator = self.clone();
178 let (tx, rx) = broadcast(1);
179 e.insert(rx.deactivate());
180 drop(catchup_map);
181 spawn_catchup(coordinator, epoch, tx);
182 Err(warn!(
183 "Randomized stake table for epoch {epoch:?} unavailable. Starting catchup"
184 ))
185 },
186 }
187 }
188
189 pub fn stake_table_for_epoch(&self, e: Option<EpochNumber>) -> Result<EpochMembership<TYPES>> {
192 let Some(epoch) = e else {
193 return Ok(EpochMembership {
194 coordinator: self.clone(),
195 snapshot: EpochMembershipSnapshot::NonEpoch(self.membership.non_epoch_snapshot()),
196 });
197 };
198 let Some(first_epoch) = self.membership.first_epoch() else {
199 return Err(error!(
200 "stake_table_for_epoch called with epoch {epoch:?} but first_epoch is unset"
201 ));
202 };
203 if epoch < first_epoch {
204 return Err(error!(
205 "stake_table_for_epoch called with epoch {epoch:?} before first_epoch \
206 {first_epoch}"
207 ));
208 }
209 if let Some(snapshot) = self.membership.snapshot(epoch) {
210 return Ok(EpochMembership {
211 coordinator: self.clone(),
212 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
213 });
214 }
215 let mut catchup_map = self.catchup_map.lock();
216 match catchup_map.entry(epoch) {
217 Entry::Occupied(_) => Err(warn!(
218 "Stake table for epoch {epoch:?} unavailable. Catchup already in progress"
219 )),
220 Entry::Vacant(e) => {
221 let coordinator = self.clone();
222 let (tx, rx) = broadcast(1);
223 e.insert(rx.deactivate());
224 drop(catchup_map);
225 spawn_catchup(coordinator, epoch, tx);
226
227 Err(warn!(
228 "Stake table for epoch {epoch:?} unavailable. Starting catchup"
229 ))
230 },
231 }
232 }
233
234 pub fn epoch_peers(
246 &self,
247 e: Option<EpochNumber>,
248 ) -> Option<HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>>> {
249 let membership = self.stake_table_for_epoch(e).ok()?;
250 let mut out: HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>> = HashMap::new();
251 let mut merge =
252 |key: TYPES::SignatureKey, info: Option<PeerConnectInfo>| match out.entry(key) {
253 Entry::Vacant(slot) => {
254 slot.insert(info);
255 },
256 Entry::Occupied(mut slot) => {
257 if slot.get().is_none() && info.is_some() {
258 slot.insert(info);
259 }
260 },
261 };
262 if let Some(snap) = membership.snapshot() {
263 for m in snap.stake_table().chain(snap.da_stake_table()) {
264 merge(m.stake_table_entry.public_key(), m.connect_info.clone());
265 }
266 } else {
267 let snap = membership.non_epoch_snapshot()?;
268 for m in snap.stake_table().chain(snap.da_stake_table()) {
269 merge(m.stake_table_entry.public_key(), m.connect_info.clone());
270 }
271 }
272 Some(out)
273 }
274
275 pub fn window_peers(&self, e: EpochNumber) -> HashMap<TYPES::SignatureKey, PeerConnectInfo> {
284 let curr = self.epoch_peers(Some(e)).unwrap_or_default();
285 let prev = if *e > 0 {
286 self.epoch_peers(Some(e - 1)).unwrap_or_default()
287 } else {
288 HashMap::new()
289 };
290 let next = self.epoch_peers(Some(e + 1)).unwrap_or_default();
291
292 let mut merged: HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>> = prev;
294 for (k, v) in curr.into_iter().chain(next) {
295 merged.insert(k, v);
296 }
297
298 merged
299 .into_iter()
300 .filter_map(|(k, v)| v.map(|info| (k, info)))
301 .collect()
302 }
303
304 #[allow(clippy::await_holding_lock)]
324 async fn catchup(self, epoch: EpochNumber, epoch_tx: Sender<Result<EpochMembership<TYPES>>>) {
325 let mut fetch_epochs = vec![];
327
328 let mut try_epoch = EpochNumber::new(epoch.saturating_sub(1));
329 let maybe_first_epoch = self.membership.first_epoch();
330 let Some(first_epoch) = maybe_first_epoch else {
331 let err = anytrace::error!(
332 "We got a catchup request for epoch {epoch:?} but the first epoch is not set"
333 );
334 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
335 return;
336 };
337
338 loop {
340 let has_stake_table = self.membership.snapshot(try_epoch).is_some();
341 if has_stake_table {
342 if try_epoch <= EpochNumber::new(epoch.saturating_sub(2)) {
345 break;
346 }
347 try_epoch = EpochNumber::new(try_epoch.saturating_sub(1));
348 } else {
349 if try_epoch <= first_epoch + 1 {
350 let err = anytrace::error!(
351 "We are trying to catchup to an epoch lower than the second epoch! This \
352 means the initial stake table is missing!"
353 );
354 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
355 return;
356 }
357 let mut map_lock = self.catchup_map.lock();
359 match map_lock
360 .get(&try_epoch)
361 .map(InactiveReceiver::activate_cloned)
362 {
363 Some(mut rx) => {
364 drop(map_lock);
367 if let Ok(Ok(_)) = rx.recv_direct().await {
368 break;
369 };
370 },
372 _ => {
373 let (mut tx, rx) = broadcast(1);
376 tx.set_overflow(true);
377 map_lock.insert(try_epoch, rx.deactivate());
378 drop(map_lock);
379 fetch_epochs.push((try_epoch, tx));
380 try_epoch = EpochNumber::new(try_epoch.saturating_sub(1));
381 },
382 }
383 };
384 }
385
386 let epochs = fetch_epochs.iter().map(|(e, _)| e).collect::<Vec<_>>();
387 tracing::warn!("Fetching stake tables for epochs: {epochs:?}");
388
389 while let Some((current_fetch_epoch, tx)) = fetch_epochs.pop() {
391 match self.fetch_stake_table(current_fetch_epoch).await {
392 Ok(_) => {},
393 Err(err) => {
394 fetch_epochs.push((current_fetch_epoch, tx));
395 self.catchup_cleanup(epoch, epoch_tx, fetch_epochs, err);
396 return;
397 },
398 };
399
400 let Some(snapshot) = self.membership.snapshot(current_fetch_epoch) else {
405 let err = anytrace::error!(
406 "snapshot for epoch {current_fetch_epoch} unavailable after fetch_stake_table"
407 );
408 fetch_epochs.push((current_fetch_epoch, tx));
409 self.catchup_cleanup(epoch, epoch_tx, fetch_epochs, err);
410 return;
411 };
412 let mem = EpochMembership {
413 coordinator: self.clone(),
414 snapshot: EpochMembershipSnapshot::Epoch {
415 epoch: current_fetch_epoch,
416 snapshot,
417 },
418 };
419 if let Ok(Some(res)) = tx.try_broadcast(Ok(mem)) {
420 tracing::warn!(
421 "The catchup channel for epoch {} was overflown, dropped message {:?}",
422 current_fetch_epoch,
423 res.map(|em| em.epoch())
424 );
425 }
426
427 self.catchup_map.lock().remove(¤t_fetch_epoch);
429 }
430
431 let root_leaf = match self.fetch_stake_table(epoch).await {
432 Ok(root_leaf) => root_leaf,
433 Err(err) => {
434 tracing::error!("Failed to fetch stake table for epoch {epoch:?}: {err:?}");
435 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
436 return;
437 },
438 };
439
440 match self.get_epoch_drb(epoch).await {
441 Ok(drb_result) => {
442 tracing::warn!(
443 ?drb_result,
444 "DRB result for epoch {epoch:?} retrieved from peers. Updating membership."
445 );
446 self.membership.add_drb_result(epoch, drb_result);
447 },
448 Err(err) => {
449 tracing::warn!(
450 "Recalculating missing DRB result for epoch {}. Catchup failed with error: {}",
451 epoch,
452 err
453 );
454
455 let result = self.compute_drb_result(epoch, root_leaf).await;
456
457 log!(result);
458
459 if let Err(err) = result {
460 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
461 return;
462 }
463 },
464 };
465
466 let Some(snapshot) = self.membership.snapshot(epoch) else {
469 let err = anytrace::error!(
470 "snapshot for epoch {epoch} unavailable after fetch_stake_table + DRB"
471 );
472 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
473 return;
474 };
475 let mem = EpochMembership {
476 coordinator: self.clone(),
477 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
478 };
479 if let Ok(Some(res)) = epoch_tx.try_broadcast(Ok(mem)) {
480 tracing::warn!(
481 "The catchup channel for epoch {} was overflown, dropped message {:?}",
482 epoch,
483 res.map(|em| em.epoch())
484 );
485 }
486
487 self.catchup_map.lock().remove(&epoch);
489 }
490
491 pub async fn wait_for_stake_table(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
498 match self.stake_table_for_epoch(Some(epoch)) {
499 Ok(mem) => Ok(mem),
500 Err(_) => self.wait_for_catchup(epoch).await,
501 }
502 }
503
504 pub async fn wait_for_catchup(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
510 let maybe_receiver = self
511 .catchup_map
512 .lock()
513 .get(&epoch)
514 .map(InactiveReceiver::activate_cloned);
515 let Some(mut rx) = maybe_receiver else {
516 if let Some(snapshot) = self.membership.snapshot(epoch) {
518 return Ok(EpochMembership {
519 coordinator: self.clone(),
520 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
521 });
522 }
523 return Err(anytrace::error!(
524 "No catchup in progress for epoch {epoch} and we don't have a stake table for it"
525 ));
526 };
527 let Ok(Ok(mem)) = rx.recv_direct().await else {
528 return Err(anytrace::error!("Catchup for epoch {epoch} failed"));
529 };
530 Ok(mem)
531 }
532
533 fn catchup_cleanup(
540 &self,
541 req_epoch: EpochNumber,
542 epoch_tx: Sender<Result<EpochMembership<TYPES>>>,
543 mut cancel_epochs: Vec<EpochSender<TYPES>>,
544 err: Error,
545 ) {
546 cancel_epochs.push((req_epoch, epoch_tx));
548
549 tracing::error!(
550 "catchup for epoch {req_epoch:?} failed: {err:?}. Canceling catchup for epochs: {:?}",
551 cancel_epochs.iter().map(|(e, _)| e).collect::<Vec<_>>()
552 );
553
554 {
555 let mut map_lock = self.catchup_map.lock();
556 for (epoch, _) in cancel_epochs.iter() {
557 map_lock.remove(epoch);
559 }
560 }
561
562 for (cancel_epoch, tx) in cancel_epochs {
563 if let Ok(Some(res)) = tx.try_broadcast(Err(err.clone())) {
565 tracing::warn!(
566 "The catchup channel for epoch {} was overflown during cleanup, dropped \
567 message {:?}",
568 cancel_epoch,
569 res.map(|em| em.epoch())
570 );
571 }
572 }
573 }
574
575 async fn fetch_stake_table(&self, epoch: EpochNumber) -> Result<Leaf2<TYPES>> {
590 let root_epoch = EpochNumber::new(epoch.saturating_sub(2));
591 let Ok(root_membership) = self.stake_table_for_epoch(Some(root_epoch)) else {
592 return Err(anytrace::error!(
593 "We tried to fetch stake table for epoch {epoch:?} but we don't have its root \
594 epoch {root_epoch:?}. This should not happen"
595 ));
596 };
597
598 let Ok(root_leaf) = root_membership.get_epoch_root().await else {
601 return Err(anytrace::error!(
602 "get epoch root leaf failed for epoch {root_epoch:?}"
603 ));
604 };
605
606 self.add_epoch_root(root_leaf.block_header().clone())
607 .await
608 .map_err(|e| {
609 anytrace::error!("Failed to add epoch root for epoch {epoch:?} to membership: {e}")
610 })?;
611
612 Ok(root_leaf)
613 }
614
615 pub async fn compute_drb_result(
616 &self,
617 epoch: EpochNumber,
618 root_leaf: Leaf2<TYPES>,
619 ) -> Result<DrbResult> {
620 let cancel_token = {
621 let mut drb_calculation_map_lock = self.drb_calculation_map.lock();
622
623 if drb_calculation_map_lock.contains(&epoch) {
624 return Err(anytrace::debug!(
625 "DRB calculation for epoch {} already in progress",
626 epoch
627 ));
628 }
629 drb_calculation_map_lock.insert(epoch);
630
631 let token = CancellationToken::new();
632 self.drb_cancel_map.lock().insert(epoch, token.clone());
633 token
634 };
635
636 let Ok(drb_seed_input_vec) = bincode::serialize(&root_leaf.justify_qc().signatures) else {
637 self.clear_drb_state(epoch);
638 return Err(anytrace::error!(
639 "Failed to serialize the QC signature for leaf {root_leaf:?}"
640 ));
641 };
642
643 let Some(drb_difficulty_selector) = self.drb_difficulty_selector.read().clone() else {
644 self.clear_drb_state(epoch);
645 return Err(anytrace::error!(
646 "The DRB difficulty selector is missing from the epoch membership coordinator. \
647 This node will not be able to spawn any DRB calculation tasks from catchup."
648 ));
649 };
650
651 let drb_difficulty = drb_difficulty_selector(root_leaf.block_header().version()).await;
652
653 let mut drb_seed_input = [0u8; 32];
654
655 if root_leaf.block_header().version() >= DRB_FIX_VERSION {
656 drb_seed_input = Sha256::digest(&drb_seed_input_vec).into();
657 } else {
658 let len = drb_seed_input_vec.len().min(32);
659 drb_seed_input[..len].copy_from_slice(&drb_seed_input_vec[..len]);
660 }
661
662 let drb_input = DrbInput {
663 epoch: *epoch,
664 iteration: 0,
665 value: drb_seed_input,
666 difficulty_level: drb_difficulty,
667 };
668
669 let store_drb_progress_fn = self.store_drb_progress_fn.clone();
670 let load_drb_progress_fn = self.load_drb_progress_fn.clone();
671
672 let drb = match compute_drb_result(
673 drb_input,
674 store_drb_progress_fn,
675 load_drb_progress_fn,
676 cancel_token,
677 )
678 .await
679 {
680 Some(drb) => drb,
681 None => {
682 self.clear_drb_state(epoch);
683 return self.get_epoch_drb(epoch).await.map_err(|e| {
684 anytrace::error!(
685 "DRB calculation for epoch {epoch} was cancelled but no externally \
686 supplied result is available: {e}"
687 )
688 });
689 },
690 };
691
692 self.clear_drb_state(epoch);
693
694 tracing::info!("Writing drb result from catchup to storage for epoch {epoch}: {drb:?}");
695 if let Err(e) = (self.store_drb_result_fn)(epoch, drb).await {
696 tracing::warn!("Failed to add drb result to storage: {e}");
697 }
698 self.membership.add_drb_result(epoch, drb);
699
700 Ok(drb)
701 }
702
703 pub fn supply_drb(&self, epoch: EpochNumber, drb: DrbResult) {
713 if self.membership.snapshot(epoch).is_none() {
714 tracing::error!(
715 "supply_drb called for epoch {epoch} but stake table not yet loaded; dropping \
716 externally-supplied DRB and relying on in-flight catchup"
717 );
718 return;
719 }
720 self.membership.add_drb_result(epoch, drb);
721 let maybe_token = self.drb_cancel_map.lock().remove(&epoch);
722 if let Some(token) = maybe_token {
723 token.cancel();
724 }
725 let store_drb_result_fn = self.store_drb_result_fn.clone();
726 tokio::spawn(async move {
727 tracing::info!(
728 "Writing externally supplied drb result to storage for epoch {epoch}: {drb:?}"
729 );
730 if let Err(e) = store_drb_result_fn(epoch, drb).await {
731 tracing::warn!("Failed to add externally supplied drb result to storage: {e}");
732 }
733 });
734 }
735
736 fn clear_drb_state(&self, epoch: EpochNumber) {
739 self.drb_calculation_map.lock().remove(&epoch);
740 self.drb_cancel_map.lock().remove(&epoch);
741 }
742
743 pub fn cancel_all_drb(&self) {
745 let tokens: Vec<_> = self.drb_cancel_map.lock().drain().map(|(_, t)| t).collect();
746 for token in tokens {
747 token.cancel();
748 }
749 }
750}
751
752fn spawn_catchup<T: NodeType>(
753 coordinator: EpochMembershipCoordinator<T>,
754 epoch: EpochNumber,
755 epoch_tx: Sender<Result<EpochMembership<T>>>,
756) {
757 tokio::spawn(async move {
758 coordinator.clone().catchup(epoch, epoch_tx).await;
759 });
760}
761
762pub struct EpochMembership<TYPES: NodeType> {
766 snapshot: EpochMembershipSnapshot<TYPES>,
768 pub coordinator: EpochMembershipCoordinator<TYPES>,
771}
772
773enum EpochMembershipSnapshot<TYPES: NodeType> {
774 Epoch {
775 epoch: EpochNumber,
776 snapshot: <TYPES::Membership as Membership<TYPES>>::Snapshot,
777 },
778 NonEpoch(<TYPES::Membership as Membership<TYPES>>::NonEpochSnapshot),
779}
780
781impl<TYPES: NodeType> Clone for EpochMembershipSnapshot<TYPES> {
782 fn clone(&self) -> Self {
783 match self {
784 Self::Epoch { epoch, snapshot } => Self::Epoch {
785 epoch: *epoch,
786 snapshot: snapshot.clone(),
787 },
788 Self::NonEpoch(s) => Self::NonEpoch(s.clone()),
789 }
790 }
791}
792
793impl<TYPES: NodeType> Clone for EpochMembership<TYPES> {
794 fn clone(&self) -> Self {
795 Self {
796 coordinator: self.coordinator.clone(),
797 snapshot: self.snapshot.clone(),
798 }
799 }
800}
801
802impl<TYPES: NodeType> EpochMembership<TYPES> {
803 pub fn epoch(&self) -> Option<EpochNumber> {
804 match &self.snapshot {
805 EpochMembershipSnapshot::Epoch { epoch, .. } => Some(*epoch),
806 EpochMembershipSnapshot::NonEpoch(_) => None,
807 }
808 }
809
810 pub fn next_epoch(&self) -> Result<Self> {
811 let epoch = self
812 .epoch()
813 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
814 self.coordinator.membership_for_epoch(Some(epoch + 1))
815 }
816
817 pub fn next_epoch_stake_table(&self) -> Result<Self> {
818 let epoch = self
819 .epoch()
820 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
821 self.coordinator.stake_table_for_epoch(Some(epoch + 1))
822 }
823
824 pub fn get_new_epoch(&self, epoch: Option<EpochNumber>) -> Result<Self> {
825 self.coordinator.membership_for_epoch(epoch)
826 }
827
828 async fn get_epoch_root(&self) -> anyhow::Result<Leaf2<TYPES>> {
829 let Some(epoch) = self.epoch() else {
830 anyhow::bail!("Cannot get root for None epoch");
831 };
832 let leaf = self.coordinator.get_epoch_root(epoch).await?;
833 Ok(leaf)
834 }
835
836 pub async fn get_epoch_drb(&self) -> Result<DrbResult> {
837 let Some(epoch) = self.epoch() else {
838 return Err(anytrace::warn!("Cannot get drb for None epoch"));
839 };
840 self.coordinator.get_epoch_drb(epoch).await.wrap()
841 }
842
843 pub fn snapshot(&self) -> Option<&<TYPES::Membership as Membership<TYPES>>::Snapshot> {
845 match &self.snapshot {
846 EpochMembershipSnapshot::Epoch { snapshot, .. } => Some(snapshot),
847 EpochMembershipSnapshot::NonEpoch(_) => None,
848 }
849 }
850
851 pub fn non_epoch_snapshot(
854 &self,
855 ) -> Option<&<TYPES::Membership as Membership<TYPES>>::NonEpochSnapshot> {
856 match &self.snapshot {
857 EpochMembershipSnapshot::NonEpoch(s) => Some(s),
858 EpochMembershipSnapshot::Epoch { .. } => None,
859 }
860 }
861
862 pub fn add_drb_result(&self, drb_result: DrbResult) {
864 if let Some(epoch) = self.epoch() {
865 self.coordinator
866 .membership
867 .add_drb_result(epoch, drb_result);
868 }
869 }
870
871 pub fn stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
881 match &self.snapshot {
882 EpochMembershipSnapshot::Epoch { snapshot, .. } => Either::Left(snapshot.stake_table()),
883 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.stake_table()),
884 }
885 }
886
887 pub fn da_stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
888 match &self.snapshot {
889 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
890 Either::Left(snapshot.da_stake_table())
891 },
892 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.da_stake_table()),
893 }
894 }
895
896 pub fn committee_members(
897 &self,
898 view: ViewNumber,
899 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
900 match &self.snapshot {
901 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
902 Either::Left(snapshot.committee_members(view))
903 },
904 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.committee_members(view)),
905 }
906 }
907
908 pub fn da_committee_members(
909 &self,
910 view: ViewNumber,
911 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
912 match &self.snapshot {
913 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
914 Either::Left(snapshot.da_committee_members(view))
915 },
916 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.da_committee_members(view)),
917 }
918 }
919
920 pub fn stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
921 match &self.snapshot {
922 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake(key),
923 EpochMembershipSnapshot::NonEpoch(s) => s.stake(key),
924 }
925 }
926
927 pub fn da_stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
928 match &self.snapshot {
929 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_stake(key),
930 EpochMembershipSnapshot::NonEpoch(s) => s.da_stake(key),
931 }
932 }
933
934 pub fn has_stake(&self, key: &TYPES::SignatureKey) -> bool {
935 match &self.snapshot {
936 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_stake(key),
937 EpochMembershipSnapshot::NonEpoch(s) => s.has_stake(key),
938 }
939 }
940
941 pub fn has_da_stake(&self, key: &TYPES::SignatureKey) -> bool {
942 match &self.snapshot {
943 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_da_stake(key),
944 EpochMembershipSnapshot::NonEpoch(s) => s.has_da_stake(key),
945 }
946 }
947
948 pub fn leader(&self, view: ViewNumber) -> Result<TYPES::SignatureKey> {
954 match &self.snapshot {
955 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.leader(view),
956 EpochMembershipSnapshot::NonEpoch(s) => s.leader(view),
957 }
958 }
959
960 pub fn lookup_leader(
966 &self,
967 view: ViewNumber,
968 ) -> std::result::Result<
969 TYPES::SignatureKey,
970 <<TYPES as NodeType>::Membership as Membership<TYPES>>::Error,
971 > {
972 match &self.snapshot {
973 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.lookup_leader(view),
974 EpochMembershipSnapshot::NonEpoch(s) => s.lookup_leader(view),
975 }
976 }
977
978 pub fn total_nodes(&self) -> usize {
979 match &self.snapshot {
980 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.total_nodes(),
981 EpochMembershipSnapshot::NonEpoch(s) => s.total_nodes(),
982 }
983 }
984
985 pub fn da_total_nodes(&self) -> usize {
986 match &self.snapshot {
987 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_total_nodes(),
988 EpochMembershipSnapshot::NonEpoch(s) => s.da_total_nodes(),
989 }
990 }
991
992 pub fn success_threshold(&self) -> U256 {
993 match &self.snapshot {
994 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.success_threshold(),
995 EpochMembershipSnapshot::NonEpoch(s) => s.success_threshold(),
996 }
997 }
998
999 pub fn da_success_threshold(&self) -> U256 {
1000 match &self.snapshot {
1001 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_success_threshold(),
1002 EpochMembershipSnapshot::NonEpoch(s) => s.da_success_threshold(),
1003 }
1004 }
1005
1006 pub fn failure_threshold(&self) -> U256 {
1007 match &self.snapshot {
1008 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.failure_threshold(),
1009 EpochMembershipSnapshot::NonEpoch(s) => s.failure_threshold(),
1010 }
1011 }
1012
1013 pub fn upgrade_threshold(&self) -> U256 {
1014 match &self.snapshot {
1015 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.upgrade_threshold(),
1016 EpochMembershipSnapshot::NonEpoch(s) => s.upgrade_threshold(),
1017 }
1018 }
1019
1020 pub fn stake_table_hash(&self) -> Option<Commitment<SnapshotStakeTableHash<TYPES>>> {
1021 match &self.snapshot {
1022 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake_table_hash(),
1023 EpochMembershipSnapshot::NonEpoch(_) => None,
1024 }
1025 }
1026}