1use crate::{
46 merkle::{Family, Location, Proof},
47 qmdb::{
48 self,
49 any::{value::ValueEncoding, FixedValue, VariableValue},
50 immutable::{
51 fixed::{Db as ImmutableFixedDb, Operation as ImmutableFixedOp},
52 variable::{Db as ImmutableVariableDb, Operation as ImmutableVariableOp},
53 CompactDb as ImmutableCompactDb, Operation as ImmutableOp,
54 },
55 keyless::{
56 fixed::{Db as KeylessFixedDb, Operation as KeylessFixedOp},
57 variable::{Db as KeylessVariableDb, Operation as KeylessVariableOp},
58 CompactDb as KeylessCompactDb, Operation as KeylessOp,
59 },
60 operation::Key,
61 sync::{EngineError, Error},
62 verify_proof,
63 },
64 translator::Translator,
65};
66use commonware_codec::{
67 Encode, EncodeSize, Error as CodecError, RangeCfg, Read, ReadExt as _, Write,
68};
69use commonware_cryptography::{Digest, Hasher};
70use commonware_macros::{boxed, select};
71use commonware_parallel::Strategy;
72use commonware_runtime::{reschedule, Buf, BufMut, Clock, Metrics, Storage, Supervisor};
73use commonware_utils::{
74 channel::{mpsc, oneshot},
75 sync::{AsyncRwLock, TracedAsyncRwLock},
76 Array,
77};
78use futures::future::{pending, Either};
79use std::{future::Future, num::NonZeroU64, sync::Arc};
80
81#[derive(Debug)]
87pub struct Target<F: Family, D: Digest> {
88 pub root: D,
90 pub leaf_count: Location<F>,
92}
93
94impl<F: Family, D: Digest> Target<F, D> {
95 const INVALID_LEAF_COUNT: &'static str = "leaf_count must be in 1..=MAX_LEAVES";
96
97 pub const fn new(root: D, leaf_count: Location<F>) -> Self {
99 Self { root, leaf_count }
100 }
101
102 pub fn validate(&self) -> Result<(), &'static str> {
104 if !self.leaf_count.is_valid() || self.leaf_count == 0 {
105 return Err(Self::INVALID_LEAF_COUNT);
106 }
107 Ok(())
108 }
109}
110
111impl<F: Family, D: Digest> Clone for Target<F, D> {
112 fn clone(&self) -> Self {
113 Self {
114 root: self.root,
115 leaf_count: self.leaf_count,
116 }
117 }
118}
119
120impl<F: Family, D: Digest> PartialEq for Target<F, D> {
121 fn eq(&self, other: &Self) -> bool {
122 self.root == other.root && self.leaf_count == other.leaf_count
123 }
124}
125
126impl<F: Family, D: Digest> Eq for Target<F, D> {}
127
128impl<F: Family, D: Digest> Write for Target<F, D> {
129 fn write(&self, buf: &mut impl BufMut) {
130 self.root.write(buf);
131 self.leaf_count.write(buf);
132 }
133}
134
135impl<F: Family, D: Digest> EncodeSize for Target<F, D> {
136 fn encode_size(&self) -> usize {
137 self.root.encode_size() + self.leaf_count.encode_size()
138 }
139}
140
141impl<F: Family, D: Digest> Read for Target<F, D> {
142 type Cfg = ();
143
144 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
145 let root = D::read(buf)?;
146 let leaf_count = Location::<F>::read(buf)?;
147 let target = Self { root, leaf_count };
148 target.validate().map_err(|reason| {
149 CodecError::Invalid("storage::qmdb::sync::compact::Target", reason)
150 })?;
151 Ok(target)
152 }
153}
154
155#[cfg(feature = "arbitrary")]
156impl<F: Family, D: Digest> arbitrary::Arbitrary<'_> for Target<F, D>
157where
158 D: for<'a> arbitrary::Arbitrary<'a>,
159{
160 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
161 let root = u.arbitrary()?;
162 let leaf_count = Location::new(u.int_in_range(1..=*F::MAX_LEAVES)?);
163 Ok(Self { root, leaf_count })
164 }
165}
166
167#[derive(Clone, Debug)]
169pub struct State<F: Family, Op, D: Digest> {
170 pub leaf_count: Location<F>,
172 pub pinned_nodes: Vec<D>,
174 pub last_commit_op: Op,
176 pub last_commit_proof: Proof<F, D>,
178}
179
180#[derive(Clone, Debug)]
182pub struct ValidatedState<F: Family, Op, D: Digest> {
183 pub state: State<F, Op, D>,
185 pub root: D,
187}
188
189impl<F: Family, Op, D: Digest> Write for State<F, Op, D>
190where
191 Op: Write,
192{
193 fn write(&self, buf: &mut impl BufMut) {
194 self.leaf_count.write(buf);
195 self.pinned_nodes.write(buf);
196 self.last_commit_op.write(buf);
197 self.last_commit_proof.write(buf);
198 }
199}
200
201pub struct FetchResult<F: Family, Op, D: Digest> {
203 pub state: State<F, Op, D>,
205 pub callback: Option<oneshot::Sender<bool>>,
207}
208
209impl<F: Family, Op: std::fmt::Debug, D: Digest> std::fmt::Debug for FetchResult<F, Op, D> {
210 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
211 f.debug_struct("FetchResult")
212 .field("state", &self.state)
213 .field("callback", &self.callback.as_ref().map(|_| "<callback>"))
214 .finish()
215 }
216}
217
218impl<F: Family, Op, D: Digest> From<State<F, Op, D>> for FetchResult<F, Op, D> {
219 fn from(state: State<F, Op, D>) -> Self {
220 Self {
221 state,
222 callback: None,
223 }
224 }
225}
226
227impl<F: Family, Op, D: Digest> EncodeSize for State<F, Op, D>
228where
229 Op: EncodeSize,
230{
231 fn encode_size(&self) -> usize {
232 self.leaf_count.encode_size()
233 + self.pinned_nodes.encode_size()
234 + self.last_commit_op.encode_size()
235 + self.last_commit_proof.encode_size()
236 }
237}
238
239impl<F: Family, Op, D: Digest> Read for State<F, Op, D>
240where
241 Op: Read,
242{
243 type Cfg = (RangeCfg<usize>, Op::Cfg, usize);
244
245 fn read_cfg(buf: &mut impl Buf, cfg: &Self::Cfg) -> Result<Self, CodecError> {
246 let (pinned_nodes_cfg, op_cfg, max_proof_digests) = cfg;
247 Ok(Self {
248 leaf_count: Location::<F>::read(buf)?,
249 pinned_nodes: Vec::<D>::read_cfg(buf, &(*pinned_nodes_cfg, ()))?,
250 last_commit_op: Op::read_cfg(buf, op_cfg)?,
251 last_commit_proof: Proof::<F, D>::read_cfg(buf, max_proof_digests)?,
252 })
253 }
254}
255
256#[derive(Debug, thiserror::Error)]
258pub enum ServeError<F: Family, D: Digest> {
259 #[error("compact source database error: {0}")]
261 Database(#[from] qmdb::Error<F>),
262 #[error("invalid compact target: {0}")]
264 InvalidTarget(&'static str),
265 #[error("compact source missing")]
267 MissingSource,
268 #[error("stale compact target - requested {requested:?}, current {current:?}")]
270 StaleTarget {
271 requested: Target<F, D>,
272 current: Target<F, D>,
273 },
274}
275
276#[allow(clippy::type_complexity)]
278pub trait Resolver: Send + Sync + Clone + 'static {
279 type Family: Family;
281
282 type Digest: Digest;
284
285 type Op;
287
288 type Error: std::error::Error + Send + 'static;
290
291 fn get_compact_state<'a>(
293 &'a self,
294 target: Target<Self::Family, Self::Digest>,
295 ) -> impl Future<Output = Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error>>
296 + Send
297 + 'a;
298}
299
300pub trait CompactDbResolver<DB: Database>:
306 Resolver<Family = DB::Family, Op = DB::Op, Digest = DB::Digest>
307{
308}
309
310impl<DB, R> CompactDbResolver<DB> for R
311where
312 DB: Database,
313 R: Resolver<Family = DB::Family, Op = DB::Op, Digest = DB::Digest>,
314{
315}
316
317pub trait Database: Sized + Send {
319 type Family: Family;
320 type Op: Encode + Send;
321 type Config: Clone;
322 type Digest: Digest;
323 type Context: Storage + Clock + Metrics;
324 type Hasher: Hasher<Digest = Self::Digest>;
325
326 fn from_validated_state(
333 context: Self::Context,
334 config: Self::Config,
335 state: ValidatedState<Self::Family, Self::Op, Self::Digest>,
336 ) -> impl Future<Output = Result<Self, qmdb::Error<Self::Family>>> + Send;
337
338 fn inactivity_floor(op: &Self::Op) -> Option<Location<Self::Family>>;
340
341 fn root(&self) -> Self::Digest;
343
344 fn persist_compact_state(
346 &mut self,
347 ) -> impl Future<Output = Result<(), qmdb::Error<Self::Family>>> + Send;
348}
349
350pub struct Config<DB, R>
352where
353 DB: Database,
354 R: CompactDbResolver<DB>,
355{
356 pub context: DB::Context,
358 pub resolver: R,
360 pub target: Target<DB::Family, DB::Digest>,
362 pub db_config: DB::Config,
364 pub update_rx: Option<mpsc::Receiver<Target<DB::Family, DB::Digest>>>,
367 pub finish_rx: Option<mpsc::Receiver<()>>,
371 pub reached_target_tx: Option<mpsc::Sender<Target<DB::Family, DB::Digest>>>,
374}
375
376const MAX_UPDATE_DRAIN_PER_TICK: usize = 32;
378
379async fn drain_latest_target<T>(update_rx: &mut mpsc::Receiver<T>) -> Option<T> {
381 let mut latest = None;
382 let mut drained = 0usize;
383 loop {
384 match update_rx.try_recv() {
385 Ok(update) => {
386 latest = Some(update);
387 drained += 1;
388 if drained.is_multiple_of(MAX_UPDATE_DRAIN_PER_TICK) {
389 reschedule().await;
390 }
391 }
392 Err(mpsc::error::TryRecvError::Empty | mpsc::error::TryRecvError::Disconnected) => {
393 return latest;
394 }
395 }
396 }
397}
398
399#[boxed]
409pub async fn sync<DB, R>(
410 config: Config<DB, R>,
411) -> Result<DB, Error<DB::Family, R::Error, DB::Digest>>
412where
413 DB: Database,
414 R: CompactDbResolver<DB>,
415{
416 let Config {
417 context,
418 resolver,
419 mut target,
420 db_config,
421 mut update_rx,
422 mut finish_rx,
423 reached_target_tx,
424 } = config;
425 let metrics = super::Metrics::new(&context);
426 let mut attempt = 0u64;
427 loop {
428 if let Some(update_rx) = update_rx.as_mut() {
430 if let Some(update) = drain_latest_target(update_rx).await {
431 target = update;
432 }
433 }
434 target
435 .validate()
436 .map_err(|reason| Error::Engine(EngineError::InvalidCompactTarget(reason)))?;
437 metrics.record_target(*target.leaf_count);
438
439 attempt += 1;
440 let update_future = update_rx.as_mut().map_or_else(
441 || Either::Right(pending()),
442 |update_rx| Either::Left(update_rx.recv()),
443 );
444 let db = select! {
445 update = update_future => {
446 let Some(update) = update else {
447 update_rx = None;
448 continue;
449 };
450 target = update;
451 continue;
452 },
453 db = attempt_sync(&context, attempt, &resolver, &db_config, &target) => db?,
454 };
455 metrics.record_synced(*target.leaf_count);
456
457 if let Some(update_rx) = update_rx.as_mut() {
459 if let Some(update) = drain_latest_target(update_rx).await {
460 target = update;
461 continue;
462 }
463 }
464
465 if let Some(reached_target_tx) = reached_target_tx.as_ref() {
466 if reached_target_tx.send(target.clone()).await.is_err() {
467 return Ok(db);
468 }
469 }
470
471 let Some(finish_rx) = finish_rx.as_mut() else {
472 return Ok(db);
473 };
474 let Some(update_rx) = update_rx.as_mut() else {
475 return Ok(db);
476 };
477 select! {
478 _ = finish_rx.recv() => return Ok(db),
479 update = update_rx.recv() => {
480 let Some(update) = update else {
481 return Ok(db);
482 };
483 target = update;
484 },
485 }
486 }
487}
488
489async fn attempt_sync<DB, R>(
500 context: &DB::Context,
501 attempt: u64,
502 resolver: &R,
503 db_config: &DB::Config,
504 target: &Target<DB::Family, DB::Digest>,
505) -> Result<DB, Error<DB::Family, R::Error, DB::Digest>>
506where
507 DB: Database,
508 R: CompactDbResolver<DB>,
509{
510 loop {
513 let FetchResult { state, callback } = resolver
514 .get_compact_state(target.clone())
515 .await
516 .map_err(Error::Resolver)?;
517
518 let validated_state = match validate_compact_state::<DB>(target, state) {
521 Ok(state) => state,
522 Err(err) => {
523 if let Some(callback) = callback {
524 let _ = callback.send(false);
525 }
526 tracing::debug!(error = ?err, "compact state failed validation, will retry");
527 continue;
528 }
529 };
530
531 let mut db = DB::from_validated_state(
535 context.child("compact").with_attribute("attempt", attempt),
536 db_config.clone(),
537 validated_state,
538 )
539 .await
540 .map_err(Error::Database)?;
541 assert_eq!(
542 db.root(),
543 target.root,
544 "validated compact state reconstructed unexpected root",
545 );
546
547 if let Some(callback) = callback {
548 let _ = callback.send(true);
549 }
550 db.persist_compact_state().await?;
551 return Ok(db);
552 }
553}
554
555fn validate_compact_state<DB>(
557 target: &Target<DB::Family, DB::Digest>,
558 state: State<DB::Family, DB::Op, DB::Digest>,
559) -> CompactFrontierValidation<DB>
560where
561 DB: Database,
562{
563 if state.leaf_count != target.leaf_count {
564 return Err(EngineError::UnexpectedLeafCount {
565 expected: target.leaf_count,
566 actual: state.leaf_count,
567 });
568 }
569
570 let last_commit_loc = Location::new(*state.leaf_count - 1);
571 if !verify_proof::<DB::Hasher, _, _>(
572 &state.last_commit_proof,
573 last_commit_loc,
574 std::slice::from_ref(&state.last_commit_op),
575 &target.root,
576 ) {
577 return Err(EngineError::InvalidProof);
578 }
579
580 validate_compact_frontier::<DB>(target, state)
581}
582
583type CompactFrontierValidation<DB> = Result<
585 ValidatedState<<DB as Database>::Family, <DB as Database>::Op, <DB as Database>::Digest>,
586 EngineError<<DB as Database>::Family, <DB as Database>::Digest>,
587>;
588
589fn validate_compact_frontier<DB>(
591 target: &Target<DB::Family, DB::Digest>,
592 state: State<DB::Family, DB::Op, DB::Digest>,
593) -> CompactFrontierValidation<DB>
594where
595 DB: Database,
596{
597 let last_commit_loc = Location::new(*state.leaf_count - 1);
600 let Some(inactivity_floor_loc) = DB::inactivity_floor(&state.last_commit_op) else {
601 return Err(EngineError::InvalidProof);
602 };
603 if inactivity_floor_loc > last_commit_loc {
604 return Err(EngineError::InvalidProof);
605 }
606
607 let mem = crate::merkle::mem::Mem::<DB::Family, DB::Digest>::init(crate::merkle::mem::Config {
610 nodes: Vec::new(),
611 pruning_boundary: state.leaf_count,
612 pinned_nodes: state.pinned_nodes.clone(),
613 })
614 .map_err(|_| EngineError::InvalidProof)?;
615 let hasher = qmdb::hasher::<DB::Hasher>();
616 let inactive_peaks = DB::Family::inactive_peaks(
617 DB::Family::location_to_position(state.leaf_count),
618 inactivity_floor_loc,
619 );
620 let actual = mem
621 .root(&hasher, inactive_peaks)
622 .map_err(|_| EngineError::InvalidProof)?;
623 if actual != target.root {
624 return Err(EngineError::RootMismatch {
625 expected: target.root,
626 actual,
627 });
628 }
629
630 Ok(ValidatedState {
631 state,
632 root: target.root,
633 })
634}
635
636async fn fetch_state_from_full_source<F, Op, D, Current, Hist, HistFut, Pins, PinsFut>(
637 target: Target<F, D>,
638 current_target: Current,
639 historical_proof: Hist,
640 pinned_nodes_at: Pins,
641) -> Result<State<F, Op, D>, ServeError<F, D>>
642where
643 F: Family,
644 D: Digest,
645 Current: FnOnce() -> Target<F, D>,
646 Hist: FnOnce(Location<F>, Location<F>) -> HistFut,
647 HistFut: Future<Output = Result<(Proof<F, D>, Vec<Op>), qmdb::Error<F>>>,
648 Pins: FnOnce(Location<F>) -> PinsFut,
649 PinsFut: Future<Output = Result<Vec<D>, qmdb::Error<F>>>,
650{
651 target.validate().map_err(ServeError::InvalidTarget)?;
654 let current = current_target();
655 if target.root != current.root || target.leaf_count != current.leaf_count {
656 return Err(ServeError::StaleTarget {
657 requested: target,
658 current,
659 });
660 }
661 let leaf_count = target.leaf_count;
662 let last_commit_loc = Location::new(*leaf_count - 1);
663 let (last_commit_proof, mut operations) = historical_proof(leaf_count, last_commit_loc)
664 .await
665 .map_err(ServeError::Database)?;
666 let last_commit_op =
668 operations
669 .pop()
670 .ok_or(ServeError::Database(qmdb::Error::DataCorrupted(
671 "missing last commit operation",
672 )))?;
673 let pinned_nodes = pinned_nodes_at(leaf_count)
674 .await
675 .map_err(ServeError::Database)?;
676 Ok(State {
677 leaf_count,
678 pinned_nodes,
679 last_commit_op,
680 last_commit_proof,
681 })
682}
683
684macro_rules! impl_compact_resolver_keyless {
687 ($db:ident, $op:ident, $val_bound:ident) => {
688 impl<F, E, V, H, S> Resolver for Arc<$db<F, E, V, H, S>>
689 where
690 F: Family,
691 E: crate::Context,
692 V: $val_bound + Send + Sync + 'static,
693 H: Hasher,
694 S: Strategy,
695 {
696 type Family = F;
697 type Digest = H::Digest;
698 type Op = $op<F, V>;
699 type Error = ServeError<F, H::Digest>;
700
701 async fn get_compact_state(
702 &self,
703 target: Target<Self::Family, Self::Digest>,
704 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
705 fetch_state_from_full_source(
706 target,
707 || Target::new(self.root(), self.bounds().end),
708 |leaf_count, last_commit_loc| {
709 self.historical_proof(
710 leaf_count,
711 last_commit_loc,
712 NonZeroU64::new(1).unwrap(),
713 )
714 },
715 |leaf_count| self.pinned_nodes_at(leaf_count),
716 )
717 .await
718 .map(Into::into)
719 }
720 }
721 impl_compact_resolver_keyless!(@locked $db, $op, $val_bound, AsyncRwLock);
722 impl_compact_resolver_keyless!(@locked $db, $op, $val_bound, TracedAsyncRwLock);
723 };
724 (@locked $db:ident, $op:ident, $val_bound:ident, $lock:ident) => {
725
726 impl<F, E, V, H, S> Resolver for Arc<$lock<$db<F, E, V, H, S>>>
727 where
728 F: Family,
729 E: crate::Context,
730 V: $val_bound + Send + Sync + 'static,
731 H: Hasher,
732 S: Strategy,
733 {
734 type Family = F;
735 type Digest = H::Digest;
736 type Op = $op<F, V>;
737 type Error = ServeError<F, H::Digest>;
738
739 async fn get_compact_state(
740 &self,
741 target: Target<Self::Family, Self::Digest>,
742 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
743 let db = self.read().await;
744 fetch_state_from_full_source(
745 target,
746 || Target::new(db.root(), db.bounds().end),
747 |leaf_count, last_commit_loc| {
748 db.historical_proof(
749 leaf_count,
750 last_commit_loc,
751 NonZeroU64::new(1).unwrap(),
752 )
753 },
754 |leaf_count| db.pinned_nodes_at(leaf_count),
755 )
756 .await
757 .map(Into::into)
758 }
759 }
760
761 impl<F, E, V, H, S> Resolver for Arc<$lock<Option<$db<F, E, V, H, S>>>>
762 where
763 F: Family,
764 E: crate::Context,
765 V: $val_bound + Send + Sync + 'static,
766 H: Hasher,
767 S: Strategy,
768 {
769 type Family = F;
770 type Digest = H::Digest;
771 type Op = $op<F, V>;
772 type Error = ServeError<F, H::Digest>;
773
774 async fn get_compact_state(
775 &self,
776 target: Target<Self::Family, Self::Digest>,
777 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
778 let guard = self.read().await;
779 let db = guard.as_ref().ok_or(ServeError::MissingSource)?;
780 fetch_state_from_full_source(
781 target,
782 || Target::new(db.root(), db.bounds().end),
783 |leaf_count, last_commit_loc| {
784 db.historical_proof(
785 leaf_count,
786 last_commit_loc,
787 NonZeroU64::new(1).unwrap(),
788 )
789 },
790 |leaf_count| db.pinned_nodes_at(leaf_count),
791 )
792 .await
793 .map(Into::into)
794 }
795 }
796 };
797}
798
799macro_rules! impl_compact_resolver_immutable {
802 ($db:ident, $op:ident, $val_bound:ident, $key_bound:path) => {
803 impl<F, E, K, V, H, T, S> Resolver for Arc<$db<F, E, K, V, H, T, S>>
804 where
805 F: Family,
806 E: crate::Context,
807 K: $key_bound,
808 V: $val_bound + Send + Sync + 'static,
809 H: Hasher,
810 T: Translator + Send + Sync + 'static,
811 T::Key: Send + Sync,
812 S: Strategy,
813 {
814 type Family = F;
815 type Digest = H::Digest;
816 type Op = $op<F, K, V>;
817 type Error = ServeError<F, H::Digest>;
818
819 async fn get_compact_state(
820 &self,
821 target: Target<Self::Family, Self::Digest>,
822 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
823 fetch_state_from_full_source(
824 target,
825 || Target::new(self.root(), self.bounds().end),
826 |leaf_count, last_commit_loc| {
827 self.historical_proof(
828 leaf_count,
829 last_commit_loc,
830 NonZeroU64::new(1).unwrap(),
831 )
832 },
833 |leaf_count| self.pinned_nodes_at(leaf_count),
834 )
835 .await
836 .map(Into::into)
837 }
838 }
839 impl_compact_resolver_immutable!(@locked $db, $op, $val_bound, $key_bound, AsyncRwLock);
840 impl_compact_resolver_immutable!(@locked $db, $op, $val_bound, $key_bound, TracedAsyncRwLock);
841 };
842 (@locked $db:ident, $op:ident, $val_bound:ident, $key_bound:path, $lock:ident) => {
843
844 impl<F, E, K, V, H, T, S> Resolver for Arc<$lock<$db<F, E, K, V, H, T, S>>>
845 where
846 F: Family,
847 E: crate::Context,
848 K: $key_bound,
849 V: $val_bound + Send + Sync + 'static,
850 H: Hasher,
851 T: Translator + Send + Sync + 'static,
852 T::Key: Send + Sync,
853 S: Strategy,
854 {
855 type Family = F;
856 type Digest = H::Digest;
857 type Op = $op<F, K, V>;
858 type Error = ServeError<F, H::Digest>;
859
860 async fn get_compact_state(
861 &self,
862 target: Target<Self::Family, Self::Digest>,
863 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
864 let db = self.read().await;
865 fetch_state_from_full_source(
866 target,
867 || Target::new(db.root(), db.bounds().end),
868 |leaf_count, last_commit_loc| {
869 db.historical_proof(
870 leaf_count,
871 last_commit_loc,
872 NonZeroU64::new(1).unwrap(),
873 )
874 },
875 |leaf_count| db.pinned_nodes_at(leaf_count),
876 )
877 .await
878 .map(Into::into)
879 }
880 }
881
882 impl<F, E, K, V, H, T, S> Resolver for Arc<$lock<Option<$db<F, E, K, V, H, T, S>>>>
883 where
884 F: Family,
885 E: crate::Context,
886 K: $key_bound,
887 V: $val_bound + Send + Sync + 'static,
888 H: Hasher,
889 T: Translator + Send + Sync + 'static,
890 T::Key: Send + Sync,
891 S: Strategy,
892 {
893 type Family = F;
894 type Digest = H::Digest;
895 type Op = $op<F, K, V>;
896 type Error = ServeError<F, H::Digest>;
897
898 async fn get_compact_state(
899 &self,
900 target: Target<Self::Family, Self::Digest>,
901 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
902 let guard = self.read().await;
903 let db = guard.as_ref().ok_or(ServeError::MissingSource)?;
904 fetch_state_from_full_source(
905 target,
906 || Target::new(db.root(), db.bounds().end),
907 |leaf_count, last_commit_loc| {
908 db.historical_proof(
909 leaf_count,
910 last_commit_loc,
911 NonZeroU64::new(1).unwrap(),
912 )
913 },
914 |leaf_count| db.pinned_nodes_at(leaf_count),
915 )
916 .await
917 .map(Into::into)
918 }
919 }
920 };
921}
922
923macro_rules! impl_compact_resolver_compact_keyless {
926 ($db:ident, $op:ident) => {
927 impl<F, E, V, H, C, S> Resolver for Arc<$db<F, E, V, H, C, S>>
928 where
929 F: Family,
930 E: crate::Context,
931 V: ValueEncoding + Send + Sync + 'static,
932 H: Hasher,
933 $op<F, V>: Encode + Read<Cfg = C>,
934 C: Clone + Send + Sync + 'static,
935 S: Strategy,
936 {
937 type Family = F;
938 type Digest = H::Digest;
939 type Op = $op<F, V>;
940 type Error = ServeError<F, H::Digest>;
941
942 async fn get_compact_state(
943 &self,
944 target: Target<Self::Family, Self::Digest>,
945 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
946 self.compact_state(target).map(Into::into)
947 }
948 }
949 impl_compact_resolver_compact_keyless!(@locked $db, $op, AsyncRwLock);
950 impl_compact_resolver_compact_keyless!(@locked $db, $op, TracedAsyncRwLock);
951 };
952 (@locked $db:ident, $op:ident, $lock:ident) => {
953
954 impl<F, E, V, H, C, S> Resolver for Arc<$lock<$db<F, E, V, H, C, S>>>
955 where
956 F: Family,
957 E: crate::Context,
958 V: ValueEncoding + Send + Sync + 'static,
959 H: Hasher,
960 $op<F, V>: Encode + Read<Cfg = C>,
961 C: Clone + Send + Sync + 'static,
962 S: Strategy,
963 {
964 type Family = F;
965 type Digest = H::Digest;
966 type Op = $op<F, V>;
967 type Error = ServeError<F, H::Digest>;
968
969 async fn get_compact_state(
970 &self,
971 target: Target<Self::Family, Self::Digest>,
972 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
973 let db = self.read().await;
974 db.compact_state(target).map(Into::into)
975 }
976 }
977
978 impl<F, E, V, H, C, S> Resolver for Arc<$lock<Option<$db<F, E, V, H, C, S>>>>
979 where
980 F: Family,
981 E: crate::Context,
982 V: ValueEncoding + Send + Sync + 'static,
983 H: Hasher,
984 $op<F, V>: Encode + Read<Cfg = C>,
985 C: Clone + Send + Sync + 'static,
986 S: Strategy,
987 {
988 type Family = F;
989 type Digest = H::Digest;
990 type Op = $op<F, V>;
991 type Error = ServeError<F, H::Digest>;
992
993 async fn get_compact_state(
994 &self,
995 target: Target<Self::Family, Self::Digest>,
996 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
997 let guard = self.read().await;
998 let db = guard.as_ref().ok_or(ServeError::MissingSource)?;
999 db.compact_state(target).map(Into::into)
1000 }
1001 }
1002 };
1003}
1004
1005macro_rules! impl_compact_resolver_compact_immutable {
1008 ($db:ident, $op:ident) => {
1009 impl<F, E, K, V, H, C, S> Resolver for Arc<$db<F, E, K, V, H, C, S>>
1010 where
1011 F: Family,
1012 E: crate::Context,
1013 K: Key,
1014 V: ValueEncoding + Send + Sync + 'static,
1015 H: Hasher,
1016 $op<F, K, V>: Encode + Read<Cfg = C>,
1017 C: Clone + Send + Sync + 'static,
1018 S: Strategy,
1019 {
1020 type Family = F;
1021 type Digest = H::Digest;
1022 type Op = $op<F, K, V>;
1023 type Error = ServeError<F, H::Digest>;
1024
1025 async fn get_compact_state(
1026 &self,
1027 target: Target<Self::Family, Self::Digest>,
1028 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
1029 self.compact_state(target).map(Into::into)
1030 }
1031 }
1032 impl_compact_resolver_compact_immutable!(@locked $db, $op, AsyncRwLock);
1033 impl_compact_resolver_compact_immutable!(@locked $db, $op, TracedAsyncRwLock);
1034 };
1035 (@locked $db:ident, $op:ident, $lock:ident) => {
1036
1037 impl<F, E, K, V, H, C, S> Resolver for Arc<$lock<$db<F, E, K, V, H, C, S>>>
1038 where
1039 F: Family,
1040 E: crate::Context,
1041 K: Key,
1042 V: ValueEncoding + Send + Sync + 'static,
1043 H: Hasher,
1044 $op<F, K, V>: Encode + Read<Cfg = C>,
1045 C: Clone + Send + Sync + 'static,
1046 S: Strategy,
1047 {
1048 type Family = F;
1049 type Digest = H::Digest;
1050 type Op = $op<F, K, V>;
1051 type Error = ServeError<F, H::Digest>;
1052
1053 async fn get_compact_state(
1054 &self,
1055 target: Target<Self::Family, Self::Digest>,
1056 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
1057 let db = self.read().await;
1058 db.compact_state(target).map(Into::into)
1059 }
1060 }
1061
1062 impl<F, E, K, V, H, C, S> Resolver for Arc<$lock<Option<$db<F, E, K, V, H, C, S>>>>
1063 where
1064 F: Family,
1065 E: crate::Context,
1066 K: Key,
1067 V: ValueEncoding + Send + Sync + 'static,
1068 H: Hasher,
1069 $op<F, K, V>: Encode + Read<Cfg = C>,
1070 C: Clone + Send + Sync + 'static,
1071 S: Strategy,
1072 {
1073 type Family = F;
1074 type Digest = H::Digest;
1075 type Op = $op<F, K, V>;
1076 type Error = ServeError<F, H::Digest>;
1077
1078 async fn get_compact_state(
1079 &self,
1080 target: Target<Self::Family, Self::Digest>,
1081 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
1082 let guard = self.read().await;
1083 let db = guard.as_ref().ok_or(ServeError::MissingSource)?;
1084 db.compact_state(target).map(Into::into)
1085 }
1086 }
1087 };
1088}
1089
1090impl_compact_resolver_compact_keyless!(KeylessCompactDb, KeylessOp);
1091impl_compact_resolver_compact_immutable!(ImmutableCompactDb, ImmutableOp);
1092
1093impl_compact_resolver_keyless!(KeylessFixedDb, KeylessFixedOp, FixedValue);
1094impl_compact_resolver_keyless!(KeylessVariableDb, KeylessVariableOp, VariableValue);
1095impl_compact_resolver_immutable!(ImmutableFixedDb, ImmutableFixedOp, FixedValue, Array);
1096impl_compact_resolver_immutable!(ImmutableVariableDb, ImmutableVariableOp, VariableValue, Key);
1097
1098#[cfg(test)]
1099mod tests {
1100 use super::{Config, Database, FetchResult, Resolver, State, Target};
1101 use crate::{
1102 merkle::{mmr, Location},
1103 qmdb,
1104 };
1105 use commonware_codec::{DecodeExt as _, Encode as _, RangeCfg};
1106 use commonware_cryptography::{sha256::Digest, Hasher as _, Sha256};
1107 use commonware_parallel::Rayon;
1108 use commonware_runtime::{deterministic, Runner as _};
1109 use commonware_utils::sync::AsyncRwLock;
1110 use std::{
1111 collections::VecDeque,
1112 convert::Infallible,
1113 sync::{
1114 atomic::{AtomicUsize, Ordering},
1115 Arc,
1116 },
1117 };
1118
1119 macro_rules! assert_resolver_variants {
1120 ($db:ty) => {
1121 assert_resolver::<Arc<$db>>();
1122 assert_resolver::<Arc<AsyncRwLock<$db>>>();
1123 assert_resolver::<Arc<AsyncRwLock<Option<$db>>>>();
1124 };
1125 }
1126
1127 fn assert_resolver<R: super::Resolver>() {}
1128
1129 struct TestDb {
1130 root: Digest,
1131 }
1132
1133 impl Database for TestDb {
1134 type Family = mmr::Family;
1135 type Op = u8;
1136 type Config = (Digest, Arc<AtomicUsize>);
1137 type Digest = Digest;
1138 type Context = deterministic::Context;
1139 type Hasher = Sha256;
1140
1141 async fn from_validated_state(
1142 _context: Self::Context,
1143 (root, constructions): Self::Config,
1144 _state: super::ValidatedState<Self::Family, Self::Op, Self::Digest>,
1145 ) -> Result<Self, qmdb::Error<Self::Family>> {
1146 constructions.fetch_add(1, Ordering::SeqCst);
1147 Ok(Self { root })
1148 }
1149
1150 fn inactivity_floor(_op: &Self::Op) -> Option<Location<Self::Family>> {
1151 Some(Location::new(0))
1152 }
1153
1154 fn root(&self) -> Self::Digest {
1155 self.root
1156 }
1157
1158 async fn persist_compact_state(&mut self) -> Result<(), qmdb::Error<Self::Family>> {
1159 Ok(())
1160 }
1161 }
1162
1163 #[derive(Clone)]
1164 struct SequenceResolver {
1165 states: Arc<commonware_utils::sync::Mutex<VecDeque<FetchResult<mmr::Family, u8, Digest>>>>,
1166 }
1167
1168 impl Resolver for SequenceResolver {
1169 type Family = mmr::Family;
1170 type Digest = Digest;
1171 type Op = u8;
1172 type Error = Infallible;
1173
1174 async fn get_compact_state(
1175 &self,
1176 _target: Target<Self::Family, Self::Digest>,
1177 ) -> Result<FetchResult<Self::Family, Self::Op, Self::Digest>, Self::Error> {
1178 Ok(self
1179 .states
1180 .lock()
1181 .pop_front()
1182 .expect("missing compact fetch result"))
1183 }
1184 }
1185
1186 fn valid_state_and_target() -> (State<mmr::Family, u8, Digest>, Target<mmr::Family, Digest>) {
1187 let hasher = qmdb::hasher::<Sha256>();
1188 let mut merkle = crate::merkle::mem::Mem::<mmr::Family, Digest>::new();
1189 let op = 0u8;
1190 let first_op = 1u8;
1191 let batch = merkle
1192 .new_batch()
1193 .add(&hasher, &first_op.encode())
1194 .add(&hasher, &op.encode());
1195 let batch = batch.merkleize(&merkle, &hasher);
1196 merkle.apply_batch(&batch).unwrap();
1197 let root = merkle.root(&hasher, 0).unwrap();
1198 let leaf_count = Location::new(2);
1199 let pinned_nodes = merkle
1200 .nodes_to_pin(leaf_count)
1201 .into_values()
1202 .collect::<Vec<_>>();
1203 let proof = merkle.proof(&hasher, Location::new(1), 0).unwrap();
1204 (
1205 State {
1206 leaf_count,
1207 pinned_nodes,
1208 last_commit_op: op,
1209 last_commit_proof: proof,
1210 },
1211 Target::<mmr::Family, Digest> { root, leaf_count },
1212 )
1213 }
1214
1215 #[test]
1216 fn test_all_compact_qmdb_variants_implement_strategy_resolvers() {
1217 type KeylessFixedCompactDb = crate::qmdb::keyless::fixed::CompactDb<
1218 mmr::Family,
1219 deterministic::Context,
1220 Digest,
1221 commonware_cryptography::Sha256,
1222 Rayon,
1223 >;
1224 type KeylessVariableCompactDb = crate::qmdb::keyless::variable::CompactDb<
1225 mmr::Family,
1226 deterministic::Context,
1227 Vec<u8>,
1228 commonware_cryptography::Sha256,
1229 (RangeCfg<usize>, ()),
1230 Rayon,
1231 >;
1232 type ImmutableFixedCompactDb = crate::qmdb::immutable::fixed::CompactDb<
1233 mmr::Family,
1234 deterministic::Context,
1235 Digest,
1236 Digest,
1237 commonware_cryptography::Sha256,
1238 Rayon,
1239 >;
1240 type ImmutableVariableCompactDb = crate::qmdb::immutable::variable::CompactDb<
1241 mmr::Family,
1242 deterministic::Context,
1243 Digest,
1244 Vec<u8>,
1245 commonware_cryptography::Sha256,
1246 ((), (RangeCfg<usize>, ())),
1247 Rayon,
1248 >;
1249
1250 assert_resolver_variants!(KeylessFixedCompactDb);
1251 assert_resolver_variants!(KeylessVariableCompactDb);
1252 assert_resolver_variants!(ImmutableFixedCompactDb);
1253 assert_resolver_variants!(ImmutableVariableCompactDb);
1254 }
1255
1256 #[test]
1257 fn test_target_decode_rejects_zero_leaf_count() {
1258 let unused_root = commonware_cryptography::Sha256::hash(b"unused");
1259 let encoded = Target::<mmr::Family, Digest> {
1260 root: unused_root,
1261 leaf_count: crate::merkle::Location::new(0),
1262 }
1263 .encode();
1264
1265 assert!(Target::<mmr::Family, Digest>::decode(encoded).is_err());
1266 }
1267
1268 #[test]
1269 fn test_compact_sync_retries_invalid_state_without_feedback() {
1270 deterministic::Runner::default().start(|context| async move {
1271 let (good_state, target) = valid_state_and_target();
1272 let mut bad_state = good_state.clone();
1273 bad_state.pinned_nodes.push(Sha256::hash(b"extra pin"));
1274 let (good_tx, good_rx) = commonware_utils::channel::oneshot::channel();
1275 let constructions = Arc::new(AtomicUsize::new(0));
1276
1277 let db = super::sync::<TestDb, _>(Config {
1278 context,
1279 resolver: SequenceResolver {
1280 states: Arc::new(commonware_utils::sync::Mutex::new(VecDeque::from([
1281 FetchResult {
1282 state: bad_state,
1283 callback: None,
1284 },
1285 FetchResult {
1286 state: good_state,
1287 callback: Some(good_tx),
1288 },
1289 ]))),
1290 },
1291 target: target.clone(),
1292 db_config: (target.root, constructions.clone()),
1293 update_rx: None,
1294 finish_rx: None,
1295 reached_target_tx: None,
1296 })
1297 .await
1298 .unwrap();
1299
1300 assert!(good_rx.await.expect("valid feedback should arrive"));
1301 assert_eq!(constructions.load(Ordering::SeqCst), 1);
1302 assert_eq!(db.root(), target.root);
1303 });
1304 }
1305}
1306
1307#[cfg(all(test, feature = "arbitrary"))]
1308mod conformance {
1309 use super::*;
1310 use crate::merkle::{mmb, mmr};
1311 use commonware_codec::conformance::CodecConformance;
1312 use commonware_cryptography::sha256::Digest as Sha256Digest;
1313
1314 commonware_conformance::conformance_tests! {
1315 CodecConformance<Target<mmr::Family, Sha256Digest>>,
1316 CodecConformance<Target<mmb::Family, Sha256Digest>>,
1317 }
1318}