1use std::sync::Arc;
5
6use consensus_config::Stake;
7use consensus_types::block::{BlockRef, BlockTimestampMs, Round};
8use itertools::Itertools;
9use parking_lot::RwLock;
10
11use crate::{
12 block::{BlockAPI, VerifiedBlock},
13 commit::{
14 Commit, CommittedSubDag, TrustedCommit, compute_sort_seed, sort_committed_blocks,
15 sort_sub_dag_blocks,
16 },
17 context::Context,
18 dag_state::DagState,
19};
20
21pub(crate) trait BlockStoreAPI {
24 fn get_blocks(&self, refs: &[BlockRef]) -> Vec<Option<VerifiedBlock>>;
25
26 fn gc_round(&self) -> Round;
27
28 fn set_committed(&mut self, block_ref: &BlockRef) -> bool;
29
30 fn is_committed(&self, block_ref: &BlockRef) -> bool;
31}
32
33impl BlockStoreAPI
34 for parking_lot::lock_api::RwLockWriteGuard<'_, parking_lot::RawRwLock, DagState>
35{
36 fn get_blocks(&self, refs: &[BlockRef]) -> Vec<Option<VerifiedBlock>> {
37 DagState::get_blocks(self, refs)
38 }
39
40 fn gc_round(&self) -> Round {
41 DagState::gc_round(self)
42 }
43
44 fn set_committed(&mut self, block_ref: &BlockRef) -> bool {
45 DagState::set_committed(self, block_ref)
46 }
47
48 fn is_committed(&self, block_ref: &BlockRef) -> bool {
49 DagState::is_committed(self, block_ref)
50 }
51}
52
53#[derive(Clone)]
55pub struct Linearizer {
56 context: Arc<Context>,
58 dag_state: Arc<RwLock<DagState>>,
59}
60
61impl Linearizer {
62 pub fn new(context: Arc<Context>, dag_state: Arc<RwLock<DagState>>) -> Self {
63 Self { context, dag_state }
64 }
65
66 fn collect_sub_dag_and_commit(
69 &mut self,
70 leader_block: VerifiedBlock,
71 ) -> (CommittedSubDag, TrustedCommit) {
72 let _s = self
73 .context
74 .metrics
75 .node_metrics
76 .scope_processing_time
77 .with_label_values(&["Linearizer::collect_sub_dag_and_commit"])
78 .start_timer();
79
80 let mut dag_state = self.dag_state.write();
82 let last_commit_index = dag_state.last_commit_index();
83 let last_commit_digest = dag_state.last_commit_digest();
84 let last_commit_timestamp_ms = dag_state.last_commit_timestamp_ms();
85
86 let to_commit = Self::linearize_sub_dag(
88 leader_block.clone(),
89 &mut dag_state,
90 self.context.protocol_config.shuffle_commit_blocks(),
91 );
92
93 let timestamp_ms = Self::calculate_commit_timestamp(
94 &self.context,
95 &mut dag_state,
96 &leader_block,
97 last_commit_timestamp_ms,
98 );
99
100 drop(dag_state);
101
102 let commit = Commit::new(
104 last_commit_index + 1,
105 last_commit_digest,
106 timestamp_ms,
107 leader_block.reference(),
108 to_commit
109 .iter()
110 .map(|block| block.reference())
111 .collect::<Vec<_>>(),
112 );
113 let serialized = commit
114 .serialize()
115 .unwrap_or_else(|e| panic!("Failed to serialize commit: {}", e));
116 let commit = TrustedCommit::new_trusted(commit, serialized);
117
118 let sub_dag = CommittedSubDag::new(
120 leader_block.reference(),
121 to_commit,
122 timestamp_ms,
123 commit.reference(),
124 );
125
126 (sub_dag, commit)
127 }
128
129 pub(crate) fn calculate_commit_timestamp(
133 context: &Context,
134 dag_state: &mut impl BlockStoreAPI,
135 leader_block: &VerifiedBlock,
136 last_commit_timestamp_ms: BlockTimestampMs,
137 ) -> BlockTimestampMs {
138 let timestamp_ms = {
139 let block_refs = leader_block
141 .ancestors()
142 .iter()
143 .filter(|block_ref| block_ref.round == leader_block.round() - 1)
144 .cloned()
145 .collect::<Vec<_>>();
146 let block_opts = dag_state.get_blocks(&block_refs);
148 let blocks = block_opts.iter().map(|block_opt| {
149 block_opt
150 .as_ref()
151 .expect("We should have all blocks in dag state.")
152 });
153 median_timestamp_by_stake(context, blocks).unwrap_or_else(|e| {
154 panic!(
155 "Cannot compute median timestamp for leader block {:?} ancestors: {}",
156 leader_block, e
157 )
158 })
159 };
160
161 timestamp_ms.max(last_commit_timestamp_ms)
163 }
164
165 pub(crate) fn linearize_sub_dag(
169 leader_block: VerifiedBlock,
170 dag_state: &mut impl BlockStoreAPI,
171 shuffle_blocks: bool,
172 ) -> Vec<VerifiedBlock> {
173 let gc_round: Round = dag_state.gc_round();
178 let leader_block_ref = leader_block.reference();
179 let seed = shuffle_blocks.then(|| compute_sort_seed(std::slice::from_ref(&leader_block)));
180 let mut buffer = vec![leader_block];
181 let mut to_commit = Vec::new();
182
183 assert!(
186 dag_state.set_committed(&leader_block_ref),
187 "Leader block with reference {:?} attempted to be committed twice",
188 leader_block_ref
189 );
190
191 while let Some(x) = buffer.pop() {
192 to_commit.push(x.clone());
193
194 let ancestors: Vec<VerifiedBlock> = dag_state
195 .get_blocks(
196 &x.ancestors()
197 .iter()
198 .copied()
199 .filter(|ancestor| {
200 ancestor.round > gc_round && !dag_state.is_committed(ancestor)
201 })
202 .collect::<Vec<_>>(),
203 )
204 .into_iter()
205 .map(|ancestor_opt| {
206 ancestor_opt.expect("We should have all uncommitted blocks in dag state.")
207 })
208 .collect();
209
210 for ancestor in ancestors {
211 buffer.push(ancestor.clone());
212 assert!(
213 dag_state.set_committed(&ancestor.reference()),
214 "Block with reference {:?} attempted to be committed twice",
215 ancestor.reference()
216 );
217 }
218 }
219
220 assert!(
223 to_commit.iter().all(|block| block.round() > gc_round),
224 "No blocks <= {gc_round} should be committed. Leader round {}, blocks {to_commit:?}.",
225 leader_block_ref
226 );
227
228 match seed {
229 Some(seed) => sort_committed_blocks(&mut to_commit, &seed),
230 None => sort_sub_dag_blocks(&mut to_commit),
231 }
232
233 to_commit
234 }
235
236 pub fn handle_commit(&mut self, committed_leaders: Vec<VerifiedBlock>) -> Vec<CommittedSubDag> {
240 if committed_leaders.is_empty() {
241 return vec![];
242 }
243
244 let mut committed_sub_dags = vec![];
245 for leader_block in committed_leaders {
246 let (sub_dag, commit) = self.collect_sub_dag_and_commit(leader_block);
248
249 self.update_blocks_pruned_metric(&sub_dag);
250
251 self.dag_state.write().add_commit(commit.clone());
254
255 committed_sub_dags.push(sub_dag);
256 }
257
258 committed_sub_dags
259 }
260
261 fn update_blocks_pruned_metric(&self, sub_dag: &CommittedSubDag) {
266 let (last_committed_rounds, gc_round) = {
267 let dag_state = self.dag_state.read();
268 (dag_state.last_committed_rounds(), dag_state.gc_round())
269 };
270
271 for block_ref in sub_dag
272 .blocks
273 .iter()
274 .flat_map(|block| block.ancestors())
275 .filter(
276 |ancestor_ref| {
277 ancestor_ref.round <= gc_round
278 && last_committed_rounds[ancestor_ref.author] != ancestor_ref.round
279 }, )
282 .unique()
283 {
284 let hostname = &self.context.committee.authority(block_ref.author).hostname;
285
286 let label_values = if last_committed_rounds[block_ref.author] < block_ref.round {
288 &[hostname, "uncommitted"]
289 } else {
290 &[hostname, "higher_committed"]
292 };
293
294 self.context
295 .metrics
296 .node_metrics
297 .blocks_pruned_on_commit
298 .with_label_values(label_values)
299 .inc();
300 }
301 }
302}
303
304pub(crate) fn median_timestamp_by_stake<'a>(
308 context: &Context,
309 blocks: impl IntoIterator<Item = &'a VerifiedBlock>,
310) -> Result<BlockTimestampMs, String> {
311 let mut total_stake = 0;
312 let mut timestamps = vec![];
313 for block in blocks {
314 let stake = context.committee.authority(block.author()).stake;
315 timestamps.push((block.timestamp_ms(), stake));
316 total_stake += stake;
317 }
318
319 if timestamps.is_empty() {
320 return Err("No blocks provided".to_string());
321 }
322 if total_stake < context.committee.quorum_threshold() {
323 return Err(format!(
324 "Total stake {} < quorum threshold {}",
325 total_stake,
326 context.committee.quorum_threshold()
327 ));
328 }
329
330 Ok(median_timestamps_by_stake_inner(timestamps, total_stake))
331}
332
333fn median_timestamps_by_stake_inner(
334 mut timestamps: Vec<(BlockTimestampMs, Stake)>,
335 total_stake: Stake,
336) -> BlockTimestampMs {
337 timestamps.sort_by_key(|(ts, _)| *ts);
338
339 let mut cumulative_stake = 0;
340 for (ts, stake) in ×tamps {
341 cumulative_stake += stake;
342 if cumulative_stake > total_stake / 2 {
343 return *ts;
344 }
345 }
346
347 timestamps.last().unwrap().0
348}
349
350#[cfg(test)]
351mod tests {
352 use consensus_config::AuthorityIndex;
353 use mysten_common::ZipDebugEqIteratorExt as _;
354 use rstest::rstest;
355
356 use super::*;
357 use crate::{
358 CommitIndex, TestBlock,
359 commit::{CommitAPI as _, CommitDigest, DEFAULT_WAVE_LENGTH},
360 context::Context,
361 leader_schedule::{LeaderSchedule, LeaderSwapTable},
362 storage::mem_store::MemStore,
363 test_dag_builder::DagBuilder,
364 test_dag_parser::parse_dag,
365 };
366
367 #[rstest]
368 #[tokio::test]
369 async fn test_handle_commit() {
370 telemetry_subscribers::init_for_testing();
371 let num_authorities = 4;
372 let (context, _keys) = Context::new_for_test(num_authorities);
373 let context = Arc::new(context);
374
375 let dag_state = Arc::new(RwLock::new(DagState::new(
376 context.clone(),
377 Arc::new(MemStore::new()),
378 )));
379 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
380
381 let num_rounds: u32 = 10;
383 let mut dag_builder = DagBuilder::new(context.clone());
384 dag_builder
385 .layers(1..=num_rounds)
386 .build()
387 .persist_layers(dag_state.clone());
388
389 let leaders = dag_builder
390 .leader_blocks(1..=num_rounds)
391 .into_iter()
392 .map(Option::unwrap)
393 .collect::<Vec<_>>();
394
395 let commits = linearizer.handle_commit(leaders.clone());
396 for (idx, subdag) in commits.into_iter().enumerate() {
397 tracing::info!("{subdag:?}");
398 assert_eq!(subdag.leader, leaders[idx].reference());
399
400 let expected_ts = {
401 let block_refs = leaders[idx]
402 .ancestors()
403 .iter()
404 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
405 .cloned()
406 .collect::<Vec<_>>();
407 let block_opts = dag_state.read().get_blocks(&block_refs);
408 let blocks = block_opts.iter().map(|block_opt| {
409 block_opt
410 .as_ref()
411 .expect("We should have all blocks in dag state.")
412 });
413
414 median_timestamp_by_stake(&context, blocks).unwrap()
415 };
416 assert_eq!(subdag.timestamp_ms, expected_ts);
417
418 if idx == 0 {
419 assert_eq!(subdag.blocks.len(), 1);
421 } else {
422 assert_eq!(subdag.blocks.len(), num_authorities);
425 }
426 for block in subdag.blocks.iter() {
427 assert!(block.round() <= leaders[idx].round());
428 }
429 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
430 }
431 }
432
433 #[rstest]
434 #[tokio::test]
435 async fn test_handle_commit_shuffles_blocks_within_round() {
436 telemetry_subscribers::init_for_testing();
437 let (context, _keys) = Context::new_for_test(7);
438 let num_rounds: u32 = 10;
439 let mut dag_builder = DagBuilder::new(Arc::new(context.clone()));
440 dag_builder.layers(1..=num_rounds).build();
441 let leaders: Vec<_> = dag_builder
442 .leader_blocks(1..=num_rounds)
443 .into_iter()
444 .map(Option::unwrap)
445 .collect();
446
447 let commit_all = |shuffle: bool| {
448 let mut context = context.clone();
449 context
450 .protocol_config
451 .set_shuffle_commit_blocks_for_testing(shuffle);
452 let context = Arc::new(context);
453 let dag_state = Arc::new(RwLock::new(DagState::new(
454 context.clone(),
455 Arc::new(MemStore::new()),
456 )));
457 dag_builder.persist_all_blocks(dag_state.clone());
458 Linearizer::new(context, dag_state)
459 .handle_commit(leaders.clone())
460 .into_iter()
461 .map(|subdag| {
462 subdag
463 .blocks
464 .iter()
465 .map(|b| b.reference())
466 .collect::<Vec<_>>()
467 })
468 .collect::<Vec<_>>()
469 };
470
471 let unshuffled = commit_all(false);
472 let shuffled = commit_all(true);
473 assert_eq!(shuffled, commit_all(true));
474 assert_eq!(shuffled.len(), unshuffled.len());
475
476 let mut any_reordered = false;
477 for ((shuffled, unshuffled), leader) in shuffled
478 .iter()
479 .zip_debug_eq(&unshuffled)
480 .zip_debug_eq(&leaders)
481 {
482 assert!(unshuffled.is_sorted_by_key(|b| (b.round, b.author)));
483 assert!(shuffled.is_sorted_by_key(|b| b.round));
484 assert_eq!(shuffled.last(), Some(&leader.reference()));
485
486 let mut sorted = shuffled.clone();
487 sorted.sort_by_key(|b| (b.round, b.author));
488 assert_eq!(&sorted, unshuffled);
489 any_reordered |= shuffled != unshuffled;
490 }
491 assert!(any_reordered);
492 }
493
494 #[tokio::test]
495 async fn test_handle_already_committed() {
496 telemetry_subscribers::init_for_testing();
497 let num_authorities = 4;
498 let (context, _) = Context::new_for_test(num_authorities);
499 let context = Arc::new(context);
500
501 let dag_state = Arc::new(RwLock::new(DagState::new(
502 context.clone(),
503 Arc::new(MemStore::new()),
504 )));
505 let leader_schedule = Arc::new(LeaderSchedule::new(
506 context.clone(),
507 LeaderSwapTable::default(),
508 ));
509 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
510 let wave_length = DEFAULT_WAVE_LENGTH;
511
512 let leader_round_wave_1 = 3;
513 let leader_round_wave_2 = leader_round_wave_1 + wave_length;
514
515 let mut dag_builder = DagBuilder::new(context.clone());
517 dag_builder.layers(1..=leader_round_wave_2).build();
518
519 let mut blocks = dag_builder.blocks(0..=leader_round_wave_1 - 1);
523 blocks.push(
524 dag_builder
525 .leader_block(leader_round_wave_1)
526 .expect("Leader block should have been found"),
527 );
528 dag_state.write().accept_blocks(blocks.clone());
529
530 let first_leader = dag_builder
531 .leader_block(leader_round_wave_1)
532 .expect("Wave 1 leader round block should exist");
533 let mut last_commit_index = 1;
534 let first_commit_data = TrustedCommit::new_for_test(
535 last_commit_index,
536 CommitDigest::MIN,
537 0,
538 first_leader.reference(),
539 blocks.iter().map(|block| block.reference()).collect(),
540 );
541 dag_state.write().add_commit(first_commit_data);
542
543 for block in blocks.iter() {
545 dag_state.write().set_committed(&block.reference());
546 }
547
548 let mut blocks = dag_builder.blocks(leader_round_wave_1..=leader_round_wave_2 - 1);
550 blocks.retain(|block| {
552 !(block.round() == leader_round_wave_1
553 && block.author() == leader_schedule.elect_leader(leader_round_wave_1, 0))
554 });
555 blocks.push(
557 dag_builder
558 .leader_block(leader_round_wave_2)
559 .expect("Leader block should have been found"),
560 );
561 dag_state.write().accept_blocks(blocks.clone());
563
564 let mut blocks: Vec<_> = blocks.into_iter().map(|block| block.reference()).collect();
565
566 let leader = dag_builder
568 .leader_block(leader_round_wave_2)
569 .expect("Leader block should exist");
570
571 last_commit_index += 1;
572 let expected_second_commit = TrustedCommit::new_for_test(
573 last_commit_index,
574 CommitDigest::MIN,
575 0,
576 leader.reference(),
577 blocks.clone(),
578 );
579
580 let commit = linearizer.handle_commit(vec![leader.clone()]);
581 assert_eq!(commit.len(), 1);
582
583 let subdag = &commit[0];
584 tracing::info!("{subdag:?}");
585 assert_eq!(subdag.leader, leader.reference());
586 assert_eq!(subdag.commit_ref.index, expected_second_commit.index());
587
588 let expected_ts = median_timestamp_by_stake(
589 &context,
590 subdag
591 .blocks
592 .iter()
593 .filter(|block| block.round() == subdag.leader.round - 1),
594 )
595 .unwrap();
596 assert_eq!(subdag.timestamp_ms, expected_ts);
597
598 let mut committed: Vec<_> = subdag.blocks.iter().map(|b| b.reference()).collect();
599 committed.sort();
600 blocks.sort();
601 assert_eq!(committed, blocks);
602 for block in subdag.blocks.iter() {
603 assert!(block.round() <= expected_second_commit.leader().round);
604 }
605 }
606
607 #[tokio::test]
610 async fn test_handle_commit_with_gc_simple() {
611 telemetry_subscribers::init_for_testing();
612
613 const GC_DEPTH: u32 = 3;
614
615 let num_authorities = 4;
616 let (mut context, _keys) = Context::new_for_test(num_authorities);
617 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
618
619 let context = Arc::new(context);
620 let dag_state = Arc::new(RwLock::new(DagState::new(
621 context.clone(),
622 Arc::new(MemStore::new()),
623 )));
624 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
625
626 let dag_str = "DAG {
632 Round 0 : { 4 },
633 Round 1 : { * },
634 Round 2 : {
635 A -> [-D1],
636 B -> [-D1],
637 C -> [-D1],
638 D -> [*],
639 },
640 Round 3 : {
641 A -> [-D2],
642 B -> [-D2],
643 C -> [-D2],
644 },
645 Round 4 : {
646 A -> [-D3],
647 B -> [-D3],
648 C -> [-D3],
649 D -> [A3, B3, C3, D2],
650 },
651 Round 5 : { * },
652 }";
653
654 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
655 dag_builder.print();
656 dag_builder.persist_all_blocks(dag_state.clone());
657
658 let leaders = dag_builder
659 .leader_blocks(1..=6)
660 .into_iter()
661 .flatten()
662 .collect::<Vec<_>>();
663
664 let commits = linearizer.handle_commit(leaders.clone());
665 for (idx, subdag) in commits.into_iter().enumerate() {
666 tracing::info!("{subdag:?}");
667 assert_eq!(subdag.leader, leaders[idx].reference());
668
669 let expected_ts = {
670 let block_refs = leaders[idx]
671 .ancestors()
672 .iter()
673 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
674 .cloned()
675 .collect::<Vec<_>>();
676 let block_opts = dag_state.read().get_blocks(&block_refs);
677 let blocks = block_opts.iter().map(|block_opt| {
678 block_opt
679 .as_ref()
680 .expect("We should have all blocks in dag state.")
681 });
682
683 median_timestamp_by_stake(&context, blocks).unwrap()
684 };
685 assert_eq!(subdag.timestamp_ms, expected_ts);
686
687 if idx == 0 {
688 assert_eq!(subdag.blocks.len(), 1);
690 } else if idx == 1 {
691 assert_eq!(subdag.blocks.len(), 3);
692 } else if idx == 2 {
693 assert_eq!(subdag.blocks.len(), 6);
698 } else {
699 assert_eq!(subdag.blocks.len(), 5);
704
705 assert!(
706 subdag.blocks.iter().all(|block| block.round() >= 2),
707 "Found blocks that are of round < 2."
708 );
709
710 assert_eq!(dag_state.read().gc_round(), subdag.leader.round - GC_DEPTH);
712 }
713 for block in subdag.blocks.iter() {
714 assert!(block.round() <= leaders[idx].round());
715 }
716 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
717 }
718 }
719
720 #[tokio::test]
721 async fn test_handle_commit_below_highest_committed_round() {
722 telemetry_subscribers::init_for_testing();
723
724 const GC_DEPTH: u32 = 3;
725
726 let num_authorities = 4;
727 let (mut context, _keys) = Context::new_for_test(num_authorities);
728 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
729
730 let context = Arc::new(context);
731 let dag_state = Arc::new(RwLock::new(DagState::new(
732 context.clone(),
733 Arc::new(MemStore::new()),
734 )));
735 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
736
737 let dag_str = "DAG {
741 Round 0 : { 4 },
742 Round 1 : { * },
743 Round 2 : {
744 A -> [-D1],
745 B -> [-D1],
746 C -> [-D1],
747 D -> [-D1],
748 },
749 Round 3 : {
750 A -> [A2, B2, C2, D1],
751 B -> [A2, B2, C2, D1],
752 C -> [A2, B2, C2, D1],
753 D -> [A2, B2, C2, D2]
754 },
755 Round 4 : { * },
756 }";
757
758 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
759 dag_builder.print();
760 dag_builder.persist_all_blocks(dag_state.clone());
761
762 let leaders = dag_builder
763 .leader_blocks(1..=4)
764 .into_iter()
765 .flatten()
766 .collect::<Vec<_>>();
767
768 let commits = linearizer.handle_commit(leaders.clone());
769 for (idx, subdag) in commits.into_iter().enumerate() {
770 tracing::info!("{subdag:?}");
771 assert_eq!(subdag.leader, leaders[idx].reference());
772
773 let expected_ts = {
774 let block_refs = leaders[idx]
775 .ancestors()
776 .iter()
777 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
778 .cloned()
779 .collect::<Vec<_>>();
780 let block_opts = dag_state.read().get_blocks(&block_refs);
781 let blocks = block_opts.iter().map(|block_opt| {
782 block_opt
783 .as_ref()
784 .expect("We should have all blocks in dag state.")
785 });
786
787 median_timestamp_by_stake(&context, blocks).unwrap()
788 };
789 assert_eq!(subdag.timestamp_ms, expected_ts);
790
791 if idx == 0 {
792 assert_eq!(subdag.blocks.len(), 1);
794 } else if idx == 1 {
795 assert_eq!(subdag.blocks.len(), 3);
799 } else if idx == 2 {
800 assert_eq!(subdag.blocks.len(), 4);
804
805 assert!(
806 subdag.blocks.iter().any(|block| block.round() == 2
807 && block.author() == AuthorityIndex::new_for_test(3)),
808 "Block D2 should have been committed."
809 );
810 } else if idx == 3 {
811 assert_eq!(subdag.blocks.len(), 5);
816 assert!(
817 subdag.blocks.iter().any(|block| block.round() == 1
818 && block.author() == AuthorityIndex::new_for_test(3)),
819 "Block D1 should have been committed."
820 );
821 } else {
822 panic!("Unexpected subdag with index {:?}", idx);
823 }
824
825 for block in subdag.blocks.iter() {
826 assert!(block.round() <= leaders[idx].round());
827 }
828 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
829 }
830 }
831
832 #[rstest]
833 #[case(3_000, 3_000, 6_000)]
834 #[tokio::test]
835 async fn test_calculate_commit_timestamp(
836 #[case] timestamp_1: u64,
837 #[case] timestamp_2: u64,
838 #[case] timestamp_3: u64,
839 ) {
840 telemetry_subscribers::init_for_testing();
842
843 let num_authorities = 4;
844 let (context, _keys) = Context::new_for_test(num_authorities);
845
846 let context = Arc::new(context);
847 let store = Arc::new(MemStore::new());
848 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
849 let mut dag_state = dag_state.write();
850
851 let ancestors = vec![
852 VerifiedBlock::new_for_test(TestBlock::new(4, 0).set_timestamp_ms(1_000).build()),
853 VerifiedBlock::new_for_test(TestBlock::new(4, 1).set_timestamp_ms(2_000).build()),
854 VerifiedBlock::new_for_test(TestBlock::new(4, 2).set_timestamp_ms(3_000).build()),
855 VerifiedBlock::new_for_test(TestBlock::new(4, 3).set_timestamp_ms(4_000).build()),
856 ];
857
858 let leader_block = VerifiedBlock::new_for_test(
859 TestBlock::new(5, 0)
860 .set_timestamp_ms(5_000)
861 .set_ancestors(
862 ancestors
863 .iter()
864 .map(|block| block.reference())
865 .collect::<Vec<_>>(),
866 )
867 .build(),
868 );
869
870 for block in &ancestors {
871 dag_state.accept_block(block.clone());
872 }
873
874 let last_commit_timestamp_ms = 0;
875
876 let timestamp = Linearizer::calculate_commit_timestamp(
878 &context,
879 &mut dag_state,
880 &leader_block,
881 last_commit_timestamp_ms,
882 );
883 assert_eq!(timestamp, timestamp_1);
884
885 let leader_block = VerifiedBlock::new_for_test(
887 TestBlock::new(5, 0)
888 .set_timestamp_ms(5_000)
889 .set_ancestors(
890 ancestors
891 .iter()
892 .skip(1)
893 .map(|block| block.reference())
894 .collect::<Vec<_>>(),
895 )
896 .build(),
897 );
898
899 let timestamp = Linearizer::calculate_commit_timestamp(
900 &context,
901 &mut dag_state,
902 &leader_block,
903 last_commit_timestamp_ms,
904 );
905 assert_eq!(timestamp, timestamp_2);
906
907 let last_commit_timestamp_ms = 6_000;
909 let timestamp = Linearizer::calculate_commit_timestamp(
910 &context,
911 &mut dag_state,
912 &leader_block,
913 last_commit_timestamp_ms,
914 );
915 assert_eq!(timestamp, timestamp_3);
916
917 let (context, _) = Context::new_for_test(1);
919 let leader_block = VerifiedBlock::new_for_test(
920 TestBlock::new(5, 0)
921 .set_timestamp_ms(5_000)
922 .set_ancestors(
923 ancestors
924 .iter()
925 .take(1)
926 .map(|block| block.reference())
927 .collect::<Vec<_>>(),
928 )
929 .build(),
930 );
931 let last_commit_timestamp_ms = 0;
932 let timestamp = Linearizer::calculate_commit_timestamp(
933 &context,
934 &mut dag_state,
935 &leader_block,
936 last_commit_timestamp_ms,
937 );
938 assert_eq!(timestamp, 1_000);
939 }
940
941 #[test]
942 fn test_median_timestamps_by_stake() {
943 let timestamps = vec![(1_000, 1)];
945 assert_eq!(median_timestamps_by_stake_inner(timestamps, 1), 1_000);
946
947 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1)];
949 assert_eq!(median_timestamps_by_stake_inner(timestamps, 3), 2_000);
950
951 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1), (4_000, 1)];
953 assert_eq!(median_timestamps_by_stake_inner(timestamps, 4), 3_000);
954
955 let timestamps = vec![(4_000, 1), (3_000, 1), (1_000, 1), (2_000, 1)];
957 assert_eq!(median_timestamps_by_stake_inner(timestamps, 4), 3_000);
958
959 let timestamps = vec![(2_000, 2), (4_000, 2), (1_000, 3), (3_000, 3)];
961 assert_eq!(median_timestamps_by_stake_inner(timestamps, 10), 3_000);
962
963 let timestamps = vec![
965 (500, 2),
966 (4_000, 2),
967 (2_500, 3),
968 (1_000, 5),
969 (3_000, 3),
970 (2_000, 4),
971 ];
972 assert_eq!(median_timestamps_by_stake_inner(timestamps, 19), 2_000);
973
974 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1), (4_000, 1), (5_000, 10)];
976 assert_eq!(median_timestamps_by_stake_inner(timestamps, 14), 5_000);
977 }
978
979 #[tokio::test]
980 async fn test_median_timestamps_by_stake_errors() {
981 let num_authorities = 4;
982 let (context, _keys) = Context::new_for_test(num_authorities);
983 let context = Arc::new(context);
984
985 let err = median_timestamp_by_stake(&context, &[] as &[VerifiedBlock]).unwrap_err();
987 assert_eq!(err, "No blocks provided");
988
989 let block = VerifiedBlock::new_for_test(TestBlock::new(5, 0).build());
991 let err = median_timestamp_by_stake(&context, &[block]).unwrap_err();
992 assert_eq!(err, "Total stake 1 < quorum threshold 3");
993 }
994}