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 || self.membership.load_stake_table(try_epoch).await;
342 if has_stake_table {
343 if try_epoch <= EpochNumber::new(epoch.saturating_sub(2)) {
346 break;
347 }
348 try_epoch = EpochNumber::new(try_epoch.saturating_sub(1));
349 } else {
350 if try_epoch <= first_epoch + 1 {
351 let err = anytrace::error!(
352 "We are trying to catchup to an epoch lower than the second epoch! This \
353 means the initial stake table is missing!"
354 );
355 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
356 return;
357 }
358 let mut map_lock = self.catchup_map.lock();
360 match map_lock
361 .get(&try_epoch)
362 .map(InactiveReceiver::activate_cloned)
363 {
364 Some(mut rx) => {
365 drop(map_lock);
368 if let Ok(Ok(_)) = rx.recv_direct().await {
369 break;
370 };
371 },
373 _ => {
374 let (mut tx, rx) = broadcast(1);
377 tx.set_overflow(true);
378 map_lock.insert(try_epoch, rx.deactivate());
379 drop(map_lock);
380 fetch_epochs.push((try_epoch, tx));
381 try_epoch = EpochNumber::new(try_epoch.saturating_sub(1));
382 },
383 }
384 };
385 }
386
387 let epochs = fetch_epochs.iter().map(|(e, _)| e).collect::<Vec<_>>();
388 tracing::warn!("Fetching stake tables for epochs: {epochs:?}");
389
390 while let Some((current_fetch_epoch, tx)) = fetch_epochs.pop() {
392 match self.fetch_stake_table(current_fetch_epoch).await {
393 Ok(_) => {},
394 Err(err) => {
395 fetch_epochs.push((current_fetch_epoch, tx));
396 self.catchup_cleanup(epoch, epoch_tx, fetch_epochs, err);
397 return;
398 },
399 };
400
401 let Some(snapshot) = self.membership.snapshot(current_fetch_epoch) else {
406 let err = anytrace::error!(
407 "snapshot for epoch {current_fetch_epoch} unavailable after fetch_stake_table"
408 );
409 fetch_epochs.push((current_fetch_epoch, tx));
410 self.catchup_cleanup(epoch, epoch_tx, fetch_epochs, err);
411 return;
412 };
413 let mem = EpochMembership {
414 coordinator: self.clone(),
415 snapshot: EpochMembershipSnapshot::Epoch {
416 epoch: current_fetch_epoch,
417 snapshot,
418 },
419 };
420 if let Ok(Some(res)) = tx.try_broadcast(Ok(mem)) {
421 tracing::warn!(
422 "The catchup channel for epoch {} was overflown, dropped message {:?}",
423 current_fetch_epoch,
424 res.map(|em| em.epoch())
425 );
426 }
427
428 self.catchup_map.lock().remove(¤t_fetch_epoch);
430 }
431
432 let root_leaf = match self.fetch_stake_table(epoch).await {
433 Ok(root_leaf) => root_leaf,
434 Err(err) => {
435 tracing::error!("Failed to fetch stake table for epoch {epoch:?}: {err:?}");
436 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
437 return;
438 },
439 };
440
441 match self.get_epoch_drb(epoch).await {
442 Ok(drb_result) => {
443 tracing::warn!(
444 ?drb_result,
445 "DRB result for epoch {epoch:?} retrieved from peers. Updating membership."
446 );
447 self.membership.add_drb_result(epoch, drb_result);
448 },
449 Err(err) => {
450 tracing::warn!(
451 "Recalculating missing DRB result for epoch {}. Catchup failed with error: {}",
452 epoch,
453 err
454 );
455
456 let result = self.compute_drb_result(epoch, root_leaf).await;
457
458 log!(result);
459
460 if let Err(err) = result {
461 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
462 return;
463 }
464 },
465 };
466
467 let Some(snapshot) = self.membership.snapshot(epoch) else {
470 let err = anytrace::error!(
471 "snapshot for epoch {epoch} unavailable after fetch_stake_table + DRB"
472 );
473 self.catchup_cleanup(epoch, epoch_tx.clone(), fetch_epochs, err);
474 return;
475 };
476 let mem = EpochMembership {
477 coordinator: self.clone(),
478 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
479 };
480 if let Ok(Some(res)) = epoch_tx.try_broadcast(Ok(mem)) {
481 tracing::warn!(
482 "The catchup channel for epoch {} was overflown, dropped message {:?}",
483 epoch,
484 res.map(|em| em.epoch())
485 );
486 }
487
488 self.catchup_map.lock().remove(&epoch);
490 }
491
492 pub async fn wait_for_stake_table(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
499 match self.stake_table_for_epoch(Some(epoch)) {
500 Ok(mem) => Ok(mem),
501 Err(_) => self.wait_for_catchup(epoch).await,
502 }
503 }
504
505 pub async fn wait_for_catchup(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
511 let maybe_receiver = self
512 .catchup_map
513 .lock()
514 .get(&epoch)
515 .map(InactiveReceiver::activate_cloned);
516 let Some(mut rx) = maybe_receiver else {
517 if let Some(snapshot) = self.membership.snapshot(epoch) {
519 return Ok(EpochMembership {
520 coordinator: self.clone(),
521 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
522 });
523 }
524 return Err(anytrace::error!(
525 "No catchup in progress for epoch {epoch} and we don't have a stake table for it"
526 ));
527 };
528 let Ok(Ok(mem)) = rx.recv_direct().await else {
529 return Err(anytrace::error!("Catchup for epoch {epoch} failed"));
530 };
531 Ok(mem)
532 }
533
534 fn catchup_cleanup(
541 &self,
542 req_epoch: EpochNumber,
543 epoch_tx: Sender<Result<EpochMembership<TYPES>>>,
544 mut cancel_epochs: Vec<EpochSender<TYPES>>,
545 err: Error,
546 ) {
547 cancel_epochs.push((req_epoch, epoch_tx));
549
550 tracing::error!(
551 "catchup for epoch {req_epoch:?} failed: {err:?}. Canceling catchup for epochs: {:?}",
552 cancel_epochs.iter().map(|(e, _)| e).collect::<Vec<_>>()
553 );
554
555 {
556 let mut map_lock = self.catchup_map.lock();
557 for (epoch, _) in cancel_epochs.iter() {
558 map_lock.remove(epoch);
560 }
561 }
562
563 for (cancel_epoch, tx) in cancel_epochs {
564 if let Ok(Some(res)) = tx.try_broadcast(Err(err.clone())) {
566 tracing::warn!(
567 "The catchup channel for epoch {} was overflown during cleanup, dropped \
568 message {:?}",
569 cancel_epoch,
570 res.map(|em| em.epoch())
571 );
572 }
573 }
574 }
575
576 async fn fetch_stake_table(&self, epoch: EpochNumber) -> Result<Leaf2<TYPES>> {
591 let root_epoch = EpochNumber::new(epoch.saturating_sub(2));
592 let Ok(root_membership) = self.stake_table_for_epoch(Some(root_epoch)) else {
593 return Err(anytrace::error!(
594 "We tried to fetch stake table for epoch {epoch:?} but we don't have its root \
595 epoch {root_epoch:?}. This should not happen"
596 ));
597 };
598
599 let Ok(root_leaf) = root_membership.get_epoch_root().await else {
602 return Err(anytrace::error!(
603 "get epoch root leaf failed for epoch {root_epoch:?}"
604 ));
605 };
606
607 self.add_epoch_root(root_leaf.block_header().clone())
608 .await
609 .map_err(|e| {
610 anytrace::error!("Failed to add epoch root for epoch {epoch:?} to membership: {e}")
611 })?;
612
613 Ok(root_leaf)
614 }
615
616 pub async fn compute_drb_result(
617 &self,
618 epoch: EpochNumber,
619 root_leaf: Leaf2<TYPES>,
620 ) -> Result<DrbResult> {
621 let cancel_token = {
622 let mut drb_calculation_map_lock = self.drb_calculation_map.lock();
623
624 if drb_calculation_map_lock.contains(&epoch) {
625 return Err(anytrace::debug!(
626 "DRB calculation for epoch {} already in progress",
627 epoch
628 ));
629 }
630 drb_calculation_map_lock.insert(epoch);
631
632 let token = CancellationToken::new();
633 self.drb_cancel_map.lock().insert(epoch, token.clone());
634 token
635 };
636
637 let Ok(drb_seed_input_vec) = bincode::serialize(&root_leaf.justify_qc().signatures) else {
638 self.clear_drb_state(epoch);
639 return Err(anytrace::error!(
640 "Failed to serialize the QC signature for leaf {root_leaf:?}"
641 ));
642 };
643
644 let Some(drb_difficulty_selector) = self.drb_difficulty_selector.read().clone() else {
645 self.clear_drb_state(epoch);
646 return Err(anytrace::error!(
647 "The DRB difficulty selector is missing from the epoch membership coordinator. \
648 This node will not be able to spawn any DRB calculation tasks from catchup."
649 ));
650 };
651
652 let drb_difficulty = drb_difficulty_selector(root_leaf.block_header().version()).await;
653
654 let mut drb_seed_input = [0u8; 32];
655
656 if root_leaf.block_header().version() >= DRB_FIX_VERSION {
657 drb_seed_input = Sha256::digest(&drb_seed_input_vec).into();
658 } else {
659 let len = drb_seed_input_vec.len().min(32);
660 drb_seed_input[..len].copy_from_slice(&drb_seed_input_vec[..len]);
661 }
662
663 let drb_input = DrbInput {
664 epoch: *epoch,
665 iteration: 0,
666 value: drb_seed_input,
667 difficulty_level: drb_difficulty,
668 };
669
670 let store_drb_progress_fn = self.store_drb_progress_fn.clone();
671 let load_drb_progress_fn = self.load_drb_progress_fn.clone();
672
673 let drb = match compute_drb_result(
674 drb_input,
675 store_drb_progress_fn,
676 load_drb_progress_fn,
677 cancel_token,
678 )
679 .await
680 {
681 Some(drb) => drb,
682 None => {
683 self.clear_drb_state(epoch);
684 return self.get_epoch_drb(epoch).await.map_err(|e| {
685 anytrace::error!(
686 "DRB calculation for epoch {epoch} was cancelled but no externally \
687 supplied result is available: {e}"
688 )
689 });
690 },
691 };
692
693 self.clear_drb_state(epoch);
694
695 tracing::info!("Writing drb result from catchup to storage for epoch {epoch}: {drb:?}");
696 if let Err(e) = (self.store_drb_result_fn)(epoch, drb).await {
697 tracing::warn!("Failed to add drb result to storage: {e}");
698 }
699 self.membership.add_drb_result(epoch, drb);
700
701 Ok(drb)
702 }
703
704 pub fn supply_drb(&self, epoch: EpochNumber, drb: DrbResult) {
714 if self.membership.snapshot(epoch).is_none() {
715 tracing::error!(
716 "supply_drb called for epoch {epoch} but stake table not yet loaded; dropping \
717 externally-supplied DRB and relying on in-flight catchup"
718 );
719 return;
720 }
721 self.membership.add_drb_result(epoch, drb);
722 let maybe_token = self.drb_cancel_map.lock().remove(&epoch);
723 if let Some(token) = maybe_token {
724 token.cancel();
725 }
726 let store_drb_result_fn = self.store_drb_result_fn.clone();
727 tokio::spawn(async move {
728 tracing::info!(
729 "Writing externally supplied drb result to storage for epoch {epoch}: {drb:?}"
730 );
731 if let Err(e) = store_drb_result_fn(epoch, drb).await {
732 tracing::warn!("Failed to add externally supplied drb result to storage: {e}");
733 }
734 });
735 }
736
737 fn clear_drb_state(&self, epoch: EpochNumber) {
740 self.drb_calculation_map.lock().remove(&epoch);
741 self.drb_cancel_map.lock().remove(&epoch);
742 }
743
744 pub fn cancel_all_drb(&self) {
746 let tokens: Vec<_> = self.drb_cancel_map.lock().drain().map(|(_, t)| t).collect();
747 for token in tokens {
748 token.cancel();
749 }
750 }
751}
752
753fn spawn_catchup<T: NodeType>(
754 coordinator: EpochMembershipCoordinator<T>,
755 epoch: EpochNumber,
756 epoch_tx: Sender<Result<EpochMembership<T>>>,
757) {
758 tokio::spawn(async move {
759 coordinator.clone().catchup(epoch, epoch_tx).await;
760 });
761}
762
763pub struct EpochMembership<TYPES: NodeType> {
767 snapshot: EpochMembershipSnapshot<TYPES>,
769 pub coordinator: EpochMembershipCoordinator<TYPES>,
772}
773
774enum EpochMembershipSnapshot<TYPES: NodeType> {
775 Epoch {
776 epoch: EpochNumber,
777 snapshot: <TYPES::Membership as Membership<TYPES>>::Snapshot,
778 },
779 NonEpoch(<TYPES::Membership as Membership<TYPES>>::NonEpochSnapshot),
780}
781
782impl<TYPES: NodeType> Clone for EpochMembershipSnapshot<TYPES> {
783 fn clone(&self) -> Self {
784 match self {
785 Self::Epoch { epoch, snapshot } => Self::Epoch {
786 epoch: *epoch,
787 snapshot: snapshot.clone(),
788 },
789 Self::NonEpoch(s) => Self::NonEpoch(s.clone()),
790 }
791 }
792}
793
794impl<TYPES: NodeType> Clone for EpochMembership<TYPES> {
795 fn clone(&self) -> Self {
796 Self {
797 coordinator: self.coordinator.clone(),
798 snapshot: self.snapshot.clone(),
799 }
800 }
801}
802
803impl<TYPES: NodeType> EpochMembership<TYPES> {
804 pub fn epoch(&self) -> Option<EpochNumber> {
805 match &self.snapshot {
806 EpochMembershipSnapshot::Epoch { epoch, .. } => Some(*epoch),
807 EpochMembershipSnapshot::NonEpoch(_) => None,
808 }
809 }
810
811 pub fn next_epoch(&self) -> Result<Self> {
812 let epoch = self
813 .epoch()
814 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
815 self.coordinator.membership_for_epoch(Some(epoch + 1))
816 }
817
818 pub fn next_epoch_stake_table(&self) -> Result<Self> {
819 let epoch = self
820 .epoch()
821 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
822 self.coordinator.stake_table_for_epoch(Some(epoch + 1))
823 }
824
825 pub fn get_new_epoch(&self, epoch: Option<EpochNumber>) -> Result<Self> {
826 self.coordinator.membership_for_epoch(epoch)
827 }
828
829 async fn get_epoch_root(&self) -> anyhow::Result<Leaf2<TYPES>> {
830 let Some(epoch) = self.epoch() else {
831 anyhow::bail!("Cannot get root for None epoch");
832 };
833 let leaf = self.coordinator.get_epoch_root(epoch).await?;
834 Ok(leaf)
835 }
836
837 pub async fn get_epoch_drb(&self) -> Result<DrbResult> {
838 let Some(epoch) = self.epoch() else {
839 return Err(anytrace::warn!("Cannot get drb for None epoch"));
840 };
841 self.coordinator.get_epoch_drb(epoch).await.wrap()
842 }
843
844 pub fn snapshot(&self) -> Option<&<TYPES::Membership as Membership<TYPES>>::Snapshot> {
846 match &self.snapshot {
847 EpochMembershipSnapshot::Epoch { snapshot, .. } => Some(snapshot),
848 EpochMembershipSnapshot::NonEpoch(_) => None,
849 }
850 }
851
852 pub fn non_epoch_snapshot(
855 &self,
856 ) -> Option<&<TYPES::Membership as Membership<TYPES>>::NonEpochSnapshot> {
857 match &self.snapshot {
858 EpochMembershipSnapshot::NonEpoch(s) => Some(s),
859 EpochMembershipSnapshot::Epoch { .. } => None,
860 }
861 }
862
863 pub fn add_drb_result(&self, drb_result: DrbResult) {
865 if let Some(epoch) = self.epoch() {
866 self.coordinator
867 .membership
868 .add_drb_result(epoch, drb_result);
869 }
870 }
871
872 pub fn stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
882 match &self.snapshot {
883 EpochMembershipSnapshot::Epoch { snapshot, .. } => Either::Left(snapshot.stake_table()),
884 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.stake_table()),
885 }
886 }
887
888 pub fn da_stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
889 match &self.snapshot {
890 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
891 Either::Left(snapshot.da_stake_table())
892 },
893 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.da_stake_table()),
894 }
895 }
896
897 pub fn committee_members(
898 &self,
899 view: ViewNumber,
900 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
901 match &self.snapshot {
902 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
903 Either::Left(snapshot.committee_members(view))
904 },
905 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.committee_members(view)),
906 }
907 }
908
909 pub fn da_committee_members(
910 &self,
911 view: ViewNumber,
912 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
913 match &self.snapshot {
914 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
915 Either::Left(snapshot.da_committee_members(view))
916 },
917 EpochMembershipSnapshot::NonEpoch(s) => Either::Right(s.da_committee_members(view)),
918 }
919 }
920
921 pub fn stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
922 match &self.snapshot {
923 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake(key),
924 EpochMembershipSnapshot::NonEpoch(s) => s.stake(key),
925 }
926 }
927
928 pub fn da_stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
929 match &self.snapshot {
930 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_stake(key),
931 EpochMembershipSnapshot::NonEpoch(s) => s.da_stake(key),
932 }
933 }
934
935 pub fn has_stake(&self, key: &TYPES::SignatureKey) -> bool {
936 match &self.snapshot {
937 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_stake(key),
938 EpochMembershipSnapshot::NonEpoch(s) => s.has_stake(key),
939 }
940 }
941
942 pub fn has_da_stake(&self, key: &TYPES::SignatureKey) -> bool {
943 match &self.snapshot {
944 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_da_stake(key),
945 EpochMembershipSnapshot::NonEpoch(s) => s.has_da_stake(key),
946 }
947 }
948
949 pub fn leader(&self, view: ViewNumber) -> Result<TYPES::SignatureKey> {
955 match &self.snapshot {
956 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.leader(view),
957 EpochMembershipSnapshot::NonEpoch(s) => s.leader(view),
958 }
959 }
960
961 pub fn lookup_leader(
967 &self,
968 view: ViewNumber,
969 ) -> std::result::Result<
970 TYPES::SignatureKey,
971 <<TYPES as NodeType>::Membership as Membership<TYPES>>::Error,
972 > {
973 match &self.snapshot {
974 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.lookup_leader(view),
975 EpochMembershipSnapshot::NonEpoch(s) => s.lookup_leader(view),
976 }
977 }
978
979 pub fn total_nodes(&self) -> usize {
980 match &self.snapshot {
981 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.total_nodes(),
982 EpochMembershipSnapshot::NonEpoch(s) => s.total_nodes(),
983 }
984 }
985
986 pub fn da_total_nodes(&self) -> usize {
987 match &self.snapshot {
988 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_total_nodes(),
989 EpochMembershipSnapshot::NonEpoch(s) => s.da_total_nodes(),
990 }
991 }
992
993 pub fn success_threshold(&self) -> U256 {
994 match &self.snapshot {
995 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.success_threshold(),
996 EpochMembershipSnapshot::NonEpoch(s) => s.success_threshold(),
997 }
998 }
999
1000 pub fn da_success_threshold(&self) -> U256 {
1001 match &self.snapshot {
1002 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_success_threshold(),
1003 EpochMembershipSnapshot::NonEpoch(s) => s.da_success_threshold(),
1004 }
1005 }
1006
1007 pub fn failure_threshold(&self) -> U256 {
1008 match &self.snapshot {
1009 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.failure_threshold(),
1010 EpochMembershipSnapshot::NonEpoch(s) => s.failure_threshold(),
1011 }
1012 }
1013
1014 pub fn upgrade_threshold(&self) -> U256 {
1015 match &self.snapshot {
1016 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.upgrade_threshold(),
1017 EpochMembershipSnapshot::NonEpoch(s) => s.upgrade_threshold(),
1018 }
1019 }
1020
1021 pub fn stake_table_hash(&self) -> Option<Commitment<SnapshotStakeTableHash<TYPES>>> {
1022 match &self.snapshot {
1023 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake_table_hash(),
1024 EpochMembershipSnapshot::NonEpoch(_) => None,
1025 }
1026 }
1027}