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