1use crate::{
3 merkle::{hasher::Standard as StandardHasher, Family, Location},
4 qmdb::{
5 self,
6 sync::{
7 database::Config as _,
8 error::EngineError,
9 requests::{Id as RequestId, Requests},
10 resolver::{FetchResult, Resolver},
11 target::validate_update,
12 Database, DbResolver, Error as SyncError, Journal, Metrics, Target,
13 },
14 },
15};
16use commonware_codec::Encode;
17use commonware_cryptography::Digest;
18use commonware_macros::{boxed, select};
19use commonware_runtime::Supervisor as _;
20use commonware_utils::{
21 channel::{
22 fallible::{AsyncFallibleExt, OneshotExt as _},
23 mpsc, oneshot,
24 },
25 NZU64,
26};
27use futures::{
28 future::{pending, Either},
29 StreamExt,
30};
31use mpsc::error::TryRecvError;
32use std::{
33 collections::{BTreeMap, HashMap, VecDeque},
34 fmt::Debug,
35 num::NonZeroU64,
36};
37
38type Error<DB, R> =
40 qmdb::sync::Error<<DB as Database>::Family, <R as Resolver>::Error, <DB as Database>::Digest>;
41
42#[derive(Debug)]
44pub(crate) enum NextStep<C, D> {
45 Continue(C),
47 Complete(D),
49}
50
51#[derive(Debug)]
53enum Event<F: Family, Op, D: Digest, E> {
54 TargetUpdate(Target<F, D>),
56 BatchReceived(IndexedFetchResult<F, Op, D, E>),
58 UpdateChannelClosed,
60 FinishRequested,
62 FinishChannelClosed,
64}
65
66#[derive(Debug)]
68pub(super) struct IndexedFetchResult<F: Family, Op, D: Digest, E> {
69 pub id: RequestId,
71 pub result: Result<FetchResult<F, Op, D>, E>,
73}
74
75async fn wait_for_event<F: Family, Op, D: Digest, E>(
78 update_rx: &mut Option<mpsc::Receiver<Target<F, D>>>,
79 finish_rx: &mut Option<mpsc::Receiver<()>>,
80 outstanding_requests: &mut Requests<F, Op, D, E>,
81) -> Option<Event<F, Op, D, E>> {
82 if outstanding_requests.len() == 0 && update_rx.is_none() && finish_rx.is_none() {
83 return None;
84 }
85
86 let target_update_fut = update_rx.as_mut().map_or_else(
87 || Either::Right(pending()),
88 |update_rx| Either::Left(update_rx.recv()),
89 );
90 let finish_fut = finish_rx.as_mut().map_or_else(
91 || Either::Right(pending()),
92 |finish_rx| Either::Left(finish_rx.recv()),
93 );
94 let batch_result_fut = if outstanding_requests.len() == 0 {
95 Either::Right(pending())
96 } else {
97 Either::Left(outstanding_requests.futures_mut().next())
98 };
99
100 select! {
101 finish = finish_fut => finish.map_or_else(
102 || Some(Event::FinishChannelClosed),
103 |_| Some(Event::FinishRequested)
104 ),
105 target = target_update_fut => target.map_or_else(
106 || Some(Event::UpdateChannelClosed),
107 |target| Some(Event::TargetUpdate(target))
108 ),
109 result = batch_result_fut => result.map(|fetch_result| Event::BatchReceived(fetch_result)),
110 }
111}
112
113pub struct Config<DB, R>
115where
116 DB: Database,
117 R: DbResolver<DB>,
118 DB::Op: Encode,
119{
120 pub context: DB::Context,
122 pub resolver: R,
124 pub target: Target<DB::Family, DB::Digest>,
126 pub max_outstanding_requests: usize,
128 pub fetch_batch_size: NonZeroU64,
130 pub apply_batch_size: usize,
132 pub db_config: DB::Config,
134 pub update_rx: Option<mpsc::Receiver<Target<DB::Family, DB::Digest>>>,
136 pub finish_rx: Option<mpsc::Receiver<()>>,
140 pub reached_target_tx: Option<mpsc::Sender<Target<DB::Family, DB::Digest>>>,
147 pub max_retained_roots: usize,
151}
152pub(crate) struct Engine<DB, R>
154where
155 DB: Database,
156 R: DbResolver<DB>,
157 DB::Op: Encode,
158{
159 outstanding_requests: Requests<DB::Family, DB::Op, DB::Digest, R::Error>,
161
162 fetched_operations: BTreeMap<Location<DB::Family>, Vec<DB::Op>>,
168
169 pinned_nodes: Option<Vec<DB::Digest>>,
171
172 retained_roots: HashMap<Location<DB::Family>, DB::Digest>,
178
179 retained_roots_order: VecDeque<Location<DB::Family>>,
182
183 max_retained_roots: usize,
185
186 target: Target<DB::Family, DB::Digest>,
188
189 max_outstanding_requests: usize,
191
192 fetch_batch_size: NonZeroU64,
194
195 apply_batch_size: usize,
197
198 journal: DB::Journal,
200
201 resolver: R,
203
204 hasher: StandardHasher<DB::Hasher>,
206
207 context: DB::Context,
209
210 config: DB::Config,
212
213 update_rx: Option<mpsc::Receiver<Target<DB::Family, DB::Digest>>>,
215
216 finish_rx: Option<mpsc::Receiver<()>>,
220
221 reached_target_tx: Option<mpsc::Sender<Target<DB::Family, DB::Digest>>>,
228
229 metrics: Metrics,
231
232 finish_requested: bool,
234
235 reached_current_target_reported: bool,
237}
238
239#[cfg(test)]
240impl<DB, R> Engine<DB, R>
241where
242 DB: Database,
243 R: DbResolver<DB>,
244 DB::Op: Encode,
245{
246 pub(crate) fn journal(&self) -> &DB::Journal {
247 &self.journal
248 }
249}
250
251impl<DB, R> Engine<DB, R>
252where
253 DB: Database,
254 R: DbResolver<DB>,
255 DB::Op: Encode,
256{
257 pub async fn new(config: Config<DB, R>) -> Result<Self, Error<DB, R>> {
258 if !config.target.range.end().is_valid() {
259 return Err(SyncError::Engine(EngineError::InvalidTarget {
260 lower_bound_pos: config.target.range.start(),
261 upper_bound_pos: config.target.range.end(),
262 }));
263 }
264
265 let journal = <DB::Journal as Journal<DB::Family>>::new(
267 config.context.child("journal"),
268 config.db_config.journal_config(),
269 config.target.range.clone(),
270 )
271 .await?;
272 let journal_size = journal.size();
273
274 let pinned_nodes = if journal_size == *config.target.range.end() {
279 DB::local_boundary_nodes(
280 config.context.child("local_boundary"),
281 &config.db_config,
282 &config.target,
283 &journal,
284 )
285 .await?
286 } else {
287 None
288 };
289
290 let metrics = Metrics::new(&config.context);
291 let mut engine = Self {
292 outstanding_requests: Requests::new(),
293 fetched_operations: BTreeMap::new(),
294 pinned_nodes,
295 retained_roots: HashMap::new(),
296 retained_roots_order: VecDeque::new(),
297 max_retained_roots: config.max_retained_roots,
298 target: config.target.clone(),
299 max_outstanding_requests: config.max_outstanding_requests,
300 fetch_batch_size: config.fetch_batch_size,
301 apply_batch_size: config.apply_batch_size,
302 journal,
303 resolver: config.resolver.clone(),
304 hasher: qmdb::hasher::<DB::Hasher>(),
305 context: config.context,
306 config: config.db_config,
307 update_rx: config.update_rx,
308 finish_rx: config.finish_rx,
309 reached_target_tx: config.reached_target_tx,
310 finish_requested: false,
311 reached_current_target_reported: false,
312 metrics,
313 };
314 engine.schedule_requests()?;
315 engine.record_progress();
316 Ok(engine)
317 }
318
319 fn schedule_requests(&mut self) -> Result<(), Error<DB, R>> {
321 let target_size = self.target.range.end();
322
323 if !self.has_boundary_state()
326 && !self
327 .outstanding_requests
328 .contains(&self.target.range.start())
329 {
330 let start_loc = self.target.range.start();
331 let resolver = self.resolver.clone();
332 let (cancel_tx, cancel_rx) = oneshot::channel();
333 let id = self.outstanding_requests.next_id();
334 self.outstanding_requests.insert(
335 id,
336 start_loc,
337 target_size,
338 cancel_tx,
339 Box::pin(async move {
340 let result = resolver
341 .get_operations(target_size, start_loc, NZU64!(1), true, cancel_rx)
342 .await;
343 IndexedFetchResult { id, result }
344 }),
345 );
346 }
347
348 let num_requests = self
350 .max_outstanding_requests
351 .saturating_sub(self.outstanding_requests.len());
352
353 let log_size = self.journal.size();
354
355 for _ in 0..num_requests {
356 let operation_counts: BTreeMap<Location<DB::Family>, u64> = self
358 .fetched_operations
359 .iter()
360 .map(|(&start_loc, operations)| (start_loc, operations.len() as u64))
361 .collect();
362
363 let Some(gap_range) = crate::qmdb::sync::gaps::find_next(
365 Location::new(log_size)..self.target.range.end(),
366 &operation_counts,
367 self.outstanding_requests.locations(),
368 self.fetch_batch_size,
369 ) else {
370 break; };
372
373 let gap_size = *gap_range.end.checked_sub(*gap_range.start).unwrap();
375 let gap_size: NonZeroU64 = gap_size.try_into().unwrap();
376 let batch_size = self.fetch_batch_size.min(gap_size);
377
378 let resolver = self.resolver.clone();
380 let (cancel_tx, cancel_rx) = oneshot::channel();
381 let id = self.outstanding_requests.next_id();
382 self.outstanding_requests.insert(
383 id,
384 gap_range.start,
385 target_size,
386 cancel_tx,
387 Box::pin(async move {
388 let result = resolver
389 .get_operations(target_size, gap_range.start, batch_size, false, cancel_rx)
390 .await;
391 IndexedFetchResult { id, result }
392 }),
393 );
394 }
395
396 Ok(())
397 }
398
399 pub async fn reset_for_target_update(
406 mut self,
407 new_target: Target<DB::Family, DB::Digest>,
408 ) -> Result<Self, Error<DB, R>> {
409 self.journal.resize(new_target.range.start()).await?;
410 self.outstanding_requests
413 .remove_before(new_target.range.start().checked_add(1).unwrap());
414 self.fetched_operations.clear();
415 self.pinned_nodes = None;
416
417 if self.max_retained_roots > 0 {
420 let old_target_size = self.target.range.end();
421 assert!(
422 self.retained_roots
423 .insert(old_target_size, self.target.root)
424 .is_none(),
425 "duplicate retained root for tree size {old_target_size:?}"
426 );
427 self.retained_roots_order.push_back(old_target_size);
428 while self.retained_roots.len() > self.max_retained_roots {
429 if let Some(oldest) = self.retained_roots_order.pop_front() {
430 self.retained_roots.remove(&oldest);
431 }
432 }
433 }
434
435 self.target = new_target;
436 self.reached_current_target_reported = false;
437 Ok(self)
438 }
439
440 fn drain_finish_requests(&mut self) -> Result<(), Error<DB, R>> {
446 let Some(finish_rx) = self.finish_rx.as_mut() else {
447 return Ok(());
448 };
449 match finish_rx.try_recv() {
450 Ok(()) => {
451 self.accept_finish();
452 Ok(())
453 }
454 Err(TryRecvError::Empty) => Ok(()),
455 Err(TryRecvError::Disconnected) => {
456 Err(SyncError::Engine(EngineError::FinishChannelClosed))
457 }
458 }
459 }
460
461 fn accept_finish(&mut self) {
466 self.finish_requested = true;
467 self.finish_rx = None;
468 }
469
470 async fn report_reached_target(&mut self) {
478 if self.reached_current_target_reported {
479 return;
480 }
481 if let Some(sender) = self.reached_target_tx.as_ref() {
482 if !sender.send_lossy(self.target.clone()).await {
483 self.reached_target_tx = None;
484 }
485 }
486 self.reached_current_target_reported = true;
487 }
488
489 fn record_progress(&mut self) {
491 self.metrics.record_target(*self.target.range.end());
492 self.metrics.record_synced(self.journal.size());
493 }
494
495 pub(crate) fn store_operations(
497 &mut self,
498 start_loc: Location<DB::Family>,
499 operations: Vec<DB::Op>,
500 ) {
501 if operations.is_empty() {
502 return;
503 }
504 self.fetched_operations.insert(start_loc, operations);
505 }
506
507 pub(crate) async fn apply_operations(&mut self) -> Result<(), Error<DB, R>> {
513 let mut next_loc = self.journal.size();
514
515 self.fetched_operations.retain(|&start_loc, operations| {
518 assert!(!operations.is_empty());
519 let end_loc = start_loc.checked_add(operations.len() as u64 - 1).unwrap();
520 end_loc >= next_loc
521 });
522
523 loop {
524 let range_start_loc =
527 self.fetched_operations
528 .iter()
529 .find_map(|(range_start, range_ops)| {
530 assert!(!range_ops.is_empty());
531 let range_end =
532 range_start.checked_add(range_ops.len() as u64 - 1).unwrap();
533 if *range_start <= next_loc && next_loc <= range_end {
534 Some(*range_start)
535 } else {
536 None
537 }
538 });
539
540 let Some(range_start_loc) = range_start_loc else {
541 break;
543 };
544
545 let operations = self.fetched_operations.remove(&range_start_loc).unwrap();
547 assert!(!operations.is_empty());
548 let skip_count = (next_loc - *range_start_loc) as usize;
550 let operations_count = operations.len() - skip_count;
551 let remaining_operations = operations.into_iter().skip(skip_count);
552 next_loc += operations_count as u64;
553 self.apply_operations_batch(remaining_operations).await?;
554 }
555
556 Ok(())
557 }
558
559 async fn apply_operations_batch<I>(&mut self, operations: I) -> Result<(), Error<DB, R>>
561 where
562 I: IntoIterator<Item = DB::Op>,
563 {
564 for op in operations {
565 self.journal.append(op).await?;
566 }
567 Ok(())
568 }
569
570 pub fn is_at_target(&mut self) -> Result<bool, Error<DB, R>> {
572 let journal_size = self.journal.size();
573 let target_journal_size = self.target.range.end();
574
575 if journal_size >= target_journal_size {
577 if journal_size > target_journal_size {
578 return Err(SyncError::Engine(EngineError::InvalidState));
580 }
581 return Ok(true);
582 }
583
584 Ok(false)
585 }
586
587 fn needs_pinned_boundary(&self) -> bool {
589 self.target.range.start() > Location::new(0)
590 }
591
592 fn has_boundary_state(&self) -> bool {
594 !self.needs_pinned_boundary() || self.pinned_nodes.is_some()
595 }
596
597 fn is_ready_to_complete(&mut self) -> Result<bool, Error<DB, R>> {
599 Ok(self.is_at_target()? && self.has_boundary_state())
600 }
601
602 fn handle_fetch_result(
609 &mut self,
610 fetch_result: IndexedFetchResult<DB::Family, DB::Op, DB::Digest, R::Error>,
611 ) -> Result<(), Error<DB, R>> {
612 let Some(request) = self.outstanding_requests.remove(fetch_result.id) else {
616 return Ok(());
617 };
618
619 let start_loc = request.start_loc;
620 let FetchResult {
621 proof,
622 operations,
623 pinned_nodes,
624 callback,
625 } = fetch_result.result.map_err(SyncError::Resolver)?;
626
627 let operations_len = operations.len() as u64;
629 if operations_len == 0 || operations_len > self.fetch_batch_size.get() {
630 if let Some(callback) = callback {
633 callback.send_lossy(false);
634 }
635 return Ok(());
636 }
637
638 if proof.leaves != request.target_size {
639 if let Some(callback) = callback {
640 callback.send_lossy(false);
641 }
642 return Ok(());
643 }
644
645 let is_current_target = request.target_size == self.target.range.end();
649 let target_root = if is_current_target {
650 &self.target.root
651 } else {
652 let Some(root) = self.retained_roots.get(&request.target_size) else {
653 return Ok(());
657 };
658 root
659 };
660
661 let need_pinned = is_current_target
664 && self.pinned_nodes.is_none()
665 && start_loc == self.target.range.start();
666 let elements = operations.iter().map(|op| op.encode()).collect::<Vec<_>>();
667 let valid = if need_pinned {
668 let nodes = pinned_nodes.as_deref().unwrap_or(&[]);
669 proof.verify_proof_and_pinned_nodes(
670 &self.hasher,
671 &elements,
672 start_loc,
673 nodes,
674 target_root,
675 )
676 } else {
677 proof.verify_range_inclusion(&self.hasher, &elements, start_loc, target_root)
678 };
679
680 if let Some(callback) = callback {
682 callback.send_lossy(valid);
683 }
684
685 if !valid {
686 if need_pinned {
687 tracing::warn!("boundary proof or pinned nodes failed verification, will retry");
688 }
689 return Ok(());
690 }
691
692 if need_pinned {
694 if let Some(nodes) = pinned_nodes {
695 self.pinned_nodes = Some(nodes);
696 }
697 }
698
699 self.store_operations(start_loc, operations);
701
702 Ok(())
703 }
704
705 async fn handle_event(
707 mut self,
708 event: Event<DB::Family, DB::Op, DB::Digest, R::Error>,
709 ) -> Result<NextStep<Self, DB>, Error<DB, R>> {
710 match event {
711 Event::TargetUpdate(new_target) => {
712 validate_update(&self.target, &new_target)?;
713
714 let mut updated_self = self.reset_for_target_update(new_target).await?;
715 updated_self.record_progress();
716 updated_self.schedule_requests()?;
717 Ok(NextStep::Continue(updated_self))
718 }
719 Event::UpdateChannelClosed => {
720 self.update_rx = None;
721 Ok(NextStep::Continue(self))
722 }
723 Event::FinishRequested => {
724 self.accept_finish();
725 Ok(NextStep::Continue(self))
726 }
727 Event::FinishChannelClosed => Err(SyncError::Engine(EngineError::FinishChannelClosed)),
728 Event::BatchReceived(fetch_result) => {
729 self.handle_fetch_result(fetch_result)?;
730 self.schedule_requests()?;
731 self.apply_operations().await?;
732 self.record_progress();
733 Ok(NextStep::Continue(self))
734 }
735 }
736 }
737
738 #[boxed]
749 pub(crate) async fn step(mut self) -> Result<NextStep<Self, DB>, Error<DB, R>> {
750 self.drain_finish_requests()?;
751
752 if self.is_ready_to_complete()? {
754 self.report_reached_target().await;
755
756 if self.finish_rx.is_some() && !self.finish_requested {
757 let event = wait_for_event(
758 &mut self.update_rx,
759 &mut self.finish_rx,
760 &mut self.outstanding_requests,
761 )
762 .await
763 .ok_or(SyncError::Engine(EngineError::SyncStalled))?;
764 return self.handle_event(event).await;
765 }
766
767 self.journal.sync().await?;
768
769 let database = DB::from_sync_result(
771 self.context,
772 self.config,
773 self.journal,
774 self.pinned_nodes,
775 self.target.range.clone(),
776 self.apply_batch_size,
777 )
778 .await?;
779
780 let got_root = database.root();
782 let expected_root = self.target.root;
783 if got_root != expected_root {
784 return Err(SyncError::Engine(EngineError::RootMismatch {
785 expected: expected_root,
786 actual: got_root,
787 }));
788 }
789
790 return Ok(NextStep::Complete(database));
791 }
792
793 let event = wait_for_event(
795 &mut self.update_rx,
796 &mut self.finish_rx,
797 &mut self.outstanding_requests,
798 )
799 .await
800 .ok_or(SyncError::Engine(EngineError::SyncStalled))?;
801 self.handle_event(event).await
802 }
803
804 pub async fn sync(mut self) -> Result<DB, Error<DB, R>> {
809 loop {
811 match self.step().await? {
812 NextStep::Continue(new_engine) => self = new_engine,
813 NextStep::Complete(database) => return Ok(database),
814 }
815 }
816 }
817}
818
819#[cfg(test)]
820mod tests {
821 use super::*;
822 use crate::{
823 merkle::mmr::{Family as MmrFamily, Proof},
824 qmdb::sync::requests::FetchFuture,
825 };
826 use commonware_cryptography::{sha256, Sha256};
827 use commonware_runtime::{deterministic, Runner as _};
828 use commonware_utils::{channel::oneshot, non_empty_range, NZU64};
829 use std::{
830 convert::Infallible,
831 sync::{
832 atomic::{AtomicUsize, Ordering},
833 Arc,
834 },
835 };
836
837 #[derive(Clone)]
838 struct TestConfig {
839 journal_size: u64,
840 boundary_probes: Arc<AtomicUsize>,
841 }
842
843 impl crate::qmdb::sync::DatabaseConfig for TestConfig {
844 type JournalConfig = u64;
845
846 fn journal_config(&self) -> Self::JournalConfig {
847 self.journal_size
848 }
849 }
850
851 struct TestJournal {
852 size: u64,
853 }
854
855 impl Journal<MmrFamily> for TestJournal {
856 type Config = u64;
857 type Context = deterministic::Context;
858 type Error = crate::journal::Error;
859 type Op = i32;
860
861 async fn new(
862 _context: Self::Context,
863 size: Self::Config,
864 _range: commonware_utils::range::NonEmptyRange<Location<MmrFamily>>,
865 ) -> Result<Self, Self::Error> {
866 Ok(Self { size })
867 }
868
869 async fn resize(&mut self, start: Location<MmrFamily>) -> Result<(), Self::Error> {
870 self.size = *start;
871 Ok(())
872 }
873
874 async fn sync(&mut self) -> Result<(), Self::Error> {
875 Ok(())
876 }
877
878 fn size(&self) -> u64 {
879 self.size
880 }
881
882 async fn append(&mut self, _op: Self::Op) -> Result<(), Self::Error> {
883 self.size += 1;
884 Ok(())
885 }
886 }
887
888 struct TestDb;
889
890 impl Database for TestDb {
891 type Config = TestConfig;
892 type Context = deterministic::Context;
893 type Digest = sha256::Digest;
894 type Family = MmrFamily;
895 type Hasher = Sha256;
896 type Journal = TestJournal;
897 type Op = i32;
898
899 async fn from_sync_result(
900 _context: Self::Context,
901 _config: Self::Config,
902 _journal: Self::Journal,
903 _pinned_nodes: Option<Vec<Self::Digest>>,
904 _range: commonware_utils::range::NonEmptyRange<Location<Self::Family>>,
905 _apply_batch_size: usize,
906 ) -> Result<Self, qmdb::Error<Self::Family>> {
907 Ok(Self)
908 }
909
910 async fn local_boundary_nodes(
911 _context: Self::Context,
912 config: &Self::Config,
913 _target: &Target<Self::Family, Self::Digest>,
914 _journal: &Self::Journal,
915 ) -> Result<Option<Vec<Self::Digest>>, qmdb::Error<Self::Family>> {
916 config.boundary_probes.fetch_add(1, Ordering::SeqCst);
917 Ok(Some(vec![]))
918 }
919
920 fn root(&self) -> Self::Digest {
921 sha256::Digest::from([0u8; 32])
922 }
923 }
924
925 #[derive(Clone)]
926 struct TestResolver;
927
928 impl Resolver for TestResolver {
929 type Digest = sha256::Digest;
930 type Error = Infallible;
931 type Family = MmrFamily;
932 type Op = i32;
933
934 async fn get_operations(
935 &self,
936 _op_count: Location<Self::Family>,
937 _start_loc: Location<Self::Family>,
938 _max_ops: NonZeroU64,
939 _include_pinned_nodes: bool,
940 _cancel_rx: oneshot::Receiver<()>,
941 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
942 Ok(FetchResult::new(
943 Proof {
944 leaves: Location::new(0),
945 inactive_peaks: 0,
946 digests: vec![],
947 },
948 vec![],
949 None,
950 ))
951 }
952 }
953
954 fn test_engine_config(
955 context: deterministic::Context,
956 journal_size: u64,
957 boundary_probes: Arc<AtomicUsize>,
958 ) -> Config<TestDb, TestResolver> {
959 Config {
960 context,
961 resolver: TestResolver,
962 target: Target {
963 root: sha256::Digest::from([1u8; 32]),
964 range: non_empty_range!(Location::new(5), Location::new(10)),
965 },
966 max_outstanding_requests: 1,
967 fetch_batch_size: NZU64!(1),
968 apply_batch_size: 1,
969 db_config: TestConfig {
970 journal_size,
971 boundary_probes,
972 },
973 update_rx: None,
974 finish_rx: None,
975 reached_target_tx: None,
976 max_retained_roots: 0,
977 }
978 }
979
980 #[test]
981 fn new_probes_local_boundary_when_journal_reaches_target() {
982 deterministic::Runner::default().start(|context| async move {
983 let boundary_probes = Arc::new(AtomicUsize::new(0));
984 Engine::new(test_engine_config(context, 10, boundary_probes.clone()))
985 .await
986 .unwrap();
987
988 assert_eq!(boundary_probes.load(Ordering::SeqCst), 1);
989 });
990 }
991
992 #[test]
993 fn new_skips_local_boundary_when_journal_is_partial() {
994 deterministic::Runner::default().start(|context| async move {
995 let boundary_probes = Arc::new(AtomicUsize::new(0));
996 Engine::new(test_engine_config(context, 7, boundary_probes.clone()))
997 .await
998 .unwrap();
999
1000 assert_eq!(boundary_probes.load(Ordering::SeqCst), 0);
1001 });
1002 }
1003
1004 fn dummy_future(id: RequestId) -> FetchFuture<MmrFamily, i32, sha256::Digest, ()> {
1006 Box::pin(async move {
1007 IndexedFetchResult {
1008 id,
1009 result: Ok(FetchResult::new(
1010 Proof {
1011 leaves: Location::new(0),
1012 inactive_peaks: 0,
1013 digests: vec![],
1014 },
1015 vec![],
1016 None,
1017 )),
1018 }
1019 })
1020 }
1021
1022 fn add(requests: &mut Requests<MmrFamily, i32, sha256::Digest, ()>, loc: u64) -> RequestId {
1024 let id = requests.next_id();
1025 requests.insert(
1026 id,
1027 Location::new(loc),
1028 Location::new(loc),
1029 oneshot::channel().0,
1030 dummy_future(id),
1031 );
1032 id
1033 }
1034
1035 #[test]
1036 fn test_add_and_remove() {
1037 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1038 assert_eq!(requests.len(), 0);
1039
1040 let id = add(&mut requests, 10);
1041 assert_eq!(requests.len(), 1);
1042 assert!(requests.contains(&Location::new(10)));
1043
1044 assert!(requests.remove(id).is_some());
1045 assert!(!requests.contains(&Location::new(10)));
1046 assert!(requests.remove(id).is_none());
1047 }
1048
1049 #[test]
1050 fn test_remove_before() {
1051 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1052
1053 add(&mut requests, 5);
1054 add(&mut requests, 10);
1055 add(&mut requests, 15);
1056 add(&mut requests, 20);
1057 assert_eq!(requests.len(), 4);
1058
1059 requests.remove_before(Location::new(10));
1060 assert_eq!(requests.len(), 3);
1061 assert!(!requests.contains(&Location::new(5)));
1062 assert!(requests.contains(&Location::new(10)));
1063 assert!(requests.contains(&Location::new(15)));
1064 assert!(requests.contains(&Location::new(20)));
1065 }
1066
1067 #[test]
1068 fn test_remove_before_all() {
1069 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1070
1071 add(&mut requests, 5);
1072 add(&mut requests, 10);
1073 assert_eq!(requests.len(), 2);
1074
1075 requests.remove_before(Location::new(100));
1076 assert_eq!(requests.len(), 0);
1077 }
1078
1079 #[test]
1080 fn test_remove_before_empty() {
1081 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1082 requests.remove_before(Location::new(10));
1083 assert_eq!(requests.len(), 0);
1084 }
1085
1086 #[test]
1087 fn test_remove_before_none() {
1088 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1089
1090 add(&mut requests, 10);
1091 add(&mut requests, 20);
1092 assert_eq!(requests.len(), 2);
1093
1094 requests.remove_before(Location::new(5));
1095 assert_eq!(requests.len(), 2);
1096 assert!(requests.contains(&Location::new(10)));
1097 assert!(requests.contains(&Location::new(20)));
1098 }
1099
1100 #[test]
1101 fn test_superseded_request() {
1102 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1103
1104 let old_id = add(&mut requests, 10);
1106 assert_eq!(requests.len(), 1);
1107
1108 let new_id = add(&mut requests, 10);
1110 assert_eq!(requests.len(), 1);
1111
1112 assert!(requests.remove(old_id).is_none());
1114
1115 assert!(requests.contains(&Location::new(10)));
1117 assert!(requests.remove(new_id).is_some());
1118 assert!(!requests.contains(&Location::new(10)));
1119 }
1120
1121 #[test]
1122 fn test_stale_id_after_remove_before() {
1123 let mut requests: Requests<MmrFamily, i32, sha256::Digest, ()> = Requests::new();
1124
1125 let old_id = add(&mut requests, 5);
1126 add(&mut requests, 15);
1127 requests.remove_before(Location::new(10));
1128
1129 assert!(requests.remove(old_id).is_none());
1131
1132 let new_id = add(&mut requests, 5);
1134 assert_ne!(old_id, new_id);
1135 assert!(requests.remove(new_id).is_some());
1136 }
1137}