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::{Commit, CommittedSubDag, TrustedCommit, sort_sub_dag_blocks},
14 context::Context,
15 dag_state::DagState,
16};
17
18pub(crate) trait BlockStoreAPI {
21 fn get_blocks(&self, refs: &[BlockRef]) -> Vec<Option<VerifiedBlock>>;
22
23 fn gc_round(&self) -> Round;
24
25 fn set_committed(&mut self, block_ref: &BlockRef) -> bool;
26
27 fn is_committed(&self, block_ref: &BlockRef) -> bool;
28}
29
30impl BlockStoreAPI
31 for parking_lot::lock_api::RwLockWriteGuard<'_, parking_lot::RawRwLock, DagState>
32{
33 fn get_blocks(&self, refs: &[BlockRef]) -> Vec<Option<VerifiedBlock>> {
34 DagState::get_blocks(self, refs)
35 }
36
37 fn gc_round(&self) -> Round {
38 DagState::gc_round(self)
39 }
40
41 fn set_committed(&mut self, block_ref: &BlockRef) -> bool {
42 DagState::set_committed(self, block_ref)
43 }
44
45 fn is_committed(&self, block_ref: &BlockRef) -> bool {
46 DagState::is_committed(self, block_ref)
47 }
48}
49
50#[derive(Clone)]
52pub struct Linearizer {
53 context: Arc<Context>,
55 dag_state: Arc<RwLock<DagState>>,
56}
57
58impl Linearizer {
59 pub fn new(context: Arc<Context>, dag_state: Arc<RwLock<DagState>>) -> Self {
60 Self { context, dag_state }
61 }
62
63 fn collect_sub_dag_and_commit(
66 &mut self,
67 leader_block: VerifiedBlock,
68 ) -> (CommittedSubDag, TrustedCommit) {
69 let _s = self
70 .context
71 .metrics
72 .node_metrics
73 .scope_processing_time
74 .with_label_values(&["Linearizer::collect_sub_dag_and_commit"])
75 .start_timer();
76
77 let mut dag_state = self.dag_state.write();
79 let last_commit_index = dag_state.last_commit_index();
80 let last_commit_digest = dag_state.last_commit_digest();
81 let last_commit_timestamp_ms = dag_state.last_commit_timestamp_ms();
82
83 let to_commit = Self::linearize_sub_dag(leader_block.clone(), &mut dag_state);
85
86 let timestamp_ms = Self::calculate_commit_timestamp(
87 &self.context,
88 &mut dag_state,
89 &leader_block,
90 last_commit_timestamp_ms,
91 );
92
93 drop(dag_state);
94
95 let commit = Commit::new(
97 last_commit_index + 1,
98 last_commit_digest,
99 timestamp_ms,
100 leader_block.reference(),
101 to_commit
102 .iter()
103 .map(|block| block.reference())
104 .collect::<Vec<_>>(),
105 );
106 let serialized = commit
107 .serialize()
108 .unwrap_or_else(|e| panic!("Failed to serialize commit: {}", e));
109 let commit = TrustedCommit::new_trusted(commit, serialized);
110
111 let sub_dag = CommittedSubDag::new(
113 leader_block.reference(),
114 to_commit,
115 timestamp_ms,
116 commit.reference(),
117 );
118
119 (sub_dag, commit)
120 }
121
122 pub(crate) fn calculate_commit_timestamp(
126 context: &Context,
127 dag_state: &mut impl BlockStoreAPI,
128 leader_block: &VerifiedBlock,
129 last_commit_timestamp_ms: BlockTimestampMs,
130 ) -> BlockTimestampMs {
131 let timestamp_ms = {
132 let block_refs = leader_block
134 .ancestors()
135 .iter()
136 .filter(|block_ref| block_ref.round == leader_block.round() - 1)
137 .cloned()
138 .collect::<Vec<_>>();
139 let block_opts = dag_state.get_blocks(&block_refs);
141 let blocks = block_opts.iter().map(|block_opt| {
142 block_opt
143 .as_ref()
144 .expect("We should have all blocks in dag state.")
145 });
146 median_timestamp_by_stake(context, blocks).unwrap_or_else(|e| {
147 panic!(
148 "Cannot compute median timestamp for leader block {:?} ancestors: {}",
149 leader_block, e
150 )
151 })
152 };
153
154 timestamp_ms.max(last_commit_timestamp_ms)
156 }
157
158 pub(crate) fn linearize_sub_dag(
159 leader_block: VerifiedBlock,
160 dag_state: &mut impl BlockStoreAPI,
161 ) -> Vec<VerifiedBlock> {
162 let gc_round: Round = dag_state.gc_round();
167 let leader_block_ref = leader_block.reference();
168 let mut buffer = vec![leader_block];
169 let mut to_commit = Vec::new();
170
171 assert!(
174 dag_state.set_committed(&leader_block_ref),
175 "Leader block with reference {:?} attempted to be committed twice",
176 leader_block_ref
177 );
178
179 while let Some(x) = buffer.pop() {
180 to_commit.push(x.clone());
181
182 let ancestors: Vec<VerifiedBlock> = dag_state
183 .get_blocks(
184 &x.ancestors()
185 .iter()
186 .copied()
187 .filter(|ancestor| {
188 ancestor.round > gc_round && !dag_state.is_committed(ancestor)
189 })
190 .collect::<Vec<_>>(),
191 )
192 .into_iter()
193 .map(|ancestor_opt| {
194 ancestor_opt.expect("We should have all uncommitted blocks in dag state.")
195 })
196 .collect();
197
198 for ancestor in ancestors {
199 buffer.push(ancestor.clone());
200 assert!(
201 dag_state.set_committed(&ancestor.reference()),
202 "Block with reference {:?} attempted to be committed twice",
203 ancestor.reference()
204 );
205 }
206 }
207
208 assert!(
211 to_commit.iter().all(|block| block.round() > gc_round),
212 "No blocks <= {gc_round} should be committed. Leader round {}, blocks {to_commit:?}.",
213 leader_block_ref
214 );
215
216 sort_sub_dag_blocks(&mut to_commit);
218
219 to_commit
220 }
221
222 pub fn handle_commit(&mut self, committed_leaders: Vec<VerifiedBlock>) -> Vec<CommittedSubDag> {
226 if committed_leaders.is_empty() {
227 return vec![];
228 }
229
230 let mut committed_sub_dags = vec![];
231 for leader_block in committed_leaders {
232 let (sub_dag, commit) = self.collect_sub_dag_and_commit(leader_block);
234
235 self.update_blocks_pruned_metric(&sub_dag);
236
237 self.dag_state.write().add_commit(commit.clone());
240
241 committed_sub_dags.push(sub_dag);
242 }
243
244 committed_sub_dags
245 }
246
247 fn update_blocks_pruned_metric(&self, sub_dag: &CommittedSubDag) {
252 let (last_committed_rounds, gc_round) = {
253 let dag_state = self.dag_state.read();
254 (dag_state.last_committed_rounds(), dag_state.gc_round())
255 };
256
257 for block_ref in sub_dag
258 .blocks
259 .iter()
260 .flat_map(|block| block.ancestors())
261 .filter(
262 |ancestor_ref| {
263 ancestor_ref.round <= gc_round
264 && last_committed_rounds[ancestor_ref.author] != ancestor_ref.round
265 }, )
268 .unique()
269 {
270 let hostname = &self.context.committee.authority(block_ref.author).hostname;
271
272 let label_values = if last_committed_rounds[block_ref.author] < block_ref.round {
274 &[hostname, "uncommitted"]
275 } else {
276 &[hostname, "higher_committed"]
278 };
279
280 self.context
281 .metrics
282 .node_metrics
283 .blocks_pruned_on_commit
284 .with_label_values(label_values)
285 .inc();
286 }
287 }
288}
289
290pub(crate) fn median_timestamp_by_stake<'a>(
294 context: &Context,
295 blocks: impl IntoIterator<Item = &'a VerifiedBlock>,
296) -> Result<BlockTimestampMs, String> {
297 let mut total_stake = 0;
298 let mut timestamps = vec![];
299 for block in blocks {
300 let stake = context.committee.authority(block.author()).stake;
301 timestamps.push((block.timestamp_ms(), stake));
302 total_stake += stake;
303 }
304
305 if timestamps.is_empty() {
306 return Err("No blocks provided".to_string());
307 }
308 if total_stake < context.committee.quorum_threshold() {
309 return Err(format!(
310 "Total stake {} < quorum threshold {}",
311 total_stake,
312 context.committee.quorum_threshold()
313 ));
314 }
315
316 Ok(median_timestamps_by_stake_inner(timestamps, total_stake))
317}
318
319fn median_timestamps_by_stake_inner(
320 mut timestamps: Vec<(BlockTimestampMs, Stake)>,
321 total_stake: Stake,
322) -> BlockTimestampMs {
323 timestamps.sort_by_key(|(ts, _)| *ts);
324
325 let mut cumulative_stake = 0;
326 for (ts, stake) in ×tamps {
327 cumulative_stake += stake;
328 if cumulative_stake > total_stake / 2 {
329 return *ts;
330 }
331 }
332
333 timestamps.last().unwrap().0
334}
335
336#[cfg(test)]
337mod tests {
338 use consensus_config::AuthorityIndex;
339 use rstest::rstest;
340
341 use super::*;
342 use crate::{
343 CommitIndex, TestBlock,
344 commit::{CommitAPI as _, CommitDigest, DEFAULT_WAVE_LENGTH},
345 context::Context,
346 leader_schedule::{LeaderSchedule, LeaderSwapTable},
347 storage::mem_store::MemStore,
348 test_dag_builder::DagBuilder,
349 test_dag_parser::parse_dag,
350 };
351
352 #[rstest]
353 #[tokio::test]
354 async fn test_handle_commit() {
355 telemetry_subscribers::init_for_testing();
356 let num_authorities = 4;
357 let (context, _keys) = Context::new_for_test(num_authorities);
358 let context = Arc::new(context);
359
360 let dag_state = Arc::new(RwLock::new(DagState::new(
361 context.clone(),
362 Arc::new(MemStore::new()),
363 )));
364 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
365
366 let num_rounds: u32 = 10;
368 let mut dag_builder = DagBuilder::new(context.clone());
369 dag_builder
370 .layers(1..=num_rounds)
371 .build()
372 .persist_layers(dag_state.clone());
373
374 let leaders = dag_builder
375 .leader_blocks(1..=num_rounds)
376 .into_iter()
377 .map(Option::unwrap)
378 .collect::<Vec<_>>();
379
380 let commits = linearizer.handle_commit(leaders.clone());
381 for (idx, subdag) in commits.into_iter().enumerate() {
382 tracing::info!("{subdag:?}");
383 assert_eq!(subdag.leader, leaders[idx].reference());
384
385 let expected_ts = {
386 let block_refs = leaders[idx]
387 .ancestors()
388 .iter()
389 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
390 .cloned()
391 .collect::<Vec<_>>();
392 let block_opts = dag_state.read().get_blocks(&block_refs);
393 let blocks = block_opts.iter().map(|block_opt| {
394 block_opt
395 .as_ref()
396 .expect("We should have all blocks in dag state.")
397 });
398
399 median_timestamp_by_stake(&context, blocks).unwrap()
400 };
401 assert_eq!(subdag.timestamp_ms, expected_ts);
402
403 if idx == 0 {
404 assert_eq!(subdag.blocks.len(), 1);
406 } else {
407 assert_eq!(subdag.blocks.len(), num_authorities);
410 }
411 for block in subdag.blocks.iter() {
412 assert!(block.round() <= leaders[idx].round());
413 }
414 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
415 }
416 }
417
418 #[rstest]
419 #[tokio::test]
420 async fn test_handle_already_committed() {
421 telemetry_subscribers::init_for_testing();
422 let num_authorities = 4;
423 let (context, _) = Context::new_for_test(num_authorities);
424 let context = Arc::new(context);
425
426 let dag_state = Arc::new(RwLock::new(DagState::new(
427 context.clone(),
428 Arc::new(MemStore::new()),
429 )));
430 let leader_schedule = Arc::new(LeaderSchedule::new(
431 context.clone(),
432 LeaderSwapTable::default(),
433 ));
434 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
435 let wave_length = DEFAULT_WAVE_LENGTH;
436
437 let leader_round_wave_1 = 3;
438 let leader_round_wave_2 = leader_round_wave_1 + wave_length;
439
440 let mut dag_builder = DagBuilder::new(context.clone());
442 dag_builder.layers(1..=leader_round_wave_2).build();
443
444 let mut blocks = dag_builder.blocks(0..=leader_round_wave_1 - 1);
448 blocks.push(
449 dag_builder
450 .leader_block(leader_round_wave_1)
451 .expect("Leader block should have been found"),
452 );
453 dag_state.write().accept_blocks(blocks.clone());
454
455 let first_leader = dag_builder
456 .leader_block(leader_round_wave_1)
457 .expect("Wave 1 leader round block should exist");
458 let mut last_commit_index = 1;
459 let first_commit_data = TrustedCommit::new_for_test(
460 last_commit_index,
461 CommitDigest::MIN,
462 0,
463 first_leader.reference(),
464 blocks.iter().map(|block| block.reference()).collect(),
465 );
466 dag_state.write().add_commit(first_commit_data);
467
468 for block in blocks.iter() {
470 dag_state.write().set_committed(&block.reference());
471 }
472
473 let mut blocks = dag_builder.blocks(leader_round_wave_1..=leader_round_wave_2 - 1);
475 blocks.retain(|block| {
477 !(block.round() == leader_round_wave_1
478 && block.author() == leader_schedule.elect_leader(leader_round_wave_1, 0))
479 });
480 blocks.push(
482 dag_builder
483 .leader_block(leader_round_wave_2)
484 .expect("Leader block should have been found"),
485 );
486 dag_state.write().accept_blocks(blocks.clone());
488
489 let mut blocks: Vec<_> = blocks.into_iter().map(|block| block.reference()).collect();
490
491 let leader = dag_builder
493 .leader_block(leader_round_wave_2)
494 .expect("Leader block should exist");
495
496 last_commit_index += 1;
497 let expected_second_commit = TrustedCommit::new_for_test(
498 last_commit_index,
499 CommitDigest::MIN,
500 0,
501 leader.reference(),
502 blocks.clone(),
503 );
504
505 let commit = linearizer.handle_commit(vec![leader.clone()]);
506 assert_eq!(commit.len(), 1);
507
508 let subdag = &commit[0];
509 tracing::info!("{subdag:?}");
510 assert_eq!(subdag.leader, leader.reference());
511 assert_eq!(subdag.commit_ref.index, expected_second_commit.index());
512
513 let expected_ts = median_timestamp_by_stake(
514 &context,
515 subdag
516 .blocks
517 .iter()
518 .filter(|block| block.round() == subdag.leader.round - 1),
519 )
520 .unwrap();
521 assert_eq!(subdag.timestamp_ms, expected_ts);
522
523 blocks.sort_by(|a, b| a.round.cmp(&b.round).then_with(|| a.author.cmp(&b.author)));
525 assert_eq!(
526 subdag
527 .blocks
528 .clone()
529 .into_iter()
530 .map(|b| b.reference())
531 .collect::<Vec<_>>(),
532 blocks
533 );
534 for block in subdag.blocks.iter() {
535 assert!(block.round() <= expected_second_commit.leader().round);
536 }
537 }
538
539 #[tokio::test]
542 async fn test_handle_commit_with_gc_simple() {
543 telemetry_subscribers::init_for_testing();
544
545 const GC_DEPTH: u32 = 3;
546
547 let num_authorities = 4;
548 let (mut context, _keys) = Context::new_for_test(num_authorities);
549 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
550
551 let context = Arc::new(context);
552 let dag_state = Arc::new(RwLock::new(DagState::new(
553 context.clone(),
554 Arc::new(MemStore::new()),
555 )));
556 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
557
558 let dag_str = "DAG {
564 Round 0 : { 4 },
565 Round 1 : { * },
566 Round 2 : {
567 A -> [-D1],
568 B -> [-D1],
569 C -> [-D1],
570 D -> [*],
571 },
572 Round 3 : {
573 A -> [-D2],
574 B -> [-D2],
575 C -> [-D2],
576 },
577 Round 4 : {
578 A -> [-D3],
579 B -> [-D3],
580 C -> [-D3],
581 D -> [A3, B3, C3, D2],
582 },
583 Round 5 : { * },
584 }";
585
586 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
587 dag_builder.print();
588 dag_builder.persist_all_blocks(dag_state.clone());
589
590 let leaders = dag_builder
591 .leader_blocks(1..=6)
592 .into_iter()
593 .flatten()
594 .collect::<Vec<_>>();
595
596 let commits = linearizer.handle_commit(leaders.clone());
597 for (idx, subdag) in commits.into_iter().enumerate() {
598 tracing::info!("{subdag:?}");
599 assert_eq!(subdag.leader, leaders[idx].reference());
600
601 let expected_ts = {
602 let block_refs = leaders[idx]
603 .ancestors()
604 .iter()
605 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
606 .cloned()
607 .collect::<Vec<_>>();
608 let block_opts = dag_state.read().get_blocks(&block_refs);
609 let blocks = block_opts.iter().map(|block_opt| {
610 block_opt
611 .as_ref()
612 .expect("We should have all blocks in dag state.")
613 });
614
615 median_timestamp_by_stake(&context, blocks).unwrap()
616 };
617 assert_eq!(subdag.timestamp_ms, expected_ts);
618
619 if idx == 0 {
620 assert_eq!(subdag.blocks.len(), 1);
622 } else if idx == 1 {
623 assert_eq!(subdag.blocks.len(), 3);
624 } else if idx == 2 {
625 assert_eq!(subdag.blocks.len(), 6);
630 } else {
631 assert_eq!(subdag.blocks.len(), 5);
636
637 assert!(
638 subdag.blocks.iter().all(|block| block.round() >= 2),
639 "Found blocks that are of round < 2."
640 );
641
642 assert_eq!(dag_state.read().gc_round(), subdag.leader.round - GC_DEPTH);
644 }
645 for block in subdag.blocks.iter() {
646 assert!(block.round() <= leaders[idx].round());
647 }
648 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
649 }
650 }
651
652 #[tokio::test]
653 async fn test_handle_commit_below_highest_committed_round() {
654 telemetry_subscribers::init_for_testing();
655
656 const GC_DEPTH: u32 = 3;
657
658 let num_authorities = 4;
659 let (mut context, _keys) = Context::new_for_test(num_authorities);
660 context.protocol_config.set_gc_depth_for_testing(GC_DEPTH);
661
662 let context = Arc::new(context);
663 let dag_state = Arc::new(RwLock::new(DagState::new(
664 context.clone(),
665 Arc::new(MemStore::new()),
666 )));
667 let mut linearizer = Linearizer::new(context.clone(), dag_state.clone());
668
669 let dag_str = "DAG {
673 Round 0 : { 4 },
674 Round 1 : { * },
675 Round 2 : {
676 A -> [-D1],
677 B -> [-D1],
678 C -> [-D1],
679 D -> [-D1],
680 },
681 Round 3 : {
682 A -> [A2, B2, C2, D1],
683 B -> [A2, B2, C2, D1],
684 C -> [A2, B2, C2, D1],
685 D -> [A2, B2, C2, D2]
686 },
687 Round 4 : { * },
688 }";
689
690 let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag");
691 dag_builder.print();
692 dag_builder.persist_all_blocks(dag_state.clone());
693
694 let leaders = dag_builder
695 .leader_blocks(1..=4)
696 .into_iter()
697 .flatten()
698 .collect::<Vec<_>>();
699
700 let commits = linearizer.handle_commit(leaders.clone());
701 for (idx, subdag) in commits.into_iter().enumerate() {
702 tracing::info!("{subdag:?}");
703 assert_eq!(subdag.leader, leaders[idx].reference());
704
705 let expected_ts = {
706 let block_refs = leaders[idx]
707 .ancestors()
708 .iter()
709 .filter(|block_ref| block_ref.round == leaders[idx].round() - 1)
710 .cloned()
711 .collect::<Vec<_>>();
712 let block_opts = dag_state.read().get_blocks(&block_refs);
713 let blocks = block_opts.iter().map(|block_opt| {
714 block_opt
715 .as_ref()
716 .expect("We should have all blocks in dag state.")
717 });
718
719 median_timestamp_by_stake(&context, blocks).unwrap()
720 };
721 assert_eq!(subdag.timestamp_ms, expected_ts);
722
723 if idx == 0 {
724 assert_eq!(subdag.blocks.len(), 1);
726 } else if idx == 1 {
727 assert_eq!(subdag.blocks.len(), 3);
731 } else if idx == 2 {
732 assert_eq!(subdag.blocks.len(), 4);
736
737 assert!(
738 subdag.blocks.iter().any(|block| block.round() == 2
739 && block.author() == AuthorityIndex::new_for_test(3)),
740 "Block D2 should have been committed."
741 );
742 } else if idx == 3 {
743 assert_eq!(subdag.blocks.len(), 5);
748 assert!(
749 subdag.blocks.iter().any(|block| block.round() == 1
750 && block.author() == AuthorityIndex::new_for_test(3)),
751 "Block D1 should have been committed."
752 );
753 } else {
754 panic!("Unexpected subdag with index {:?}", idx);
755 }
756
757 for block in subdag.blocks.iter() {
758 assert!(block.round() <= leaders[idx].round());
759 }
760 assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1);
761 }
762 }
763
764 #[rstest]
765 #[case(3_000, 3_000, 6_000)]
766 #[tokio::test]
767 async fn test_calculate_commit_timestamp(
768 #[case] timestamp_1: u64,
769 #[case] timestamp_2: u64,
770 #[case] timestamp_3: u64,
771 ) {
772 telemetry_subscribers::init_for_testing();
774
775 let num_authorities = 4;
776 let (context, _keys) = Context::new_for_test(num_authorities);
777
778 let context = Arc::new(context);
779 let store = Arc::new(MemStore::new());
780 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
781 let mut dag_state = dag_state.write();
782
783 let ancestors = vec![
784 VerifiedBlock::new_for_test(TestBlock::new(4, 0).set_timestamp_ms(1_000).build()),
785 VerifiedBlock::new_for_test(TestBlock::new(4, 1).set_timestamp_ms(2_000).build()),
786 VerifiedBlock::new_for_test(TestBlock::new(4, 2).set_timestamp_ms(3_000).build()),
787 VerifiedBlock::new_for_test(TestBlock::new(4, 3).set_timestamp_ms(4_000).build()),
788 ];
789
790 let leader_block = VerifiedBlock::new_for_test(
791 TestBlock::new(5, 0)
792 .set_timestamp_ms(5_000)
793 .set_ancestors(
794 ancestors
795 .iter()
796 .map(|block| block.reference())
797 .collect::<Vec<_>>(),
798 )
799 .build(),
800 );
801
802 for block in &ancestors {
803 dag_state.accept_block(block.clone());
804 }
805
806 let last_commit_timestamp_ms = 0;
807
808 let timestamp = Linearizer::calculate_commit_timestamp(
810 &context,
811 &mut dag_state,
812 &leader_block,
813 last_commit_timestamp_ms,
814 );
815 assert_eq!(timestamp, timestamp_1);
816
817 let leader_block = VerifiedBlock::new_for_test(
819 TestBlock::new(5, 0)
820 .set_timestamp_ms(5_000)
821 .set_ancestors(
822 ancestors
823 .iter()
824 .skip(1)
825 .map(|block| block.reference())
826 .collect::<Vec<_>>(),
827 )
828 .build(),
829 );
830
831 let timestamp = Linearizer::calculate_commit_timestamp(
832 &context,
833 &mut dag_state,
834 &leader_block,
835 last_commit_timestamp_ms,
836 );
837 assert_eq!(timestamp, timestamp_2);
838
839 let last_commit_timestamp_ms = 6_000;
841 let timestamp = Linearizer::calculate_commit_timestamp(
842 &context,
843 &mut dag_state,
844 &leader_block,
845 last_commit_timestamp_ms,
846 );
847 assert_eq!(timestamp, timestamp_3);
848
849 let (context, _) = Context::new_for_test(1);
851 let leader_block = VerifiedBlock::new_for_test(
852 TestBlock::new(5, 0)
853 .set_timestamp_ms(5_000)
854 .set_ancestors(
855 ancestors
856 .iter()
857 .take(1)
858 .map(|block| block.reference())
859 .collect::<Vec<_>>(),
860 )
861 .build(),
862 );
863 let last_commit_timestamp_ms = 0;
864 let timestamp = Linearizer::calculate_commit_timestamp(
865 &context,
866 &mut dag_state,
867 &leader_block,
868 last_commit_timestamp_ms,
869 );
870 assert_eq!(timestamp, 1_000);
871 }
872
873 #[test]
874 fn test_median_timestamps_by_stake() {
875 let timestamps = vec![(1_000, 1)];
877 assert_eq!(median_timestamps_by_stake_inner(timestamps, 1), 1_000);
878
879 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1)];
881 assert_eq!(median_timestamps_by_stake_inner(timestamps, 3), 2_000);
882
883 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1), (4_000, 1)];
885 assert_eq!(median_timestamps_by_stake_inner(timestamps, 4), 3_000);
886
887 let timestamps = vec![(4_000, 1), (3_000, 1), (1_000, 1), (2_000, 1)];
889 assert_eq!(median_timestamps_by_stake_inner(timestamps, 4), 3_000);
890
891 let timestamps = vec![(2_000, 2), (4_000, 2), (1_000, 3), (3_000, 3)];
893 assert_eq!(median_timestamps_by_stake_inner(timestamps, 10), 3_000);
894
895 let timestamps = vec![
897 (500, 2),
898 (4_000, 2),
899 (2_500, 3),
900 (1_000, 5),
901 (3_000, 3),
902 (2_000, 4),
903 ];
904 assert_eq!(median_timestamps_by_stake_inner(timestamps, 19), 2_000);
905
906 let timestamps = vec![(1_000, 1), (2_000, 1), (3_000, 1), (4_000, 1), (5_000, 10)];
908 assert_eq!(median_timestamps_by_stake_inner(timestamps, 14), 5_000);
909 }
910
911 #[tokio::test]
912 async fn test_median_timestamps_by_stake_errors() {
913 let num_authorities = 4;
914 let (context, _keys) = Context::new_for_test(num_authorities);
915 let context = Arc::new(context);
916
917 let err = median_timestamp_by_stake(&context, &[] as &[VerifiedBlock]).unwrap_err();
919 assert_eq!(err, "No blocks provided");
920
921 let block = VerifiedBlock::new_for_test(TestBlock::new(5, 0).build());
923 let err = median_timestamp_by_stake(&context, &[block]).unwrap_err();
924 assert_eq!(err, "Total stake 1 < quorum threshold 3");
925 }
926}