1use std::collections::BTreeMap;
72
73use allocative::Allocative;
74use custom_debug_derive::Debug;
75use futures::future::Either;
76use linera_base::{
77 crypto::{AccountPublicKey, ValidatorSecretKey},
78 data_types::{Blob, BlockHeight, Epoch, Round, Timestamp},
79 ensure,
80 identifiers::{AccountOwner, BlobId, ChainId},
81 ownership::ChainOwnership,
82};
83use linera_execution::ExecutionRuntimeContext;
84use linera_views::{
85 context::Context,
86 map_view::MapView,
87 register_view::RegisterView,
88 views::{ClonableView, View},
89 ViewError,
90};
91use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
92use rand_distr::{Distribution, WeightedAliasIndex};
93use serde::{Deserialize, Serialize};
94
95use crate::{
96 block::{Block, ConfirmedBlock, Timeout, ValidatedBlock},
97 data_types::{BlockProposal, LiteVote, OriginalProposal, ProposedBlock, Vote},
98 types::{TimeoutCertificate, ValidatedBlockCertificate},
99 ChainError,
100};
101
102#[derive(Eq, PartialEq)]
104pub enum Outcome {
105 Accept,
106 Skip,
107}
108
109pub type ValidatedOrConfirmedVote<'a> = Either<&'a Vote<ValidatedBlock>, &'a Vote<ConfirmedBlock>>;
110
111#[derive(Debug, Clone, Serialize, Deserialize, Allocative)]
115#[cfg_attr(with_testing, derive(Eq, PartialEq))]
116pub enum LockingBlock {
117 Fast(BlockProposal),
119 Regular(ValidatedBlockCertificate),
121}
122
123impl LockingBlock {
124 pub fn round(&self) -> Round {
127 match self {
128 Self::Fast(_) => Round::Fast,
129 Self::Regular(certificate) => certificate.round,
130 }
131 }
132
133 pub fn chain_id(&self) -> ChainId {
134 match self {
135 Self::Fast(proposal) => proposal.content.block.chain_id,
136 Self::Regular(certificate) => certificate.value().chain_id(),
137 }
138 }
139}
140
141#[cfg_attr(with_graphql, derive(async_graphql::SimpleObject), graphql(complex))]
143#[derive(Debug, View, ClonableView, Allocative)]
144#[allocative(bound = "C")]
145pub struct ChainManager<C>
146where
147 C: Clone + Context + 'static,
148{
149 pub ownership: RegisterView<C, ChainOwnership>,
151 pub seed: RegisterView<C, u64>,
153 #[cfg_attr(with_graphql, graphql(skip))] #[allocative(skip)]
156 pub distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
157 #[cfg_attr(with_graphql, graphql(skip))] #[allocative(skip)]
160 pub fallback_distribution: RegisterView<C, Option<WeightedAliasIndex<u64>>>,
161 #[cfg_attr(with_graphql, graphql(skip))]
166 pub signed_proposal: RegisterView<C, Option<BlockProposal>>,
167 #[cfg_attr(with_graphql, graphql(skip))]
170 pub proposed: RegisterView<C, Option<BlockProposal>>,
171 pub proposed_blobs: MapView<C, BlobId, Blob>,
173 #[cfg_attr(with_graphql, graphql(skip))]
176 pub locking_block: RegisterView<C, Option<LockingBlock>>,
177 pub locking_blobs: MapView<C, BlobId, Blob>,
179 #[cfg_attr(with_graphql, graphql(skip))]
181 pub timeout: RegisterView<C, Option<TimeoutCertificate>>,
182 #[cfg_attr(with_graphql, graphql(skip))]
184 pub confirmed_vote: RegisterView<C, Option<Vote<ConfirmedBlock>>>,
185 #[cfg_attr(with_graphql, graphql(skip))]
187 pub validated_vote: RegisterView<C, Option<Vote<ValidatedBlock>>>,
188 #[cfg_attr(with_graphql, graphql(skip))]
190 pub timeout_vote: RegisterView<C, Option<Vote<Timeout>>>,
191 #[cfg_attr(with_graphql, graphql(skip))]
193 pub fallback_vote: RegisterView<C, Option<Vote<Timeout>>>,
194 pub round_timeout: RegisterView<C, Option<Timestamp>>,
196 #[cfg_attr(with_graphql, graphql(skip))]
203 pub current_round: RegisterView<C, Round>,
204 pub fallback_owners: RegisterView<C, BTreeMap<AccountOwner, u64>>,
206}
207
208#[cfg(with_graphql)]
209#[async_graphql::ComplexObject]
210impl<C> ChainManager<C>
211where
212 C: Context + Clone + 'static,
213{
214 #[graphql(derived(name = "current_round"))]
221 async fn _current_round(&self) -> Round {
222 self.current_round()
223 }
224}
225
226impl<C> ChainManager<C>
227where
228 C: Context + Clone + 'static,
229{
230 pub fn reset<'a>(
232 &mut self,
233 ownership: ChainOwnership,
234 height: BlockHeight,
235 local_time: Timestamp,
236 fallback_owners: impl Iterator<Item = (AccountPublicKey, u64)> + 'a,
237 ) -> Result<(), ChainError> {
238 let distribution = calculate_distribution(ownership.owners.iter());
239
240 let fallback_owners = fallback_owners
241 .map(|(pub_key, weight)| (AccountOwner::from(pub_key), weight))
242 .collect::<BTreeMap<_, _>>();
243 let fallback_distribution = calculate_distribution(fallback_owners.iter());
244
245 let current_round = ownership.first_round();
246 let round_duration = ownership.round_timeout(current_round);
247 let round_timeout = round_duration.map(|rd| local_time.saturating_add(rd));
248
249 self.clear();
250 self.seed.set(height.0);
251 self.ownership.set(ownership);
252 self.distribution.set(distribution);
253 self.fallback_distribution.set(fallback_distribution);
254 self.fallback_owners.set(fallback_owners);
255 self.current_round.set(current_round);
256 self.round_timeout.set(round_timeout);
257 Ok(())
258 }
259
260 pub fn confirmed_vote(&self) -> Option<&Vote<ConfirmedBlock>> {
262 self.confirmed_vote.get().as_ref()
263 }
264
265 pub fn validated_vote(&self) -> Option<&Vote<ValidatedBlock>> {
267 self.validated_vote.get().as_ref()
268 }
269
270 pub fn timeout_vote(&self) -> Option<&Vote<Timeout>> {
272 self.timeout_vote.get().as_ref()
273 }
274
275 pub fn fallback_vote(&self) -> Option<&Vote<Timeout>> {
277 self.fallback_vote.get().as_ref()
278 }
279
280 pub fn current_round(&self) -> Round {
287 *self.current_round.get()
288 }
289
290 pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
292 let new_block = &proposal.content.block;
293 let new_round = proposal.content.round;
294 if let Some(old_proposal) = self.proposed.get() {
295 if old_proposal.content == proposal.content {
296 return Ok(Outcome::Skip); }
298 }
299 ensure!(
301 new_block.height < BlockHeight::MAX,
302 ChainError::BlockHeightOverflow
303 );
304 let current_round = self.current_round();
305 match new_round {
306 Round::Fast => {}
309 Round::MultiLeader(_) | Round::SingleLeader(0) => {
310 ensure!(
313 self.is_super(&proposal.owner()) || !current_round.is_fast(),
314 ChainError::WrongRound(current_round)
315 );
316 ensure!(
318 new_round >= current_round,
319 ChainError::InsufficientRound(new_round)
320 );
321 }
322 Round::SingleLeader(_) | Round::Validator(_) => {
323 ensure!(
325 new_round == current_round,
326 ChainError::WrongRound(current_round)
327 );
328 }
329 }
330 if let Some(vote) = self.validated_vote() {
332 ensure!(
333 new_round > vote.round,
334 ChainError::InsufficientRoundStrict(vote.round)
335 );
336 }
337 if let Some(locking_block) = self.locking_block.get() {
339 ensure!(
340 locking_block.round() < new_round,
341 ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())
342 );
343 }
344 if let Some(vote) = self.confirmed_vote() {
347 ensure!(
348 match proposal.original_proposal.as_ref() {
349 None => false,
350 Some(OriginalProposal::Regular { certificate }) =>
351 vote.round <= certificate.round,
352 Some(OriginalProposal::Fast(_)) => {
353 vote.round.is_fast() && vote.value().matches_proposed_block(new_block)
354 }
355 },
356 ChainError::HasIncompatibleConfirmedVote(new_block.height, vote.round)
357 );
358 }
359 Ok(Outcome::Accept)
360 }
361
362 pub fn create_timeout_vote(
365 &mut self,
366 chain_id: ChainId,
367 height: BlockHeight,
368 round: Round,
369 epoch: Epoch,
370 key_pair: Option<&ValidatorSecretKey>,
371 local_time: Timestamp,
372 ) -> Result<bool, ChainError> {
373 let Some(key_pair) = key_pair else {
374 return Ok(false); };
376 ensure!(
377 round == self.current_round(),
378 ChainError::WrongRound(self.current_round())
379 );
380 let Some(round_timeout) = *self.round_timeout.get() else {
381 return Err(ChainError::RoundDoesNotTimeOut);
382 };
383 ensure!(
384 local_time >= round_timeout,
385 ChainError::NotTimedOutYet(round_timeout)
386 );
387 if let Some(vote) = self.timeout_vote.get() {
388 if vote.round == round {
389 return Ok(false); }
391 }
392 let value = Timeout::new(chain_id, height, epoch);
393 self.timeout_vote
394 .set(Some(Vote::new(value, round, key_pair)));
395 Ok(true)
396 }
397
398 pub fn vote_fallback(
403 &mut self,
404 chain_id: ChainId,
405 height: BlockHeight,
406 epoch: Epoch,
407 key_pair: Option<&ValidatorSecretKey>,
408 ) -> bool {
409 let Some(key_pair) = key_pair else {
410 return false; };
412 if self.fallback_vote.get().is_some() || self.current_round() >= Round::Validator(0) {
413 return false; }
415 let value = Timeout::new(chain_id, height, epoch);
416 let last_regular_round = Round::SingleLeader(u32::MAX);
417 self.fallback_vote
418 .set(Some(Vote::new(value, last_regular_round, key_pair)));
419 true
420 }
421
422 pub fn check_validated_block(
424 &self,
425 certificate: &ValidatedBlockCertificate,
426 ) -> Result<Outcome, ChainError> {
427 let new_block = certificate.block();
428 let new_round = certificate.round;
429 if let Some(Vote { value, round, .. }) = self.confirmed_vote.get() {
430 if value.block() == new_block && *round == new_round {
431 return Ok(Outcome::Skip); }
433 }
434
435 if let Some(Vote { round, .. }) = self.validated_vote.get() {
437 ensure!(new_round >= *round, ChainError::InsufficientRound(*round))
438 }
439
440 if let Some(locking) = self.locking_block.get() {
441 ensure!(
442 new_round > locking.round(),
443 ChainError::InsufficientRoundStrict(locking.round())
444 );
445 }
446 Ok(Outcome::Accept)
447 }
448
449 pub fn create_vote(
451 &mut self,
452 proposal: BlockProposal,
453 block: Block,
454 key_pair: Option<&ValidatorSecretKey>,
455 local_time: Timestamp,
456 blobs: BTreeMap<BlobId, Blob>,
457 ) -> Result<Option<ValidatedOrConfirmedVote>, ChainError> {
458 let round = proposal.content.round;
459
460 match &proposal.original_proposal {
461 Some(OriginalProposal::Regular { certificate }) => {
463 if self
464 .locking_block
465 .get()
466 .as_ref()
467 .is_none_or(|locking| locking.round() < certificate.round)
468 {
469 let value = ValidatedBlock::new(block.clone());
470 if let Some(certificate) = certificate.clone().with_value(value) {
471 self.update_locking(LockingBlock::Regular(certificate), blobs.clone())?;
472 }
473 }
474 }
475 Some(OriginalProposal::Fast(signature)) => {
478 if self.locking_block.get().is_none() {
479 let original_proposal = BlockProposal {
480 signature: *signature,
481 ..proposal.clone()
482 };
483 self.update_locking(LockingBlock::Fast(original_proposal), blobs.clone())?;
484 }
485 }
486 None => {
489 if round.is_fast() && self.locking_block.get().is_none() {
490 self.update_locking(LockingBlock::Fast(proposal.clone()), blobs.clone())?;
492 }
493 }
494 }
495
496 self.update_proposed(proposal.clone(), blobs)?;
498 self.update_current_round(local_time);
499
500 let Some(key_pair) = key_pair else {
501 return Ok(None);
503 };
504
505 if round.is_fast() {
507 self.validated_vote.set(None);
508 let value = ConfirmedBlock::new(block);
509 let vote = Vote::new(value, round, key_pair);
510 Ok(Some(Either::Right(
511 self.confirmed_vote.get_mut().insert(vote),
512 )))
513 } else {
514 let value = ValidatedBlock::new(block);
515 let vote = Vote::new(value, round, key_pair);
516 Ok(Some(Either::Left(
517 self.validated_vote.get_mut().insert(vote),
518 )))
519 }
520 }
521
522 pub fn create_final_vote(
524 &mut self,
525 validated: ValidatedBlockCertificate,
526 key_pair: Option<&ValidatorSecretKey>,
527 local_time: Timestamp,
528 blobs: BTreeMap<BlobId, Blob>,
529 ) -> Result<(), ViewError> {
530 let round = validated.round;
531 let confirmed_block = ConfirmedBlock::new(validated.inner().block().clone());
532 self.update_locking(LockingBlock::Regular(validated), blobs)?;
533 self.update_current_round(local_time);
534 if let Some(key_pair) = key_pair {
535 if self.current_round() != round {
536 return Ok(()); }
538 let vote = Vote::new(confirmed_block, round, key_pair);
540 self.confirmed_vote.set(Some(vote));
542 self.validated_vote.set(None);
543 }
544 Ok(())
545 }
546
547 pub async fn pending_blob(&self, blob_id: &BlobId) -> Result<Option<Blob>, ViewError> {
549 if let Some(blob) = self.proposed_blobs.get(blob_id).await? {
550 return Ok(Some(blob));
551 }
552 self.locking_blobs.get(blob_id).await
553 }
554
555 pub async fn pending_blobs(&self, blob_ids: &[BlobId]) -> Result<Vec<Option<Blob>>, ViewError> {
557 let mut blobs = self.proposed_blobs.multi_get(blob_ids).await?;
558 let mut missing_indices = Vec::new();
559 let mut missing_blob_ids = Vec::new();
560 for (i, (blob, blob_id)) in blobs.iter().zip(blob_ids).enumerate() {
561 if blob.is_none() {
562 missing_indices.push(i);
563 missing_blob_ids.push(blob_id);
564 }
565 }
566 let second_blobs = self.locking_blobs.multi_get(missing_blob_ids).await?;
567 for (blob, i) in second_blobs.into_iter().zip(missing_indices) {
568 blobs[i] = blob;
569 }
570 Ok(blobs)
571 }
572
573 fn update_current_round(&mut self, local_time: Timestamp) {
591 let current_round = self
592 .timeout
593 .get()
594 .iter()
595 .map(|certificate| {
597 self.ownership
598 .get()
599 .next_round(certificate.round)
600 .unwrap_or(Round::Validator(u32::MAX))
601 })
602 .chain(self.locking_block.get().as_ref().map(LockingBlock::round))
605 .chain(
606 self.proposed
607 .get()
608 .iter()
609 .chain(self.signed_proposal.get())
610 .map(|proposal| proposal.content.round),
611 )
612 .max()
613 .unwrap_or_default()
614 .max(self.ownership.get().first_round());
616 if current_round <= self.current_round() {
617 return;
618 }
619 let round_duration = self.ownership.get().round_timeout(current_round);
620 self.round_timeout
621 .set(round_duration.map(|rd| local_time.saturating_add(rd)));
622 self.current_round.set(current_round);
623 }
624
625 pub fn handle_timeout_certificate(
628 &mut self,
629 certificate: TimeoutCertificate,
630 local_time: Timestamp,
631 ) {
632 let round = certificate.round;
633 if let Some(known_certificate) = self.timeout.get() {
634 if known_certificate.round >= round {
635 return;
636 }
637 }
638 self.timeout.set(Some(certificate));
639 self.update_current_round(local_time);
640 }
641
642 pub fn can_propose(&self, owner: &AccountOwner, round: Round) -> bool {
650 let ownership = self.ownership.get();
651 if ownership.super_owners.contains(owner) {
652 return !round.is_validator();
653 }
654 match round {
655 Round::Fast => false,
656 Round::MultiLeader(_) => ownership.is_multi_leader_owner(owner),
657 Round::SingleLeader(_) | Round::Validator(_) => self.round_leader(round) == Some(owner),
658 }
659 }
660
661 fn round_leader(&self, round: Round) -> Option<&AccountOwner> {
664 let ownership = self.ownership.get();
665 compute_round_leader(
666 round,
667 *self.seed.get(),
668 ownership.first_leader.as_ref(),
669 &ownership.owners,
670 self.distribution.get().as_ref(),
671 self.fallback_owners.get(),
672 self.fallback_distribution.get().as_ref(),
673 )
674 }
675
676 fn is_super(&self, owner: &AccountOwner) -> bool {
678 self.ownership.get().super_owners.contains(owner)
679 }
680
681 pub fn update_signed_proposal(
687 &mut self,
688 proposal: &BlockProposal,
689 local_time: Timestamp,
690 ) -> bool {
691 if proposal.content.round > Round::SingleLeader(0) {
692 return false;
693 }
694 if let Some(old_proposal) = self.signed_proposal.get() {
695 if old_proposal.content.round >= proposal.content.round {
696 if *self.current_round.get() < old_proposal.content.round {
697 tracing::warn!(
698 chain_id = %proposal.content.block.chain_id,
699 current_round = ?self.current_round.get(),
700 proposal_round = ?old_proposal.content.round,
701 "Proposal round is greater than current round. Updating."
702 );
703 self.update_current_round(local_time);
704 return true;
705 }
706 return false;
707 }
708 }
709 if let Some(old_proposal) = self.proposed.get() {
710 if old_proposal.content.round >= proposal.content.round {
711 return false;
712 }
713 }
714 self.signed_proposal.set(Some(proposal.clone()));
715 self.update_current_round(local_time);
716 true
717 }
718
719 fn update_proposed(
721 &mut self,
722 proposal: BlockProposal,
723 blobs: BTreeMap<BlobId, Blob>,
724 ) -> Result<(), ViewError> {
725 if let Some(old_proposal) = self.proposed.get() {
726 if old_proposal.content.round >= proposal.content.round {
727 return Ok(());
728 }
729 }
730 if let Some(old_proposal) = self.signed_proposal.get() {
731 if old_proposal.content.round <= proposal.content.round {
732 self.signed_proposal.set(None);
733 }
734 }
735 self.proposed.set(Some(proposal));
736 self.proposed_blobs.clear();
737 for (blob_id, blob) in blobs {
738 self.proposed_blobs.insert(&blob_id, blob)?;
739 }
740 Ok(())
741 }
742
743 fn update_locking(
745 &mut self,
746 locking: LockingBlock,
747 blobs: BTreeMap<BlobId, Blob>,
748 ) -> Result<(), ViewError> {
749 if let Some(old_locked) = self.locking_block.get() {
750 if old_locked.round() >= locking.round() {
751 return Ok(());
752 }
753 }
754 self.locking_block.set(Some(locking));
755 self.locking_blobs.clear();
756 for (blob_id, blob) in blobs {
757 self.locking_blobs.insert(&blob_id, blob)?;
758 }
759 Ok(())
760 }
761}
762
763#[derive(Default, Clone, Debug, Serialize, Deserialize)]
765#[cfg_attr(with_testing, derive(Eq, PartialEq))]
766pub struct ChainManagerInfo {
767 pub ownership: ChainOwnership,
769 pub seed: u64,
771 pub requested_signed_proposal: Option<Box<BlockProposal>>,
774 #[debug(skip_if = Option::is_none)]
776 pub requested_proposed: Option<Box<BlockProposal>>,
777 #[debug(skip_if = Option::is_none)]
780 pub requested_locking: Option<Box<LockingBlock>>,
781 #[debug(skip_if = Option::is_none)]
783 pub timeout: Option<Box<TimeoutCertificate>>,
784 #[debug(skip_if = Option::is_none)]
786 pub pending: Option<LiteVote>,
787 #[debug(skip_if = Option::is_none)]
789 pub timeout_vote: Option<LiteVote>,
790 #[debug(skip_if = Option::is_none)]
792 pub fallback_vote: Option<LiteVote>,
793 #[debug(skip_if = Option::is_none)]
795 pub requested_confirmed: Option<Box<ConfirmedBlock>>,
796 #[debug(skip_if = Option::is_none)]
798 pub requested_validated: Option<Box<ValidatedBlock>>,
799 pub current_round: Round,
801 #[debug(skip_if = Option::is_none)]
804 pub leader: Option<AccountOwner>,
805 #[debug(skip_if = Option::is_none)]
807 pub round_timeout: Option<Timestamp>,
808}
809
810impl<C> From<&ChainManager<C>> for ChainManagerInfo
811where
812 C: Context + Clone + 'static,
813{
814 fn from(manager: &ChainManager<C>) -> Self {
815 let current_round = manager.current_round();
816 let pending = match (manager.confirmed_vote.get(), manager.validated_vote.get()) {
817 (None, None) => None,
818 (Some(confirmed_vote), Some(validated_vote))
819 if validated_vote.round > confirmed_vote.round =>
820 {
821 Some(validated_vote.lite())
822 }
823 (Some(vote), _) => Some(vote.lite()),
824 (None, Some(vote)) => Some(vote.lite()),
825 };
826 ChainManagerInfo {
827 ownership: manager.ownership.get().clone(),
828 seed: *manager.seed.get(),
829 requested_signed_proposal: None,
830 requested_proposed: None,
831 requested_locking: None,
832 timeout: manager.timeout.get().clone().map(Box::new),
833 pending,
834 timeout_vote: manager.timeout_vote.get().as_ref().map(Vote::lite),
835 fallback_vote: manager.fallback_vote.get().as_ref().map(Vote::lite),
836 requested_confirmed: None,
837 requested_validated: None,
838 current_round,
839 leader: manager.round_leader(current_round).copied(),
840 round_timeout: *manager.round_timeout.get(),
841 }
842 }
843}
844
845impl ChainManagerInfo {
846 pub fn add_values<C>(&mut self, manager: &ChainManager<C>)
848 where
849 C: Context + Clone + 'static,
850 C::Extra: ExecutionRuntimeContext,
851 {
852 self.requested_signed_proposal = manager.signed_proposal.get().clone().map(Box::new);
853 self.requested_proposed = manager.proposed.get().clone().map(Box::new);
854 self.requested_locking = manager.locking_block.get().clone().map(Box::new);
855 self.requested_confirmed = manager
856 .confirmed_vote
857 .get()
858 .as_ref()
859 .map(|vote| Box::new(vote.value.clone()));
860 self.requested_validated = manager
861 .validated_vote
862 .get()
863 .as_ref()
864 .map(|vote| Box::new(vote.value.clone()));
865 }
866
867 pub fn should_propose(
872 &self,
873 identity: &AccountOwner,
874 round: Round,
875 seed: u64,
876 current_committee: &BTreeMap<AccountOwner, u64>,
877 ) -> bool {
878 match round {
879 Round::Fast => self.ownership.super_owners.contains(identity),
880 Round::MultiLeader(_) => self.ownership.is_multi_leader_owner(identity),
881 Round::SingleLeader(_) | Round::Validator(_) => {
882 let distribution = calculate_distribution(self.ownership.owners.iter());
883 let fallback_distribution = calculate_distribution(current_committee.iter());
884 let leader = compute_round_leader(
885 round,
886 seed,
887 self.ownership.first_leader.as_ref(),
888 &self.ownership.owners,
889 distribution.as_ref(),
890 current_committee,
891 fallback_distribution.as_ref(),
892 );
893 leader == Some(identity)
894 }
895 }
896 }
897
898 pub fn already_handled_proposal(&self, round: Round, proposed_block: &ProposedBlock) -> bool {
900 self.requested_proposed.as_ref().is_some_and(|proposal| {
901 proposal.content.round == round && *proposed_block == proposal.content.block
902 })
903 }
904
905 pub fn has_locking_block_in_current_round(&self) -> bool {
907 self.requested_locking
908 .as_ref()
909 .is_some_and(|locking| locking.round() == self.current_round)
910 }
911}
912
913fn calculate_distribution<'a, T: 'a>(
915 weights: impl IntoIterator<Item = (&'a T, &'a u64)>,
916) -> Option<WeightedAliasIndex<u64>> {
917 let weights: Vec<_> = weights.into_iter().map(|(_, weight)| *weight).collect();
918 if weights.is_empty() {
919 None
920 } else {
921 Some(WeightedAliasIndex::new(weights).ok()?)
922 }
923}
924
925fn compute_round_leader<'a>(
928 round: Round,
929 seed: u64,
930 first_leader: Option<&'a AccountOwner>,
931 owners: &'a BTreeMap<AccountOwner, u64>,
932 distribution: Option<&WeightedAliasIndex<u64>>,
933 fallback_owners: &'a BTreeMap<AccountOwner, u64>,
934 fallback_distribution: Option<&WeightedAliasIndex<u64>>,
935) -> Option<&'a AccountOwner> {
936 match round {
937 Round::SingleLeader(r) => {
938 if r == 0 {
939 if let Some(first_leader) = first_leader {
940 return Some(first_leader);
941 }
942 }
943 let index = round_leader_index(r, seed, distribution)?;
944 owners.keys().nth(index)
945 }
946 Round::Validator(r) => {
947 let index = round_leader_index(r, seed, fallback_distribution)?;
948 fallback_owners.keys().nth(index)
949 }
950 Round::Fast | Round::MultiLeader(_) => None,
951 }
952}
953
954fn round_leader_index(
956 round: u32,
957 seed: u64,
958 distribution: Option<&WeightedAliasIndex<u64>>,
959) -> Option<usize> {
960 let seed = u64::from(round).rotate_left(32).wrapping_add(seed);
961 let mut rng = ChaCha8Rng::seed_from_u64(seed);
962 Some(distribution?.sample(&mut rng))
963}