1use std::{
2 collections::{HashMap, hash_map::Entry},
3 sync::Arc,
4 time::Duration,
5};
6
7use alloy_primitives::U256;
8use committable::Commitment;
9use either::Either;
10use futures::future::{BoxFuture, FutureExt, Shared};
11use hotshot_utils::anytrace::{self, Wrap};
12use parking_lot::{Mutex, RwLock};
13use sha2::{Digest, Sha256};
14use tokio::{spawn, time::timeout};
15use tokio_util::sync::CancellationToken;
16use tracing::{debug, error, info, warn};
17use versions::DRB_FIX_VERSION;
18
19use crate::{
20 PeerConfig, PeerConnectInfo,
21 data::{BlockNumber, EpochNumber, Leaf2, ViewNumber},
22 drb::{
23 DRB_PROGRESS_LOAD_TIMEOUT, DrbDifficultySelectorFn, DrbInput, DrbResult, Heartbeat,
24 compute_drb_result,
25 },
26 traits::{
27 block_contents::BlockHeader,
28 election::{GenesisStakeTables, Membership, MembershipSnapshot},
29 node_implementation::NodeType,
30 signature_key::StakeTableEntryType,
31 storage::{
32 LoadDrbProgressFn, Storage, StoreDrbProgressFn, StoreDrbResultFn, load_drb_progress_fn,
33 store_drb_progress_fn, store_drb_result_fn,
34 },
35 },
36};
37
38type Result<T> = anytrace::Result<T>;
39
40type Attempt = Shared<BoxFuture<'static, Result<()>>>;
44
45type DrbComputations = Arc<Mutex<HashMap<EpochNumber, CancellationToken>>>;
53
54type Snapshot<T> = <<T as NodeType>::Membership as Membership<T>>::Snapshot;
56
57type SnapshotStakeTableHash<T> = <Snapshot<T> as MembershipSnapshot<T>>::StakeTableHash;
60
61pub struct EpochMembershipCoordinator<TYPES: NodeType> {
63 membership: Arc<TYPES::Membership>,
64 catchup_map: Attempts,
65 stake_table_map: Attempts,
66 drb_computations: DrbComputations,
67 epoch_height: BlockNumber,
68 catchup_timeout: Duration,
69 store_drb_progress_fn: StoreDrbProgressFn,
70 load_drb_progress_fn: LoadDrbProgressFn,
71 store_drb_result_fn: StoreDrbResultFn,
72 drb_difficulty_selector: Arc<RwLock<Option<DrbDifficultySelectorFn>>>,
73}
74
75impl<TYPES: NodeType> Clone for EpochMembershipCoordinator<TYPES> {
76 fn clone(&self) -> Self {
77 Self {
78 membership: Arc::clone(&self.membership),
79 catchup_map: self.catchup_map.clone(),
80 stake_table_map: self.stake_table_map.clone(),
81 drb_computations: Arc::clone(&self.drb_computations),
82 epoch_height: self.epoch_height,
83 catchup_timeout: self.catchup_timeout,
84 store_drb_progress_fn: Arc::clone(&self.store_drb_progress_fn),
85 load_drb_progress_fn: Arc::clone(&self.load_drb_progress_fn),
86 store_drb_result_fn: self.store_drb_result_fn.clone(),
87 drb_difficulty_selector: Arc::clone(&self.drb_difficulty_selector),
88 }
89 }
90}
91
92impl<TYPES: NodeType> EpochMembershipCoordinator<TYPES> {
93 pub fn new<M, S, B>(membership: M, epoch_height: B, storage: &S) -> Self
94 where
95 M: Into<Arc<TYPES::Membership>>,
96 B: Into<BlockNumber>,
97 S: Storage<TYPES>,
98 {
99 Self {
100 membership: membership.into(),
101 catchup_map: Attempts::default(),
102 stake_table_map: Attempts::default(),
103 drb_computations: Arc::default(),
104 epoch_height: epoch_height.into(),
105 catchup_timeout: DEFAULT_CATCHUP_TIMEOUT,
106 store_drb_progress_fn: store_drb_progress_fn(storage.clone()),
107 load_drb_progress_fn: load_drb_progress_fn(storage.clone()),
108 store_drb_result_fn: store_drb_result_fn(storage.clone()),
109 drb_difficulty_selector: Arc::new(RwLock::new(None)),
110 }
111 }
112
113 pub fn epoch_height(&self) -> BlockNumber {
114 self.epoch_height
115 }
116
117 pub fn with_catchup_timeout(mut self, timeout: Duration) -> Self {
119 self.catchup_timeout = timeout;
120 self
121 }
122
123 pub fn with_store_drb_result_fn(mut self, f: StoreDrbResultFn) -> Self {
125 self.store_drb_result_fn = f;
126 self
127 }
128
129 pub fn membership(&self) -> &TYPES::Membership {
131 &self.membership
132 }
133
134 pub async fn add_epoch_root(
136 &self,
137 header: TYPES::BlockHeader,
138 ) -> std::result::Result<(), <TYPES::Membership as Membership<TYPES>>::Error> {
139 self.membership.add_epoch_root(header, self).await
140 }
141
142 pub async fn get_epoch_root(
145 &self,
146 epoch: EpochNumber,
147 ) -> std::result::Result<Leaf2<TYPES>, <TYPES::Membership as Membership<TYPES>>::Error> {
148 self.membership.get_epoch_root(epoch, self).await
149 }
150
151 pub async fn get_epoch_drb(
153 &self,
154 epoch: EpochNumber,
155 ) -> std::result::Result<DrbResult, <TYPES::Membership as Membership<TYPES>>::Error> {
156 self.membership.get_epoch_drb(epoch, self).await
157 }
158
159 pub fn set_drb_difficulty_selector(&self, f: DrbDifficultySelectorFn) {
161 let mut drb_difficulty_selector_writer = self.drb_difficulty_selector.write();
162 *drb_difficulty_selector_writer = Some(f);
163 }
164
165 pub fn membership_for_epoch(
168 &self,
169 maybe_epoch: Option<EpochNumber>,
170 ) -> Result<EpochMembership<TYPES>> {
171 let Some(epoch) = maybe_epoch else {
172 return Ok(self.genesis_membership());
173 };
174 let first_epoch = self.membership.first_epoch();
175 if epoch < first_epoch {
176 return Err(anytrace::error!(
177 "membership_for_epoch called with epoch {epoch:?} before first_epoch {first_epoch}"
178 ));
179 }
180 if let Some(snapshot) = self.membership.snapshot(epoch)
181 && snapshot.has_drb()
182 {
183 return Ok(EpochMembership {
184 coordinator: self.clone(),
185 snapshot: EpochMembershipSnapshot::Epoch { epoch, snapshot },
186 });
187 }
188 if self.spawn_catchup(epoch) {
189 Err(anytrace::warn!(
190 "Randomized stake table for epoch {epoch:?} unavailable. Starting catchup"
191 ))
192 } else {
193 Err(anytrace::warn!(
194 "Randomized stake table for epoch {epoch:?} unavailable. Catchup already in \
195 progress"
196 ))
197 }
198 }
199
200 pub fn stake_table_for_epoch(&self, e: Option<EpochNumber>) -> Result<EpochMembership<TYPES>> {
203 let Some(epoch) = e else {
204 return Ok(self.genesis_membership());
205 };
206 let first_epoch = self.membership.first_epoch();
207 if epoch < first_epoch {
208 return Err(anytrace::error!(
209 "stake_table_for_epoch called with epoch {epoch:?} before first_epoch \
210 {first_epoch}"
211 ));
212 }
213 if let Some(membership) = self.epoch_membership(epoch) {
214 return Ok(membership);
215 }
216 if self.spawn_catchup(epoch) {
217 Err(anytrace::warn!(
218 "Stake table for epoch {epoch:?} unavailable. Starting catchup"
219 ))
220 } else {
221 Err(anytrace::warn!(
222 "Stake table for epoch {epoch:?} unavailable. Catchup already in progress"
223 ))
224 }
225 }
226
227 pub fn epoch_peers(
239 &self,
240 e: Option<EpochNumber>,
241 ) -> Option<HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>>> {
242 let membership = self.stake_table_for_epoch(e).ok()?;
243 let mut out: HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>> = HashMap::new();
244 let mut merge =
245 |key: TYPES::SignatureKey, info: Option<PeerConnectInfo>| match out.entry(key) {
246 Entry::Vacant(slot) => {
247 slot.insert(info);
248 },
249 Entry::Occupied(mut slot) => {
250 if slot.get().is_none() && info.is_some() {
251 slot.insert(info);
252 }
253 },
254 };
255 for m in membership.stake_table().chain(membership.da_stake_table()) {
256 merge(m.stake_table_entry.public_key(), m.connect_info.clone());
257 }
258 Some(out)
259 }
260
261 pub fn window_peers(&self, e: EpochNumber) -> HashMap<TYPES::SignatureKey, PeerConnectInfo> {
270 let curr = self.epoch_peers(Some(e)).unwrap_or_default();
271 let prev = if *e > 0 {
272 self.epoch_peers(Some(e - 1)).unwrap_or_default()
273 } else {
274 HashMap::new()
275 };
276 let next = self.epoch_peers(Some(e + 1)).unwrap_or_default();
277
278 let mut merged: HashMap<TYPES::SignatureKey, Option<PeerConnectInfo>> = prev;
280 for (k, v) in curr.into_iter().chain(next) {
281 merged.insert(k, v);
282 }
283
284 merged
285 .into_iter()
286 .filter_map(|(k, v)| v.map(|info| (k, info)))
287 .collect()
288 }
289
290 fn spawn_catchup(&self, epoch: EpochNumber) -> bool {
299 if self.stake_table_map.contains_attempt(epoch) {
300 return false;
301 }
302 let (attempt, entry) = self.catchup_map.get_or_insert(epoch, || {
303 let coordinator = self.clone();
304 async move { coordinator.catchup(epoch).await }.boxed()
305 });
306 let Some(entry) = entry else {
307 return false;
308 };
309 spawn(async move {
310 let _entry = entry; if let Err(err) = attempt.await {
312 error!(%epoch, %err, "catchup failed");
313 }
314 });
315 true
316 }
317
318 async fn catchup(&self, epoch: EpochNumber) -> Result<()> {
331 let seeded = self.membership.first_epoch() + 1;
332
333 if epoch <= seeded {
334 return Err(anytrace::error!(
335 "catchup requested for epoch {epoch}, which the membership seeds at construction \
336 rather than deriving it from an epoch root"
337 ));
338 }
339
340 let known = self.membership.highest_known_epoch().unwrap_or(seeded);
341
342 let mut start = known
343 .min(EpochNumber::new(epoch.saturating_sub(1)))
344 .max(seeded);
345
346 while start > seeded {
347 if self.stake_table_present(start).await? && self.stake_table_present(start - 1).await?
348 {
349 break;
350 }
351 start = start - 1;
352 }
353
354 info!(target: "announce::catchup", %epoch, %start, "catching up to epoch");
355
356 let mut try_epoch = start + 1;
357 while try_epoch <= epoch {
358 let (attempt, _entry) = self.stake_table_map.get_or_insert(try_epoch, || {
359 let coordinator = self.clone();
360 async move {
361 if coordinator.stake_table_present(try_epoch).await? {
362 return Ok(());
363 }
364 coordinator.fetch_stake_table(try_epoch).await
365 }
366 .boxed()
367 });
368 attempt.await.map_err(|err| {
369 anytrace::error!("catchup for epoch {epoch} failed at epoch {try_epoch}: {err:?}")
370 })?;
371 try_epoch += 1;
372 }
373
374 self.drb_ready(epoch).await
375 }
376
377 fn epoch_membership(&self, epoch: EpochNumber) -> Option<EpochMembership<TYPES>> {
379 Some(EpochMembership {
380 coordinator: self.clone(),
381 snapshot: EpochMembershipSnapshot::Epoch {
382 epoch,
383 snapshot: self.membership.snapshot(epoch)?,
384 },
385 })
386 }
387
388 fn genesis_membership(&self) -> EpochMembership<TYPES> {
390 EpochMembership {
391 coordinator: self.clone(),
392 snapshot: EpochMembershipSnapshot::Genesis(self.membership.genesis_stake_tables()),
393 }
394 }
395
396 async fn stake_table_present(&self, epoch: EpochNumber) -> Result<bool> {
400 if self.membership.snapshot(epoch).is_some() {
401 return Ok(true);
402 }
403 debug!(%epoch, "loading stake table from storage");
404 timeout(
405 self.catchup_timeout,
406 self.membership.load_stake_table(epoch),
407 )
408 .await
409 .map_err(|_| {
410 anytrace::error!(
411 "loading the stake table for epoch {epoch} from storage exceeded {:?}",
412 self.catchup_timeout
413 )
414 })
415 }
416
417 async fn drb_ready(&self, epoch: EpochNumber) -> Result<()> {
421 if self
422 .membership
423 .snapshot(epoch)
424 .is_some_and(|snapshot| snapshot.has_drb())
425 {
426 return Ok(());
427 }
428 debug!(%epoch, "fetching drb result from peers");
429 let fetched = timeout(self.catchup_timeout, self.get_epoch_drb(epoch))
430 .await
431 .map_err(|_| {
432 anytrace::error!(
433 "fetching the DRB result for epoch {epoch} from peers exceeded {:?}",
434 self.catchup_timeout
435 )
436 })?;
437 match fetched {
438 Ok(drb_result) => {
439 info!(
440 target: "announce::catchup",
441 %epoch,
442 ?drb_result,
443 "drb result retrieved from peers"
444 );
445 self.membership.add_drb_result(epoch, drb_result);
446 },
447 Err(err) => {
448 info!(
449 target: "announce::catchup",
450 %epoch,
451 %err,
452 "recalculating the drb result the peers lack"
453 );
454 let root_leaf = self.epoch_root_leaf(epoch).await?;
455 self.compute_drb_result(epoch, root_leaf).await?;
456 },
457 }
458
459 if self
460 .membership
461 .snapshot(epoch)
462 .is_some_and(|snapshot| snapshot.has_drb())
463 {
464 Ok(())
465 } else {
466 Err(anytrace::error!(
467 "the drb result for epoch {epoch} was not stored; its stake table is gone"
468 ))
469 }
470 }
471
472 pub async fn wait_for_stake_table(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
479 match self.stake_table_for_epoch(Some(epoch)) {
480 Ok(mem) => Ok(mem),
481 Err(_) => self.wait_for_catchup(epoch).await,
482 }
483 }
484
485 pub async fn wait_for_catchup(&self, epoch: EpochNumber) -> Result<EpochMembership<TYPES>> {
491 let failed = |err| anytrace::error!("Catchup for epoch {epoch} failed: {err:?}");
492 if let Some(attempt) = self.catchup_map.get_attempt(epoch) {
493 attempt.await.map_err(failed)?;
494 } else if let Some(attempt) = self.stake_table_map.get_attempt(epoch) {
495 attempt.await.map_err(failed)?;
496 } else if let Some(membership) = self.epoch_membership(epoch) {
497 return Ok(membership);
498 } else {
499 return Err(anytrace::error!(
500 "No catchup in progress for epoch {epoch} and we don't have a stake table for it"
501 ));
502 }
503 self.epoch_membership(epoch).ok_or_else(|| {
504 anytrace::error!("Catchup for epoch {epoch} reported success but left no stake table")
505 })
506 }
507
508 async fn epoch_root_leaf(&self, epoch: EpochNumber) -> Result<Leaf2<TYPES>> {
514 let root_epoch = EpochNumber::new(epoch.saturating_sub(2));
515 self.stake_table_present(root_epoch).await?;
521 let Some(root_membership) = self.epoch_membership(root_epoch) else {
522 return Err(anytrace::error!(
523 "the stake table for epoch {epoch} is derived from epoch {root_epoch}, which is \
524 in neither memory nor local storage"
525 ));
526 };
527
528 debug!(%epoch, %root_epoch, "fetching epoch root");
530 let fetched = timeout(self.catchup_timeout, root_membership.get_epoch_root())
531 .await
532 .map_err(|_| {
533 anytrace::error!(
534 "fetching the epoch root of epoch {epoch} exceeded {:?}",
535 self.catchup_timeout
536 )
537 })?;
538 fetched.map_err(|err| {
539 anytrace::error!("get epoch root leaf failed for epoch {root_epoch:?}: {err:#}")
540 })
541 }
542
543 async fn fetch_stake_table(&self, epoch: EpochNumber) -> Result<()> {
545 let root_leaf = self.epoch_root_leaf(epoch).await?;
546
547 debug!(%epoch, "adding epoch root to membership");
548 timeout(
549 self.catchup_timeout,
550 self.add_epoch_root(root_leaf.block_header().clone()),
551 )
552 .await
553 .map_err(|_| {
554 anytrace::error!(
555 "adding the epoch root of epoch {epoch} to membership exceeded {:?}",
556 self.catchup_timeout
557 )
558 })?
559 .map_err(|e| {
560 anytrace::error!("Failed to add epoch root for epoch {epoch:?} to membership: {e}")
561 })
562 }
563
564 pub async fn compute_drb_result(
572 &self,
573 epoch: EpochNumber,
574 root_leaf: Leaf2<TYPES>,
575 ) -> Result<DrbResult> {
576 let cancel_token = {
577 let mut computations = self.drb_computations.lock();
578 if computations.contains_key(&epoch) {
579 return Err(anytrace::debug!(
580 "DRB calculation for epoch {epoch} already in progress"
581 ));
582 }
583 let token = CancellationToken::new();
584 computations.insert(epoch, token.clone());
585 token
586 };
587 let _drb_state = DrbStateGuard {
589 coordinator: self.clone(),
590 epoch,
591 };
592
593 let Ok(drb_seed_input_vec) = bincode::serialize(&root_leaf.justify_qc().signatures) else {
594 return Err(anytrace::error!(
595 "Failed to serialize the QC signature for leaf {root_leaf:?}"
596 ));
597 };
598
599 let Some(drb_difficulty_selector) = self.drb_difficulty_selector.read().clone() else {
600 return Err(anytrace::error!(
601 "The DRB difficulty selector is missing from the epoch membership coordinator. \
602 This node will not be able to spawn any DRB calculation tasks from catchup."
603 ));
604 };
605
606 let drb_difficulty = drb_difficulty_selector(root_leaf.block_header().version()).await;
607
608 let mut drb_seed_input = [0u8; 32];
609
610 if root_leaf.block_header().version() >= DRB_FIX_VERSION {
611 drb_seed_input = Sha256::digest(&drb_seed_input_vec).into();
612 } else {
613 let len = drb_seed_input_vec.len().min(32);
614 drb_seed_input[..len].copy_from_slice(&drb_seed_input_vec[..len]);
615 }
616
617 let drb_input = DrbInput {
618 epoch: *epoch,
619 iteration: 0,
620 value: drb_seed_input,
621 difficulty_level: drb_difficulty,
622 };
623
624 let store_drb_progress_fn = self.store_drb_progress_fn.clone();
625 let load_drb_progress_fn = self.load_drb_progress_fn.clone();
626
627 let heartbeat = Heartbeat::default();
628 let Some(computed) = heartbeat
629 .while_alive(
630 self.catchup_timeout,
631 compute_drb_result(
632 drb_input,
633 store_drb_progress_fn,
634 load_drb_progress_fn,
635 DRB_PROGRESS_LOAD_TIMEOUT,
636 heartbeat.clone(),
637 cancel_token,
638 ),
639 )
640 .await
641 else {
642 return Err(anytrace::error!(
643 "the DRB computation for epoch {epoch} reported no progress for {:?}",
644 self.catchup_timeout
645 ));
646 };
647 let drb = match computed {
648 Some(drb) => drb,
649 None => {
650 info!(
651 %epoch,
652 "the local drb computation was cancelled; fetching the externally supplied \
653 result"
654 );
655 let fetched = timeout(self.catchup_timeout, self.get_epoch_drb(epoch))
656 .await
657 .map_err(|_| {
658 anytrace::error!(
659 "fetching the DRB result for epoch {epoch} from peers after \
660 cancellation exceeded {:?}",
661 self.catchup_timeout
662 )
663 })?;
664 return fetched.map_err(|e| {
665 anytrace::error!(
666 "DRB calculation for epoch {epoch} was cancelled but no externally \
667 supplied result is available: {e}"
668 )
669 });
670 },
671 };
672
673 self.membership.add_drb_result(epoch, drb);
676
677 info!(%epoch, ?drb, "writing the computed drb result to storage");
680 match timeout(self.catchup_timeout, (self.store_drb_result_fn)(epoch, drb)).await {
681 Ok(Err(err)) => warn!(%epoch, %err, "failed to store the drb result"),
682 Ok(Ok(())) => {},
683 Err(_) => warn!(
684 %epoch,
685 timeout = ?self.catchup_timeout,
686 "storing the drb result timed out"
687 ),
688 }
689
690 Ok(drb)
691 }
692
693 pub fn supply_drb(&self, epoch: EpochNumber, drb: DrbResult) {
703 if self.membership.snapshot(epoch).is_none() {
704 error!(
705 %epoch,
706 "supply_drb called before the stake table is loaded; dropping the externally \
707 supplied drb and relying on the in-flight catchup"
708 );
709 return;
710 }
711 self.membership.add_drb_result(epoch, drb);
712 let maybe_token = self.drb_computations.lock().get(&epoch).cloned();
715 if let Some(token) = maybe_token {
716 token.cancel();
717 }
718 let store_drb_result_fn = self.store_drb_result_fn.clone();
719 spawn(async move {
720 info!(%epoch, ?drb, "writing the supplied drb result to storage");
721 if let Err(e) = store_drb_result_fn(epoch, drb).await {
722 warn!(%epoch, %e, "failed to store the supplied drb result");
723 }
724 });
725 }
726
727 pub fn drb_cancel_token(&self, epoch: EpochNumber) -> Option<CancellationToken> {
731 self.drb_computations.lock().get(&epoch).cloned()
732 }
733
734 pub fn cancel_all_drb(&self) {
736 let tokens: Vec<_> = self.drb_computations.lock().values().cloned().collect();
739 for token in tokens {
740 token.cancel();
741 }
742 }
743}
744
745struct DrbStateGuard<TYPES: NodeType> {
748 coordinator: EpochMembershipCoordinator<TYPES>,
749 epoch: EpochNumber,
750}
751
752impl<TYPES: NodeType> Drop for DrbStateGuard<TYPES> {
753 fn drop(&mut self) {
754 let token = self.coordinator.drb_computations.lock().remove(&self.epoch);
755 if let Some(token) = token {
756 token.cancel();
757 }
758 }
759}
760
761const DEFAULT_CATCHUP_TIMEOUT: Duration = Duration::from_secs(300);
763
764#[derive(Clone, Default)]
766struct Attempts {
767 map: Arc<Mutex<HashMap<EpochNumber, Attempt>>>,
768}
769
770impl Attempts {
771 fn get_or_insert(
772 &self,
773 epoch: EpochNumber,
774 action: impl FnOnce() -> BoxFuture<'static, Result<()>>,
775 ) -> (Attempt, Option<AttemptEntry>) {
776 let mut map = self.map.lock();
777 if let Some(running) = map.get(&epoch) {
778 return (running.clone(), None);
779 }
780 let task = spawn(action());
788 let attempt = async move {
789 task.await.unwrap_or_else(|err| {
790 Err(anytrace::error!(
791 "the attempt for epoch {epoch} died: {err}"
792 ))
793 })
794 }
795 .boxed()
796 .shared();
797 map.insert(epoch, attempt.clone());
798 (
799 attempt,
800 Some(AttemptEntry {
801 attempts: self.clone(),
802 epoch,
803 }),
804 )
805 }
806
807 fn contains_attempt(&self, epoch: EpochNumber) -> bool {
808 self.map.lock().contains_key(&epoch)
809 }
810
811 fn get_attempt(&self, epoch: EpochNumber) -> Option<Attempt> {
812 self.map.lock().get(&epoch).cloned()
813 }
814}
815
816struct AttemptEntry {
818 attempts: Attempts,
819 epoch: EpochNumber,
820}
821
822impl Drop for AttemptEntry {
823 fn drop(&mut self) {
824 self.attempts.map.lock().remove(&self.epoch);
825 }
826}
827
828pub struct EpochMembership<TYPES: NodeType> {
832 snapshot: EpochMembershipSnapshot<TYPES>,
834 pub coordinator: EpochMembershipCoordinator<TYPES>,
837}
838
839enum EpochMembershipSnapshot<TYPES: NodeType> {
840 Epoch {
841 epoch: EpochNumber,
842 snapshot: <TYPES::Membership as Membership<TYPES>>::Snapshot,
843 },
844 Genesis(Arc<GenesisStakeTables<TYPES>>),
846}
847
848impl<TYPES: NodeType> Clone for EpochMembershipSnapshot<TYPES> {
849 fn clone(&self) -> Self {
850 match self {
851 Self::Epoch { epoch, snapshot } => Self::Epoch {
852 epoch: *epoch,
853 snapshot: snapshot.clone(),
854 },
855 Self::Genesis(g) => Self::Genesis(g.clone()),
856 }
857 }
858}
859
860impl<TYPES: NodeType> Clone for EpochMembership<TYPES> {
861 fn clone(&self) -> Self {
862 Self {
863 coordinator: self.coordinator.clone(),
864 snapshot: self.snapshot.clone(),
865 }
866 }
867}
868
869impl<TYPES: NodeType> EpochMembership<TYPES> {
870 pub fn epoch(&self) -> Option<EpochNumber> {
871 match &self.snapshot {
872 EpochMembershipSnapshot::Epoch { epoch, .. } => Some(*epoch),
873 EpochMembershipSnapshot::Genesis(_) => None,
874 }
875 }
876
877 pub fn next_epoch(&self) -> Result<Self> {
878 let epoch = self
879 .epoch()
880 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
881 self.coordinator.membership_for_epoch(Some(epoch + 1))
882 }
883
884 pub fn next_epoch_stake_table(&self) -> Result<Self> {
885 let epoch = self
886 .epoch()
887 .ok_or_else(|| anytrace::error!("No next epoch because epoch is None"))?;
888 self.coordinator.stake_table_for_epoch(Some(epoch + 1))
889 }
890
891 pub fn get_new_epoch(&self, epoch: Option<EpochNumber>) -> Result<Self> {
892 self.coordinator.membership_for_epoch(epoch)
893 }
894
895 async fn get_epoch_root(&self) -> anyhow::Result<Leaf2<TYPES>> {
896 let Some(epoch) = self.epoch() else {
897 anyhow::bail!("Cannot get root for None epoch");
898 };
899 let leaf = self.coordinator.get_epoch_root(epoch).await?;
900 Ok(leaf)
901 }
902
903 pub async fn get_epoch_drb(&self) -> Result<DrbResult> {
904 let Some(epoch) = self.epoch() else {
905 return Err(anytrace::warn!("Cannot get drb for None epoch"));
906 };
907 self.coordinator.get_epoch_drb(epoch).await.wrap()
908 }
909
910 pub fn snapshot(&self) -> Option<&<TYPES::Membership as Membership<TYPES>>::Snapshot> {
912 match &self.snapshot {
913 EpochMembershipSnapshot::Epoch { snapshot, .. } => Some(snapshot),
914 EpochMembershipSnapshot::Genesis(_) => None,
915 }
916 }
917
918 pub fn add_drb_result(&self, drb_result: DrbResult) {
920 if let Some(epoch) = self.epoch() {
921 self.coordinator
922 .membership
923 .add_drb_result(epoch, drb_result);
924 }
925 }
926
927 pub fn stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
936 match &self.snapshot {
937 EpochMembershipSnapshot::Epoch { snapshot, .. } => Either::Left(snapshot.stake_table()),
938 EpochMembershipSnapshot::Genesis(g) => Either::Right(g.stake_table()),
939 }
940 }
941
942 pub fn da_stake_table(&self) -> impl ExactSizeIterator<Item = &PeerConfig<TYPES>> + Send {
943 match &self.snapshot {
944 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
945 Either::Left(snapshot.da_stake_table())
946 },
947 EpochMembershipSnapshot::Genesis(g) => Either::Right(g.da_stake_table()),
948 }
949 }
950
951 pub fn committee_members(
952 &self,
953 view: ViewNumber,
954 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
955 match &self.snapshot {
956 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
957 Either::Left(snapshot.committee_members(view))
958 },
959 EpochMembershipSnapshot::Genesis(g) => Either::Right(g.committee_members()),
960 }
961 }
962
963 pub fn da_committee_members(
964 &self,
965 view: ViewNumber,
966 ) -> impl ExactSizeIterator<Item = &TYPES::SignatureKey> + Send {
967 match &self.snapshot {
968 EpochMembershipSnapshot::Epoch { snapshot, .. } => {
969 Either::Left(snapshot.da_committee_members(view))
970 },
971 EpochMembershipSnapshot::Genesis(g) => Either::Right(g.da_committee_members()),
972 }
973 }
974
975 pub fn stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
976 match &self.snapshot {
977 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake(key),
978 EpochMembershipSnapshot::Genesis(g) => g.stake(key),
979 }
980 }
981
982 pub fn da_stake(&self, key: &TYPES::SignatureKey) -> Option<PeerConfig<TYPES>> {
983 match &self.snapshot {
984 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_stake(key),
985 EpochMembershipSnapshot::Genesis(g) => g.da_stake(key),
986 }
987 }
988
989 pub fn has_stake(&self, key: &TYPES::SignatureKey) -> bool {
990 match &self.snapshot {
991 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_stake(key),
992 EpochMembershipSnapshot::Genesis(g) => g.has_stake(key),
993 }
994 }
995
996 pub fn has_da_stake(&self, key: &TYPES::SignatureKey) -> bool {
997 match &self.snapshot {
998 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.has_da_stake(key),
999 EpochMembershipSnapshot::Genesis(g) => g.has_da_stake(key),
1000 }
1001 }
1002
1003 pub fn leader(&self, view: ViewNumber) -> Result<TYPES::SignatureKey> {
1009 match &self.snapshot {
1010 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.leader(view),
1011 EpochMembershipSnapshot::Genesis(g) => g.leader(view),
1012 }
1013 }
1014
1015 pub fn total_nodes(&self) -> usize {
1016 match &self.snapshot {
1017 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.total_nodes(),
1018 EpochMembershipSnapshot::Genesis(g) => g.total_nodes(),
1019 }
1020 }
1021
1022 pub fn da_total_nodes(&self) -> usize {
1023 match &self.snapshot {
1024 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_total_nodes(),
1025 EpochMembershipSnapshot::Genesis(g) => g.da_total_nodes(),
1026 }
1027 }
1028
1029 pub fn success_threshold(&self) -> U256 {
1030 match &self.snapshot {
1031 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.success_threshold(),
1032 EpochMembershipSnapshot::Genesis(g) => g.success_threshold(),
1033 }
1034 }
1035
1036 pub fn da_success_threshold(&self) -> U256 {
1037 match &self.snapshot {
1038 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.da_success_threshold(),
1039 EpochMembershipSnapshot::Genesis(g) => g.da_success_threshold(),
1040 }
1041 }
1042
1043 pub fn failure_threshold(&self) -> U256 {
1044 match &self.snapshot {
1045 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.failure_threshold(),
1046 EpochMembershipSnapshot::Genesis(g) => g.failure_threshold(),
1047 }
1048 }
1049
1050 pub fn upgrade_threshold(&self) -> U256 {
1051 match &self.snapshot {
1052 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.upgrade_threshold(),
1053 EpochMembershipSnapshot::Genesis(g) => g.upgrade_threshold(),
1054 }
1055 }
1056
1057 pub fn stake_table_hash(&self) -> Option<Commitment<SnapshotStakeTableHash<TYPES>>> {
1058 match &self.snapshot {
1059 EpochMembershipSnapshot::Epoch { snapshot, .. } => snapshot.stake_table_hash(),
1060 EpochMembershipSnapshot::Genesis(_) => None,
1061 }
1062 }
1063}