1use crate::{
2 Context,
3 journal::{authenticated, contiguous::Contiguous},
4 merkle::{Family, Location, MAX_PINNED_NODES, MAX_PROOF_DIGESTS_PER_ELEMENT, Proof},
5 qmdb::{self, operation::Floored, sync::ServeError},
6};
7use bytes::{Buf, BufMut};
8use commonware_codec::{
9 EncodeShared, EncodeSize, Error as CodecError, Read, ReadExt as _, ReadRangeExt as _, Write,
10};
11use commonware_cryptography::{Digest, Hasher};
12use commonware_parallel::Strategy;
13use commonware_utils::{
14 Span,
15 channel::oneshot,
16 sync::{AsyncRwLock, TracedAsyncRwLock},
17};
18use std::{cmp::Ordering, future::Future, num::NonZeroU64, sync::Arc};
19
20pub enum Request<F: Family> {
22 Operations {
24 size: Location<F>,
26 start: Location<F>,
28 max_ops: NonZeroU64,
30 },
31 Boundary {
35 size: Location<F>,
37 start: Location<F>,
39 },
40}
41
42impl<F: Family> Request<F> {
43 pub const fn size(&self) -> Location<F> {
45 match self {
46 Self::Operations { size, .. } | Self::Boundary { size, .. } => *size,
47 }
48 }
49
50 pub const fn start(&self) -> Location<F> {
52 match self {
53 Self::Operations { start, .. } | Self::Boundary { start, .. } => *start,
54 }
55 }
56
57 pub const fn max_ops(&self) -> NonZeroU64 {
59 match self {
60 Self::Operations { max_ops, .. } => *max_ops,
61 Self::Boundary { .. } => NonZeroU64::MIN,
62 }
63 }
64
65 fn order_key(&self) -> (u64, u64, u64, bool) {
67 (
68 *self.size(),
69 *self.start(),
70 self.max_ops().get(),
71 matches!(self, Self::Boundary { .. }),
72 )
73 }
74}
75
76impl<F: Family> Clone for Request<F> {
77 fn clone(&self) -> Self {
78 *self
79 }
80}
81
82impl<F: Family> Copy for Request<F> {}
83
84impl<F: Family> PartialEq for Request<F> {
85 fn eq(&self, other: &Self) -> bool {
86 self.order_key() == other.order_key()
87 }
88}
89
90impl<F: Family> Eq for Request<F> {}
91
92impl<F: Family> PartialOrd for Request<F> {
93 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
94 Some(self.cmp(other))
95 }
96}
97
98impl<F: Family> Ord for Request<F> {
99 fn cmp(&self, other: &Self) -> Ordering {
100 self.order_key().cmp(&other.order_key())
101 }
102}
103
104impl<F: Family> std::hash::Hash for Request<F> {
105 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
106 self.order_key().hash(state);
107 }
108}
109
110impl<F: Family> std::fmt::Debug for Request<F> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 match self {
113 Self::Operations {
114 size,
115 start,
116 max_ops,
117 } => f
118 .debug_struct("Operations")
119 .field("size", size)
120 .field("start", start)
121 .field("max_ops", max_ops)
122 .finish(),
123 Self::Boundary { size, start } => f
124 .debug_struct("Boundary")
125 .field("size", size)
126 .field("start", start)
127 .finish(),
128 }
129 }
130}
131
132impl<F: Family> std::fmt::Display for Request<F> {
133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134 match self {
135 Self::Operations {
136 size,
137 start,
138 max_ops,
139 } => write!(f, "Operations(size={size}, start={start}, max={max_ops})"),
140 Self::Boundary { size, start } => write!(f, "Boundary(size={size}, start={start})"),
141 }
142 }
143}
144
145impl<F: Family> Write for Request<F> {
146 fn write(&self, buf: &mut impl BufMut) {
147 match self {
148 Self::Operations {
149 size,
150 start,
151 max_ops,
152 } => {
153 0u8.write(buf);
154 size.write(buf);
155 start.write(buf);
156 max_ops.write(buf);
157 }
158 Self::Boundary { size, start } => {
159 1u8.write(buf);
160 size.write(buf);
161 start.write(buf);
162 }
163 }
164 }
165}
166
167impl<F: Family> EncodeSize for Request<F> {
168 fn encode_size(&self) -> usize {
169 1 + match self {
170 Self::Operations {
171 size,
172 start,
173 max_ops,
174 } => size.encode_size() + start.encode_size() + max_ops.encode_size(),
175 Self::Boundary { size, start } => size.encode_size() + start.encode_size(),
176 }
177 }
178}
179
180impl<F: Family> Read for Request<F> {
181 type Cfg = ();
182
183 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
184 let request = match u8::read(buf)? {
185 0 => Self::Operations {
186 size: Location::<F>::read(buf)?,
187 start: Location::<F>::read(buf)?,
188 max_ops: NonZeroU64::read(buf)?,
189 },
190 1 => Self::Boundary {
191 size: Location::<F>::read(buf)?,
192 start: Location::<F>::read(buf)?,
193 },
194 d => return Err(CodecError::InvalidEnum(d)),
195 };
196 if request.start() >= request.size() {
197 return Err(CodecError::Invalid("Request", "start >= size"));
198 }
199 Ok(request)
200 }
201}
202
203impl<F: Family> Span for Request<F> {}
204
205#[cfg(feature = "arbitrary")]
206impl<F: Family> arbitrary::Arbitrary<'_> for Request<F> {
207 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
208 let size = u.int_in_range(1..=*F::MAX_LEAVES)?;
209 let start = u.int_in_range(0..=size - 1)?;
210 let size = Location::new(size);
211 let start = Location::new(start);
212 Ok(if u.arbitrary()? {
213 Self::Boundary { size, start }
214 } else {
215 Self::Operations {
216 size,
217 start,
218 max_ops: u.arbitrary()?,
219 }
220 })
221 }
222}
223
224pub enum Response<F: Family, Op, D: Digest> {
230 Operations {
232 proof: Proof<F, D>,
234 operations: Vec<Op>,
236 },
237 Boundary {
239 proof: Proof<F, D>,
241 op: Op,
243 pinned_nodes: Vec<D>,
245 },
246}
247
248impl<F: Family, Op, D: Digest> Response<F, Op, D> {
249 pub const fn proof(&self) -> &Proof<F, D> {
251 match self {
252 Self::Operations { proof, .. } | Self::Boundary { proof, .. } => proof,
253 }
254 }
255}
256
257impl<F: Family, Op: Clone, D: Digest> Clone for Response<F, Op, D> {
258 fn clone(&self) -> Self {
259 match self {
260 Self::Operations { proof, operations } => Self::Operations {
261 proof: proof.clone(),
262 operations: operations.clone(),
263 },
264 Self::Boundary {
265 proof,
266 op,
267 pinned_nodes,
268 } => Self::Boundary {
269 proof: proof.clone(),
270 op: op.clone(),
271 pinned_nodes: pinned_nodes.clone(),
272 },
273 }
274 }
275}
276
277impl<F: Family, Op: std::fmt::Debug, D: Digest> std::fmt::Debug for Response<F, Op, D> {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 match self {
280 Self::Operations { proof, operations } => f
281 .debug_struct("Operations")
282 .field("proof", proof)
283 .field("operations", operations)
284 .finish(),
285 Self::Boundary {
286 proof,
287 op,
288 pinned_nodes,
289 } => f
290 .debug_struct("Boundary")
291 .field("proof", proof)
292 .field("op", op)
293 .field("pinned_nodes", pinned_nodes)
294 .finish(),
295 }
296 }
297}
298
299impl<F: Family, Op: Write, D: Digest> Write for Response<F, Op, D> {
300 fn write(&self, buf: &mut impl BufMut) {
301 match self {
302 Self::Operations { proof, operations } => {
303 0u8.write(buf);
304 proof.write(buf);
305 operations.write(buf);
306 }
307 Self::Boundary {
308 proof,
309 op,
310 pinned_nodes,
311 } => {
312 1u8.write(buf);
313 proof.write(buf);
314 op.write(buf);
315 pinned_nodes.write(buf);
316 }
317 }
318 }
319}
320
321impl<F: Family, Op: EncodeSize, D: Digest> EncodeSize for Response<F, Op, D> {
322 fn encode_size(&self) -> usize {
323 1 + match self {
324 Self::Operations { proof, operations } => {
325 proof.encode_size() + operations.encode_size()
326 }
327 Self::Boundary {
328 proof,
329 op,
330 pinned_nodes,
331 } => proof.encode_size() + op.encode_size() + pinned_nodes.encode_size(),
332 }
333 }
334}
335
336impl<F: Family, Op: Read, D: Digest> Read for Response<F, Op, D> {
337 type Cfg = (usize, Op::Cfg);
339
340 fn read_cfg(buf: &mut impl Buf, (max_ops, op_cfg): &Self::Cfg) -> Result<Self, CodecError> {
341 match u8::read(buf)? {
342 0 => {
343 let max_proof_digests = max_ops.saturating_mul(MAX_PROOF_DIGESTS_PER_ELEMENT);
344 let proof = Proof::<F, D>::read_cfg(buf, &max_proof_digests)?;
345 let operations = Vec::<Op>::read_cfg(buf, &((..=*max_ops).into(), op_cfg.clone()))?;
346 Ok(Self::Operations { proof, operations })
347 }
348 1 => {
349 let proof = Proof::<F, D>::read_cfg(buf, &MAX_PROOF_DIGESTS_PER_ELEMENT)?;
350 let op = Op::read_cfg(buf, op_cfg)?;
351 let pinned_nodes = Vec::<D>::read_range(buf, ..=MAX_PINNED_NODES)?;
352 Ok(Self::Boundary {
353 proof,
354 op,
355 pinned_nodes,
356 })
357 }
358 d => Err(CodecError::InvalidEnum(d)),
359 }
360 }
361}
362
363#[cfg(feature = "arbitrary")]
364impl<F: Family, Op, D: Digest> arbitrary::Arbitrary<'_> for Response<F, Op, D>
365where
366 Op: for<'a> arbitrary::Arbitrary<'a>,
367 D: for<'a> arbitrary::Arbitrary<'a>,
368{
369 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
370 Ok(if u.arbitrary()? {
371 Self::Boundary {
372 proof: u.arbitrary()?,
373 op: u.arbitrary()?,
374 pinned_nodes: u.arbitrary()?,
375 }
376 } else {
377 Self::Operations {
378 proof: u.arbitrary()?,
379 operations: u.arbitrary()?,
380 }
381 })
382 }
383}
384
385pub type FeedbackTx = Option<oneshot::Sender<bool>>;
391
392pub trait Source: Send + Sync {
394 type Family: Family;
396
397 type Digest: Digest;
399
400 type Op;
402
403 type Error: std::error::Error + Send + 'static;
405
406 #[allow(clippy::type_complexity)]
408 fn serve<'a>(
409 &'a self,
410 request: Request<Self::Family>,
411 ) -> impl Future<
412 Output = Result<(Response<Self::Family, Self::Op, Self::Digest>, FeedbackTx), Self::Error>,
413 > + Send
414 + 'a;
415}
416
417impl<T> Source for Arc<T>
418where
419 T: Source + ?Sized,
420{
421 type Family = T::Family;
422 type Digest = T::Digest;
423 type Op = T::Op;
424 type Error = T::Error;
425
426 fn serve<'a>(
427 &'a self,
428 request: Request<Self::Family>,
429 ) -> impl Future<
430 Output = Result<(Response<Self::Family, Self::Op, Self::Digest>, FeedbackTx), Self::Error>,
431 > + Send
432 + 'a {
433 T::serve(self, request)
434 }
435}
436
437impl<T> Source for Option<T>
438where
439 T: Source,
440 ServeError<T::Family>: From<T::Error>,
441{
442 type Family = T::Family;
443 type Digest = T::Digest;
444 type Op = T::Op;
445 type Error = ServeError<T::Family>;
446
447 async fn serve(
448 &self,
449 request: Request<Self::Family>,
450 ) -> Result<(Response<Self::Family, Self::Op, Self::Digest>, FeedbackTx), Self::Error> {
451 let source = self.as_ref().ok_or(ServeError::MissingSource)?;
452 Ok(source.serve(request).await?)
453 }
454}
455
456macro_rules! impl_locked_source {
457 ($lock:ident) => {
458 impl<T> Source for $lock<T>
459 where
460 T: Source,
461 {
462 type Family = T::Family;
463 type Digest = T::Digest;
464 type Op = T::Op;
465 type Error = T::Error;
466
467 async fn serve(
468 &self,
469 request: Request<Self::Family>,
470 ) -> Result<(Response<Self::Family, Self::Op, Self::Digest>, FeedbackTx), Self::Error>
471 {
472 self.read().await.serve(request).await
473 }
474 }
475 };
476}
477
478impl_locked_source!(AsyncRwLock);
479impl_locked_source!(TracedAsyncRwLock);
480
481impl<F, E, C, H, S> Source for authenticated::Journal<F, E, C, H, S>
482where
483 F: Family,
484 E: Context,
485 C: Contiguous<Item: EncodeShared + Floored<F>>,
486 H: Hasher,
487 S: Strategy,
488{
489 type Family = F;
490 type Digest = H::Digest;
491 type Op = C::Item;
492 type Error = qmdb::Error<F>;
493
494 #[allow(clippy::type_complexity)]
495 #[tracing::instrument(
496 name = "qmdb.sync.serve",
497 level = "info",
498 skip_all,
499 fields(
500 size = *request.size(),
501 start = *request.start(),
502 max_ops = request.max_ops().get(),
503 ),
504 )]
505 async fn serve(
506 &self,
507 request: Request<F>,
508 ) -> Result<(Response<F, C::Item, H::Digest>, FeedbackTx), qmdb::Error<F>> {
509 if request.size() > self.size() {
512 return Err(crate::merkle::Error::RangeOutOfBounds(request.size()).into());
513 }
514 let inactive_peaks = qmdb::inactive_peaks_at::<F, _>(self, request.size()).await?;
515 let response = match request {
516 Request::Operations {
517 size,
518 start,
519 max_ops,
520 } => {
521 let (proof, operations) = self
522 .historical_proof(size, start, max_ops, inactive_peaks)
523 .await?;
524 Response::Operations { proof, operations }
525 }
526 Request::Boundary { size, start } => {
527 let (proof, mut operations) = self
528 .historical_proof(size, start, NonZeroU64::MIN, inactive_peaks)
529 .await?;
530 let op = operations
531 .pop()
532 .ok_or(crate::merkle::Error::RangeOutOfBounds(start))?;
533 let pinned_nodes = self.merkle.pinned_nodes_at(start).await?;
534 Response::Boundary {
535 proof,
536 op,
537 pinned_nodes,
538 }
539 }
540 };
541 Ok((response, None))
542 }
543}
544
545impl<F, E, C, I, H, U, const N: usize, S> Source
546 for crate::qmdb::any::db::Db<F, E, C, I, H, U, N, S>
547where
548 F: Family,
549 E: Context,
550 C: crate::journal::contiguous::Mutable<Item = crate::qmdb::any::operation::Operation<F, U>>,
551 I: crate::index::Unordered<Value = Location<F>>,
552 H: Hasher,
553 U: crate::qmdb::any::operation::update::Update,
554 S: Strategy,
555 crate::qmdb::any::operation::Operation<F, U>: commonware_codec::Codec,
556{
557 type Family = F;
558 type Digest = H::Digest;
559 type Op = crate::qmdb::any::operation::Operation<F, U>;
560 type Error = qmdb::Error<F>;
561
562 async fn serve(
563 &self,
564 request: Request<F>,
565 ) -> Result<(Response<Self::Family, Self::Op, Self::Digest>, FeedbackTx), Self::Error> {
566 self.log.serve(request).await
567 }
568}
569
570#[cfg(test)]
571pub(crate) mod tests {
572 use super::*;
573 use crate::{
574 merkle::mmr,
575 translator::{OneCap, TwoCap},
576 };
577 use commonware_codec::{Decode as _, DecodeExt as _, Encode as _};
578 use commonware_cryptography::{Sha256, sha256::Digest as ShaDigest};
579 use commonware_parallel::Rayon;
580 use commonware_runtime::{Runner as _, deterministic};
581 use commonware_utils::{
582 NZU64,
583 sync::{AsyncRwLock, TracedAsyncRwLock},
584 };
585 use std::{collections::VecDeque, marker::PhantomData, sync::Arc};
586
587 macro_rules! assert_source_variants {
588 ($db:ty) => {
589 assert_serves::<Arc<$db>>();
590 assert_serves::<Arc<AsyncRwLock<$db>>>();
591 assert_serves::<Arc<AsyncRwLock<Option<$db>>>>();
592 assert_serves::<Arc<TracedAsyncRwLock<$db>>>();
593 assert_serves::<Arc<TracedAsyncRwLock<Option<$db>>>>();
594 };
595 }
596
597 fn assert_serves<S: Source>() {}
598
599 pub fn dropped_feedback() -> FeedbackTx {
602 let (tx, _rx) = oneshot::channel();
603 Some(tx)
604 }
605
606 #[derive(Clone)]
608 pub struct SequenceSource<F: Family, Op, D: Digest> {
609 #[allow(clippy::type_complexity)]
610 responses: Arc<commonware_utils::sync::Mutex<VecDeque<(Response<F, Op, D>, FeedbackTx)>>>,
611 }
612
613 impl<F: Family, Op, D: Digest> SequenceSource<F, Op, D> {
614 pub fn new(responses: Vec<(Response<F, Op, D>, FeedbackTx)>) -> Self {
615 Self {
616 responses: Arc::new(commonware_utils::sync::Mutex::new(VecDeque::from(
617 responses,
618 ))),
619 }
620 }
621 }
622
623 impl<F, Op, D> Source for SequenceSource<F, Op, D>
624 where
625 F: Family,
626 D: Digest,
627 Op: Send + Sync + Clone + 'static,
628 {
629 type Family = F;
630 type Digest = D;
631 type Op = Op;
632 type Error = qmdb::Error<F>;
633
634 async fn serve(
635 &self,
636 _request: Request<F>,
637 ) -> Result<(Response<F, Op, D>, FeedbackTx), qmdb::Error<F>> {
638 self.responses
639 .lock()
640 .pop_front()
641 .ok_or(qmdb::Error::DataCorrupted("missing scripted response"))
642 }
643 }
644
645 pub async fn fetch_compact_state<R: Source>(
647 source: &R,
648 target: crate::qmdb::sync::CompactTarget<R::Family, R::Digest>,
649 ) -> Result<(Response<R::Family, R::Op, R::Digest>, FeedbackTx), R::Error> {
650 source
651 .serve(Request::Boundary {
652 size: target.size,
653 start: target.size - 1,
654 })
655 .await
656 }
657
658 pub struct FailSource<F: Family, Op, D> {
660 _phantom: PhantomData<(F, Op, D)>,
661 }
662
663 impl<F, Op, D> Source for FailSource<F, Op, D>
664 where
665 F: Family,
666 D: Digest,
667 Op: Send + Sync + Clone + 'static,
668 {
669 type Family = F;
670 type Digest = D;
671 type Op = Op;
672 type Error = qmdb::Error<F>;
673
674 async fn serve(
675 &self,
676 _request: Request<F>,
677 ) -> Result<(Response<F, Op, D>, FeedbackTx), qmdb::Error<F>> {
678 Err(qmdb::Error::KeyNotFound) }
680 }
681
682 impl<F: Family, Op, D> FailSource<F, Op, D> {
683 pub fn new() -> Self {
684 Self {
685 _phantom: PhantomData,
686 }
687 }
688 }
689
690 #[test]
691 fn test_all_qmdb_variants_implement_source() {
692 type AnyOrderedFixed = crate::qmdb::any::ordered::fixed::Db<
693 mmr::Family,
694 deterministic::Context,
695 ShaDigest,
696 ShaDigest,
697 Sha256,
698 OneCap,
699 Rayon,
700 >;
701 type AnyOrderedVariable = crate::qmdb::any::ordered::variable::Db<
702 mmr::Family,
703 deterministic::Context,
704 ShaDigest,
705 Vec<u8>,
706 Sha256,
707 OneCap,
708 Rayon,
709 >;
710 type AnyUnorderedFixed = crate::qmdb::any::unordered::fixed::Db<
711 mmr::Family,
712 deterministic::Context,
713 ShaDigest,
714 ShaDigest,
715 Sha256,
716 TwoCap,
717 Rayon,
718 >;
719 type AnyUnorderedVariable = crate::qmdb::any::unordered::variable::Db<
720 mmr::Family,
721 deterministic::Context,
722 ShaDigest,
723 Vec<u8>,
724 Sha256,
725 TwoCap,
726 Rayon,
727 >;
728 type CurrentOrderedFixed = crate::qmdb::current::ordered::fixed::Db<
729 mmr::Family,
730 deterministic::Context,
731 ShaDigest,
732 ShaDigest,
733 Sha256,
734 OneCap,
735 32,
736 Rayon,
737 >;
738 type CurrentOrderedVariable = crate::qmdb::current::ordered::variable::Db<
739 mmr::Family,
740 deterministic::Context,
741 ShaDigest,
742 Vec<u8>,
743 Sha256,
744 OneCap,
745 32,
746 Rayon,
747 >;
748 type CurrentUnorderedFixed = crate::qmdb::current::unordered::fixed::Db<
749 mmr::Family,
750 deterministic::Context,
751 ShaDigest,
752 ShaDigest,
753 Sha256,
754 TwoCap,
755 32,
756 Rayon,
757 >;
758 type CurrentUnorderedVariable = crate::qmdb::current::unordered::variable::Db<
759 mmr::Family,
760 deterministic::Context,
761 ShaDigest,
762 Vec<u8>,
763 Sha256,
764 TwoCap,
765 32,
766 Rayon,
767 >;
768 type ImmutableFixed = crate::qmdb::immutable::fixed::Db<
769 mmr::Family,
770 deterministic::Context,
771 ShaDigest,
772 ShaDigest,
773 Sha256,
774 TwoCap,
775 Rayon,
776 >;
777 type ImmutableVariable = crate::qmdb::immutable::variable::Db<
778 mmr::Family,
779 deterministic::Context,
780 ShaDigest,
781 Vec<u8>,
782 Sha256,
783 TwoCap,
784 Rayon,
785 >;
786 type KeylessFixed = crate::qmdb::keyless::fixed::Db<
787 mmr::Family,
788 deterministic::Context,
789 ShaDigest,
790 Sha256,
791 Rayon,
792 >;
793 type KeylessVariable = crate::qmdb::keyless::variable::Db<
794 mmr::Family,
795 deterministic::Context,
796 Vec<u8>,
797 Sha256,
798 Rayon,
799 >;
800
801 assert_source_variants!(AnyOrderedFixed);
802 assert_source_variants!(AnyOrderedVariable);
803 assert_source_variants!(AnyUnorderedFixed);
804 assert_source_variants!(AnyUnorderedVariable);
805 assert_source_variants!(CurrentOrderedFixed);
806 assert_source_variants!(CurrentOrderedVariable);
807 assert_source_variants!(CurrentUnorderedFixed);
808 assert_source_variants!(CurrentUnorderedVariable);
809 assert_source_variants!(ImmutableFixed);
810 assert_source_variants!(ImmutableVariable);
811 assert_source_variants!(KeylessFixed);
812 assert_source_variants!(KeylessVariable);
813
814 type KeylessFixedCompactDb = crate::qmdb::keyless::fixed::CompactDb<
815 mmr::Family,
816 deterministic::Context,
817 ShaDigest,
818 Sha256,
819 Rayon,
820 >;
821 type KeylessVariableCompactDb = crate::qmdb::keyless::variable::CompactDb<
822 mmr::Family,
823 deterministic::Context,
824 Vec<u8>,
825 Sha256,
826 (commonware_codec::RangeCfg<usize>, ()),
827 Rayon,
828 >;
829 type ImmutableFixedCompactDb = crate::qmdb::immutable::fixed::CompactDb<
830 mmr::Family,
831 deterministic::Context,
832 ShaDigest,
833 ShaDigest,
834 Sha256,
835 Rayon,
836 >;
837 type ImmutableVariableCompactDb = crate::qmdb::immutable::variable::CompactDb<
838 mmr::Family,
839 deterministic::Context,
840 ShaDigest,
841 Vec<u8>,
842 Sha256,
843 ((), (commonware_codec::RangeCfg<usize>, ())),
844 Rayon,
845 >;
846
847 assert_source_variants!(KeylessFixedCompactDb);
848 assert_source_variants!(KeylessVariableCompactDb);
849 assert_source_variants!(ImmutableFixedCompactDb);
850 assert_source_variants!(ImmutableVariableCompactDb);
851 }
852
853 #[test]
855 fn test_request_decode_rejects_malformed() {
856 let valid = Request::<mmr::Family>::Operations {
857 size: Location::new(10),
858 start: Location::new(3),
859 max_ops: NZU64!(2),
860 };
861 let decoded = Request::<mmr::Family>::decode(valid.encode()).unwrap();
862 assert_eq!(decoded, valid);
863
864 let mut malformed = Vec::new();
865 1u8.write(&mut malformed); Location::<mmr::Family>::new(10).write(&mut malformed);
867 Location::<mmr::Family>::new(10).write(&mut malformed); assert!(Request::<mmr::Family>::decode(&malformed[..]).is_err());
869
870 let bad_tag = [7u8];
871 assert!(Request::<mmr::Family>::decode(&bad_tag[..]).is_err());
872 }
873
874 #[test]
877 fn test_request_identity() {
878 let operations = Request::<mmr::Family>::Operations {
879 size: Location::new(10),
880 start: Location::new(3),
881 max_ops: NZU64!(1),
882 };
883 let boundary = Request::<mmr::Family>::Boundary {
884 size: Location::new(10),
885 start: Location::new(3),
886 };
887 assert_eq!(boundary.max_ops(), NZU64!(1));
888 assert_ne!(operations, boundary);
889
890 let mut set = std::collections::BTreeSet::new();
891 assert!(set.insert(operations));
892 assert!(set.insert(boundary));
893 assert!(!set.insert(operations));
894 assert_eq!(set.len(), 2);
895
896 let smaller_size = Request::<mmr::Family>::Operations {
898 size: Location::new(9),
899 start: Location::new(8),
900 max_ops: NZU64!(5),
901 };
902 let smaller_start = Request::<mmr::Family>::Operations {
903 size: Location::new(10),
904 start: Location::new(2),
905 max_ops: NZU64!(5),
906 };
907 let fewer_ops = Request::<mmr::Family>::Operations {
908 size: Location::new(10),
909 start: Location::new(3),
910 max_ops: NZU64!(2),
911 };
912 let larger_ops = Request::<mmr::Family>::Operations {
913 size: Location::new(10),
914 start: Location::new(3),
915 max_ops: NZU64!(5),
916 };
917 assert!(smaller_size < smaller_start);
918 assert!(smaller_start < fewer_ops);
919 assert!(fewer_ops < larger_ops);
920 }
921
922 #[test]
924 fn test_response_decode_rejects_malformed() {
925 type R = Response<mmr::Family, u64, ShaDigest>;
926 let digest = ShaDigest::from([7u8; 32]);
927 let proof = Proof::<mmr::Family, ShaDigest> {
928 leaves: Location::new(3),
929 inactive_peaks: 0,
930 digests: vec![digest],
931 };
932
933 let response = R::Operations {
935 proof: proof.clone(),
936 operations: vec![1, 2, 3],
937 };
938 assert!(R::decode_cfg(response.encode(), &(3, ())).is_ok());
939 assert!(R::decode_cfg(response.encode(), &(2, ())).is_err());
940
941 let oversized = Proof::<mmr::Family, ShaDigest> {
943 leaves: Location::new(3),
944 inactive_peaks: 0,
945 digests: vec![digest; MAX_PROOF_DIGESTS_PER_ELEMENT + 1],
946 };
947 let response = R::Operations {
948 proof: oversized,
949 operations: vec![1],
950 };
951 assert!(R::decode_cfg(response.encode(), &(1, ())).is_err());
952
953 let response = R::Boundary {
955 proof: proof.clone(),
956 op: 1,
957 pinned_nodes: vec![digest; MAX_PINNED_NODES + 1],
958 };
959 assert!(R::decode_cfg(response.encode(), &(1, ())).is_err());
960 let response = R::Boundary {
961 proof,
962 op: 1,
963 pinned_nodes: vec![digest; MAX_PINNED_NODES],
964 };
965 assert!(R::decode_cfg(response.encode(), &(1, ())).is_ok());
966
967 assert!(R::decode_cfg(&[9u8][..], &(1, ())).is_err());
969 }
970
971 #[test]
973 fn test_locked_source_reaches_source() {
974 deterministic::Runner::default().start(|_context| async move {
975 let lock = AsyncRwLock::new(FailSource::<mmr::Family, u8, ShaDigest>::new());
976
977 let request = Request::Operations {
978 size: Location::new(1),
979 start: Location::new(0),
980 max_ops: NZU64!(1),
981 };
982 let result = lock.serve(request).await;
983 assert!(matches!(result, Err(crate::qmdb::Error::KeyNotFound)));
984 });
985 }
986}
987
988#[cfg(all(test, feature = "arbitrary"))]
989mod conformance {
990 use super::*;
991 use crate::merkle::{mmb, mmr};
992 use commonware_codec::conformance::CodecConformance;
993 use commonware_cryptography::sha256::Digest as Sha256Digest;
994
995 commonware_conformance::conformance_tests! {
996 CodecConformance<Request<mmr::Family>>,
997 CodecConformance<Request<mmb::Family>>,
998 CodecConformance<Response<mmr::Family, u64, Sha256Digest>>,
999 CodecConformance<Response<mmb::Family, u64, Sha256Digest>>,
1000 }
1001}