linera_chain/manager.rs
1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! # Chain manager
5//!
6//! This module contains the consensus mechanism for all microchains. Whenever a block is
7//! confirmed, a new chain manager is created for the next block height. It manages the consensus
8//! state until a new block is confirmed. As long as less than a third of the validators are faulty,
9//! it guarantees that at most one `ConfirmedBlock` certificate will be created for this height.
10//!
11//! The protocol proceeds in rounds, until it reaches a round where a block gets confirmed.
12//!
13//! There are four kinds of rounds:
14//!
15//! * In `Round::Fast`, only super owners can propose blocks, and validators vote to confirm a
16//! block immediately. Super owners must be careful to make only one block proposal, or else they
17//! can permanently block the microchain. If there are no super owners, `Round::Fast` is skipped.
18//! * In cooperative mode (`Round::MultiLeader`), all chain owners can propose blocks at any time.
19//! The protocol is guaranteed to eventually confirm a block as long as no chain owner
20//! continuously actively prevents progress.
21//! * In leader rotation mode (`Round::SingleLeader`), chain owners take turns at proposing blocks.
22//! It can make progress as long as at least one owner is honest, even if other owners try to
23//! prevent it.
24//! * In fallback/public mode (`Round::Validator`), validators take turns at proposing blocks.
25//! It can always make progress under the standard assumption that there is a quorum of honest
26//! validators.
27//!
28//! ## Safety, i.e. at most one block will be confirmed
29//!
30//! In all modes this is guaranteed as follows:
31//!
32//! * Validators (honest ones) never cast a vote if they have already cast any vote in a later
33//! round.
34//! * Validators never vote for a `ValidatedBlock` **A** in round **r** if they have voted for a
35//! _different_ `ConfirmedBlock` **B** in an earlier round **s** ≤ **r**, unless there is a
36//! `ValidatedBlock` certificate (with a quorum of validator signatures) for **A** in some round
37//! between **s** and **r** included in the block proposal.
38//! * Validators only vote for a `ConfirmedBlock` if there is a `ValidatedBlock` certificate for the
39//! same block in the same round.
40//!
41//! This guarantees that once a quorum votes for some `ConfirmedBlock`, there can never be a
42//! `ValidatedBlock` certificate (and thus also no `ConfirmedBlock` certificate) for a different
43//! block in a later round. So if there are two different `ConfirmedBlock` certificates, they may
44//! be from different rounds, but they are guaranteed to contain the same block.
45//!
46//! ## Liveness, i.e. some block will eventually be confirmed
47//!
48//! In `Round::Fast`, liveness depends on the super owners coordinating, and proposing at most one
49//! block.
50//!
51//! If they propose none, and there are other owners, `Round::Fast` will eventually time out.
52//!
53//! In cooperative mode, if there is contention, the owners need to agree on a single owner as the
54//! next proposer. That owner should then download all highest-round certificates and block
55//! proposals known to the honest validators. They can then make a proposal in a round higher than
56//! all previous proposals. If there is any `ValidatedBlock` certificate they must include the
57//! highest one in their proposal, and propose that block. Otherwise they can propose a new block.
58//! Now all honest validators are allowed to vote for that proposal, and eventually confirm it.
59//!
60//! If the owners fail to cooperate, any honest owner can initiate the last multi-leader round by
61//! making a proposal there, then wait for it to time out, which starts the leader-based mode:
62//!
63//! In leader-based and fallback/public mode, an honest participant should subscribe to
64//! notifications from all validators, and follow the chain. Whenever another leader's round takes
65//! too long, they should request timeout votes from the validators to make the next round begin.
66//! Once the honest participant becomes the round leader, they should update all validators, so
67//! that they all agree on the current round. Then they download the highest `ValidatedBlock`
68//! certificate known to any honest validator and include that in their block proposal, just like
69//! in the cooperative case.
70
71use std::collections::BTreeMap;
72
73use linera_base::{
74 crypto::{KeyPair, PublicKey},
75 data_types::{ArithmeticError, Blob, BlockHeight, Round, Timestamp},
76 doc_scalar, ensure,
77 identifiers::{BlobId, ChainId, Owner},
78 ownership::ChainOwnership,
79};
80use linera_execution::committee::Epoch;
81use rand_chacha::{rand_core::SeedableRng, ChaCha8Rng};
82use rand_distr::{Distribution, WeightedAliasIndex};
83use serde::{Deserialize, Serialize};
84use tracing::error;
85
86use crate::{
87 data_types::{
88 Block, BlockExecutionOutcome, BlockProposal, Certificate, CertificateValue,
89 HashedCertificateValue, LiteVote, ProposalContent, Vote,
90 },
91 ChainError,
92};
93
94/// The result of verifying a (valid) query.
95#[derive(Eq, PartialEq)]
96pub enum Outcome {
97 Accept,
98 Skip,
99}
100
101/// The state of the certification process for a chain's next block.
102#[derive(Default, Clone, Debug, Serialize, Deserialize)]
103pub struct ChainManager {
104 /// The public keys, weights and types of the chain's owners.
105 pub ownership: ChainOwnership,
106 /// The seed for the pseudo-random number generator that determines the round leaders.
107 pub seed: u64,
108 /// The probability distribution for choosing a round leader.
109 pub distribution: Option<WeightedAliasIndex<u64>>,
110 /// The probability distribution for choosing a fallback round leader.
111 pub fallback_distribution: Option<WeightedAliasIndex<u64>>,
112 /// Highest-round authenticated block that we have received and checked. If there are multiple
113 /// proposals in the same round, this contains only the first one.
114 pub proposed: Option<BlockProposal>,
115 /// Latest validated proposal that we have voted to confirm (or would have, if we are not a
116 /// validator).
117 pub locked: Option<Certificate>,
118 /// Latest leader timeout certificate we have received.
119 pub timeout: Option<Certificate>,
120 /// Latest vote we have cast, to validate or confirm.
121 pub pending: Option<Vote>,
122 /// Latest timeout vote we cast.
123 pub timeout_vote: Option<Vote>,
124 /// Fallback vote we cast.
125 pub fallback_vote: Option<Vote>,
126 /// The time after which we are ready to sign a timeout certificate for the current round.
127 pub round_timeout: Option<Timestamp>,
128 /// The lowest round where we can still vote to validate or confirm a block. This is
129 /// the round to which the timeout applies.
130 ///
131 /// Having a leader timeout certificate in any given round causes the next one to become
132 /// current. Seeing a validated block certificate or a valid proposal in any round causes that
133 /// round to become current, unless a higher one already is.
134 pub current_round: Round,
135 /// The owners that take over in fallback mode.
136 pub fallback_owners: BTreeMap<Owner, (PublicKey, u64)>,
137 /// These are blobs belonging to proposed or validated blocks that have not been confirmed yet.
138 pub pending_blobs: BTreeMap<BlobId, Blob>,
139}
140
141doc_scalar!(
142 ChainManager,
143 "The state of the certification process for a chain's next block"
144);
145
146impl ChainManager {
147 /// Replaces `self` with a new chain manager.
148 pub fn reset<'a>(
149 &mut self,
150 ownership: &ChainOwnership,
151 height: BlockHeight,
152 local_time: Timestamp,
153 fallback_owners: impl Iterator<Item = (PublicKey, u64)> + 'a,
154 ) -> Result<(), ChainError> {
155 *self = ChainManager::new(ownership.clone(), height.0, local_time, fallback_owners)?;
156 Ok(())
157 }
158
159 /// Creates a new `ChainManager`, and starts the first round.
160 fn new<'a>(
161 ownership: ChainOwnership,
162 seed: u64,
163 local_time: Timestamp,
164 fallback_owners: impl Iterator<Item = (PublicKey, u64)> + 'a,
165 ) -> Result<Self, ChainError> {
166 let distribution = if !ownership.owners.is_empty() {
167 let weights = ownership
168 .owners
169 .values()
170 .map(|(_, weight)| *weight)
171 .collect();
172 Some(WeightedAliasIndex::new(weights)?)
173 } else {
174 None
175 };
176 let fallback_owners = fallback_owners
177 .map(|(pub_key, weight)| (Owner::from(pub_key), (pub_key, weight)))
178 .collect::<BTreeMap<_, _>>();
179 let fallback_distribution = if !fallback_owners.is_empty() {
180 let weights = fallback_owners
181 .values()
182 .map(|(_, weight)| *weight)
183 .collect();
184 Some(WeightedAliasIndex::new(weights)?)
185 } else {
186 None
187 };
188
189 let current_round = ownership.first_round();
190 let round_duration = ownership.round_timeout(current_round);
191 let round_timeout = round_duration.map(|rd| local_time.saturating_add(rd));
192
193 Ok(ChainManager {
194 ownership,
195 seed,
196 distribution,
197 fallback_distribution,
198 proposed: None,
199 locked: None,
200 timeout: None,
201 pending: None,
202 timeout_vote: None,
203 fallback_vote: None,
204 round_timeout,
205 current_round,
206 fallback_owners,
207 pending_blobs: BTreeMap::new(),
208 })
209 }
210
211 /// Returns the most recent vote we cast.
212 pub fn pending(&self) -> Option<&Vote> {
213 self.pending.as_ref()
214 }
215
216 /// Verifies the safety of a proposed block with respect to voting rules.
217 pub fn check_proposed_block(&self, proposal: &BlockProposal) -> Result<Outcome, ChainError> {
218 let new_round = proposal.content.round;
219 let new_block = &proposal.content.block;
220 let owner = &proposal.owner;
221
222 // When a block is certified, incrementing its height must succeed.
223 ensure!(
224 new_block.height < BlockHeight::MAX,
225 ChainError::InvalidBlockHeight
226 );
227 let expected_round = match &proposal.validated_block_certificate {
228 None => self.current_round,
229 Some(cert) => self
230 .ownership
231 .next_round(cert.round)
232 .ok_or_else(|| ChainError::ArithmeticError(ArithmeticError::Overflow))?
233 .max(self.current_round),
234 };
235 // In leader rotation mode, the round must equal the expected one exactly.
236 // Only the first single-leader round can be entered at any time.
237 if self.is_super(owner)
238 || (new_round <= Round::SingleLeader(0) && !expected_round.is_fast())
239 {
240 ensure!(
241 expected_round <= new_round,
242 ChainError::InsufficientRound(expected_round)
243 );
244 } else {
245 ensure!(
246 expected_round == new_round,
247 ChainError::WrongRound(expected_round)
248 );
249 }
250 if let Some(old_proposal) = &self.proposed {
251 if old_proposal.content == proposal.content {
252 return Ok(Outcome::Skip); // We already voted for this proposal; nothing to do.
253 }
254 ensure!(
255 new_round > old_proposal.content.round,
256 // We already accepted a proposal in this round or in a higher round.
257 ChainError::InsufficientRoundStrict(old_proposal.content.round)
258 );
259 // Any proposal in the fast round is considered locked, because validators vote to
260 // confirm it immediately.
261 if old_proposal.content.round.is_fast()
262 && proposal.validated_block_certificate.is_none()
263 {
264 ensure!(
265 old_proposal.content.block == *new_block,
266 ChainError::HasLockedBlock(new_block.height, Round::Fast)
267 )
268 }
269 }
270 // If we have a locked block, the proposal must contain a certificate that validates the
271 // proposed block and is no older than the locked block.
272 if let Some(locked) = &self.locked {
273 ensure!(
274 proposal
275 .validated_block_certificate
276 .as_ref()
277 .is_some_and(|cert| locked.round <= cert.round),
278 ChainError::HasLockedBlock(locked.value().height(), locked.round)
279 )
280 }
281 Ok(Outcome::Accept)
282 }
283
284 /// Checks if the current round has timed out, and signs a `Timeout`.
285 pub fn vote_timeout(
286 &mut self,
287 chain_id: ChainId,
288 height: BlockHeight,
289 epoch: Epoch,
290 key_pair: Option<&KeyPair>,
291 local_time: Timestamp,
292 ) -> bool {
293 let Some(key_pair) = key_pair else {
294 return false; // We are not a validator.
295 };
296 let Some(round_timeout) = self.round_timeout else {
297 return false; // The current round does not time out.
298 };
299 if local_time < round_timeout || self.ownership.owners.is_empty() {
300 return false; // Round has not timed out yet, or there are no regular owners.
301 }
302 let current_round = self.current_round;
303 if let Some(vote) = &self.timeout_vote {
304 if vote.round == current_round {
305 return false; // We already signed this timeout.
306 }
307 }
308 let value = HashedCertificateValue::new_timeout(chain_id, height, epoch);
309 self.timeout_vote = Some(Vote::new(value, current_round, key_pair));
310 true
311 }
312
313 /// Signs a `Timeout` certificate to switch to fallback mode.
314 ///
315 /// This must only be called after verifying that the condition for fallback mode is
316 /// satisfied locally.
317 pub fn vote_fallback(
318 &mut self,
319 chain_id: ChainId,
320 height: BlockHeight,
321 epoch: Epoch,
322 key_pair: Option<&KeyPair>,
323 ) -> bool {
324 let Some(key_pair) = key_pair else {
325 return false; // We are not a validator.
326 };
327 if self.fallback_vote.is_some() || self.current_round >= Round::Validator(0) {
328 return false; // We already signed this or are already in fallback mode.
329 }
330 let value = HashedCertificateValue::new_timeout(chain_id, height, epoch);
331 let last_regular_round = Round::SingleLeader(u32::MAX);
332 self.fallback_vote = Some(Vote::new(value, last_regular_round, key_pair));
333 true
334 }
335
336 /// Verifies that we can vote to confirm a validated block.
337 pub fn check_validated_block(&self, certificate: &Certificate) -> Result<Outcome, ChainError> {
338 let new_block = certificate.value().block();
339 let new_round = certificate.round;
340 if let Some(Vote { value, round, .. }) = &self.pending {
341 match value.inner() {
342 CertificateValue::ConfirmedBlock { executed_block } => {
343 if Some(&executed_block.block) == new_block && *round == new_round {
344 return Ok(Outcome::Skip); // We already voted to confirm this block.
345 }
346 }
347 CertificateValue::ValidatedBlock { .. } => {
348 ensure!(new_round >= *round, ChainError::InsufficientRound(*round))
349 }
350 CertificateValue::Timeout { .. } => {
351 // Unreachable: We only put validated or confirmed blocks in pending.
352 return Err(ChainError::InternalError(
353 "pending can only be validated or confirmed block".to_string(),
354 ));
355 }
356 }
357 }
358 // We don't compare to `current_round` here: Non-validators must update their locked block
359 // even if it is older than the current round. Validators will only sign in the current
360 // round, though. (See `create_final_vote` below.)
361 if let Some(locked) = &self.locked {
362 if locked.hash() == certificate.hash() && locked.round == certificate.round {
363 return Ok(Outcome::Skip);
364 }
365 ensure!(
366 new_round > locked.round,
367 ChainError::InsufficientRoundStrict(locked.round)
368 );
369 }
370 Ok(Outcome::Accept)
371 }
372
373 /// Signs a vote to validate the proposed block.
374 pub fn create_vote(
375 &mut self,
376 proposal: BlockProposal,
377 outcome: BlockExecutionOutcome,
378 key_pair: Option<&KeyPair>,
379 local_time: Timestamp,
380 ) {
381 // Record the proposed block, so it can be supplied to clients that request it.
382 self.proposed = Some(proposal.clone());
383 self.update_current_round(local_time);
384 let ProposalContent { block, round, .. } = proposal.content;
385 let executed_block = outcome.with(block);
386
387 // If the validated block certificate is more recent, update our locked block.
388 if let Some(lite_cert) = proposal.validated_block_certificate {
389 if self
390 .locked
391 .as_ref()
392 .map_or(true, |locked| locked.round < lite_cert.round)
393 {
394 let value = HashedCertificateValue::new_validated(executed_block.clone());
395 if let Some(certificate) = lite_cert.with_value(value) {
396 self.locked = Some(certificate);
397 }
398 }
399 }
400
401 for blob in proposal.blobs {
402 self.pending_blobs.insert(blob.id(), blob);
403 }
404
405 if let Some(key_pair) = key_pair {
406 // If this is a fast block, vote to confirm. Otherwise vote to validate.
407 let value = if round.is_fast() {
408 HashedCertificateValue::new_confirmed(executed_block)
409 } else {
410 HashedCertificateValue::new_validated(executed_block)
411 };
412 self.pending = Some(Vote::new(value, round, key_pair));
413 }
414 }
415
416 /// Signs a vote to confirm the validated block.
417 pub fn create_final_vote(
418 &mut self,
419 certificate: Certificate,
420 key_pair: Option<&KeyPair>,
421 local_time: Timestamp,
422 ) {
423 let round = certificate.round;
424 // Validators only change their locked block if the new one is included in a proposal in the
425 // current round, or it is itself in the current round.
426 if key_pair.is_some() && round < self.current_round {
427 return;
428 }
429 let Some(value) = certificate.value.validated_to_confirmed() else {
430 // Unreachable: This is only called with validated blocks.
431 error!("Unexpected certificate; expected ValidatedBlock");
432 return;
433 };
434 self.locked = Some(certificate);
435 self.update_current_round(local_time);
436 if let Some(key_pair) = key_pair {
437 // Vote to confirm.
438 let vote = Vote::new(value, round, key_pair);
439 // Ok to overwrite validation votes with confirmation votes at equal or higher round.
440 self.pending = Some(vote);
441 }
442 }
443
444 /// Updates `current_round` and `round_timeout` if necessary.
445 ///
446 /// This must be after every change to `timeout`, `locked` or `proposed`.
447 fn update_current_round(&mut self, local_time: Timestamp) {
448 let current_round = self
449 .timeout
450 .iter()
451 .map(|certificate| {
452 self.ownership
453 .next_round(certificate.round)
454 .unwrap_or(Round::Validator(u32::MAX))
455 })
456 .chain(self.locked.iter().map(|certificate| certificate.round))
457 .chain(self.proposed.iter().map(|proposal| proposal.content.round))
458 .max()
459 .unwrap_or_default()
460 .max(self.ownership.first_round());
461 if current_round <= self.current_round {
462 return;
463 }
464 let round_duration = self.ownership.round_timeout(current_round);
465 self.round_timeout = round_duration.map(|rd| local_time.saturating_add(rd));
466 self.current_round = current_round;
467 }
468
469 /// Updates the round number and timer if the timeout certificate is from a higher round than
470 /// any known certificate.
471 pub fn handle_timeout_certificate(&mut self, certificate: Certificate, local_time: Timestamp) {
472 if !certificate.value().is_timeout() {
473 // Unreachable: This is only called with timeout certificates.
474 error!("Unexpected certificate; expected leader timeout");
475 return;
476 }
477 let round = certificate.round;
478 if let Some(known_certificate) = &self.timeout {
479 if known_certificate.round >= round {
480 return;
481 }
482 }
483 self.timeout = Some(certificate);
484 self.update_current_round(local_time);
485 }
486
487 /// Returns the public key of the block proposal's signer, if they are a valid owner and allowed
488 /// to propose a block in the proposal's round.
489 pub fn verify_owner(&self, proposal: &BlockProposal) -> Option<PublicKey> {
490 if let Some(public_key) = self.ownership.super_owners.get(&proposal.owner) {
491 return Some(*public_key);
492 }
493 match proposal.content.round {
494 Round::Fast => {
495 None // Only super owners can propose in the first round.
496 }
497 Round::MultiLeader(_) => {
498 // Not in leader rotation mode; any owner is allowed to propose.
499 self.ownership
500 .owners
501 .get(&proposal.owner)
502 .map(|(public_key, _)| *public_key)
503 }
504 Round::SingleLeader(r) => {
505 let index = self.round_leader_index(r)?;
506 let (leader, (public_key, _)) = self.ownership.owners.iter().nth(index)?;
507 (*leader == proposal.owner).then_some(*public_key)
508 }
509 Round::Validator(r) => {
510 let index = self.fallback_round_leader_index(r)?;
511 let (leader, (public_key, _)) = self.fallback_owners.iter().nth(index)?;
512 (*leader == proposal.owner).then_some(*public_key)
513 }
514 }
515 }
516
517 /// Returns the leader who is allowed to propose a block in the given round, or `None` if every
518 /// owner is allowed to propose. Exception: In `Round::Fast`, only super owners can propose.
519 fn round_leader(&self, round: Round) -> Option<&Owner> {
520 match round {
521 Round::SingleLeader(r) => {
522 let index = self.round_leader_index(r)?;
523 self.ownership.owners.keys().nth(index)
524 }
525 Round::Validator(r) => {
526 let index = self.fallback_round_leader_index(r)?;
527 self.fallback_owners.keys().nth(index)
528 }
529 Round::Fast | Round::MultiLeader(_) => None,
530 }
531 }
532
533 /// Returns the index of the leader who is allowed to propose a block in the given round.
534 fn round_leader_index(&self, round: u32) -> Option<usize> {
535 let seed = u64::from(round).rotate_left(32).wrapping_add(self.seed);
536 let mut rng = ChaCha8Rng::seed_from_u64(seed);
537 Some(self.distribution.as_ref()?.sample(&mut rng))
538 }
539
540 /// Returns the index of the fallback leader who is allowed to propose a block in the given
541 /// round.
542 fn fallback_round_leader_index(&self, round: u32) -> Option<usize> {
543 let seed = u64::from(round).rotate_left(32).wrapping_add(self.seed);
544 let mut rng = ChaCha8Rng::seed_from_u64(seed);
545 Some(self.fallback_distribution.as_ref()?.sample(&mut rng))
546 }
547
548 /// Returns whether the owner is a super owner.
549 fn is_super(&self, owner: &Owner) -> bool {
550 self.ownership.super_owners.contains_key(owner)
551 }
552}
553
554/// Chain manager information that is included in `ChainInfo` sent to clients.
555#[derive(Default, Clone, Debug, Serialize, Deserialize)]
556#[cfg_attr(with_testing, derive(Eq, PartialEq))]
557pub struct ChainManagerInfo {
558 /// The configuration of the chain's owners.
559 pub ownership: ChainOwnership,
560 /// Latest authenticated block that we have received, if requested.
561 pub requested_proposed: Option<Box<BlockProposal>>,
562 /// Latest validated proposal that we have voted to confirm (or would have, if we are not a
563 /// validator).
564 pub requested_locked: Option<Box<Certificate>>,
565 /// Latest timeout certificate we have seen.
566 pub timeout: Option<Box<Certificate>>,
567 /// Latest vote we cast (either to validate or to confirm a block).
568 pub pending: Option<LiteVote>,
569 /// Latest timeout vote we cast.
570 pub timeout_vote: Option<LiteVote>,
571 /// Fallback vote we cast.
572 pub fallback_vote: Option<LiteVote>,
573 /// The value we voted for, if requested.
574 pub requested_pending_value: Option<Box<HashedCertificateValue>>,
575 /// The current round, i.e. the lowest round where we can still vote to validate a block.
576 pub current_round: Round,
577 /// The current leader, who is allowed to propose the next block.
578 /// `None` if everyone is allowed to propose.
579 pub leader: Option<Owner>,
580 /// The timestamp when the current round times out.
581 pub round_timeout: Option<Timestamp>,
582 /// These are blobs belonging to proposed or validated blocks that have not been confirmed yet.
583 pub pending_blobs: BTreeMap<BlobId, Blob>,
584}
585
586impl From<&ChainManager> for ChainManagerInfo {
587 fn from(manager: &ChainManager) -> Self {
588 let current_round = manager.current_round;
589 ChainManagerInfo {
590 ownership: manager.ownership.clone(),
591 requested_proposed: None,
592 requested_locked: None,
593 timeout: manager.timeout.clone().map(Box::new),
594 pending: manager.pending.as_ref().map(|vote| vote.lite()),
595 timeout_vote: manager.timeout_vote.as_ref().map(Vote::lite),
596 fallback_vote: manager.fallback_vote.as_ref().map(Vote::lite),
597 requested_pending_value: None,
598 current_round,
599 leader: manager.round_leader(current_round).cloned(),
600 round_timeout: manager.round_timeout,
601 pending_blobs: BTreeMap::new(),
602 }
603 }
604}
605
606impl ChainManagerInfo {
607 /// Adds requested certificate values and proposals to the `ChainManagerInfo`.
608 pub fn add_values(&mut self, manager: &ChainManager) {
609 self.requested_proposed = manager.proposed.clone().map(Box::new);
610 self.requested_locked = manager.locked.clone().map(Box::new);
611 self.requested_pending_value = manager
612 .pending
613 .as_ref()
614 .map(|vote| Box::new(vote.value.clone()));
615 self.pending_blobs = manager.pending_blobs.clone();
616 }
617
618 /// Gets the highest validated block.
619 pub fn highest_validated_block(&self) -> Option<&Block> {
620 if let Some(certificate) = &self.requested_locked {
621 let block = certificate.value().block();
622 if block.is_some() {
623 return block;
624 }
625 }
626
627 if let Some(proposal) = &self.requested_proposed {
628 if proposal.content.round.is_fast() {
629 return Some(&proposal.content.block);
630 }
631 }
632
633 None
634 }
635
636 /// Returns whether the `identity` is allowed to propose a block in `round`.
637 /// This is dependant on the type of round and whether `identity` is a validator or (super)owner.
638 pub fn can_propose(&self, identity: &Owner, round: Round) -> bool {
639 match round {
640 Round::Fast => self.ownership.super_owners.contains_key(identity),
641 Round::MultiLeader(_) => true,
642 Round::SingleLeader(_) | Round::Validator(_) => self.leader.as_ref() == Some(identity),
643 }
644 }
645}