1use 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 block_manager: BlockManager,
58 committer: Arc<UniversalCommitter>,
60 last_signaled_round: Round,
62 last_decided_leader: Slot,
67 leader_schedule: Arc<LeaderSchedule>,
70 leader_schedule_v3: Option<LeaderScheduleV3>,
72 commit_observer: CommitObserver,
75 signals: CoreSignals,
77 dag_state: Arc<RwLock<DagState>>,
79 proposer: Option<Box<dyn Proposer>>,
82}
83
84impl Core {
85 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 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 Some(0)
146 };
147
148 let ancestor_state_manager = AncestorStateManager::new(context.clone(), dag_state.clone());
149
150 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 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 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 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 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 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 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 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 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 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 #[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 self.try_commit(vec![])?;
389
390 self.try_propose(false)?;
392
393 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 #[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 let (_, missing_block_refs) = self.accept_blocks(votes);
429
430 self.try_commit(commits)?;
432
433 self.try_propose(false)?;
435
436 self.try_signal_new_round();
440
441 Ok(missing_block_refs)
442 }
443
444 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 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 fn try_signal_new_round(&mut self) {
478 let new_clock_round = self.dag_state.read().threshold_clock_round();
483 if new_clock_round <= self.last_signaled_round {
484 return;
485 }
486 self.signals.new_round(new_clock_round);
488 self.last_signaled_round = new_clock_round;
489
490 self.context
492 .metrics
493 .node_metrics
494 .threshold_clock_round
495 .set(new_clock_round as i64);
496 }
497
498 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 self.try_signal_new_round();
519 return result;
520 }
521 Ok(None)
522 }
523
524 fn filter_new_commits(
527 &mut self,
528 commits: Vec<CertifiedCommit>,
529 ) -> ConsensusResult<Vec<CertifiedCommit>> {
530 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 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 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 self.try_commit(vec![])?;
577 return Ok(Some(extended_block.block));
578 }
579 Ok(None)
580 }
581
582 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 loop {
614 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 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 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 let (decided_leaders, local) = if certified_leaders.is_empty() {
670 let mut decided_leaders = self.committer.try_decide(self.last_decided_leader);
672 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 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 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 let subdags = self
714 .commit_observer
715 .handle_commit(sequenced_leaders, local)?;
716
717 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 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 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 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 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 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 pub(crate) fn last_proposed_round(&self) -> Option<Round> {
793 self.proposer.as_ref().map(|p| p.last_proposed_round())
794 }
795
796 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 #[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 certified_commits.drain(..limit).collect::<Vec<_>>()
828 } else {
829 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 let leader = DecidedLeader::Commit(leader.clone(), false);
846 UniversalCommitter::update_metrics(&self.context, &leader, Decision::Certified);
847 (leader, commit)
848 })
849 .collect::<Vec<_>>()
850 }
851
852 #[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 #[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
872pub(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 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 pub(crate) fn new_block(&self, extended_block: ExtendedBlock) -> ConsensusResult<()> {
913 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 pub(crate) fn new_accepted_block(&self, block: VerifiedBlock) {
938 let _ = self.tx_accepted_block_broadcast.send(block);
940 }
941
942 pub(crate) fn new_round(&mut self, round_number: Round) {
945 let _ = self.new_round_sender.send_replace(round_number);
946 }
947}
948
949pub(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#[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 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 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 #[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 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 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 store
1180 .write(WriteBatch::default().blocks(all_blocks))
1181 .expect("Storage error");
1182
1183 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 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 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 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 let mut new_round = signal_receivers.new_round_receiver();
1237 assert_eq!(*new_round.borrow_and_update(), 5);
1238
1239 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 assert_eq!(ancestors.len(), 4);
1249 for ancestor in ancestors {
1250 assert_eq!(ancestor.round, 4);
1251 }
1252
1253 dag_state.write().flush();
1255
1256 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 while let Some(result) = block_status_subscriptions.next().await {
1270 let status = result.unwrap();
1271 assert!(matches!(status, BlockStatus::Sequenced(_)));
1272 }
1273 }
1274
1275 #[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 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 let authorities_to_skip = if round == 4 {
1291 context.committee.validity_threshold() as usize
1292 } else {
1293 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 let mut new_round = fixture.signal_receivers.new_round_receiver();
1319 assert_eq!(*new_round.borrow_and_update(), 5);
1320
1321 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 fixture.core.try_commit(vec![]).ok();
1342
1343 fixture.core.dag_state.write().flush();
1345
1346 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 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 if total >= 1_000 {
1398 break;
1399 }
1400 }
1401
1402 let extended_block = fixture
1404 .block_receiver
1405 .recv()
1406 .await
1407 .expect("A new block should have been created");
1408
1409 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 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 assert!(fixture.core.try_propose(false).unwrap().is_none());
1461 assert!(fixture.core.try_propose(true).unwrap().is_none());
1462
1463 fixture.dag_state.write().flush();
1465
1466 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 let block_1 = VerifiedBlock::new_for_test(TestBlock::new(1, 1).build());
1492 expected_ancestors.insert(block_1.reference());
1493 sleep(context.parameters.min_round_delay).await;
1495 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 assert!(core.try_propose(false).unwrap().is_none());
1503
1504 let block_2 = VerifiedBlock::new_for_test(TestBlock::new(1, 2).build());
1506 expected_ancestors.insert(block_2.reference());
1507 sleep(context.parameters.min_round_delay).await;
1509 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 dag_state.write().flush();
1563
1564 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 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 store
1630 .write(WriteBatch::default().blocks(dag_builder.blocks(1..=8)))
1631 .expect("Storage error");
1632
1633 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 dag_state.write().flush();
1657
1658 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 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 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 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 #[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 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 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 fixture.core.set_last_known_proposed_round(4);
1801
1802 let mut all_blocks = dag_builder.blocks(1..=11);
1808 all_blocks.sort_by_key(|b| b.round());
1809 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 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 assert!(fixture.core.try_propose(true).unwrap().is_none());
1848
1849 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 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 assert!(fixture.core.try_propose(true).unwrap().is_none());
1879
1880 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 async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
1904 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 let mut all_cores = create_cores(context, vec![1, 1, 1, 1]).await;
1919
1920 let (_last_core, cores) = all_cores.split_last_mut().unwrap();
1925
1926 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 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 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 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 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 core_fixture.dag_state.write().flush();
1976
1977 let last_commit = core_fixture
1979 .store
1980 .read_last_commit()
1981 .unwrap()
1982 .expect("last commit should be set");
1983 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 async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
2005 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 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 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 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 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 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 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 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 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_eq!(block.ancestors().len(), 4);
2134 } else {
2135 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 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 assert!(fixture.core.try_propose(true).unwrap().is_none());
2167
2168 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 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 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 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 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 sleep(min_round_delay).await;
2244 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 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2274 assert_eq!(fixture.core.last_proposed_block().round(), 16);
2275
2276 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 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 sleep(min_round_delay).await;
2317 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2319 assert_eq!(fixture.core.last_proposed_block().round(), 16);
2320
2321 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 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 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 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 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 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 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 let mut builder = DagBuilder::new(fixture.core.context.clone());
2433 builder.layers(1..=3).build();
2434
2435 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 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 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 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 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 assert!(fixture.core.try_propose(true).unwrap().is_none());
2512
2513 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 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 fixture.dag_state.write().accept_block(block);
2544 }
2545
2546 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 let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
2558
2559 let mut last_round_blocks = Vec::new();
2561 for round in 1..=30 {
2562 let mut this_round_blocks = Vec::new();
2563
2564 sleep(default_params.min_round_delay).await;
2566
2567 for core_fixture in &mut cores {
2568 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 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 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 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2615
2616 let block = core_fixture.core.last_proposed_block();
2617
2618 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 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 core_fixture.dag_state.write().flush();
2642
2643 let last_commit = core_fixture
2645 .store
2646 .read_last_commit()
2647 .unwrap()
2648 .expect("last commit should be set");
2649 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 assert_eq!(
2708 core.last_proposed_round(),
2709 Some(GENESIS_ROUND),
2710 "No block should have been created other than genesis"
2711 );
2712
2713 let mut dag_builder = DagBuilder::new(core.context.clone());
2715 dag_builder.layers(1..=12).build();
2716
2717 dag_builder.print();
2719 let blocks = dag_builder.blocks(1..=6);
2720 core.dag_state.write().accept_blocks(blocks);
2721
2722 let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
2724
2725 let committed_sub_dags = core.try_commit(vec![]).unwrap();
2727
2728 assert_eq!(committed_sub_dags.len(), 4);
2730
2731 println!("Case 1. Provide certified commits that are all before the last committed round.");
2733
2734 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 assert!(certified_commits.is_empty());
2751
2752 println!("Case 2. Provide certified commits that are all after the last committed round.");
2753
2754 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 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 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 assert_eq!(
2808 core.last_proposed_round(),
2809 Some(GENESIS_ROUND),
2810 "No block should have been created other than genesis"
2811 );
2812
2813 let mut dag_builder = DagBuilder::new(core.context.clone());
2815 dag_builder.layers(1..=12).build();
2816
2817 dag_builder.print();
2819 let blocks = dag_builder.blocks(1..=6);
2820 core.dag_state.write().accept_blocks(blocks);
2821
2822 let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
2824
2825 let committed_sub_dags = core.try_commit(vec![]).unwrap();
2827
2828 assert_eq!(committed_sub_dags.len(), 4);
2830
2831 core.dag_state.write().flush();
2833
2834 {
2835 println!("Case 1. Provide no certified commits. No commit should happen.");
2836 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 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 let blocks = dag_builder.blocks(7..=12);
2860 core.dag_state.write().accept_blocks(blocks);
2861
2862 core.add_certified_commits(CertifiedCommits::new(certified_commits.clone(), vec![]))
2864 .expect("Should not fail");
2865
2866 core.dag_state.write().flush();
2868
2869 let commits = store.scan_commits((6..=10).into()).unwrap();
2870
2871 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 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 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 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 let committed_sub_dags = core.try_commit(certified_commits).unwrap();
2992
2993 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 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 let mut cores = create_cores(context, vec![1, 1, 1, 1, 1, 1]).await;
3029
3030 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 sleep(default_params.min_round_delay).await;
3037
3038 for core_fixture in &mut cores {
3039 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 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 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 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
3090
3091 let block = core_fixture.core.last_proposed_block();
3092
3093 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 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 let expected_commit_count = match num_leaders_per_round {
3127 Some(1) => 30,
3128 _ => 31,
3129 };
3130
3131 core_fixture.dag_state.write().flush();
3133
3134 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 let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
3188
3189 let mut last_round_blocks = Vec::new();
3191 for round in 1..=10 {
3192 let mut this_round_blocks = Vec::new();
3193
3194 sleep(default_params.min_round_delay).await;
3196
3197 for core_fixture in &mut cores {
3198 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 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 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 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
3245
3246 let block = core_fixture.core.last_proposed_block();
3247
3248 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 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 core_fixture.dag_state.write().flush();
3272 let last_commit = core_fixture
3274 .store
3275 .read_last_commit()
3276 .unwrap()
3277 .expect("last commit should be set");
3278 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 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 if core_fixture.core.context.own_index == excluded_authority {
3310 continue;
3311 }
3312
3313 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 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 let core_fixture = &mut cores[excluded_authority];
3350 sleep(default_params.min_round_delay).await;
3352 core_fixture.add_blocks(all_blocks).unwrap();
3354
3355 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 core_fixture.dag_state.write().flush();
3370
3371 let last_commit = core_fixture
3373 .store
3374 .read_last_commit()
3375 .unwrap()
3376 .expect("last commit should be set");
3377 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 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 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 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}