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 current_round(&self) -> Round {
277 *self.current_round.get()
278 }
279
280 pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
282 let new_block = &proposal.content.block;
283 let new_round = proposal.content.round;
284 if let Some(old_proposal) = self.proposed.get() {
285 if old_proposal.content == proposal.content {
286 return Ok(Outcome::Skip); }
288 }
289 ensure!(
291 new_block.height < BlockHeight::MAX,
292 ChainError::BlockHeightOverflow
293 );
294 let current_round = self.current_round();
295 match new_round {
296 Round::Fast => {}
299 Round::MultiLeader(_) | Round::SingleLeader(0) => {
300 ensure!(
303 self.is_super(&proposal.owner()) || !current_round.is_fast(),
304 ChainError::WrongRound(current_round)
305 );
306 ensure!(
308 new_round >= current_round,
309 ChainError::InsufficientRound(new_round)
310 );
311 }
312 Round::SingleLeader(_) | Round::Validator(_) => {
313 ensure!(
315 new_round == current_round,
316 ChainError::WrongRound(current_round)
317 );
318 }
319 }
320 if let Some(vote) = self.validated_vote() {
322 ensure!(
323 new_round > vote.round,
324 ChainError::InsufficientRoundStrict(vote.round)
325 );
326 }
327 if let Some(locking_block) = self.locking_block.get() {
329 ensure!(
330 locking_block.round() < new_round,
331 ChainError::MustBeNewerThanLockingBlock(new_block.height, locking_block.round())
332 );
333 }
334 if let Some(vote) = self.confirmed_vote() {
337 ensure!(
338 match proposal.original_proposal.as_ref() {
339 None => false,
340 Some(OriginalProposal::Regular { certificate }) =>
341 vote.round <= certificate.round,
342 Some(OriginalProposal::Fast(_)) => {
343 vote.round.is_fast() && vote.value().matches_proposed_block(new_block)
344 }
345 },
346 ChainError::HasIncompatibleConfirmedVote(new_block.height, vote.round)
347 );
348 }
349 Ok(Outcome::Accept)
350 }
351
352 pub fn create_timeout_vote(
355 &mut self,
356 chain_id: ChainId,
357 height: BlockHeight,
358 round: Round,
359 epoch: Epoch,
360 key_pair: Option<&ValidatorSecretKey>,
361 local_time: Timestamp,
362 ) -> Result<bool, ChainError> {
363 let Some(key_pair) = key_pair else {
364 return Ok(false); };
366 ensure!(
367 round == self.current_round(),
368 ChainError::WrongRound(self.current_round())
369 );
370 let Some(round_timeout) = *self.round_timeout.get() else {
371 return Err(ChainError::RoundDoesNotTimeOut);
372 };
373 ensure!(
374 local_time >= round_timeout,
375 ChainError::NotTimedOutYet(round_timeout)
376 );
377 if let Some(vote) = self.timeout_vote.get() {
378 if vote.round == round {
379 return Ok(false); }
381 }
382 let value = Timeout::new(chain_id, height, epoch);
383 self.timeout_vote
384 .set(Some(Vote::new(value, round, key_pair)));
385 Ok(true)
386 }
387
388 pub fn vote_fallback(
393 &mut self,
394 chain_id: ChainId,
395 height: BlockHeight,
396 epoch: Epoch,
397 key_pair: Option<&ValidatorSecretKey>,
398 ) -> bool {
399 let Some(key_pair) = key_pair else {
400 return false; };
402 if self.fallback_vote.get().is_some() || self.current_round() >= Round::Validator(0) {
403 return false; }
405 let value = Timeout::new(chain_id, height, epoch);
406 let last_regular_round = Round::SingleLeader(u32::MAX);
407 self.fallback_vote
408 .set(Some(Vote::new(value, last_regular_round, key_pair)));
409 true
410 }
411
412 pub fn check_validated_block(
414 &self,
415 certificate: &ValidatedBlockCertificate,
416 ) -> Result<Outcome, ChainError> {
417 let new_block = certificate.block();
418 let new_round = certificate.round;
419 if let Some(Vote { value, round, .. }) = self.confirmed_vote.get() {
420 if value.block() == new_block && *round == new_round {
421 return Ok(Outcome::Skip); }
423 }
424
425 if let Some(Vote { round, .. }) = self.validated_vote.get() {
427 ensure!(new_round >= *round, ChainError::InsufficientRound(*round))
428 }
429
430 if let Some(locking) = self.locking_block.get() {
431 ensure!(
432 new_round > locking.round(),
433 ChainError::InsufficientRoundStrict(locking.round())
434 );
435 }
436 Ok(Outcome::Accept)
437 }
438
439 pub fn create_vote(
441 &mut self,
442 proposal: &BlockProposal,
443 block: Block,
444 key_pair: Option<&ValidatorSecretKey>,
445 local_time: Timestamp,
446 blobs: BTreeMap<BlobId, Blob>,
447 ) -> Result<Option<ValidatedOrConfirmedVote<'_>>, ChainError> {
448 let round = proposal.content.round;
449
450 match &proposal.original_proposal {
451 Some(OriginalProposal::Regular { certificate }) => {
453 if self
454 .locking_block
455 .get()
456 .as_ref()
457 .is_none_or(|locking| locking.round() < certificate.round)
458 {
459 let value = ValidatedBlock::new(block.clone());
460 if let Some(certificate) = certificate.clone().with_value(value) {
461 self.update_locking(LockingBlock::Regular(certificate), blobs.clone())?;
462 }
463 }
464 }
465 Some(OriginalProposal::Fast(signature)) => {
468 if self.locking_block.get().is_none() {
469 let original_proposal = BlockProposal {
470 signature: *signature,
471 ..proposal.clone()
472 };
473 self.update_locking(LockingBlock::Fast(original_proposal), blobs.clone())?;
474 }
475 }
476 None => {
479 if round.is_fast() && self.locking_block.get().is_none() {
480 self.update_locking(LockingBlock::Fast(proposal.clone()), blobs.clone())?;
482 }
483 }
484 }
485
486 self.update_proposed(proposal.clone(), blobs)?;
488 self.update_current_round(local_time);
489
490 let Some(key_pair) = key_pair else {
491 return Ok(None);
493 };
494
495 if round.is_fast() {
497 self.validated_vote.set(None);
498 let value = ConfirmedBlock::new(block);
499 let vote = Vote::new(value, round, key_pair);
500 Ok(Some(Either::Right(
501 self.confirmed_vote.get_mut().insert(vote),
502 )))
503 } else {
504 let value = ValidatedBlock::new(block);
505 let vote = Vote::new(value, round, key_pair);
506 Ok(Some(Either::Left(
507 self.validated_vote.get_mut().insert(vote),
508 )))
509 }
510 }
511
512 pub fn create_final_vote(
514 &mut self,
515 validated: ValidatedBlockCertificate,
516 key_pair: Option<&ValidatorSecretKey>,
517 local_time: Timestamp,
518 blobs: BTreeMap<BlobId, Blob>,
519 ) -> Result<(), ViewError> {
520 let round = validated.round;
521 let confirmed_block = ConfirmedBlock::new(validated.inner().block().clone());
522 self.update_locking(LockingBlock::Regular(validated), blobs)?;
523 self.update_current_round(local_time);
524 if let Some(key_pair) = key_pair {
525 if self.current_round() != round {
526 return Ok(()); }
528 let vote = Vote::new(confirmed_block, round, key_pair);
530 self.confirmed_vote.set(Some(vote));
532 self.validated_vote.set(None);
533 }
534 Ok(())
535 }
536
537 pub async fn pending_blob(&self, blob_id: &BlobId) -> Result<Option<Blob>, ViewError> {
539 if let Some(blob) = self.proposed_blobs.get(blob_id).await? {
540 return Ok(Some(blob));
541 }
542 self.locking_blobs.get(blob_id).await
543 }
544
545 pub async fn pending_blobs(&self, blob_ids: &[BlobId]) -> Result<Vec<Option<Blob>>, ViewError> {
547 let mut blobs = self.proposed_blobs.multi_get(blob_ids).await?;
548 let mut missing_indices = Vec::new();
549 let mut missing_blob_ids = Vec::new();
550 for (i, (blob, blob_id)) in blobs.iter().zip(blob_ids).enumerate() {
551 if blob.is_none() {
552 missing_indices.push(i);
553 missing_blob_ids.push(blob_id);
554 }
555 }
556 let second_blobs = self.locking_blobs.multi_get(missing_blob_ids).await?;
557 for (blob, i) in second_blobs.into_iter().zip(missing_indices) {
558 blobs[i] = blob;
559 }
560 Ok(blobs)
561 }
562
563 fn update_current_round(&mut self, local_time: Timestamp) {
581 let current_round = self
582 .timeout
583 .get()
584 .iter()
585 .map(|certificate| {
587 self.ownership
588 .get()
589 .next_round(certificate.round)
590 .unwrap_or(Round::Validator(u32::MAX))
591 })
592 .chain(self.locking_block.get().as_ref().map(LockingBlock::round))
595 .chain(
596 self.proposed
597 .get()
598 .iter()
599 .chain(self.signed_proposal.get())
600 .map(|proposal| proposal.content.round),
601 )
602 .max()
603 .unwrap_or_default()
604 .max(self.ownership.get().first_round());
606 if current_round <= self.current_round() {
607 return;
608 }
609 let round_duration = self.ownership.get().round_timeout(current_round);
610 self.round_timeout
611 .set(round_duration.map(|rd| local_time.saturating_add(rd)));
612 self.current_round.set(current_round);
613 }
614
615 pub fn handle_timeout_certificate(
618 &mut self,
619 certificate: TimeoutCertificate,
620 local_time: Timestamp,
621 ) {
622 let round = certificate.round;
623 if let Some(known_certificate) = self.timeout.get() {
624 if known_certificate.round >= round {
625 return;
626 }
627 }
628 self.timeout.set(Some(certificate));
629 self.update_current_round(local_time);
630 }
631
632 pub fn can_propose(&self, owner: &AccountOwner, round: Round) -> bool {
640 let ownership = self.ownership.get();
641 if ownership.super_owners.contains(owner) {
642 return !round.is_validator();
643 }
644 match round {
645 Round::Fast => false,
646 Round::MultiLeader(_) => ownership.can_propose_in_multi_leader_round(owner),
647 Round::SingleLeader(_) | Round::Validator(_) => self.round_leader(round) == Some(owner),
648 }
649 }
650
651 fn round_leader(&self, round: Round) -> Option<&AccountOwner> {
654 let ownership = self.ownership.get();
655 compute_round_leader(
656 round,
657 *self.seed.get(),
658 ownership.first_leader.as_ref(),
659 &ownership.owners,
660 self.distribution.get().as_ref(),
661 self.fallback_owners.get(),
662 self.fallback_distribution.get().as_ref(),
663 )
664 }
665
666 fn is_super(&self, owner: &AccountOwner) -> bool {
668 self.ownership.get().super_owners.contains(owner)
669 }
670
671 pub fn update_signed_proposal(
677 &mut self,
678 proposal: &BlockProposal,
679 local_time: Timestamp,
680 ) -> bool {
681 if proposal.content.round > Round::SingleLeader(0) {
682 return false;
683 }
684 if let Some(old_proposal) = self.signed_proposal.get() {
685 if old_proposal.content.round >= proposal.content.round {
686 if *self.current_round.get() < old_proposal.content.round {
687 tracing::warn!(
688 chain_id = %proposal.content.block.chain_id,
689 current_round = ?self.current_round.get(),
690 proposal_round = ?old_proposal.content.round,
691 "Proposal round is greater than current round. Updating."
692 );
693 self.update_current_round(local_time);
694 return true;
695 }
696 return false;
697 }
698 }
699 if let Some(old_proposal) = self.proposed.get() {
700 if old_proposal.content.round >= proposal.content.round {
701 return false;
702 }
703 }
704 self.signed_proposal.set(Some(proposal.clone()));
705 self.update_current_round(local_time);
706 true
707 }
708
709 fn update_proposed(
711 &mut self,
712 proposal: BlockProposal,
713 blobs: BTreeMap<BlobId, Blob>,
714 ) -> Result<(), ViewError> {
715 if let Some(old_proposal) = self.proposed.get() {
716 if old_proposal.content.round >= proposal.content.round {
717 return Ok(());
718 }
719 }
720 if let Some(old_proposal) = self.signed_proposal.get() {
721 if old_proposal.content.round <= proposal.content.round {
722 self.signed_proposal.set(None);
723 }
724 }
725 self.proposed.set(Some(proposal));
726 self.proposed_blobs.clear();
727 for (blob_id, blob) in blobs {
728 self.proposed_blobs.insert(&blob_id, blob)?;
729 }
730 Ok(())
731 }
732
733 fn update_locking(
735 &mut self,
736 locking: LockingBlock,
737 blobs: BTreeMap<BlobId, Blob>,
738 ) -> Result<(), ViewError> {
739 if let Some(old_locked) = self.locking_block.get() {
740 if old_locked.round() >= locking.round() {
741 return Ok(());
742 }
743 }
744 self.locking_block.set(Some(locking));
745 self.locking_blobs.clear();
746 for (blob_id, blob) in blobs {
747 self.locking_blobs.insert(&blob_id, blob)?;
748 }
749 Ok(())
750 }
751}
752
753#[derive(Default, Clone, Debug, Serialize, Deserialize)]
755#[cfg_attr(with_testing, derive(Eq, PartialEq))]
756pub struct ChainManagerInfo {
757 pub ownership: ChainOwnership,
759 pub seed: u64,
761 pub requested_signed_proposal: Option<Box<BlockProposal>>,
764 #[debug(skip_if = Option::is_none)]
766 pub requested_proposed: Option<Box<BlockProposal>>,
767 #[debug(skip_if = Option::is_none)]
770 pub requested_locking: Option<Box<LockingBlock>>,
771 #[debug(skip_if = Option::is_none)]
773 pub timeout: Option<Box<TimeoutCertificate>>,
774 #[debug(skip_if = Option::is_none)]
776 pub pending: Option<LiteVote>,
777 #[debug(skip_if = Option::is_none)]
779 pub timeout_vote: Option<LiteVote>,
780 #[debug(skip_if = Option::is_none)]
782 pub fallback_vote: Option<LiteVote>,
783 #[debug(skip_if = Option::is_none)]
785 pub requested_confirmed: Option<Box<ConfirmedBlock>>,
786 #[debug(skip_if = Option::is_none)]
788 pub requested_validated: Option<Box<ValidatedBlock>>,
789 pub current_round: Round,
791 #[debug(skip_if = Option::is_none)]
794 pub leader: Option<AccountOwner>,
795 #[debug(skip_if = Option::is_none)]
797 pub round_timeout: Option<Timestamp>,
798}
799
800impl<C> From<&ChainManager<C>> for ChainManagerInfo
801where
802 C: Context + Clone + 'static,
803{
804 fn from(manager: &ChainManager<C>) -> Self {
805 let current_round = manager.current_round();
806 let pending = match (manager.confirmed_vote.get(), manager.validated_vote.get()) {
807 (None, None) => None,
808 (Some(confirmed_vote), Some(validated_vote))
809 if validated_vote.round > confirmed_vote.round =>
810 {
811 Some(validated_vote.lite())
812 }
813 (Some(vote), _) => Some(vote.lite()),
814 (None, Some(vote)) => Some(vote.lite()),
815 };
816 ChainManagerInfo {
817 ownership: manager.ownership.get().clone(),
818 seed: *manager.seed.get(),
819 requested_signed_proposal: None,
820 requested_proposed: None,
821 requested_locking: None,
822 timeout: manager.timeout.get().clone().map(Box::new),
823 pending,
824 timeout_vote: manager.timeout_vote.get().as_ref().map(Vote::lite),
825 fallback_vote: manager.fallback_vote.get().as_ref().map(Vote::lite),
826 requested_confirmed: None,
827 requested_validated: None,
828 current_round,
829 leader: manager.round_leader(current_round).copied(),
830 round_timeout: *manager.round_timeout.get(),
831 }
832 }
833}
834
835impl ChainManagerInfo {
836 pub fn add_values<C>(&mut self, manager: &ChainManager<C>)
838 where
839 C: Context + Clone + 'static,
840 C::Extra: ExecutionRuntimeContext,
841 {
842 self.requested_signed_proposal = manager.signed_proposal.get().clone().map(Box::new);
843 self.requested_proposed = manager.proposed.get().clone().map(Box::new);
844 self.requested_locking = manager.locking_block.get().clone().map(Box::new);
845 self.requested_confirmed = manager
846 .confirmed_vote
847 .get()
848 .as_ref()
849 .map(|vote| Box::new(vote.value.clone()));
850 self.requested_validated = manager
851 .validated_vote
852 .get()
853 .as_ref()
854 .map(|vote| Box::new(vote.value.clone()));
855 }
856
857 pub fn should_propose(
862 &self,
863 identity: &AccountOwner,
864 round: Round,
865 seed: u64,
866 current_committee: &BTreeMap<AccountOwner, u64>,
867 ) -> bool {
868 match round {
869 Round::Fast => self.ownership.super_owners.contains(identity),
870 Round::MultiLeader(_) => self.ownership.can_propose_in_multi_leader_round(identity),
871 Round::SingleLeader(_) | Round::Validator(_) => {
872 let distribution = calculate_distribution(self.ownership.owners.iter());
873 let fallback_distribution = calculate_distribution(current_committee.iter());
874 let leader = compute_round_leader(
875 round,
876 seed,
877 self.ownership.first_leader.as_ref(),
878 &self.ownership.owners,
879 distribution.as_ref(),
880 current_committee,
881 fallback_distribution.as_ref(),
882 );
883 leader == Some(identity)
884 }
885 }
886 }
887
888 pub fn already_handled_proposal(&self, round: Round, proposed_block: &ProposedBlock) -> bool {
890 self.requested_proposed.as_ref().is_some_and(|proposal| {
891 proposal.content.round == round && *proposed_block == proposal.content.block
892 })
893 }
894
895 pub fn has_locking_block_in_current_round(&self) -> bool {
897 self.requested_locking
898 .as_ref()
899 .is_some_and(|locking| locking.round() == self.current_round)
900 }
901}
902
903fn calculate_distribution<'a, T: 'a>(
905 weights: impl IntoIterator<Item = (&'a T, &'a u64)>,
906) -> Option<WeightedAliasIndex<u64>> {
907 let weights: Vec<_> = weights.into_iter().map(|(_, weight)| *weight).collect();
908 if weights.is_empty() {
909 None
910 } else {
911 Some(WeightedAliasIndex::new(weights).ok()?)
912 }
913}
914
915fn compute_round_leader<'a>(
918 round: Round,
919 seed: u64,
920 first_leader: Option<&'a AccountOwner>,
921 owners: &'a BTreeMap<AccountOwner, u64>,
922 distribution: Option<&WeightedAliasIndex<u64>>,
923 fallback_owners: &'a BTreeMap<AccountOwner, u64>,
924 fallback_distribution: Option<&WeightedAliasIndex<u64>>,
925) -> Option<&'a AccountOwner> {
926 match round {
927 Round::SingleLeader(r) => {
928 if r == 0 {
929 if let Some(first_leader) = first_leader {
930 return Some(first_leader);
931 }
932 }
933 let index = round_leader_index(r, seed, distribution)?;
934 owners.keys().nth(index)
935 }
936 Round::Validator(r) => {
937 let index = round_leader_index(r, seed, fallback_distribution)?;
938 fallback_owners.keys().nth(index)
939 }
940 Round::Fast | Round::MultiLeader(_) => None,
941 }
942}
943
944fn round_leader_index(
946 round: u32,
947 seed: u64,
948 distribution: Option<&WeightedAliasIndex<u64>>,
949) -> Option<usize> {
950 let seed = u64::from(round).rotate_left(32).wrapping_add(seed);
951 let mut rng = ChaCha8Rng::seed_from_u64(seed);
952 Some(distribution?.sample(&mut rng))
953}