Skip to main content

commonware_storage/qmdb/sync/
source.rs

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
20/// A request for operations from a source's log.
21pub enum Request<F: Family> {
22    /// Fetch the operations in `[start, start + max_ops)`.
23    Operations {
24        /// Prove against the root the database had at this size.
25        size: Location<F>,
26        /// First operation to return.
27        start: Location<F>,
28        /// Maximum number of operations to return.
29        max_ops: NonZeroU64,
30    },
31    /// Fetch the single operation at `start` plus the pinned nodes at `start`, the lowest
32    /// location the client will retain. The proof in the response authenticates the pinned nodes,
33    /// so there is no way to request them on their own.
34    Boundary {
35        /// Prove against the root the database had at this size.
36        size: Location<F>,
37        /// The operation to return, which is also the location of the returned pinned nodes.
38        start: Location<F>,
39    },
40}
41
42impl<F: Family> Request<F> {
43    /// The size whose root the response's proof must verify against.
44    pub const fn size(&self) -> Location<F> {
45        match self {
46            Self::Operations { size, .. } | Self::Boundary { size, .. } => *size,
47        }
48    }
49
50    /// First operation to return.
51    pub const fn start(&self) -> Location<F> {
52        match self {
53            Self::Operations { start, .. } | Self::Boundary { start, .. } => *start,
54        }
55    }
56
57    /// Maximum number of operations to return.
58    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    /// Total-order key for map lookups. The final component separates the variants.
66    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
224/// One authenticated response, shaped like the [`Request`] it answers.
225///
226/// In a [`Response::Boundary`], the proof, the operation, and the pinned nodes are verified as a
227/// unit. The pinned nodes are only believable because the proof folds them into digests it already
228/// commits to.
229pub enum Response<F: Family, Op, D: Digest> {
230    /// Answer to a [`Request::Operations`].
231    Operations {
232        /// Proof authenticating `operations` against the root at the requested size.
233        proof: Proof<F, D>,
234        /// The operations that were fetched.
235        operations: Vec<Op>,
236    },
237    /// Answer to a [`Request::Boundary`].
238    Boundary {
239        /// Proof authenticating `op` against the root at the requested size.
240        proof: Proof<F, D>,
241        /// The operation at the requested boundary.
242        op: Op,
243        /// Pinned nodes at the requested location.
244        pinned_nodes: Vec<D>,
245    },
246}
247
248impl<F: Family, Op, D: Digest> Response<F, Op, D> {
249    /// The proof authenticating this response.
250    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    /// The `max_ops` the request asked for, and the configuration for decoding one operation.
338    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
385/// Where to report whether a response verified.
386///
387/// After verifying a response, the sync engine sends `true` if it was valid and `false` if it
388/// was not, letting the [`Source`] provide feedback to whoever served it. `None` means the
389/// source accepts no feedback and its answer is final.
390pub type FeedbackTx = Option<oneshot::Sender<bool>>;
391
392/// A source for proofs and operations.
393pub trait Source: Send + Sync {
394    /// The merkle family backing this source's proofs.
395    type Family: Family;
396
397    /// The digest type used in this source's proofs.
398    type Digest: Digest;
399
400    /// The type of operations this source yields.
401    type Op;
402
403    /// Why this source could not answer.
404    type Error: std::error::Error + Send + 'static;
405
406    /// Serve a request.
407    #[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        // Reject before the floor lookup so the error carries the requested size and the
510        // floor read never touches out-of-range locations.
511        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    /// A feedback slot whose receiver is dropped. It marks a response as feedback-accepting,
600    /// so the engine retries instead of failing.
601    pub fn dropped_feedback() -> FeedbackTx {
602        let (tx, _rx) = oneshot::channel();
603        Some(tx)
604    }
605
606    /// A source that answers each request with the next scripted response.
607    #[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    /// Fetch `target`'s final commit operation and pinned nodes from `source`.
646    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    /// A source that always fails. Not `Clone`, which the engine must not require.
659    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) // Arbitrary dummy error
679        }
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    /// The request codec refuses frames whose start reaches their size, and unknown tags.
854    #[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); // Boundary tag
866        Location::<mmr::Family>::new(10).write(&mut malformed);
867        Location::<mmr::Family>::new(10).write(&mut malformed); // start == size
868        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    /// Requests are map keys, so equality and ordering must separate every distinct request.
875    /// A `Boundary` differs from a one-op `Operations` at the same coordinates only by variant.
876    #[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        // Ordering is by size, then start, then max_ops.
897        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    /// The response codec enforces the request-derived caps and rejects unknown tags.
923    #[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        // More operations than the request's max_ops.
934        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        // More proof digests than the request-derived budget.
942        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        // More pinned nodes than the codec allows.
954        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        // Unknown tag.
968        assert!(R::decode_cfg(&[9u8][..], &(1, ())).is_err());
969    }
970
971    /// A source behind a lock reaches the source and reports its error.
972    #[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}