1use std::{
5 cmp::max,
6 collections::{BTreeMap, BTreeSet, VecDeque},
7 ops::Bound::{Excluded, Included, Unbounded},
8 panic,
9 sync::Arc,
10 time::Duration,
11 vec,
12};
13
14use consensus_config::AuthorityIndex;
15use consensus_types::block::{BlockDigest, BlockRef, BlockTimestampMs, Round, TransactionIndex};
16use itertools::Itertools as _;
17use mysten_common::ZipDebugEqIteratorExt;
18use tokio::time::Instant;
19use tracing::{debug, error, info, trace};
20
21use crate::{
22 CommittedSubDag,
23 block::{BlockAPI, GENESIS_ROUND, Slot, VerifiedBlock, genesis_blocks},
24 commit::{
25 CommitAPI as _, CommitDigest, CommitIndex, CommitInfo, CommitRef, CommitVote,
26 GENESIS_COMMIT_INDEX, TrustedCommit, load_committed_subdag_from_store,
27 },
28 context::Context,
29 leader_scoring::{ReputationScores, ScoringSubdag},
30 stake_aggregator::{QuorumThreshold, StakeAggregator},
31 storage::{Store, WriteBatch},
32 threshold_clock::ThresholdClock,
33};
34
35pub struct DagState {
43 context: Arc<Context>,
44
45 genesis: BTreeMap<BlockRef, VerifiedBlock>,
47
48 recent_blocks: BTreeMap<BlockRef, BlockInfo>,
56
57 recent_refs_by_authority: Vec<BTreeSet<BlockRef>>,
60
61 round_info: VecDeque<RoundInfo>,
65
66 threshold_clock: ThresholdClock,
68
69 evicted_rounds: Vec<Round>,
73
74 highest_accepted_round: Round,
76
77 last_commit: Option<TrustedCommit>,
79
80 last_commit_round_advancement_time: Option<std::time::Instant>,
82
83 last_committed_rounds: Vec<Round>,
85
86 scoring_subdag: ScoringSubdag,
89
90 pending_commit_votes: VecDeque<CommitVote>,
94
95 blocks_to_write: Vec<VerifiedBlock>,
98 commits_to_write: Vec<TrustedCommit>,
99
100 commit_info_to_write: Vec<(CommitRef, CommitInfo)>,
104
105 finalized_commits_to_write: Vec<(CommitRef, BTreeMap<BlockRef, Vec<TransactionIndex>>)>,
107
108 store: Arc<dyn Store>,
110
111 cached_rounds: Round,
113}
114
115impl DagState {
116 pub fn new(context: Arc<Context>, store: Arc<dyn Store>) -> Self {
118 let cached_rounds = context.parameters.dag_state_cached_rounds as Round;
119 let num_authorities = context.committee.size();
120
121 let genesis = genesis_blocks(context.as_ref())
122 .into_iter()
123 .map(|block| (block.reference(), block))
124 .collect();
125
126 let threshold_clock = ThresholdClock::new(1, context.clone());
127
128 let last_commit = store
129 .read_last_commit()
130 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e));
131
132 let commit_info = store
133 .read_last_commit_info()
134 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e));
135 let (mut last_committed_rounds, commit_recovery_start_index) =
136 if let Some((commit_ref, commit_info)) = commit_info {
137 tracing::info!("Recovering committed state from {commit_ref} {commit_info:?}");
138 (commit_info.committed_rounds, commit_ref.index + 1)
139 } else {
140 tracing::info!("Found no stored CommitInfo to recover from");
141 (vec![0; num_authorities], GENESIS_COMMIT_INDEX + 1)
142 };
143
144 let mut unscored_committed_subdags = Vec::new();
145 let mut scoring_subdag = ScoringSubdag::new(context.clone());
146
147 if !context.protocol_config.enable_v3()
154 && let Some(last_commit) = last_commit.as_ref()
155 {
156 store
157 .scan_commits((commit_recovery_start_index..=last_commit.index()).into())
158 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e))
159 .iter()
160 .for_each(|commit| {
161 for block_ref in commit.blocks() {
162 last_committed_rounds[block_ref.author] =
163 max(last_committed_rounds[block_ref.author], block_ref.round);
164 }
165 let committed_subdag =
166 load_committed_subdag_from_store(store.as_ref(), commit.clone());
167 unscored_committed_subdags.push(committed_subdag);
168 });
169 }
170
171 tracing::info!(
172 "DagState was initialized with the following state: \
173 {last_commit:?}; {last_committed_rounds:?}; {} unscored committed subdags;",
174 unscored_committed_subdags.len()
175 );
176
177 scoring_subdag.add_subdags(std::mem::take(&mut unscored_committed_subdags));
178
179 let mut state = Self {
180 context: context.clone(),
181 genesis,
182 recent_blocks: BTreeMap::new(),
183 recent_refs_by_authority: vec![BTreeSet::new(); num_authorities],
184 round_info: VecDeque::new(),
185 threshold_clock,
186 highest_accepted_round: 0,
187 last_commit: last_commit.clone(),
188 last_commit_round_advancement_time: None,
189 last_committed_rounds: last_committed_rounds.clone(),
190 pending_commit_votes: VecDeque::new(),
191 blocks_to_write: vec![],
192 commits_to_write: vec![],
193 commit_info_to_write: vec![],
194 finalized_commits_to_write: vec![],
195 scoring_subdag,
196 store: store.clone(),
197 cached_rounds,
198 evicted_rounds: vec![0; num_authorities],
199 };
200
201 let mut recovered_blocks = Vec::new();
202 for (authority_index, _) in context.committee.authorities() {
203 let (blocks, eviction_round) = {
204 let last_block = state
207 .store
208 .scan_last_blocks_by_author(authority_index, 1, None)
209 .expect("Database error");
210 let last_block_round = last_block
211 .last()
212 .map(|b| b.round())
213 .unwrap_or(GENESIS_ROUND);
214
215 let eviction_round =
216 Self::eviction_round(last_block_round, state.gc_round(), state.cached_rounds);
217 let blocks = state
218 .store
219 .scan_blocks_by_author(authority_index, eviction_round + 1)
220 .expect("Database error");
221
222 (blocks, eviction_round)
223 };
224
225 debug!(
226 "Recovered blocks {}: {:?}",
227 authority_index,
228 blocks
229 .iter()
230 .map(|b| b.reference())
231 .collect::<Vec<BlockRef>>()
232 );
233 recovered_blocks.extend(blocks);
234
235 state.evicted_rounds[authority_index] = eviction_round;
236 }
237
238 recovered_blocks.sort_by_key(|b| b.reference());
240 for block in &recovered_blocks {
241 state.update_block_metadata(block);
242 }
243
244 if let Some(last_commit) = last_commit {
245 let mut index = last_commit.index();
246 let gc_round = state.gc_round();
247 info!(
248 "Recovering block commit statuses from commit index {} and backwards until leader of round <= gc_round {:?}",
249 index, gc_round
250 );
251
252 loop {
253 let commits = store
254 .scan_commits((index..=index).into())
255 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e));
256 let Some(commit) = commits.first() else {
257 info!("Recovering finished up to index {index}, no more commits to recover");
258 break;
259 };
260
261 if gc_round > 0 && commit.leader().round <= gc_round {
263 info!(
264 "Recovering finished, reached commit leader round {} <= gc_round {}",
265 commit.leader().round,
266 gc_round
267 );
268 break;
269 }
270
271 if context.protocol_config.enable_v3() {
277 for block_ref in commit.blocks() {
278 state.last_committed_rounds[block_ref.author] = max(
279 state.last_committed_rounds[block_ref.author],
280 block_ref.round,
281 );
282 }
283 }
284
285 commit.blocks().iter().filter(|b| b.round > gc_round).for_each(|block_ref|{
286 debug!(
287 "Setting block {:?} as committed based on commit {:?}",
288 block_ref,
289 commit.index()
290 );
291 assert!(state.set_committed(block_ref), "Attempted to set again a block {:?} as committed when recovering commit {:?}", block_ref, commit);
292 });
293
294 index = index.saturating_sub(1);
296 if index == 0 {
297 break;
298 }
299 }
300 }
301
302 let proposed_blocks = store
304 .scan_blocks_by_author(context.own_index, state.gc_round() + 1)
305 .expect("Database error");
306 for block in proposed_blocks {
307 state.link_causal_history(block.reference());
308 }
309
310 state
311 }
312
313 pub(crate) fn accept_block(&mut self, block: VerifiedBlock) {
315 assert_ne!(
316 block.round(),
317 0,
318 "Genesis block should not be accepted into DAG."
319 );
320
321 let block_ref = block.reference();
322 if self.contains_block(&block_ref) {
323 return;
324 }
325
326 let now = self.context.clock.timestamp_utc_ms();
327 if block.timestamp_ms() > now {
328 trace!(
329 "Block {:?} with timestamp {} is greater than local timestamp {}.",
330 block,
331 block.timestamp_ms(),
332 now,
333 );
334 }
335 let hostname = &self.context.committee.authority(block_ref.author).hostname;
336 self.context
337 .metrics
338 .node_metrics
339 .accepted_block_time_drift_ms
340 .with_label_values(&[hostname])
341 .inc_by(block.timestamp_ms().saturating_sub(now));
342
343 if block_ref.author == self.context.own_index {
346 let existing_blocks = self.get_uncommitted_blocks_at_slot(block_ref.into());
347 if !self
348 .context
349 .parameters
350 .internal
351 .skip_equivocation_validation
352 {
353 assert!(
354 existing_blocks.is_empty(),
355 "Block Rejected! Attempted to add block {block:#?} to own slot where \
356 block(s) {existing_blocks:#?} already exists."
357 );
358 }
359 }
360 self.update_block_metadata(&block);
361 self.blocks_to_write.push(block);
362 let source = if self.context.own_index == block_ref.author {
363 "own"
364 } else {
365 "others"
366 };
367 self.context
368 .metrics
369 .node_metrics
370 .accepted_blocks
371 .with_label_values(&[source])
372 .inc();
373 }
374
375 fn update_block_metadata(&mut self, block: &VerifiedBlock) {
377 let block_ref = block.reference();
378 self.recent_blocks
379 .insert(block_ref, BlockInfo::new(block.clone()));
380 self.recent_refs_by_authority[block_ref.author].insert(block_ref);
381 if self.context.protocol_config.enable_v3() {
382 for ancestor in block.ancestors() {
384 if ancestor.round + 1 != block_ref.round || ancestor.round <= self.gc_round() {
386 continue;
387 }
388 let block_info = self.recent_blocks.get_mut(ancestor).unwrap_or_else(|| {
389 panic!(
390 "Parent block {} of block {} does not exist",
391 ancestor, block_ref
392 )
393 });
394 block_info.children.insert(block_ref);
395 block_info
396 .children_stake
397 .add_unique(block_ref.author, &self.context.committee);
398 }
399 self.update_round_info(block);
400 }
401 if self.threshold_clock.add_block(block_ref) {
402 if let Some(last_proposed_block) = self.get_last_proposed_block()
404 && last_proposed_block.round() == block_ref.round
405 {
406 let quorum_delay_ms = self
407 .context
408 .clock
409 .timestamp_utc_ms()
410 .saturating_sub(last_proposed_block.timestamp_ms());
411 self.context
412 .metrics
413 .node_metrics
414 .quorum_receive_latency
415 .observe(Duration::from_millis(quorum_delay_ms).as_secs_f64());
416 }
417 }
418
419 self.highest_accepted_round = max(self.highest_accepted_round, block.round());
420 self.context
421 .metrics
422 .node_metrics
423 .highest_accepted_round
424 .set(self.highest_accepted_round as i64);
425
426 let highest_accepted_round_for_author = self.recent_refs_by_authority[block_ref.author]
427 .last()
428 .map(|block_ref| block_ref.round)
429 .expect("There should be by now at least one block ref");
430 let hostname = &self.context.committee.authority(block_ref.author).hostname;
431 self.context
432 .metrics
433 .node_metrics
434 .highest_accepted_authority_round
435 .with_label_values(&[hostname])
436 .set(highest_accepted_round_for_author as i64);
437 }
438
439 pub(crate) fn accept_blocks(&mut self, blocks: Vec<VerifiedBlock>) {
441 debug!(
442 "Accepting blocks: {}",
443 blocks.iter().map(|b| b.reference().to_string()).join(",")
444 );
445 for block in blocks {
446 self.accept_block(block);
447 }
448 }
449
450 pub(crate) fn get_block(&self, reference: &BlockRef) -> Option<VerifiedBlock> {
453 self.get_blocks(&[*reference])
454 .pop()
455 .expect("Exactly one element should be returned")
456 }
457
458 pub(crate) fn get_blocks(&self, block_refs: &[BlockRef]) -> Vec<Option<VerifiedBlock>> {
461 if block_refs.is_empty() {
462 return vec![];
463 }
464
465 let mut blocks = vec![None; block_refs.len()];
466 let mut missing = Vec::new();
467
468 for (index, block_ref) in block_refs.iter().enumerate() {
469 if block_ref.round == GENESIS_ROUND {
470 if let Some(block) = self.genesis.get(block_ref) {
472 blocks[index] = Some(block.clone());
473 }
474 continue;
475 }
476 if let Some(block_info) = self.recent_blocks.get(block_ref) {
477 blocks[index] = Some(block_info.block.clone());
478 continue;
479 }
480 missing.push((index, block_ref));
481 }
482
483 if missing.is_empty() {
484 return blocks;
485 }
486
487 let missing_refs = missing
488 .iter()
489 .map(|(_, block_ref)| **block_ref)
490 .collect::<Vec<_>>();
491 let store_results = self
492 .store
493 .read_blocks(&missing_refs)
494 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e));
495 self.context
496 .metrics
497 .node_metrics
498 .dag_state_store_read_count
499 .with_label_values(&["get_blocks"])
500 .inc();
501
502 for ((index, _), result) in missing.into_iter().zip_debug_eq(store_results) {
503 blocks[index] = result;
504 }
505
506 blocks
507 }
508
509 pub(crate) fn get_block_info_at_slot(&self, slot: Slot) -> Vec<BlockInfo> {
512 assert!(
513 slot.round > self.gc_round(),
514 "get_block_info_at_slot() should only be called for slots above gc_round: slot {}, gc_round {}",
515 slot,
516 self.gc_round()
517 );
518 let mut results = vec![];
519 for (_block_ref, block_info) in self.recent_blocks.range((
520 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)),
521 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)),
522 )) {
523 results.push(block_info.clone());
524 }
525 results
526 }
527
528 pub(crate) fn get_uncommitted_blocks_at_slot(&self, slot: Slot) -> Vec<VerifiedBlock> {
531 let mut blocks = vec![];
535 for (_block_ref, block_info) in self.recent_blocks.range((
536 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)),
537 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)),
538 )) {
539 blocks.push(block_info.block.clone())
540 }
541 blocks
542 }
543
544 pub(crate) fn get_uncommitted_blocks_at_round(&self, round: Round) -> Vec<VerifiedBlock> {
547 if round <= self.last_commit_round() {
548 panic!("Round {} have committed blocks!", round);
549 }
550
551 let mut blocks = vec![];
552 for (_block_ref, block_info) in self.recent_blocks.range((
553 Included(BlockRef::new(round, AuthorityIndex::ZERO, BlockDigest::MIN)),
554 Excluded(BlockRef::new(
555 round + 1,
556 AuthorityIndex::ZERO,
557 BlockDigest::MIN,
558 )),
559 )) {
560 blocks.push(block_info.block.clone())
561 }
562 blocks
563 }
564
565 #[cfg(test)]
566 pub(crate) fn get_block_children(&self, block_ref: &BlockRef) -> Option<Vec<BlockRef>> {
567 if block_ref.round <= self.gc_round() {
568 return None;
569 }
570 self.recent_blocks
571 .get(block_ref)
572 .map(|block_info| block_info.children.iter().cloned().collect())
573 }
574
575 #[cfg(test)]
576 pub(crate) fn get_block_children_authorities(
577 &self,
578 block_ref: &BlockRef,
579 ) -> Option<BTreeSet<AuthorityIndex>> {
580 if block_ref.round <= self.gc_round() {
581 return None;
582 }
583 self.recent_blocks
584 .get(block_ref)
585 .map(|block_info| block_info.children_stake.authorities().clone())
586 }
587
588 #[cfg(test)]
589 pub(crate) fn get_block_total_children_stake(
590 &self,
591 block_ref: &BlockRef,
592 ) -> Option<consensus_config::Stake> {
593 if block_ref.round <= self.gc_round() {
594 return None;
595 }
596 self.recent_blocks
597 .get(block_ref)
598 .map(|block_info| block_info.children_stake.stake())
599 }
600
601 pub(crate) fn ancestors_at_round(
603 &self,
604 later_block: &VerifiedBlock,
605 earlier_round: Round,
606 ) -> Vec<VerifiedBlock> {
607 let mut linked: BTreeSet<BlockRef> = later_block.ancestors().iter().cloned().collect();
609 while !linked.is_empty() {
610 let round = linked.last().unwrap().round;
611 if round <= earlier_round {
613 break;
614 }
615 let block_ref = linked.pop_last().unwrap();
616 let Some(block) = self.get_block(&block_ref) else {
617 panic!("Block {:?} should exist in DAG!", block_ref);
618 };
619 linked.extend(block.ancestors().iter().cloned());
620 }
621 linked
622 .range((
623 Included(BlockRef::new(
624 earlier_round,
625 AuthorityIndex::ZERO,
626 BlockDigest::MIN,
627 )),
628 Unbounded,
629 ))
630 .map(|r| {
631 self.get_block(r)
632 .unwrap_or_else(|| panic!("Block {:?} should exist in DAG!", r))
633 .clone()
634 })
635 .collect()
636 }
637
638 pub(crate) fn get_last_proposed_block(&self) -> Option<VerifiedBlock> {
642 if self.context.is_validator() {
643 Some(self.get_last_block_for_authority(self.context.own_index))
644 } else {
645 None
646 }
647 }
648
649 pub(crate) fn get_last_block_for_authority(&self, authority: AuthorityIndex) -> VerifiedBlock {
652 if let Some(last) = self.recent_refs_by_authority[authority].last() {
653 return self
654 .recent_blocks
655 .get(last)
656 .expect("Block should be found in recent blocks")
657 .block
658 .clone();
659 }
660
661 let (_, genesis_block) = self
663 .genesis
664 .iter()
665 .find(|(block_ref, _)| block_ref.author == authority)
666 .expect("Genesis should be found for authority {authority_index}");
667 genesis_block.clone()
668 }
669
670 pub(crate) fn get_cached_blocks(
676 &self,
677 authority: AuthorityIndex,
678 start: Round,
679 ) -> Vec<VerifiedBlock> {
680 self.get_cached_blocks_in_range(authority, start, Round::MAX, usize::MAX)
681 }
682
683 pub(crate) fn get_cached_blocks_in_range(
686 &self,
687 authority: AuthorityIndex,
688 start_round: Round,
689 end_round: Round,
690 limit: usize,
691 ) -> Vec<VerifiedBlock> {
692 if start_round >= end_round || limit == 0 {
693 return vec![];
694 }
695
696 let mut blocks = vec![];
697 for block_ref in self.recent_refs_by_authority[authority].range((
698 Included(BlockRef::new(start_round, authority, BlockDigest::MIN)),
699 Excluded(BlockRef::new(
700 end_round,
701 AuthorityIndex::MIN,
702 BlockDigest::MIN,
703 )),
704 )) {
705 let block_info = self
706 .recent_blocks
707 .get(block_ref)
708 .expect("Block should exist in recent blocks");
709 blocks.push(block_info.block.clone());
710 if blocks.len() >= limit {
711 break;
712 }
713 }
714 blocks
715 }
716
717 pub(crate) fn get_last_cached_block_in_range(
719 &self,
720 authority: AuthorityIndex,
721 start_round: Round,
722 end_round: Round,
723 ) -> Option<VerifiedBlock> {
724 if start_round >= end_round {
725 return None;
726 }
727
728 let block_ref = self.recent_refs_by_authority[authority]
729 .range((
730 Included(BlockRef::new(start_round, authority, BlockDigest::MIN)),
731 Excluded(BlockRef::new(
732 end_round,
733 AuthorityIndex::MIN,
734 BlockDigest::MIN,
735 )),
736 ))
737 .last()?;
738
739 self.recent_blocks
740 .get(block_ref)
741 .map(|block_info| block_info.block.clone())
742 }
743
744 pub(crate) fn get_last_cached_block_per_authority(
751 &self,
752 end_round: Round,
753 ) -> Vec<(VerifiedBlock, Vec<BlockRef>)> {
754 let mut blocks = self.genesis.values().cloned().collect::<Vec<_>>();
756 let mut equivocating_blocks = vec![vec![]; self.context.committee.size()];
757
758 if end_round == GENESIS_ROUND {
759 panic!(
760 "Attempted to retrieve blocks earlier than the genesis round which is not possible"
761 );
762 }
763
764 if end_round == GENESIS_ROUND + 1 {
765 return blocks.into_iter().map(|b| (b, vec![])).collect();
766 }
767
768 for (authority_index, block_refs) in self.recent_refs_by_authority.iter().enumerate() {
769 let authority_index = self
770 .context
771 .committee
772 .to_authority_index(authority_index)
773 .unwrap();
774
775 let last_evicted_round = self.evicted_rounds[authority_index];
776 if end_round.saturating_sub(1) <= last_evicted_round {
777 panic!(
778 "Attempted to request for blocks of rounds < {end_round}, when the last evicted round is {last_evicted_round} for authority {authority_index}",
779 );
780 }
781
782 let block_ref_iter = block_refs
783 .range((
784 Included(BlockRef::new(
785 last_evicted_round + 1,
786 authority_index,
787 BlockDigest::MIN,
788 )),
789 Excluded(BlockRef::new(end_round, authority_index, BlockDigest::MIN)),
790 ))
791 .rev();
792
793 let mut last_round = 0;
794 for block_ref in block_ref_iter {
795 if last_round == 0 {
796 last_round = block_ref.round;
797 let block_info = self
798 .recent_blocks
799 .get(block_ref)
800 .expect("Block should exist in recent blocks");
801 blocks[authority_index] = block_info.block.clone();
802 continue;
803 }
804 if block_ref.round < last_round {
805 break;
806 }
807 equivocating_blocks[authority_index].push(*block_ref);
808 }
809 }
810
811 blocks
812 .into_iter()
813 .zip_debug_eq(equivocating_blocks)
814 .collect()
815 }
816
817 pub(crate) fn contains_cached_block_at_slot(&self, slot: Slot) -> bool {
820 if slot.round == GENESIS_ROUND {
822 return true;
823 }
824
825 let eviction_round = self.evicted_rounds[slot.authority];
826 if slot.round <= eviction_round {
827 panic!(
828 "{}",
829 format!(
830 "Attempted to check for slot {slot} that is <= the last evicted round {eviction_round}"
831 )
832 );
833 }
834
835 let mut result = self.recent_refs_by_authority[slot.authority].range((
836 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)),
837 Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)),
838 ));
839 result.next().is_some()
840 }
841
842 pub(crate) fn contains_blocks(&self, block_refs: Vec<BlockRef>) -> Vec<bool> {
845 let mut exist = vec![false; block_refs.len()];
846 let mut missing = Vec::new();
847
848 for (index, block_ref) in block_refs.into_iter().enumerate() {
849 let recent_refs = &self.recent_refs_by_authority[block_ref.author];
850 if recent_refs.contains(&block_ref) || self.genesis.contains_key(&block_ref) {
851 exist[index] = true;
852 } else if recent_refs.is_empty() || recent_refs.last().unwrap().round < block_ref.round
853 {
854 exist[index] = false;
858 } else {
859 missing.push((index, block_ref));
860 }
861 }
862
863 if missing.is_empty() {
864 return exist;
865 }
866
867 let missing_refs = missing
868 .iter()
869 .map(|(_, block_ref)| *block_ref)
870 .collect::<Vec<_>>();
871 let store_results = self
872 .store
873 .contains_blocks(&missing_refs)
874 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e));
875 self.context
876 .metrics
877 .node_metrics
878 .dag_state_store_read_count
879 .with_label_values(&["contains_blocks"])
880 .inc();
881
882 for ((index, _), result) in missing.into_iter().zip_debug_eq(store_results) {
883 exist[index] = result;
884 }
885
886 exist
887 }
888
889 pub(crate) fn contains_block(&self, block_ref: &BlockRef) -> bool {
890 let blocks = self.contains_blocks(vec![*block_ref]);
891 blocks.first().cloned().unwrap()
892 }
893
894 pub(crate) fn set_committed(&mut self, block_ref: &BlockRef) -> bool {
897 if let Some(block_info) = self.recent_blocks.get_mut(block_ref) {
898 if !block_info.committed {
899 block_info.committed = true;
900 return true;
901 }
902 false
903 } else {
904 panic!(
905 "Block {:?} not found in cache to set as committed.",
906 block_ref
907 );
908 }
909 }
910
911 pub(crate) fn is_committed(&self, block_ref: &BlockRef) -> bool {
913 self.recent_blocks
914 .get(block_ref)
915 .unwrap_or_else(|| panic!("Attempted to query for commit status for a block not in cached data {block_ref}"))
916 .committed
917 }
918
919 pub(crate) fn link_causal_history(&mut self, root_block: BlockRef) -> Vec<BlockRef> {
925 let gc_round = self.gc_round();
926 let mut linked_blocks = vec![];
927 let mut targets = VecDeque::new();
928 targets.push_back(root_block);
929 while let Some(block_ref) = targets.pop_front() {
930 if block_ref.round <= gc_round {
938 continue;
939 }
940 let block_info = self
941 .recent_blocks
942 .get_mut(&block_ref)
943 .unwrap_or_else(|| panic!("Block {:?} is not in DAG state", block_ref));
944 if block_info.included {
945 continue;
946 }
947 linked_blocks.push(block_ref);
948 block_info.included = true;
949 targets.extend(block_info.block.ancestors().iter());
950 }
951 linked_blocks
952 }
953
954 pub(crate) fn has_been_included(&self, block_ref: &BlockRef) -> bool {
957 self.recent_blocks
958 .get(block_ref)
959 .unwrap_or_else(|| {
960 panic!(
961 "Attempted to query for inclusion status for a block not in cached data {}",
962 block_ref
963 )
964 })
965 .included
966 }
967
968 pub(crate) fn threshold_clock_round(&self) -> Round {
969 self.threshold_clock.get_round()
970 }
971
972 pub(crate) fn threshold_clock_quorum_ts(&self) -> Instant {
974 self.threshold_clock.get_quorum_ts()
975 }
976
977 pub(crate) fn highest_accepted_round(&self) -> Round {
978 self.highest_accepted_round
979 }
980
981 pub(crate) fn get_round_info(&self, round: Round) -> Option<&RoundInfo> {
984 let front_round = self.round_info.front()?.round;
985 if round < front_round || round <= self.gc_round() {
986 return None;
987 }
988 let round_info = self.round_info.get((round - front_round) as usize)?;
989 assert_eq!(
990 round_info.round, round,
991 "RoundInfo round {} does not match requested round {}. RoundInfo should be contiguous.",
992 round_info.round, round
993 );
994 Some(round_info)
995 }
996
997 fn update_round_info(&mut self, block: &VerifiedBlock) {
1000 let block_ref = block.reference();
1001
1002 let gc_round = self.gc_round();
1004 if block.round() <= gc_round {
1005 return;
1006 }
1007
1008 let next_round = self
1011 .round_info
1012 .back()
1013 .map(|info| info.round + 1)
1014 .unwrap_or(gc_round + 1);
1016 assert!(
1018 block.round() <= next_round,
1019 "Attempted to update round info for block {block_ref} with round higher than next round {next_round}"
1020 );
1021 if block.round() == next_round {
1022 self.round_info.push_back(RoundInfo::new(block.round()));
1023 }
1024
1025 let front_round = self
1027 .round_info
1028 .front()
1029 .expect("round_info non-empty after extend")
1030 .round;
1031 let index = (block.round() - front_round) as usize;
1032 let info = &mut self.round_info[index];
1033 info.blocks.insert(block_ref);
1034 info.total_stake
1035 .add_unique(block_ref.author, &self.context.committee);
1036 }
1037
1038 pub(crate) fn add_commit(&mut self, commit: TrustedCommit) {
1041 let time_diff = if let Some(last_commit) = &self.last_commit {
1042 if commit.index() <= last_commit.index() {
1043 error!(
1044 "New commit index {} <= last commit index {}!",
1045 commit.index(),
1046 last_commit.index()
1047 );
1048 return;
1049 }
1050 assert_eq!(commit.index(), last_commit.index() + 1);
1051
1052 if commit.timestamp_ms() < last_commit.timestamp_ms() {
1053 panic!(
1054 "Commit timestamps do not monotonically increment, prev commit {:?}, new commit {:?}",
1055 last_commit, commit
1056 );
1057 }
1058 commit
1059 .timestamp_ms()
1060 .saturating_sub(last_commit.timestamp_ms())
1061 } else {
1062 assert_eq!(commit.index(), 1);
1063 0
1064 };
1065
1066 self.context
1067 .metrics
1068 .node_metrics
1069 .last_commit_time_diff
1070 .observe(time_diff as f64);
1071
1072 let commit_round_advanced = if let Some(previous_commit) = &self.last_commit {
1073 previous_commit.round() < commit.round()
1074 } else {
1075 true
1076 };
1077
1078 self.last_commit = Some(commit.clone());
1079
1080 if commit_round_advanced {
1081 let now = std::time::Instant::now();
1082 if let Some(previous_time) = self.last_commit_round_advancement_time {
1083 self.context
1084 .metrics
1085 .node_metrics
1086 .commit_round_advancement_interval
1087 .observe(now.duration_since(previous_time).as_secs_f64())
1088 }
1089 self.last_commit_round_advancement_time = Some(now);
1090 }
1091
1092 for block_ref in commit.blocks().iter() {
1093 self.last_committed_rounds[block_ref.author] = max(
1094 self.last_committed_rounds[block_ref.author],
1095 block_ref.round,
1096 );
1097 }
1098
1099 for (i, round) in self.last_committed_rounds.iter().enumerate() {
1100 let index = self.context.committee.to_authority_index(i).unwrap();
1101 let hostname = &self.context.committee.authority(index).hostname;
1102 self.context
1103 .metrics
1104 .node_metrics
1105 .last_committed_authority_round
1106 .with_label_values(&[hostname])
1107 .set((*round).into());
1108 }
1109
1110 self.pending_commit_votes.push_back(commit.reference());
1111 self.commits_to_write.push(commit);
1112 }
1113
1114 pub(crate) fn recover_commits_to_write(&mut self, commits: Vec<TrustedCommit>) {
1116 self.commits_to_write.extend(commits);
1117 }
1118
1119 pub(crate) fn ensure_commits_to_write_is_empty(&self) {
1120 assert!(
1121 self.commits_to_write.is_empty(),
1122 "Commits to write should be empty. {:?}",
1123 self.commits_to_write,
1124 );
1125 }
1126
1127 pub(crate) fn add_commit_info(&mut self, reputation_scores: ReputationScores) {
1128 assert!(self.scoring_subdag.is_empty());
1132
1133 let commit_info = CommitInfo {
1134 committed_rounds: self.last_committed_rounds.clone(),
1135 reputation_scores,
1136 };
1137 let last_commit = self
1138 .last_commit
1139 .as_ref()
1140 .expect("Last commit should already be set.");
1141 self.commit_info_to_write
1142 .push((last_commit.reference(), commit_info));
1143 }
1144
1145 pub(crate) fn add_finalized_commit(
1146 &mut self,
1147 commit_ref: CommitRef,
1148 rejected_transactions: BTreeMap<BlockRef, Vec<TransactionIndex>>,
1149 ) {
1150 self.finalized_commits_to_write
1151 .push((commit_ref, rejected_transactions));
1152 }
1153
1154 pub(crate) fn take_commit_votes(&mut self, limit: usize) -> Vec<CommitVote> {
1155 let mut votes = Vec::new();
1156 while !self.pending_commit_votes.is_empty() && votes.len() < limit {
1157 votes.push(self.pending_commit_votes.pop_front().unwrap());
1158 }
1159 votes
1160 }
1161
1162 pub(crate) fn last_commit_index(&self) -> CommitIndex {
1164 match &self.last_commit {
1165 Some(commit) => commit.index(),
1166 None => 0,
1167 }
1168 }
1169
1170 pub(crate) fn last_commit_digest(&self) -> CommitDigest {
1172 match &self.last_commit {
1173 Some(commit) => commit.digest(),
1174 None => CommitDigest::MIN,
1175 }
1176 }
1177
1178 pub(crate) fn last_commit_timestamp_ms(&self) -> BlockTimestampMs {
1180 match &self.last_commit {
1181 Some(commit) => commit.timestamp_ms(),
1182 None => 0,
1183 }
1184 }
1185
1186 pub(crate) fn last_commit_leader(&self) -> Slot {
1188 match &self.last_commit {
1189 Some(commit) => commit.leader().into(),
1190 None => self
1191 .genesis
1192 .iter()
1193 .next()
1194 .map(|(genesis_ref, _)| *genesis_ref)
1195 .expect("Genesis blocks should always be available.")
1196 .into(),
1197 }
1198 }
1199
1200 pub(crate) fn last_commit_round(&self) -> Round {
1202 match &self.last_commit {
1203 Some(commit) => commit.leader().round,
1204 None => 0,
1205 }
1206 }
1207
1208 pub(crate) fn last_committed_rounds(&self) -> Vec<Round> {
1215 self.last_committed_rounds.clone()
1216 }
1217
1218 pub(crate) fn gc_round(&self) -> Round {
1222 self.calculate_gc_round(self.last_commit_round())
1223 }
1224
1225 pub(crate) fn calculate_gc_round(&self, commit_round: Round) -> Round {
1228 commit_round.saturating_sub(self.context.protocol_config.gc_depth())
1229 }
1230
1231 pub(crate) fn flush(&mut self) {
1241 let _s = self
1242 .context
1243 .metrics
1244 .node_metrics
1245 .scope_processing_time
1246 .with_label_values(&["DagState::flush"])
1247 .start_timer();
1248
1249 let pending_blocks = std::mem::take(&mut self.blocks_to_write);
1251 let pending_commits = std::mem::take(&mut self.commits_to_write);
1252 let pending_commit_info = std::mem::take(&mut self.commit_info_to_write);
1253 let pending_finalized_commits = std::mem::take(&mut self.finalized_commits_to_write);
1254 if pending_blocks.is_empty()
1255 && pending_commits.is_empty()
1256 && pending_commit_info.is_empty()
1257 && pending_finalized_commits.is_empty()
1258 {
1259 return;
1260 }
1261
1262 debug!(
1263 "Flushing {} blocks ({}), {} commits ({}), {} commit infos ({}), {} finalized commits ({}) to storage.",
1264 pending_blocks.len(),
1265 pending_blocks
1266 .iter()
1267 .map(|b| b.reference().to_string())
1268 .join(","),
1269 pending_commits.len(),
1270 pending_commits
1271 .iter()
1272 .map(|c| c.reference().to_string())
1273 .join(","),
1274 pending_commit_info.len(),
1275 pending_commit_info
1276 .iter()
1277 .map(|(commit_ref, _)| commit_ref.to_string())
1278 .join(","),
1279 pending_finalized_commits.len(),
1280 pending_finalized_commits
1281 .iter()
1282 .map(|(commit_ref, _)| commit_ref.to_string())
1283 .join(","),
1284 );
1285 self.store
1286 .write(WriteBatch::new(
1287 pending_blocks,
1288 pending_commits,
1289 pending_commit_info,
1290 pending_finalized_commits,
1291 ))
1292 .unwrap_or_else(|e| panic!("Failed to write to storage: {:?}", e));
1293 self.context
1294 .metrics
1295 .node_metrics
1296 .dag_state_store_write_count
1297 .inc();
1298
1299 for (authority_index, _) in self.context.committee.authorities() {
1301 let eviction_round = self.calculate_authority_eviction_round(authority_index);
1302 while let Some(block_ref) = self.recent_refs_by_authority[authority_index].first() {
1303 if block_ref.round <= eviction_round {
1304 self.recent_blocks.remove(block_ref);
1305 self.recent_refs_by_authority[authority_index].pop_first();
1306 } else {
1307 break;
1308 }
1309 }
1310 self.evicted_rounds[authority_index] = eviction_round;
1311 }
1312
1313 while let Some(info) = self.round_info.front() {
1315 if info.round <= self.gc_round() {
1316 self.round_info.pop_front();
1317 } else {
1318 break;
1319 }
1320 }
1321
1322 let metrics = &self.context.metrics.node_metrics;
1323 metrics
1324 .dag_state_recent_blocks
1325 .set(self.recent_blocks.len() as i64);
1326 metrics.dag_state_recent_refs.set(
1327 self.recent_refs_by_authority
1328 .iter()
1329 .map(BTreeSet::len)
1330 .sum::<usize>() as i64,
1331 );
1332 }
1333
1334 pub(crate) fn recover_last_commit_info(&self) -> Option<(CommitRef, CommitInfo)> {
1335 self.store
1336 .read_last_commit_info()
1337 .unwrap_or_else(|e| panic!("Failed to read from storage: {:?}", e))
1338 }
1339
1340 pub(crate) fn add_scoring_subdags(&mut self, scoring_subdags: Vec<CommittedSubDag>) {
1341 self.scoring_subdag.add_subdags(scoring_subdags);
1342 }
1343
1344 pub(crate) fn clear_scoring_subdag(&mut self) {
1345 self.scoring_subdag.clear();
1346 }
1347
1348 pub(crate) fn scoring_subdags_count(&self) -> usize {
1349 self.scoring_subdag.scored_subdags_count()
1350 }
1351
1352 pub(crate) fn calculate_scoring_subdag_scores(&self) -> ReputationScores {
1353 self.scoring_subdag.calculate_distributed_vote_scores()
1354 }
1355
1356 pub(crate) fn scoring_subdag_commit_range(&self) -> CommitIndex {
1357 self.scoring_subdag
1358 .commit_range
1359 .as_ref()
1360 .expect("commit range should exist for scoring subdag")
1361 .end()
1362 }
1363
1364 fn calculate_authority_eviction_round(&self, authority_index: AuthorityIndex) -> Round {
1368 let last_round = self.recent_refs_by_authority[authority_index]
1369 .last()
1370 .map(|block_ref| block_ref.round)
1371 .unwrap_or(GENESIS_ROUND);
1372
1373 Self::eviction_round(last_round, self.gc_round(), self.cached_rounds)
1374 }
1375
1376 fn eviction_round(last_round: Round, gc_round: Round, cached_rounds: u32) -> Round {
1379 gc_round.min(last_round.saturating_sub(cached_rounds))
1380 }
1381
1382 pub(crate) fn store(&self) -> Arc<dyn Store> {
1384 self.store.clone()
1385 }
1386
1387 #[cfg(test)]
1390 pub(crate) fn last_quorum(&self) -> Vec<VerifiedBlock> {
1391 for round in
1394 (self.highest_accepted_round.saturating_sub(1)..=self.highest_accepted_round).rev()
1395 {
1396 if round == GENESIS_ROUND {
1397 return self.genesis_blocks();
1398 }
1399 use crate::stake_aggregator::{QuorumThreshold, StakeAggregator};
1400 let mut quorum = StakeAggregator::<QuorumThreshold>::new();
1401
1402 let blocks = self.get_uncommitted_blocks_at_round(round);
1404 for block in &blocks {
1405 if quorum.add(block.author(), &self.context.committee) {
1406 return blocks;
1407 }
1408 }
1409 }
1410
1411 panic!("Fatal error, no quorum has been detected in our DAG on the last two rounds.");
1412 }
1413
1414 #[cfg(test)]
1415 pub(crate) fn genesis_blocks(&self) -> Vec<VerifiedBlock> {
1416 self.genesis.values().cloned().collect()
1417 }
1418
1419 #[cfg(test)]
1420 pub(crate) fn set_last_commit(&mut self, commit: TrustedCommit) {
1421 self.last_commit = Some(commit);
1422 }
1423}
1424
1425#[derive(Clone)]
1427pub(crate) struct BlockInfo {
1428 pub(crate) block: VerifiedBlock,
1429
1430 pub(crate) children: BTreeSet<BlockRef>,
1433 pub(crate) children_stake: StakeAggregator<QuorumThreshold>,
1436
1437 pub(crate) committed: bool,
1439 included: bool,
1446}
1447
1448impl BlockInfo {
1449 fn new(block: VerifiedBlock) -> Self {
1450 Self {
1451 block,
1452 children: BTreeSet::new(),
1453 children_stake: StakeAggregator::new(),
1454 committed: false,
1455 included: false,
1456 }
1457 }
1458}
1459
1460pub(crate) struct RoundInfo {
1464 pub(crate) round: Round,
1465 pub(crate) blocks: BTreeSet<BlockRef>,
1467 pub(crate) total_stake: StakeAggregator<QuorumThreshold>,
1470}
1471
1472impl RoundInfo {
1473 fn new(round: Round) -> Self {
1474 Self {
1475 round,
1476 blocks: BTreeSet::new(),
1477 total_stake: StakeAggregator::new(),
1478 }
1479 }
1480}
1481
1482#[cfg(test)]
1483mod test {
1484 use std::vec;
1485
1486 use consensus_config::Stake;
1487 use consensus_types::block::{BlockDigest, BlockRef, BlockTimestampMs};
1488 use parking_lot::RwLock;
1489
1490 use super::*;
1491 use crate::{
1492 block::{TestBlock, VerifiedBlock},
1493 storage::{WriteBatch, mem_store::MemStore},
1494 test_dag_builder::DagBuilder,
1495 test_dag_parser::parse_dag,
1496 };
1497
1498 #[tokio::test]
1499 async fn test_get_blocks() {
1500 let (context, _) = Context::new_for_test(4);
1501 let context = Arc::new(context);
1502 let store = Arc::new(MemStore::new());
1503 let mut dag_state = DagState::new(context.clone(), store.clone());
1504 let own_index = AuthorityIndex::new_for_test(0);
1505
1506 let num_rounds: u32 = 10;
1508 let non_existent_round: u32 = 100;
1509 let num_authorities: u32 = 3;
1510 let num_blocks_per_slot: usize = 3;
1511 let mut blocks = BTreeMap::new();
1512 for round in 1..=num_rounds {
1513 for author in 0..num_authorities {
1514 let base_ts = round as BlockTimestampMs * 1000;
1516 for timestamp in base_ts..base_ts + num_blocks_per_slot as u64 {
1517 let block = VerifiedBlock::new_for_test(
1518 TestBlock::new(round, author)
1519 .set_timestamp_ms(timestamp)
1520 .build(),
1521 );
1522 dag_state.accept_block(block.clone());
1523 blocks.insert(block.reference(), block);
1524
1525 if AuthorityIndex::new_for_test(author) == own_index {
1527 break;
1528 }
1529 }
1530 }
1531 }
1532
1533 for (r, block) in &blocks {
1535 assert_eq!(&dag_state.get_block(r).unwrap(), block);
1536 }
1537
1538 let last_ref = blocks.keys().last().unwrap();
1540 assert!(
1541 dag_state
1542 .get_block(&BlockRef::new(
1543 last_ref.round,
1544 last_ref.author,
1545 BlockDigest::MIN
1546 ))
1547 .is_none()
1548 );
1549
1550 for round in 1..=num_rounds {
1552 for author in 0..num_authorities {
1553 let slot = Slot::new(
1554 round,
1555 context
1556 .committee
1557 .to_authority_index(author as usize)
1558 .unwrap(),
1559 );
1560 let blocks = dag_state.get_uncommitted_blocks_at_slot(slot);
1561
1562 if AuthorityIndex::new_for_test(author) == own_index {
1564 assert_eq!(blocks.len(), 1);
1565 } else {
1566 assert_eq!(blocks.len(), num_blocks_per_slot);
1567 }
1568
1569 for b in blocks {
1570 assert_eq!(b.round(), round);
1571 assert_eq!(
1572 b.author(),
1573 context
1574 .committee
1575 .to_authority_index(author as usize)
1576 .unwrap()
1577 );
1578 }
1579 }
1580 }
1581
1582 let slot = Slot::new(non_existent_round, AuthorityIndex::ZERO);
1584 assert!(dag_state.get_uncommitted_blocks_at_slot(slot).is_empty());
1585
1586 for round in 1..=num_rounds {
1588 let blocks = dag_state.get_uncommitted_blocks_at_round(round);
1589 assert_eq!(
1592 blocks.len(),
1593 (num_authorities - 1) as usize * num_blocks_per_slot + 1
1594 );
1595 for b in blocks {
1596 assert_eq!(b.round(), round);
1597 }
1598 }
1599
1600 assert!(
1602 dag_state
1603 .get_uncommitted_blocks_at_round(non_existent_round)
1604 .is_empty()
1605 );
1606 }
1607
1608 #[tokio::test]
1609 async fn test_ancestors_at_uncommitted_round() {
1610 let (context, _) = Context::new_for_test(4);
1612 let context = Arc::new(context);
1613 let store = Arc::new(MemStore::new());
1614 let mut dag_state = DagState::new(context.clone(), store.clone());
1615
1616 let round_10_refs: Vec<_> = (0..4)
1620 .map(|a| {
1621 VerifiedBlock::new_for_test(TestBlock::new(10, a).set_timestamp_ms(1000).build())
1622 .reference()
1623 })
1624 .collect();
1625
1626 let round_11 = [
1628 VerifiedBlock::new_for_test(
1630 TestBlock::new(11, 0)
1631 .set_timestamp_ms(1100)
1632 .set_ancestors(round_10_refs.clone())
1633 .build(),
1634 ),
1635 VerifiedBlock::new_for_test(
1638 TestBlock::new(11, 1)
1639 .set_timestamp_ms(1110)
1640 .set_ancestors(round_10_refs.clone())
1641 .build(),
1642 ),
1643 VerifiedBlock::new_for_test(
1645 TestBlock::new(11, 1)
1646 .set_timestamp_ms(1111)
1647 .set_ancestors(round_10_refs.clone())
1648 .build(),
1649 ),
1650 VerifiedBlock::new_for_test(
1652 TestBlock::new(11, 1)
1653 .set_timestamp_ms(1112)
1654 .set_ancestors(round_10_refs.clone())
1655 .build(),
1656 ),
1657 VerifiedBlock::new_for_test(
1659 TestBlock::new(11, 2)
1660 .set_timestamp_ms(1120)
1661 .set_ancestors(round_10_refs.clone())
1662 .build(),
1663 ),
1664 VerifiedBlock::new_for_test(
1666 TestBlock::new(11, 3)
1667 .set_timestamp_ms(1130)
1668 .set_ancestors(round_10_refs.clone())
1669 .build(),
1670 ),
1671 ];
1672
1673 let ancestors_for_round_12 = vec![
1675 round_11[0].reference(),
1676 round_11[1].reference(),
1677 round_11[5].reference(),
1678 ];
1679 let round_12 = [
1680 VerifiedBlock::new_for_test(
1681 TestBlock::new(12, 0)
1682 .set_timestamp_ms(1200)
1683 .set_ancestors(ancestors_for_round_12.clone())
1684 .build(),
1685 ),
1686 VerifiedBlock::new_for_test(
1687 TestBlock::new(12, 2)
1688 .set_timestamp_ms(1220)
1689 .set_ancestors(ancestors_for_round_12.clone())
1690 .build(),
1691 ),
1692 VerifiedBlock::new_for_test(
1693 TestBlock::new(12, 3)
1694 .set_timestamp_ms(1230)
1695 .set_ancestors(ancestors_for_round_12.clone())
1696 .build(),
1697 ),
1698 ];
1699
1700 let ancestors_for_round_13 = vec![
1702 round_12[0].reference(),
1703 round_12[1].reference(),
1704 round_12[2].reference(),
1705 round_11[2].reference(),
1706 ];
1707 let round_13 = [
1708 VerifiedBlock::new_for_test(
1709 TestBlock::new(12, 1)
1710 .set_timestamp_ms(1300)
1711 .set_ancestors(ancestors_for_round_13.clone())
1712 .build(),
1713 ),
1714 VerifiedBlock::new_for_test(
1715 TestBlock::new(12, 2)
1716 .set_timestamp_ms(1320)
1717 .set_ancestors(ancestors_for_round_13.clone())
1718 .build(),
1719 ),
1720 VerifiedBlock::new_for_test(
1721 TestBlock::new(12, 3)
1722 .set_timestamp_ms(1330)
1723 .set_ancestors(ancestors_for_round_13.clone())
1724 .build(),
1725 ),
1726 ];
1727
1728 let ancestors_for_round_14 = round_13.iter().map(|b| b.reference()).collect();
1730 let anchor = VerifiedBlock::new_for_test(
1731 TestBlock::new(14, 1)
1732 .set_timestamp_ms(1410)
1733 .set_ancestors(ancestors_for_round_14)
1734 .build(),
1735 );
1736
1737 for b in round_11
1739 .iter()
1740 .chain(round_12.iter())
1741 .chain(round_13.iter())
1742 .chain([anchor.clone()].iter())
1743 {
1744 dag_state.accept_block(b.clone());
1745 }
1746
1747 let ancestors = dag_state.ancestors_at_round(&anchor, 11);
1749 let mut ancestors_refs: Vec<BlockRef> = ancestors.iter().map(|b| b.reference()).collect();
1750 ancestors_refs.sort();
1751 let mut expected_refs = vec![
1752 round_11[0].reference(),
1753 round_11[1].reference(),
1754 round_11[2].reference(),
1755 round_11[5].reference(),
1756 ];
1757 expected_refs.sort(); assert_eq!(
1759 ancestors_refs, expected_refs,
1760 "Expected round 11 ancestors: {:?}. Got: {:?}",
1761 expected_refs, ancestors_refs
1762 );
1763 }
1764
1765 #[tokio::test]
1766 async fn test_link_causal_history() {
1767 let (mut context, _) = Context::new_for_test(4);
1768 context.parameters.dag_state_cached_rounds = 10;
1769 context.protocol_config.set_gc_depth_for_testing(3);
1770 let context = Arc::new(context);
1771
1772 let store = Arc::new(MemStore::new());
1773 let mut dag_state = DagState::new(context.clone(), store.clone());
1774
1775 let mut dag_builder = DagBuilder::new(context.clone());
1777 dag_builder.layers(1..=3).build();
1778 dag_builder
1779 .layers(4..=6)
1780 .authorities(vec![AuthorityIndex::new_for_test(0)])
1781 .skip_block()
1782 .build();
1783
1784 let all_blocks = dag_builder.all_blocks();
1786 dag_state.accept_blocks(all_blocks.clone());
1787
1788 for block in &all_blocks {
1790 assert!(!dag_state.has_been_included(&block.reference()));
1791 }
1792
1793 let round_1_block = &all_blocks[1];
1795 assert_eq!(round_1_block.round(), 1);
1796 let linked_blocks = dag_state.link_causal_history(round_1_block.reference());
1797
1798 assert_eq!(linked_blocks.len(), 1);
1800 assert_eq!(linked_blocks[0], round_1_block.reference());
1801 for block_ref in linked_blocks {
1802 assert!(dag_state.has_been_included(&block_ref));
1803 }
1804
1805 let round_2_block = &all_blocks[4];
1807 assert_eq!(round_2_block.round(), 2);
1808 let linked_blocks = dag_state.link_causal_history(round_2_block.reference());
1809
1810 assert_eq!(linked_blocks.len(), 4);
1812 for block_ref in linked_blocks {
1813 assert!(block_ref == round_2_block.reference() || block_ref.round == 1);
1814 }
1815
1816 for block in &all_blocks {
1818 if block.round() == 1 || block.reference() == round_2_block.reference() {
1819 assert!(dag_state.has_been_included(&block.reference()));
1820 } else {
1821 assert!(!dag_state.has_been_included(&block.reference()));
1822 }
1823 }
1824
1825 let round_6_block = all_blocks.last().unwrap();
1827 assert_eq!(round_6_block.round(), 6);
1828
1829 let last_commit = TrustedCommit::new_for_test(
1831 6,
1832 CommitDigest::MIN,
1833 context.clock.timestamp_utc_ms(),
1834 round_6_block.reference(),
1835 vec![],
1836 );
1837 dag_state.set_last_commit(last_commit);
1838 assert_eq!(
1839 dag_state.gc_round(),
1840 3,
1841 "GC round should have moved to round 3"
1842 );
1843
1844 let linked_blocks = dag_state.link_causal_history(round_6_block.reference());
1846
1847 assert_eq!(linked_blocks.len(), 7, "Linked blocks: {:?}", linked_blocks);
1849 for block_ref in linked_blocks {
1850 assert!(
1851 block_ref.round == 4
1852 || block_ref.round == 5
1853 || block_ref == round_6_block.reference()
1854 );
1855 }
1856
1857 for block in &all_blocks {
1859 let block_ref = block.reference();
1860 if block.round() == 1
1861 || block_ref == round_2_block.reference()
1862 || block_ref.round == 4
1863 || block_ref.round == 5
1864 || block_ref == round_6_block.reference()
1865 {
1866 assert!(dag_state.has_been_included(&block.reference()));
1867 } else {
1868 assert!(!dag_state.has_been_included(&block.reference()));
1869 }
1870 }
1871 }
1872
1873 #[tokio::test]
1874 async fn test_block_children_basics() {
1875 let (mut context, _) = Context::new_for_test(4);
1876 context.parameters.dag_state_cached_rounds = 2;
1879 context.protocol_config.set_gc_depth_for_testing(3);
1880 context.protocol_config.set_enable_v3_for_testing(true);
1881 let context = Arc::new(context);
1882
1883 let store = Arc::new(MemStore::new());
1884 let mut dag_state = DagState::new(context.clone(), store.clone());
1885
1886 let mut dag_builder = DagBuilder::new(context.clone());
1888 dag_builder.layers(1..=5).build();
1889
1890 let all_blocks = dag_builder.all_blocks();
1891 dag_state.accept_blocks(all_blocks.clone());
1892
1893 let mut expected_children: BTreeMap<BlockRef, BTreeSet<BlockRef>> = BTreeMap::new();
1896 for block in &all_blocks {
1897 expected_children
1898 .entry(block.reference())
1899 .or_default()
1900 .extend(
1901 all_blocks
1902 .iter()
1903 .filter(|b| b.round() == block.round() + 1)
1904 .map(|b| b.reference()),
1905 );
1906 }
1907
1908 for block in &all_blocks {
1910 let block_ref = block.reference();
1911 let actual: BTreeSet<BlockRef> = dag_state
1912 .get_block_children(&block_ref)
1913 .expect("accepted block should be in recent_blocks")
1914 .into_iter()
1915 .collect();
1916 let want = expected_children.get(&block_ref).cloned().unwrap();
1917 assert_eq!(actual, want, "mismatched children for {block_ref:?}");
1918
1919 let expected_authorities: BTreeSet<AuthorityIndex> =
1921 want.iter().map(|r| r.author).collect();
1922 let expected_stake: Stake = expected_authorities
1923 .iter()
1924 .map(|a| context.committee.stake(*a))
1925 .sum();
1926 assert_eq!(
1927 dag_state
1928 .get_block_children_authorities(&block_ref)
1929 .expect("accepted block should be in recent_blocks"),
1930 expected_authorities,
1931 "mismatched children_authorities for {block_ref:?}"
1932 );
1933 assert_eq!(
1934 dag_state
1935 .get_block_total_children_stake(&block_ref)
1936 .expect("accepted block should be in recent_blocks"),
1937 expected_stake,
1938 "mismatched total_children_stake for {block_ref:?}"
1939 );
1940 }
1941
1942 let round_2_block = all_blocks
1946 .iter()
1947 .find(|b| b.round() == 2)
1948 .expect("should have a round-2 block")
1949 .clone();
1950 let before: Vec<(BlockRef, BTreeSet<BlockRef>)> = round_2_block
1951 .ancestors()
1952 .iter()
1953 .map(|a| {
1954 (
1955 *a,
1956 dag_state
1957 .get_block_children(a)
1958 .unwrap()
1959 .into_iter()
1960 .collect(),
1961 )
1962 })
1963 .collect();
1964 dag_state.accept_block(round_2_block);
1965 for (ancestor, before_set) in before {
1966 let after: BTreeSet<BlockRef> = dag_state
1967 .get_block_children(&ancestor)
1968 .unwrap()
1969 .into_iter()
1970 .collect();
1971 assert_eq!(
1972 before_set, after,
1973 "children changed for {ancestor:?} after re-accept"
1974 );
1975 }
1976
1977 let round_5_leader = all_blocks
1981 .last()
1982 .expect("last block should be round 5")
1983 .reference();
1984 let last_commit = TrustedCommit::new_for_test(
1985 5,
1986 CommitDigest::MIN,
1987 context.clock.timestamp_utc_ms(),
1988 round_5_leader,
1989 vec![],
1990 );
1991 dag_state.set_last_commit(last_commit);
1992 assert_eq!(dag_state.gc_round(), 2);
1993
1994 for block in all_blocks.iter().filter(|block| block.round() <= 2) {
1997 let block_ref = block.reference();
1998 assert!(
1999 dag_state.get_block_children(&block_ref).is_none(),
2000 "below-gc block {block_ref:?} should hide children before flush"
2001 );
2002 assert!(
2003 dag_state
2004 .get_block_children_authorities(&block_ref)
2005 .is_none(),
2006 "below-gc block {block_ref:?} should hide child authorities before flush"
2007 );
2008 assert!(
2009 dag_state
2010 .get_block_total_children_stake(&block_ref)
2011 .is_none(),
2012 "below-gc block {block_ref:?} should hide child stake before flush"
2013 );
2014 }
2015
2016 dag_state.flush();
2017
2018 for block in &all_blocks {
2022 let block_ref = block.reference();
2023 match block.round() {
2024 1..=2 => assert!(
2025 dag_state.get_block_children(&block_ref).is_none(),
2026 "round {} block {block_ref:?} should be evicted after flush",
2027 block.round()
2028 ),
2029 3..=4 => {
2030 let actual: BTreeSet<BlockRef> = dag_state
2031 .get_block_children(&block_ref)
2032 .expect("above-gc block should remain")
2033 .into_iter()
2034 .collect();
2035 let want = expected_children
2036 .get(&block_ref)
2037 .cloned()
2038 .unwrap_or_default();
2039 assert_eq!(
2040 actual, want,
2041 "children changed for {block_ref:?} after flush"
2042 );
2043 let expected_authorities: BTreeSet<AuthorityIndex> =
2046 want.iter().map(|r| r.author).collect();
2047 let expected_stake: Stake = expected_authorities
2048 .iter()
2049 .map(|a| context.committee.stake(*a))
2050 .sum();
2051 assert_eq!(
2052 dag_state
2053 .get_block_children_authorities(&block_ref)
2054 .expect("above-gc block should remain"),
2055 expected_authorities,
2056 "children_authorities changed for {block_ref:?} after flush"
2057 );
2058 assert_eq!(
2059 dag_state
2060 .get_block_total_children_stake(&block_ref)
2061 .expect("above-gc block should remain"),
2062 expected_stake,
2063 "total_children_stake changed for {block_ref:?} after flush"
2064 );
2065 }
2066 5 => {
2067 let actual = dag_state
2068 .get_block_children(&block_ref)
2069 .expect("round-5 block should remain");
2070 assert!(
2071 actual.is_empty(),
2072 "round-5 block {block_ref:?} has unexpected children {actual:?}"
2073 );
2074 assert!(
2075 dag_state
2076 .get_block_children_authorities(&block_ref)
2077 .expect("round-5 block should remain")
2078 .is_empty(),
2079 "round-5 block {block_ref:?} must have no children_authorities"
2080 );
2081 assert_eq!(
2082 dag_state
2083 .get_block_total_children_stake(&block_ref)
2084 .expect("round-5 block should remain"),
2085 0,
2086 "round-5 block {block_ref:?} must have zero total_children_stake"
2087 );
2088 }
2089 _ => unreachable!(),
2090 }
2091 }
2092 }
2093
2094 #[tokio::test]
2095 async fn test_get_block_info_at_slot() {
2096 let (mut context, _) = Context::new_for_test(4);
2097 context.protocol_config.set_enable_v3_for_testing(true);
2100 let context = Arc::new(context);
2101
2102 let store = Arc::new(MemStore::new());
2103 let mut dag_state = DagState::new(context.clone(), store.clone());
2104
2105 let author_1 = AuthorityIndex::new_for_test(1);
2109 let author_2 = AuthorityIndex::new_for_test(2);
2110 let slot_1_1 = Slot::new(1, author_1);
2111
2112 assert!(dag_state.get_block_info_at_slot(slot_1_1).is_empty());
2114
2115 let block_1_1 = VerifiedBlock::new_for_test(TestBlock::new(1, 1).build());
2118 dag_state.accept_block(block_1_1.clone());
2119 let infos = dag_state.get_block_info_at_slot(slot_1_1);
2120 assert_eq!(infos.len(), 1);
2121 assert_eq!(infos[0].block.reference(), block_1_1.reference());
2122 assert_eq!(infos[0].children_stake.stake(), 0);
2123
2124 assert!(
2126 dag_state
2127 .get_block_info_at_slot(Slot::new(1, author_2))
2128 .is_empty()
2129 );
2130
2131 let block_1_1_equiv =
2134 VerifiedBlock::new_for_test(TestBlock::new(1, 1).set_timestamp_ms(1).build());
2135 assert_ne!(block_1_1_equiv.reference(), block_1_1.reference());
2136 dag_state.accept_block(block_1_1_equiv.clone());
2137
2138 let infos = dag_state.get_block_info_at_slot(slot_1_1);
2139 assert_eq!(infos.len(), 2);
2140 let returned_refs: BTreeSet<BlockRef> = infos.iter().map(|i| i.block.reference()).collect();
2141 assert_eq!(
2142 returned_refs,
2143 BTreeSet::from([block_1_1.reference(), block_1_1_equiv.reference()])
2144 );
2145
2146 let block_2_2 = VerifiedBlock::new_for_test(
2150 TestBlock::new(2, 2)
2151 .set_ancestors(vec![block_1_1.reference()])
2152 .build(),
2153 );
2154 dag_state.accept_block(block_2_2);
2155
2156 let stake_2 = context.committee.stake(author_2);
2157 let by_ref: BTreeMap<BlockRef, BlockInfo> = dag_state
2158 .get_block_info_at_slot(slot_1_1)
2159 .into_iter()
2160 .map(|i| (i.block.reference(), i))
2161 .collect();
2162 assert_eq!(
2163 by_ref[&block_1_1.reference()].children_stake.stake(),
2164 stake_2
2165 );
2166 assert_eq!(
2167 by_ref[&block_1_1_equiv.reference()].children_stake.stake(),
2168 0
2169 );
2170 }
2171
2172 #[tokio::test]
2173 #[should_panic(
2174 expected = "get_block_info_at_slot() should only be called for slots above gc_round"
2175 )]
2176 async fn test_get_block_info_at_slot_panics_at_or_below_gc_round() {
2177 let (context, _) = Context::new_for_test(4);
2178 let context = Arc::new(context);
2179 let store = Arc::new(MemStore::new());
2180 let dag_state = DagState::new(context, store);
2181
2182 let _ = dag_state.get_block_info_at_slot(Slot::new(0, AuthorityIndex::new_for_test(0)));
2185 }
2186
2187 #[tokio::test]
2188 async fn test_block_children_exclusion() {
2189 let (mut context, _) = Context::new_for_test(4);
2190 context.parameters.dag_state_cached_rounds = 10;
2191 context.protocol_config.set_gc_depth_for_testing(3);
2192 context.protocol_config.set_enable_v3_for_testing(true);
2193 let context = Arc::new(context);
2194
2195 let store = Arc::new(MemStore::new());
2196 let mut dag_state = DagState::new(context.clone(), store.clone());
2197
2198 let mut dag_builder = DagBuilder::new(context.clone());
2200 dag_builder.layers(1..=2).build();
2201 let base_blocks = dag_builder.all_blocks();
2202 dag_state.accept_blocks(base_blocks.clone());
2203
2204 let round_2_refs: Vec<BlockRef> = base_blocks
2208 .iter()
2209 .filter(|b| b.round() == 2 && b.author() != AuthorityIndex::new_for_test(0))
2210 .map(|b| b.reference())
2211 .collect();
2212 let weak_ancestor = base_blocks
2213 .iter()
2214 .find(|b| b.round() == 1 && b.author() == AuthorityIndex::new_for_test(0))
2215 .expect("should have a round-1 authority-0 block")
2216 .reference();
2217
2218 let mut ancestors = round_2_refs.clone();
2219 ancestors.push(weak_ancestor);
2220 let round_3 =
2221 VerifiedBlock::new_for_test(TestBlock::new(3, 1).set_ancestors_raw(ancestors).build());
2222 let round_3_ref = round_3.reference();
2223 dag_state.accept_block(round_3);
2224
2225 let author_1 = AuthorityIndex::new_for_test(1);
2229 let stake_1 = context.committee.stake(author_1);
2230 for r2_ref in &round_2_refs {
2231 let children = dag_state
2232 .get_block_children(r2_ref)
2233 .expect("round-2 block should still be present");
2234 assert!(
2235 children.contains(&round_3_ref),
2236 "round-2 parent {r2_ref:?} should have round-3 block as child",
2237 );
2238 let authorities = dag_state
2239 .get_block_children_authorities(r2_ref)
2240 .expect("round-2 block should still be present");
2241 assert_eq!(
2242 authorities,
2243 BTreeSet::from([author_1]),
2244 "round-2 parent {r2_ref:?} should have children_authorities == {{1}}",
2245 );
2246 assert_eq!(
2247 dag_state
2248 .get_block_total_children_stake(r2_ref)
2249 .expect("round-2 block should still be present"),
2250 stake_1,
2251 "round-2 parent {r2_ref:?} total_children_stake mismatch",
2252 );
2253 }
2254
2255 let weak_children = dag_state
2258 .get_block_children(&weak_ancestor)
2259 .expect("weak ancestor should still be present");
2260 assert!(
2261 !weak_children.contains(&round_3_ref),
2262 "weak ancestor {weak_ancestor:?} must NOT have round-3 block as child",
2263 );
2264 for c in &weak_children {
2265 assert_eq!(
2266 c.round, 2,
2267 "weak ancestor's children should all be round 2, got {c:?}"
2268 );
2269 }
2270 let weak_authorities = dag_state
2275 .get_block_children_authorities(&weak_ancestor)
2276 .expect("weak ancestor should still be present");
2277 let expected_weak_authorities: BTreeSet<AuthorityIndex> =
2278 (0..4).map(AuthorityIndex::new_for_test).collect();
2279 assert_eq!(
2280 weak_authorities, expected_weak_authorities,
2281 "weak ancestor {weak_ancestor:?} children_authorities should cover all 4 round-2 authors",
2282 );
2283 assert_eq!(
2284 dag_state
2285 .get_block_total_children_stake(&weak_ancestor)
2286 .expect("weak ancestor should still be present"),
2287 context.committee.total_stake(),
2288 "weak ancestor {weak_ancestor:?} total_children_stake should equal total committee stake",
2289 );
2290 }
2291
2292 #[tokio::test]
2293 async fn test_round_info() {
2294 let (mut context, _) = Context::new_for_test(4);
2295 context.parameters.dag_state_cached_rounds = 2;
2297 context.protocol_config.set_gc_depth_for_testing(3);
2298 context.protocol_config.set_enable_v3_for_testing(true);
2299 let context = Arc::new(context);
2300
2301 let store = Arc::new(MemStore::new());
2302 let mut dag_state = DagState::new(context.clone(), store.clone());
2303
2304 assert!(dag_state.get_round_info(1).is_none());
2306
2307 let mut dag_builder = DagBuilder::new(context.clone());
2310 dag_builder.layers(1..=4).build();
2311 dag_state.accept_blocks(dag_builder.all_blocks());
2312
2313 let all_authorities: BTreeSet<AuthorityIndex> =
2314 (0..4).map(AuthorityIndex::new_for_test).collect();
2315 for round in 1..=4 {
2316 let info = dag_state
2317 .get_round_info(round)
2318 .unwrap_or_else(|| panic!("round_info missing for round {round}"));
2319 assert_eq!(info.round, round);
2320 assert_eq!(
2321 info.total_stake.authorities(),
2322 &all_authorities,
2323 "round {round} authorities mismatch"
2324 );
2325 assert_eq!(
2326 info.total_stake.stake(),
2327 context.committee.total_stake(),
2328 "round {round} total_stake mismatch"
2329 );
2330 }
2331
2332 assert!(dag_state.get_round_info(5).is_none());
2334
2335 dag_state.accept_blocks(dag_builder.all_blocks());
2338 for round in 1..=4 {
2339 let info = dag_state.get_round_info(round).unwrap();
2340 assert_eq!(
2341 info.total_stake.authorities(),
2342 &all_authorities,
2343 "round {round} authorities changed after re-accept"
2344 );
2345 assert_eq!(
2346 info.total_stake.stake(),
2347 context.committee.total_stake(),
2348 "round {round} total_stake changed after re-accept"
2349 );
2350 }
2351
2352 let round_5_block = VerifiedBlock::new_for_test(
2354 TestBlock::new(5, 0)
2355 .set_ancestors_raw(
2356 dag_builder
2357 .all_blocks()
2358 .iter()
2359 .filter(|b| b.round() == 4)
2360 .map(|b| b.reference())
2361 .collect(),
2362 )
2363 .build(),
2364 );
2365 dag_state.accept_block(round_5_block);
2366 let info_5 = dag_state
2367 .get_round_info(5)
2368 .expect("round 5 entry should exist");
2369 let author_0 = AuthorityIndex::new_for_test(0);
2370 assert_eq!(
2371 info_5.total_stake.authorities(),
2372 &BTreeSet::from([author_0])
2373 );
2374 assert_eq!(
2375 info_5.total_stake.stake(),
2376 context.committee.stake(author_0)
2377 );
2378
2379 let round_5_leader_ref = dag_state
2382 .recent_refs_by_authority
2383 .iter()
2384 .flat_map(|set| set.iter())
2385 .find(|r| r.round == 5)
2386 .copied()
2387 .expect("round 5 block should be accepted");
2388 let last_commit = TrustedCommit::new_for_test(
2389 5,
2390 CommitDigest::MIN,
2391 context.clock.timestamp_utc_ms(),
2392 round_5_leader_ref,
2393 vec![],
2394 );
2395 dag_state.set_last_commit(last_commit);
2396 assert_eq!(dag_state.gc_round(), 2);
2397
2398 for round in 1..=2 {
2401 assert!(
2402 dag_state.get_round_info(round).is_none(),
2403 "round {round} should be hidden before flush after GC advances"
2404 );
2405 }
2406
2407 dag_state.flush();
2408
2409 for round in 1..=2 {
2411 assert!(
2412 dag_state.get_round_info(round).is_none(),
2413 "round {round} should be evicted after flush"
2414 );
2415 }
2416 for round in 3..=4 {
2417 let info = dag_state
2418 .get_round_info(round)
2419 .unwrap_or_else(|| panic!("round_info missing for round {round} after flush"));
2420 assert_eq!(info.total_stake.authorities(), &all_authorities);
2421 assert_eq!(info.total_stake.stake(), context.committee.total_stake());
2422 }
2423 let info_5 = dag_state
2424 .get_round_info(5)
2425 .expect("round 5 should still be present after flush");
2426 assert_eq!(
2427 info_5.total_stake.authorities(),
2428 &BTreeSet::from([author_0])
2429 );
2430 assert_eq!(
2431 info_5.total_stake.stake(),
2432 context.committee.stake(author_0)
2433 );
2434 }
2435
2436 #[tokio::test]
2437 async fn test_contains_blocks_in_cache_or_store() {
2438 const CACHED_ROUNDS: Round = 2;
2440
2441 let (mut context, _) = Context::new_for_test(4);
2442 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
2443
2444 let context = Arc::new(context);
2445 let store = Arc::new(MemStore::new());
2446 let mut dag_state = DagState::new(context.clone(), store.clone());
2447
2448 let num_rounds: u32 = 10;
2450 let num_authorities: u32 = 4;
2451 let mut blocks = Vec::new();
2452
2453 for round in 1..=num_rounds {
2454 for author in 0..num_authorities {
2455 let block = VerifiedBlock::new_for_test(TestBlock::new(round, author).build());
2456 blocks.push(block);
2457 }
2458 }
2459
2460 blocks.clone().into_iter().for_each(|block| {
2462 if block.round() <= 4 {
2463 store
2464 .write(WriteBatch::default().blocks(vec![block]))
2465 .unwrap();
2466 } else {
2467 dag_state.accept_blocks(vec![block]);
2468 }
2469 });
2470
2471 let mut block_refs = blocks
2474 .iter()
2475 .map(|block| block.reference())
2476 .collect::<Vec<_>>();
2477 let result = dag_state.contains_blocks(block_refs.clone());
2478
2479 let mut expected = vec![true; (num_rounds * num_authorities) as usize];
2481 assert_eq!(result, expected);
2482
2483 block_refs.insert(
2485 3,
2486 BlockRef::new(11, AuthorityIndex::new_for_test(3), BlockDigest::default()),
2487 );
2488 let result = dag_state.contains_blocks(block_refs.clone());
2489
2490 expected.insert(3, false);
2492 assert_eq!(result, expected.clone());
2493 }
2494
2495 #[tokio::test]
2496 async fn test_contains_cached_block_at_slot() {
2497 const CACHED_ROUNDS: Round = 2;
2499
2500 let num_authorities: u32 = 4;
2501 let (mut context, _) = Context::new_for_test(num_authorities as usize);
2502 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
2503
2504 let context = Arc::new(context);
2505 let store = Arc::new(MemStore::new());
2506 let mut dag_state = DagState::new(context.clone(), store.clone());
2507
2508 let num_rounds: u32 = 10;
2510 let mut blocks = Vec::new();
2511
2512 for round in 1..=num_rounds {
2513 for author in 0..num_authorities {
2514 let block = VerifiedBlock::new_for_test(TestBlock::new(round, author).build());
2515 blocks.push(block.clone());
2516 dag_state.accept_block(block);
2517 }
2518 }
2519
2520 for (author, _) in context.committee.authorities() {
2522 assert!(
2523 dag_state.contains_cached_block_at_slot(Slot::new(GENESIS_ROUND, author)),
2524 "Genesis should always be found"
2525 );
2526 }
2527
2528 let mut block_refs = blocks
2531 .iter()
2532 .map(|block| block.reference())
2533 .collect::<Vec<_>>();
2534
2535 for block_ref in block_refs.clone() {
2536 let slot = block_ref.into();
2537 let found = dag_state.contains_cached_block_at_slot(slot);
2538 assert!(found, "A block should be found at slot {}", slot);
2539 }
2540
2541 block_refs.insert(
2544 3,
2545 BlockRef::new(11, AuthorityIndex::new_for_test(3), BlockDigest::default()),
2546 );
2547 let mut expected = vec![true; (num_rounds * num_authorities) as usize];
2548 expected.insert(3, false);
2549
2550 for block_ref in block_refs {
2552 let slot = block_ref.into();
2553 let found = dag_state.contains_cached_block_at_slot(slot);
2554
2555 assert_eq!(expected.remove(0), found);
2556 }
2557 }
2558
2559 #[tokio::test]
2560 #[ignore]
2561 #[should_panic(
2562 expected = "Attempted to check for slot [1]3 that is <= the last gc evicted round 3"
2563 )]
2564 async fn test_contains_cached_block_at_slot_panics_when_ask_out_of_range() {
2565 const GC_DEPTH: u32 = 2;
2568 const CACHED_ROUNDS: Round = 3;
2570
2571 let (mut context, _) = Context::new_for_test(4);
2572 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
2573 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
2574
2575 let context = Arc::new(context);
2576 let store = Arc::new(MemStore::new());
2577 let mut dag_state = DagState::new(context.clone(), store.clone());
2578
2579 let mut dag_builder = DagBuilder::new(context.clone());
2581 dag_builder.layers(1..=3).build();
2582 dag_builder
2583 .layers(4..=6)
2584 .authorities(vec![AuthorityIndex::new_for_test(0)])
2585 .skip_block()
2586 .build();
2587
2588 dag_builder
2590 .all_blocks()
2591 .into_iter()
2592 .for_each(|block| dag_state.accept_block(block));
2593
2594 dag_state.add_commit(TrustedCommit::new_for_test(
2596 1 as CommitIndex,
2597 CommitDigest::MIN,
2598 0,
2599 dag_builder.leader_block(5).unwrap().reference(),
2600 vec![],
2601 ));
2602 dag_state.flush();
2604
2605 assert_eq!(dag_state.gc_round(), 3, "GC round should be 3");
2607
2608 for authority_index in 1..=3 {
2612 for round in 4..=6 {
2613 assert!(dag_state.contains_cached_block_at_slot(Slot::new(
2614 round,
2615 AuthorityIndex::new_for_test(authority_index)
2616 )));
2617 }
2618 }
2619
2620 for round in 1..=3 {
2621 assert!(
2622 dag_state.contains_cached_block_at_slot(Slot::new(
2623 round,
2624 AuthorityIndex::new_for_test(0)
2625 ))
2626 );
2627 }
2628
2629 let _ =
2632 dag_state.contains_cached_block_at_slot(Slot::new(3, AuthorityIndex::new_for_test(1)));
2633 }
2634
2635 #[tokio::test]
2636 async fn test_get_blocks_in_cache_or_store() {
2637 let (context, _) = Context::new_for_test(4);
2638 let context = Arc::new(context);
2639 let store = Arc::new(MemStore::new());
2640 let mut dag_state = DagState::new(context.clone(), store.clone());
2641
2642 let num_rounds: u32 = 10;
2644 let num_authorities: u32 = 4;
2645 let mut blocks = Vec::new();
2646
2647 for round in 1..=num_rounds {
2648 for author in 0..num_authorities {
2649 let block = VerifiedBlock::new_for_test(TestBlock::new(round, author).build());
2650 blocks.push(block);
2651 }
2652 }
2653
2654 blocks.clone().into_iter().for_each(|block| {
2656 if block.round() <= 4 {
2657 store
2658 .write(WriteBatch::default().blocks(vec![block]))
2659 .unwrap();
2660 } else {
2661 dag_state.accept_blocks(vec![block]);
2662 }
2663 });
2664
2665 let mut block_refs = blocks
2668 .iter()
2669 .map(|block| block.reference())
2670 .collect::<Vec<_>>();
2671 let result = dag_state.get_blocks(&block_refs);
2672
2673 let mut expected = blocks
2674 .into_iter()
2675 .map(Some)
2676 .collect::<Vec<Option<VerifiedBlock>>>();
2677
2678 assert_eq!(result, expected.clone());
2680
2681 block_refs.insert(
2683 3,
2684 BlockRef::new(11, AuthorityIndex::new_for_test(3), BlockDigest::default()),
2685 );
2686 let result = dag_state.get_blocks(&block_refs);
2687
2688 expected.insert(3, None);
2690 assert_eq!(result, expected);
2691 }
2692
2693 #[tokio::test]
2694 async fn test_flush_and_recovery() {
2695 telemetry_subscribers::init_for_testing();
2696
2697 const GC_DEPTH: u32 = 3;
2698 const CACHED_ROUNDS: u32 = 4;
2699
2700 let num_authorities: u32 = 4;
2701 let (mut context, _) = Context::new_for_test(num_authorities as usize);
2702 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
2703 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
2704
2705 let context = Arc::new(context);
2706
2707 let store = Arc::new(MemStore::new());
2708 let mut dag_state = DagState::new(context.clone(), store.clone());
2709
2710 const NUM_ROUNDS: Round = 20;
2711 let mut dag_builder = DagBuilder::new(context.clone());
2712 dag_builder.layers(1..=5).build();
2713 dag_builder
2714 .layers(6..=8)
2715 .authorities(vec![AuthorityIndex::new_for_test(0)])
2716 .skip_block()
2717 .build();
2718 dag_builder.layers(9..=NUM_ROUNDS).build();
2719
2720 const LAST_COMMIT_ROUND: Round = 16;
2722 const LAST_COMMIT_INDEX: CommitIndex = 15;
2723 let commits = dag_builder
2724 .get_sub_dag_and_commits(1..=NUM_ROUNDS)
2725 .into_iter()
2726 .map(|(_subdag, commit)| commit)
2727 .take(LAST_COMMIT_INDEX as usize)
2728 .collect::<Vec<_>>();
2729 assert_eq!(commits.len(), LAST_COMMIT_INDEX as usize);
2730 assert_eq!(commits.last().unwrap().round(), LAST_COMMIT_ROUND);
2731
2732 const PERSISTED_BLOCK_ROUNDS: u32 = 12;
2736 const NUM_PERSISTED_COMMITS: usize = 8;
2737 const LAST_PERSISTED_COMMIT_ROUND: Round = 9;
2738 const LAST_PERSISTED_COMMIT_INDEX: CommitIndex = 8;
2739 dag_state.accept_blocks(dag_builder.blocks(1..=PERSISTED_BLOCK_ROUNDS));
2740 let mut finalized_commits = vec![];
2741 for commit in commits.iter().take(NUM_PERSISTED_COMMITS).cloned() {
2742 finalized_commits.push(commit.clone());
2743 dag_state.add_commit(commit);
2744 }
2745 let last_finalized_commit = finalized_commits.last().unwrap();
2746 assert_eq!(last_finalized_commit.round(), LAST_PERSISTED_COMMIT_ROUND);
2747 assert_eq!(last_finalized_commit.index(), LAST_PERSISTED_COMMIT_INDEX);
2748
2749 let finalized_blocks = finalized_commits
2751 .iter()
2752 .flat_map(|commit| commit.blocks())
2753 .collect::<BTreeSet<_>>();
2754
2755 dag_state.flush();
2757
2758 let store_blocks = store
2760 .scan_blocks_by_author(AuthorityIndex::new_for_test(1), 1)
2761 .unwrap();
2762 assert_eq!(store_blocks.last().unwrap().round(), PERSISTED_BLOCK_ROUNDS);
2763 let store_commits = store.scan_commits((0..=CommitIndex::MAX).into()).unwrap();
2764 assert_eq!(store_commits.len(), NUM_PERSISTED_COMMITS);
2765 assert_eq!(
2766 store_commits.last().unwrap().index(),
2767 LAST_PERSISTED_COMMIT_INDEX
2768 );
2769 assert_eq!(
2770 store_commits.last().unwrap().round(),
2771 LAST_PERSISTED_COMMIT_ROUND
2772 );
2773
2774 dag_state.accept_blocks(dag_builder.blocks(PERSISTED_BLOCK_ROUNDS + 1..=NUM_ROUNDS));
2776 for commit in commits.iter().skip(NUM_PERSISTED_COMMITS).cloned() {
2777 dag_state.add_commit(commit);
2778 }
2779
2780 let all_blocks = dag_builder.blocks(1..=NUM_ROUNDS);
2782 let block_refs = all_blocks
2783 .iter()
2784 .map(|block| block.reference())
2785 .collect::<Vec<_>>();
2786 let result = dag_state
2787 .get_blocks(&block_refs)
2788 .into_iter()
2789 .map(|b| b.unwrap())
2790 .collect::<Vec<_>>();
2791 assert_eq!(result, all_blocks);
2792
2793 assert_eq!(dag_state.last_commit_index(), LAST_COMMIT_INDEX);
2795
2796 drop(dag_state);
2798
2799 let dag_state = DagState::new(context.clone(), store.clone());
2801
2802 let all_blocks = dag_builder.blocks(1..=PERSISTED_BLOCK_ROUNDS);
2804 let block_refs = all_blocks
2805 .iter()
2806 .map(|block| block.reference())
2807 .collect::<Vec<_>>();
2808 let result = dag_state
2809 .get_blocks(&block_refs)
2810 .into_iter()
2811 .map(|b| b.unwrap())
2812 .collect::<Vec<_>>();
2813 assert_eq!(result, all_blocks);
2814
2815 let missing_blocks = dag_builder.blocks(PERSISTED_BLOCK_ROUNDS + 1..=NUM_ROUNDS);
2817 let block_refs = missing_blocks
2818 .iter()
2819 .map(|block| block.reference())
2820 .collect::<Vec<_>>();
2821 let retrieved_blocks = dag_state
2822 .get_blocks(&block_refs)
2823 .into_iter()
2824 .flatten()
2825 .collect::<Vec<_>>();
2826 assert!(retrieved_blocks.is_empty());
2827
2828 assert_eq!(dag_state.last_commit_index(), LAST_PERSISTED_COMMIT_INDEX);
2830 assert_eq!(dag_state.last_commit_round(), LAST_PERSISTED_COMMIT_ROUND);
2831
2832 let expected_last_committed_rounds = vec![5, 9, 8, 8];
2834 assert_eq!(
2835 dag_state.last_committed_rounds(),
2836 expected_last_committed_rounds
2837 );
2838 assert_eq!(dag_state.scoring_subdags_count(), NUM_PERSISTED_COMMITS);
2840
2841 for (authority_index, _) in context.committee.authorities() {
2843 let blocks = dag_state.get_cached_blocks(authority_index, 1);
2844
2845 if authority_index == AuthorityIndex::new_for_test(0) {
2849 assert_eq!(blocks.len(), 4);
2850 assert_eq!(dag_state.evicted_rounds[authority_index.value()], 6);
2851 assert!(
2852 blocks
2853 .into_iter()
2854 .all(|block| block.round() >= 7 && block.round() <= 12)
2855 );
2856 } else {
2857 assert_eq!(blocks.len(), 6);
2858 assert_eq!(dag_state.evicted_rounds[authority_index.value()], 6);
2859 assert!(
2860 blocks
2861 .into_iter()
2862 .all(|block| block.round() >= 7 && block.round() <= 12)
2863 );
2864 }
2865 }
2866
2867 let gc_round = dag_state.gc_round();
2869 assert_eq!(gc_round, 6);
2870 dag_state
2871 .recent_blocks
2872 .iter()
2873 .for_each(|(block_ref, block_info)| {
2874 if block_ref.round > gc_round && finalized_blocks.contains(block_ref) {
2875 assert!(
2876 block_info.committed,
2877 "Block {:?} should be set as committed",
2878 block_ref
2879 );
2880 }
2881 });
2882
2883 dag_state
2888 .recent_blocks
2889 .iter()
2890 .for_each(|(block_ref, block_info)| {
2891 if block_ref.round < PERSISTED_BLOCK_ROUNDS || block_ref.author.value() == 0 {
2892 assert!(block_info.included);
2893 } else {
2894 assert!(!block_info.included);
2895 }
2896 });
2897 }
2898
2899 #[tokio::test]
2900 async fn test_block_info_as_committed() {
2901 let num_authorities: u32 = 4;
2902 let (context, _) = Context::new_for_test(num_authorities as usize);
2903 let context = Arc::new(context);
2904
2905 let store = Arc::new(MemStore::new());
2906 let mut dag_state = DagState::new(context.clone(), store.clone());
2907
2908 let block = VerifiedBlock::new_for_test(
2910 TestBlock::new(1, 0)
2911 .set_timestamp_ms(1000)
2912 .set_ancestors(vec![])
2913 .build(),
2914 );
2915
2916 dag_state.accept_block(block.clone());
2917
2918 assert!(!dag_state.is_committed(&block.reference()));
2920
2921 assert!(
2923 dag_state.set_committed(&block.reference()),
2924 "Block should be successfully set as committed for first time"
2925 );
2926
2927 assert!(dag_state.is_committed(&block.reference()));
2929
2930 assert!(
2932 !dag_state.set_committed(&block.reference()),
2933 "Block should not be successfully set as committed"
2934 );
2935 }
2936
2937 #[tokio::test]
2938 async fn test_get_cached_blocks() {
2939 let (mut context, _) = Context::new_for_test(4);
2940 context.parameters.dag_state_cached_rounds = 5;
2941
2942 let context = Arc::new(context);
2943 let store = Arc::new(MemStore::new());
2944 let mut dag_state = DagState::new(context.clone(), store.clone());
2945
2946 let mut all_blocks = Vec::new();
2951 for author in 1..=3 {
2952 for round in 10..(10 + author) {
2953 let block = VerifiedBlock::new_for_test(TestBlock::new(round, author).build());
2954 all_blocks.push(block.clone());
2955 dag_state.accept_block(block);
2956 }
2957 }
2958
2959 let cached_blocks =
2962 dag_state.get_cached_blocks(context.committee.to_authority_index(0).unwrap(), 0);
2963 assert!(cached_blocks.is_empty());
2964
2965 let cached_blocks =
2966 dag_state.get_cached_blocks(context.committee.to_authority_index(1).unwrap(), 10);
2967 assert_eq!(cached_blocks.len(), 1);
2968 assert_eq!(cached_blocks[0].round(), 10);
2969
2970 let cached_blocks =
2971 dag_state.get_cached_blocks(context.committee.to_authority_index(2).unwrap(), 10);
2972 assert_eq!(cached_blocks.len(), 2);
2973 assert_eq!(cached_blocks[0].round(), 10);
2974 assert_eq!(cached_blocks[1].round(), 11);
2975
2976 let cached_blocks =
2977 dag_state.get_cached_blocks(context.committee.to_authority_index(2).unwrap(), 11);
2978 assert_eq!(cached_blocks.len(), 1);
2979 assert_eq!(cached_blocks[0].round(), 11);
2980
2981 let cached_blocks =
2982 dag_state.get_cached_blocks(context.committee.to_authority_index(3).unwrap(), 10);
2983 assert_eq!(cached_blocks.len(), 3);
2984 assert_eq!(cached_blocks[0].round(), 10);
2985 assert_eq!(cached_blocks[1].round(), 11);
2986 assert_eq!(cached_blocks[2].round(), 12);
2987
2988 let cached_blocks =
2989 dag_state.get_cached_blocks(context.committee.to_authority_index(3).unwrap(), 12);
2990 assert_eq!(cached_blocks.len(), 1);
2991 assert_eq!(cached_blocks[0].round(), 12);
2992
2993 let cached_blocks = dag_state.get_cached_blocks_in_range(
2997 context.committee.to_authority_index(3).unwrap(),
2998 10,
2999 10,
3000 1,
3001 );
3002 assert!(cached_blocks.is_empty());
3003
3004 let cached_blocks = dag_state.get_cached_blocks_in_range(
3006 context.committee.to_authority_index(3).unwrap(),
3007 11,
3008 10,
3009 1,
3010 );
3011 assert!(cached_blocks.is_empty());
3012
3013 let cached_blocks = dag_state.get_cached_blocks_in_range(
3015 context.committee.to_authority_index(0).unwrap(),
3016 9,
3017 10,
3018 1,
3019 );
3020 assert!(cached_blocks.is_empty());
3021
3022 let cached_blocks = dag_state.get_cached_blocks_in_range(
3024 context.committee.to_authority_index(1).unwrap(),
3025 9,
3026 11,
3027 1,
3028 );
3029 assert_eq!(cached_blocks.len(), 1);
3030 assert_eq!(cached_blocks[0].round(), 10);
3031
3032 let cached_blocks = dag_state.get_cached_blocks_in_range(
3034 context.committee.to_authority_index(2).unwrap(),
3035 9,
3036 12,
3037 5,
3038 );
3039 assert_eq!(cached_blocks.len(), 2);
3040 assert_eq!(cached_blocks[0].round(), 10);
3041 assert_eq!(cached_blocks[1].round(), 11);
3042
3043 let cached_blocks = dag_state.get_cached_blocks_in_range(
3045 context.committee.to_authority_index(3).unwrap(),
3046 11,
3047 20,
3048 5,
3049 );
3050 assert_eq!(cached_blocks.len(), 2);
3051 assert_eq!(cached_blocks[0].round(), 11);
3052 assert_eq!(cached_blocks[1].round(), 12);
3053
3054 let cached_blocks = dag_state.get_cached_blocks_in_range(
3056 context.committee.to_authority_index(3).unwrap(),
3057 10,
3058 20,
3059 1,
3060 );
3061 assert_eq!(cached_blocks.len(), 1);
3062 assert_eq!(cached_blocks[0].round(), 10);
3063 }
3064
3065 #[tokio::test]
3066 async fn test_get_last_cached_block() {
3067 const CACHED_ROUNDS: Round = 2;
3069 const GC_DEPTH: u32 = 1;
3070 let (mut context, _) = Context::new_for_test(4);
3071 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
3072 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
3073
3074 let context = Arc::new(context);
3075 let store = Arc::new(MemStore::new());
3076 let mut dag_state = DagState::new(context.clone(), store.clone());
3077
3078 let dag_str = "DAG {
3083 Round 0 : { 4 },
3084 Round 1 : {
3085 B -> [*],
3086 C -> [*],
3087 D -> [*],
3088 },
3089 Round 2 : {
3090 C -> [*],
3091 D -> [*],
3092 },
3093 Round 3 : {
3094 D -> [*],
3095 },
3096 }";
3097
3098 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
3099
3100 let block = VerifiedBlock::new_for_test(TestBlock::new(2, 2).build());
3102
3103 for block in dag_builder
3105 .all_blocks()
3106 .into_iter()
3107 .chain(std::iter::once(block))
3108 {
3109 dag_state.accept_block(block);
3110 }
3111
3112 dag_state.add_commit(TrustedCommit::new_for_test(
3113 1 as CommitIndex,
3114 CommitDigest::MIN,
3115 context.clock.timestamp_utc_ms(),
3116 dag_builder.leader_block(3).unwrap().reference(),
3117 vec![],
3118 ));
3119
3120 let end_round = 4;
3122 let expected_rounds = vec![0, 1, 2, 3];
3123 let expected_excluded_and_equivocating_blocks = vec![0, 0, 1, 0];
3124 let last_blocks = dag_state.get_last_cached_block_per_authority(end_round);
3126 assert_eq!(
3127 last_blocks.iter().map(|b| b.0.round()).collect::<Vec<_>>(),
3128 expected_rounds
3129 );
3130 assert_eq!(
3131 last_blocks.iter().map(|b| b.1.len()).collect::<Vec<_>>(),
3132 expected_excluded_and_equivocating_blocks
3133 );
3134
3135 for (i, expected_round) in expected_rounds.iter().enumerate() {
3137 let round = dag_state
3138 .get_last_cached_block_in_range(
3139 context.committee.to_authority_index(i).unwrap(),
3140 0,
3141 end_round,
3142 )
3143 .map(|b| b.round())
3144 .unwrap_or_default();
3145 assert_eq!(round, *expected_round, "Authority {i}");
3146 }
3147
3148 let start_round = 2;
3150 let expected_rounds = [0, 0, 2, 3];
3151
3152 for (i, expected_round) in expected_rounds.iter().enumerate() {
3154 let round = dag_state
3155 .get_last_cached_block_in_range(
3156 context.committee.to_authority_index(i).unwrap(),
3157 start_round,
3158 end_round,
3159 )
3160 .map(|b| b.round())
3161 .unwrap_or_default();
3162 assert_eq!(round, *expected_round, "Authority {i}");
3163 }
3164
3165 dag_state.flush();
3171
3172 let end_round = 3;
3174 let expected_rounds = vec![0, 1, 2, 2];
3175
3176 let last_blocks = dag_state.get_last_cached_block_per_authority(end_round);
3178 assert_eq!(
3179 last_blocks.iter().map(|b| b.0.round()).collect::<Vec<_>>(),
3180 expected_rounds
3181 );
3182
3183 for (i, expected_round) in expected_rounds.iter().enumerate() {
3185 let round = dag_state
3186 .get_last_cached_block_in_range(
3187 context.committee.to_authority_index(i).unwrap(),
3188 0,
3189 end_round,
3190 )
3191 .map(|b| b.round())
3192 .unwrap_or_default();
3193 assert_eq!(round, *expected_round, "Authority {i}");
3194 }
3195 }
3196
3197 #[tokio::test]
3198 #[should_panic(
3199 expected = "Attempted to request for blocks of rounds < 2, when the last evicted round is 1 for authority [2]"
3200 )]
3201 async fn test_get_cached_last_block_per_authority_requesting_out_of_round_range() {
3202 const CACHED_ROUNDS: Round = 1;
3204 const GC_DEPTH: u32 = 1;
3205 let (mut context, _) = Context::new_for_test(4);
3206 context.parameters.dag_state_cached_rounds = CACHED_ROUNDS;
3207 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
3208
3209 let context = Arc::new(context);
3210 let store = Arc::new(MemStore::new());
3211 let mut dag_state = DagState::new(context.clone(), store.clone());
3212
3213 let mut dag_builder = DagBuilder::new(context.clone());
3218 dag_builder
3219 .layers(1..=1)
3220 .authorities(vec![AuthorityIndex::new_for_test(0)])
3221 .skip_block()
3222 .build();
3223 dag_builder
3224 .layers(2..=2)
3225 .authorities(vec![
3226 AuthorityIndex::new_for_test(0),
3227 AuthorityIndex::new_for_test(1),
3228 ])
3229 .skip_block()
3230 .build();
3231 dag_builder
3232 .layers(3..=3)
3233 .authorities(vec![
3234 AuthorityIndex::new_for_test(0),
3235 AuthorityIndex::new_for_test(1),
3236 AuthorityIndex::new_for_test(2),
3237 ])
3238 .skip_block()
3239 .build();
3240
3241 for block in dag_builder.all_blocks() {
3243 dag_state.accept_block(block);
3244 }
3245
3246 dag_state.add_commit(TrustedCommit::new_for_test(
3247 1 as CommitIndex,
3248 CommitDigest::MIN,
3249 0,
3250 dag_builder.leader_block(3).unwrap().reference(),
3251 vec![],
3252 ));
3253
3254 dag_state.flush();
3256
3257 dag_state.get_last_cached_block_per_authority(2);
3259 }
3260
3261 #[tokio::test]
3262 async fn test_last_quorum() {
3263 let (context, _) = Context::new_for_test(4);
3265 let context = Arc::new(context);
3266 let store = Arc::new(MemStore::new());
3267 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
3268
3269 {
3271 let genesis = genesis_blocks(context.as_ref());
3272
3273 assert_eq!(dag_state.read().last_quorum(), genesis);
3274 }
3275
3276 {
3278 let mut dag_builder = DagBuilder::new(context.clone());
3279 dag_builder
3280 .layers(1..=4)
3281 .build()
3282 .persist_layers(dag_state.clone());
3283 let round_4_blocks: Vec<_> = dag_builder
3284 .blocks(4..=4)
3285 .into_iter()
3286 .map(|block| block.reference())
3287 .collect();
3288
3289 let last_quorum = dag_state.read().last_quorum();
3290
3291 assert_eq!(
3292 last_quorum
3293 .into_iter()
3294 .map(|block| block.reference())
3295 .collect::<Vec<_>>(),
3296 round_4_blocks
3297 );
3298 }
3299
3300 {
3302 let block = VerifiedBlock::new_for_test(TestBlock::new(5, 0).build());
3303 dag_state.write().accept_block(block);
3304
3305 let round_4_blocks = dag_state.read().get_uncommitted_blocks_at_round(4);
3306
3307 let last_quorum = dag_state.read().last_quorum();
3308
3309 assert_eq!(last_quorum, round_4_blocks);
3310 }
3311 }
3312
3313 #[tokio::test]
3314 async fn test_last_block_for_authority() {
3315 let (context, _) = Context::new_for_test(4);
3317 let context = Arc::new(context);
3318 let store = Arc::new(MemStore::new());
3319 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
3320
3321 {
3323 let genesis = genesis_blocks(context.as_ref());
3324 let my_genesis = genesis
3325 .into_iter()
3326 .find(|block| block.author() == context.own_index)
3327 .unwrap();
3328
3329 assert_eq!(dag_state.read().get_last_proposed_block(), Some(my_genesis));
3330 }
3331
3332 {
3334 let mut dag_builder = DagBuilder::new(context.clone());
3336 dag_builder
3337 .layers(1..=4)
3338 .build()
3339 .persist_layers(dag_state.clone());
3340
3341 let block = VerifiedBlock::new_for_test(TestBlock::new(5, 0).build());
3343 dag_state.write().accept_block(block);
3344
3345 let block = dag_state
3346 .read()
3347 .get_last_block_for_authority(AuthorityIndex::new_for_test(0));
3348 assert_eq!(block.round(), 5);
3349
3350 for (authority_index, _) in context.committee.authorities() {
3351 let block = dag_state
3352 .read()
3353 .get_last_block_for_authority(authority_index);
3354
3355 if authority_index.value() == 0 {
3356 assert_eq!(block.round(), 5);
3357 } else {
3358 assert_eq!(block.round(), 4);
3359 }
3360 }
3361 }
3362 }
3363
3364 #[tokio::test]
3365 async fn test_accept_block_not_panics_when_timestamp_is_ahead_and_median_timestamp() {
3366 let (context, _) = Context::new_for_test(4);
3368 let context = Arc::new(context);
3369 let store = Arc::new(MemStore::new());
3370 let mut dag_state = DagState::new(context.clone(), store.clone());
3371
3372 let block_timestamp = context.clock.timestamp_utc_ms() + 5_000;
3374
3375 let block = VerifiedBlock::new_for_test(
3376 TestBlock::new(10, 0)
3377 .set_timestamp_ms(block_timestamp)
3378 .build(),
3379 );
3380
3381 dag_state.accept_block(block);
3383 }
3384
3385 #[tokio::test]
3386 async fn test_last_finalized_commit() {
3387 let (context, _) = Context::new_for_test(4);
3389 let context = Arc::new(context);
3390 let store = Arc::new(MemStore::new());
3391 let mut dag_state = DagState::new(context.clone(), store.clone());
3392
3393 let commit_ref = CommitRef::new(1, CommitDigest::MIN);
3395 let rejected_transactions = BTreeMap::new();
3396 dag_state.add_finalized_commit(commit_ref, rejected_transactions.clone());
3397
3398 assert_eq!(dag_state.finalized_commits_to_write.len(), 1);
3400 assert_eq!(
3401 dag_state.finalized_commits_to_write[0],
3402 (commit_ref, rejected_transactions.clone())
3403 );
3404
3405 dag_state.flush();
3407
3408 let last_finalized_commit = store.read_last_finalized_commit().unwrap();
3410 assert_eq!(last_finalized_commit, Some(commit_ref));
3411 let stored_rejected_transactions = store
3412 .read_rejected_transactions(commit_ref)
3413 .unwrap()
3414 .unwrap();
3415 assert_eq!(stored_rejected_transactions, rejected_transactions);
3416 }
3417}