1use std::collections::BTreeMap;
72
73use custom_debug_derive::Debug;
74use futures::future::Either;
75use linera_base::{
76 crypto::{AccountPublicKey, CryptoError, ValidatorSecretKey},
77 data_types::{Blob, BlockHeight, Epoch, Round, Timestamp},
78 ensure,
79 identifiers::{AccountOwner, BlobId, ChainId},
80 ownership::ChainOwnership,
81};
82use linera_execution::ExecutionRuntimeContext;
83use linera_views::{
84 context::Context,
85 map_view::MapView,
86 register_view::RegisterView,
87 views::{ClonableView, View},
88 ViewError,
89};
90use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
91use rand_distr::{Distribution, WeightedAliasIndex};
92use serde::{Deserialize, Serialize};
93
94use crate::{
95 block::{Block, ConfirmedBlock, Timeout, ValidatedBlock},
96 data_types::{BlockProposal, LiteVote, OriginalProposal, ProposedBlock, Vote},
97 types::{TimeoutCertificate, ValidatedBlockCertificate},
98 ChainError,
99};
100
101#[derive(Eq, PartialEq)]
103pub enum Outcome {
104 Accept,
105 Skip,
106}
107
108pub type ValidatedOrConfirmedVote<'a> = Either<&'a Vote<ValidatedBlock>, &'a Vote<ConfirmedBlock>>;
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
114#[cfg_attr(with_testing, derive(Eq, PartialEq))]
115pub enum LockingBlock {
116 Fast(BlockProposal),
118 Regular(ValidatedBlockCertificate),
120}
121
122impl LockingBlock {
123 pub fn round(&self) -> Round {
126 match self {
127 Self::Fast(_) => Round::Fast,
128 Self::Regular(certificate) => certificate.round,
129 }
130 }
131
132 pub fn chain_id(&self) -> ChainId {
133 match self {
134 Self::Fast(proposal) => proposal.content.block.chain_id,
135 Self::Regular(certificate) => certificate.value().chain_id(),
136 }
137 }
138}
139
140#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject), graphql(complex))]
142#[derive(Debug, View, ClonableView)]
143pub struct ChainManager<C>
144where
145 C: Clone + Context + Send + Sync + 'static,
146{
147 pub ownership: RegisterView<C, ChainOwnership>,
149 pub seed: RegisterView<C, u64>,
151 #[cfg_attr(with_graphql, graphql(skip))] pub distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
154 #[cfg_attr(with_graphql, graphql(skip))] pub fallback_distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
157 #[cfg_attr(with_graphql, graphql(skip))]
162 pub signed_proposal: RegisterView<C, Option<BlockProposal>>,
163 #[cfg_attr(with_graphql, graphql(skip))]
166 pub proposed: RegisterView<C, Option<BlockProposal>>,
167 pub proposed_blobs: MapView<C, BlobId, Blob>,
169 #[cfg_attr(with_graphql, graphql(skip))]
172 pub locking_block: RegisterView<C, Option<LockingBlock>>,
173 pub locking_blobs: MapView<C, BlobId, Blob>,
175 #[cfg_attr(with_graphql, graphql(skip))]
177 pub timeout: RegisterView<C, Option<TimeoutCertificate>>,
178 #[cfg_attr(with_graphql, graphql(skip))]
180 pub confirmed_vote: RegisterView<C, Option<Vote<ConfirmedBlock>>>,
181 #[cfg_attr(with_graphql, graphql(skip))]
183 pub validated_vote: RegisterView<C, Option<Vote<ValidatedBlock>>>,
184 #[cfg_attr(with_graphql, graphql(skip))]
186 pub timeout_vote: RegisterView<C, Option<Vote<Timeout>>>,
187 #[cfg_attr(with_graphql, graphql(skip))]
189 pub fallback_vote: RegisterView<C, Option<Vote<Timeout>>>,
190 pub round_timeout: RegisterView<C, Option<Timestamp>>,
192 #[cfg_attr(with_graphql, graphql(skip))]
199 pub current_round: RegisterView<C, Round>,
200 pub fallback_owners: RegisterView<C, BTreeMap<AccountOwner, u64>>,
202}
203
204#[cfg(with_graphql)]
205#[async_graphql::ComplexObject]
206impl<C> ChainManager<C>
207where
208 C: Context + Clone + Send + Sync + 'static,
209{
210 #[graphql(derived(name = "current_round"))]
217 async fn _current_round(&self) -> Round {
218 self.current_round()
219 }
220}
221
222impl<C> ChainManager<C>
223where
224 C: Context + Clone + Send + Sync + 'static,
225{
226 pub fn reset<'a>(
228 &mut self,
229 ownership: ChainOwnership,
230 height: BlockHeight,
231 local_time: Timestamp,
232 fallback_owners: impl Iterator<Item = (AccountPublicKey, u64)> + 'a,
233 ) -> Result<(), ChainError> {
234 let distribution = calculate_distribution(ownership.owners.iter());
235
236 let fallback_owners = fallback_owners
237 .map(|(pub_key, weight)| (AccountOwner::from(pub_key), weight))
238 .collect::<BTreeMap<_, _>>();
239 let fallback_distribution = calculate_distribution(fallback_owners.iter());
240
241 let current_round = ownership.first_round();
242 let round_duration = ownership.round_timeout(current_round);
243 let round_timeout = round_duration.map(|rd| local_time.saturating_add(rd));
244
245 self.clear();
246 self.seed.set(height.0);
247 self.ownership.set(ownership);
248 self.distribution.set(distribution);
249 self.fallback_distribution.set(fallback_distribution);
250 self.fallback_owners.set(fallback_owners);
251 self.current_round.set(current_round);
252 self.round_timeout.set(round_timeout);
253 Ok(())
254 }
255
256 pub fn confirmed_vote(&self) -> Option<&Vote<ConfirmedBlock>> {
258 self.confirmed_vote.get().as_ref()
259 }
260
261 pub fn validated_vote(&self) -> Option<&Vote<ValidatedBlock>> {
263 self.validated_vote.get().as_ref()
264 }
265
266 pub fn timeout_vote(&self) -> Option<&Vote<Timeout>> {
268 self.timeout_vote.get().as_ref()
269 }
270
271 pub fn fallback_vote(&self) -> Option<&Vote<Timeout>> {
273 self.fallback_vote.get().as_ref()
274 }
275
276 pub fn current_round(&self) -> Round {
283 *self.current_round.get()
284 }
285
286 pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
288 let new_block = &proposal.content.block;
289 let new_round = proposal.content.round;
290 if let Some(old_proposal) = self.proposed.get() {
291 if old_proposal.content == proposal.content {
292 return Ok(Outcome::Skip); }
294 }
295 ensure!(
297 new_block.height < BlockHeight::MAX,
298 ChainError::BlockHeightOverflow
299 );
300 let current_round = self.current_round();
301 match new_round {
302 Round::Fast => {}
305 Round::MultiLeader(_) | Round::SingleLeader(0) => {
306 ensure!(
309 self.is_super(&proposal.owner()) || !current_round.is_fast(),
310 ChainError::WrongRound(current_round)
311 );
312 ensure!(
314 new_round >= current_round,
315 ChainError::InsufficientRound(new_round)
316 );
317 }
318 Round::SingleLeader(_) | Round::Validator(_) => {
319 ensure!(
321 new_round == current_round,
322 ChainError::WrongRound(current_round)
323 );
324 }
325 }
326 if let Some(vote) = self.validated_vote() {
328 ensure!(
329 new_round > vote.round,
330 ChainError::InsufficientRoundStrict(vote.round)
331 );
332 }
333 if let Some(locking_block) = self.locking_block.get() {
335 ensure!(
336 locking_block.round() < new_round,
337 ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())
338 );
339 }
340 if let Some(vote) = self.confirmed_vote() {
343 ensure!(
344 match proposal.original_proposal.as_ref() {
345 None => false,
346 Some(OriginalProposal::Regular { certificate }) =>
347 vote.round <= certificate.round,
348 Some(OriginalProposal::Fast(_)) => {
349 vote.round.is_fast() && vote.value().matches_proposed_block(new_block)
350 }
351 },
352 ChainError::HasIncompatibleConfirmedVote(new_block.height, vote.round)
353 );
354 }
355 Ok(Outcome::Accept)
356 }
357
358 pub fn create_timeout_vote(
361 &mut self,
362 chain_id: ChainId,
363 height: BlockHeight,
364 round: Round,
365 epoch: Epoch,
366 key_pair: Option<&ValidatorSecretKey>,
367 local_time: Timestamp,
368 ) -> Result<bool, ChainError> {
369 let Some(key_pair) = key_pair else {
370 return Ok(false); };
372 ensure!(
373 round == self.current_round(),
374 ChainError::WrongRound(self.current_round())
375 );
376 let Some(round_timeout) = *self.round_timeout.get() else {
377 return Err(ChainError::RoundDoesNotTimeOut);
378 };
379 ensure!(
380 local_time >= round_timeout,
381 ChainError::NotTimedOutYet(round_timeout)
382 );
383 if let Some(vote) = self.timeout_vote.get() {
384 if vote.round == round {
385 return Ok(false); }
387 }
388 let value = Timeout::new(chain_id, height, epoch);
389 self.timeout_vote
390 .set(Some(Vote::new(value, round, key_pair)));
391 Ok(true)
392 }
393
394 pub fn vote_fallback(
399 &mut self,
400 chain_id: ChainId,
401 height: BlockHeight,
402 epoch: Epoch,
403 key_pair: Option<&ValidatorSecretKey>,
404 ) -> bool {
405 let Some(key_pair) = key_pair else {
406 return false; };
408 if self.fallback_vote.get().is_some() || self.current_round() >= Round::Validator(0) {
409 return false; }
411 let value = Timeout::new(chain_id, height, epoch);
412 let last_regular_round = Round::SingleLeader(u32::MAX);
413 self.fallback_vote
414 .set(Some(Vote::new(value, last_regular_round, key_pair)));
415 true
416 }
417
418 pub fn check_validated_block(
420 &self,
421 certificate: &ValidatedBlockCertificate,
422 ) -> Result<Outcome, ChainError> {
423 let new_block = certificate.block();
424 let new_round = certificate.round;
425 if let Some(Vote { value, round, .. }) = self.confirmed_vote.get() {
426 if value.block() == new_block && *round == new_round {
427 return Ok(Outcome::Skip); }
429 }
430
431 if let Some(Vote { round, .. }) = self.validated_vote.get() {
433 ensure!(new_round >= *round, ChainError::InsufficientRound(*round))
434 }
435
436 if let Some(locking) = self.locking_block.get() {
437 ensure!(
438 new_round > locking.round(),
439 ChainError::InsufficientRoundStrict(locking.round())
440 );
441 }
442 Ok(Outcome::Accept)
443 }
444
445 pub fn create_vote(
447 &mut self,
448 proposal: BlockProposal,
449 block: Block,
450 key_pair: Option<&ValidatorSecretKey>,
451 local_time: Timestamp,
452 blobs: BTreeMap<BlobId, Blob>,
453 ) -> Result<Option<ValidatedOrConfirmedVote>, ChainError> {
454 let round = proposal.content.round;
455
456 match &proposal.original_proposal {
457 Some(OriginalProposal::Regular { certificate }) => {
459 if self
460 .locking_block
461 .get()
462 .as_ref()
463 .is_none_or(|locking| locking.round() < certificate.round)
464 {
465 let value = ValidatedBlock::new(block.clone());
466 if let Some(certificate) = certificate.clone().with_value(value) {
467 self.update_locking(LockingBlock::Regular(certificate), blobs.clone())?;
468 }
469 }
470 }
471 Some(OriginalProposal::Fast(signature)) => {
474 if self.locking_block.get().is_none() {
475 let original_proposal = BlockProposal {
476 signature: *signature,
477 ..proposal.clone()
478 };
479 self.update_locking(LockingBlock::Fast(original_proposal), blobs.clone())?;
480 }
481 }
482 None => {
485 if round.is_fast() && self.locking_block.get().is_none() {
486 self.update_locking(LockingBlock::Fast(proposal.clone()), blobs.clone())?;
488 }
489 }
490 }
491
492 self.update_proposed(proposal.clone(), blobs)?;
494 self.update_current_round(local_time);
495
496 let Some(key_pair) = key_pair else {
497 return Ok(None);
499 };
500
501 if round.is_fast() {
503 self.validated_vote.set(None);
504 let value = ConfirmedBlock::new(block);
505 let vote = Vote::new(value, round, key_pair);
506 Ok(Some(Either::Right(
507 self.confirmed_vote.get_mut().insert(vote),
508 )))
509 } else {
510 let value = ValidatedBlock::new(block);
511 let vote = Vote::new(value, round, key_pair);
512 Ok(Some(Either::Left(
513 self.validated_vote.get_mut().insert(vote),
514 )))
515 }
516 }
517
518 pub fn create_final_vote(
520 &mut self,
521 validated: ValidatedBlockCertificate,
522 key_pair: Option<&ValidatorSecretKey>,
523 local_time: Timestamp,
524 blobs: BTreeMap<BlobId, Blob>,
525 ) -> Result<(), ViewError> {
526 let round = validated.round;
527 let confirmed_block = ConfirmedBlock::new(validated.inner().block().clone());
528 self.update_locking(LockingBlock::Regular(validated), blobs)?;
529 self.update_current_round(local_time);
530 if let Some(key_pair) = key_pair {
531 if self.current_round() != round {
532 return Ok(()); }
534 let vote = Vote::new(confirmed_block, round, key_pair);
536 self.confirmed_vote.set(Some(vote));
538 self.validated_vote.set(None);
539 }
540 Ok(())
541 }
542
543 pub async fn pending_blob(&self, blob_id: &BlobId) -> Result<Option<Blob>, ViewError> {
545 if let Some(blob) = self.proposed_blobs.get(blob_id).await? {
546 return Ok(Some(blob));
547 }
548 self.locking_blobs.get(blob_id).await
549 }
550
551 fn update_current_round(&mut self, local_time: Timestamp) {
569 let current_round = self
570 .timeout
571 .get()
572 .iter()
573 .map(|certificate| {
575 self.ownership
576 .get()
577 .next_round(certificate.round)
578 .unwrap_or(Round::Validator(u32::MAX))
579 })
580 .chain(self.locking_block.get().as_ref().map(LockingBlock::round))
583 .chain(
584 self.proposed
585 .get()
586 .iter()
587 .chain(self.signed_proposal.get())
588 .map(|proposal| proposal.content.round),
589 )
590 .max()
591 .unwrap_or_default()
592 .max(self.ownership.get().first_round());
594 if current_round <= self.current_round() {
595 return;
596 }
597 let round_duration = self.ownership.get().round_timeout(current_round);
598 self.round_timeout
599 .set(round_duration.map(|rd| local_time.saturating_add(rd)));
600 self.current_round.set(current_round);
601 }
602
603 pub fn handle_timeout_certificate(
606 &mut self,
607 certificate: TimeoutCertificate,
608 local_time: Timestamp,
609 ) {
610 let round = certificate.round;
611 if let Some(known_certificate) = self.timeout.get() {
612 if known_certificate.round >= round {
613 return;
614 }
615 }
616 self.timeout.set(Some(certificate));
617 self.update_current_round(local_time);
618 }
619
620 pub fn verify_owner(
623 &self,
624 proposal_owner: &AccountOwner,
625 proposal_round: Round,
626 ) -> Result<bool, CryptoError> {
627 if self.ownership.get().super_owners.contains(proposal_owner) {
628 return Ok(true);
629 }
630
631 Ok(match proposal_round {
632 Round::Fast => {
633 false }
635 Round::MultiLeader(_) => {
636 let ownership = self.ownership.get();
637 ownership.open_multi_leader_rounds || ownership.owners.contains_key(proposal_owner)
639 }
640 Round::SingleLeader(r) => {
641 let Some(index) =
642 round_leader_index(r, *self.seed.get(), self.distribution.get().as_ref())
643 else {
644 return Ok(false);
645 };
646 self.ownership.get().owners.keys().nth(index) == Some(proposal_owner)
647 }
648 Round::Validator(r) => {
649 let Some(index) = round_leader_index(
650 r,
651 *self.seed.get(),
652 self.fallback_distribution.get().as_ref(),
653 ) else {
654 return Ok(false);
655 };
656 self.fallback_owners.get().keys().nth(index) == Some(proposal_owner)
657 }
658 })
659 }
660
661 fn round_leader(&self, round: Round) -> Option<&AccountOwner> {
664 match round {
665 Round::SingleLeader(r) => {
666 let index =
667 round_leader_index(r, *self.seed.get(), self.distribution.get().as_ref())?;
668 self.ownership.get().owners.keys().nth(index)
669 }
670 Round::Validator(r) => {
671 let index = round_leader_index(
672 r,
673 *self.seed.get(),
674 self.fallback_distribution.get().as_ref(),
675 )?;
676 self.fallback_owners.get().keys().nth(index)
677 }
678 Round::Fast | Round::MultiLeader(_) => None,
679 }
680 }
681
682 fn is_super(&self, owner: &AccountOwner) -> bool {
684 self.ownership.get().super_owners.contains(owner)
685 }
686
687 pub fn update_signed_proposal(
693 &mut self,
694 proposal: &BlockProposal,
695 local_time: Timestamp,
696 ) -> bool {
697 if proposal.content.round > Round::SingleLeader(0) {
698 return false;
699 }
700 if let Some(old_proposal) = self.signed_proposal.get() {
701 if old_proposal.content.round >= proposal.content.round {
702 if *self.current_round.get() < old_proposal.content.round {
703 tracing::warn!(
704 chain_id = %proposal.content.block.chain_id,
705 current_round = ?self.current_round.get(),
706 proposal_round = ?old_proposal.content.round,
707 "Proposal round is greater than current round. Updating."
708 );
709 self.update_current_round(local_time);
710 return true;
711 }
712 return false;
713 }
714 }
715 if let Some(old_proposal) = self.proposed.get() {
716 if old_proposal.content.round >= proposal.content.round {
717 return false;
718 }
719 }
720 self.signed_proposal.set(Some(proposal.clone()));
721 self.update_current_round(local_time);
722 true
723 }
724
725 fn update_proposed(
727 &mut self,
728 proposal: BlockProposal,
729 blobs: BTreeMap<BlobId, Blob>,
730 ) -> Result<(), ViewError> {
731 if let Some(old_proposal) = self.proposed.get() {
732 if old_proposal.content.round >= proposal.content.round {
733 return Ok(());
734 }
735 }
736 if let Some(old_proposal) = self.signed_proposal.get() {
737 if old_proposal.content.round <= proposal.content.round {
738 self.signed_proposal.set(None);
739 }
740 }
741 self.proposed.set(Some(proposal));
742 self.proposed_blobs.clear();
743 for (blob_id, blob) in blobs {
744 self.proposed_blobs.insert(&blob_id, blob)?;
745 }
746 Ok(())
747 }
748
749 fn update_locking(
751 &mut self,
752 locking: LockingBlock,
753 blobs: BTreeMap<BlobId, Blob>,
754 ) -> Result<(), ViewError> {
755 if let Some(old_locked) = self.locking_block.get() {
756 if old_locked.round() >= locking.round() {
757 return Ok(());
758 }
759 }
760 self.locking_block.set(Some(locking));
761 self.locking_blobs.clear();
762 for (blob_id, blob) in blobs {
763 self.locking_blobs.insert(&blob_id, blob)?;
764 }
765 Ok(())
766 }
767}
768
769#[derive(Default, Clone, Debug, Serialize, Deserialize)]
771#[cfg_attr(with_testing, derive(Eq, PartialEq))]
772pub struct ChainManagerInfo {
773 pub ownership: ChainOwnership,
775 pub requested_signed_proposal: Option<Box<BlockProposal>>,
778 #[debug(skip_if = Option::is_none)]
780 pub requested_proposed: Option<Box<BlockProposal>>,
781 #[debug(skip_if = Option::is_none)]
784 pub requested_locking: Option<Box<LockingBlock>>,
785 #[debug(skip_if = Option::is_none)]
787 pub timeout: Option<Box<TimeoutCertificate>>,
788 #[debug(skip_if = Option::is_none)]
790 pub pending: Option<LiteVote>,
791 #[debug(skip_if = Option::is_none)]
793 pub timeout_vote: Option<LiteVote>,
794 #[debug(skip_if = Option::is_none)]
796 pub fallback_vote: Option<LiteVote>,
797 #[debug(skip_if = Option::is_none)]
799 pub requested_confirmed: Option<Box<ConfirmedBlock>>,
800 #[debug(skip_if = Option::is_none)]
802 pub requested_validated: Option<Box<ValidatedBlock>>,
803 pub current_round: Round,
805 #[debug(skip_if = Option::is_none)]
808 pub leader: Option<AccountOwner>,
809 #[debug(skip_if = Option::is_none)]
811 pub round_timeout: Option<Timestamp>,
812}
813
814impl<C> From<&ChainManager<C>> for ChainManagerInfo
815where
816 C: Context + Clone + Send + Sync + 'static,
817{
818 fn from(manager: &ChainManager<C>) -> Self {
819 let current_round = manager.current_round();
820 let pending = match (manager.confirmed_vote.get(), manager.validated_vote.get()) {
821 (None, None) => None,
822 (Some(confirmed_vote), Some(validated_vote))
823 if validated_vote.round > confirmed_vote.round =>
824 {
825 Some(validated_vote.lite())
826 }
827 (Some(vote), _) => Some(vote.lite()),
828 (None, Some(vote)) => Some(vote.lite()),
829 };
830 ChainManagerInfo {
831 ownership: manager.ownership.get().clone(),
832 requested_signed_proposal: None,
833 requested_proposed: None,
834 requested_locking: None,
835 timeout: manager.timeout.get().clone().map(Box::new),
836 pending,
837 timeout_vote: manager.timeout_vote.get().as_ref().map(Vote::lite),
838 fallback_vote: manager.fallback_vote.get().as_ref().map(Vote::lite),
839 requested_confirmed: None,
840 requested_validated: None,
841 current_round,
842 leader: manager.round_leader(current_round).copied(),
843 round_timeout: *manager.round_timeout.get(),
844 }
845 }
846}
847
848impl ChainManagerInfo {
849 pub fn add_values<C>(&mut self, manager: &ChainManager<C>)
851 where
852 C: Context + Clone + Send + Sync + 'static,
853 C::Extra: ExecutionRuntimeContext,
854 {
855 self.requested_signed_proposal = manager.signed_proposal.get().clone().map(Box::new);
856 self.requested_proposed = manager.proposed.get().clone().map(Box::new);
857 self.requested_locking = manager.locking_block.get().clone().map(Box::new);
858 self.requested_confirmed = manager
859 .confirmed_vote
860 .get()
861 .as_ref()
862 .map(|vote| Box::new(vote.value.clone()));
863 self.requested_validated = manager
864 .validated_vote
865 .get()
866 .as_ref()
867 .map(|vote| Box::new(vote.value.clone()));
868 }
869
870 pub fn can_propose(
873 &self,
874 identity: &AccountOwner,
875 round: Round,
876 seed: u64,
877 current_committee: &BTreeMap<AccountOwner, u64>,
878 ) -> bool {
879 match round {
880 Round::Fast => self.ownership.super_owners.contains(identity),
881 Round::MultiLeader(_) => true,
882 Round::SingleLeader(r) => {
883 if let Some(distribution) = calculate_distribution(self.ownership.owners.iter()) {
884 let leader_index = round_leader_index(r, seed, Some(&distribution))
885 .expect("cannot fail if distribution is set");
886 self.ownership.owners.keys().nth(leader_index) == Some(identity)
887 } else {
888 tracing::warn!("no owners in chain ownership");
889 false
890 }
891 }
892 Round::Validator(r) => {
893 if let Some(distribution) = calculate_distribution(current_committee.iter()) {
894 let leader_index = round_leader_index(r, seed, Some(&distribution))
895 .expect("cannot fail if distribution is set");
896 current_committee.keys().nth(leader_index) == Some(identity)
897 } else {
898 tracing::warn!("no owners in current committee");
899 false
900 }
901 }
902 }
903 }
904
905 pub fn already_handled_proposal(&self, round: Round, proposed_block: &ProposedBlock) -> bool {
907 self.requested_proposed.as_ref().is_some_and(|proposal| {
908 proposal.content.round == round && *proposed_block == proposal.content.block
909 })
910 }
911
912 pub fn has_locking_block_in_current_round(&self) -> bool {
914 self.requested_locking
915 .as_ref()
916 .is_some_and(|locking| locking.round() == self.current_round)
917 }
918}
919
920fn calculate_distribution<'a, T: 'a>(
922 weights: impl IntoIterator<Item = (&'a T, &'a u64)>,
923) -> Option<WeightedAliasIndex<u64>> {
924 let weights: Vec<_> = weights.into_iter().map(|(_, weight)| *weight).collect();
925 if weights.is_empty() {
926 None
927 } else {
928 Some(WeightedAliasIndex::new(weights).ok()?)
929 }
930}
931
932fn round_leader_index(
934 round: u32,
935 seed: u64,
936 distribution: Option<&WeightedAliasIndex<u64>>,
937) -> Option<usize> {
938 let seed = u64::from(round).rotate_left(32).wrapping_add(seed);
939 let mut rng = ChaCha8Rng::seed_from_u64(seed);
940 Some(distribution?.sample(&mut rng))
941}