Skip to main content

consensus_core/
core.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    collections::{BTreeMap, BTreeSet},
6    sync::Arc,
7    vec,
8};
9
10use consensus_config::ProtocolKeyPair;
11#[cfg(test)]
12use consensus_config::{AuthorityIndex, Stake, local_committee_and_keys};
13use consensus_types::block::{BlockRef, Round};
14use itertools::Itertools as _;
15#[cfg(test)]
16use mysten_metrics::monitored_mpsc::UnboundedReceiver;
17use mysten_metrics::monitored_scope;
18use parking_lot::RwLock;
19use sui_macros::fail_point;
20use tokio::sync::{broadcast, watch};
21use tracing::{debug, info, trace, warn};
22
23#[cfg(test)]
24use crate::{
25    CommitConsumerArgs, TransactionClient,
26    block_verifier::NoopBlockVerifier,
27    storage::{Store, WriteBatch, mem_store::MemStore},
28    transaction::{TransactionConsumer, TransactionConsumerPool},
29};
30use crate::{
31    ancestor::AncestorStateManager,
32    block::{BlockAPI, ExtendedBlock, GENESIS_ROUND, Slot, VerifiedBlock},
33    block_manager::BlockManager,
34    commit::{
35        CertifiedCommit, CertifiedCommits, CommitAPI, CommittedSubDag, DecidedLeader, Decision,
36    },
37    commit_observer::CommitObserver,
38    context::Context,
39    dag_state::DagState,
40    error::{ConsensusError, ConsensusResult},
41    leader_schedule::LeaderSchedule,
42    leader_schedule_v3::LeaderScheduleV3,
43    leader_scoring::ReputationScores,
44    proposer::{ProposalLeaderWaiter, Proposer, ValidatorProposer},
45    round_tracker::RoundTracker,
46    transaction::TransactionPool,
47    transaction_vote_tracker::TransactionVoteTracker,
48    universal_committer::{
49        UniversalCommitter, universal_committer_builder::UniversalCommitterBuilder,
50    },
51};
52
53pub(crate) struct Core {
54    context: Arc<Context>,
55    /// The block manager which is responsible for keeping track of the DAG dependencies when processing new blocks
56    /// and accept them or suspend if we are missing their causal history
57    block_manager: BlockManager,
58    /// Used to make commit decisions for leader blocks in the dag.
59    committer: Arc<UniversalCommitter>,
60    /// The last new round for which core has sent out a signal.
61    last_signaled_round: Round,
62    /// The last decided leader returned from the universal committer. Important to note
63    /// that this does not signify that the leader has been persisted yet as it still has
64    /// to go through CommitObserver and persist the commit in store. On recovery/restart
65    /// the last_decided_leader will be set to the last_commit leader in dag state.
66    last_decided_leader: Slot,
67    /// The consensus leader schedule to be used to resolve the leader for a
68    /// given round.
69    leader_schedule: Arc<LeaderSchedule>,
70    /// Scores validators using the DAG and schedules leader for next commit, in a sliding-window.
71    leader_schedule_v3: Option<LeaderScheduleV3>,
72    /// The commit observer is responsible for observing the commits and collecting
73    /// + sending subdags over the consensus output channel.
74    commit_observer: CommitObserver,
75    /// Sender of outgoing signals from Core.
76    signals: CoreSignals,
77    /// Keeping track of state of the DAG, including blocks, commits and last committed rounds.
78    dag_state: Arc<RwLock<DagState>>,
79    /// Block proposal engine for Validator nodes only.
80    /// Validators have a proposer to create blocks, Observers have None (they only receive blocks).
81    proposer: Option<Box<dyn Proposer>>,
82}
83
84impl Core {
85    /// Creates a new Core instance for a validator node that participates in consensus.
86    pub(crate) fn new_validator(
87        context: Arc<Context>,
88        leader_schedule: Arc<LeaderSchedule>,
89        transaction_pool: Arc<dyn TransactionPool>,
90        transaction_vote_tracker: TransactionVoteTracker,
91        block_manager: BlockManager,
92        commit_observer: CommitObserver,
93        signals: CoreSignals,
94        block_signer: ProtocolKeyPair,
95        dag_state: Arc<RwLock<DagState>>,
96        sync_last_known_own_block: bool,
97        round_tracker: Arc<RwLock<RoundTracker>>,
98    ) -> Self {
99        let last_decided_leader = dag_state.read().last_commit_leader();
100        let number_of_leaders = context.protocol_config.num_leaders_per_round().unwrap_or(1);
101
102        let leader_schedule_v3 = if context.protocol_config.enable_v3() {
103            Some(LeaderScheduleV3::from_store(
104                context.clone(),
105                dag_state.clone(),
106            ))
107        } else {
108            None
109        };
110
111        let committer = Arc::new(
112            UniversalCommitterBuilder::new(
113                context.clone(),
114                leader_schedule.clone(),
115                dag_state.clone(),
116            )
117            .with_number_of_leaders(number_of_leaders)
118            .with_pipeline(true)
119            .build(),
120        );
121
122        let last_proposed_block = dag_state
123            .read()
124            .get_last_proposed_block()
125            .expect("A block should have been returned");
126        let last_signaled_round = last_proposed_block.round();
127
128        // Recover the last included ancestor rounds based on the last proposed block. That will allow
129        // to perform the next block proposal by using ancestor blocks of higher rounds and avoid
130        // re-including blocks that have been already included in the last (or earlier) block proposal.
131        // This is only strongly guaranteed for a quorum of ancestors. It is still possible to re-include
132        // a block from an authority which hadn't been added as part of the last proposal hence its
133        // latest included ancestor is not accurately captured here. This is considered a small deficiency,
134        // and it mostly matters just for this next proposal without any actual penalties in performance
135        // or block proposal.
136        let mut last_included_ancestors = vec![None; context.committee.size()];
137        for ancestor in last_proposed_block.ancestors() {
138            last_included_ancestors[ancestor.author] = Some(*ancestor);
139        }
140
141        let last_known_proposed_round = if sync_last_known_own_block {
142            None
143        } else {
144            // if the sync is disabled then we practically don't want to impose any restriction.
145            Some(0)
146        };
147
148        let ancestor_state_manager = AncestorStateManager::new(context.clone(), dag_state.clone());
149
150        // Create the ValidatorProposer.
151        let leader_waiter = if let Some(schedule) = leader_schedule_v3.as_ref() {
152            let next_commit_leader_schedule = schedule.next_commit_leader_schedule();
153            info!(
154                "Recovered next commit leaders: index={} min_round={} num={} allowed={:?}",
155                next_commit_leader_schedule.next_commit_index,
156                next_commit_leader_schedule.min_next_leader_round,
157                next_commit_leader_schedule.num_leaders(),
158                next_commit_leader_schedule.allowed_leaders,
159            );
160            ProposalLeaderWaiter::V3(next_commit_leader_schedule)
161        } else {
162            ProposalLeaderWaiter::V2(committer.clone())
163        };
164        let proposer = Some(Box::new(ValidatorProposer::new(
165            dag_state.clone(),
166            context.clone(),
167            transaction_pool,
168            transaction_vote_tracker.clone(),
169            block_signer,
170            last_known_proposed_round,
171            ancestor_state_manager,
172            round_tracker.clone(),
173            leader_waiter,
174        )) as Box<dyn Proposer>);
175
176        let mut core = Self {
177            context,
178            last_signaled_round,
179            last_decided_leader,
180            leader_schedule,
181            leader_schedule_v3,
182            block_manager,
183            committer,
184            commit_observer,
185            signals,
186            dag_state,
187            proposer,
188        };
189
190        // Initialize propagation scores for the proposer before recovery.
191        let propagation_scores = core.current_reputation_scores();
192        core.proposer
193            .as_mut()
194            .unwrap()
195            .set_propagation_scores(propagation_scores);
196
197        core.recover_validator()
198    }
199
200    /// Creates a new Core instance for an observer node that only processes blocks.
201    pub(crate) fn new_observer(
202        context: Arc<Context>,
203        leader_schedule: Arc<LeaderSchedule>,
204        block_manager: BlockManager,
205        commit_observer: CommitObserver,
206        signals: CoreSignals,
207        dag_state: Arc<RwLock<DagState>>,
208    ) -> Self {
209        let last_decided_leader = dag_state.read().last_commit_leader();
210        let number_of_leaders = context.protocol_config.num_leaders_per_round().unwrap_or(1);
211
212        let leader_schedule_v3 = if context.protocol_config.enable_v3() {
213            Some(LeaderScheduleV3::from_store(
214                context.clone(),
215                dag_state.clone(),
216            ))
217        } else {
218            None
219        };
220
221        let committer = Arc::new(
222            UniversalCommitterBuilder::new(
223                context.clone(),
224                leader_schedule.clone(),
225                dag_state.clone(),
226            )
227            .with_number_of_leaders(number_of_leaders)
228            .with_pipeline(true)
229            .build(),
230        );
231
232        // For the Observer nodes let's consider the last signaled round as the latest threshold clock round.
233        let last_signaled_round = dag_state.read().threshold_clock_round();
234
235        Self {
236            context,
237            last_signaled_round,
238            last_decided_leader,
239            leader_schedule,
240            leader_schedule_v3,
241            block_manager,
242            committer,
243            commit_observer,
244            signals,
245            dag_state,
246            proposer: None,
247        }
248        .recover_observer()
249    }
250
251    fn recover_validator(mut self) -> Self {
252        let _s = self
253            .context
254            .metrics
255            .node_metrics
256            .scope_processing_time
257            .with_label_values(&["Core::recover_validator"])
258            .start_timer();
259
260        // Try to commit and propose, since they may not have run after the last storage write.
261        self.try_commit(vec![]).unwrap();
262
263        let last_proposed_block = if let Some(last_proposed_block) = self.try_propose(true).unwrap()
264        {
265            last_proposed_block
266        } else {
267            let proposer = self
268                .proposer
269                .as_ref()
270                .expect("Validator must have proposer");
271            let last_proposed_block = proposer.last_proposed_block();
272
273            if proposer.should_propose() {
274                assert!(
275                    last_proposed_block.round() > GENESIS_ROUND,
276                    "At minimum a block of round higher than genesis should have been produced during recovery"
277                );
278            }
279
280            // if no new block proposed then just re-broadcast the last proposed one to ensure liveness.
281            self.signals
282                .new_block(ExtendedBlock {
283                    block: last_proposed_block.clone(),
284                    excluded_ancestors: vec![],
285                })
286                .unwrap();
287            last_proposed_block
288        };
289
290        // Try to set up leader timeout if needed.
291        // This needs to be called after try_commit() and try_propose(), which may
292        // have advanced the threshold clock round.
293        self.try_signal_new_round();
294
295        info!(
296            "Core recovery for validator completed with last proposed block {:?}",
297            last_proposed_block
298        );
299
300        self
301    }
302
303    fn recover_observer(mut self) -> Self {
304        let _s = self
305            .context
306            .metrics
307            .node_metrics
308            .scope_processing_time
309            .with_label_values(&["Core::recover_observer"])
310            .start_timer();
311
312        // Try to commit, since they may not have run after the last storage write.
313        self.try_commit(vec![]).unwrap();
314
315        self.try_signal_new_round();
316
317        self
318    }
319
320    pub(crate) async fn stop(&mut self) {
321        self.commit_observer.stop().await;
322    }
323
324    /// Calls `BlockManager::try_accept_blocks` and broadcasts each accepted block to any active
325    /// observer subscribers. Returns the accepted blocks alongside any missing block refs.
326    fn accept_blocks(
327        &mut self,
328        blocks: Vec<VerifiedBlock>,
329    ) -> (Vec<VerifiedBlock>, BTreeSet<BlockRef>) {
330        let (accepted_blocks, missing_block_refs) = self.block_manager.try_accept_blocks(blocks);
331        for block in &accepted_blocks {
332            tracing::trace!(
333                "{} Core accepted round {}, author {} at timestamp: {}",
334                self.context.own_index,
335                block.round(),
336                block.author(),
337                block.timestamp_ms()
338            );
339            self.signals.new_accepted_block(block.clone());
340        }
341        (accepted_blocks, missing_block_refs)
342    }
343
344    /// Calls `BlockManager::try_accept_committed_blocks` and broadcasts each accepted block to any
345    /// active observer subscribers. Returns all accepted blocks.
346    fn accept_committed_blocks(&mut self, blocks: Vec<VerifiedBlock>) -> Vec<VerifiedBlock> {
347        let accepted_blocks = self.block_manager.try_accept_committed_blocks(blocks);
348        for block in &accepted_blocks {
349            self.signals.new_accepted_block(block.clone());
350        }
351        accepted_blocks
352    }
353
354    /// Processes the provided blocks and accepts them if possible when their causal history exists.
355    /// The method returns:
356    /// - The references of ancestors missing their block
357    #[tracing::instrument(skip_all)]
358    pub(crate) fn add_blocks(
359        &mut self,
360        blocks: Vec<VerifiedBlock>,
361    ) -> ConsensusResult<BTreeSet<BlockRef>> {
362        let _scope = monitored_scope("Core::add_blocks");
363        let _s = self
364            .context
365            .metrics
366            .node_metrics
367            .scope_processing_time
368            .with_label_values(&["Core::add_blocks"])
369            .start_timer();
370        self.context
371            .metrics
372            .node_metrics
373            .core_add_blocks_batch_size
374            .observe(blocks.len() as f64);
375
376        let (accepted_blocks, missing_block_refs) = self.accept_blocks(blocks);
377
378        if !accepted_blocks.is_empty() {
379            trace!(
380                "Accepted blocks: {}",
381                accepted_blocks
382                    .iter()
383                    .map(|b| b.reference().to_string())
384                    .join(",")
385            );
386
387            // Try to commit the new blocks if possible.
388            self.try_commit(vec![])?;
389
390            // Try to propose now since there are new blocks accepted.
391            self.try_propose(false)?;
392
393            // Now set up leader timeout if needed.
394            // This needs to be called after try_commit() and try_propose(), which may
395            // have advanced the threshold clock round.
396            self.try_signal_new_round();
397        };
398
399        if !missing_block_refs.is_empty() {
400            trace!(
401                "Missing block refs: {}",
402                missing_block_refs.iter().map(|b| b.to_string()).join(", ")
403            );
404        }
405
406        Ok(missing_block_refs)
407    }
408
409    // Adds the certified commits that have been synced via the commit syncer. We are using the commit info in order to skip running the decision
410    // rule and immediately commit the corresponding leaders and sub dags. Pay attention that no block acceptance is happening here, but rather
411    // internally in the `try_commit` method which ensures that everytime only the blocks corresponding to the certified commits that are about to
412    // be committed are accepted.
413    #[tracing::instrument(skip_all)]
414    pub(crate) fn add_certified_commits(
415        &mut self,
416        certified_commits: CertifiedCommits,
417    ) -> ConsensusResult<BTreeSet<BlockRef>> {
418        let _scope = monitored_scope("Core::add_certified_commits");
419
420        let votes = certified_commits.votes().to_vec();
421        let commits = self
422            .filter_new_commits(certified_commits.commits().to_vec())
423            .expect("Certified commits validation failed");
424
425        // Try to accept the certified commit votes.
426        // Even if they may not be part of a future commit, these blocks are useful for certifying
427        // commits when helping peers sync commits.
428        let (_, missing_block_refs) = self.accept_blocks(votes);
429
430        // Try to commit the new blocks. Take into account the trusted commit that has been provided.
431        self.try_commit(commits)?;
432
433        // Try to propose now since there are new blocks accepted.
434        self.try_propose(false)?;
435
436        // Now set up leader timeout if needed.
437        // This needs to be called after try_commit() and try_propose(), which may
438        // have advanced the threshold clock round.
439        self.try_signal_new_round();
440
441        Ok(missing_block_refs)
442    }
443
444    /// Checks if provided block refs have been accepted. If not, missing block refs are kept for synchronizations.
445    /// Returns the references of missing blocks among the input blocks.
446    pub(crate) fn check_block_refs(
447        &mut self,
448        block_refs: Vec<BlockRef>,
449    ) -> ConsensusResult<BTreeSet<BlockRef>> {
450        let _scope = monitored_scope("Core::check_block_refs");
451        let _s = self
452            .context
453            .metrics
454            .node_metrics
455            .scope_processing_time
456            .with_label_values(&["Core::check_block_refs"])
457            .start_timer();
458        self.context
459            .metrics
460            .node_metrics
461            .core_check_block_refs_batch_size
462            .observe(block_refs.len() as f64);
463
464        // Try to find them via the block manager
465        let missing_block_refs = self.block_manager.try_find_blocks(block_refs);
466
467        if !missing_block_refs.is_empty() {
468            trace!(
469                "Missing block refs: {}",
470                missing_block_refs.iter().map(|b| b.to_string()).join(", ")
471            );
472        }
473        Ok(missing_block_refs)
474    }
475
476    /// If needed, signals a new clock round and sets up leader timeout.
477    fn try_signal_new_round(&mut self) {
478        // Signal only when the threshold clock round is more advanced than the last signaled round.
479        //
480        // NOTE: a signal is still sent even when a block has been proposed at the new round.
481        // We can consider changing this in the future.
482        let new_clock_round = self.dag_state.read().threshold_clock_round();
483        if new_clock_round <= self.last_signaled_round {
484            return;
485        }
486        // Then send a signal to set up leader timeout.
487        self.signals.new_round(new_clock_round);
488        self.last_signaled_round = new_clock_round;
489
490        // Report the threshold clock round
491        self.context
492            .metrics
493            .node_metrics
494            .threshold_clock_round
495            .set(new_clock_round as i64);
496    }
497
498    /// Creating a new block for the dictated round. This is used when a leader timeout occurs, either
499    /// when the min timeout expires or max. When `force = true` , then any checks like previous round
500    /// leader existence will get skipped.
501    pub(crate) fn new_block(
502        &mut self,
503        round: Round,
504        force: bool,
505    ) -> ConsensusResult<Option<VerifiedBlock>> {
506        let _scope = monitored_scope("Core::new_block");
507        if let Some(last_round) = self.last_proposed_round()
508            && last_round < round
509        {
510            self.context
511                .metrics
512                .node_metrics
513                .leader_timeout_total
514                .with_label_values(&[&format!("{force}")])
515                .inc();
516            let result = self.try_propose(force);
517            // The threshold clock round may have advanced, so a signal needs to be sent.
518            self.try_signal_new_round();
519            return result;
520        }
521        Ok(None)
522    }
523
524    /// Keeps only the certified commits that have a commit index > last commit index.
525    /// It also ensures that the first commit in the list is the next one in line, otherwise it panics.
526    fn filter_new_commits(
527        &mut self,
528        commits: Vec<CertifiedCommit>,
529    ) -> ConsensusResult<Vec<CertifiedCommit>> {
530        // Filter out the commits that have been already locally committed and keep only anything that is above the last committed index.
531        let last_commit_index = self.dag_state.read().last_commit_index();
532        let commits = commits
533            .iter()
534            .filter(|commit| {
535                if commit.index() > last_commit_index {
536                    true
537                } else {
538                    tracing::debug!(
539                        "Skip commit for index {} as it is already committed with last commit index {}",
540                        commit.index(),
541                        last_commit_index
542                    );
543                    false
544                }
545            })
546            .cloned()
547            .collect::<Vec<_>>();
548
549        // Make sure that the first commit we find is the next one in line and there is no gap.
550        if let Some(commit) = commits.first()
551            && commit.index() != last_commit_index + 1
552        {
553            return Err(ConsensusError::UnexpectedCertifiedCommitIndex {
554                expected_commit_index: last_commit_index + 1,
555                commit_index: commit.index(),
556            });
557        }
558
559        Ok(commits)
560    }
561
562    // Attempts to create a new block, persist and propose it to all peers.
563    // When force is true, ignore if leader from the last round exists among ancestors and if
564    // the minimum round delay has passed.
565    fn try_propose(&mut self, force: bool) -> ConsensusResult<Option<VerifiedBlock>> {
566        if let Some(proposer) = &mut self.proposer
567            && let Some(extended_block) = proposer.try_new_block(force)
568        {
569            self.signals.new_block(extended_block.clone())?;
570            self.signals
571                .new_accepted_block(extended_block.block.clone());
572
573            fail_point!("consensus-after-propose");
574
575            // The new block may help commit.
576            self.try_commit(vec![])?;
577            return Ok(Some(extended_block.block));
578        }
579        Ok(None)
580    }
581
582    /// Runs commit rule to attempt to commit additional blocks from the DAG. If any `certified_commits` are provided, then
583    /// it will attempt to commit those first before trying to commit any further leaders.
584    fn try_commit(
585        &mut self,
586        mut certified_commits: Vec<CertifiedCommit>,
587    ) -> ConsensusResult<Vec<CommittedSubDag>> {
588        let _s = self
589            .context
590            .metrics
591            .node_metrics
592            .scope_processing_time
593            .with_label_values(&["Core::try_commit"])
594            .start_timer();
595
596        let mut certified_commits_map = BTreeMap::new();
597        for c in &certified_commits {
598            certified_commits_map.insert(c.index(), c.reference());
599        }
600
601        if !certified_commits.is_empty() {
602            info!(
603                "Processing synced commits: {:?}",
604                certified_commits
605                    .iter()
606                    .map(|c| (c.index(), c.leader()))
607                    .collect::<Vec<_>>()
608            );
609        }
610
611        let mut committed_sub_dags = Vec::new();
612        // TODO: Add optimization to abort early without quorum for a round.
613        loop {
614            // LeaderSchedule has a limit to how many sequenced leaders can be committed
615            // before a change is triggered. Calling into leader schedule will get you
616            // how many commits till next leader change. We will loop back and recalculate
617            // any discarded leaders with the new schedule.
618            let mut commits_until_update = self
619                .leader_schedule
620                .commits_until_leader_schedule_update(self.dag_state.clone());
621
622            if commits_until_update == 0 {
623                let last_commit_index = self.dag_state.read().last_commit_index();
624
625                tracing::info!(
626                    "Leader schedule change triggered at commit index {last_commit_index}"
627                );
628
629                self.leader_schedule
630                    .update_leader_schedule_v2(&self.dag_state);
631
632                let propagation_scores = self.current_reputation_scores();
633                if let Some(proposer) = &mut self.proposer {
634                    proposer.set_propagation_scores(propagation_scores);
635                }
636
637                commits_until_update = self
638                    .leader_schedule
639                    .commits_until_leader_schedule_update(self.dag_state.clone());
640
641                fail_point!("consensus-after-leader-schedule-change");
642            }
643            assert!(commits_until_update > 0);
644
645            // If there are certified commits to process, find out which leaders and commits from them
646            // are decided and use them as the next commits.
647            let (certified_leaders, decided_certified_commits): (
648                Vec<DecidedLeader>,
649                Vec<CertifiedCommit>,
650            ) = self
651                .try_select_certified_leaders(&mut certified_commits, commits_until_update)
652                .into_iter()
653                .unzip();
654
655            // Only accept blocks for the certified commits that we are certain to sequence.
656            // This ensures that only blocks corresponding to committed certified commits are flushed to disk.
657            // Blocks from non-committed certified commits will not be flushed, preventing issues during crash-recovery.
658            // This avoids scenarios where accepting and flushing blocks of non-committed certified commits could lead to
659            // premature commit rule execution. Due to GC, this could cause a panic if the commit rule tries to access
660            // missing causal history from blocks of certified commits.
661            let blocks = decided_certified_commits
662                .iter()
663                .flat_map(|c| c.blocks())
664                .cloned()
665                .collect::<Vec<_>>();
666            self.accept_committed_blocks(blocks);
667
668            // If there is no certified commit to process, run the decision rule.
669            let (decided_leaders, local) = if certified_leaders.is_empty() {
670                // TODO: limit commits by commits_until_update for efficiency, which may be needed when leader schedule length is reduced.
671                let mut decided_leaders = self.committer.try_decide(self.last_decided_leader);
672                // Truncate the decided leaders to fit the commit schedule limit.
673                if decided_leaders.len() >= commits_until_update {
674                    let _ = decided_leaders.split_off(commits_until_update);
675                }
676                (decided_leaders, true)
677            } else {
678                (certified_leaders, false)
679            };
680
681            // If the decided leaders list is empty then just break the loop.
682            let Some(last_decided) = decided_leaders.last().cloned() else {
683                break;
684            };
685
686            self.last_decided_leader = last_decided.slot();
687            self.context
688                .metrics
689                .node_metrics
690                .last_decided_leader_round
691                .set(self.last_decided_leader.round as i64);
692
693            let sequenced_leaders = decided_leaders
694                .into_iter()
695                .filter_map(|leader| leader.into_committed_block())
696                .collect::<Vec<_>>();
697            // It's possible to reach this point as the decided leaders might all of them be "Skip" decisions. In this case there is no
698            // leader to commit and we should break the loop.
699            if sequenced_leaders.is_empty() {
700                break;
701            }
702            tracing::info!(
703                "Committing {} leaders: {}; {} commits before next leader schedule change",
704                sequenced_leaders.len(),
705                sequenced_leaders
706                    .iter()
707                    .map(|b| b.reference().to_string())
708                    .join(","),
709                commits_until_update,
710            );
711
712            // TODO: refcount subdags
713            let subdags = self
714                .commit_observer
715                .handle_commit(sequenced_leaders, local)?;
716
717            // Try to unsuspend blocks if gc_round has advanced.
718            self.block_manager
719                .try_unsuspend_blocks_for_latest_gc_round();
720
721            committed_sub_dags.extend(subdags);
722
723            fail_point!("consensus-after-handle-commit");
724        }
725
726        // Sanity check: for commits that have been linearized using the certified commits, ensure that the same sub dag has been committed.
727        for sub_dag in &committed_sub_dags {
728            if let Some(commit_ref) = certified_commits_map.remove(&sub_dag.commit_ref.index) {
729                assert_eq!(
730                    commit_ref, sub_dag.commit_ref,
731                    "Certified commit has different reference than the committed sub dag"
732                );
733            }
734        }
735
736        // Notify about our own committed blocks
737        let committed_block_refs = committed_sub_dags
738            .iter()
739            .flat_map(|sub_dag| sub_dag.blocks.iter())
740            .filter_map(|block| {
741                (block.author() == self.context.own_index).then_some(block.reference())
742            })
743            .collect::<Vec<_>>();
744        if let Some(proposer) = &self.proposer {
745            proposer.notify_own_blocks_committed(
746                committed_block_refs,
747                self.dag_state.read().gc_round(),
748            );
749        }
750
751        Ok(committed_sub_dags)
752    }
753
754    pub(crate) fn get_missing_blocks(&self) -> BTreeSet<BlockRef> {
755        let _scope = monitored_scope("Core::get_missing_blocks");
756        self.block_manager.missing_blocks()
757    }
758
759    /// Sets the delay by round for propagating blocks to a quorum.
760    pub(crate) fn set_propagation_delay(&mut self, delay: Round) {
761        info!("Propagation round delay set to: {delay}");
762        if let Some(proposer) = &mut self.proposer {
763            proposer.set_propagation_delay(delay);
764        }
765    }
766
767    /// Sets the min propose round for the proposer allowing to propose blocks only for round numbers
768    /// `> last_known_proposed_round`. At the moment is allowed to call the method only once leading to a panic
769    /// if attempt to do multiple times.
770    pub(crate) fn set_last_known_proposed_round(&mut self, round: Round) {
771        if let Some(proposer) = &mut self.proposer {
772            if proposer.get_last_known_proposed_round().is_some() {
773                panic!(
774                    "Should not attempt to set the last known proposed round if that has been already set"
775                );
776            }
777            proposer.set_last_known_proposed_round(round);
778            info!("Last known proposed round set to {round}");
779        }
780    }
781
782    /// Returns true if the node should propose blocks.
783    /// Observers always return false since they don't have a proposer.
784    pub(crate) fn should_propose(&self) -> bool {
785        self.proposer
786            .as_ref()
787            .map(|p| p.should_propose())
788            .unwrap_or(false)
789    }
790
791    /// Returns the last proposed round, or None if this is an observer node.
792    pub(crate) fn last_proposed_round(&self) -> Option<Round> {
793        self.proposer.as_ref().map(|p| p.last_proposed_round())
794    }
795
796    /// Returns the current `ReputationScores` from the leader schedule.
797    fn current_reputation_scores(&self) -> ReputationScores {
798        if let Some(schedule) = self.leader_schedule_v3.as_ref() {
799            schedule.current_reputation_scores()
800        } else {
801            self.leader_schedule
802                .leader_swap_table
803                .read()
804                .reputation_scores
805                .clone()
806        }
807    }
808
809    // Tries to select a prefix of certified commits to be committed next respecting the `limit`.
810    // If provided `limit` is zero, it will panic.
811    // The function returns a list of certified leaders and certified commits. If empty vector is returned, it means that
812    // there are no certified commits to be committed, as input `certified_commits` is either empty or all of the certified
813    // commits have been already committed.
814    #[tracing::instrument(skip_all)]
815    fn try_select_certified_leaders(
816        &mut self,
817        certified_commits: &mut Vec<CertifiedCommit>,
818        limit: usize,
819    ) -> Vec<(DecidedLeader, CertifiedCommit)> {
820        assert!(limit > 0, "limit should be greater than 0");
821        if certified_commits.is_empty() {
822            return vec![];
823        }
824
825        let to_commit = if certified_commits.len() >= limit {
826            // We keep only the number of leaders as dictated by the `limit`
827            certified_commits.drain(..limit).collect::<Vec<_>>()
828        } else {
829            // Otherwise just take all of them and leave the `synced_commits` empty.
830            std::mem::take(certified_commits)
831        };
832
833        tracing::debug!(
834            "Selected {} certified leaders: {}",
835            to_commit.len(),
836            to_commit.iter().map(|c| c.leader().to_string()).join(",")
837        );
838
839        to_commit
840            .into_iter()
841            .map(|commit| {
842                let leader = commit.blocks().last().expect("Certified commit should have at least one block");
843                assert_eq!(leader.reference(), commit.leader(), "Last block of the committed sub dag should have the same digest as the leader of the commit");
844                // There is no knowledge of direct commit with certified commits, so assuming indirect commit.
845                let leader = DecidedLeader::Commit(leader.clone(), /* direct */ false);
846                UniversalCommitter::update_metrics(&self.context, &leader, Decision::Certified);
847                (leader, commit)
848            })
849            .collect::<Vec<_>>()
850    }
851
852    /// Helper method for tests to get the last proposed block from the proposer.
853    #[cfg(test)]
854    pub(crate) fn last_proposed_block(&self) -> VerifiedBlock {
855        self.proposer
856            .as_ref()
857            .expect("Proposer should be present")
858            .last_proposed_block()
859    }
860
861    /// Helper method for tests to get the round tracker from the proposer.
862    /// Returns None if this is an observer node.
863    #[cfg(test)]
864    pub(crate) fn round_tracker_for_tests(&self) -> Arc<RwLock<RoundTracker>> {
865        self.proposer
866            .as_ref()
867            .expect("Proposer should be present")
868            .round_tracker_for_tests()
869    }
870}
871
872/// Senders of signals from Core, for outputs and events (ex new block produced).
873pub(crate) struct CoreSignals {
874    tx_block_broadcast: broadcast::Sender<ExtendedBlock>,
875    tx_accepted_block_broadcast: broadcast::Sender<VerifiedBlock>,
876    new_round_sender: watch::Sender<Round>,
877    context: Arc<Context>,
878}
879
880impl CoreSignals {
881    pub fn new(context: Arc<Context>) -> (Self, CoreSignalsReceivers) {
882        // Blocks buffered in broadcast channel should be roughly equal to thosed cached in dag state,
883        // since the underlying blocks are ref counted so a lower buffer here will not reduce memory
884        // usage significantly.
885        let (tx_block_broadcast, rx_block_broadcast) = broadcast::channel::<ExtendedBlock>(
886            context.parameters.dag_state_cached_rounds as usize,
887        );
888        let (tx_accepted_block_broadcast, rx_accepted_block_broadcast) =
889            broadcast::channel::<VerifiedBlock>(
890                2 * context.parameters.dag_state_cached_rounds as usize * context.committee.size(),
891            );
892        let (new_round_sender, new_round_receiver) = watch::channel(0);
893
894        let me = Self {
895            tx_block_broadcast,
896            tx_accepted_block_broadcast,
897            new_round_sender,
898            context,
899        };
900
901        let receivers = CoreSignalsReceivers {
902            rx_block_broadcast,
903            rx_accepted_block_broadcast,
904            new_round_receiver,
905        };
906
907        (me, receivers)
908    }
909
910    /// Sends a signal to all the waiters that a new block has been produced. The method will return
911    /// true if block has reached even one subscriber, false otherwise.
912    pub(crate) fn new_block(&self, extended_block: ExtendedBlock) -> ConsensusResult<()> {
913        // When there is only one authority in committee, it is unnecessary to broadcast
914        // the block which will fail anyway without subscribers to the signal.
915        if self.context.committee.size() > 1 {
916            if extended_block.block.round() == GENESIS_ROUND {
917                debug!("Ignoring broadcasting genesis block to peers");
918                return Ok(());
919            }
920
921            if let Err(err) = self.tx_block_broadcast.send(extended_block) {
922                warn!("Couldn't broadcast the block to any receiver: {err}");
923                return Err(ConsensusError::Shutdown);
924            }
925        } else {
926            debug!(
927                "Did not broadcast block {extended_block:?} to receivers as committee size is <= 1"
928            );
929        }
930        Ok(())
931    }
932
933    /// Broadcasts a block that has been accepted into the local DAG to any active observer
934    /// subscribers. Unlike `new_block()`, this covers blocks from all authorities, not just
935    /// own proposals.
936    /// Silently drops the send when there are no active observer subscribers.
937    pub(crate) fn new_accepted_block(&self, block: VerifiedBlock) {
938        // Ignoring send errors here: it is normal for there to be no observers subscribed.
939        let _ = self.tx_accepted_block_broadcast.send(block);
940    }
941
942    /// Sends a signal that threshold clock has advanced to new round. The `round_number` is the round at which the
943    /// threshold clock has advanced to.
944    pub(crate) fn new_round(&mut self, round_number: Round) {
945        let _ = self.new_round_sender.send_replace(round_number);
946    }
947}
948
949/// Receivers of signals from Core.
950/// Intentionally un-clonable. Comonents should only subscribe to channels they need.
951pub(crate) struct CoreSignalsReceivers {
952    rx_block_broadcast: broadcast::Receiver<ExtendedBlock>,
953    rx_accepted_block_broadcast: broadcast::Receiver<VerifiedBlock>,
954    new_round_receiver: watch::Receiver<Round>,
955}
956
957impl CoreSignalsReceivers {
958    pub(crate) fn block_broadcast_receiver(&self) -> broadcast::Receiver<ExtendedBlock> {
959        self.rx_block_broadcast.resubscribe()
960    }
961
962    pub(crate) fn accepted_block_broadcast_receiver(&self) -> broadcast::Receiver<VerifiedBlock> {
963        self.rx_accepted_block_broadcast.resubscribe()
964    }
965
966    pub(crate) fn new_round_receiver(&self) -> watch::Receiver<Round> {
967        self.new_round_receiver.clone()
968    }
969}
970
971/// Creates cores for the specified number of authorities for their corresponding stakes. The method returns the
972/// cores and their respective signal receivers are returned in `AuthorityIndex` order asc.
973#[cfg(test)]
974pub(crate) async fn create_cores(
975    context: Context,
976    authorities: Vec<Stake>,
977) -> Vec<CoreTestFixture> {
978    let mut cores = Vec::new();
979
980    for index in 0..authorities.len() {
981        let own_index = AuthorityIndex::new_for_test(index as u32);
982        let core =
983            CoreTestFixture::new(context.clone(), authorities.clone(), own_index, false).await;
984        cores.push(core);
985    }
986    cores
987}
988
989#[cfg(test)]
990pub(crate) struct CoreTestFixture {
991    pub(crate) core: Core,
992    pub(crate) transaction_vote_tracker: TransactionVoteTracker,
993    pub(crate) signal_receivers: CoreSignalsReceivers,
994    pub(crate) block_receiver: broadcast::Receiver<ExtendedBlock>,
995    pub(crate) _commit_output_receiver: UnboundedReceiver<CommittedSubDag>,
996    pub(crate) dag_state: Arc<RwLock<DagState>>,
997    pub(crate) store: Arc<MemStore>,
998    pub(crate) transaction_client: TransactionClient,
999}
1000
1001#[cfg(test)]
1002impl CoreTestFixture {
1003    async fn new(
1004        context: Context,
1005        authorities: Vec<Stake>,
1006        own_index: AuthorityIndex,
1007        sync_last_known_own_block: bool,
1008    ) -> Self {
1009        Self::new_with_prepopulated_blocks(
1010            context,
1011            authorities,
1012            own_index,
1013            sync_last_known_own_block,
1014            vec![],
1015        )
1016        .await
1017    }
1018
1019    /// Variant of `new` that writes `prepopulated_blocks` into the store before
1020    /// constructing `DagState` and `Core`. Used by tests that recover from a
1021    /// pre-existing store state.
1022    async fn new_with_prepopulated_blocks(
1023        context: Context,
1024        authorities: Vec<Stake>,
1025        own_index: AuthorityIndex,
1026        sync_last_known_own_block: bool,
1027        prepopulated_blocks: Vec<VerifiedBlock>,
1028    ) -> Self {
1029        let (committee, mut signers) = local_committee_and_keys(0, authorities.clone());
1030        let mut context = context.clone();
1031        context = context
1032            .with_committee(committee)
1033            .with_authority_index(own_index);
1034        context
1035            .protocol_config
1036            .set_bad_nodes_stake_threshold_for_testing(33);
1037
1038        let context = Arc::new(context);
1039        let store = Arc::new(MemStore::new());
1040        if !prepopulated_blocks.is_empty() {
1041            store
1042                .write(WriteBatch::default().blocks(prepopulated_blocks))
1043                .expect("Storage error");
1044        }
1045        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1046
1047        let block_manager = BlockManager::new(context.clone(), dag_state.clone());
1048        let leader_schedule = Arc::new(
1049            LeaderSchedule::from_store(context.clone(), dag_state.clone())
1050                .with_num_commits_per_schedule(10),
1051        );
1052        let (transaction_client, tx_receiver, priority_tx_receiver) =
1053            TransactionClient::new(context.clone());
1054        let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1055            tx_receiver,
1056            priority_tx_receiver,
1057            context.clone(),
1058        )));
1059        let transaction_vote_tracker = TransactionVoteTracker::new(
1060            context.clone(),
1061            Arc::new(NoopBlockVerifier {}),
1062            dag_state.clone(),
1063        );
1064        let (signals, signal_receivers) = CoreSignals::new(context.clone());
1065        // Need at least one subscriber to the block broadcast channel.
1066        let block_receiver = signal_receivers.block_broadcast_receiver();
1067
1068        let (commit_consumer, commit_output_receiver) = CommitConsumerArgs::new(0, 0);
1069        let commit_observer = CommitObserver::new(
1070            context.clone(),
1071            commit_consumer,
1072            dag_state.clone(),
1073            transaction_vote_tracker.clone(),
1074        )
1075        .await;
1076
1077        let block_signer = signers.remove(own_index.value()).1;
1078
1079        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1080        let core = Core::new_validator(
1081            context.clone(),
1082            leader_schedule,
1083            transaction_pool,
1084            transaction_vote_tracker.clone(),
1085            block_manager,
1086            commit_observer,
1087            signals,
1088            block_signer,
1089            dag_state.clone(),
1090            sync_last_known_own_block,
1091            round_tracker.clone(),
1092        );
1093
1094        Self {
1095            core,
1096            transaction_vote_tracker,
1097            signal_receivers,
1098            block_receiver,
1099            _commit_output_receiver: commit_output_receiver,
1100            dag_state,
1101            store,
1102            transaction_client,
1103        }
1104    }
1105
1106    pub(crate) fn add_blocks(
1107        &mut self,
1108        blocks: Vec<VerifiedBlock>,
1109    ) -> ConsensusResult<BTreeSet<BlockRef>> {
1110        self.transaction_vote_tracker
1111            .add_voted_blocks(blocks.iter().map(|b| (b.clone(), vec![])).collect());
1112        self.core.add_blocks(blocks)
1113    }
1114}
1115
1116#[cfg(test)]
1117mod test {
1118    use std::{collections::BTreeSet, iter, time::Duration};
1119
1120    use consensus_config::{AuthorityIndex, Parameters};
1121    use consensus_types::block::BlockTimestampMs;
1122    use futures::{StreamExt, stream::FuturesUnordered};
1123    use tokio::time::sleep;
1124
1125    use super::*;
1126    use crate::{
1127        CommitConsumerArgs, CommitIndex,
1128        block::{TestBlock, genesis_blocks},
1129        block_verifier::NoopBlockVerifier,
1130        commit::CommitAPI,
1131        leader_scoring::ReputationScores,
1132        storage::{Store, WriteBatch, mem_store::MemStore},
1133        test_dag_builder::DagBuilder,
1134        test_dag_parser::parse_dag,
1135        transaction::{BlockStatus, Priority, TransactionClient},
1136    };
1137
1138    /// Recover Core and continue proposing from the last round which forms a quorum.
1139    #[tokio::test]
1140    async fn test_core_recover_from_store_for_full_round() {
1141        telemetry_subscribers::init_for_testing();
1142        let (context, mut key_pairs) = Context::new_for_test(4);
1143        let context = Arc::new(context);
1144        let store = Arc::new(MemStore::new());
1145        let (_transaction_client, tx_receiver, priority_tx_receiver) =
1146            TransactionClient::new(context.clone());
1147        let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1148            tx_receiver,
1149            priority_tx_receiver,
1150            context.clone(),
1151        )));
1152        let mut block_status_subscriptions = FuturesUnordered::new();
1153
1154        // Create test blocks for all the authorities for 4 rounds and populate them in store
1155        let mut last_round_blocks = genesis_blocks(&context);
1156        let mut all_blocks: Vec<VerifiedBlock> = last_round_blocks.clone();
1157        for round in 1..=4 {
1158            let mut this_round_blocks = Vec::new();
1159            for (index, _authority) in context.committee.authorities() {
1160                let block = VerifiedBlock::new_for_test(
1161                    TestBlock::new(round, index.value() as u32)
1162                        .set_ancestors(last_round_blocks.iter().map(|b| b.reference()).collect())
1163                        .build(),
1164                );
1165
1166                // If it's round 1, that one will be committed later on, and it's our "own" block, then subscribe to listen for the block status.
1167                if round == 1 && index == context.own_index {
1168                    let subscription =
1169                        transaction_pool.subscribe_for_block_status_testing(block.reference());
1170                    block_status_subscriptions.push(subscription);
1171                }
1172
1173                this_round_blocks.push(block);
1174            }
1175            all_blocks.extend(this_round_blocks.clone());
1176            last_round_blocks = this_round_blocks;
1177        }
1178        // write them in store
1179        store
1180            .write(WriteBatch::default().blocks(all_blocks))
1181            .expect("Storage error");
1182
1183        // create dag state after all blocks have been written to store
1184        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1185        let block_manager = BlockManager::new(context.clone(), dag_state.clone());
1186        let leader_schedule = Arc::new(LeaderSchedule::from_store(
1187            context.clone(),
1188            dag_state.clone(),
1189        ));
1190        let transaction_vote_tracker = TransactionVoteTracker::new(
1191            context.clone(),
1192            Arc::new(NoopBlockVerifier {}),
1193            dag_state.clone(),
1194        );
1195
1196        let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
1197        let commit_observer = CommitObserver::new(
1198            context.clone(),
1199            commit_consumer,
1200            dag_state.clone(),
1201            transaction_vote_tracker.clone(),
1202        )
1203        .await;
1204
1205        // Check no commits have been persisted to dag_state or store.
1206        let last_commit = store.read_last_commit().unwrap();
1207        assert!(last_commit.is_none());
1208        assert_eq!(dag_state.read().last_commit_index(), 0);
1209
1210        // Now spin up core
1211        let (signals, signal_receivers) = CoreSignals::new(context.clone());
1212        let transaction_vote_tracker = TransactionVoteTracker::new(
1213            context.clone(),
1214            Arc::new(NoopBlockVerifier {}),
1215            dag_state.clone(),
1216        );
1217        transaction_vote_tracker.recover_blocks_after_round(dag_state.read().gc_round());
1218        // Need at least one subscriber to the block broadcast channel.
1219        let mut block_receiver = signal_receivers.block_broadcast_receiver();
1220        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1221        let _core = Core::new_validator(
1222            context.clone(),
1223            leader_schedule,
1224            transaction_pool,
1225            transaction_vote_tracker.clone(),
1226            block_manager,
1227            commit_observer,
1228            signals,
1229            key_pairs.remove(context.own_index.value()).1,
1230            dag_state.clone(),
1231            false,
1232            round_tracker,
1233        );
1234
1235        // New round should be 5
1236        let mut new_round = signal_receivers.new_round_receiver();
1237        assert_eq!(*new_round.borrow_and_update(), 5);
1238
1239        // Block for round 5 should have been proposed.
1240        let proposed_block = block_receiver
1241            .recv()
1242            .await
1243            .expect("A block should have been created");
1244        assert_eq!(proposed_block.block.round(), 5);
1245        let ancestors = proposed_block.block.ancestors();
1246
1247        // Only ancestors of round 4 should be included.
1248        assert_eq!(ancestors.len(), 4);
1249        for ancestor in ancestors {
1250            assert_eq!(ancestor.round, 4);
1251        }
1252
1253        // Flush the DAG state to storage.
1254        dag_state.write().flush();
1255
1256        // There were no commits prior to the core starting up but there was completed
1257        // rounds up to and including round 4. So we should commit leaders in round 1 & 2
1258        // as soon as the new block for round 5 is proposed.
1259        let last_commit = store
1260            .read_last_commit()
1261            .unwrap()
1262            .expect("last commit should be set");
1263        assert_eq!(last_commit.index(), 2);
1264        assert_eq!(dag_state.read().last_commit_index(), 2);
1265        let all_stored_commits = store.scan_commits((0..=CommitIndex::MAX).into()).unwrap();
1266        assert_eq!(all_stored_commits.len(), 2);
1267
1268        // And ensure that our "own" block 1 sent to TransactionConsumer as notification alongside with gc_round
1269        while let Some(result) = block_status_subscriptions.next().await {
1270            let status = result.unwrap();
1271            assert!(matches!(status, BlockStatus::Sequenced(_)));
1272        }
1273    }
1274
1275    /// Recover Core and continue proposing when having a partial last round which doesn't form a quorum and we haven't
1276    /// proposed for that round yet.
1277    #[tokio::test]
1278    async fn test_core_recover_from_store_for_partial_round() {
1279        telemetry_subscribers::init_for_testing();
1280
1281        let (context, _) = Context::new_for_test(4);
1282
1283        // Create test blocks for all authorities except our's (index = 0).
1284        let mut last_round_blocks = genesis_blocks(&context);
1285        let mut all_blocks = last_round_blocks.clone();
1286        for round in 1..=4 {
1287            let mut this_round_blocks = Vec::new();
1288
1289            // For round 4 only produce f+1 blocks. Skip our validator 0 and that of position 1 from creating blocks.
1290            let authorities_to_skip = if round == 4 {
1291                context.committee.validity_threshold() as usize
1292            } else {
1293                // otherwise always skip creating a block for our authority
1294                1
1295            };
1296
1297            for (index, _authority) in context.committee.authorities().skip(authorities_to_skip) {
1298                let block = TestBlock::new(round, index.value() as u32)
1299                    .set_ancestors(last_round_blocks.iter().map(|b| b.reference()).collect())
1300                    .build();
1301                this_round_blocks.push(VerifiedBlock::new_for_test(block));
1302            }
1303            all_blocks.extend(this_round_blocks.clone());
1304            last_round_blocks = this_round_blocks;
1305        }
1306
1307        let mut fixture = CoreTestFixture::new_with_prepopulated_blocks(
1308            context,
1309            vec![1, 1, 1, 1],
1310            AuthorityIndex::new_for_test(0),
1311            false,
1312            all_blocks,
1313        )
1314        .await;
1315
1316        // Clock round should have advanced to 5 during recovery because
1317        // a quorum has formed in round 4.
1318        let mut new_round = fixture.signal_receivers.new_round_receiver();
1319        assert_eq!(*new_round.borrow_and_update(), 5);
1320
1321        // During recovery, round 4 block should have been proposed.
1322        let proposed_block = fixture
1323            .block_receiver
1324            .recv()
1325            .await
1326            .expect("A block should have been created");
1327        assert_eq!(proposed_block.block.round(), 4);
1328        let ancestors = proposed_block.block.ancestors();
1329
1330        assert_eq!(ancestors.len(), 4);
1331        let own_index = fixture.core.context.own_index;
1332        for ancestor in ancestors {
1333            if ancestor.author == own_index {
1334                assert_eq!(ancestor.round, 0);
1335            } else {
1336                assert_eq!(ancestor.round, 3);
1337            }
1338        }
1339
1340        // Run commit rule.
1341        fixture.core.try_commit(vec![]).ok();
1342
1343        // Flush the DAG state to storage.
1344        fixture.core.dag_state.write().flush();
1345
1346        // There were no commits prior to the core starting up but there was completed
1347        // rounds up to round 4. So we should commit leaders in round 1 & 2 as soon
1348        // as the new block for round 4 is proposed.
1349        let last_commit = fixture
1350            .store
1351            .read_last_commit()
1352            .unwrap()
1353            .expect("last commit should be set");
1354        assert_eq!(last_commit.index(), 2);
1355        assert_eq!(fixture.dag_state.read().last_commit_index(), 2);
1356        let all_stored_commits = fixture
1357            .store
1358            .scan_commits((0..=CommitIndex::MAX).into())
1359            .unwrap();
1360        assert_eq!(all_stored_commits.len(), 2);
1361    }
1362
1363    #[tokio::test]
1364    async fn test_core_propose_after_genesis() {
1365        telemetry_subscribers::init_for_testing();
1366        let (mut context, _) = Context::new_for_test(4);
1367        context
1368            .protocol_config
1369            .set_max_transaction_size_bytes_for_testing(2_000);
1370        context
1371            .protocol_config
1372            .set_max_transactions_in_block_bytes_for_testing(2_000);
1373
1374        let mut fixture = CoreTestFixture::new(
1375            context,
1376            vec![1, 1, 1, 1],
1377            AuthorityIndex::new_for_test(0),
1378            false,
1379        )
1380        .await;
1381
1382        // Send some transactions
1383        let mut total = 0;
1384        let mut index = 0;
1385        loop {
1386            let transaction =
1387                bcs::to_bytes(&format!("Transaction {index}")).expect("Shouldn't fail");
1388            total += transaction.len();
1389            index += 1;
1390            let _w = fixture
1391                .transaction_client
1392                .submit_no_wait(vec![transaction], Priority::Normal)
1393                .await
1394                .unwrap();
1395
1396            // Create total size of transactions up to 1KB
1397            if total >= 1_000 {
1398                break;
1399            }
1400        }
1401
1402        // a new block should have been created during recovery.
1403        let extended_block = fixture
1404            .block_receiver
1405            .recv()
1406            .await
1407            .expect("A new block should have been created");
1408
1409        // A new block created - assert the details
1410        assert_eq!(extended_block.block.round(), 1);
1411        assert_eq!(extended_block.block.author().value(), 0);
1412        assert_eq!(extended_block.block.ancestors().len(), 4);
1413
1414        let mut total = 0;
1415        for (i, transaction) in extended_block.block.transactions().iter().enumerate() {
1416            total += transaction.data().len() as u64;
1417            let transaction: String = bcs::from_bytes(transaction.data()).unwrap();
1418            assert_eq!(format!("Transaction {i}"), transaction);
1419        }
1420        assert!(
1421            total
1422                <= fixture
1423                    .core
1424                    .context
1425                    .protocol_config
1426                    .max_transactions_in_block_bytes()
1427        );
1428        assert_eq!(
1429            fixture
1430                .core
1431                .context
1432                .metrics
1433                .node_metrics
1434                .proposed_block_transaction_bytes
1435                .get_sample_count(),
1436            1
1437        );
1438        assert_eq!(
1439            fixture
1440                .core
1441                .context
1442                .metrics
1443                .node_metrics
1444                .proposed_block_transaction_bytes
1445                .get_sample_sum(),
1446            total as f64
1447        );
1448
1449        // genesis blocks should be referenced
1450        let all_genesis = genesis_blocks(&fixture.core.context);
1451
1452        for ancestor in extended_block.block.ancestors() {
1453            all_genesis
1454                .iter()
1455                .find(|block| block.reference() == *ancestor)
1456                .expect("Block should be found amongst genesis blocks");
1457        }
1458
1459        // Try to propose again - with or without ignore leaders check, it will not return any block
1460        assert!(fixture.core.try_propose(false).unwrap().is_none());
1461        assert!(fixture.core.try_propose(true).unwrap().is_none());
1462
1463        // Flush the DAG state to storage.
1464        fixture.dag_state.write().flush();
1465
1466        // Check no commits have been persisted to dag_state & store
1467        let last_commit = fixture.store.read_last_commit().unwrap();
1468        assert!(last_commit.is_none());
1469        assert_eq!(fixture.dag_state.read().last_commit_index(), 0);
1470    }
1471
1472    #[tokio::test]
1473    async fn test_core_propose_once_receiving_a_quorum() {
1474        telemetry_subscribers::init_for_testing();
1475        let (context, _key_pairs) = Context::new_for_test(4);
1476        let mut core_fixture = CoreTestFixture::new(
1477            context.clone(),
1478            vec![1, 1, 1, 1],
1479            AuthorityIndex::new_for_test(0),
1480            false,
1481        )
1482        .await;
1483        let transaction_vote_tracker = &core_fixture.transaction_vote_tracker;
1484        let store = &core_fixture.store;
1485        let dag_state = &core_fixture.dag_state;
1486        let core = &mut core_fixture.core;
1487
1488        let mut expected_ancestors = BTreeSet::new();
1489
1490        // Adding one block now will trigger the creation of new block for round 1
1491        let block_1 = VerifiedBlock::new_for_test(TestBlock::new(1, 1).build());
1492        expected_ancestors.insert(block_1.reference());
1493        // Wait for min round delay to allow blocks to be proposed.
1494        sleep(context.parameters.min_round_delay).await;
1495        // add blocks to trigger proposal.
1496        transaction_vote_tracker.add_voted_blocks(vec![(block_1.clone(), vec![])]);
1497        _ = core.add_blocks(vec![block_1]);
1498
1499        assert_eq!(core.last_proposed_round(), Some(1));
1500        expected_ancestors.insert(core.last_proposed_block().reference());
1501        // attempt to create a block - none will be produced.
1502        assert!(core.try_propose(false).unwrap().is_none());
1503
1504        // Adding another block now forms a quorum for round 1, so block at round 2 will proposed
1505        let block_2 = VerifiedBlock::new_for_test(TestBlock::new(1, 2).build());
1506        expected_ancestors.insert(block_2.reference());
1507        // Wait for min round delay to allow blocks to be proposed.
1508        sleep(context.parameters.min_round_delay).await;
1509        // add blocks to trigger proposal.
1510        transaction_vote_tracker.add_voted_blocks(vec![(block_2.clone(), vec![1, 4])]);
1511        _ = core.add_blocks(vec![block_2.clone()]);
1512
1513        assert_eq!(core.last_proposed_round(), Some(2));
1514
1515        let proposed_block = core.last_proposed_block();
1516        assert_eq!(proposed_block.round(), 2);
1517        assert_eq!(proposed_block.author(), context.own_index);
1518        assert_eq!(proposed_block.ancestors().len(), 3);
1519        let ancestors = proposed_block.ancestors();
1520        let ancestors = ancestors.iter().cloned().collect::<BTreeSet<_>>();
1521        assert_eq!(ancestors, expected_ancestors);
1522
1523        let transaction_votes = proposed_block.transaction_votes();
1524        assert_eq!(transaction_votes.len(), 1);
1525        let transaction_vote = transaction_votes.first().unwrap();
1526        assert_eq!(transaction_vote.block_ref, block_2.reference());
1527        assert_eq!(transaction_vote.rejects, vec![1, 4]);
1528        assert_eq!(
1529            context
1530                .metrics
1531                .node_metrics
1532                .proposed_block_transaction_vote_blocks
1533                .get_sample_count(),
1534            2
1535        );
1536        assert_eq!(
1537            context
1538                .metrics
1539                .node_metrics
1540                .proposed_block_transaction_vote_blocks
1541                .get_sample_sum(),
1542            1.0
1543        );
1544        assert_eq!(
1545            context
1546                .metrics
1547                .node_metrics
1548                .proposed_block_transaction_vote_entries
1549                .get_sample_count(),
1550            2
1551        );
1552        assert_eq!(
1553            context
1554                .metrics
1555                .node_metrics
1556                .proposed_block_transaction_vote_entries
1557                .get_sample_sum(),
1558            2.0
1559        );
1560
1561        // Flush the DAG state to storage.
1562        dag_state.write().flush();
1563
1564        // Check no commits have been persisted to dag_state & store
1565        let last_commit = store.read_last_commit().unwrap();
1566        assert!(last_commit.is_none());
1567        assert_eq!(dag_state.read().last_commit_index(), 0);
1568    }
1569
1570    #[tokio::test]
1571    async fn test_commit_and_notify_for_block_status() {
1572        telemetry_subscribers::init_for_testing();
1573        let (mut context, mut key_pairs) = Context::new_for_test(4);
1574        const GC_DEPTH: u32 = 2;
1575
1576        context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
1577
1578        let context = Arc::new(context);
1579
1580        let store = Arc::new(MemStore::new());
1581        let (_transaction_client, tx_receiver, priority_tx_receiver) =
1582            TransactionClient::new(context.clone());
1583        let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1584            tx_receiver,
1585            priority_tx_receiver,
1586            context.clone(),
1587        )));
1588        let mut block_status_subscriptions = FuturesUnordered::new();
1589
1590        let dag_str = "DAG {
1591            Round 0 : { 4 },
1592            Round 1 : { * },
1593            Round 2 : { * },
1594            Round 3 : {
1595                A -> [*],
1596                B -> [-A2],
1597                C -> [-A2],
1598                D -> [-A2],
1599            },
1600            Round 4 : { 
1601                B -> [-A3],
1602                C -> [-A3],
1603                D -> [-A3],
1604            },
1605            Round 5 : { 
1606                A -> [A3, B4, C4, D4]
1607                B -> [*],
1608                C -> [*],
1609                D -> [*],
1610            },
1611            Round 6 : { * },
1612            Round 7 : { * },
1613            Round 8 : { * },
1614        }";
1615
1616        let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
1617        dag_builder.print();
1618
1619        // Subscribe to all created "own" blocks. We know that for our node (A) we'll be able to commit up to round 5.
1620        for block in dag_builder.blocks(1..=5) {
1621            if block.author() == context.own_index {
1622                let subscription =
1623                    transaction_pool.subscribe_for_block_status_testing(block.reference());
1624                block_status_subscriptions.push(subscription);
1625            }
1626        }
1627
1628        // write them in store
1629        store
1630            .write(WriteBatch::default().blocks(dag_builder.blocks(1..=8)))
1631            .expect("Storage error");
1632
1633        // create dag state after all blocks have been written to store
1634        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1635        let block_manager = BlockManager::new(context.clone(), dag_state.clone());
1636        let leader_schedule = Arc::new(LeaderSchedule::from_store(
1637            context.clone(),
1638            dag_state.clone(),
1639        ));
1640        let transaction_vote_tracker = TransactionVoteTracker::new(
1641            context.clone(),
1642            Arc::new(NoopBlockVerifier {}),
1643            dag_state.clone(),
1644        );
1645
1646        let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
1647        let commit_observer = CommitObserver::new(
1648            context.clone(),
1649            commit_consumer,
1650            dag_state.clone(),
1651            transaction_vote_tracker.clone(),
1652        )
1653        .await;
1654
1655        // Flush the DAG state to storage.
1656        dag_state.write().flush();
1657
1658        // Check no commits have been persisted to dag_state or store.
1659        let last_commit = store.read_last_commit().unwrap();
1660        assert!(last_commit.is_none());
1661        assert_eq!(dag_state.read().last_commit_index(), 0);
1662
1663        // Now recover Core and other components.
1664        let (signals, signal_receivers) = CoreSignals::new(context.clone());
1665        let transaction_vote_tracker = TransactionVoteTracker::new(
1666            context.clone(),
1667            Arc::new(NoopBlockVerifier {}),
1668            dag_state.clone(),
1669        );
1670        transaction_vote_tracker.recover_blocks_after_round(dag_state.read().gc_round());
1671        // Need at least one subscriber to the block broadcast channel.
1672        let _block_receiver = signal_receivers.block_broadcast_receiver();
1673        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1674        let _core = Core::new_validator(
1675            context.clone(),
1676            leader_schedule,
1677            transaction_pool,
1678            transaction_vote_tracker,
1679            block_manager,
1680            commit_observer,
1681            signals,
1682            key_pairs.remove(context.own_index.value()).1,
1683            dag_state.clone(),
1684            false,
1685            round_tracker,
1686        );
1687
1688        // Flush the DAG state to storage.
1689        dag_state.write().flush();
1690
1691        let last_commit = store
1692            .read_last_commit()
1693            .unwrap()
1694            .expect("last commit should be set");
1695
1696        assert_eq!(last_commit.index(), 5);
1697
1698        while let Some(result) = block_status_subscriptions.next().await {
1699            let status = result.unwrap();
1700
1701            match status {
1702                BlockStatus::Sequenced(block_ref) => {
1703                    assert!(block_ref.round == 1 || block_ref.round == 5);
1704                }
1705                BlockStatus::GarbageCollected(block_ref) => {
1706                    assert!(block_ref.round == 2 || block_ref.round == 3);
1707                }
1708            }
1709        }
1710    }
1711
1712    // Tests that the threshold clock advances when blocks get unsuspended due to GC'ed blocks and newly created blocks are always higher
1713    // than the last advanced gc round.
1714    #[tokio::test]
1715    async fn test_multiple_commits_advance_threshold_clock() {
1716        telemetry_subscribers::init_for_testing();
1717        let (mut context, _) = Context::new_for_test(4);
1718        const GC_DEPTH: u32 = 2;
1719
1720        context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
1721
1722        // On round 1 we do produce the block for authority D but we do not link it until round 6. This is making round 6 unable to get processed
1723        // until leader of round 3 is committed where round 1 gets garbage collected.
1724        // Then we add more rounds so we can trigger a commit for leader of round 9 which will move the gc round to 7.
1725        let dag_str = "DAG {
1726            Round 0 : { 4 },
1727            Round 1 : { * },
1728            Round 2 : {
1729                B -> [-D1],
1730                C -> [-D1],
1731                D -> [-D1],
1732            },
1733            Round 3 : {
1734                B -> [*],
1735                C -> [*]
1736                D -> [*],
1737            },
1738            Round 4 : {
1739                A -> [*],
1740                B -> [*],
1741                C -> [*]
1742                D -> [*],
1743            },
1744            Round 5 : {
1745                A -> [*],
1746                B -> [*],
1747                C -> [*],
1748                D -> [*],
1749            },
1750            Round 6 : {
1751                B -> [A5, B5, C5, D1],
1752                C -> [A5, B5, C5, D1],
1753                D -> [A5, B5, C5, D1],
1754            },
1755            Round 7 : {
1756                B -> [*],
1757                C -> [*],
1758                D -> [*],
1759            },
1760            Round 8 : {
1761                B -> [*],
1762                C -> [*],
1763                D -> [*],
1764            },
1765            Round 9 : {
1766                B -> [*],
1767                C -> [*],
1768                D -> [*],
1769            },
1770            Round 10 : {
1771                B -> [*],
1772                C -> [*],
1773                D -> [*],
1774            },
1775            Round 11 : {
1776                B -> [*],
1777                C -> [*],
1778                D -> [*],
1779            },
1780        }";
1781
1782        let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
1783        dag_builder.print();
1784
1785        let mut fixture = CoreTestFixture::new(
1786            context,
1787            vec![1, 1, 1, 1],
1788            AuthorityIndex::new_for_test(0),
1789            true,
1790        )
1791        .await;
1792
1793        // Check no commits have been persisted to dag_state or store.
1794        let last_commit = fixture.store.read_last_commit().unwrap();
1795        assert!(last_commit.is_none());
1796        assert_eq!(fixture.dag_state.read().last_commit_index(), 0);
1797
1798        // We set the last known round to 4 so we avoid creating new blocks until then - otherwise it will crash as the already created DAG contains blocks for this
1799        // authority.
1800        fixture.core.set_last_known_proposed_round(4);
1801
1802        // We add all the blocks except D1. The only ones we can immediately accept are the ones up to round 5 as they don't have a dependency on D1. Rest of blocks do have causal dependency
1803        // to D1 so they can't be processed until the leader of round 3 can get committed and gc round moves to 1. That will make all the blocks that depend to D1 get accepted.
1804        // However, our threshold clock is now at round 6 as the last quorum that we managed to process was the round 5.
1805        // As commits happen blocks of later rounds get accepted and more leaders get committed. Eventually the leader of round 9 gets committed and gc is moved to 9 - 2 = 7.
1806        // If our node attempts to produce a block for the threshold clock 6, that will make the acceptance checks fail as now gc has moved far past this round.
1807        let mut all_blocks = dag_builder.blocks(1..=11);
1808        all_blocks.sort_by_key(|b| b.round());
1809        // Register votes for every block - including the one withheld from Core -
1810        // so transaction_vote_tracker's state matches the full DAG.
1811        fixture
1812            .transaction_vote_tracker
1813            .add_voted_blocks(all_blocks.iter().map(|b| (b.clone(), vec![])).collect());
1814        let blocks: Vec<VerifiedBlock> = all_blocks
1815            .into_iter()
1816            .filter(|b| !(b.round() == 1 && b.author() == AuthorityIndex::new_for_test(3)))
1817            .collect();
1818        fixture.core.add_blocks(blocks).expect("Should not fail");
1819
1820        assert_eq!(fixture.core.last_proposed_round(), Some(12));
1821    }
1822
1823    #[tokio::test]
1824    async fn test_core_set_min_propose_round() {
1825        telemetry_subscribers::init_for_testing();
1826        let (context, _) = Context::new_for_test(4);
1827        let context = context.with_parameters(Parameters {
1828            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
1829            ..Default::default()
1830        });
1831        let mut fixture = CoreTestFixture::new(
1832            context,
1833            vec![1, 1, 1, 1],
1834            AuthorityIndex::new_for_test(0),
1835            true,
1836        )
1837        .await;
1838
1839        // No new block should have been produced
1840        assert_eq!(
1841            fixture.core.last_proposed_round(),
1842            Some(GENESIS_ROUND),
1843            "No block should have been created other than genesis"
1844        );
1845
1846        // Trying to explicitly propose a block will not produce anything
1847        assert!(fixture.core.try_propose(true).unwrap().is_none());
1848
1849        // Create blocks for the whole network - even "our" node in order to replicate an "amnesia" recovery.
1850        let mut builder = DagBuilder::new(fixture.core.context.clone());
1851        builder.layers(1..=10).build();
1852
1853        let blocks = builder.blocks.values().cloned().collect::<Vec<_>>();
1854
1855        // Process all the blocks
1856        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
1857
1858        fixture
1859            .core
1860            .round_tracker_for_tests()
1861            .write()
1862            .update_from_probe(
1863                vec![
1864                    vec![10, 10, 10, 10],
1865                    vec![10, 10, 10, 10],
1866                    vec![10, 10, 10, 10],
1867                    vec![10, 10, 10, 10],
1868                ],
1869                vec![
1870                    vec![10, 10, 10, 10],
1871                    vec![10, 10, 10, 10],
1872                    vec![10, 10, 10, 10],
1873                    vec![10, 10, 10, 10],
1874                ],
1875            );
1876
1877        // Try to propose - no block should be produced.
1878        assert!(fixture.core.try_propose(true).unwrap().is_none());
1879
1880        // Now set the last known proposed round which is the highest round for which the network informed
1881        // us that we do have proposed a block about.
1882        fixture.core.set_last_known_proposed_round(10);
1883
1884        let block = fixture.core.try_propose(true).expect("No error").unwrap();
1885        assert_eq!(block.round(), 11);
1886        assert_eq!(block.ancestors().len(), 4);
1887
1888        let our_ancestor_included = block.ancestors()[0];
1889        assert_eq!(our_ancestor_included.author, fixture.core.context.own_index);
1890        assert_eq!(our_ancestor_included.round, 10);
1891    }
1892
1893    #[tokio::test(flavor = "current_thread", start_paused = true)]
1894    async fn test_core_try_new_block_leader_timeout() {
1895        telemetry_subscribers::init_for_testing();
1896
1897        // Since we run the test with started_paused = true, any time-dependent operations using Tokio's time
1898        // facilities, such as tokio::time::sleep or tokio::time::Instant, will not advance. So practically each
1899        // Core's clock will have initialised potentially with different values but it never advances.
1900        // To ensure that blocks won't get rejected by cores we'll need to manually wait for the time
1901        // diff before processing them. By calling the `tokio::time::sleep` we implicitly also advance the
1902        // tokio clock.
1903        async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
1904            // Simulate the time wait before processing a block to ensure that block.timestamp <= now
1905            let now = context.clock.timestamp_utc_ms();
1906            let max_timestamp = blocks
1907                .iter()
1908                .max_by_key(|block| block.timestamp_ms() as BlockTimestampMs)
1909                .map(|block| block.timestamp_ms())
1910                .unwrap_or(0);
1911
1912            let wait_time = Duration::from_millis(max_timestamp.saturating_sub(now));
1913            sleep(wait_time).await;
1914        }
1915
1916        let (context, _) = Context::new_for_test(4);
1917        // Create the cores for all authorities
1918        let mut all_cores = create_cores(context, vec![1, 1, 1, 1]).await;
1919
1920        // Create blocks for rounds 1..=3 from all Cores except last Core of authority 3, so we miss the block from it. As
1921        // it will be the leader of round 3 then no-one will be able to progress to round 4 unless we explicitly trigger
1922        // the block creation.
1923        // create the cores and their signals for all the authorities
1924        let (_last_core, cores) = all_cores.split_last_mut().unwrap();
1925
1926        // Now iterate over a few rounds and ensure the corresponding signals are created while network advances
1927        let mut last_round_blocks = Vec::<VerifiedBlock>::new();
1928        for round in 1..=3 {
1929            let mut this_round_blocks = Vec::new();
1930
1931            for core_fixture in cores.iter_mut() {
1932                wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
1933
1934                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
1935
1936                // Only when round > 1 and using non-genesis parents.
1937                if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
1938                    assert_eq!(round - 1, r);
1939                    if core_fixture.core.last_proposed_round() == Some(r) {
1940                        // Force propose new block regardless of min round delay.
1941                        core_fixture
1942                            .core
1943                            .try_propose(true)
1944                            .unwrap()
1945                            .unwrap_or_else(|| {
1946                                panic!("Block should have been proposed for round {}", round)
1947                            });
1948                    }
1949                }
1950
1951                assert_eq!(core_fixture.core.last_proposed_round(), Some(round));
1952
1953                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
1954            }
1955
1956            last_round_blocks = this_round_blocks;
1957        }
1958
1959        // Try to create the blocks for round 4 by calling the try_propose() method. No block should be created as the
1960        // leader - authority 3 - hasn't proposed any block.
1961        for core_fixture in cores.iter_mut() {
1962            wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
1963
1964            core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
1965            assert!(core_fixture.core.try_propose(false).unwrap().is_none());
1966        }
1967
1968        // Now try to create the blocks for round 4 via the leader timeout method which should
1969        // ignore any leader checks or min round delay.
1970        for core_fixture in cores.iter_mut() {
1971            assert!(core_fixture.core.new_block(4, true).unwrap().is_some());
1972            assert_eq!(core_fixture.core.last_proposed_round(), Some(4));
1973
1974            // Flush the DAG state to storage.
1975            core_fixture.dag_state.write().flush();
1976
1977            // Check commits have been persisted to store
1978            let last_commit = core_fixture
1979                .store
1980                .read_last_commit()
1981                .unwrap()
1982                .expect("last commit should be set");
1983            // There are 1 leader rounds with rounds completed up to and including
1984            // round 4
1985            assert_eq!(last_commit.index(), 1);
1986            let all_stored_commits = core_fixture
1987                .store
1988                .scan_commits((0..=CommitIndex::MAX).into())
1989                .unwrap();
1990            assert_eq!(all_stored_commits.len(), 1);
1991        }
1992    }
1993
1994    #[tokio::test(flavor = "current_thread", start_paused = true)]
1995    async fn test_core_try_new_block_with_leader_timeout_and_low_scoring_authority() {
1996        telemetry_subscribers::init_for_testing();
1997
1998        // Since we run the test with started_paused = true, any time-dependent operations using Tokio's time
1999        // facilities, such as tokio::time::sleep or tokio::time::Instant, will not advance. So practically each
2000        // Core's clock will have initialised potentially with different values but it never advances.
2001        // To ensure that blocks won't get rejected by cores we'll need to manually wait for the time
2002        // diff before processing them. By calling the `tokio::time::sleep` we implicitly also advance the
2003        // tokio clock.
2004        async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
2005            // Simulate the time wait before processing a block to ensure that block.timestamp <= now
2006            let now = context.clock.timestamp_utc_ms();
2007            let max_timestamp = blocks
2008                .iter()
2009                .max_by_key(|block| block.timestamp_ms() as BlockTimestampMs)
2010                .map(|block| block.timestamp_ms())
2011                .unwrap_or(0);
2012
2013            let wait_time = Duration::from_millis(max_timestamp.saturating_sub(now));
2014            sleep(wait_time).await;
2015        }
2016
2017        let (mut context, _) = Context::new_for_test(5);
2018        context
2019            .protocol_config
2020            .set_bad_nodes_stake_threshold_for_testing(33);
2021
2022        // Create the cores for all authorities
2023        let mut all_cores = create_cores(context, vec![1, 1, 1, 1, 1]).await;
2024        let (_last_core, cores) = all_cores.split_last_mut().unwrap();
2025
2026        // Create blocks for rounds 1..=30 from all Cores except last Core of authority 4.
2027        let mut last_round_blocks = Vec::<VerifiedBlock>::new();
2028        for round in 1..=30 {
2029            let mut this_round_blocks = Vec::new();
2030
2031            for core_fixture in cores.iter_mut() {
2032                wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2033
2034                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2035
2036                core_fixture
2037                    .core
2038                    .round_tracker_for_tests()
2039                    .write()
2040                    .update_from_probe(
2041                        vec![
2042                            vec![round, round, round, round, 0],
2043                            vec![round, round, round, round, 0],
2044                            vec![round, round, round, round, 0],
2045                            vec![round, round, round, round, 0],
2046                            vec![0, 0, 0, 0, 0],
2047                        ],
2048                        vec![
2049                            vec![round, round, round, round, 0],
2050                            vec![round, round, round, round, 0],
2051                            vec![round, round, round, round, 0],
2052                            vec![round, round, round, round, 0],
2053                            vec![0, 0, 0, 0, 0],
2054                        ],
2055                    );
2056
2057                // Only when round > 1 and using non-genesis parents.
2058                if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
2059                    assert_eq!(round - 1, r);
2060                    if core_fixture.core.last_proposed_round() == Some(r) {
2061                        // Force propose new block regardless of min round delay.
2062                        core_fixture
2063                            .core
2064                            .try_propose(true)
2065                            .unwrap()
2066                            .unwrap_or_else(|| {
2067                                panic!("Block should have been proposed for round {}", round)
2068                            });
2069                    }
2070                }
2071
2072                assert_eq!(core_fixture.core.last_proposed_round(), Some(round));
2073
2074                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2075            }
2076
2077            last_round_blocks = this_round_blocks;
2078        }
2079
2080        // Now produce blocks for all Cores
2081        for round in 31..=40 {
2082            let mut this_round_blocks = Vec::new();
2083
2084            for core_fixture in all_cores.iter_mut() {
2085                wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2086
2087                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2088
2089                // Don't update probed rounds for authority 3 so it will remain
2090                // excluded
2091                core_fixture
2092                    .core
2093                    .round_tracker_for_tests()
2094                    .write()
2095                    .update_from_probe(
2096                        vec![
2097                            vec![round, round, round, round, 0],
2098                            vec![round, round, round, round, 0],
2099                            vec![round, round, round, round, 0],
2100                            vec![round, round, round, round, 0],
2101                            vec![0, 0, 0, 0, 0],
2102                        ],
2103                        vec![
2104                            vec![round, round, round, round, 0],
2105                            vec![round, round, round, round, 0],
2106                            vec![round, round, round, round, 0],
2107                            vec![round, round, round, round, 0],
2108                            vec![0, 0, 0, 0, 0],
2109                        ],
2110                    );
2111
2112                // Only when round > 1 and using non-genesis parents.
2113                if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
2114                    assert_eq!(round - 1, r);
2115                    if core_fixture.core.last_proposed_round() == Some(r) {
2116                        // Force propose new block regardless of min round delay.
2117                        core_fixture
2118                            .core
2119                            .try_propose(true)
2120                            .unwrap()
2121                            .unwrap_or_else(|| {
2122                                panic!("Block should have been proposed for round {}", round)
2123                            });
2124                    }
2125                }
2126
2127                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2128
2129                for block in this_round_blocks.iter() {
2130                    if block.author() != AuthorityIndex::new_for_test(4) {
2131                        // Assert blocks created include only 4 ancestors per block as one
2132                        // should be excluded
2133                        assert_eq!(block.ancestors().len(), 4);
2134                    } else {
2135                        // Authority 3 is the low scoring authority so it will still include
2136                        // its own blocks.
2137                        assert_eq!(block.ancestors().len(), 5);
2138                    }
2139                }
2140            }
2141
2142            last_round_blocks = this_round_blocks;
2143        }
2144    }
2145
2146    #[tokio::test]
2147    async fn test_smart_ancestor_selection() {
2148        telemetry_subscribers::init_for_testing();
2149        let (context, _) = Context::new_for_test(7);
2150        let context = context.with_parameters(Parameters {
2151            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2152            ..Default::default()
2153        });
2154        let mut fixture =
2155            CoreTestFixture::new(context, vec![1; 7], AuthorityIndex::new_for_test(0), true).await;
2156        let min_round_delay = fixture.core.context.parameters.min_round_delay;
2157
2158        // No new block should have been produced
2159        assert_eq!(
2160            fixture.core.last_proposed_round(),
2161            Some(GENESIS_ROUND),
2162            "No block should have been created other than genesis"
2163        );
2164
2165        // Trying to explicitly propose a block will not produce anything
2166        assert!(fixture.core.try_propose(true).unwrap().is_none());
2167
2168        // Create blocks for the whole network but not for authority 1
2169        let mut builder = DagBuilder::new(fixture.core.context.clone());
2170        builder
2171            .layers(1..=12)
2172            .authorities(vec![AuthorityIndex::new_for_test(1)])
2173            .skip_block()
2174            .build();
2175        let blocks = builder.blocks(1..=12);
2176        // Process all the blocks
2177        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2178        fixture.core.set_last_known_proposed_round(12);
2179
2180        fixture
2181            .core
2182            .round_tracker_for_tests()
2183            .write()
2184            .update_from_probe(
2185                vec![
2186                    vec![12, 12, 12, 12, 12, 12, 12],
2187                    vec![0, 0, 0, 0, 0, 0, 0],
2188                    vec![12, 12, 12, 12, 12, 12, 12],
2189                    vec![12, 12, 12, 12, 12, 12, 12],
2190                    vec![12, 12, 12, 12, 12, 12, 12],
2191                    vec![12, 12, 12, 12, 12, 12, 12],
2192                    vec![12, 12, 12, 12, 12, 12, 12],
2193                ],
2194                vec![
2195                    vec![12, 12, 12, 12, 12, 12, 12],
2196                    vec![0, 0, 0, 0, 0, 0, 0],
2197                    vec![12, 12, 12, 12, 12, 12, 12],
2198                    vec![12, 12, 12, 12, 12, 12, 12],
2199                    vec![12, 12, 12, 12, 12, 12, 12],
2200                    vec![12, 12, 12, 12, 12, 12, 12],
2201                    vec![12, 12, 12, 12, 12, 12, 12],
2202                ],
2203            );
2204
2205        let block = fixture.core.try_propose(true).expect("No error").unwrap();
2206        assert_eq!(block.round(), 13);
2207        assert_eq!(block.ancestors().len(), 7);
2208
2209        // Build blocks for rest of the network other than own index
2210        builder
2211            .layers(13..=14)
2212            .authorities(vec![AuthorityIndex::new_for_test(0)])
2213            .skip_block()
2214            .build();
2215        let blocks = builder.blocks(13..=14);
2216        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2217
2218        // We now have triggered a leader schedule change so we should have
2219        // one EXCLUDE authority (1) when we go to select ancestors for the next proposal
2220        let block = fixture.core.try_propose(true).expect("No error").unwrap();
2221        assert_eq!(block.round(), 15);
2222        assert_eq!(block.ancestors().len(), 6);
2223
2224        // Build blocks for a quorum of the network including the EXCLUDE authority (1)
2225        // which will trigger smart select and we will not propose a block
2226        let round_14_ancestors = builder.last_ancestors.clone();
2227        builder
2228            .layer(15)
2229            .authorities(vec![
2230                AuthorityIndex::new_for_test(0),
2231                AuthorityIndex::new_for_test(5),
2232                AuthorityIndex::new_for_test(6),
2233            ])
2234            .skip_block()
2235            .build();
2236        let blocks = builder.blocks(15..=15);
2237        let authority_1_excluded_block_reference = blocks
2238            .iter()
2239            .find(|block| block.author() == AuthorityIndex::new_for_test(1))
2240            .unwrap()
2241            .reference();
2242        // Wait for min round delay to allow blocks to be proposed.
2243        sleep(min_round_delay).await;
2244        // Smart select should be triggered and no block should be proposed.
2245        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2246        assert_eq!(fixture.core.last_proposed_block().round(), 15);
2247
2248        builder
2249            .layer(15)
2250            .authorities(vec![
2251                AuthorityIndex::new_for_test(0),
2252                AuthorityIndex::new_for_test(1),
2253                AuthorityIndex::new_for_test(2),
2254                AuthorityIndex::new_for_test(3),
2255                AuthorityIndex::new_for_test(4),
2256            ])
2257            .skip_block()
2258            .override_last_ancestors(round_14_ancestors)
2259            .build();
2260        let blocks = builder.blocks(15..=15);
2261        let round_15_ancestors: Vec<BlockRef> = blocks
2262            .iter()
2263            .filter(|block| block.round() == 15)
2264            .map(|block| block.reference())
2265            .collect();
2266        let included_block_references = iter::once(&fixture.core.last_proposed_block())
2267            .chain(blocks.iter())
2268            .filter(|block| block.author() != AuthorityIndex::new_for_test(1))
2269            .map(|block| block.reference())
2270            .collect::<Vec<_>>();
2271
2272        // Have enough ancestor blocks to propose now.
2273        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2274        assert_eq!(fixture.core.last_proposed_block().round(), 16);
2275
2276        // Check that a new block has been proposed & signaled.
2277        let extended_block = loop {
2278            let extended_block =
2279                tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2280                    .await
2281                    .unwrap()
2282                    .unwrap();
2283            if extended_block.block.round() == 16 {
2284                break extended_block;
2285            }
2286        };
2287        assert_eq!(extended_block.block.round(), 16);
2288        assert_eq!(
2289            extended_block.block.author(),
2290            fixture.core.context.own_index
2291        );
2292        assert_eq!(extended_block.block.ancestors().len(), 6);
2293        assert_eq!(extended_block.block.ancestors(), included_block_references);
2294        assert_eq!(extended_block.excluded_ancestors.len(), 1);
2295        assert_eq!(
2296            extended_block.excluded_ancestors[0],
2297            authority_1_excluded_block_reference
2298        );
2299
2300        // Build blocks for a quorum of the network including the EXCLUDE ancestor
2301        // which will trigger smart select and we will not propose a block.
2302        // This time we will force propose by hitting the leader timeout after which
2303        // should cause us to include this EXCLUDE ancestor.
2304        builder
2305            .layer(16)
2306            .authorities(vec![
2307                AuthorityIndex::new_for_test(0),
2308                AuthorityIndex::new_for_test(5),
2309                AuthorityIndex::new_for_test(6),
2310            ])
2311            .skip_block()
2312            .override_last_ancestors(round_15_ancestors)
2313            .build();
2314        let blocks = builder.blocks(16..=16);
2315        // Wait for leader timeout to force blocks to be proposed.
2316        sleep(min_round_delay).await;
2317        // Smart select should be triggered and no block should be proposed.
2318        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2319        assert_eq!(fixture.core.last_proposed_block().round(), 16);
2320
2321        // Simulate a leader timeout and a force proposal where we will include
2322        // one EXCLUDE ancestor when we go to select ancestors for the next proposal
2323        let block = fixture.core.try_propose(true).expect("No error").unwrap();
2324        assert_eq!(block.round(), 17);
2325        assert_eq!(block.ancestors().len(), 5);
2326
2327        // Check that a new block has been proposed & signaled.
2328        let extended_block =
2329            tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2330                .await
2331                .unwrap()
2332                .unwrap();
2333        assert_eq!(extended_block.block.round(), 17);
2334        assert_eq!(
2335            extended_block.block.author(),
2336            fixture.core.context.own_index
2337        );
2338        assert_eq!(extended_block.block.ancestors().len(), 5);
2339        assert_eq!(extended_block.excluded_ancestors.len(), 0);
2340
2341        // Excluded authority is locked until round 20, simulate enough rounds to
2342        // unlock
2343        builder
2344            .layers(17..=22)
2345            .authorities(vec![AuthorityIndex::new_for_test(0)])
2346            .skip_block()
2347            .build();
2348        let blocks = builder.blocks(17..=22);
2349
2350        // Simulate updating received and accepted rounds from prober.
2351        // New quorum rounds for authority can then be computed which will unlock
2352        // the Excluded authority (1) and then we should be able to create a new
2353        // layer of blocks which will then all be included as ancestors for the
2354        // next proposal
2355        fixture
2356            .core
2357            .round_tracker_for_tests()
2358            .write()
2359            .update_from_probe(
2360                vec![
2361                    vec![22, 22, 22, 22, 22, 22, 22],
2362                    vec![22, 22, 22, 22, 22, 22, 22],
2363                    vec![22, 22, 22, 22, 22, 22, 22],
2364                    vec![22, 22, 22, 22, 22, 22, 22],
2365                    vec![22, 22, 22, 22, 22, 22, 22],
2366                    vec![22, 22, 22, 22, 22, 22, 22],
2367                    vec![22, 22, 22, 22, 22, 22, 22],
2368                ],
2369                vec![
2370                    vec![22, 22, 22, 22, 22, 22, 22],
2371                    vec![22, 22, 22, 22, 22, 22, 22],
2372                    vec![22, 22, 22, 22, 22, 22, 22],
2373                    vec![22, 22, 22, 22, 22, 22, 22],
2374                    vec![22, 22, 22, 22, 22, 22, 22],
2375                    vec![22, 22, 22, 22, 22, 22, 22],
2376                    vec![22, 22, 22, 22, 22, 22, 22],
2377                ],
2378            );
2379
2380        let own_index = fixture.core.context.own_index;
2381        let included_block_references = iter::once(&fixture.core.last_proposed_block())
2382            .chain(blocks.iter())
2383            .filter(|block| block.round() == 22 || block.author() == own_index)
2384            .map(|block| block.reference())
2385            .collect::<Vec<_>>();
2386
2387        // Have enough ancestor blocks to propose now.
2388        sleep(min_round_delay).await;
2389        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2390        assert_eq!(fixture.core.last_proposed_block().round(), 23);
2391
2392        // Check that a new block has been proposed & signaled.
2393        let extended_block =
2394            tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2395                .await
2396                .unwrap()
2397                .unwrap();
2398        assert_eq!(extended_block.block.round(), 23);
2399        assert_eq!(
2400            extended_block.block.author(),
2401            fixture.core.context.own_index
2402        );
2403        assert_eq!(extended_block.block.ancestors().len(), 7);
2404        assert_eq!(extended_block.block.ancestors(), included_block_references);
2405        assert_eq!(extended_block.excluded_ancestors.len(), 0);
2406    }
2407
2408    #[tokio::test]
2409    async fn test_excluded_ancestor_limit() {
2410        telemetry_subscribers::init_for_testing();
2411        let (context, _) = Context::new_for_test(4);
2412        let context = context.with_parameters(Parameters {
2413            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2414            ..Default::default()
2415        });
2416        let mut fixture = CoreTestFixture::new(
2417            context,
2418            vec![1, 1, 1, 1],
2419            AuthorityIndex::new_for_test(0),
2420            true,
2421        )
2422        .await;
2423
2424        // No new block should have been produced
2425        assert_eq!(
2426            fixture.core.last_proposed_round(),
2427            Some(GENESIS_ROUND),
2428            "No block should have been created other than genesis"
2429        );
2430
2431        // Create blocks for the whole network
2432        let mut builder = DagBuilder::new(fixture.core.context.clone());
2433        builder.layers(1..=3).build();
2434
2435        // This will equivocate 9 blocks for authority 1 which will be excluded on
2436        // the proposal but because of the limits set will be dropped and not included
2437        // as part of the ExtendedBlock structure sent to the rest of the network
2438        builder
2439            .layer(4)
2440            .authorities(vec![AuthorityIndex::new_for_test(1)])
2441            .equivocate(9)
2442            .build();
2443        let blocks = builder.blocks(1..=4);
2444
2445        // Process all the blocks
2446        assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2447        fixture.core.set_last_known_proposed_round(3);
2448
2449        let block = fixture.core.try_propose(true).expect("No error").unwrap();
2450        assert_eq!(block.round(), 5);
2451        assert_eq!(block.ancestors().len(), 4);
2452
2453        // Check that a new block has been proposed & signaled.
2454        let extended_block =
2455            tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2456                .await
2457                .unwrap()
2458                .unwrap();
2459        assert_eq!(extended_block.block.round(), 5);
2460        assert_eq!(
2461            extended_block.block.author(),
2462            fixture.core.context.own_index
2463        );
2464        assert_eq!(extended_block.block.ancestors().len(), 4);
2465        assert_eq!(extended_block.excluded_ancestors.len(), 8);
2466    }
2467
2468    #[tokio::test]
2469    async fn test_core_set_propagation_delay_per_authority() {
2470        telemetry_subscribers::init_for_testing();
2471        let (context, _) = Context::new_for_test(4);
2472        let mut fixture = CoreTestFixture::new(
2473            context,
2474            vec![1, 1, 1, 1],
2475            AuthorityIndex::new_for_test(0),
2476            false,
2477        )
2478        .await;
2479
2480        // Use a large propagation delay to disable proposing.
2481        // This is done by accepting an own block at round 1000 to dag state and
2482        // then simulating updating round tracker received rounds from probe where
2483        // low quorum round for own index should get calculated to round 0.
2484        let test_block = VerifiedBlock::new_for_test(TestBlock::new(1000, 0).build());
2485        fixture
2486            .transaction_vote_tracker
2487            .add_voted_blocks(vec![(test_block.clone(), vec![])]);
2488        // Force accepting the block to dag state because its causal history is incomplete.
2489        fixture.dag_state.write().accept_block(test_block);
2490
2491        fixture
2492            .core
2493            .round_tracker_for_tests()
2494            .write()
2495            .update_from_probe(
2496                vec![
2497                    vec![0, 0, 0, 0],
2498                    vec![0, 0, 0, 0],
2499                    vec![0, 0, 0, 0],
2500                    vec![0, 0, 0, 0],
2501                ],
2502                vec![
2503                    vec![0, 0, 0, 0],
2504                    vec![0, 0, 0, 0],
2505                    vec![0, 0, 0, 0],
2506                    vec![0, 0, 0, 0],
2507                ],
2508            );
2509
2510        // There is no proposal even with forced proposing.
2511        assert!(fixture.core.try_propose(true).unwrap().is_none());
2512
2513        // Let Core know there is no propagation delay.
2514        // This is done by simulating updating round tracker recieved rounds from probe
2515        // where low quorum round for own index should get calculated to round 1000.
2516        fixture
2517            .core
2518            .round_tracker_for_tests()
2519            .write()
2520            .update_from_probe(
2521                vec![
2522                    vec![1000, 1000, 1000, 1000],
2523                    vec![1000, 1000, 1000, 1000],
2524                    vec![1000, 1000, 1000, 1000],
2525                    vec![1000, 1000, 1000, 1000],
2526                ],
2527                vec![
2528                    vec![1000, 1000, 1000, 1000],
2529                    vec![1000, 1000, 1000, 1000],
2530                    vec![1000, 1000, 1000, 1000],
2531                    vec![1000, 1000, 1000, 1000],
2532                ],
2533            );
2534
2535        // Also add the necessary blocks from round 1000 so core will propose for
2536        // round 1001
2537        for author in 1..4 {
2538            let block = VerifiedBlock::new_for_test(TestBlock::new(1000, author).build());
2539            fixture
2540                .transaction_vote_tracker
2541                .add_voted_blocks(vec![(block.clone(), vec![])]);
2542            // Force accepting the block to dag state because its causal history is incomplete.
2543            fixture.dag_state.write().accept_block(block);
2544        }
2545
2546        // Proposing now would succeed.
2547        assert!(fixture.core.try_propose(true).unwrap().is_some());
2548    }
2549
2550    #[tokio::test(flavor = "current_thread", start_paused = true)]
2551    async fn test_leader_schedule_change() {
2552        telemetry_subscribers::init_for_testing();
2553        let default_params = Parameters::default();
2554
2555        let (context, _) = Context::new_for_test(4);
2556        // create the cores and their signals for all the authorities
2557        let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
2558
2559        // Now iterate over a few rounds and ensure the corresponding signals are created while network advances
2560        let mut last_round_blocks = Vec::new();
2561        for round in 1..=30 {
2562            let mut this_round_blocks = Vec::new();
2563
2564            // Wait for min round delay to allow blocks to be proposed.
2565            sleep(default_params.min_round_delay).await;
2566
2567            for core_fixture in &mut cores {
2568                // add the blocks from last round
2569                // this will trigger a block creation for the round and a signal should be emitted
2570                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2571
2572                core_fixture
2573                    .core
2574                    .round_tracker_for_tests()
2575                    .write()
2576                    .update_from_probe(
2577                        vec![
2578                            vec![round, round, round, round],
2579                            vec![round, round, round, round],
2580                            vec![round, round, round, round],
2581                            vec![round, round, round, round],
2582                        ],
2583                        vec![
2584                            vec![round, round, round, round],
2585                            vec![round, round, round, round],
2586                            vec![round, round, round, round],
2587                            vec![round, round, round, round],
2588                        ],
2589                    );
2590
2591                // A "new round" signal should be received given that all the blocks of previous round have been processed
2592                let new_round = receive(
2593                    Duration::from_secs(1),
2594                    core_fixture.signal_receivers.new_round_receiver(),
2595                )
2596                .await;
2597                assert_eq!(new_round, round);
2598
2599                // Check that a new block has been proposed.
2600                let extended_block = tokio::time::timeout(
2601                    Duration::from_secs(1),
2602                    core_fixture.block_receiver.recv(),
2603                )
2604                .await
2605                .unwrap()
2606                .unwrap();
2607                assert_eq!(extended_block.block.round(), round);
2608                assert_eq!(
2609                    extended_block.block.author(),
2610                    core_fixture.core.context.own_index
2611                );
2612
2613                // append the new block to this round blocks
2614                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2615
2616                let block = core_fixture.core.last_proposed_block();
2617
2618                // ensure that produced block is referring to the blocks of last_round
2619                assert_eq!(
2620                    block.ancestors().len(),
2621                    core_fixture.core.context.committee.size()
2622                );
2623                for ancestor in block.ancestors() {
2624                    if block.round() > 1 {
2625                        // don't bother with round 1 block which just contains the genesis blocks.
2626                        assert!(
2627                            last_round_blocks
2628                                .iter()
2629                                .any(|block| block.reference() == *ancestor),
2630                            "Reference from previous round should be added"
2631                        );
2632                    }
2633                }
2634            }
2635
2636            last_round_blocks = this_round_blocks;
2637        }
2638
2639        for core_fixture in cores {
2640            // Flush the DAG state to storage.
2641            core_fixture.dag_state.write().flush();
2642
2643            // Check commits have been persisted to store
2644            let last_commit = core_fixture
2645                .store
2646                .read_last_commit()
2647                .unwrap()
2648                .expect("last commit should be set");
2649            // There are 28 leader rounds with rounds completed up to and including
2650            // round 29. Round 30 blocks will only include their own blocks, so the
2651            // 28th leader will not be committed.
2652            assert_eq!(last_commit.index(), 27);
2653            let all_stored_commits = core_fixture
2654                .store
2655                .scan_commits((0..=CommitIndex::MAX).into())
2656                .unwrap();
2657            assert_eq!(all_stored_commits.len(), 27);
2658            assert_eq!(
2659                core_fixture
2660                    .core
2661                    .leader_schedule
2662                    .leader_swap_table
2663                    .read()
2664                    .bad_nodes
2665                    .len(),
2666                1
2667            );
2668            assert_eq!(
2669                core_fixture
2670                    .core
2671                    .leader_schedule
2672                    .leader_swap_table
2673                    .read()
2674                    .good_nodes
2675                    .len(),
2676                1
2677            );
2678            let expected_reputation_scores =
2679                ReputationScores::new((11..=20).into(), vec![29, 29, 29, 29]);
2680            assert_eq!(
2681                core_fixture
2682                    .core
2683                    .leader_schedule
2684                    .leader_swap_table
2685                    .read()
2686                    .reputation_scores,
2687                expected_reputation_scores
2688            );
2689        }
2690    }
2691
2692    #[tokio::test]
2693    async fn test_filter_new_commits() {
2694        telemetry_subscribers::init_for_testing();
2695
2696        let (context, _key_pairs) = Context::new_for_test(4);
2697        let context = context.with_parameters(Parameters {
2698            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2699            ..Default::default()
2700        });
2701
2702        let authority_index = AuthorityIndex::new_for_test(0);
2703        let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
2704        let mut core = core.core;
2705
2706        // No new block should have been produced
2707        assert_eq!(
2708            core.last_proposed_round(),
2709            Some(GENESIS_ROUND),
2710            "No block should have been created other than genesis"
2711        );
2712
2713        // create a DAG of 12 rounds
2714        let mut dag_builder = DagBuilder::new(core.context.clone());
2715        dag_builder.layers(1..=12).build();
2716
2717        // Store all blocks up to round 6 which should be enough to decide up to leader 4
2718        dag_builder.print();
2719        let blocks = dag_builder.blocks(1..=6);
2720        core.dag_state.write().accept_blocks(blocks);
2721
2722        // Get all the committed sub dags up to round 10
2723        let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
2724
2725        // Now try to commit up to the latest leader (round = 4). Do not provide any certified commits.
2726        let committed_sub_dags = core.try_commit(vec![]).unwrap();
2727
2728        // We should have committed up to round 4
2729        assert_eq!(committed_sub_dags.len(), 4);
2730
2731        // Now validate the certified commits. We'll try 3 different scenarios:
2732        println!("Case 1. Provide certified commits that are all before the last committed round.");
2733
2734        // Highest certified commit should be for leader of round 4.
2735        let certified_commits = sub_dags_and_commits
2736            .iter()
2737            .take(4)
2738            .map(|(_, c)| c)
2739            .cloned()
2740            .collect::<Vec<_>>();
2741        assert!(
2742            certified_commits.last().unwrap().index()
2743                <= committed_sub_dags.last().unwrap().commit_ref.index,
2744            "Highest certified commit should older than the highest committed index."
2745        );
2746
2747        let certified_commits = core.filter_new_commits(certified_commits).unwrap();
2748
2749        // No commits should be processed
2750        assert!(certified_commits.is_empty());
2751
2752        println!("Case 2. Provide certified commits that are all after the last committed round.");
2753
2754        // Highest certified commit should be for leader of round 4.
2755        let certified_commits = sub_dags_and_commits
2756            .iter()
2757            .take(5)
2758            .map(|(_, c)| c.clone())
2759            .collect::<Vec<_>>();
2760
2761        let certified_commits = core.filter_new_commits(certified_commits.clone()).unwrap();
2762
2763        // The certified commit of index 5 should be processed.
2764        assert_eq!(certified_commits.len(), 1);
2765        assert_eq!(certified_commits.first().unwrap().reference().index, 5);
2766
2767        println!(
2768            "Case 3. Provide certified commits where the first certified commit index is not the last_commited_index + 1."
2769        );
2770
2771        // Highest certified commit should be for leader of round 4.
2772        let certified_commits = sub_dags_and_commits
2773            .iter()
2774            .skip(5)
2775            .take(1)
2776            .map(|(_, c)| c.clone())
2777            .collect::<Vec<_>>();
2778
2779        let err = core
2780            .filter_new_commits(certified_commits.clone())
2781            .unwrap_err();
2782        match err {
2783            ConsensusError::UnexpectedCertifiedCommitIndex {
2784                expected_commit_index: 5,
2785                commit_index: 6,
2786            } => (),
2787            _ => panic!("Unexpected error: {:?}", err),
2788        }
2789    }
2790
2791    #[tokio::test]
2792    async fn test_add_certified_commits() {
2793        telemetry_subscribers::init_for_testing();
2794
2795        let (context, _key_pairs) = Context::new_for_test(4);
2796        let context = context.with_parameters(Parameters {
2797            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2798            ..Default::default()
2799        });
2800
2801        let authority_index = AuthorityIndex::new_for_test(0);
2802        let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
2803        let store = core.store.clone();
2804        let mut core = core.core;
2805
2806        // No new block should have been produced
2807        assert_eq!(
2808            core.last_proposed_round(),
2809            Some(GENESIS_ROUND),
2810            "No block should have been created other than genesis"
2811        );
2812
2813        // create a DAG of 12 rounds
2814        let mut dag_builder = DagBuilder::new(core.context.clone());
2815        dag_builder.layers(1..=12).build();
2816
2817        // Store all blocks up to round 6 which should be enough to decide up to leader 4
2818        dag_builder.print();
2819        let blocks = dag_builder.blocks(1..=6);
2820        core.dag_state.write().accept_blocks(blocks);
2821
2822        // Get all the committed sub dags up to round 10
2823        let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
2824
2825        // Now try to commit up to the latest leader (round = 4). Do not provide any certified commits.
2826        let committed_sub_dags = core.try_commit(vec![]).unwrap();
2827
2828        // We should have committed up to round 4
2829        assert_eq!(committed_sub_dags.len(), 4);
2830
2831        // Flush the DAG state to storage.
2832        core.dag_state.write().flush();
2833
2834        {
2835            println!("Case 1. Provide no certified commits. No commit should happen.");
2836            // Now try to commit up to the latest leader (round = 4). Do not provide any certified commits.
2837            let committed_sub_dags = core.try_commit(vec![]).unwrap();
2838            assert!(committed_sub_dags.is_empty());
2839            let last_commit = store
2840                .read_last_commit()
2841                .unwrap()
2842                .expect("Last commit should be set");
2843            assert_eq!(last_commit.reference().index, 4);
2844        }
2845
2846        println!(
2847            "Case 2. Provide certified commits that before and after the last committed round and also there are additional blocks so can run the direct decide rule as well."
2848        );
2849
2850        // The commits of leader rounds 5-8 should be committed via the certified commits.
2851        let certified_commits = sub_dags_and_commits
2852            .iter()
2853            .skip(3)
2854            .take(5)
2855            .map(|(_, c)| c.clone())
2856            .collect::<Vec<_>>();
2857
2858        // Now only add the blocks of rounds 7..=12.
2859        let blocks = dag_builder.blocks(7..=12);
2860        core.dag_state.write().accept_blocks(blocks);
2861
2862        // The corresponding blocks of the certified commits should be accepted and stored before linearizing and committing the DAG.
2863        core.add_certified_commits(CertifiedCommits::new(certified_commits.clone(), vec![]))
2864            .expect("Should not fail");
2865
2866        // Flush the DAG state to storage.
2867        core.dag_state.write().flush();
2868
2869        let commits = store.scan_commits((6..=10).into()).unwrap();
2870
2871        // We expect all the sub dags up to leader round 10 to be committed.
2872        assert_eq!(commits.len(), 5);
2873
2874        for i in 6..=10 {
2875            let commit = &commits[i - 6];
2876            assert_eq!(commit.reference().index, i as u32);
2877        }
2878    }
2879
2880    #[tokio::test]
2881    async fn try_commit_with_certified_commits_gced_blocks() {
2882        const GC_DEPTH: u32 = 3;
2883        telemetry_subscribers::init_for_testing();
2884
2885        let (mut context, mut key_pairs) = Context::new_for_test(5);
2886        context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
2887        let context = Arc::new(context.with_parameters(Parameters {
2888            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2889            ..Default::default()
2890        }));
2891
2892        let store = Arc::new(MemStore::new());
2893        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
2894
2895        let block_manager = BlockManager::new(context.clone(), dag_state.clone());
2896        let leader_schedule = Arc::new(
2897            LeaderSchedule::from_store(context.clone(), dag_state.clone())
2898                .with_num_commits_per_schedule(10),
2899        );
2900
2901        let (_transaction_client, tx_receiver, priority_tx_receiver) =
2902            TransactionClient::new(context.clone());
2903        let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
2904            tx_receiver,
2905            priority_tx_receiver,
2906            context.clone(),
2907        )));
2908        let (signals, signal_receivers) = CoreSignals::new(context.clone());
2909        let transaction_vote_tracker = TransactionVoteTracker::new(
2910            context.clone(),
2911            Arc::new(NoopBlockVerifier {}),
2912            dag_state.clone(),
2913        );
2914        // Need at least one subscriber to the block broadcast channel.
2915        let _block_receiver = signal_receivers.block_broadcast_receiver();
2916
2917        let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
2918        let commit_observer = CommitObserver::new(
2919            context.clone(),
2920            commit_consumer,
2921            dag_state.clone(),
2922            transaction_vote_tracker.clone(),
2923        )
2924        .await;
2925
2926        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
2927        let mut core = Core::new_validator(
2928            context.clone(),
2929            leader_schedule,
2930            transaction_pool,
2931            transaction_vote_tracker.clone(),
2932            block_manager,
2933            commit_observer,
2934            signals,
2935            key_pairs.remove(context.own_index.value()).1,
2936            dag_state.clone(),
2937            true,
2938            round_tracker,
2939        );
2940
2941        // No new block should have been produced
2942        assert_eq!(
2943            core.last_proposed_round(),
2944            Some(GENESIS_ROUND),
2945            "No block should have been created other than genesis"
2946        );
2947
2948        let dag_str = "DAG {
2949            Round 0 : { 5 },
2950            Round 1 : { * },
2951            Round 2 : { 
2952                A -> [-E1],
2953                B -> [-E1],
2954                C -> [-E1],
2955                D -> [-E1],
2956            },
2957            Round 3 : {
2958                A -> [*],
2959                B -> [*],
2960                C -> [*],
2961                D -> [*],
2962            },
2963            Round 4 : { 
2964                A -> [*],
2965                B -> [*],
2966                C -> [*],
2967                D -> [*],
2968            },
2969            Round 5 : { 
2970                A -> [*],
2971                B -> [*],
2972                C -> [*],
2973                D -> [*],
2974                E -> [A4, B4, C4, D4, E1]
2975            },
2976            Round 6 : { * },
2977            Round 7 : { * },
2978        }";
2979
2980        let (_, mut dag_builder) = parse_dag(dag_str).expect("Invalid dag");
2981        dag_builder.print();
2982
2983        // Now get all the committed sub dags from the DagBuilder
2984        let (_sub_dags, certified_commits): (Vec<_>, Vec<_>) = dag_builder
2985            .get_sub_dag_and_certified_commits(1..=5)
2986            .into_iter()
2987            .unzip();
2988
2989        // Now try to commit up to the latest leader (round = 5) with the provided certified commits. Not that we have not accepted any
2990        // blocks. That should happen during the commit process.
2991        let committed_sub_dags = core.try_commit(certified_commits).unwrap();
2992
2993        // We should have committed up to round 4
2994        assert_eq!(committed_sub_dags.len(), 4);
2995        for (index, committed_sub_dag) in committed_sub_dags.iter().enumerate() {
2996            assert_eq!(committed_sub_dag.commit_ref.index as usize, index + 1);
2997
2998            // ensure that block from E1 node has not been committed
2999            for block in committed_sub_dag.blocks.iter() {
3000                if block.round() == 1 && block.author() == AuthorityIndex::new_for_test(5) {
3001                    panic!("Did not expect to commit block E1");
3002                }
3003            }
3004        }
3005    }
3006
3007    #[tokio::test(flavor = "current_thread", start_paused = true)]
3008    async fn test_commit_on_leader_schedule_change_boundary_without_multileader() {
3009        parameterized_test_commit_on_leader_schedule_change_boundary(Some(1)).await;
3010    }
3011
3012    #[tokio::test(flavor = "current_thread", start_paused = true)]
3013    async fn test_commit_on_leader_schedule_change_boundary_with_multileader() {
3014        parameterized_test_commit_on_leader_schedule_change_boundary(None).await;
3015    }
3016
3017    async fn parameterized_test_commit_on_leader_schedule_change_boundary(
3018        num_leaders_per_round: Option<usize>,
3019    ) {
3020        telemetry_subscribers::init_for_testing();
3021        let default_params = Parameters::default();
3022
3023        let (mut context, _) = Context::new_for_test(6);
3024        context
3025            .protocol_config
3026            .set_num_leaders_per_round_for_testing(num_leaders_per_round);
3027        // create the cores and their signals for all the authorities
3028        let mut cores = create_cores(context, vec![1, 1, 1, 1, 1, 1]).await;
3029
3030        // Now iterate over a few rounds and ensure the corresponding signals are created while network advances
3031        let mut last_round_blocks: Vec<VerifiedBlock> = Vec::new();
3032        for round in 1..=33 {
3033            let mut this_round_blocks = Vec::new();
3034
3035            // Wait for min round delay to allow blocks to be proposed.
3036            sleep(default_params.min_round_delay).await;
3037
3038            for core_fixture in &mut cores {
3039                // add the blocks from last round
3040                // this will trigger a block creation for the round and a signal should be emitted
3041                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
3042
3043                core_fixture
3044                    .core
3045                    .round_tracker_for_tests()
3046                    .write()
3047                    .update_from_probe(
3048                        vec![
3049                            vec![round, round, round, round, round, round],
3050                            vec![round, round, round, round, round, round],
3051                            vec![round, round, round, round, round, round],
3052                            vec![round, round, round, round, round, round],
3053                            vec![round, round, round, round, round, round],
3054                            vec![round, round, round, round, round, round],
3055                        ],
3056                        vec![
3057                            vec![round, round, round, round, round, round],
3058                            vec![round, round, round, round, round, round],
3059                            vec![round, round, round, round, round, round],
3060                            vec![round, round, round, round, round, round],
3061                            vec![round, round, round, round, round, round],
3062                            vec![round, round, round, round, round, round],
3063                        ],
3064                    );
3065
3066                // A "new round" signal should be received given that all the blocks of previous round have been processed
3067                let new_round = receive(
3068                    Duration::from_secs(1),
3069                    core_fixture.signal_receivers.new_round_receiver(),
3070                )
3071                .await;
3072                assert_eq!(new_round, round);
3073
3074                // Check that a new block has been proposed.
3075                let extended_block = tokio::time::timeout(
3076                    Duration::from_secs(1),
3077                    core_fixture.block_receiver.recv(),
3078                )
3079                .await
3080                .unwrap()
3081                .unwrap();
3082                assert_eq!(extended_block.block.round(), round);
3083                assert_eq!(
3084                    extended_block.block.author(),
3085                    core_fixture.core.context.own_index
3086                );
3087
3088                // append the new block to this round blocks
3089                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
3090
3091                let block = core_fixture.core.last_proposed_block();
3092
3093                // ensure that produced block is referring to the blocks of last_round
3094                assert_eq!(
3095                    block.ancestors().len(),
3096                    core_fixture.core.context.committee.size()
3097                );
3098                for ancestor in block.ancestors() {
3099                    if block.round() > 1 {
3100                        // don't bother with round 1 block which just contains the genesis blocks.
3101                        assert!(
3102                            last_round_blocks
3103                                .iter()
3104                                .any(|block| block.reference() == *ancestor),
3105                            "Reference from previous round should be added"
3106                        );
3107                    }
3108                }
3109            }
3110
3111            last_round_blocks = this_round_blocks;
3112        }
3113
3114        for core_fixture in cores {
3115            // There are 31 leader rounds with rounds completed up to and including
3116            // round 33. Round 33 blocks will only include their own blocks, so there
3117            // should only be 30 commits.
3118            // However on a leader schedule change boundary its is possible for a
3119            // new leader to get selected for the same round if the leader elected
3120            // gets swapped allowing for multiple leaders to be committed at a round.
3121            // Meaning with multi leader per round explicitly set to 1 we will have 30,
3122            // otherwise 31.
3123            // NOTE: We used 31 leader rounds to specifically trigger the scenario
3124            // where the leader schedule boundary occurred AND we had a swap to a new
3125            // leader for the same round
3126            let expected_commit_count = match num_leaders_per_round {
3127                Some(1) => 30,
3128                _ => 31,
3129            };
3130
3131            // Flush the DAG state to storage.
3132            core_fixture.dag_state.write().flush();
3133
3134            // Check commits have been persisted to store
3135            let last_commit = core_fixture
3136                .store
3137                .read_last_commit()
3138                .unwrap()
3139                .expect("last commit should be set");
3140            assert_eq!(last_commit.index(), expected_commit_count);
3141            let all_stored_commits = core_fixture
3142                .store
3143                .scan_commits((0..=CommitIndex::MAX).into())
3144                .unwrap();
3145            assert_eq!(all_stored_commits.len(), expected_commit_count as usize);
3146            assert_eq!(
3147                core_fixture
3148                    .core
3149                    .leader_schedule
3150                    .leader_swap_table
3151                    .read()
3152                    .bad_nodes
3153                    .len(),
3154                1
3155            );
3156            assert_eq!(
3157                core_fixture
3158                    .core
3159                    .leader_schedule
3160                    .leader_swap_table
3161                    .read()
3162                    .good_nodes
3163                    .len(),
3164                1
3165            );
3166            let expected_reputation_scores =
3167                ReputationScores::new((21..=30).into(), vec![43, 43, 43, 43, 43, 43]);
3168            assert_eq!(
3169                core_fixture
3170                    .core
3171                    .leader_schedule
3172                    .leader_swap_table
3173                    .read()
3174                    .reputation_scores,
3175                expected_reputation_scores
3176            );
3177        }
3178    }
3179
3180    #[tokio::test]
3181    async fn test_core_signals() {
3182        telemetry_subscribers::init_for_testing();
3183        let default_params = Parameters::default();
3184
3185        let (context, _) = Context::new_for_test(4);
3186        // create the cores and their signals for all the authorities
3187        let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
3188
3189        // Now iterate over a few rounds and ensure the corresponding signals are created while network advances
3190        let mut last_round_blocks = Vec::new();
3191        for round in 1..=10 {
3192            let mut this_round_blocks = Vec::new();
3193
3194            // Wait for min round delay to allow blocks to be proposed.
3195            sleep(default_params.min_round_delay).await;
3196
3197            for core_fixture in &mut cores {
3198                // add the blocks from last round
3199                // this will trigger a block creation for the round and a signal should be emitted
3200                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
3201
3202                core_fixture
3203                    .core
3204                    .round_tracker_for_tests()
3205                    .write()
3206                    .update_from_probe(
3207                        vec![
3208                            vec![round, round, round, round],
3209                            vec![round, round, round, round],
3210                            vec![round, round, round, round],
3211                            vec![round, round, round, round],
3212                        ],
3213                        vec![
3214                            vec![round, round, round, round],
3215                            vec![round, round, round, round],
3216                            vec![round, round, round, round],
3217                            vec![round, round, round, round],
3218                        ],
3219                    );
3220
3221                // A "new round" signal should be received given that all the blocks of previous round have been processed
3222                let new_round = receive(
3223                    Duration::from_secs(1),
3224                    core_fixture.signal_receivers.new_round_receiver(),
3225                )
3226                .await;
3227                assert_eq!(new_round, round);
3228
3229                // Check that a new block has been proposed.
3230                let extended_block = tokio::time::timeout(
3231                    Duration::from_secs(1),
3232                    core_fixture.block_receiver.recv(),
3233                )
3234                .await
3235                .unwrap()
3236                .unwrap();
3237                assert_eq!(extended_block.block.round(), round);
3238                assert_eq!(
3239                    extended_block.block.author(),
3240                    core_fixture.core.context.own_index
3241                );
3242
3243                // append the new block to this round blocks
3244                this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
3245
3246                let block = core_fixture.core.last_proposed_block();
3247
3248                // ensure that produced block is referring to the blocks of last_round
3249                assert_eq!(
3250                    block.ancestors().len(),
3251                    core_fixture.core.context.committee.size()
3252                );
3253                for ancestor in block.ancestors() {
3254                    if block.round() > 1 {
3255                        // don't bother with round 1 block which just contains the genesis blocks.
3256                        assert!(
3257                            last_round_blocks
3258                                .iter()
3259                                .any(|block| block.reference() == *ancestor),
3260                            "Reference from previous round should be added"
3261                        );
3262                    }
3263                }
3264            }
3265
3266            last_round_blocks = this_round_blocks;
3267        }
3268
3269        for core_fixture in cores {
3270            // Flush the DAG state to storage.
3271            core_fixture.dag_state.write().flush();
3272            // Check commits have been persisted to store
3273            let last_commit = core_fixture
3274                .store
3275                .read_last_commit()
3276                .unwrap()
3277                .expect("last commit should be set");
3278            // There are 8 leader rounds with rounds completed up to and including
3279            // round 9. Round 10 blocks will only include their own blocks, so the
3280            // 8th leader will not be committed.
3281            assert_eq!(last_commit.index(), 7);
3282            let all_stored_commits = core_fixture
3283                .store
3284                .scan_commits((0..=CommitIndex::MAX).into())
3285                .unwrap();
3286            assert_eq!(all_stored_commits.len(), 7);
3287        }
3288    }
3289
3290    #[tokio::test]
3291    async fn test_core_compress_proposal_references() {
3292        telemetry_subscribers::init_for_testing();
3293        let default_params = Parameters::default();
3294
3295        let (context, _) = Context::new_for_test(4);
3296        // create the cores and their signals for all the authorities
3297        let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
3298
3299        let mut last_round_blocks = Vec::new();
3300        let mut all_blocks = Vec::new();
3301
3302        let excluded_authority = AuthorityIndex::new_for_test(3);
3303
3304        for round in 1..=10 {
3305            let mut this_round_blocks = Vec::new();
3306
3307            for core_fixture in &mut cores {
3308                // do not produce any block for authority 3
3309                if core_fixture.core.context.own_index == excluded_authority {
3310                    continue;
3311                }
3312
3313                // try to propose to ensure that we are covering the case where we miss the leader authority 3
3314                core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
3315                core_fixture
3316                    .core
3317                    .round_tracker_for_tests()
3318                    .write()
3319                    .update_from_probe(
3320                        vec![
3321                            vec![round, round, round, round],
3322                            vec![round, round, round, round],
3323                            vec![round, round, round, round],
3324                            vec![round, round, round, round],
3325                        ],
3326                        vec![
3327                            vec![round, round, round, round],
3328                            vec![round, round, round, round],
3329                            vec![round, round, round, round],
3330                            vec![round, round, round, round],
3331                        ],
3332                    );
3333                core_fixture.core.new_block(round, true).unwrap();
3334
3335                let block = core_fixture.core.last_proposed_block();
3336                assert_eq!(block.round(), round);
3337
3338                // append the new block to this round blocks
3339                this_round_blocks.push(block.clone());
3340            }
3341
3342            last_round_blocks = this_round_blocks.clone();
3343            all_blocks.extend(this_round_blocks);
3344        }
3345
3346        // Now send all the produced blocks to core of authority 3. It should produce a new block. If no compression would
3347        // be applied the we should expect all the previous blocks to be referenced from round 0..=10. However, since compression
3348        // is applied only the last round's (10) blocks should be referenced + the authority's block of round 0.
3349        let core_fixture = &mut cores[excluded_authority];
3350        // Wait for min round delay to allow blocks to be proposed.
3351        sleep(default_params.min_round_delay).await;
3352        // add blocks to trigger proposal.
3353        core_fixture.add_blocks(all_blocks).unwrap();
3354
3355        // Assert that a block has been created for round 11 and it references to blocks of round 10 for the other peers, and
3356        // to round 1 for its own block (created after recovery).
3357        let block = core_fixture.core.last_proposed_block();
3358        assert_eq!(block.round(), 11);
3359        assert_eq!(block.ancestors().len(), 4);
3360        for block_ref in block.ancestors() {
3361            if block_ref.author == excluded_authority {
3362                assert_eq!(block_ref.round, 1);
3363            } else {
3364                assert_eq!(block_ref.round, 10);
3365            }
3366        }
3367
3368        // Flush the DAG state to storage.
3369        core_fixture.dag_state.write().flush();
3370
3371        // Check commits have been persisted to store
3372        let last_commit = core_fixture
3373            .store
3374            .read_last_commit()
3375            .unwrap()
3376            .expect("last commit should be set");
3377        // There are 8 leader rounds with rounds completed up to and including
3378        // round 10. However because there were no blocks produced for authority 3
3379        // 2 leader rounds will be skipped.
3380        assert_eq!(last_commit.index(), 6);
3381        let all_stored_commits = core_fixture
3382            .store
3383            .scan_commits((0..=CommitIndex::MAX).into())
3384            .unwrap();
3385        assert_eq!(all_stored_commits.len(), 6);
3386    }
3387
3388    #[tokio::test]
3389    async fn try_select_certified_leaders() {
3390        // GIVEN
3391        telemetry_subscribers::init_for_testing();
3392
3393        let (context, _) = Context::new_for_test(4);
3394
3395        let authority_index = AuthorityIndex::new_for_test(0);
3396        let core =
3397            CoreTestFixture::new(context.clone(), vec![1, 1, 1, 1], authority_index, true).await;
3398        let mut core = core.core;
3399
3400        let mut dag_builder = DagBuilder::new(Arc::new(context.clone()));
3401        dag_builder.layers(1..=12).build();
3402
3403        let limit = 2;
3404
3405        let blocks = dag_builder.blocks(1..=12);
3406        core.dag_state.write().accept_blocks(blocks);
3407
3408        // WHEN
3409        let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=4);
3410        let mut certified_commits = sub_dags_and_commits
3411            .into_iter()
3412            .map(|(_, commit)| commit)
3413            .collect::<Vec<_>>();
3414
3415        let leaders = core.try_select_certified_leaders(&mut certified_commits, limit);
3416
3417        // THEN
3418        assert_eq!(leaders.len(), 2);
3419        assert_eq!(certified_commits.len(), 2);
3420    }
3421
3422    pub(crate) async fn receive<T: Copy>(timeout: Duration, mut receiver: watch::Receiver<T>) -> T {
3423        tokio::time::timeout(timeout, receiver.changed())
3424            .await
3425            .expect("Timeout while waiting to read from receiver")
3426            .expect("Signal receive channel shouldn't be closed");
3427        *receiver.borrow_and_update()
3428    }
3429}