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 TrustedCommit,
37 },
38 commit_observer::CommitObserver,
39 context::Context,
40 dag_state::DagState,
41 error::{ConsensusError, ConsensusResult},
42 flex_committer::FlexCommitter,
43 leader_schedule::LeaderSchedule,
44 leader_schedule_v3::LeaderScheduleV3,
45 leader_scoring::ReputationScores,
46 proposer::{ProposalLeaderWaiter, Proposer, ValidatorProposer},
47 round_tracker::RoundTracker,
48 transaction::TransactionPool,
49 transaction_vote_tracker::TransactionVoteTracker,
50 universal_committer::{
51 UniversalCommitter, universal_committer_builder::UniversalCommitterBuilder,
52 },
53};
54
55pub(crate) struct Core {
56 context: Arc<Context>,
57 block_manager: BlockManager,
60 committer: Arc<UniversalCommitter>,
62 last_signaled_round: Round,
64 last_decided_leader: Slot,
69 leader_schedule: Arc<LeaderSchedule>,
72 leader_schedule_v3: Option<LeaderScheduleV3>,
74 commit_observer: CommitObserver,
77 flex_committer: Option<FlexCommitter>,
81 signals: CoreSignals,
83 dag_state: Arc<RwLock<DagState>>,
85 proposer: Option<Box<dyn Proposer>>,
88}
89
90impl Core {
91 pub(crate) fn new_validator(
93 context: Arc<Context>,
94 leader_schedule: Arc<LeaderSchedule>,
95 transaction_pool: Arc<dyn TransactionPool>,
96 transaction_vote_tracker: TransactionVoteTracker,
97 block_manager: BlockManager,
98 commit_observer: CommitObserver,
99 signals: CoreSignals,
100 block_signer: ProtocolKeyPair,
101 dag_state: Arc<RwLock<DagState>>,
102 sync_last_known_own_block: bool,
103 round_tracker: Arc<RwLock<RoundTracker>>,
104 ) -> Self {
105 let last_decided_leader = dag_state.read().last_commit_leader();
106 let number_of_leaders = context.protocol_config.num_leaders_per_round().unwrap_or(1);
107
108 let leader_schedule_v3 = if context.protocol_config.enable_v3() {
109 Some(LeaderScheduleV3::from_store(
110 context.clone(),
111 dag_state.clone(),
112 ))
113 } else {
114 None
115 };
116
117 let flex_committer = if context.protocol_config.enable_v3() {
118 Some(FlexCommitter::new(context.clone(), dag_state.clone()))
119 } else {
120 None
121 };
122
123 let committer = Arc::new(
124 UniversalCommitterBuilder::new(
125 context.clone(),
126 leader_schedule.clone(),
127 dag_state.clone(),
128 )
129 .with_number_of_leaders(number_of_leaders)
130 .with_pipeline(true)
131 .build(),
132 );
133
134 let last_proposed_block = dag_state
135 .read()
136 .get_last_proposed_block()
137 .expect("A block should have been returned");
138 let last_signaled_round = last_proposed_block.round();
139
140 let mut last_included_ancestors = vec![None; context.committee.size()];
149 for ancestor in last_proposed_block.ancestors() {
150 last_included_ancestors[ancestor.author] = Some(*ancestor);
151 }
152
153 let last_known_proposed_round = if sync_last_known_own_block {
154 None
155 } else {
156 Some(0)
158 };
159
160 let ancestor_state_manager = AncestorStateManager::new(context.clone(), dag_state.clone());
161
162 let leader_waiter = if let Some(schedule) = leader_schedule_v3.as_ref() {
164 let next_commit_leader_schedule = schedule.next_commit_leader_schedule();
165 info!(
166 "Recovered next commit leaders: index={} min_round={} num={} allowed={:?}",
167 next_commit_leader_schedule.next_commit_index,
168 next_commit_leader_schedule.min_next_leader_round,
169 next_commit_leader_schedule.num_leaders(),
170 next_commit_leader_schedule.allowed_leaders,
171 );
172 ProposalLeaderWaiter::V3(next_commit_leader_schedule)
173 } else {
174 ProposalLeaderWaiter::V2(committer.clone())
175 };
176 let proposer = Some(Box::new(ValidatorProposer::new(
177 dag_state.clone(),
178 context.clone(),
179 transaction_pool,
180 transaction_vote_tracker.clone(),
181 block_signer,
182 last_known_proposed_round,
183 ancestor_state_manager,
184 round_tracker.clone(),
185 leader_waiter,
186 )) as Box<dyn Proposer>);
187
188 let mut core = Self {
189 context,
190 last_signaled_round,
191 last_decided_leader,
192 leader_schedule,
193 leader_schedule_v3,
194 block_manager,
195 committer,
196 commit_observer,
197 flex_committer,
198 signals,
199 dag_state,
200 proposer,
201 };
202
203 let propagation_scores = core.current_reputation_scores();
205 core.proposer
206 .as_mut()
207 .unwrap()
208 .set_propagation_scores(propagation_scores);
209
210 core.recover_validator()
211 }
212
213 pub(crate) fn new_observer(
215 context: Arc<Context>,
216 leader_schedule: Arc<LeaderSchedule>,
217 block_manager: BlockManager,
218 commit_observer: CommitObserver,
219 signals: CoreSignals,
220 dag_state: Arc<RwLock<DagState>>,
221 ) -> Self {
222 let last_decided_leader = dag_state.read().last_commit_leader();
223 let number_of_leaders = context.protocol_config.num_leaders_per_round().unwrap_or(1);
224
225 let leader_schedule_v3 = if context.protocol_config.enable_v3() {
226 Some(LeaderScheduleV3::from_store(
227 context.clone(),
228 dag_state.clone(),
229 ))
230 } else {
231 None
232 };
233
234 let flex_committer = if context.protocol_config.enable_v3() {
235 Some(FlexCommitter::new(context.clone(), dag_state.clone()))
236 } else {
237 None
238 };
239
240 let committer = Arc::new(
241 UniversalCommitterBuilder::new(
242 context.clone(),
243 leader_schedule.clone(),
244 dag_state.clone(),
245 )
246 .with_number_of_leaders(number_of_leaders)
247 .with_pipeline(true)
248 .build(),
249 );
250
251 let last_signaled_round = dag_state.read().threshold_clock_round();
253
254 Self {
255 context,
256 last_signaled_round,
257 last_decided_leader,
258 leader_schedule,
259 leader_schedule_v3,
260 block_manager,
261 committer,
262 commit_observer,
263 flex_committer,
264 signals,
265 dag_state,
266 proposer: None,
267 }
268 .recover_observer()
269 }
270
271 fn recover_validator(mut self) -> Self {
272 let _s = self
273 .context
274 .metrics
275 .node_metrics
276 .scope_processing_time
277 .with_label_values(&["Core::recover_validator"])
278 .start_timer();
279
280 self.try_commit_local().unwrap();
282
283 let last_proposed_block = if let Some(last_proposed_block) = self.try_propose(true).unwrap()
284 {
285 last_proposed_block
286 } else {
287 let proposer = self
288 .proposer
289 .as_ref()
290 .expect("Validator must have proposer");
291 let last_proposed_block = proposer.last_proposed_block();
292
293 if proposer.should_propose() {
294 assert!(
295 last_proposed_block.round() > GENESIS_ROUND,
296 "At minimum a block of round higher than genesis should have been produced during recovery"
297 );
298 }
299
300 self.signals
302 .new_block(ExtendedBlock {
303 block: last_proposed_block.clone(),
304 excluded_ancestors: vec![],
305 })
306 .unwrap();
307 last_proposed_block
308 };
309
310 self.try_signal_new_round();
314
315 info!(
316 "Core recovery for validator completed with last proposed block {:?}",
317 last_proposed_block
318 );
319
320 self
321 }
322
323 fn recover_observer(mut self) -> Self {
324 let _s = self
325 .context
326 .metrics
327 .node_metrics
328 .scope_processing_time
329 .with_label_values(&["Core::recover_observer"])
330 .start_timer();
331
332 self.try_commit_local().unwrap();
334
335 self.try_signal_new_round();
336
337 self
338 }
339
340 pub(crate) async fn stop(&mut self) {
341 self.commit_observer.stop().await;
342 }
343
344 fn accept_blocks(
347 &mut self,
348 blocks: Vec<VerifiedBlock>,
349 ) -> (Vec<VerifiedBlock>, BTreeSet<BlockRef>) {
350 let (accepted_blocks, missing_block_refs) = self.block_manager.try_accept_blocks(blocks);
351 for block in &accepted_blocks {
352 tracing::trace!(
353 "{} Core accepted round {}, author {} at timestamp: {}",
354 self.context.own_index,
355 block.round(),
356 block.author(),
357 block.timestamp_ms()
358 );
359 self.signals.new_accepted_block(block.clone());
360 }
361 (accepted_blocks, missing_block_refs)
362 }
363
364 fn accept_committed_blocks(&mut self, blocks: Vec<VerifiedBlock>) -> Vec<VerifiedBlock> {
367 let accepted_blocks = self.block_manager.try_accept_committed_blocks(blocks);
368 for block in &accepted_blocks {
369 self.signals.new_accepted_block(block.clone());
370 }
371 #[cfg(test)]
372 sui_macros::handle_fail_point("consensus-after-accept-certified-blocks");
373 accepted_blocks
374 }
375
376 #[tracing::instrument(skip_all)]
380 pub(crate) fn add_blocks(
381 &mut self,
382 blocks: Vec<VerifiedBlock>,
383 ) -> ConsensusResult<BTreeSet<BlockRef>> {
384 let _scope = monitored_scope("Core::add_blocks");
385 let _s = self
386 .context
387 .metrics
388 .node_metrics
389 .scope_processing_time
390 .with_label_values(&["Core::add_blocks"])
391 .start_timer();
392 self.context
393 .metrics
394 .node_metrics
395 .core_add_blocks_batch_size
396 .observe(blocks.len() as f64);
397
398 let (accepted_blocks, missing_block_refs) = self.accept_blocks(blocks);
399
400 if !accepted_blocks.is_empty() {
401 trace!(
402 "Accepted blocks: {}",
403 accepted_blocks
404 .iter()
405 .map(|b| b.reference().to_string())
406 .join(",")
407 );
408
409 self.try_commit_local()?;
411
412 self.try_propose(false)?;
414
415 self.try_signal_new_round();
419 };
420
421 if !missing_block_refs.is_empty() {
422 trace!(
423 "Missing block refs: {}",
424 missing_block_refs.iter().map(|b| b.to_string()).join(", ")
425 );
426 }
427
428 Ok(missing_block_refs)
429 }
430
431 #[tracing::instrument(skip_all)]
436 pub(crate) fn add_certified_commits(
437 &mut self,
438 certified_commits: CertifiedCommits,
439 ) -> ConsensusResult<BTreeSet<BlockRef>> {
440 let _scope = monitored_scope("Core::add_certified_commits");
441
442 let votes = certified_commits.votes().to_vec();
443 let commits = self
444 .filter_new_commits(certified_commits.commits().to_vec())
445 .expect("Certified commits validation failed");
446
447 let (_, missing_block_refs) = self.accept_blocks(votes);
451
452 if self.context.protocol_config.enable_v3() {
453 self.process_certified_commits(commits)?;
454 self.try_commit_v3()?;
455 } else {
456 self.try_commit(commits)?;
457 }
458
459 self.try_propose(false)?;
461
462 self.try_signal_new_round();
466
467 Ok(missing_block_refs)
468 }
469
470 pub(crate) fn check_block_refs(
473 &mut self,
474 block_refs: Vec<BlockRef>,
475 ) -> ConsensusResult<BTreeSet<BlockRef>> {
476 let _scope = monitored_scope("Core::check_block_refs");
477 let _s = self
478 .context
479 .metrics
480 .node_metrics
481 .scope_processing_time
482 .with_label_values(&["Core::check_block_refs"])
483 .start_timer();
484 self.context
485 .metrics
486 .node_metrics
487 .core_check_block_refs_batch_size
488 .observe(block_refs.len() as f64);
489
490 let missing_block_refs = self.block_manager.try_find_blocks(block_refs);
492
493 if !missing_block_refs.is_empty() {
494 trace!(
495 "Missing block refs: {}",
496 missing_block_refs.iter().map(|b| b.to_string()).join(", ")
497 );
498 }
499 Ok(missing_block_refs)
500 }
501
502 fn try_signal_new_round(&mut self) {
504 let new_clock_round = self.dag_state.read().threshold_clock_round();
509 if new_clock_round <= self.last_signaled_round {
510 return;
511 }
512 self.signals.new_round(new_clock_round);
514 self.last_signaled_round = new_clock_round;
515
516 self.context
518 .metrics
519 .node_metrics
520 .threshold_clock_round
521 .set(new_clock_round as i64);
522 }
523
524 pub(crate) fn new_block(
528 &mut self,
529 round: Round,
530 force: bool,
531 ) -> ConsensusResult<Option<VerifiedBlock>> {
532 let _scope = monitored_scope("Core::new_block");
533 if let Some(last_round) = self.last_proposed_round()
534 && last_round < round
535 {
536 self.context
537 .metrics
538 .node_metrics
539 .leader_timeout_total
540 .with_label_values(&[&format!("{force}")])
541 .inc();
542 let result = self.try_propose(force);
543 self.try_signal_new_round();
545 return result;
546 }
547 Ok(None)
548 }
549
550 fn filter_new_commits(
553 &mut self,
554 commits: Vec<CertifiedCommit>,
555 ) -> ConsensusResult<Vec<CertifiedCommit>> {
556 let last_commit_index = self.dag_state.read().last_commit_index();
558 let commits = commits
559 .iter()
560 .filter(|commit| {
561 if commit.index() > last_commit_index {
562 true
563 } else {
564 self.context
565 .metrics
566 .node_metrics
567 .core_certified_commits_processed
568 .with_label_values(&["skipped"])
569 .inc();
570 tracing::debug!(
571 "Skip commit for index {} as it is already committed with last commit index {}",
572 commit.index(),
573 last_commit_index
574 );
575 false
576 }
577 })
578 .cloned()
579 .collect::<Vec<_>>();
580
581 if let Some(commit) = commits.first()
583 && commit.index() != last_commit_index + 1
584 {
585 return Err(ConsensusError::UnexpectedCertifiedCommitIndex {
586 expected_commit_index: last_commit_index + 1,
587 commit_index: commit.index(),
588 });
589 }
590
591 Ok(commits)
592 }
593
594 fn try_propose(&mut self, force: bool) -> ConsensusResult<Option<VerifiedBlock>> {
598 if let Some(proposer) = &mut self.proposer
599 && let Some(extended_block) = proposer.try_new_block(force)
600 {
601 self.signals.new_block(extended_block.clone())?;
602 self.signals
603 .new_accepted_block(extended_block.block.clone());
604
605 fail_point!("consensus-after-propose");
606
607 self.try_commit_local()?;
609 return Ok(Some(extended_block.block));
610 }
611 Ok(None)
612 }
613
614 fn try_commit(
617 &mut self,
618 mut certified_commits: Vec<CertifiedCommit>,
619 ) -> ConsensusResult<Vec<CommittedSubDag>> {
620 let _s = self
621 .context
622 .metrics
623 .node_metrics
624 .scope_processing_time
625 .with_label_values(&["Core::try_commit"])
626 .start_timer();
627
628 let mut certified_commits_map = BTreeMap::new();
629 for c in &certified_commits {
630 certified_commits_map.insert(c.index(), c.reference());
631 }
632
633 if !certified_commits.is_empty() {
634 info!(
635 "Processing synced commits: {:?}",
636 certified_commits
637 .iter()
638 .map(|c| (c.index(), c.leader()))
639 .collect::<Vec<_>>()
640 );
641 }
642
643 let mut committed_sub_dags = Vec::new();
644 loop {
646 let mut commits_until_update = self
651 .leader_schedule
652 .commits_until_leader_schedule_update(self.dag_state.clone());
653
654 if commits_until_update == 0 {
655 let last_commit_index = self.dag_state.read().last_commit_index();
656
657 tracing::info!(
658 "Leader schedule change triggered at commit index {last_commit_index}"
659 );
660
661 self.leader_schedule
662 .update_leader_schedule_v2(&self.dag_state);
663
664 let propagation_scores = self.current_reputation_scores();
665 if let Some(proposer) = &mut self.proposer {
666 proposer.set_propagation_scores(propagation_scores);
667 }
668
669 commits_until_update = self
670 .leader_schedule
671 .commits_until_leader_schedule_update(self.dag_state.clone());
672
673 fail_point!("consensus-after-leader-schedule-change");
674 }
675 assert!(commits_until_update > 0);
676
677 let (certified_leaders, decided_certified_commits): (
680 Vec<DecidedLeader>,
681 Vec<CertifiedCommit>,
682 ) = self
683 .try_select_certified_leaders(&mut certified_commits, commits_until_update)
684 .into_iter()
685 .unzip();
686
687 if !decided_certified_commits.is_empty() {
691 self.context
692 .metrics
693 .node_metrics
694 .core_certified_commits_processed
695 .with_label_values(&["committed"])
696 .inc_by(decided_certified_commits.len() as u64);
697 }
698
699 let (decided_leaders, local) = if certified_leaders.is_empty() {
701 let mut decided_leaders = self.committer.try_decide(self.last_decided_leader);
703 if decided_leaders.len() >= commits_until_update {
705 let _ = decided_leaders.split_off(commits_until_update);
706 }
707 (decided_leaders, true)
708 } else {
709 (certified_leaders, false)
710 };
711
712 let Some(last_decided) = decided_leaders.last().cloned() else {
714 break;
715 };
716
717 self.last_decided_leader = last_decided.slot();
718 self.context
719 .metrics
720 .node_metrics
721 .last_decided_leader_round
722 .set(self.last_decided_leader.round as i64);
723
724 let sequenced_leaders = decided_leaders
725 .into_iter()
726 .filter_map(|leader| leader.into_committed_block())
727 .collect::<Vec<_>>();
728 if sequenced_leaders.is_empty() {
731 break;
732 }
733 tracing::info!(
734 "Committing {} leaders: {}; {} commits before next leader schedule change",
735 sequenced_leaders.len(),
736 sequenced_leaders
737 .iter()
738 .map(|b| b.reference().to_string())
739 .join(","),
740 commits_until_update,
741 );
742
743 let subdags = if local {
745 self.commit_observer
746 .handle_committed_leaders(sequenced_leaders, true)?
747 } else {
748 let mut subdags = Vec::with_capacity(sequenced_leaders.len());
749 for (leader, certified_commit) in sequenced_leaders
750 .into_iter()
751 .zip_eq(decided_certified_commits)
752 {
753 self.accept_committed_blocks(certified_commit.blocks().to_vec());
757 subdags.extend(
758 self.commit_observer
759 .handle_committed_leaders(vec![leader], false)?,
760 );
761 }
762 subdags
763 };
764
765 self.block_manager
767 .try_unsuspend_blocks_for_latest_gc_round();
768
769 committed_sub_dags.extend(subdags);
770
771 fail_point!("consensus-after-handle-commit");
772 }
773
774 for sub_dag in &committed_sub_dags {
776 if let Some(commit_ref) = certified_commits_map.remove(&sub_dag.commit_ref.index) {
777 assert_eq!(
778 commit_ref, sub_dag.commit_ref,
779 "Certified commit has different reference than the committed sub dag"
780 );
781 }
782 }
783
784 let committed_block_refs = committed_sub_dags
786 .iter()
787 .flat_map(|sub_dag| sub_dag.blocks.iter())
788 .filter_map(|block| {
789 (block.author() == self.context.own_index).then_some(block.reference())
790 })
791 .collect::<Vec<_>>();
792 if let Some(proposer) = &self.proposer {
793 proposer.notify_own_blocks_committed(
794 committed_block_refs,
795 self.dag_state.read().gc_round(),
796 );
797 }
798
799 Ok(committed_sub_dags)
800 }
801
802 fn process_certified_commits(
809 &mut self,
810 certified_commits: Vec<CertifiedCommit>,
811 ) -> ConsensusResult<Vec<CommittedSubDag>> {
812 if certified_commits.is_empty() {
813 return Ok(vec![]);
814 }
815
816 let _s = self
817 .context
818 .metrics
819 .node_metrics
820 .scope_processing_time
821 .with_label_values(&["Core::process_certified_commits"])
822 .start_timer();
823 info!(
824 "Processing certified commits: {:?}",
825 certified_commits
826 .iter()
827 .map(|c| (c.index(), c.leader()))
828 .collect::<Vec<_>>()
829 );
830
831 let num_certified = certified_commits.len();
832 let mut subdags = Vec::with_capacity(num_certified);
833 for cert in certified_commits {
834 self.accept_committed_blocks(cert.blocks().to_vec());
839
840 let commit: TrustedCommit = (*cert).clone();
841
842 assert_eq!(
846 commit.previous_digest(),
847 self.dag_state.read().last_commit_digest(),
848 "Certified commit {} does not chain onto the last local commit",
849 commit.reference(),
850 );
851
852 let subdag = self
855 .flex_committer
856 .as_ref()
857 .expect("FlexCommitter must be initialized on the v3 path")
858 .handle_certified_commit(&commit);
859
860 self.post_commit(commit, subdag.clone())?;
861 subdags.push(subdag);
862
863 fail_point!("consensus-after-handle-commit");
864 }
865
866 self.context
867 .metrics
868 .node_metrics
869 .core_certified_commits_processed
870 .with_label_values(&["committed"])
871 .inc_by(num_certified as u64);
872
873 Ok(subdags)
874 }
875
876 fn try_commit_local(&mut self) -> ConsensusResult<Vec<CommittedSubDag>> {
879 if self.context.protocol_config.enable_v3() {
880 self.try_commit_v3()
881 } else {
882 self.try_commit(vec![])
883 }
884 }
885
886 fn try_commit_v3(&mut self) -> ConsensusResult<Vec<CommittedSubDag>> {
892 let _s = self
896 .context
897 .metrics
898 .node_metrics
899 .scope_processing_time
900 .with_label_values(&["Core::try_commit"])
901 .start_timer();
902
903 let mut committed_sub_dags = Vec::new();
904 loop {
905 let next_commit_leaders = self
906 .leader_schedule_v3
907 .as_ref()
908 .expect("LeaderScheduleV3 must be initialized on the v3 path")
909 .next_commit_leader_schedule();
910 let Some((commit, subdag)) = self
911 .flex_committer
912 .as_mut()
913 .expect("FlexCommitter must be initialized on the v3 path")
914 .try_commit(next_commit_leaders)
915 else {
916 break;
917 };
918
919 self.post_commit(commit, subdag.clone())?;
920 committed_sub_dags.push(subdag);
921
922 fail_point!("consensus-after-handle-commit");
923 }
924
925 Ok(committed_sub_dags)
926 }
927
928 fn post_commit(
933 &mut self,
934 commit: TrustedCommit,
935 subdag: CommittedSubDag,
936 ) -> ConsensusResult<()> {
937 self.dag_state.write().add_commit(commit);
938
939 self.commit_observer.report_commit_metrics(&subdag);
940 self.commit_observer.send_to_finalizer(subdag.clone())?;
941
942 self.last_decided_leader = subdag.leader.into();
943 self.context
944 .metrics
945 .node_metrics
946 .last_decided_leader_round
947 .set(self.last_decided_leader.round as i64);
948
949 self.block_manager
950 .try_unsuspend_blocks_for_latest_gc_round();
951
952 let committed_block_refs = subdag
953 .blocks
954 .iter()
955 .filter_map(|block| {
956 (block.author() == self.context.own_index).then_some(block.reference())
957 })
958 .collect::<Vec<_>>();
959 if let Some(proposer) = &self.proposer {
960 proposer.notify_own_blocks_committed(
961 committed_block_refs,
962 self.dag_state.read().gc_round(),
963 );
964 }
965
966 if let Some(schedule) = self.leader_schedule_v3.as_mut() {
967 schedule.add_commit(subdag);
968 let next = schedule.next_commit_leader_schedule();
969 debug!(
970 "Next v3 commit leaders: index={} min_round={} num={} allowed={:?}",
971 next.next_commit_index,
972 next.min_next_leader_round,
973 next.num_leaders(),
974 next.allowed_leaders,
975 );
976 if let Some(proposer) = self.proposer.as_mut() {
980 proposer.set_next_commit_leader_schedule(next);
981 }
982 }
983
984 let propagation_scores = self.current_reputation_scores();
989 if let Some(proposer) = &mut self.proposer {
990 proposer.set_propagation_scores(propagation_scores);
991 }
992
993 Ok(())
994 }
995
996 pub(crate) fn get_missing_blocks(&self) -> BTreeSet<BlockRef> {
997 let _scope = monitored_scope("Core::get_missing_blocks");
998 self.block_manager.missing_blocks()
999 }
1000
1001 pub(crate) fn set_propagation_delay(&mut self, delay: Round) {
1003 info!("Propagation round delay set to: {delay}");
1004 if let Some(proposer) = &mut self.proposer {
1005 proposer.set_propagation_delay(delay);
1006 }
1007 }
1008
1009 pub(crate) fn set_last_known_proposed_round(&mut self, round: Round) {
1013 if let Some(proposer) = &mut self.proposer {
1014 if proposer.get_last_known_proposed_round().is_some() {
1015 panic!(
1016 "Should not attempt to set the last known proposed round if that has been already set"
1017 );
1018 }
1019 proposer.set_last_known_proposed_round(round);
1020 info!("Last known proposed round set to {round}");
1021 }
1022 }
1023
1024 pub(crate) fn should_propose(&self) -> bool {
1027 self.proposer
1028 .as_ref()
1029 .map(|p| p.should_propose())
1030 .unwrap_or(false)
1031 }
1032
1033 pub(crate) fn last_proposed_round(&self) -> Option<Round> {
1035 self.proposer.as_ref().map(|p| p.last_proposed_round())
1036 }
1037
1038 fn current_reputation_scores(&self) -> ReputationScores {
1040 if let Some(schedule) = self.leader_schedule_v3.as_ref() {
1041 schedule.current_reputation_scores()
1042 } else {
1043 self.leader_schedule
1044 .leader_swap_table
1045 .read()
1046 .reputation_scores
1047 .clone()
1048 }
1049 }
1050
1051 #[tracing::instrument(skip_all)]
1057 fn try_select_certified_leaders(
1058 &mut self,
1059 certified_commits: &mut Vec<CertifiedCommit>,
1060 limit: usize,
1061 ) -> Vec<(DecidedLeader, CertifiedCommit)> {
1062 assert!(limit > 0, "limit should be greater than 0");
1063 if certified_commits.is_empty() {
1064 return vec![];
1065 }
1066
1067 let to_commit = if certified_commits.len() >= limit {
1068 certified_commits.drain(..limit).collect::<Vec<_>>()
1070 } else {
1071 std::mem::take(certified_commits)
1073 };
1074
1075 tracing::debug!(
1076 "Selected {} certified leaders: {}",
1077 to_commit.len(),
1078 to_commit.iter().map(|c| c.leader().to_string()).join(",")
1079 );
1080
1081 to_commit
1082 .into_iter()
1083 .map(|commit| {
1084 let leader = commit.blocks().last().expect("Certified commit should have at least one block");
1085 assert_eq!(leader.reference(), commit.leader(), "Last block of the committed sub dag should have the same digest as the leader of the commit");
1086 let leader = DecidedLeader::Commit(leader.clone(), false);
1088 UniversalCommitter::update_metrics(&self.context, &leader, Decision::Certified);
1089 (leader, commit)
1090 })
1091 .collect::<Vec<_>>()
1092 }
1093
1094 #[cfg(test)]
1096 pub(crate) fn last_proposed_block(&self) -> VerifiedBlock {
1097 self.proposer
1098 .as_ref()
1099 .expect("Proposer should be present")
1100 .last_proposed_block()
1101 }
1102
1103 #[cfg(test)]
1106 pub(crate) fn round_tracker_for_tests(&self) -> Arc<RwLock<RoundTracker>> {
1107 self.proposer
1108 .as_ref()
1109 .expect("Proposer should be present")
1110 .round_tracker_for_tests()
1111 }
1112}
1113
1114pub(crate) struct CoreSignals {
1116 tx_block_broadcast: broadcast::Sender<ExtendedBlock>,
1117 tx_accepted_block_broadcast: broadcast::Sender<VerifiedBlock>,
1118 new_round_sender: watch::Sender<Round>,
1119 context: Arc<Context>,
1120}
1121
1122impl CoreSignals {
1123 pub fn new(context: Arc<Context>) -> (Self, CoreSignalsReceivers) {
1124 let (tx_block_broadcast, rx_block_broadcast) = broadcast::channel::<ExtendedBlock>(
1128 context.parameters.dag_state_cached_rounds as usize,
1129 );
1130 let (tx_accepted_block_broadcast, rx_accepted_block_broadcast) =
1131 broadcast::channel::<VerifiedBlock>(
1132 2 * context.parameters.dag_state_cached_rounds as usize * context.committee.size(),
1133 );
1134 let (new_round_sender, new_round_receiver) = watch::channel(0);
1135
1136 let me = Self {
1137 tx_block_broadcast,
1138 tx_accepted_block_broadcast,
1139 new_round_sender,
1140 context,
1141 };
1142
1143 let receivers = CoreSignalsReceivers {
1144 rx_block_broadcast,
1145 rx_accepted_block_broadcast,
1146 new_round_receiver,
1147 };
1148
1149 (me, receivers)
1150 }
1151
1152 pub(crate) fn new_block(&self, extended_block: ExtendedBlock) -> ConsensusResult<()> {
1155 if self.context.committee.size() > 1 {
1158 if extended_block.block.round() == GENESIS_ROUND {
1159 debug!("Ignoring broadcasting genesis block to peers");
1160 return Ok(());
1161 }
1162
1163 if let Err(err) = self.tx_block_broadcast.send(extended_block) {
1164 warn!("Couldn't broadcast the block to any receiver: {err}");
1165 return Err(ConsensusError::Shutdown);
1166 }
1167 } else {
1168 debug!(
1169 "Did not broadcast block {extended_block:?} to receivers as committee size is <= 1"
1170 );
1171 }
1172 Ok(())
1173 }
1174
1175 pub(crate) fn new_accepted_block(&self, block: VerifiedBlock) {
1180 let _ = self.tx_accepted_block_broadcast.send(block);
1182 }
1183
1184 pub(crate) fn new_round(&mut self, round_number: Round) {
1187 let _ = self.new_round_sender.send_replace(round_number);
1188 }
1189}
1190
1191pub(crate) struct CoreSignalsReceivers {
1194 rx_block_broadcast: broadcast::Receiver<ExtendedBlock>,
1195 rx_accepted_block_broadcast: broadcast::Receiver<VerifiedBlock>,
1196 new_round_receiver: watch::Receiver<Round>,
1197}
1198
1199impl CoreSignalsReceivers {
1200 pub(crate) fn block_broadcast_receiver(&self) -> broadcast::Receiver<ExtendedBlock> {
1201 self.rx_block_broadcast.resubscribe()
1202 }
1203
1204 pub(crate) fn accepted_block_broadcast_receiver(&self) -> broadcast::Receiver<VerifiedBlock> {
1205 self.rx_accepted_block_broadcast.resubscribe()
1206 }
1207
1208 pub(crate) fn new_round_receiver(&self) -> watch::Receiver<Round> {
1209 self.new_round_receiver.clone()
1210 }
1211}
1212
1213#[cfg(test)]
1216pub(crate) async fn create_cores(
1217 context: Context,
1218 authorities: Vec<Stake>,
1219) -> Vec<CoreTestFixture> {
1220 let mut cores = Vec::new();
1221
1222 for index in 0..authorities.len() {
1223 let own_index = AuthorityIndex::new_for_test(index as u32);
1224 let core =
1225 CoreTestFixture::new(context.clone(), authorities.clone(), own_index, false).await;
1226 cores.push(core);
1227 }
1228 cores
1229}
1230
1231#[cfg(test)]
1232pub(crate) struct CoreTestFixture {
1233 pub(crate) core: Core,
1234 pub(crate) commit_consumer_monitor: Arc<crate::CommitConsumerMonitor>,
1235 pub(crate) transaction_vote_tracker: TransactionVoteTracker,
1236 pub(crate) signal_receivers: CoreSignalsReceivers,
1237 pub(crate) block_receiver: broadcast::Receiver<ExtendedBlock>,
1238 pub(crate) _commit_output_receiver: UnboundedReceiver<CommittedSubDag>,
1239 pub(crate) dag_state: Arc<RwLock<DagState>>,
1240 pub(crate) store: Arc<MemStore>,
1241 pub(crate) transaction_client: TransactionClient,
1242}
1243
1244#[cfg(test)]
1245impl CoreTestFixture {
1246 async fn new(
1247 context: Context,
1248 authorities: Vec<Stake>,
1249 own_index: AuthorityIndex,
1250 sync_last_known_own_block: bool,
1251 ) -> Self {
1252 Self::new_with_prepopulated_blocks(
1253 context,
1254 authorities,
1255 own_index,
1256 sync_last_known_own_block,
1257 vec![],
1258 )
1259 .await
1260 }
1261
1262 async fn new_with_prepopulated_blocks(
1266 context: Context,
1267 authorities: Vec<Stake>,
1268 own_index: AuthorityIndex,
1269 sync_last_known_own_block: bool,
1270 prepopulated_blocks: Vec<VerifiedBlock>,
1271 ) -> Self {
1272 let store = Arc::new(MemStore::new());
1273 if !prepopulated_blocks.is_empty() {
1274 store
1275 .write(WriteBatch::default().blocks(prepopulated_blocks))
1276 .expect("Storage error");
1277 }
1278 Self::new_with_store(
1279 context,
1280 authorities,
1281 own_index,
1282 sync_last_known_own_block,
1283 store,
1284 )
1285 .await
1286 }
1287
1288 async fn new_with_store(
1292 context: Context,
1293 authorities: Vec<Stake>,
1294 own_index: AuthorityIndex,
1295 sync_last_known_own_block: bool,
1296 store: Arc<MemStore>,
1297 ) -> Self {
1298 let (committee, mut signers) = local_committee_and_keys(0, authorities.clone());
1299 let mut context = context.clone();
1300 context = context
1301 .with_committee(committee)
1302 .with_authority_index(own_index);
1303 context
1304 .protocol_config
1305 .set_bad_nodes_stake_threshold_for_testing(33);
1306
1307 let context = Arc::new(context);
1308 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1309
1310 let block_manager = BlockManager::new(context.clone(), dag_state.clone());
1311 let leader_schedule = Arc::new(
1312 LeaderSchedule::from_store(context.clone(), dag_state.clone())
1313 .with_num_commits_per_schedule(10),
1314 );
1315 let (transaction_client, tx_receiver, priority_tx_receiver) =
1316 TransactionClient::new(context.clone());
1317 let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1318 tx_receiver,
1319 priority_tx_receiver,
1320 context.clone(),
1321 )));
1322 let transaction_vote_tracker = TransactionVoteTracker::new(
1323 context.clone(),
1324 Arc::new(NoopBlockVerifier {}),
1325 dag_state.clone(),
1326 );
1327 let (signals, signal_receivers) = CoreSignals::new(context.clone());
1328 let block_receiver = signal_receivers.block_broadcast_receiver();
1330
1331 let (commit_consumer, commit_output_receiver) = CommitConsumerArgs::new(0, 0);
1332 let commit_consumer_monitor = commit_consumer.monitor();
1333 let commit_observer = CommitObserver::new(
1334 context.clone(),
1335 commit_consumer,
1336 dag_state.clone(),
1337 transaction_vote_tracker.clone(),
1338 )
1339 .await;
1340
1341 let block_signer = signers.remove(own_index.value()).1;
1342
1343 let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1344 let core = Core::new_validator(
1345 context.clone(),
1346 leader_schedule,
1347 transaction_pool,
1348 transaction_vote_tracker.clone(),
1349 block_manager,
1350 commit_observer,
1351 signals,
1352 block_signer,
1353 dag_state.clone(),
1354 sync_last_known_own_block,
1355 round_tracker.clone(),
1356 );
1357
1358 Self {
1359 core,
1360 commit_consumer_monitor,
1361 transaction_vote_tracker,
1362 signal_receivers,
1363 block_receiver,
1364 _commit_output_receiver: commit_output_receiver,
1365 dag_state,
1366 store,
1367 transaction_client,
1368 }
1369 }
1370
1371 pub(crate) fn add_blocks(
1372 &mut self,
1373 blocks: Vec<VerifiedBlock>,
1374 ) -> ConsensusResult<BTreeSet<BlockRef>> {
1375 self.transaction_vote_tracker
1376 .add_voted_blocks(blocks.iter().map(|b| (b.clone(), vec![])).collect());
1377 self.core.add_blocks(blocks)
1378 }
1379}
1380
1381#[cfg(test)]
1382mod test {
1383 use std::{
1384 collections::{BTreeMap, BTreeSet},
1385 iter,
1386 time::Duration,
1387 };
1388
1389 use consensus_config::{AuthorityIndex, Parameters};
1390 use consensus_types::block::BlockTimestampMs;
1391 use futures::{StreamExt, stream::FuturesUnordered};
1392 use tokio::time::sleep;
1393
1394 use super::*;
1395 use crate::{
1396 CommitConsumerArgs, CommitIndex,
1397 block::{TestBlock, genesis_blocks, max_transaction_vote_targets},
1398 block_verifier::NoopBlockVerifier,
1399 commit::CommitAPI,
1400 leader_schedule::LeaderSwapTable,
1401 leader_schedule_v3::NextCommitLeaderSchedule,
1402 leader_scoring::ReputationScores,
1403 storage::{Store, WriteBatch, mem_store::MemStore},
1404 test_dag_builder::DagBuilder,
1405 test_dag_parser::parse_dag,
1406 transaction::{BlockStatus, Priority, TransactionClient},
1407 };
1408
1409 #[tokio::test]
1411 async fn test_core_recover_from_store_for_full_round() {
1412 telemetry_subscribers::init_for_testing();
1413 let (context, mut key_pairs) = Context::new_for_test(4);
1414 let context = Arc::new(context);
1415 let store = Arc::new(MemStore::new());
1416 let (_transaction_client, tx_receiver, priority_tx_receiver) =
1417 TransactionClient::new(context.clone());
1418 let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1419 tx_receiver,
1420 priority_tx_receiver,
1421 context.clone(),
1422 )));
1423 let mut block_status_subscriptions = FuturesUnordered::new();
1424
1425 let mut last_round_blocks = genesis_blocks(&context);
1427 let mut all_blocks: Vec<VerifiedBlock> = last_round_blocks.clone();
1428 for round in 1..=4 {
1429 let mut this_round_blocks = Vec::new();
1430 for (index, _authority) in context.committee.authorities() {
1431 let block = VerifiedBlock::new_for_test(
1432 TestBlock::new(round, index.value() as u32)
1433 .set_ancestors(last_round_blocks.iter().map(|b| b.reference()).collect())
1434 .build(),
1435 );
1436
1437 if round == 1 && index == context.own_index {
1439 let subscription =
1440 transaction_pool.subscribe_for_block_status_testing(block.reference());
1441 block_status_subscriptions.push(subscription);
1442 }
1443
1444 this_round_blocks.push(block);
1445 }
1446 all_blocks.extend(this_round_blocks.clone());
1447 last_round_blocks = this_round_blocks;
1448 }
1449 store
1451 .write(WriteBatch::default().blocks(all_blocks))
1452 .expect("Storage error");
1453
1454 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1456 let block_manager = BlockManager::new(context.clone(), dag_state.clone());
1457 let leader_schedule = Arc::new(LeaderSchedule::from_store(
1458 context.clone(),
1459 dag_state.clone(),
1460 ));
1461 let transaction_vote_tracker = TransactionVoteTracker::new(
1462 context.clone(),
1463 Arc::new(NoopBlockVerifier {}),
1464 dag_state.clone(),
1465 );
1466
1467 let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
1468 let commit_observer = CommitObserver::new(
1469 context.clone(),
1470 commit_consumer,
1471 dag_state.clone(),
1472 transaction_vote_tracker.clone(),
1473 )
1474 .await;
1475
1476 let last_commit = store.read_last_commit().unwrap();
1478 assert!(last_commit.is_none());
1479 assert_eq!(dag_state.read().last_commit_index(), 0);
1480
1481 let (signals, signal_receivers) = CoreSignals::new(context.clone());
1483 let transaction_vote_tracker = TransactionVoteTracker::new(
1484 context.clone(),
1485 Arc::new(NoopBlockVerifier {}),
1486 dag_state.clone(),
1487 );
1488 transaction_vote_tracker.recover_blocks_after_round(dag_state.read().gc_round());
1489 let mut block_receiver = signal_receivers.block_broadcast_receiver();
1491 let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1492 let _core = Core::new_validator(
1493 context.clone(),
1494 leader_schedule,
1495 transaction_pool,
1496 transaction_vote_tracker.clone(),
1497 block_manager,
1498 commit_observer,
1499 signals,
1500 key_pairs.remove(context.own_index.value()).1,
1501 dag_state.clone(),
1502 false,
1503 round_tracker,
1504 );
1505
1506 let mut new_round = signal_receivers.new_round_receiver();
1508 assert_eq!(*new_round.borrow_and_update(), 5);
1509
1510 let proposed_block = block_receiver
1512 .recv()
1513 .await
1514 .expect("A block should have been created");
1515 assert_eq!(proposed_block.block.round(), 5);
1516 let ancestors = proposed_block.block.ancestors();
1517
1518 assert_eq!(ancestors.len(), 4);
1520 for ancestor in ancestors {
1521 assert_eq!(ancestor.round, 4);
1522 }
1523
1524 dag_state.write().flush();
1526
1527 let last_commit = store
1531 .read_last_commit()
1532 .unwrap()
1533 .expect("last commit should be set");
1534 assert_eq!(last_commit.index(), 2);
1535 assert_eq!(dag_state.read().last_commit_index(), 2);
1536 let all_stored_commits = store.scan_commits((0..=CommitIndex::MAX).into()).unwrap();
1537 assert_eq!(all_stored_commits.len(), 2);
1538
1539 while let Some(result) = block_status_subscriptions.next().await {
1541 let status = result.unwrap();
1542 assert!(matches!(status, BlockStatus::Sequenced(_)));
1543 }
1544 }
1545
1546 #[tokio::test]
1549 async fn test_core_recover_from_store_for_partial_round() {
1550 telemetry_subscribers::init_for_testing();
1551
1552 let (context, _) = Context::new_for_test(4);
1553
1554 let mut last_round_blocks = genesis_blocks(&context);
1556 let mut all_blocks = last_round_blocks.clone();
1557 for round in 1..=4 {
1558 let mut this_round_blocks = Vec::new();
1559
1560 let authorities_to_skip = if round == 4 {
1562 context.committee.validity_threshold() as usize
1563 } else {
1564 1
1566 };
1567
1568 for (index, _authority) in context.committee.authorities().skip(authorities_to_skip) {
1569 let block = TestBlock::new(round, index.value() as u32)
1570 .set_ancestors(last_round_blocks.iter().map(|b| b.reference()).collect())
1571 .build();
1572 this_round_blocks.push(VerifiedBlock::new_for_test(block));
1573 }
1574 all_blocks.extend(this_round_blocks.clone());
1575 last_round_blocks = this_round_blocks;
1576 }
1577
1578 let mut fixture = CoreTestFixture::new_with_prepopulated_blocks(
1579 context,
1580 vec![1, 1, 1, 1],
1581 AuthorityIndex::new_for_test(0),
1582 false,
1583 all_blocks,
1584 )
1585 .await;
1586
1587 let mut new_round = fixture.signal_receivers.new_round_receiver();
1590 assert_eq!(*new_round.borrow_and_update(), 5);
1591
1592 let proposed_block = fixture
1594 .block_receiver
1595 .recv()
1596 .await
1597 .expect("A block should have been created");
1598 assert_eq!(proposed_block.block.round(), 4);
1599 let ancestors = proposed_block.block.ancestors();
1600
1601 assert_eq!(ancestors.len(), 4);
1602 let own_index = fixture.core.context.own_index;
1603 for ancestor in ancestors {
1604 if ancestor.author == own_index {
1605 assert_eq!(ancestor.round, 0);
1606 } else {
1607 assert_eq!(ancestor.round, 3);
1608 }
1609 }
1610
1611 fixture.core.try_commit(vec![]).ok();
1613
1614 fixture.core.dag_state.write().flush();
1616
1617 let last_commit = fixture
1621 .store
1622 .read_last_commit()
1623 .unwrap()
1624 .expect("last commit should be set");
1625 assert_eq!(last_commit.index(), 2);
1626 assert_eq!(fixture.dag_state.read().last_commit_index(), 2);
1627 let all_stored_commits = fixture
1628 .store
1629 .scan_commits((0..=CommitIndex::MAX).into())
1630 .unwrap();
1631 assert_eq!(all_stored_commits.len(), 2);
1632 }
1633
1634 #[tokio::test]
1635 async fn test_core_propose_after_genesis() {
1636 telemetry_subscribers::init_for_testing();
1637 let (mut context, _) = Context::new_for_test(4);
1638 context
1639 .protocol_config
1640 .set_max_transaction_size_bytes_for_testing(2_000);
1641 context
1642 .protocol_config
1643 .set_max_transactions_in_block_bytes_for_testing(2_000);
1644
1645 let mut fixture = CoreTestFixture::new(
1646 context,
1647 vec![1, 1, 1, 1],
1648 AuthorityIndex::new_for_test(0),
1649 false,
1650 )
1651 .await;
1652
1653 let mut total = 0;
1655 let mut index = 0;
1656 loop {
1657 let transaction =
1658 bcs::to_bytes(&format!("Transaction {index}")).expect("Shouldn't fail");
1659 total += transaction.len();
1660 index += 1;
1661 let _w = fixture
1662 .transaction_client
1663 .submit_no_wait(vec![transaction], Priority::Normal)
1664 .await
1665 .unwrap();
1666
1667 if total >= 1_000 {
1669 break;
1670 }
1671 }
1672
1673 let extended_block = fixture
1675 .block_receiver
1676 .recv()
1677 .await
1678 .expect("A new block should have been created");
1679
1680 assert_eq!(extended_block.block.round(), 1);
1682 assert_eq!(extended_block.block.author().value(), 0);
1683 assert_eq!(extended_block.block.ancestors().len(), 4);
1684
1685 let mut total = 0;
1686 for (i, transaction) in extended_block.block.transactions().iter().enumerate() {
1687 total += transaction.data().len() as u64;
1688 let transaction: String = bcs::from_bytes(transaction.data()).unwrap();
1689 assert_eq!(format!("Transaction {i}"), transaction);
1690 }
1691 assert!(
1692 total
1693 <= fixture
1694 .core
1695 .context
1696 .protocol_config
1697 .max_transactions_in_block_bytes()
1698 );
1699 assert_eq!(
1700 fixture
1701 .core
1702 .context
1703 .metrics
1704 .node_metrics
1705 .proposed_block_transaction_bytes
1706 .get_sample_count(),
1707 1
1708 );
1709 assert_eq!(
1710 fixture
1711 .core
1712 .context
1713 .metrics
1714 .node_metrics
1715 .proposed_block_transaction_bytes
1716 .get_sample_sum(),
1717 total as f64
1718 );
1719
1720 let all_genesis = genesis_blocks(&fixture.core.context);
1722
1723 for ancestor in extended_block.block.ancestors() {
1724 all_genesis
1725 .iter()
1726 .find(|block| block.reference() == *ancestor)
1727 .expect("Block should be found amongst genesis blocks");
1728 }
1729
1730 assert!(fixture.core.try_propose(false).unwrap().is_none());
1732 assert!(fixture.core.try_propose(true).unwrap().is_none());
1733
1734 fixture.dag_state.write().flush();
1736
1737 let last_commit = fixture.store.read_last_commit().unwrap();
1739 assert!(last_commit.is_none());
1740 assert_eq!(fixture.dag_state.read().last_commit_index(), 0);
1741 }
1742
1743 #[tokio::test]
1744 async fn test_core_proposes_v3_block_with_gc_cutoff() {
1745 telemetry_subscribers::init_for_testing();
1746 const GC_DEPTH: u32 = 3;
1747 let (mut context, _) = Context::new_for_test(4);
1748 context.protocol_config.set_enable_v3_for_testing(true);
1749 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
1750
1751 let mut fixture = CoreTestFixture::new(
1752 context,
1753 vec![1, 1, 1, 1],
1754 AuthorityIndex::new_for_test(0),
1755 false,
1756 )
1757 .await;
1758 let first_proposal = fixture
1759 .block_receiver
1760 .recv()
1761 .await
1762 .expect("Recovery should propose the first v3 block");
1763 assert_eq!(
1764 first_proposal.block.transaction_votes_cutoff_round(),
1765 GENESIS_ROUND,
1766 "Nothing is committed yet, so the cutoff must be the genesis round"
1767 );
1768
1769 let mut builder = DagBuilder::new(fixture.core.context.clone());
1772 builder
1773 .layers(1..=10)
1774 .authorities(vec![fixture.core.context.own_index])
1775 .skip_block()
1776 .build();
1777 assert!(
1778 fixture
1779 .add_blocks(builder.blocks.values().cloned().collect())
1780 .unwrap()
1781 .is_empty()
1782 );
1783 let gc_round = fixture.dag_state.read().gc_round();
1784 assert!(gc_round > GENESIS_ROUND, "GC round should have advanced");
1785
1786 fixture.core.try_propose(true).unwrap();
1787 let proposed = fixture.core.last_proposed_block();
1788 assert!(proposed.round() > first_proposal.block.round());
1789 assert_eq!(proposed.transaction_votes_cutoff_round(), gc_round);
1790 }
1791
1792 async fn propose_after_peer_blocks_with_reject_votes(
1796 context: Context,
1797 ) -> (CoreTestFixture, VerifiedBlock, Vec<VerifiedBlock>) {
1798 let mut fixture = CoreTestFixture::new(
1799 context,
1800 vec![1, 1, 1, 1],
1801 AuthorityIndex::new_for_test(0),
1802 false,
1803 )
1804 .await;
1805 let mut builder = DagBuilder::new(fixture.core.context.clone());
1806 builder
1807 .layers(1..=3)
1808 .authorities(vec![fixture.core.context.own_index])
1809 .skip_block()
1810 .build();
1811 let blocks = builder.blocks.values().cloned().collect::<Vec<_>>();
1812 fixture
1813 .transaction_vote_tracker
1814 .add_voted_blocks(blocks.iter().map(|b| (b.clone(), vec![0])).collect());
1815 assert!(fixture.core.add_blocks(blocks.clone()).unwrap().is_empty());
1816
1817 fixture.core.try_propose(true).unwrap();
1818 let proposed = fixture.core.last_proposed_block();
1819 (fixture, proposed, blocks)
1820 }
1821
1822 #[tokio::test]
1823 async fn test_core_v2_proposal_keeps_all_transaction_vote_targets() {
1824 telemetry_subscribers::init_for_testing();
1825 let (mut context, _) = Context::new_for_test(4);
1826 context.protocol_config.set_gc_depth_for_testing(1);
1827 let vote_target_limit = max_transaction_vote_targets(&context);
1828
1829 let (_fixture, proposed, blocks) =
1830 propose_after_peer_blocks_with_reject_votes(context).await;
1831
1832 assert!(blocks.len() > vote_target_limit);
1834 assert_eq!(proposed.transaction_votes().len(), blocks.len());
1835 }
1836
1837 #[tokio::test]
1838 async fn test_core_v3_proposal_limits_transaction_vote_targets() {
1839 telemetry_subscribers::init_for_testing();
1840 let (mut context, _) = Context::new_for_test(4);
1841 context.protocol_config.set_enable_v3_for_testing(true);
1842 context.protocol_config.set_gc_depth_for_testing(1);
1843 let vote_target_limit = max_transaction_vote_targets(&context);
1844
1845 let (fixture, proposed, blocks) =
1846 propose_after_peer_blocks_with_reject_votes(context).await;
1847
1848 assert!(blocks.len() > vote_target_limit);
1849 assert!(!proposed.transaction_votes().is_empty());
1850 assert!(proposed.transaction_votes().len() <= vote_target_limit);
1851 let cutoff_round = proposed.transaction_votes_cutoff_round();
1854 assert!(cutoff_round > fixture.dag_state.read().gc_round());
1855 let expected_targets = blocks
1856 .iter()
1857 .filter(|block| block.round() > cutoff_round)
1858 .count();
1859 assert_eq!(proposed.transaction_votes().len(), expected_targets);
1860 }
1861
1862 #[tokio::test]
1863 async fn test_core_propose_once_receiving_a_quorum() {
1864 telemetry_subscribers::init_for_testing();
1865 let (context, _key_pairs) = Context::new_for_test(4);
1866 let mut core_fixture = CoreTestFixture::new(
1867 context.clone(),
1868 vec![1, 1, 1, 1],
1869 AuthorityIndex::new_for_test(0),
1870 false,
1871 )
1872 .await;
1873 let transaction_vote_tracker = &core_fixture.transaction_vote_tracker;
1874 let store = &core_fixture.store;
1875 let dag_state = &core_fixture.dag_state;
1876 let core = &mut core_fixture.core;
1877
1878 let mut expected_ancestors = BTreeSet::new();
1879
1880 let block_1 = VerifiedBlock::new_for_test(TestBlock::new(1, 1).build());
1882 expected_ancestors.insert(block_1.reference());
1883 sleep(context.parameters.min_round_delay).await;
1885 transaction_vote_tracker.add_voted_blocks(vec![(block_1.clone(), vec![])]);
1887 _ = core.add_blocks(vec![block_1]);
1888
1889 assert_eq!(core.last_proposed_round(), Some(1));
1890 expected_ancestors.insert(core.last_proposed_block().reference());
1891 assert!(core.try_propose(false).unwrap().is_none());
1893
1894 let block_2 = VerifiedBlock::new_for_test(TestBlock::new(1, 2).build());
1896 expected_ancestors.insert(block_2.reference());
1897 sleep(context.parameters.min_round_delay).await;
1899 transaction_vote_tracker.add_voted_blocks(vec![(block_2.clone(), vec![1, 4])]);
1901 _ = core.add_blocks(vec![block_2.clone()]);
1902
1903 assert_eq!(core.last_proposed_round(), Some(2));
1904
1905 let proposed_block = core.last_proposed_block();
1906 assert_eq!(proposed_block.round(), 2);
1907 assert_eq!(proposed_block.author(), context.own_index);
1908 assert_eq!(proposed_block.ancestors().len(), 3);
1909 let ancestors = proposed_block.ancestors();
1910 let ancestors = ancestors.iter().cloned().collect::<BTreeSet<_>>();
1911 assert_eq!(ancestors, expected_ancestors);
1912
1913 let transaction_votes = proposed_block.transaction_votes();
1914 assert_eq!(transaction_votes.len(), 1);
1915 let transaction_vote = transaction_votes.first().unwrap();
1916 assert_eq!(transaction_vote.block_ref, block_2.reference());
1917 assert_eq!(transaction_vote.rejects, vec![1, 4]);
1918 assert_eq!(
1919 context
1920 .metrics
1921 .node_metrics
1922 .proposed_block_transaction_vote_blocks
1923 .get_sample_count(),
1924 2
1925 );
1926 assert_eq!(
1927 context
1928 .metrics
1929 .node_metrics
1930 .proposed_block_transaction_vote_blocks
1931 .get_sample_sum(),
1932 1.0
1933 );
1934 assert_eq!(
1935 context
1936 .metrics
1937 .node_metrics
1938 .proposed_block_transaction_vote_entries
1939 .get_sample_count(),
1940 2
1941 );
1942 assert_eq!(
1943 context
1944 .metrics
1945 .node_metrics
1946 .proposed_block_transaction_vote_entries
1947 .get_sample_sum(),
1948 2.0
1949 );
1950
1951 dag_state.write().flush();
1953
1954 let last_commit = store.read_last_commit().unwrap();
1956 assert!(last_commit.is_none());
1957 assert_eq!(dag_state.read().last_commit_index(), 0);
1958 }
1959
1960 #[tokio::test]
1961 async fn test_commit_and_notify_for_block_status() {
1962 telemetry_subscribers::init_for_testing();
1963 let (mut context, mut key_pairs) = Context::new_for_test(4);
1964 const GC_DEPTH: u32 = 2;
1965
1966 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
1967
1968 let context = Arc::new(context);
1969
1970 let store = Arc::new(MemStore::new());
1971 let (_transaction_client, tx_receiver, priority_tx_receiver) =
1972 TransactionClient::new(context.clone());
1973 let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
1974 tx_receiver,
1975 priority_tx_receiver,
1976 context.clone(),
1977 )));
1978 let mut block_status_subscriptions = FuturesUnordered::new();
1979
1980 let dag_str = "DAG {
1981 Round 0 : { 4 },
1982 Round 1 : { * },
1983 Round 2 : { * },
1984 Round 3 : {
1985 A -> [*],
1986 B -> [-A2],
1987 C -> [-A2],
1988 D -> [-A2],
1989 },
1990 Round 4 : {
1991 B -> [-A3],
1992 C -> [-A3],
1993 D -> [-A3],
1994 },
1995 Round 5 : {
1996 A -> [A3, B4, C4, D4]
1997 B -> [*],
1998 C -> [*],
1999 D -> [*],
2000 },
2001 Round 6 : { * },
2002 Round 7 : { * },
2003 Round 8 : { * },
2004 }";
2005
2006 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
2007 dag_builder.print();
2008
2009 for block in dag_builder.blocks(1..=5) {
2011 if block.author() == context.own_index {
2012 let subscription =
2013 transaction_pool.subscribe_for_block_status_testing(block.reference());
2014 block_status_subscriptions.push(subscription);
2015 }
2016 }
2017
2018 store
2020 .write(WriteBatch::default().blocks(dag_builder.blocks(1..=8)))
2021 .expect("Storage error");
2022
2023 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
2025 let block_manager = BlockManager::new(context.clone(), dag_state.clone());
2026 let leader_schedule = Arc::new(LeaderSchedule::from_store(
2027 context.clone(),
2028 dag_state.clone(),
2029 ));
2030 let transaction_vote_tracker = TransactionVoteTracker::new(
2031 context.clone(),
2032 Arc::new(NoopBlockVerifier {}),
2033 dag_state.clone(),
2034 );
2035
2036 let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
2037 let commit_observer = CommitObserver::new(
2038 context.clone(),
2039 commit_consumer,
2040 dag_state.clone(),
2041 transaction_vote_tracker.clone(),
2042 )
2043 .await;
2044
2045 dag_state.write().flush();
2047
2048 let last_commit = store.read_last_commit().unwrap();
2050 assert!(last_commit.is_none());
2051 assert_eq!(dag_state.read().last_commit_index(), 0);
2052
2053 let (signals, signal_receivers) = CoreSignals::new(context.clone());
2055 let transaction_vote_tracker = TransactionVoteTracker::new(
2056 context.clone(),
2057 Arc::new(NoopBlockVerifier {}),
2058 dag_state.clone(),
2059 );
2060 transaction_vote_tracker.recover_blocks_after_round(dag_state.read().gc_round());
2061 let _block_receiver = signal_receivers.block_broadcast_receiver();
2063 let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
2064 let _core = Core::new_validator(
2065 context.clone(),
2066 leader_schedule,
2067 transaction_pool,
2068 transaction_vote_tracker,
2069 block_manager,
2070 commit_observer,
2071 signals,
2072 key_pairs.remove(context.own_index.value()).1,
2073 dag_state.clone(),
2074 false,
2075 round_tracker,
2076 );
2077
2078 dag_state.write().flush();
2080
2081 let last_commit = store
2082 .read_last_commit()
2083 .unwrap()
2084 .expect("last commit should be set");
2085
2086 assert_eq!(last_commit.index(), 5);
2087
2088 while let Some(result) = block_status_subscriptions.next().await {
2089 let status = result.unwrap();
2090
2091 match status {
2092 BlockStatus::Sequenced(block_ref) => {
2093 assert!(block_ref.round == 1 || block_ref.round == 5);
2094 }
2095 BlockStatus::GarbageCollected(block_ref) => {
2096 assert!(block_ref.round == 2 || block_ref.round == 3);
2097 }
2098 }
2099 }
2100 }
2101
2102 #[tokio::test]
2105 async fn test_multiple_commits_advance_threshold_clock() {
2106 telemetry_subscribers::init_for_testing();
2107 let (mut context, _) = Context::new_for_test(4);
2108 const GC_DEPTH: u32 = 2;
2109
2110 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
2111
2112 let dag_str = "DAG {
2116 Round 0 : { 4 },
2117 Round 1 : { * },
2118 Round 2 : {
2119 B -> [-D1],
2120 C -> [-D1],
2121 D -> [-D1],
2122 },
2123 Round 3 : {
2124 B -> [*],
2125 C -> [*]
2126 D -> [*],
2127 },
2128 Round 4 : {
2129 A -> [*],
2130 B -> [*],
2131 C -> [*]
2132 D -> [*],
2133 },
2134 Round 5 : {
2135 A -> [*],
2136 B -> [*],
2137 C -> [*],
2138 D -> [*],
2139 },
2140 Round 6 : {
2141 B -> [A5, B5, C5, D1],
2142 C -> [A5, B5, C5, D1],
2143 D -> [A5, B5, C5, D1],
2144 },
2145 Round 7 : {
2146 B -> [*],
2147 C -> [*],
2148 D -> [*],
2149 },
2150 Round 8 : {
2151 B -> [*],
2152 C -> [*],
2153 D -> [*],
2154 },
2155 Round 9 : {
2156 B -> [*],
2157 C -> [*],
2158 D -> [*],
2159 },
2160 Round 10 : {
2161 B -> [*],
2162 C -> [*],
2163 D -> [*],
2164 },
2165 Round 11 : {
2166 B -> [*],
2167 C -> [*],
2168 D -> [*],
2169 },
2170 }";
2171
2172 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
2173 dag_builder.print();
2174
2175 let mut fixture = CoreTestFixture::new(
2176 context,
2177 vec![1, 1, 1, 1],
2178 AuthorityIndex::new_for_test(0),
2179 true,
2180 )
2181 .await;
2182
2183 let last_commit = fixture.store.read_last_commit().unwrap();
2185 assert!(last_commit.is_none());
2186 assert_eq!(fixture.dag_state.read().last_commit_index(), 0);
2187
2188 fixture.core.set_last_known_proposed_round(4);
2191
2192 let mut all_blocks = dag_builder.blocks(1..=11);
2198 all_blocks.sort_by_key(|b| b.round());
2199 fixture
2202 .transaction_vote_tracker
2203 .add_voted_blocks(all_blocks.iter().map(|b| (b.clone(), vec![])).collect());
2204 let blocks: Vec<VerifiedBlock> = all_blocks
2205 .into_iter()
2206 .filter(|b| !(b.round() == 1 && b.author() == AuthorityIndex::new_for_test(3)))
2207 .collect();
2208 fixture.core.add_blocks(blocks).expect("Should not fail");
2209
2210 assert_eq!(fixture.core.last_proposed_round(), Some(12));
2211 }
2212
2213 #[tokio::test]
2214 async fn test_core_set_min_propose_round() {
2215 telemetry_subscribers::init_for_testing();
2216 let (context, _) = Context::new_for_test(4);
2217 let context = context.with_parameters(Parameters {
2218 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2219 ..Default::default()
2220 });
2221 let mut fixture = CoreTestFixture::new(
2222 context,
2223 vec![1, 1, 1, 1],
2224 AuthorityIndex::new_for_test(0),
2225 true,
2226 )
2227 .await;
2228
2229 assert_eq!(
2231 fixture.core.last_proposed_round(),
2232 Some(GENESIS_ROUND),
2233 "No block should have been created other than genesis"
2234 );
2235
2236 assert!(fixture.core.try_propose(true).unwrap().is_none());
2238
2239 let mut builder = DagBuilder::new(fixture.core.context.clone());
2241 builder.layers(1..=10).build();
2242
2243 let blocks = builder.blocks.values().cloned().collect::<Vec<_>>();
2244
2245 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2247
2248 fixture
2249 .core
2250 .round_tracker_for_tests()
2251 .write()
2252 .update_from_probe(
2253 vec![
2254 vec![10, 10, 10, 10],
2255 vec![10, 10, 10, 10],
2256 vec![10, 10, 10, 10],
2257 vec![10, 10, 10, 10],
2258 ],
2259 vec![
2260 vec![10, 10, 10, 10],
2261 vec![10, 10, 10, 10],
2262 vec![10, 10, 10, 10],
2263 vec![10, 10, 10, 10],
2264 ],
2265 );
2266
2267 assert!(fixture.core.try_propose(true).unwrap().is_none());
2269
2270 fixture.core.set_last_known_proposed_round(10);
2273
2274 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2275 assert_eq!(block.round(), 11);
2276 assert_eq!(block.ancestors().len(), 4);
2277
2278 let our_ancestor_included = block.ancestors()[0];
2279 assert_eq!(our_ancestor_included.author, fixture.core.context.own_index);
2280 assert_eq!(our_ancestor_included.round, 10);
2281 }
2282
2283 #[tokio::test(flavor = "current_thread", start_paused = true)]
2284 async fn test_core_try_new_block_leader_timeout() {
2285 telemetry_subscribers::init_for_testing();
2286
2287 async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
2294 let now = context.clock.timestamp_utc_ms();
2296 let max_timestamp = blocks
2297 .iter()
2298 .max_by_key(|block| block.timestamp_ms() as BlockTimestampMs)
2299 .map(|block| block.timestamp_ms())
2300 .unwrap_or(0);
2301
2302 let wait_time = Duration::from_millis(max_timestamp.saturating_sub(now));
2303 sleep(wait_time).await;
2304 }
2305
2306 let (context, _) = Context::new_for_test(4);
2307 let mut all_cores = create_cores(context, vec![1, 1, 1, 1]).await;
2309
2310 let (_last_core, cores) = all_cores.split_last_mut().unwrap();
2315
2316 let mut last_round_blocks = Vec::<VerifiedBlock>::new();
2318 for round in 1..=3 {
2319 let mut this_round_blocks = Vec::new();
2320
2321 for core_fixture in cores.iter_mut() {
2322 wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2323
2324 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2325
2326 if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
2328 assert_eq!(round - 1, r);
2329 if core_fixture.core.last_proposed_round() == Some(r) {
2330 core_fixture
2332 .core
2333 .try_propose(true)
2334 .unwrap()
2335 .unwrap_or_else(|| {
2336 panic!("Block should have been proposed for round {}", round)
2337 });
2338 }
2339 }
2340
2341 assert_eq!(core_fixture.core.last_proposed_round(), Some(round));
2342
2343 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2344 }
2345
2346 last_round_blocks = this_round_blocks;
2347 }
2348
2349 for core_fixture in cores.iter_mut() {
2352 wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2353
2354 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2355 assert!(core_fixture.core.try_propose(false).unwrap().is_none());
2356 }
2357
2358 for core_fixture in cores.iter_mut() {
2361 assert!(core_fixture.core.new_block(4, true).unwrap().is_some());
2362 assert_eq!(core_fixture.core.last_proposed_round(), Some(4));
2363
2364 core_fixture.dag_state.write().flush();
2366
2367 let last_commit = core_fixture
2369 .store
2370 .read_last_commit()
2371 .unwrap()
2372 .expect("last commit should be set");
2373 assert_eq!(last_commit.index(), 1);
2376 let all_stored_commits = core_fixture
2377 .store
2378 .scan_commits((0..=CommitIndex::MAX).into())
2379 .unwrap();
2380 assert_eq!(all_stored_commits.len(), 1);
2381 }
2382 }
2383
2384 #[tokio::test(flavor = "current_thread", start_paused = true)]
2385 async fn test_core_try_new_block_with_leader_timeout_and_low_scoring_authority() {
2386 telemetry_subscribers::init_for_testing();
2387
2388 async fn wait_blocks(blocks: &[VerifiedBlock], context: &Context) {
2395 let now = context.clock.timestamp_utc_ms();
2397 let max_timestamp = blocks
2398 .iter()
2399 .max_by_key(|block| block.timestamp_ms() as BlockTimestampMs)
2400 .map(|block| block.timestamp_ms())
2401 .unwrap_or(0);
2402
2403 let wait_time = Duration::from_millis(max_timestamp.saturating_sub(now));
2404 sleep(wait_time).await;
2405 }
2406
2407 let (mut context, _) = Context::new_for_test(5);
2408 context
2409 .protocol_config
2410 .set_bad_nodes_stake_threshold_for_testing(33);
2411
2412 let mut all_cores = create_cores(context, vec![1, 1, 1, 1, 1]).await;
2414 let (_last_core, cores) = all_cores.split_last_mut().unwrap();
2415
2416 let mut last_round_blocks = Vec::<VerifiedBlock>::new();
2418 for round in 1..=30 {
2419 let mut this_round_blocks = Vec::new();
2420
2421 for core_fixture in cores.iter_mut() {
2422 wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2423
2424 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2425
2426 core_fixture
2427 .core
2428 .round_tracker_for_tests()
2429 .write()
2430 .update_from_probe(
2431 vec![
2432 vec![round, round, round, round, 0],
2433 vec![round, round, round, round, 0],
2434 vec![round, round, round, round, 0],
2435 vec![round, round, round, round, 0],
2436 vec![0, 0, 0, 0, 0],
2437 ],
2438 vec![
2439 vec![round, round, round, round, 0],
2440 vec![round, round, round, round, 0],
2441 vec![round, round, round, round, 0],
2442 vec![round, round, round, round, 0],
2443 vec![0, 0, 0, 0, 0],
2444 ],
2445 );
2446
2447 if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
2449 assert_eq!(round - 1, r);
2450 if core_fixture.core.last_proposed_round() == Some(r) {
2451 core_fixture
2453 .core
2454 .try_propose(true)
2455 .unwrap()
2456 .unwrap_or_else(|| {
2457 panic!("Block should have been proposed for round {}", round)
2458 });
2459 }
2460 }
2461
2462 assert_eq!(core_fixture.core.last_proposed_round(), Some(round));
2463
2464 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2465 }
2466
2467 last_round_blocks = this_round_blocks;
2468 }
2469
2470 for round in 31..=40 {
2472 let mut this_round_blocks = Vec::new();
2473
2474 for core_fixture in all_cores.iter_mut() {
2475 wait_blocks(&last_round_blocks, &core_fixture.core.context).await;
2476
2477 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
2478
2479 core_fixture
2482 .core
2483 .round_tracker_for_tests()
2484 .write()
2485 .update_from_probe(
2486 vec![
2487 vec![round, round, round, round, 0],
2488 vec![round, round, round, round, 0],
2489 vec![round, round, round, round, 0],
2490 vec![round, round, round, round, 0],
2491 vec![0, 0, 0, 0, 0],
2492 ],
2493 vec![
2494 vec![round, round, round, round, 0],
2495 vec![round, round, round, round, 0],
2496 vec![round, round, round, round, 0],
2497 vec![round, round, round, round, 0],
2498 vec![0, 0, 0, 0, 0],
2499 ],
2500 );
2501
2502 if let Some(r) = last_round_blocks.first().map(|b| b.round()) {
2504 assert_eq!(round - 1, r);
2505 if core_fixture.core.last_proposed_round() == Some(r) {
2506 core_fixture
2508 .core
2509 .try_propose(true)
2510 .unwrap()
2511 .unwrap_or_else(|| {
2512 panic!("Block should have been proposed for round {}", round)
2513 });
2514 }
2515 }
2516
2517 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
2518
2519 for block in this_round_blocks.iter() {
2520 if block.author() != AuthorityIndex::new_for_test(4) {
2521 assert_eq!(block.ancestors().len(), 4);
2524 } else {
2525 assert_eq!(block.ancestors().len(), 5);
2528 }
2529 }
2530 }
2531
2532 last_round_blocks = this_round_blocks;
2533 }
2534 }
2535
2536 #[tokio::test]
2537 async fn test_smart_ancestor_selection() {
2538 telemetry_subscribers::init_for_testing();
2539 let (context, _) = Context::new_for_test(7);
2540 let context = context.with_parameters(Parameters {
2541 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2542 ..Default::default()
2543 });
2544 let mut fixture =
2545 CoreTestFixture::new(context, vec![1; 7], AuthorityIndex::new_for_test(0), true).await;
2546 let min_round_delay = fixture.core.context.parameters.min_round_delay;
2547
2548 assert_eq!(
2550 fixture.core.last_proposed_round(),
2551 Some(GENESIS_ROUND),
2552 "No block should have been created other than genesis"
2553 );
2554
2555 assert!(fixture.core.try_propose(true).unwrap().is_none());
2557
2558 let mut builder = DagBuilder::new(fixture.core.context.clone());
2560 builder
2561 .layers(1..=12)
2562 .authorities(vec![AuthorityIndex::new_for_test(1)])
2563 .skip_block()
2564 .build();
2565 let blocks = builder.blocks(1..=12);
2566 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2568 fixture.core.set_last_known_proposed_round(12);
2569
2570 fixture
2571 .core
2572 .round_tracker_for_tests()
2573 .write()
2574 .update_from_probe(
2575 vec![
2576 vec![12, 12, 12, 12, 12, 12, 12],
2577 vec![0, 0, 0, 0, 0, 0, 0],
2578 vec![12, 12, 12, 12, 12, 12, 12],
2579 vec![12, 12, 12, 12, 12, 12, 12],
2580 vec![12, 12, 12, 12, 12, 12, 12],
2581 vec![12, 12, 12, 12, 12, 12, 12],
2582 vec![12, 12, 12, 12, 12, 12, 12],
2583 ],
2584 vec![
2585 vec![12, 12, 12, 12, 12, 12, 12],
2586 vec![0, 0, 0, 0, 0, 0, 0],
2587 vec![12, 12, 12, 12, 12, 12, 12],
2588 vec![12, 12, 12, 12, 12, 12, 12],
2589 vec![12, 12, 12, 12, 12, 12, 12],
2590 vec![12, 12, 12, 12, 12, 12, 12],
2591 vec![12, 12, 12, 12, 12, 12, 12],
2592 ],
2593 );
2594
2595 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2596 assert_eq!(block.round(), 13);
2597 assert_eq!(block.ancestors().len(), 7);
2598
2599 builder
2601 .layers(13..=14)
2602 .authorities(vec![AuthorityIndex::new_for_test(0)])
2603 .skip_block()
2604 .build();
2605 let blocks = builder.blocks(13..=14);
2606 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2607
2608 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2611 assert_eq!(block.round(), 15);
2612 assert_eq!(block.ancestors().len(), 6);
2613
2614 let round_14_ancestors = builder.last_ancestors.clone();
2617 builder
2618 .layer(15)
2619 .authorities(vec![
2620 AuthorityIndex::new_for_test(0),
2621 AuthorityIndex::new_for_test(5),
2622 AuthorityIndex::new_for_test(6),
2623 ])
2624 .skip_block()
2625 .build();
2626 let blocks = builder.blocks(15..=15);
2627 let authority_1_excluded_block_reference = blocks
2628 .iter()
2629 .find(|block| block.author() == AuthorityIndex::new_for_test(1))
2630 .unwrap()
2631 .reference();
2632 sleep(min_round_delay).await;
2634 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2636 assert_eq!(fixture.core.last_proposed_block().round(), 15);
2637
2638 builder
2639 .layer(15)
2640 .authorities(vec![
2641 AuthorityIndex::new_for_test(0),
2642 AuthorityIndex::new_for_test(1),
2643 AuthorityIndex::new_for_test(2),
2644 AuthorityIndex::new_for_test(3),
2645 AuthorityIndex::new_for_test(4),
2646 ])
2647 .skip_block()
2648 .override_last_ancestors(round_14_ancestors)
2649 .build();
2650 let blocks = builder.blocks(15..=15);
2651 let round_15_ancestors: Vec<BlockRef> = blocks
2652 .iter()
2653 .filter(|block| block.round() == 15)
2654 .map(|block| block.reference())
2655 .collect();
2656 let included_block_references = iter::once(&fixture.core.last_proposed_block())
2657 .chain(blocks.iter())
2658 .filter(|block| block.author() != AuthorityIndex::new_for_test(1))
2659 .map(|block| block.reference())
2660 .collect::<Vec<_>>();
2661
2662 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2664 assert_eq!(fixture.core.last_proposed_block().round(), 16);
2665
2666 let extended_block = loop {
2668 let extended_block =
2669 tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2670 .await
2671 .unwrap()
2672 .unwrap();
2673 if extended_block.block.round() == 16 {
2674 break extended_block;
2675 }
2676 };
2677 assert_eq!(extended_block.block.round(), 16);
2678 assert_eq!(
2679 extended_block.block.author(),
2680 fixture.core.context.own_index
2681 );
2682 assert_eq!(extended_block.block.ancestors().len(), 6);
2683 assert_eq!(extended_block.block.ancestors(), included_block_references);
2684 assert_eq!(extended_block.excluded_ancestors.len(), 1);
2685 assert_eq!(
2686 extended_block.excluded_ancestors[0],
2687 authority_1_excluded_block_reference
2688 );
2689
2690 builder
2695 .layer(16)
2696 .authorities(vec![
2697 AuthorityIndex::new_for_test(0),
2698 AuthorityIndex::new_for_test(5),
2699 AuthorityIndex::new_for_test(6),
2700 ])
2701 .skip_block()
2702 .override_last_ancestors(round_15_ancestors)
2703 .build();
2704 let blocks = builder.blocks(16..=16);
2705 sleep(min_round_delay).await;
2707 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2709 assert_eq!(fixture.core.last_proposed_block().round(), 16);
2710
2711 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2714 assert_eq!(block.round(), 17);
2715 assert_eq!(block.ancestors().len(), 5);
2716
2717 let extended_block =
2719 tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2720 .await
2721 .unwrap()
2722 .unwrap();
2723 assert_eq!(extended_block.block.round(), 17);
2724 assert_eq!(
2725 extended_block.block.author(),
2726 fixture.core.context.own_index
2727 );
2728 assert_eq!(extended_block.block.ancestors().len(), 5);
2729 assert_eq!(extended_block.excluded_ancestors.len(), 0);
2730
2731 builder
2734 .layers(17..=22)
2735 .authorities(vec![AuthorityIndex::new_for_test(0)])
2736 .skip_block()
2737 .build();
2738 let blocks = builder.blocks(17..=22);
2739
2740 fixture
2746 .core
2747 .round_tracker_for_tests()
2748 .write()
2749 .update_from_probe(
2750 vec![
2751 vec![22, 22, 22, 22, 22, 22, 22],
2752 vec![22, 22, 22, 22, 22, 22, 22],
2753 vec![22, 22, 22, 22, 22, 22, 22],
2754 vec![22, 22, 22, 22, 22, 22, 22],
2755 vec![22, 22, 22, 22, 22, 22, 22],
2756 vec![22, 22, 22, 22, 22, 22, 22],
2757 vec![22, 22, 22, 22, 22, 22, 22],
2758 ],
2759 vec![
2760 vec![22, 22, 22, 22, 22, 22, 22],
2761 vec![22, 22, 22, 22, 22, 22, 22],
2762 vec![22, 22, 22, 22, 22, 22, 22],
2763 vec![22, 22, 22, 22, 22, 22, 22],
2764 vec![22, 22, 22, 22, 22, 22, 22],
2765 vec![22, 22, 22, 22, 22, 22, 22],
2766 vec![22, 22, 22, 22, 22, 22, 22],
2767 ],
2768 );
2769
2770 let own_index = fixture.core.context.own_index;
2771 let included_block_references = iter::once(&fixture.core.last_proposed_block())
2772 .chain(blocks.iter())
2773 .filter(|block| block.round() == 22 || block.author() == own_index)
2774 .map(|block| block.reference())
2775 .collect::<Vec<_>>();
2776
2777 sleep(min_round_delay).await;
2779 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2780 assert_eq!(fixture.core.last_proposed_block().round(), 23);
2781
2782 let extended_block =
2784 tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
2785 .await
2786 .unwrap()
2787 .unwrap();
2788 assert_eq!(extended_block.block.round(), 23);
2789 assert_eq!(
2790 extended_block.block.author(),
2791 fixture.core.context.own_index
2792 );
2793 assert_eq!(extended_block.block.ancestors().len(), 7);
2794 assert_eq!(extended_block.block.ancestors(), included_block_references);
2795 assert_eq!(extended_block.excluded_ancestors.len(), 0);
2796 }
2797
2798 async fn fixture_with_excluded_authority(
2802 enable_v3: bool,
2803 ) -> (CoreTestFixture, DagBuilder, AuthorityIndex) {
2804 let (mut context, _) = Context::new_for_test(7);
2805 context.protocol_config.set_enable_v3_for_testing(enable_v3);
2806 let context = context.with_parameters(Parameters {
2807 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2808 ..Default::default()
2809 });
2810 let mut fixture =
2811 CoreTestFixture::new(context, vec![1; 7], AuthorityIndex::new_for_test(0), true).await;
2812 let excluded = AuthorityIndex::new_for_test(1);
2813
2814 let mut builder = DagBuilder::new(fixture.core.context.clone());
2818 builder
2819 .layers(1..=12)
2820 .authorities(vec![excluded])
2821 .skip_block()
2822 .build();
2823 assert!(
2824 fixture
2825 .add_blocks(builder.blocks(1..=12))
2826 .unwrap()
2827 .is_empty()
2828 );
2829 fixture.core.set_last_known_proposed_round(12);
2830 let mut quorum_rounds = vec![vec![12; 7]; 7];
2831 quorum_rounds[excluded] = vec![0; 7];
2832 fixture
2833 .core
2834 .round_tracker_for_tests()
2835 .write()
2836 .update_from_probe(quorum_rounds.clone(), quorum_rounds);
2837 assert_eq!(
2838 fixture
2839 .core
2840 .try_propose(true)
2841 .expect("No error")
2842 .unwrap()
2843 .round(),
2844 13
2845 );
2846
2847 builder
2848 .layers(13..=14)
2849 .authorities(vec![AuthorityIndex::new_for_test(0)])
2850 .skip_block()
2851 .build();
2852 assert!(
2853 fixture
2854 .add_blocks(builder.blocks(13..=14))
2855 .unwrap()
2856 .is_empty()
2857 );
2858 (fixture, builder, excluded)
2859 }
2860
2861 #[tokio::test]
2863 async fn test_smart_ancestor_selection_keeps_v3_leader() {
2864 telemetry_subscribers::init_for_testing();
2865 let (mut fixture, mut builder, excluded) = fixture_with_excluded_authority(true).await;
2866 let set_leader = |fixture: &mut CoreTestFixture, leader: AuthorityIndex, round: Round| {
2869 fixture
2870 .core
2871 .proposer
2872 .as_mut()
2873 .unwrap()
2874 .set_next_commit_leader_schedule(NextCommitLeaderSchedule {
2875 next_commit_index: 1,
2876 min_next_leader_round: round,
2877 allowed_leaders: vec![leader],
2878 });
2879 };
2880
2881 set_leader(&mut fixture, AuthorityIndex::new_for_test(2), 14);
2883 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2884 assert_eq!(block.round(), 15);
2885 assert!(
2886 block.ancestors().iter().all(|a| a.author != excluded),
2887 "The EXCLUDE authority is not a leader, so the proposal must not include it"
2888 );
2889
2890 builder
2891 .layer(15)
2892 .authorities(vec![AuthorityIndex::new_for_test(0)])
2893 .skip_block()
2894 .build();
2895 assert!(
2896 fixture
2897 .add_blocks(builder.blocks(15..=15))
2898 .unwrap()
2899 .is_empty()
2900 );
2901 assert_eq!(fixture.core.last_proposed_block().round(), 15);
2904
2905 set_leader(&mut fixture, excluded, 15);
2907 let proposed = fixture.core.try_propose(true).expect("No error").unwrap();
2908 assert_eq!(proposed.round(), 16);
2909 assert!(
2910 proposed.ancestors().iter().any(|a| a.author == excluded),
2911 "The proposal must include the leader block, even with its EXCLUDE state"
2912 );
2913 }
2914
2915 #[tokio::test]
2917 async fn test_smart_ancestor_selection_excludes_v2_leader() {
2918 telemetry_subscribers::init_for_testing();
2919 let (mut fixture, mut builder, excluded) = fixture_with_excluded_authority(false).await;
2920
2921 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2922 assert_eq!(block.round(), 15);
2923
2924 builder
2925 .layer(15)
2926 .authorities(vec![AuthorityIndex::new_for_test(0)])
2927 .skip_block()
2928 .build();
2929 assert!(
2930 fixture
2931 .add_blocks(builder.blocks(15..=15))
2932 .unwrap()
2933 .is_empty()
2934 );
2935
2936 assert_eq!(fixture.core.last_proposed_block().round(), 15);
2939
2940 *fixture.core.leader_schedule.leader_swap_table.write() = LeaderSwapTable::default();
2945 assert_eq!(fixture.core.committer.get_leaders(15), vec![excluded]);
2946
2947 let proposed = fixture.core.try_propose(true).expect("No error").unwrap();
2948 assert_eq!(proposed.round(), 16);
2949 assert!(
2950 proposed.ancestors().iter().all(|a| a.author != excluded),
2951 "V2 must not keep the EXCLUDE ancestor, even when it is the leader"
2952 );
2953 }
2954
2955 #[tokio::test]
2956 async fn test_excluded_ancestor_limit() {
2957 telemetry_subscribers::init_for_testing();
2958 let (context, _) = Context::new_for_test(4);
2959 let context = context.with_parameters(Parameters {
2960 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
2961 ..Default::default()
2962 });
2963 let mut fixture = CoreTestFixture::new(
2964 context,
2965 vec![1, 1, 1, 1],
2966 AuthorityIndex::new_for_test(0),
2967 true,
2968 )
2969 .await;
2970
2971 assert_eq!(
2973 fixture.core.last_proposed_round(),
2974 Some(GENESIS_ROUND),
2975 "No block should have been created other than genesis"
2976 );
2977
2978 let mut builder = DagBuilder::new(fixture.core.context.clone());
2980 builder.layers(1..=3).build();
2981
2982 builder
2986 .layer(4)
2987 .authorities(vec![AuthorityIndex::new_for_test(1)])
2988 .equivocate(9)
2989 .build();
2990 let blocks = builder.blocks(1..=4);
2991
2992 assert!(fixture.add_blocks(blocks).unwrap().is_empty());
2994 fixture.core.set_last_known_proposed_round(3);
2995
2996 let block = fixture.core.try_propose(true).expect("No error").unwrap();
2997 assert_eq!(block.round(), 5);
2998 assert_eq!(block.ancestors().len(), 4);
2999
3000 let extended_block =
3002 tokio::time::timeout(Duration::from_secs(1), fixture.block_receiver.recv())
3003 .await
3004 .unwrap()
3005 .unwrap();
3006 assert_eq!(extended_block.block.round(), 5);
3007 assert_eq!(
3008 extended_block.block.author(),
3009 fixture.core.context.own_index
3010 );
3011 assert_eq!(extended_block.block.ancestors().len(), 4);
3012 assert_eq!(extended_block.excluded_ancestors.len(), 8);
3013 }
3014
3015 #[tokio::test]
3016 async fn test_core_set_propagation_delay_per_authority() {
3017 telemetry_subscribers::init_for_testing();
3018 let (context, _) = Context::new_for_test(4);
3019 let mut fixture = CoreTestFixture::new(
3020 context,
3021 vec![1, 1, 1, 1],
3022 AuthorityIndex::new_for_test(0),
3023 false,
3024 )
3025 .await;
3026
3027 let test_block = VerifiedBlock::new_for_test(TestBlock::new(1000, 0).build());
3032 fixture
3033 .transaction_vote_tracker
3034 .add_voted_blocks(vec![(test_block.clone(), vec![])]);
3035 fixture.dag_state.write().accept_block(test_block);
3037
3038 fixture
3039 .core
3040 .round_tracker_for_tests()
3041 .write()
3042 .update_from_probe(
3043 vec![
3044 vec![0, 0, 0, 0],
3045 vec![0, 0, 0, 0],
3046 vec![0, 0, 0, 0],
3047 vec![0, 0, 0, 0],
3048 ],
3049 vec![
3050 vec![0, 0, 0, 0],
3051 vec![0, 0, 0, 0],
3052 vec![0, 0, 0, 0],
3053 vec![0, 0, 0, 0],
3054 ],
3055 );
3056
3057 assert!(fixture.core.try_propose(true).unwrap().is_none());
3059
3060 fixture
3064 .core
3065 .round_tracker_for_tests()
3066 .write()
3067 .update_from_probe(
3068 vec![
3069 vec![1000, 1000, 1000, 1000],
3070 vec![1000, 1000, 1000, 1000],
3071 vec![1000, 1000, 1000, 1000],
3072 vec![1000, 1000, 1000, 1000],
3073 ],
3074 vec![
3075 vec![1000, 1000, 1000, 1000],
3076 vec![1000, 1000, 1000, 1000],
3077 vec![1000, 1000, 1000, 1000],
3078 vec![1000, 1000, 1000, 1000],
3079 ],
3080 );
3081
3082 for author in 1..4 {
3085 let block = VerifiedBlock::new_for_test(TestBlock::new(1000, author).build());
3086 fixture
3087 .transaction_vote_tracker
3088 .add_voted_blocks(vec![(block.clone(), vec![])]);
3089 fixture.dag_state.write().accept_block(block);
3091 }
3092
3093 assert!(fixture.core.try_propose(true).unwrap().is_some());
3095 }
3096
3097 #[tokio::test(flavor = "current_thread", start_paused = true)]
3098 async fn test_leader_schedule_change() {
3099 telemetry_subscribers::init_for_testing();
3100 let default_params = Parameters::default();
3101
3102 let (context, _) = Context::new_for_test(4);
3103 let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
3105
3106 let mut last_round_blocks = Vec::new();
3108 for round in 1..=30 {
3109 let mut this_round_blocks = Vec::new();
3110
3111 sleep(default_params.min_round_delay).await;
3113
3114 for core_fixture in &mut cores {
3115 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
3118
3119 core_fixture
3120 .core
3121 .round_tracker_for_tests()
3122 .write()
3123 .update_from_probe(
3124 vec![
3125 vec![round, round, round, round],
3126 vec![round, round, round, round],
3127 vec![round, round, round, round],
3128 vec![round, round, round, round],
3129 ],
3130 vec![
3131 vec![round, round, round, round],
3132 vec![round, round, round, round],
3133 vec![round, round, round, round],
3134 vec![round, round, round, round],
3135 ],
3136 );
3137
3138 let new_round = receive(
3140 Duration::from_secs(1),
3141 core_fixture.signal_receivers.new_round_receiver(),
3142 )
3143 .await;
3144 assert_eq!(new_round, round);
3145
3146 let extended_block = tokio::time::timeout(
3148 Duration::from_secs(1),
3149 core_fixture.block_receiver.recv(),
3150 )
3151 .await
3152 .unwrap()
3153 .unwrap();
3154 assert_eq!(extended_block.block.round(), round);
3155 assert_eq!(
3156 extended_block.block.author(),
3157 core_fixture.core.context.own_index
3158 );
3159
3160 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
3162
3163 let block = core_fixture.core.last_proposed_block();
3164
3165 assert_eq!(
3167 block.ancestors().len(),
3168 core_fixture.core.context.committee.size()
3169 );
3170 for ancestor in block.ancestors() {
3171 if block.round() > 1 {
3172 assert!(
3174 last_round_blocks
3175 .iter()
3176 .any(|block| block.reference() == *ancestor),
3177 "Reference from previous round should be added"
3178 );
3179 }
3180 }
3181 }
3182
3183 last_round_blocks = this_round_blocks;
3184 }
3185
3186 for core_fixture in cores {
3187 core_fixture.dag_state.write().flush();
3189
3190 let last_commit = core_fixture
3192 .store
3193 .read_last_commit()
3194 .unwrap()
3195 .expect("last commit should be set");
3196 assert_eq!(last_commit.index(), 27);
3200 let all_stored_commits = core_fixture
3201 .store
3202 .scan_commits((0..=CommitIndex::MAX).into())
3203 .unwrap();
3204 assert_eq!(all_stored_commits.len(), 27);
3205 assert_eq!(
3206 core_fixture
3207 .core
3208 .leader_schedule
3209 .leader_swap_table
3210 .read()
3211 .bad_nodes
3212 .len(),
3213 1
3214 );
3215 assert_eq!(
3216 core_fixture
3217 .core
3218 .leader_schedule
3219 .leader_swap_table
3220 .read()
3221 .good_nodes
3222 .len(),
3223 1
3224 );
3225 let expected_reputation_scores =
3226 ReputationScores::new((11..=20).into(), vec![29, 29, 29, 29]);
3227 assert_eq!(
3228 core_fixture
3229 .core
3230 .leader_schedule
3231 .leader_swap_table
3232 .read()
3233 .reputation_scores,
3234 expected_reputation_scores
3235 );
3236 }
3237 }
3238
3239 #[tokio::test]
3240 async fn test_filter_new_commits() {
3241 telemetry_subscribers::init_for_testing();
3242
3243 let (context, _key_pairs) = Context::new_for_test(4);
3244 let context = context.with_parameters(Parameters {
3245 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3246 ..Default::default()
3247 });
3248
3249 let authority_index = AuthorityIndex::new_for_test(0);
3250 let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
3251 let mut core = core.core;
3252
3253 assert_eq!(
3255 core.last_proposed_round(),
3256 Some(GENESIS_ROUND),
3257 "No block should have been created other than genesis"
3258 );
3259
3260 let mut dag_builder = DagBuilder::new(core.context.clone());
3262 dag_builder.layers(1..=12).build();
3263
3264 dag_builder.print();
3266 let blocks = dag_builder.blocks(1..=6);
3267 core.dag_state.write().accept_blocks(blocks);
3268
3269 let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
3271
3272 let committed_sub_dags = core.try_commit(vec![]).unwrap();
3274
3275 assert_eq!(committed_sub_dags.len(), 4);
3277
3278 println!("Case 1. Provide certified commits that are all before the last committed round.");
3280
3281 let certified_commits = sub_dags_and_commits
3283 .iter()
3284 .take(4)
3285 .map(|(_, c)| c)
3286 .cloned()
3287 .collect::<Vec<_>>();
3288 assert!(
3289 certified_commits.last().unwrap().index()
3290 <= committed_sub_dags.last().unwrap().commit_ref.index,
3291 "Highest certified commit should older than the highest committed index."
3292 );
3293
3294 let certified_commits = core.filter_new_commits(certified_commits).unwrap();
3295
3296 assert!(certified_commits.is_empty());
3298
3299 println!("Case 2. Provide certified commits that are all after the last committed round.");
3300
3301 let certified_commits = sub_dags_and_commits
3303 .iter()
3304 .take(5)
3305 .map(|(_, c)| c.clone())
3306 .collect::<Vec<_>>();
3307
3308 let certified_commits = core.filter_new_commits(certified_commits.clone()).unwrap();
3309
3310 assert_eq!(certified_commits.len(), 1);
3312 assert_eq!(certified_commits.first().unwrap().reference().index, 5);
3313
3314 println!(
3315 "Case 3. Provide certified commits where the first certified commit index is not the last_commited_index + 1."
3316 );
3317
3318 let certified_commits = sub_dags_and_commits
3320 .iter()
3321 .skip(5)
3322 .take(1)
3323 .map(|(_, c)| c.clone())
3324 .collect::<Vec<_>>();
3325
3326 let err = core
3327 .filter_new_commits(certified_commits.clone())
3328 .unwrap_err();
3329 match err {
3330 ConsensusError::UnexpectedCertifiedCommitIndex {
3331 expected_commit_index: 5,
3332 commit_index: 6,
3333 } => (),
3334 _ => panic!("Unexpected error: {:?}", err),
3335 }
3336 }
3337
3338 #[tokio::test]
3339 async fn test_add_certified_commits() {
3340 telemetry_subscribers::init_for_testing();
3341
3342 let (context, _key_pairs) = Context::new_for_test(4);
3343 let context = context.with_parameters(Parameters {
3344 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3345 ..Default::default()
3346 });
3347
3348 let authority_index = AuthorityIndex::new_for_test(0);
3349 let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
3350 let store = core.store.clone();
3351 let mut core = core.core;
3352
3353 assert_eq!(
3355 core.last_proposed_round(),
3356 Some(GENESIS_ROUND),
3357 "No block should have been created other than genesis"
3358 );
3359
3360 let mut dag_builder = DagBuilder::new(core.context.clone());
3362 dag_builder.layers(1..=12).build();
3363
3364 dag_builder.print();
3366 let blocks = dag_builder.blocks(1..=6);
3367 core.dag_state.write().accept_blocks(blocks);
3368
3369 let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=10);
3371
3372 let committed_sub_dags = core.try_commit(vec![]).unwrap();
3374
3375 assert_eq!(committed_sub_dags.len(), 4);
3377
3378 core.dag_state.write().flush();
3380
3381 {
3382 println!("Case 1. Provide no certified commits. No commit should happen.");
3383 let committed_sub_dags = core.try_commit(vec![]).unwrap();
3385 assert!(committed_sub_dags.is_empty());
3386 let last_commit = store
3387 .read_last_commit()
3388 .unwrap()
3389 .expect("Last commit should be set");
3390 assert_eq!(last_commit.reference().index, 4);
3391 }
3392
3393 println!(
3394 "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."
3395 );
3396
3397 let certified_commits = sub_dags_and_commits
3399 .iter()
3400 .skip(3)
3401 .take(5)
3402 .map(|(_, c)| c.clone())
3403 .collect::<Vec<_>>();
3404
3405 let blocks = dag_builder.blocks(7..=12);
3407 core.dag_state.write().accept_blocks(blocks);
3408
3409 core.add_certified_commits(CertifiedCommits::new(certified_commits.clone(), vec![]))
3411 .expect("Should not fail");
3412
3413 core.dag_state.write().flush();
3415
3416 let commits = store.scan_commits((6..=10).into()).unwrap();
3417
3418 assert_eq!(commits.len(), 5);
3420
3421 for i in 6..=10 {
3422 let commit = &commits[i - 6];
3423 assert_eq!(commit.reference().index, i as u32);
3424 }
3425 }
3426
3427 #[tokio::test(start_paused = true)]
3428 async fn certified_commits_recover_after_flush_before_insertion() {
3429 use crate::linearizer::Linearizer;
3430
3431 let (_, builder) = parse_dag("DAG {
3434 Round 0 : { 7 },
3435 Round 1 : { A -> [*], B -> [*], C -> [*], D -> [*], E -> [*], F -> [*] },
3436 Round 2 : { A -> [-E1], B -> [-E1], C -> [-E1], D -> [-E1], F -> [-E1] },
3437 Round 3 : { A -> [*], B -> [*], C -> [*], D -> [*], F -> [*] },
3438 Round 4 : { A -> [*], B -> [*], C -> [*], D -> [*], F -> [*] },
3439 Round 5 : { A -> [*], B -> [*], C -> [*], D -> [*], E -> [A4, B4, C4, D4, F4, E1], F -> [*] },
3440 Round 6 : { A -> [*], B -> [*], C -> [*], D -> [*], E -> [*], F -> [*] },
3441 Round 7 : { A -> [*], B -> [*], C -> [*], D -> [*], E -> [*], F -> [*] }
3442 }").unwrap();
3443 let mut context = (*builder.context).clone();
3444 context.protocol_config.set_gc_depth_for_testing(4);
3445 context.protocol_config.set_enable_v3_for_testing(false);
3446 let own_index = AuthorityIndex::new_for_test(6);
3447 let block = |round, author| {
3448 builder
3449 .all_blocks()
3450 .into_iter()
3451 .find(|b| b.round() == round && b.author() == AuthorityIndex::new_for_test(author))
3452 .unwrap()
3453 };
3454 let e1 = block(1, 4);
3455 let e5 = block(5, 4);
3456 let leaders = vec![
3457 block(1, 1),
3458 block(2, 2),
3459 block(3, 3),
3460 block(5, 5),
3461 block(7, 0),
3462 ];
3463
3464 let source_store = Arc::new(MemStore::new());
3465 let source_dag = Arc::new(RwLock::new(DagState::new(
3466 Arc::new(context.clone()),
3467 source_store.clone(),
3468 )));
3469 source_dag.write().accept_blocks(builder.all_blocks());
3470 let subdags =
3471 Linearizer::new(Arc::new(context.clone()), source_dag.clone()).handle_commit(leaders);
3472 source_dag.write().flush();
3473 let certified_commits = source_store
3474 .scan_commits((1..=subdags.len() as u32).into())
3475 .unwrap()
3476 .into_iter()
3477 .zip_eq(&subdags)
3478 .map(|(commit, subdag)| CertifiedCommit::new_certified(commit, subdag.blocks.clone()))
3479 .collect::<Vec<_>>();
3480 assert!(subdags.iter().all(|subdag| {
3481 subdag
3482 .blocks
3483 .iter()
3484 .all(|b| b.reference() != e1.reference())
3485 }));
3486 assert!(
3487 subdags
3488 .last()
3489 .unwrap()
3490 .blocks
3491 .iter()
3492 .any(|b| b.reference() == e5.reference())
3493 );
3494
3495 for crash_before_index in 2..=certified_commits.len() {
3496 let mut fixture =
3497 CoreTestFixture::new(context.clone(), vec![1; 7], own_index, true).await;
3498 fixture.transaction_vote_tracker.add_voted_blocks(
3500 certified_commits
3501 .iter()
3502 .flat_map(|commit| commit.blocks().iter().cloned())
3503 .map(|block| (block, vec![]))
3504 .collect(),
3505 );
3506 let (_, missing) = fixture.core.accept_blocks(
3507 builder
3508 .all_blocks()
3509 .into_iter()
3510 .filter(|block| block.round() <= 3 && block.reference() != e1.reference())
3511 .collect(),
3512 );
3513 assert!(missing.is_empty());
3514 let prior = fixture.core.try_commit(vec![]).unwrap();
3515 assert_eq!(prior.len(), 1);
3516 assert!(prior[0].decided_with_local_blocks);
3517 assert_eq!(prior[0].commit_ref, certified_commits[0].reference());
3518
3519 let dag = fixture.dag_state.clone();
3522 let accepted_leader = certified_commits[crash_before_index - 1].leader();
3523 let finalized_commit_ref = prior[0].commit_ref;
3524 let test_thread = std::thread::current().id();
3525 sui_macros::register_fail_point("consensus-after-accept-certified-blocks", move || {
3526 if std::thread::current().id() != test_thread {
3527 return;
3528 }
3529 let mut dag = dag.write();
3530 if dag.last_commit_index() != crash_before_index as u32 - 1 {
3531 return;
3532 }
3533 assert!(dag.contains_block(&accepted_leader));
3534 dag.add_finalized_commit(finalized_commit_ref, BTreeMap::new());
3535 dag.flush();
3536 panic!("crash after certified block acceptance");
3537 });
3538 let crash = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3539 fixture.core.try_commit(certified_commits[1..].to_vec())
3540 }));
3541 sui_macros::clear_fail_point("consensus-after-accept-certified-blocks");
3542 assert_eq!(
3543 crash.unwrap_err().downcast_ref::<&str>(),
3544 Some(&"crash after certified block acceptance")
3545 );
3546 let store = fixture.store.clone();
3547 assert_eq!(
3548 store.read_last_commit().unwrap().unwrap().index(),
3549 crash_before_index as u32 - 1
3550 );
3551 fixture.core.stop().await;
3553 drop(fixture);
3554
3555 let mut recovered = CoreTestFixture::new_with_store(
3558 context.clone(),
3559 vec![1; 7],
3560 own_index,
3561 true,
3562 store.clone(),
3563 )
3564 .await;
3565 assert!(!recovered.dag_state.read().contains_block(&e1.reference()));
3566 let remaining = recovered
3567 .core
3568 .filter_new_commits(certified_commits.clone())
3569 .unwrap();
3570 recovered.transaction_vote_tracker.add_voted_blocks(
3571 remaining
3572 .iter()
3573 .flat_map(|commit| commit.blocks().iter().cloned())
3574 .map(|block| (block, vec![]))
3575 .collect(),
3576 );
3577 let resumed = recovered.core.try_commit(remaining).unwrap();
3578 assert!(
3579 resumed
3580 .iter()
3581 .all(|subdag| !subdag.decided_with_local_blocks)
3582 );
3583 recovered.dag_state.write().flush();
3584 let commits = store
3585 .scan_commits((1..=certified_commits.len() as u32).into())
3586 .unwrap();
3587 assert_eq!(commits.len(), certified_commits.len());
3588 for (actual, expected) in commits.iter().zip_eq(&certified_commits) {
3589 assert_eq!(actual.reference(), expected.reference());
3590 }
3591 recovered.core.stop().await;
3592 }
3593 }
3594
3595 #[tokio::test]
3596 async fn try_commit_v3_local_commits() {
3597 telemetry_subscribers::init_for_testing();
3598
3599 let (mut context, _key_pairs) = Context::new_for_test(4);
3600 context.protocol_config.set_enable_v3_for_testing(true);
3601 let context = context.with_parameters(Parameters {
3602 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3603 ..Default::default()
3604 });
3605
3606 let authority_index = AuthorityIndex::new_for_test(0);
3607 let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
3608 let monitor = core.commit_consumer_monitor.clone();
3609 let mut core = core.core;
3610
3611 let mut dag_builder = DagBuilder::new(core.context.clone());
3613 dag_builder.layers(1..=12).build();
3614 core.dag_state
3615 .write()
3616 .accept_blocks(dag_builder.blocks(1..=12));
3617
3618 let committed = core.try_commit_v3().unwrap();
3619
3620 assert_eq!(committed.len(), 11);
3623 for (i, subdag) in committed.iter().enumerate() {
3624 assert_eq!(subdag.commit_ref.index, (i + 1) as CommitIndex);
3625 assert_eq!(subdag.leader.round, (i + 1) as Round);
3626 assert!(subdag.decided_with_local_blocks);
3627 }
3628 assert_eq!(core.dag_state.read().last_commit_index(), 11);
3629 assert_eq!(monitor.progress().highest_committed_index, 11);
3630 assert_eq!(monitor.progress().highest_committed_round, 11);
3631 assert_eq!(monitor.highest_handled_commit(), 0);
3632
3633 assert!(core.try_commit_v3().unwrap().is_empty());
3635 }
3636
3637 #[tokio::test]
3638 async fn add_certified_commits_v3() {
3639 telemetry_subscribers::init_for_testing();
3640
3641 let (mut context, _key_pairs) = Context::new_for_test(4);
3642 context.protocol_config.set_enable_v3_for_testing(true);
3643 let context = context.with_parameters(Parameters {
3644 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3645 ..Default::default()
3646 });
3647
3648 let authority_index = AuthorityIndex::new_for_test(0);
3649 let core = CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
3650 let store = core.store.clone();
3651 let mut core = core.core;
3652
3653 let mut dag_builder = DagBuilder::new(core.context.clone());
3654 dag_builder.layers(1..=12).build();
3655
3656 let certified_commits = dag_builder
3659 .get_sub_dag_and_certified_commits(1..=5)
3660 .into_iter()
3661 .map(|(_, c)| c)
3662 .collect::<Vec<_>>();
3663
3664 core.add_certified_commits(CertifiedCommits::new(certified_commits.clone(), vec![]))
3665 .expect("Should not fail");
3666
3667 assert_eq!(core.dag_state.read().last_commit_index(), 5);
3668 assert_eq!(
3669 core.context
3670 .metrics
3671 .node_metrics
3672 .core_certified_commits_processed
3673 .with_label_values(&["committed"])
3674 .get(),
3675 5
3676 );
3677
3678 core.dag_state.write().flush();
3680 let commits = store.scan_commits((1..=5).into()).unwrap();
3681 assert_eq!(commits.len(), 5);
3682 for (i, commit) in commits.iter().enumerate() {
3683 assert_eq!(commit.reference(), certified_commits[i].reference());
3684 }
3685
3686 core.dag_state
3689 .write()
3690 .accept_blocks(dag_builder.blocks(1..=12));
3691 let committed = core.try_commit_v3().unwrap();
3692 assert!(!committed.is_empty());
3693 assert!(committed.iter().all(|s| s.decided_with_local_blocks));
3694 assert_eq!(committed.last().unwrap().commit_ref.index, 11);
3695 assert_eq!(core.dag_state.read().last_commit_index(), 11);
3696
3697 core.add_certified_commits(CertifiedCommits::new(certified_commits, vec![]))
3699 .expect("Should not fail");
3700 assert_eq!(core.dag_state.read().last_commit_index(), 11);
3701 assert_eq!(
3702 core.context
3703 .metrics
3704 .node_metrics
3705 .core_certified_commits_processed
3706 .with_label_values(&["skipped"])
3707 .get(),
3708 5
3709 );
3710 }
3711
3712 #[tokio::test]
3713 async fn add_multi_leader_certified_commits_v3() {
3714 const PREFIX_LAST_ROUND: Round = 5;
3715 const INITIAL_DAG_LAST_ROUND: Round = PREFIX_LAST_ROUND + 1;
3716 const CONTINUATION_DAG_LAST_ROUND: Round = INITIAL_DAG_LAST_ROUND + 2;
3717
3718 telemetry_subscribers::init_for_testing();
3719
3720 let (mut context, _key_pairs) = Context::new_for_test(4);
3721 context.protocol_config.set_enable_v3_for_testing(true);
3722 let context = context.with_parameters(Parameters {
3723 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3724 ..Default::default()
3725 });
3726 let authority_index = AuthorityIndex::new_for_test(0);
3727
3728 let source_fixture =
3731 CoreTestFixture::new(context.clone(), vec![1, 1, 1, 1], authority_index, true).await;
3732 let source_store = source_fixture.store.clone();
3733 let mut source = source_fixture.core;
3734
3735 let receiver_fixture =
3736 CoreTestFixture::new(context, vec![1, 1, 1, 1], authority_index, true).await;
3737 let receiver_store = receiver_fixture.store.clone();
3738 let mut receiver = receiver_fixture.core;
3739
3740 let next_commit_leaders = source
3741 .leader_schedule_v3
3742 .as_ref()
3743 .expect("LeaderScheduleV3 must be initialized")
3744 .next_commit_leader_schedule();
3745 assert!(next_commit_leaders.num_leaders() > 1);
3746 assert!(
3747 source
3748 .context
3749 .protocol_config
3750 .leader_schedule_update_interval()
3751 > PREFIX_LAST_ROUND
3752 );
3753 let scheduled_leaders = next_commit_leaders
3754 .allowed_leaders
3755 .iter()
3756 .copied()
3757 .collect::<BTreeSet<_>>();
3758
3759 let mut dag_builder = DagBuilder::new(source.context.clone());
3760 dag_builder.layers(1..=INITIAL_DAG_LAST_ROUND).build();
3761 source
3762 .dag_state
3763 .write()
3764 .accept_blocks(dag_builder.blocks(1..=INITIAL_DAG_LAST_ROUND));
3765
3766 let source_subdags = source.try_commit_v3().unwrap();
3767 assert_eq!(source_subdags.len(), PREFIX_LAST_ROUND as usize);
3769 source.dag_state.write().flush();
3770 let source_commits = source_store
3771 .scan_commits((1..=PREFIX_LAST_ROUND).into())
3772 .unwrap();
3773 assert_eq!(source_commits.len(), source_subdags.len());
3774
3775 let source_scores_at_prefix = source.current_reputation_scores();
3776 assert_ne!(source_scores_at_prefix, ReputationScores::default());
3777
3778 let certified_leader_counts = |core: &Core| {
3779 core.context
3780 .committee
3781 .authorities()
3782 .map(|(authority_index, authority)| {
3783 (
3784 authority_index,
3785 core.context
3786 .metrics
3787 .node_metrics
3788 .committed_leaders_total
3789 .with_label_values(&[&authority.hostname, "certified-commit"])
3790 .get(),
3791 )
3792 })
3793 .collect::<BTreeMap<_, _>>()
3794 };
3795 let certified_leaders_before = certified_leader_counts(&receiver);
3798
3799 let mut source_subdags_by_commit = source_subdags
3800 .into_iter()
3801 .map(|subdag| (subdag.commit_ref, subdag))
3802 .collect::<BTreeMap<_, _>>();
3803 let mut expected_certified_leaders = BTreeMap::<AuthorityIndex, u64>::new();
3804 let mut expected_receiver_subdags = Vec::new();
3805 let mut has_multi_leader_commit = false;
3806 let certified_commits = source_commits
3807 .iter()
3808 .map(|commit| {
3809 let subdag = source_subdags_by_commit
3810 .remove(&commit.reference())
3811 .expect("Every source commit must have a matching subdag");
3812 let block_refs = subdag
3813 .blocks
3814 .iter()
3815 .map(|block| block.reference())
3816 .collect::<Vec<_>>();
3817 assert_eq!(block_refs, commit.blocks());
3818
3819 let leader_blocks = subdag
3821 .blocks
3822 .iter()
3823 .filter(|block| block.round() == commit.leader().round)
3824 .map(|block| block.reference())
3825 .collect::<Vec<_>>();
3826 assert!(leader_blocks.contains(&commit.leader()));
3827 assert!(
3828 leader_blocks
3829 .iter()
3830 .all(|block| scheduled_leaders.contains(&block.author))
3831 );
3832 has_multi_leader_commit |= leader_blocks.len() > 1;
3833 for block in leader_blocks {
3834 *expected_certified_leaders.entry(block.author).or_default() += 1;
3835 }
3836
3837 let mut expected_receiver_subdag = subdag.clone();
3838 expected_receiver_subdag.decided_with_local_blocks = false;
3839 expected_receiver_subdags.push(expected_receiver_subdag);
3840
3841 CertifiedCommit::new_certified(commit.clone(), subdag.blocks)
3844 })
3845 .collect::<Vec<_>>();
3846 assert!(source_subdags_by_commit.is_empty());
3847 assert!(has_multi_leader_commit);
3848 assert!(expected_certified_leaders.values().sum::<u64>() > source_commits.len() as u64);
3849
3850 let receiver_subdags = receiver
3851 .process_certified_commits(certified_commits)
3852 .expect("Certified commits should be accepted");
3853 assert_eq!(receiver_subdags, expected_receiver_subdags);
3854 assert!(receiver.try_commit_v3().unwrap().is_empty());
3855 assert_eq!(
3856 receiver.dag_state.read().last_commit_index(),
3857 PREFIX_LAST_ROUND
3858 );
3859 assert_eq!(
3860 receiver.current_reputation_scores(),
3861 source_scores_at_prefix
3862 );
3863
3864 receiver.dag_state.write().flush();
3865 let receiver_commits = receiver_store
3866 .scan_commits((1..=PREFIX_LAST_ROUND).into())
3867 .unwrap();
3868 assert_eq!(receiver_commits, source_commits);
3869 let certified_leaders_after = certified_leader_counts(&receiver);
3870 for (authority_index, expected) in expected_certified_leaders {
3871 assert_eq!(
3872 certified_leaders_after[&authority_index]
3873 - certified_leaders_before[&authority_index],
3874 expected
3875 );
3876 }
3877
3878 dag_builder
3881 .layers(INITIAL_DAG_LAST_ROUND + 1..=CONTINUATION_DAG_LAST_ROUND)
3882 .build();
3883 source.dag_state.write().accept_blocks(
3884 dag_builder.blocks(INITIAL_DAG_LAST_ROUND + 1..=CONTINUATION_DAG_LAST_ROUND),
3885 );
3886 receiver
3889 .dag_state
3890 .write()
3891 .accept_blocks(dag_builder.blocks(1..=CONTINUATION_DAG_LAST_ROUND));
3892
3893 let source_continuation = source.try_commit_v3().unwrap();
3894 let receiver_continuation = receiver.try_commit_v3().unwrap();
3895 assert_eq!(
3896 source_continuation
3897 .iter()
3898 .map(|subdag| subdag.leader.round)
3899 .collect::<Vec<_>>(),
3900 vec![PREFIX_LAST_ROUND + 1, PREFIX_LAST_ROUND + 2]
3901 );
3902 assert_eq!(receiver_continuation, source_continuation);
3903
3904 source.dag_state.write().flush();
3905 receiver.dag_state.write().flush();
3906 let first_continuation_index = PREFIX_LAST_ROUND + 1;
3907 let last_continuation_index = source_continuation.last().unwrap().commit_ref.index;
3908 let source_continuation_commits = source_store
3909 .scan_commits((first_continuation_index..=last_continuation_index).into())
3910 .unwrap();
3911 let receiver_continuation_commits = receiver_store
3912 .scan_commits((first_continuation_index..=last_continuation_index).into())
3913 .unwrap();
3914 assert_eq!(receiver_continuation_commits, source_continuation_commits);
3915 assert_eq!(
3916 receiver_continuation_commits[0].previous_digest(),
3917 source_commits.last().unwrap().digest()
3918 );
3919 }
3920
3921 #[tokio::test]
3922 async fn add_certified_commits_v3_gced_blocks() {
3923 const GC_DEPTH: u32 = 3;
3924 telemetry_subscribers::init_for_testing();
3925
3926 let (mut context, _key_pairs) = Context::new_for_test(5);
3927 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
3928 context.protocol_config.set_enable_v3_for_testing(true);
3929 let context = context.with_parameters(Parameters {
3930 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
3931 ..Default::default()
3932 });
3933
3934 let authority_index = AuthorityIndex::new_for_test(0);
3935 let core = CoreTestFixture::new(context, vec![1, 1, 1, 1, 1], authority_index, true).await;
3936 let store = core.store.clone();
3937 let mut core = core.core;
3938
3939 let dag_str = "DAG {
3943 Round 0 : { 5 },
3944 Round 1 : { * },
3945 Round 2 : {
3946 A -> [-E1],
3947 B -> [-E1],
3948 C -> [-E1],
3949 D -> [-E1],
3950 },
3951 Round 3 : {
3952 A -> [*],
3953 B -> [*],
3954 C -> [*],
3955 D -> [*],
3956 },
3957 Round 4 : {
3958 A -> [*],
3959 B -> [*],
3960 C -> [*],
3961 D -> [*],
3962 },
3963 Round 5 : {
3964 A -> [*],
3965 B -> [*],
3966 C -> [*],
3967 D -> [*],
3968 E -> [A4, B4, C4, D4, E1]
3969 },
3970 Round 6 : { * },
3971 Round 7 : { * },
3972 }";
3973
3974 let (_, mut dag_builder) = parse_dag(dag_str).expect("Invalid dag");
3975
3976 {
3981 let mut builder_context = (*dag_builder.context).clone();
3982 builder_context
3983 .protocol_config
3984 .set_gc_depth_for_testing(GC_DEPTH);
3985 dag_builder.context = Arc::new(builder_context);
3986 }
3987
3988 let certified_commits = dag_builder
3992 .get_sub_dag_and_certified_commits(1..=6)
3993 .into_iter()
3994 .map(|(_, c)| c)
3995 .collect::<Vec<_>>();
3996 let num_certified = certified_commits.len() as CommitIndex;
3997 assert!(num_certified > 0);
3998
3999 core.add_certified_commits(CertifiedCommits::new(certified_commits, vec![]))
4000 .expect("Should not fail");
4001
4002 assert_eq!(core.dag_state.read().last_commit_index(), num_certified);
4003
4004 core.dag_state.write().flush();
4009 let commits = store.scan_commits((1..=num_certified).into()).unwrap();
4010 assert_eq!(commits.len(), num_certified as usize);
4011 let authority_e = AuthorityIndex::new_for_test(4);
4012 let mut e5_committed = false;
4013 for commit in &commits {
4014 for block_ref in commit.blocks() {
4015 assert!(
4016 !(block_ref.round == 1 && block_ref.author == authority_e),
4017 "Did not expect to commit block E1"
4018 );
4019 if block_ref.round == 5 && block_ref.author == authority_e {
4020 e5_committed = true;
4021 }
4022 }
4023 }
4024 assert!(e5_committed, "Expected block E5 to be committed");
4025 }
4026
4027 #[tokio::test]
4028 async fn test_core_recover_v3_keeps_zero_scores_unavailable() {
4029 telemetry_subscribers::init_for_testing();
4030
4031 let (mut context, _key_pairs) = Context::new_for_test(4);
4032 context.protocol_config.set_enable_v3_for_testing(true);
4033 let context = context.with_parameters(Parameters {
4034 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
4035 ..Default::default()
4036 });
4037 let store = Arc::new(MemStore::new());
4038 let authority_index = AuthorityIndex::new_for_test(0);
4039
4040 {
4041 let mut fixture = CoreTestFixture::new_with_store(
4042 context.clone(),
4043 vec![1, 1, 1, 1],
4044 authority_index,
4045 true,
4046 store.clone(),
4047 )
4048 .await;
4049 assert_eq!(
4050 fixture.core.current_reputation_scores(),
4051 ReputationScores::default()
4052 );
4053
4054 let mut dag_builder = DagBuilder::new(fixture.core.context.clone());
4055 dag_builder.layers(1..=4).build();
4056 fixture
4057 .dag_state
4058 .write()
4059 .accept_blocks(dag_builder.blocks(1..=4));
4060 assert_eq!(fixture.core.try_commit_v3().unwrap().len(), 3);
4061 assert_eq!(
4062 fixture.core.current_reputation_scores(),
4063 ReputationScores::default()
4064 );
4065 fixture.dag_state.write().flush();
4066 }
4067
4068 let fixture = CoreTestFixture::new_with_store(
4069 context,
4070 vec![1, 1, 1, 1],
4071 authority_index,
4072 true,
4073 store,
4074 )
4075 .await;
4076 assert_eq!(fixture.dag_state.read().last_commit_index(), 3);
4077 assert_eq!(
4078 fixture.core.current_reputation_scores(),
4079 ReputationScores::default()
4080 );
4081 }
4082
4083 #[tokio::test]
4084 async fn test_core_recover_from_store_v3() {
4085 telemetry_subscribers::init_for_testing();
4086
4087 let (mut context, _key_pairs) = Context::new_for_test(4);
4088 context.protocol_config.set_enable_v3_for_testing(true);
4089 let context = context.with_parameters(Parameters {
4090 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
4091 ..Default::default()
4092 });
4093 let store = Arc::new(MemStore::new());
4094 let authority_index = AuthorityIndex::new_for_test(0);
4095
4096 let (last_commit_index, last_committed_rounds) = {
4098 let mut fixture = CoreTestFixture::new_with_store(
4099 context.clone(),
4100 vec![1, 1, 1, 1],
4101 authority_index,
4102 true,
4103 store.clone(),
4104 )
4105 .await;
4106 let mut dag_builder = DagBuilder::new(fixture.core.context.clone());
4107 dag_builder.layers(1..=12).build();
4108 fixture
4109 .dag_state
4110 .write()
4111 .accept_blocks(dag_builder.blocks(1..=12));
4112 let committed = fixture.core.try_commit_v3().unwrap();
4113 assert!(!committed.is_empty());
4114 fixture.dag_state.write().flush();
4115 (
4116 fixture.dag_state.read().last_commit_index(),
4117 fixture.dag_state.read().last_committed_rounds(),
4118 )
4119 };
4120
4121 let fixture = CoreTestFixture::new_with_store(
4126 context,
4127 vec![1, 1, 1, 1],
4128 authority_index,
4129 true,
4130 store,
4131 )
4132 .await;
4133 assert_eq!(
4134 fixture.dag_state.read().last_commit_index(),
4135 last_commit_index
4136 );
4137 assert_eq!(
4138 fixture.dag_state.read().last_committed_rounds(),
4139 last_committed_rounds
4140 );
4141 assert_eq!(fixture.dag_state.read().scoring_subdags_count(), 0);
4142 }
4143
4144 #[tokio::test]
4145 async fn try_commit_with_certified_commits_gced_blocks() {
4146 const GC_DEPTH: u32 = 3;
4147 telemetry_subscribers::init_for_testing();
4148
4149 let (mut context, mut key_pairs) = Context::new_for_test(5);
4150 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
4151 let context = Arc::new(context.with_parameters(Parameters {
4152 sync_last_known_own_block_timeout: Duration::from_millis(2_000),
4153 ..Default::default()
4154 }));
4155
4156 let store = Arc::new(MemStore::new());
4157 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
4158
4159 let block_manager = BlockManager::new(context.clone(), dag_state.clone());
4160 let leader_schedule = Arc::new(
4161 LeaderSchedule::from_store(context.clone(), dag_state.clone())
4162 .with_num_commits_per_schedule(10),
4163 );
4164
4165 let (_transaction_client, tx_receiver, priority_tx_receiver) =
4166 TransactionClient::new(context.clone());
4167 let transaction_pool = Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
4168 tx_receiver,
4169 priority_tx_receiver,
4170 context.clone(),
4171 )));
4172 let (signals, signal_receivers) = CoreSignals::new(context.clone());
4173 let transaction_vote_tracker = TransactionVoteTracker::new(
4174 context.clone(),
4175 Arc::new(NoopBlockVerifier {}),
4176 dag_state.clone(),
4177 );
4178 let _block_receiver = signal_receivers.block_broadcast_receiver();
4180
4181 let (commit_consumer, _commit_receiver) = CommitConsumerArgs::new(0, 0);
4182 let commit_observer = CommitObserver::new(
4183 context.clone(),
4184 commit_consumer,
4185 dag_state.clone(),
4186 transaction_vote_tracker.clone(),
4187 )
4188 .await;
4189
4190 let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
4191 let mut core = Core::new_validator(
4192 context.clone(),
4193 leader_schedule,
4194 transaction_pool,
4195 transaction_vote_tracker.clone(),
4196 block_manager,
4197 commit_observer,
4198 signals,
4199 key_pairs.remove(context.own_index.value()).1,
4200 dag_state.clone(),
4201 true,
4202 round_tracker,
4203 );
4204
4205 assert_eq!(
4207 core.last_proposed_round(),
4208 Some(GENESIS_ROUND),
4209 "No block should have been created other than genesis"
4210 );
4211
4212 let dag_str = "DAG {
4213 Round 0 : { 5 },
4214 Round 1 : { * },
4215 Round 2 : {
4216 A -> [-E1],
4217 B -> [-E1],
4218 C -> [-E1],
4219 D -> [-E1],
4220 },
4221 Round 3 : {
4222 A -> [*],
4223 B -> [*],
4224 C -> [*],
4225 D -> [*],
4226 },
4227 Round 4 : {
4228 A -> [*],
4229 B -> [*],
4230 C -> [*],
4231 D -> [*],
4232 },
4233 Round 5 : {
4234 A -> [*],
4235 B -> [*],
4236 C -> [*],
4237 D -> [*],
4238 E -> [A4, B4, C4, D4, E1]
4239 },
4240 Round 6 : { * },
4241 Round 7 : { * },
4242 }";
4243
4244 let (_, mut dag_builder) = parse_dag(dag_str).expect("Invalid dag");
4245 dag_builder.print();
4246
4247 let (_sub_dags, certified_commits): (Vec<_>, Vec<_>) = dag_builder
4249 .get_sub_dag_and_certified_commits(1..=5)
4250 .into_iter()
4251 .unzip();
4252
4253 let committed_sub_dags = core.try_commit(certified_commits).unwrap();
4256
4257 assert_eq!(committed_sub_dags.len(), 4);
4259 for (index, committed_sub_dag) in committed_sub_dags.iter().enumerate() {
4260 assert_eq!(committed_sub_dag.commit_ref.index as usize, index + 1);
4261
4262 for block in committed_sub_dag.blocks.iter() {
4264 if block.round() == 1 && block.author() == AuthorityIndex::new_for_test(5) {
4265 panic!("Did not expect to commit block E1");
4266 }
4267 }
4268 }
4269 }
4270
4271 #[tokio::test(flavor = "current_thread", start_paused = true)]
4272 async fn test_commit_on_leader_schedule_change_boundary_without_multileader() {
4273 parameterized_test_commit_on_leader_schedule_change_boundary(Some(1)).await;
4274 }
4275
4276 #[tokio::test(flavor = "current_thread", start_paused = true)]
4277 async fn test_commit_on_leader_schedule_change_boundary_with_multileader() {
4278 parameterized_test_commit_on_leader_schedule_change_boundary(None).await;
4279 }
4280
4281 async fn parameterized_test_commit_on_leader_schedule_change_boundary(
4282 num_leaders_per_round: Option<usize>,
4283 ) {
4284 telemetry_subscribers::init_for_testing();
4285 let default_params = Parameters::default();
4286
4287 let (mut context, _) = Context::new_for_test(6);
4288 context
4289 .protocol_config
4290 .set_num_leaders_per_round_for_testing(num_leaders_per_round);
4291 let mut cores = create_cores(context, vec![1, 1, 1, 1, 1, 1]).await;
4293
4294 let mut last_round_blocks: Vec<VerifiedBlock> = Vec::new();
4296 for round in 1..=33 {
4297 let mut this_round_blocks = Vec::new();
4298
4299 sleep(default_params.min_round_delay).await;
4301
4302 for core_fixture in &mut cores {
4303 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
4306
4307 core_fixture
4308 .core
4309 .round_tracker_for_tests()
4310 .write()
4311 .update_from_probe(
4312 vec![
4313 vec![round, round, round, round, round, round],
4314 vec![round, round, round, round, round, round],
4315 vec![round, round, round, round, round, round],
4316 vec![round, round, round, round, round, round],
4317 vec![round, round, round, round, round, round],
4318 vec![round, round, round, round, round, round],
4319 ],
4320 vec![
4321 vec![round, round, round, round, round, round],
4322 vec![round, round, round, round, round, round],
4323 vec![round, round, round, round, round, round],
4324 vec![round, round, round, round, round, round],
4325 vec![round, round, round, round, round, round],
4326 vec![round, round, round, round, round, round],
4327 ],
4328 );
4329
4330 let new_round = receive(
4332 Duration::from_secs(1),
4333 core_fixture.signal_receivers.new_round_receiver(),
4334 )
4335 .await;
4336 assert_eq!(new_round, round);
4337
4338 let extended_block = tokio::time::timeout(
4340 Duration::from_secs(1),
4341 core_fixture.block_receiver.recv(),
4342 )
4343 .await
4344 .unwrap()
4345 .unwrap();
4346 assert_eq!(extended_block.block.round(), round);
4347 assert_eq!(
4348 extended_block.block.author(),
4349 core_fixture.core.context.own_index
4350 );
4351
4352 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
4354
4355 let block = core_fixture.core.last_proposed_block();
4356
4357 assert_eq!(
4359 block.ancestors().len(),
4360 core_fixture.core.context.committee.size()
4361 );
4362 for ancestor in block.ancestors() {
4363 if block.round() > 1 {
4364 assert!(
4366 last_round_blocks
4367 .iter()
4368 .any(|block| block.reference() == *ancestor),
4369 "Reference from previous round should be added"
4370 );
4371 }
4372 }
4373 }
4374
4375 last_round_blocks = this_round_blocks;
4376 }
4377
4378 for core_fixture in cores {
4379 let expected_commit_count = match num_leaders_per_round {
4391 Some(1) => 30,
4392 _ => 31,
4393 };
4394
4395 core_fixture.dag_state.write().flush();
4397
4398 let last_commit = core_fixture
4400 .store
4401 .read_last_commit()
4402 .unwrap()
4403 .expect("last commit should be set");
4404 assert_eq!(last_commit.index(), expected_commit_count);
4405 let all_stored_commits = core_fixture
4406 .store
4407 .scan_commits((0..=CommitIndex::MAX).into())
4408 .unwrap();
4409 assert_eq!(all_stored_commits.len(), expected_commit_count as usize);
4410 assert_eq!(
4411 core_fixture
4412 .core
4413 .leader_schedule
4414 .leader_swap_table
4415 .read()
4416 .bad_nodes
4417 .len(),
4418 1
4419 );
4420 assert_eq!(
4421 core_fixture
4422 .core
4423 .leader_schedule
4424 .leader_swap_table
4425 .read()
4426 .good_nodes
4427 .len(),
4428 1
4429 );
4430 let expected_reputation_scores =
4431 ReputationScores::new((21..=30).into(), vec![43, 43, 43, 43, 43, 43]);
4432 assert_eq!(
4433 core_fixture
4434 .core
4435 .leader_schedule
4436 .leader_swap_table
4437 .read()
4438 .reputation_scores,
4439 expected_reputation_scores
4440 );
4441 }
4442 }
4443
4444 #[tokio::test]
4445 async fn test_core_signals() {
4446 telemetry_subscribers::init_for_testing();
4447 let default_params = Parameters::default();
4448
4449 let (context, _) = Context::new_for_test(4);
4450 let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
4452
4453 let mut last_round_blocks = Vec::new();
4455 for round in 1..=10 {
4456 let mut this_round_blocks = Vec::new();
4457
4458 sleep(default_params.min_round_delay).await;
4460
4461 for core_fixture in &mut cores {
4462 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
4465
4466 core_fixture
4467 .core
4468 .round_tracker_for_tests()
4469 .write()
4470 .update_from_probe(
4471 vec![
4472 vec![round, round, round, round],
4473 vec![round, round, round, round],
4474 vec![round, round, round, round],
4475 vec![round, round, round, round],
4476 ],
4477 vec![
4478 vec![round, round, round, round],
4479 vec![round, round, round, round],
4480 vec![round, round, round, round],
4481 vec![round, round, round, round],
4482 ],
4483 );
4484
4485 let new_round = receive(
4487 Duration::from_secs(1),
4488 core_fixture.signal_receivers.new_round_receiver(),
4489 )
4490 .await;
4491 assert_eq!(new_round, round);
4492
4493 let extended_block = tokio::time::timeout(
4495 Duration::from_secs(1),
4496 core_fixture.block_receiver.recv(),
4497 )
4498 .await
4499 .unwrap()
4500 .unwrap();
4501 assert_eq!(extended_block.block.round(), round);
4502 assert_eq!(
4503 extended_block.block.author(),
4504 core_fixture.core.context.own_index
4505 );
4506
4507 this_round_blocks.push(core_fixture.core.last_proposed_block().clone());
4509
4510 let block = core_fixture.core.last_proposed_block();
4511
4512 assert_eq!(
4514 block.ancestors().len(),
4515 core_fixture.core.context.committee.size()
4516 );
4517 for ancestor in block.ancestors() {
4518 if block.round() > 1 {
4519 assert!(
4521 last_round_blocks
4522 .iter()
4523 .any(|block| block.reference() == *ancestor),
4524 "Reference from previous round should be added"
4525 );
4526 }
4527 }
4528 }
4529
4530 last_round_blocks = this_round_blocks;
4531 }
4532
4533 for core_fixture in cores {
4534 core_fixture.dag_state.write().flush();
4536 let last_commit = core_fixture
4538 .store
4539 .read_last_commit()
4540 .unwrap()
4541 .expect("last commit should be set");
4542 assert_eq!(last_commit.index(), 7);
4546 let all_stored_commits = core_fixture
4547 .store
4548 .scan_commits((0..=CommitIndex::MAX).into())
4549 .unwrap();
4550 assert_eq!(all_stored_commits.len(), 7);
4551 }
4552 }
4553
4554 #[tokio::test]
4555 async fn test_core_compress_proposal_references() {
4556 telemetry_subscribers::init_for_testing();
4557 let default_params = Parameters::default();
4558
4559 let (context, _) = Context::new_for_test(4);
4560 let mut cores = create_cores(context, vec![1, 1, 1, 1]).await;
4562
4563 let mut last_round_blocks = Vec::new();
4564 let mut all_blocks = Vec::new();
4565
4566 let excluded_authority = AuthorityIndex::new_for_test(3);
4567
4568 for round in 1..=10 {
4569 let mut this_round_blocks = Vec::new();
4570
4571 for core_fixture in &mut cores {
4572 if core_fixture.core.context.own_index == excluded_authority {
4574 continue;
4575 }
4576
4577 core_fixture.add_blocks(last_round_blocks.clone()).unwrap();
4579 core_fixture
4580 .core
4581 .round_tracker_for_tests()
4582 .write()
4583 .update_from_probe(
4584 vec![
4585 vec![round, round, round, round],
4586 vec![round, round, round, round],
4587 vec![round, round, round, round],
4588 vec![round, round, round, round],
4589 ],
4590 vec![
4591 vec![round, round, round, round],
4592 vec![round, round, round, round],
4593 vec![round, round, round, round],
4594 vec![round, round, round, round],
4595 ],
4596 );
4597 core_fixture.core.new_block(round, true).unwrap();
4598
4599 let block = core_fixture.core.last_proposed_block();
4600 assert_eq!(block.round(), round);
4601
4602 this_round_blocks.push(block.clone());
4604 }
4605
4606 last_round_blocks = this_round_blocks.clone();
4607 all_blocks.extend(this_round_blocks);
4608 }
4609
4610 let core_fixture = &mut cores[excluded_authority];
4614 sleep(default_params.min_round_delay).await;
4616 core_fixture.add_blocks(all_blocks).unwrap();
4618
4619 let block = core_fixture.core.last_proposed_block();
4622 assert_eq!(block.round(), 11);
4623 assert_eq!(block.ancestors().len(), 4);
4624 for block_ref in block.ancestors() {
4625 if block_ref.author == excluded_authority {
4626 assert_eq!(block_ref.round, 1);
4627 } else {
4628 assert_eq!(block_ref.round, 10);
4629 }
4630 }
4631
4632 core_fixture.dag_state.write().flush();
4634
4635 let last_commit = core_fixture
4637 .store
4638 .read_last_commit()
4639 .unwrap()
4640 .expect("last commit should be set");
4641 assert_eq!(last_commit.index(), 6);
4645 let all_stored_commits = core_fixture
4646 .store
4647 .scan_commits((0..=CommitIndex::MAX).into())
4648 .unwrap();
4649 assert_eq!(all_stored_commits.len(), 6);
4650 }
4651
4652 #[tokio::test]
4653 async fn try_select_certified_leaders() {
4654 telemetry_subscribers::init_for_testing();
4656
4657 let (context, _) = Context::new_for_test(4);
4658
4659 let authority_index = AuthorityIndex::new_for_test(0);
4660 let core =
4661 CoreTestFixture::new(context.clone(), vec![1, 1, 1, 1], authority_index, true).await;
4662 let mut core = core.core;
4663
4664 let mut dag_builder = DagBuilder::new(Arc::new(context.clone()));
4665 dag_builder.layers(1..=12).build();
4666
4667 let limit = 2;
4668
4669 let blocks = dag_builder.blocks(1..=12);
4670 core.dag_state.write().accept_blocks(blocks);
4671
4672 let sub_dags_and_commits = dag_builder.get_sub_dag_and_certified_commits(1..=4);
4674 let mut certified_commits = sub_dags_and_commits
4675 .into_iter()
4676 .map(|(_, commit)| commit)
4677 .collect::<Vec<_>>();
4678
4679 let leaders = core.try_select_certified_leaders(&mut certified_commits, limit);
4680
4681 assert_eq!(leaders.len(), 2);
4683 assert_eq!(certified_commits.len(), 2);
4684 }
4685
4686 pub(crate) async fn receive<T: Copy>(timeout: Duration, mut receiver: watch::Receiver<T>) -> T {
4687 tokio::time::timeout(timeout, receiver.changed())
4688 .await
4689 .expect("Timeout while waiting to read from receiver")
4690 .expect("Signal receive channel shouldn't be closed");
4691 *receiver.borrow_and_update()
4692 }
4693}