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::{
6        self,
7        operation::Floored,
8        sync::{ServeError, source},
9    },
10};
11use bytes::BufMut;
12use commonware_codec::{
13    Buf, EncodeShared, EncodeSize, Error as CodecError, FixedSize, MaxEncodeSize, Read,
14    ReadExt as _, ReadRangeExt as _, Write, util::max_items_size,
15};
16use commonware_cryptography::{Digest, Hasher};
17use commonware_parallel::Strategy;
18use commonware_utils::{
19    Span,
20    channel::{mpsc, oneshot},
21    sync::{AsyncRwLock, TracedAsyncRwLock},
22};
23use std::{cmp::Ordering, future::Future, num::NonZeroU64, sync::Arc};
24
25/// A request for operations from a source's log.
26pub enum Request<F: Family> {
27    /// Fetch the operations in `[start, start + max_ops)`.
28    Operations {
29        /// Prove against the root the database had at this size.
30        size: Location<F>,
31        /// First operation to return.
32        start: Location<F>,
33        /// Maximum number of operations to return.
34        max_ops: NonZeroU64,
35    },
36    /// Fetch the single operation at `start` plus the pinned nodes at `start`, the lowest
37    /// location the client will retain. The proof in the response authenticates the pinned nodes,
38    /// so there is no way to request them on their own.
39    Boundary {
40        /// Prove against the root the database had at this size.
41        size: Location<F>,
42        /// The operation to return, which is also the location of the returned pinned nodes.
43        start: Location<F>,
44    },
45}
46
47impl<F: Family> Request<F> {
48    /// The size whose root the response's proof must verify against.
49    pub const fn size(&self) -> Location<F> {
50        match self {
51            Self::Operations { size, .. } | Self::Boundary { size, .. } => *size,
52        }
53    }
54
55    /// First operation to return.
56    pub const fn start(&self) -> Location<F> {
57        match self {
58            Self::Operations { start, .. } | Self::Boundary { start, .. } => *start,
59        }
60    }
61
62    /// Maximum number of operations to return.
63    pub const fn max_ops(&self) -> NonZeroU64 {
64        match self {
65            Self::Operations { max_ops, .. } => *max_ops,
66            Self::Boundary { .. } => NonZeroU64::MIN,
67        }
68    }
69
70    /// Total-order key for map lookups. The final component separates the variants.
71    fn order_key(&self) -> (u64, u64, u64, bool) {
72        (
73            *self.size(),
74            *self.start(),
75            self.max_ops().get(),
76            matches!(self, Self::Boundary { .. }),
77        )
78    }
79}
80
81impl<F: Family> Clone for Request<F> {
82    fn clone(&self) -> Self {
83        *self
84    }
85}
86
87impl<F: Family> Copy for Request<F> {}
88
89impl<F: Family> PartialEq for Request<F> {
90    fn eq(&self, other: &Self) -> bool {
91        self.order_key() == other.order_key()
92    }
93}
94
95impl<F: Family> Eq for Request<F> {}
96
97impl<F: Family> PartialOrd for Request<F> {
98    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
99        Some(self.cmp(other))
100    }
101}
102
103impl<F: Family> Ord for Request<F> {
104    fn cmp(&self, other: &Self) -> Ordering {
105        self.order_key().cmp(&other.order_key())
106    }
107}
108
109impl<F: Family> std::hash::Hash for Request<F> {
110    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
111        self.order_key().hash(state);
112    }
113}
114
115impl<F: Family> std::fmt::Debug for Request<F> {
116    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117        match self {
118            Self::Operations {
119                size,
120                start,
121                max_ops,
122            } => f
123                .debug_struct("Operations")
124                .field("size", size)
125                .field("start", start)
126                .field("max_ops", max_ops)
127                .finish(),
128            Self::Boundary { size, start } => f
129                .debug_struct("Boundary")
130                .field("size", size)
131                .field("start", start)
132                .finish(),
133        }
134    }
135}
136
137impl<F: Family> std::fmt::Display for Request<F> {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        match self {
140            Self::Operations {
141                size,
142                start,
143                max_ops,
144            } => write!(f, "Operations(size={size}, start={start}, max={max_ops})"),
145            Self::Boundary { size, start } => write!(f, "Boundary(size={size}, start={start})"),
146        }
147    }
148}
149
150impl<F: Family> Write for Request<F> {
151    fn write(&self, buf: &mut impl BufMut) {
152        match self {
153            Self::Operations {
154                size,
155                start,
156                max_ops,
157            } => {
158                0u8.write(buf);
159                size.write(buf);
160                start.write(buf);
161                max_ops.write(buf);
162            }
163            Self::Boundary { size, start } => {
164                1u8.write(buf);
165                size.write(buf);
166                start.write(buf);
167            }
168        }
169    }
170}
171
172impl<F: Family> EncodeSize for Request<F> {
173    fn encode_size(&self) -> usize {
174        1 + match self {
175            Self::Operations {
176                size,
177                start,
178                max_ops,
179            } => size.encode_size() + start.encode_size() + max_ops.encode_size(),
180            Self::Boundary { size, start } => size.encode_size() + start.encode_size(),
181        }
182    }
183}
184
185impl<F: Family> MaxEncodeSize for Request<F> {
186    // The widest variant is an operations request: a tag, two locations, and an operation count.
187    const MAX_ENCODE_SIZE: usize = u8::SIZE + 2 * Location::<F>::MAX_ENCODE_SIZE + NonZeroU64::SIZE;
188}
189
190impl<F: Family> Read for Request<F> {
191    type Cfg = ();
192
193    fn read_cfg(buf: &mut impl Buf, _: &()) -> std::result::Result<Self, CodecError> {
194        let request = match u8::read(buf)? {
195            0 => Self::Operations {
196                size: Location::<F>::read(buf)?,
197                start: Location::<F>::read(buf)?,
198                max_ops: NonZeroU64::read(buf)?,
199            },
200            1 => Self::Boundary {
201                size: Location::<F>::read(buf)?,
202                start: Location::<F>::read(buf)?,
203            },
204            d => return Err(CodecError::InvalidEnum(d)),
205        };
206        if request.start() >= request.size() {
207            return Err(CodecError::Invalid("Request", "start >= size"));
208        }
209        Ok(request)
210    }
211}
212
213impl<F: Family> Span for Request<F> {}
214
215#[cfg(feature = "arbitrary")]
216impl<F: Family> arbitrary::Arbitrary<'_> for Request<F> {
217    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
218        let size = u.int_in_range(1..=*F::MAX_LEAVES)?;
219        let start = u.int_in_range(0..=size - 1)?;
220        let size = Location::new(size);
221        let start = Location::new(start);
222        Ok(if u.arbitrary()? {
223            Self::Boundary { size, start }
224        } else {
225            Self::Operations {
226                size,
227                start,
228                max_ops: u.arbitrary()?,
229            }
230        })
231    }
232}
233
234/// One response, shaped like the [`Request`] it answers.
235///
236/// In a [`Response::Boundary`], the proof, the operation, and the pinned nodes are verified as a
237/// unit. The pinned nodes are only believable because the proof folds them into digests it already
238/// commits to.
239pub enum Response<F: Family, Op, D: Digest> {
240    /// Answer to a [`Request::Operations`].
241    Operations {
242        /// Proof authenticating `operations` against the root at the requested size.
243        proof: Proof<F, D>,
244        /// The operations that were fetched.
245        operations: Vec<Op>,
246    },
247    /// Answer to a [`Request::Boundary`].
248    Boundary {
249        /// Proof authenticating `op` against the root at the requested size.
250        proof: Proof<F, D>,
251        /// The operation at the requested boundary.
252        op: Op,
253        /// Pinned nodes at the requested location.
254        pinned_nodes: Vec<D>,
255    },
256}
257
258impl<F: Family, Op, D: Digest> Response<F, Op, D> {
259    /// The proof authenticating this response.
260    pub const fn proof(&self) -> &Proof<F, D> {
261        match self {
262            Self::Operations { proof, .. } | Self::Boundary { proof, .. } => proof,
263        }
264    }
265}
266
267impl<F: Family, Op: Clone, D: Digest> Clone for Response<F, Op, D> {
268    fn clone(&self) -> Self {
269        match self {
270            Self::Operations { proof, operations } => Self::Operations {
271                proof: proof.clone(),
272                operations: operations.clone(),
273            },
274            Self::Boundary {
275                proof,
276                op,
277                pinned_nodes,
278            } => Self::Boundary {
279                proof: proof.clone(),
280                op: op.clone(),
281                pinned_nodes: pinned_nodes.clone(),
282            },
283        }
284    }
285}
286
287impl<F: Family, Op: std::fmt::Debug, D: Digest> std::fmt::Debug for Response<F, Op, D> {
288    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289        match self {
290            Self::Operations { proof, operations } => f
291                .debug_struct("Operations")
292                .field("proof", proof)
293                .field("operations", operations)
294                .finish(),
295            Self::Boundary {
296                proof,
297                op,
298                pinned_nodes,
299            } => f
300                .debug_struct("Boundary")
301                .field("proof", proof)
302                .field("op", op)
303                .field("pinned_nodes", pinned_nodes)
304                .finish(),
305        }
306    }
307}
308
309impl<F: Family, Op: Write, D: Digest> Write for Response<F, Op, D> {
310    fn write(&self, buf: &mut impl BufMut) {
311        match self {
312            Self::Operations { proof, operations } => {
313                0u8.write(buf);
314                proof.write(buf);
315                operations.write(buf);
316            }
317            Self::Boundary {
318                proof,
319                op,
320                pinned_nodes,
321            } => {
322                1u8.write(buf);
323                proof.write(buf);
324                op.write(buf);
325                pinned_nodes.write(buf);
326            }
327        }
328    }
329}
330
331impl<F: Family, Op: EncodeSize, D: Digest> EncodeSize for Response<F, Op, D> {
332    fn encode_size(&self) -> usize {
333        1 + match self {
334            Self::Operations { proof, operations } => {
335                proof.encode_size() + operations.encode_size()
336            }
337            Self::Boundary {
338                proof,
339                op,
340                pinned_nodes,
341            } => proof.encode_size() + op.encode_size() + pinned_nodes.encode_size(),
342        }
343    }
344}
345
346/// Returns an upper bound on the encoded size of a [`Response`] that carries at most `max_ops`
347/// operations of at most `op` encoded bytes each.
348///
349/// An operations response carries a proof of at most [`MAX_PROOF_DIGESTS_PER_ELEMENT`] digests per
350/// operation. A boundary response carries a proof for one operation and at most
351/// [`MAX_PINNED_NODES`] pinned nodes.
352pub fn max_response_size<F: Family, D: Digest>(max_ops: usize, op: usize) -> usize {
353    let operations = Proof::<F, D>::max_encode_size(max_ops * MAX_PROOF_DIGESTS_PER_ELEMENT)
354        + max_items_size(max_ops, op);
355    let boundary = Proof::<F, D>::max_encode_size(MAX_PROOF_DIGESTS_PER_ELEMENT)
356        + op
357        + max_items_size(MAX_PINNED_NODES, D::SIZE);
358    operations.max(boundary) + u8::SIZE
359}
360
361impl<F: Family, Op: Read, D: Digest> Read for Response<F, Op, D> {
362    /// The `max_ops` the request asked for, and the configuration for decoding one operation.
363    type Cfg = (usize, Op::Cfg);
364
365    fn read_cfg(
366        buf: &mut impl Buf,
367        (max_ops, op_cfg): &Self::Cfg,
368    ) -> std::result::Result<Self, CodecError> {
369        match u8::read(buf)? {
370            0 => {
371                let max_proof_digests = max_ops.saturating_mul(MAX_PROOF_DIGESTS_PER_ELEMENT);
372                let proof = Proof::<F, D>::read_cfg(buf, &max_proof_digests)?;
373                let operations = Vec::<Op>::read_cfg(buf, &((..=*max_ops).into(), op_cfg.clone()))?;
374                Ok(Self::Operations { proof, operations })
375            }
376            1 => {
377                let proof = Proof::<F, D>::read_cfg(buf, &MAX_PROOF_DIGESTS_PER_ELEMENT)?;
378                let op = Op::read_cfg(buf, op_cfg)?;
379                let pinned_nodes = Vec::<D>::read_range(buf, ..=MAX_PINNED_NODES)?;
380                Ok(Self::Boundary {
381                    proof,
382                    op,
383                    pinned_nodes,
384                })
385            }
386            d => Err(CodecError::InvalidEnum(d)),
387        }
388    }
389}
390
391#[cfg(feature = "arbitrary")]
392impl<F: Family, Op, D: Digest> arbitrary::Arbitrary<'_> for Response<F, Op, D>
393where
394    Op: for<'a> arbitrary::Arbitrary<'a>,
395    D: for<'a> arbitrary::Arbitrary<'a>,
396{
397    fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
398        Ok(if u.arbitrary()? {
399            Self::Boundary {
400                proof: u.arbitrary()?,
401                op: u.arbitrary()?,
402                pinned_nodes: u.arbitrary()?,
403            }
404        } else {
405            Self::Operations {
406                proof: u.arbitrary()?,
407                operations: u.arbitrary()?,
408            }
409        })
410    }
411}
412
413/// Reports whether a response's proof is valid and retains the request for another candidate.
414///
415/// Feedback does not report whether the response was applied or persisted. Dropping it leaves the
416/// response unjudged and cancels the request.
417pub struct Feedback<R> {
418    sender: oneshot::Sender<bool>,
419    receiver: mpsc::Receiver<(R, oneshot::Sender<bool>)>,
420}
421
422impl<R> Feedback<R> {
423    /// Creates feedback for one response and its request's later candidates.
424    pub const fn new(
425        sender: oneshot::Sender<bool>,
426        receiver: mpsc::Receiver<(R, oneshot::Sender<bool>)>,
427    ) -> Self {
428        Self { sender, receiver }
429    }
430
431    /// Reports that the response's proof is valid and closes the request.
432    pub fn accept(self) {
433        let _ = self.sender.send(true);
434    }
435
436    /// Reports that the response's proof is invalid and waits for the next candidate.
437    pub async fn reject(self) -> Option<(R, Self)> {
438        let Self {
439            sender,
440            mut receiver,
441        } = self;
442        sender.send(false).ok()?;
443        let (response, sender) = receiver.recv().await?;
444        Some((response, Self { sender, receiver }))
445    }
446}
447
448/// The response type of a [`Source`].
449pub type ResponseOf<S> = Response<<S as Source>::Family, <S as Source>::Op, <S as Source>::Digest>;
450
451/// The result of [`Source::serve`].
452pub type Result<S> =
453    std::result::Result<(ResponseOf<S>, Option<Feedback<ResponseOf<S>>>), <S as Source>::Error>;
454
455/// A source for proofs and operations.
456pub trait Source: Send + Sync {
457    /// The merkle family backing this source's proofs.
458    type Family: Family;
459
460    /// The digest type used in this source's proofs.
461    type Digest: Digest;
462
463    /// The type of operations this source yields.
464    type Op;
465
466    /// Why this source could not answer.
467    type Error: std::error::Error + Send + 'static;
468
469    /// Serves a response with optional [`Feedback`] for reporting its validity.
470    ///
471    /// Dropping the future or feedback cancels the request without judging the response.
472    fn serve(
473        &self,
474        request: Request<Self::Family>,
475    ) -> impl Future<Output = source::Result<Self>> + Send;
476}
477
478impl<T> Source for Arc<T>
479where
480    T: Source + ?Sized,
481{
482    type Family = T::Family;
483    type Digest = T::Digest;
484    type Op = T::Op;
485    type Error = T::Error;
486
487    fn serve(
488        &self,
489        request: Request<Self::Family>,
490    ) -> impl Future<Output = source::Result<Self>> + Send {
491        T::serve(self, request)
492    }
493}
494
495impl<T> Source for Option<T>
496where
497    T: Source,
498    ServeError<T::Family>: From<T::Error>,
499{
500    type Family = T::Family;
501    type Digest = T::Digest;
502    type Op = T::Op;
503    type Error = ServeError<T::Family>;
504
505    async fn serve(&self, request: Request<Self::Family>) -> source::Result<Self> {
506        let source = self.as_ref().ok_or(ServeError::MissingSource)?;
507        Ok(source.serve(request).await?)
508    }
509}
510
511macro_rules! impl_locked_source {
512    ($lock:ident) => {
513        impl<T> Source for $lock<T>
514        where
515            T: Source,
516        {
517            type Family = T::Family;
518            type Digest = T::Digest;
519            type Op = T::Op;
520            type Error = T::Error;
521
522            async fn serve(&self, request: Request<Self::Family>) -> source::Result<Self> {
523                self.read().await.serve(request).await
524            }
525        }
526    };
527}
528
529impl_locked_source!(AsyncRwLock);
530impl_locked_source!(TracedAsyncRwLock);
531
532impl<F, E, C, H, S> Source for authenticated::Journal<F, E, C, H, S>
533where
534    F: Family,
535    E: Context,
536    C: Contiguous<Item: EncodeShared + Floored<F>>,
537    H: Hasher,
538    S: Strategy,
539{
540    type Family = F;
541    type Digest = H::Digest;
542    type Op = C::Item;
543    type Error = qmdb::Error<F>;
544
545    #[tracing::instrument(
546        name = "qmdb.sync.serve",
547        level = "info",
548        skip_all,
549        fields(
550            size = *request.size(),
551            start = *request.start(),
552            max_ops = request.max_ops().get(),
553        ),
554    )]
555    async fn serve(&self, request: Request<F>) -> source::Result<Self> {
556        // Reject before the floor lookup so the error carries the requested size and the
557        // floor read never touches out-of-range locations.
558        if request.size() > self.size() {
559            return Err(crate::merkle::Error::RangeOutOfBounds(request.size()).into());
560        }
561        let inactive_peaks = qmdb::inactive_peaks_at::<F, _>(self, request.size()).await?;
562        let response = match request {
563            Request::Operations {
564                size,
565                start,
566                max_ops,
567            } => {
568                let (proof, operations) = self
569                    .historical_proof(size, start, max_ops, inactive_peaks)
570                    .await?;
571                Response::Operations { proof, operations }
572            }
573            Request::Boundary { size, start } => {
574                let (proof, mut operations) = self
575                    .historical_proof(size, start, NonZeroU64::MIN, inactive_peaks)
576                    .await?;
577                let op = operations
578                    .pop()
579                    .ok_or(crate::merkle::Error::RangeOutOfBounds(start))?;
580                let pinned_nodes = self.merkle.pinned_nodes_at(start).await?;
581                Response::Boundary {
582                    proof,
583                    op,
584                    pinned_nodes,
585                }
586            }
587        };
588        Ok((response, None))
589    }
590}
591
592impl<F, E, C, I, H, U, const N: usize, S> Source
593    for crate::qmdb::any::db::Db<F, E, C, I, H, U, N, S>
594where
595    F: Family,
596    E: Context,
597    C: crate::journal::contiguous::Mutable<Item = crate::qmdb::any::operation::Operation<F, U>>,
598    I: crate::index::Unordered<Value = Location<F>>,
599    H: Hasher,
600    U: crate::qmdb::any::operation::update::Update,
601    S: Strategy,
602    crate::qmdb::any::operation::Operation<F, U>: commonware_codec::Codec,
603{
604    type Family = F;
605    type Digest = H::Digest;
606    type Op = crate::qmdb::any::operation::Operation<F, U>;
607    type Error = qmdb::Error<F>;
608
609    async fn serve(&self, request: Request<F>) -> source::Result<Self> {
610        self.log.serve(request).await
611    }
612}
613
614#[cfg(test)]
615pub(crate) mod tests {
616    use super::*;
617    use crate::{
618        merkle::mmr,
619        translator::{OneCap, TwoCap},
620    };
621    use commonware_codec::{Copying, Decode as _, DecodeExt as _, Encode as _};
622    use commonware_cryptography::{Sha256, sha256::Digest as ShaDigest};
623    use commonware_parallel::Rayon;
624    use commonware_runtime::{Runner as _, Spawner as _, Supervisor as _, deterministic};
625    use commonware_utils::{
626        NZU64,
627        sync::{AsyncRwLock, Mutex, TracedAsyncRwLock},
628    };
629    use std::{marker::PhantomData, mem, result::Result, sync::Arc};
630
631    macro_rules! assert_source_variants {
632        ($db:ty) => {
633            assert_serves::<Arc<$db>>();
634            assert_serves::<Arc<AsyncRwLock<$db>>>();
635            assert_serves::<Arc<AsyncRwLock<Option<$db>>>>();
636            assert_serves::<Arc<TracedAsyncRwLock<$db>>>();
637            assert_serves::<Arc<TracedAsyncRwLock<Option<$db>>>>();
638        };
639    }
640
641    fn assert_serves<S: Source>() {}
642
643    /// A source that offers a fixed response sequence for one request.
644    #[derive(Clone)]
645    pub struct SequenceSource<F: Family, Op, D: Digest> {
646        responses: Arc<Mutex<Vec<Response<F, Op, D>>>>,
647        verdicts: Arc<Mutex<Vec<oneshot::Receiver<bool>>>>,
648    }
649
650    impl<F: Family, Op, D: Digest> SequenceSource<F, Op, D> {
651        pub fn new(responses: Vec<Response<F, Op, D>>) -> Self {
652            Self {
653                responses: Arc::new(Mutex::new(responses)),
654                verdicts: Arc::new(Mutex::new(Vec::new())),
655            }
656        }
657
658        /// Collects the reported verdicts, omitting unjudged responses.
659        pub async fn take_verdicts(&self) -> Vec<bool> {
660            let receivers = mem::take(&mut *self.verdicts.lock());
661            let mut verdicts = Vec::with_capacity(receivers.len());
662            for receiver in receivers {
663                if let Ok(verdict) = receiver.await {
664                    verdicts.push(verdict);
665                }
666            }
667            verdicts
668        }
669    }
670
671    impl<F, Op, D> Source for SequenceSource<F, Op, D>
672    where
673        F: Family,
674        D: Digest,
675        Op: Send,
676    {
677        type Family = F;
678        type Digest = D;
679        type Op = Op;
680        type Error = qmdb::Error<F>;
681
682        async fn serve(&self, _request: Request<F>) -> source::Result<Self> {
683            let mut responses = mem::take(&mut *self.responses.lock()).into_iter();
684            let response = responses.next().ok_or(qmdb::Error::KeyNotFound)?;
685            let (candidate_tx, candidate_rx) = mpsc::channel(responses.len().max(1));
686            let (verdict_tx, verdict_rx) = oneshot::channel();
687            let mut verdicts = self.verdicts.lock();
688            verdicts.push(verdict_rx);
689            for response in responses {
690                let (verdict_tx, verdict_rx) = oneshot::channel();
691                assert!(candidate_tx.try_send((response, verdict_tx)).is_ok());
692                verdicts.push(verdict_rx);
693            }
694            Ok((response, Some(Feedback::new(verdict_tx, candidate_rx))))
695        }
696    }
697
698    async fn next_candidate<R>(
699        verdict_rx: oneshot::Receiver<bool>,
700        candidate_tx: mpsc::Sender<(R, oneshot::Sender<bool>)>,
701        response: R,
702    ) -> Option<bool> {
703        if verdict_rx.await.ok()? {
704            return Some(true);
705        }
706        let (verdict_tx, next_verdict_rx) = oneshot::channel();
707        candidate_tx.send((response, verdict_tx)).await.ok()?;
708        next_verdict_rx.await.ok()
709    }
710
711    #[test]
712    fn feedback_accepts_and_closes_request() {
713        deterministic::Runner::default().start(|_context| async move {
714            let (candidate_tx, candidate_rx) = mpsc::channel::<(u8, _)>(1);
715            let (verdict_tx, verdict_rx) = oneshot::channel();
716
717            Feedback::new(verdict_tx, candidate_rx).accept();
718
719            assert!(verdict_rx.await.unwrap());
720            let (next_verdict_tx, _) = oneshot::channel();
721            assert!(candidate_tx.send((2, next_verdict_tx)).await.is_err());
722        });
723    }
724
725    #[test]
726    fn feedback_rejects_and_returns_next_candidate() {
727        deterministic::Runner::default().start(|context| async move {
728            let (candidate_tx, candidate_rx) = mpsc::channel(1);
729            let (verdict_tx, verdict_rx) = oneshot::channel();
730            let driver = context
731                .child("driver")
732                .spawn(move |_| async move { next_candidate(verdict_rx, candidate_tx, 2u8).await });
733
734            let (response, feedback) = Feedback::new(verdict_tx, candidate_rx)
735                .reject()
736                .await
737                .unwrap();
738            assert_eq!(response, 2);
739            feedback.accept();
740            assert_eq!(driver.await.unwrap(), Some(true));
741        });
742    }
743
744    #[test]
745    fn dropping_feedback_cancels_request() {
746        deterministic::Runner::default().start(|_context| async move {
747            let (candidate_tx, candidate_rx) = mpsc::channel::<(u8, _)>(1);
748            let (verdict_tx, verdict_rx) = oneshot::channel();
749
750            drop(Feedback::new(verdict_tx, candidate_rx));
751
752            assert!(verdict_rx.await.is_err());
753            let (next_verdict_tx, _) = oneshot::channel();
754            assert!(candidate_tx.send((2, next_verdict_tx)).await.is_err());
755        });
756    }
757
758    #[test]
759    fn dropping_rejection_wait_cancels_request() {
760        deterministic::Runner::default().start(|_context| async move {
761            let (candidate_tx, candidate_rx) = mpsc::channel::<(u8, _)>(1);
762            let (verdict_tx, verdict_rx) = oneshot::channel();
763            let mut reject = Box::pin(Feedback::new(verdict_tx, candidate_rx).reject());
764
765            commonware_macros::select! {
766                _ = reject.as_mut() => panic!("rejection completed without another candidate"),
767                verdict = verdict_rx => assert_eq!(verdict.unwrap(), false),
768            }
769            drop(reject);
770
771            let (next_verdict_tx, _) = oneshot::channel();
772            assert!(candidate_tx.send((2, next_verdict_tx)).await.is_err());
773        });
774    }
775
776    #[test]
777    fn feedback_stops_when_verdict_receiver_is_gone() {
778        deterministic::Runner::default().start(|_context| async move {
779            let (_candidate_tx, candidate_rx) = mpsc::channel::<(u8, _)>(1);
780            let (verdict_tx, verdict_rx) = oneshot::channel();
781            drop(verdict_rx);
782
783            assert!(
784                Feedback::new(verdict_tx, candidate_rx)
785                    .reject()
786                    .await
787                    .is_none()
788            );
789        });
790    }
791
792    #[test]
793    fn feedback_stops_when_source_closes_candidate_stream() {
794        deterministic::Runner::default().start(|_context| async move {
795            let (candidate_tx, candidate_rx) = mpsc::channel::<(u8, _)>(1);
796            let (verdict_tx, verdict_rx) = oneshot::channel();
797            drop(candidate_tx);
798
799            assert!(
800                Feedback::new(verdict_tx, candidate_rx)
801                    .reject()
802                    .await
803                    .is_none()
804            );
805            assert!(!verdict_rx.await.unwrap());
806        });
807    }
808
809    /// Fetch `target`'s final commit operation and pinned nodes from `source`.
810    pub async fn fetch_compact_state<R: Source>(
811        source: &R,
812        target: crate::qmdb::sync::CompactTarget<R::Family, R::Digest>,
813    ) -> Result<Response<R::Family, R::Op, R::Digest>, R::Error> {
814        let (response, _feedback) = source
815            .serve(Request::Boundary {
816                size: target.size,
817                start: target.size - 1,
818            })
819            .await?;
820        Ok(response)
821    }
822
823    /// A source that always fails. Not `Clone`, which the engine must not require.
824    pub struct FailSource<F: Family, Op, D> {
825        _phantom: PhantomData<(F, Op, D)>,
826    }
827
828    impl<F, Op, D> Source for FailSource<F, Op, D>
829    where
830        F: Family,
831        D: Digest,
832        Op: Send + Sync,
833    {
834        type Family = F;
835        type Digest = D;
836        type Op = Op;
837        type Error = qmdb::Error<F>;
838
839        async fn serve(&self, _request: Request<F>) -> source::Result<Self> {
840            Err(qmdb::Error::KeyNotFound) // Arbitrary dummy error
841        }
842    }
843
844    impl<F: Family, Op, D> FailSource<F, Op, D> {
845        pub fn new() -> Self {
846            Self {
847                _phantom: PhantomData,
848            }
849        }
850    }
851
852    #[test]
853    fn test_all_qmdb_variants_implement_source() {
854        type AnyOrderedFixed = crate::qmdb::any::ordered::fixed::Db<
855            mmr::Family,
856            deterministic::Context,
857            ShaDigest,
858            ShaDigest,
859            Sha256,
860            OneCap,
861            Rayon,
862        >;
863        type AnyOrderedVariable = crate::qmdb::any::ordered::variable::Db<
864            mmr::Family,
865            deterministic::Context,
866            ShaDigest,
867            Vec<u8>,
868            Sha256,
869            OneCap,
870            Rayon,
871        >;
872        type AnyUnorderedFixed = crate::qmdb::any::unordered::fixed::Db<
873            mmr::Family,
874            deterministic::Context,
875            ShaDigest,
876            ShaDigest,
877            Sha256,
878            TwoCap,
879            Rayon,
880        >;
881        type AnyUnorderedVariable = crate::qmdb::any::unordered::variable::Db<
882            mmr::Family,
883            deterministic::Context,
884            ShaDigest,
885            Vec<u8>,
886            Sha256,
887            TwoCap,
888            Rayon,
889        >;
890        type CurrentOrderedFixed = crate::qmdb::current::ordered::fixed::Db<
891            mmr::Family,
892            deterministic::Context,
893            ShaDigest,
894            ShaDigest,
895            Sha256,
896            OneCap,
897            32,
898            Rayon,
899        >;
900        type CurrentOrderedVariable = crate::qmdb::current::ordered::variable::Db<
901            mmr::Family,
902            deterministic::Context,
903            ShaDigest,
904            Vec<u8>,
905            Sha256,
906            OneCap,
907            32,
908            Rayon,
909        >;
910        type CurrentUnorderedFixed = crate::qmdb::current::unordered::fixed::Db<
911            mmr::Family,
912            deterministic::Context,
913            ShaDigest,
914            ShaDigest,
915            Sha256,
916            TwoCap,
917            32,
918            Rayon,
919        >;
920        type CurrentUnorderedVariable = crate::qmdb::current::unordered::variable::Db<
921            mmr::Family,
922            deterministic::Context,
923            ShaDigest,
924            Vec<u8>,
925            Sha256,
926            TwoCap,
927            32,
928            Rayon,
929        >;
930        type ImmutableFixed = crate::qmdb::immutable::fixed::Db<
931            mmr::Family,
932            deterministic::Context,
933            ShaDigest,
934            ShaDigest,
935            Sha256,
936            TwoCap,
937            Rayon,
938        >;
939        type ImmutableVariable = crate::qmdb::immutable::variable::Db<
940            mmr::Family,
941            deterministic::Context,
942            ShaDigest,
943            Vec<u8>,
944            Sha256,
945            TwoCap,
946            Rayon,
947        >;
948        type KeylessFixed = crate::qmdb::keyless::fixed::Db<
949            mmr::Family,
950            deterministic::Context,
951            ShaDigest,
952            Sha256,
953            Rayon,
954        >;
955        type KeylessVariable = crate::qmdb::keyless::variable::Db<
956            mmr::Family,
957            deterministic::Context,
958            Vec<u8>,
959            Sha256,
960            Rayon,
961        >;
962
963        assert_source_variants!(AnyOrderedFixed);
964        assert_source_variants!(AnyOrderedVariable);
965        assert_source_variants!(AnyUnorderedFixed);
966        assert_source_variants!(AnyUnorderedVariable);
967        assert_source_variants!(CurrentOrderedFixed);
968        assert_source_variants!(CurrentOrderedVariable);
969        assert_source_variants!(CurrentUnorderedFixed);
970        assert_source_variants!(CurrentUnorderedVariable);
971        assert_source_variants!(ImmutableFixed);
972        assert_source_variants!(ImmutableVariable);
973        assert_source_variants!(KeylessFixed);
974        assert_source_variants!(KeylessVariable);
975
976        type KeylessFixedCompactDb = crate::qmdb::keyless::fixed::CompactDb<
977            mmr::Family,
978            deterministic::Context,
979            ShaDigest,
980            Sha256,
981            Rayon,
982        >;
983        type KeylessVariableCompactDb = crate::qmdb::keyless::variable::CompactDb<
984            mmr::Family,
985            deterministic::Context,
986            Vec<u8>,
987            Sha256,
988            Rayon,
989        >;
990        type ImmutableFixedCompactDb = crate::qmdb::immutable::fixed::CompactDb<
991            mmr::Family,
992            deterministic::Context,
993            ShaDigest,
994            ShaDigest,
995            Sha256,
996            Rayon,
997        >;
998        type ImmutableVariableCompactDb = crate::qmdb::immutable::variable::CompactDb<
999            mmr::Family,
1000            deterministic::Context,
1001            ShaDigest,
1002            Vec<u8>,
1003            Sha256,
1004            Rayon,
1005        >;
1006
1007        assert_source_variants!(KeylessFixedCompactDb);
1008        assert_source_variants!(KeylessVariableCompactDb);
1009        assert_source_variants!(ImmutableFixedCompactDb);
1010        assert_source_variants!(ImmutableVariableCompactDb);
1011    }
1012
1013    /// The request codec refuses frames whose start reaches their size, and unknown tags.
1014    #[test]
1015    fn test_request_decode_rejects_malformed() {
1016        let valid = Request::<mmr::Family>::Operations {
1017            size: Location::new(10),
1018            start: Location::new(3),
1019            max_ops: NZU64!(2),
1020        };
1021        let decoded = Request::<mmr::Family>::decode(valid.encode()).unwrap();
1022        assert_eq!(decoded, valid);
1023
1024        let mut malformed = Vec::new();
1025        1u8.write(&mut malformed); // Boundary tag
1026        Location::<mmr::Family>::new(10).write(&mut malformed);
1027        Location::<mmr::Family>::new(10).write(&mut malformed); // start == size
1028        assert!(Request::<mmr::Family>::decode(malformed).is_err());
1029
1030        let bad_tag = [7u8];
1031        assert!(Request::<mmr::Family>::decode(Copying(&bad_tag)).is_err());
1032    }
1033
1034    /// Requests are map keys, so equality and ordering must separate every distinct request.
1035    /// A `Boundary` differs from a one-op `Operations` at the same coordinates only by variant.
1036    #[test]
1037    fn test_request_identity() {
1038        let operations = Request::<mmr::Family>::Operations {
1039            size: Location::new(10),
1040            start: Location::new(3),
1041            max_ops: NZU64!(1),
1042        };
1043        let boundary = Request::<mmr::Family>::Boundary {
1044            size: Location::new(10),
1045            start: Location::new(3),
1046        };
1047        assert_eq!(boundary.max_ops(), NZU64!(1));
1048        assert_ne!(operations, boundary);
1049
1050        let mut set = std::collections::BTreeSet::new();
1051        assert!(set.insert(operations));
1052        assert!(set.insert(boundary));
1053        assert!(!set.insert(operations));
1054        assert_eq!(set.len(), 2);
1055
1056        // Ordering is by size, then start, then max_ops.
1057        let smaller_size = Request::<mmr::Family>::Operations {
1058            size: Location::new(9),
1059            start: Location::new(8),
1060            max_ops: NZU64!(5),
1061        };
1062        let smaller_start = Request::<mmr::Family>::Operations {
1063            size: Location::new(10),
1064            start: Location::new(2),
1065            max_ops: NZU64!(5),
1066        };
1067        let fewer_ops = Request::<mmr::Family>::Operations {
1068            size: Location::new(10),
1069            start: Location::new(3),
1070            max_ops: NZU64!(2),
1071        };
1072        let larger_ops = Request::<mmr::Family>::Operations {
1073            size: Location::new(10),
1074            start: Location::new(3),
1075            max_ops: NZU64!(5),
1076        };
1077        assert!(smaller_size < smaller_start);
1078        assert!(smaller_start < fewer_ops);
1079        assert!(fewer_ops < larger_ops);
1080    }
1081
1082    /// The response codec enforces the request-derived caps and rejects unknown tags.
1083    #[test]
1084    fn test_response_decode_rejects_malformed() {
1085        type R = Response<mmr::Family, u64, ShaDigest>;
1086        let digest = ShaDigest::from([7u8; 32]);
1087        let proof = Proof::<mmr::Family, ShaDigest> {
1088            leaves: Location::new(3),
1089            inactive_peaks: 0,
1090            digests: vec![digest],
1091        };
1092
1093        // More operations than the request's max_ops.
1094        let response = R::Operations {
1095            proof: proof.clone(),
1096            operations: vec![1, 2, 3],
1097        };
1098        assert!(R::decode_cfg(response.encode(), &(3, ())).is_ok());
1099        assert!(R::decode_cfg(response.encode(), &(2, ())).is_err());
1100
1101        // More proof digests than the request-derived budget.
1102        let oversized = Proof::<mmr::Family, ShaDigest> {
1103            leaves: Location::new(3),
1104            inactive_peaks: 0,
1105            digests: vec![digest; MAX_PROOF_DIGESTS_PER_ELEMENT + 1],
1106        };
1107        let response = R::Operations {
1108            proof: oversized,
1109            operations: vec![1],
1110        };
1111        assert!(R::decode_cfg(response.encode(), &(1, ())).is_err());
1112
1113        // More pinned nodes than the codec allows.
1114        let response = R::Boundary {
1115            proof: proof.clone(),
1116            op: 1,
1117            pinned_nodes: vec![digest; MAX_PINNED_NODES + 1],
1118        };
1119        assert!(R::decode_cfg(response.encode(), &(1, ())).is_err());
1120        let response = R::Boundary {
1121            proof,
1122            op: 1,
1123            pinned_nodes: vec![digest; MAX_PINNED_NODES],
1124        };
1125        assert!(R::decode_cfg(response.encode(), &(1, ())).is_ok());
1126
1127        // Unknown tag.
1128        assert!(R::decode_cfg(Copying(&[9u8]), &(1, ())).is_err());
1129    }
1130
1131    /// A source behind a lock reaches the source and reports its error.
1132    #[test]
1133    fn test_locked_source_reaches_source() {
1134        deterministic::Runner::default().start(|_context| async move {
1135            let lock = AsyncRwLock::new(FailSource::<mmr::Family, u8, ShaDigest>::new());
1136
1137            let request = Request::Operations {
1138                size: Location::new(1),
1139                start: Location::new(0),
1140                max_ops: NZU64!(1),
1141            };
1142            let result = lock.serve(request).await;
1143            assert!(matches!(result, Err(crate::qmdb::Error::KeyNotFound)));
1144        });
1145    }
1146
1147    #[test]
1148    fn fetch_compact_state_leaves_response_unjudged() {
1149        deterministic::Runner::default().start(|_context| async move {
1150            let size = Location::new(1);
1151            let response = Response::<mmr::Family, u64, ShaDigest>::Boundary {
1152                proof: Proof {
1153                    leaves: size,
1154                    inactive_peaks: 0,
1155                    digests: vec![],
1156                },
1157                op: 7,
1158                pinned_nodes: vec![],
1159            };
1160            let expected = response.encode();
1161            let source = SequenceSource::new(vec![response]);
1162            let target = crate::qmdb::sync::CompactTarget {
1163                root: ShaDigest::from([7u8; 32]),
1164                size,
1165            };
1166
1167            let response = fetch_compact_state(&source, target).await.unwrap();
1168            assert_eq!(response.encode(), expected);
1169            assert!(source.take_verdicts().await.is_empty());
1170        });
1171    }
1172
1173    #[test]
1174    fn sequence_source_offers_candidates_until_accepted() {
1175        deterministic::Runner::default().start(|_context| async move {
1176            let response = |op| Response::Operations {
1177                proof: Proof {
1178                    leaves: Location::new(1),
1179                    inactive_peaks: 0,
1180                    digests: vec![],
1181                },
1182                operations: vec![op],
1183            };
1184            let source = SequenceSource::<mmr::Family, _, ShaDigest>::new(vec![
1185                response(1),
1186                response(2),
1187                response(3),
1188            ]);
1189            let request = Request::Operations {
1190                size: Location::new(1),
1191                start: Location::new(0),
1192                max_ops: NZU64!(1),
1193            };
1194
1195            let locked = Arc::new(AsyncRwLock::new(Some(source.clone())));
1196            let (first, feedback) = locked.serve(request).await.unwrap();
1197            assert!(matches!(
1198                first,
1199                Response::Operations { operations, .. } if operations == [1]
1200            ));
1201            let (second, feedback) = feedback.unwrap().reject().await.unwrap();
1202            assert!(matches!(
1203                second,
1204                Response::Operations { operations, .. } if operations == [2]
1205            ));
1206            feedback.accept();
1207            assert_eq!(source.take_verdicts().await, vec![false, true]);
1208            assert!(matches!(
1209                source.serve(request).await,
1210                Err(qmdb::Error::KeyNotFound)
1211            ));
1212        });
1213    }
1214}
1215
1216#[cfg(all(test, feature = "arbitrary"))]
1217mod conformance {
1218    use super::*;
1219    use crate::merkle::{mmb, mmr};
1220    use commonware_codec::conformance::CodecConformance;
1221    use commonware_cryptography::sha256::Digest as Sha256Digest;
1222
1223    commonware_conformance::conformance_tests! {
1224        CodecConformance<Request<mmr::Family>>,
1225        CodecConformance<Request<mmb::Family>>,
1226        CodecConformance<Response<mmr::Family, u64, Sha256Digest>>,
1227        CodecConformance<Response<mmb::Family, u64, Sha256Digest>>,
1228    }
1229}