Skip to main content

commonware_storage/qmdb/current/ordered/
variable.rs

1//! An _ordered_ variant of a [crate::qmdb::current] authenticated database for variable-size values
2//!
3//! This variant maintains the lexicographic-next active key for each active key, enabling exclusion
4//! proofs (proving a key is currently inactive). Use [crate::qmdb::current::unordered::variable] if
5//! exclusion proofs are not needed.
6//!
7//! See [Db] for the main database type and [super::ExclusionProof] for proving key inactivity.
8
9pub use super::db::KeyValueProof;
10use crate::{
11    index::ordered::Index,
12    journal::contiguous::variable::Journal,
13    mmr::Location,
14    qmdb::{
15        any::{ordered::variable::Operation, value::VariableEncoding, VariableValue},
16        current::VariableConfig as Config,
17        operation::Key,
18        Error,
19    },
20    translator::Translator,
21};
22use commonware_codec::{Codec, Read};
23use commonware_cryptography::Hasher;
24use commonware_runtime::{Clock, Metrics, Storage as RStorage};
25
26pub type Db<E, K, V, H, T, const N: usize> =
27    super::db::Db<E, Journal<E, Operation<K, V>>, K, VariableEncoding<V>, Index<T, Location>, H, N>;
28
29impl<
30        E: RStorage + Clock + Metrics,
31        K: Key,
32        V: VariableValue,
33        H: Hasher,
34        T: Translator,
35        const N: usize,
36    > Db<E, K, V, H, T, N>
37where
38    Operation<K, V>: Codec,
39{
40    /// Initializes a [Db] from the given `config`. Leverages parallel Merkleization to initialize
41    /// the bitmap MMR if a thread pool is provided.
42    pub async fn init(
43        context: E,
44        config: Config<T, <Operation<K, V> as Read>::Cfg>,
45    ) -> Result<Self, Error> {
46        crate::qmdb::current::init_variable(context, config, |ctx, t| Index::new(ctx, t)).await
47    }
48}
49
50pub mod partitioned {
51    //! A variant of [super] that uses a partitioned index for the snapshot.
52
53    pub use super::KeyValueProof;
54    use crate::{
55        index::partitioned::ordered::Index,
56        journal::contiguous::variable::Journal,
57        mmr::Location,
58        qmdb::{
59            any::{
60                ordered::variable::partitioned::Operation, value::VariableEncoding, VariableValue,
61            },
62            current::VariableConfig as Config,
63            operation::Key,
64            Error,
65        },
66        translator::Translator,
67    };
68    use commonware_codec::{Codec, Read};
69    use commonware_cryptography::Hasher;
70    use commonware_runtime::{Clock, Metrics, Storage as RStorage};
71
72    /// A partitioned variant of [super::Db].
73    ///
74    /// The const generic `P` specifies the number of prefix bytes used for partitioning:
75    /// - `P = 1`: 256 partitions
76    /// - `P = 2`: 65,536 partitions
77    /// - `P = 3`: ~16 million partitions
78    pub type Db<E, K, V, H, T, const P: usize, const N: usize> =
79        crate::qmdb::current::ordered::db::Db<
80            E,
81            Journal<E, Operation<K, V>>,
82            K,
83            VariableEncoding<V>,
84            Index<T, Location, P>,
85            H,
86            N,
87        >;
88
89    impl<
90            E: RStorage + Clock + Metrics,
91            K: Key,
92            V: VariableValue,
93            H: Hasher,
94            T: Translator,
95            const P: usize,
96            const N: usize,
97        > Db<E, K, V, H, T, P, N>
98    where
99        Operation<K, V>: Codec,
100    {
101        /// Initializes a [Db] from the given `config`. Leverages parallel Merkleization to initialize
102        /// the bitmap MMR if a thread pool is provided.
103        pub async fn init(
104            context: E,
105            config: Config<T, <Operation<K, V> as Read>::Cfg>,
106        ) -> Result<Self, Error> {
107            crate::qmdb::current::init_variable(context, config, |ctx, t| Index::new(ctx, t)).await
108        }
109    }
110}
111
112#[cfg(test)]
113mod test {
114    use crate::{
115        kv::tests::{assert_gettable, assert_send},
116        mmr::{hasher::Hasher as _, Location, StandardHasher},
117        qmdb::{
118            any::ordered::variable::Operation,
119            current::{
120                ordered::{db::KeyValueProof, variable::Db},
121                proof::{OperationProof, RangeProof},
122                tests::{apply_random_ops, variable_config},
123            },
124            store::{
125                tests::{assert_log_store, assert_merkleized_store, assert_prunable_store},
126                LogStore,
127            },
128            Error,
129        },
130        translator::OneCap,
131    };
132    use commonware_cryptography::{sha256::Digest, Digest as _, Hasher as _, Sha256};
133    use commonware_macros::test_traced;
134    use commonware_runtime::{deterministic, Runner as _};
135    use commonware_utils::{bitmap::Prunable as BitMap, NZU64};
136    use rand::RngCore;
137
138    /// A type alias for the concrete [Db] type used in these unit tests.
139    type CurrentTest = Db<deterministic::Context, Digest, Digest, Sha256, OneCap, 32>;
140
141    /// Return a [Db] database initialized with a fixed config.
142    async fn open_db(context: deterministic::Context, partition_prefix: String) -> CurrentTest {
143        let cfg = variable_config::<OneCap>(&partition_prefix, &context);
144        CurrentTest::init(context, cfg).await.unwrap()
145    }
146
147    /// Build a tiny database and make sure we can't convince the verifier that some old value of a
148    /// key is active. We specifically test over the partial chunk case, since these bits are yet to
149    /// be committed to the underlying MMR.
150    #[test_traced("DEBUG")]
151    pub fn test_current_db_verify_proof_over_bits_in_uncommitted_chunk() {
152        let executor = deterministic::Runner::default();
153        executor.start(|context| async move {
154            let mut hasher = StandardHasher::<Sha256>::new();
155            let partition = "build-small".into();
156            let mut db = open_db(context, partition).await;
157
158            // Add one key.
159            let k = Sha256::fill(0x01);
160            let v1 = Sha256::fill(0xA1);
161            let finalized = {
162                let mut batch = db.new_batch();
163                batch.write(k, Some(v1));
164                batch.merkleize(None).await.unwrap().finalize()
165            };
166            db.apply_batch(finalized).await.unwrap();
167
168            let (_, op_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
169            let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
170
171            // Proof should be verifiable against current root.
172            let root = db.root();
173            assert!(CurrentTest::verify_key_value_proof(
174                hasher.inner(),
175                k,
176                v1,
177                &proof,
178                &root,
179            ));
180
181            let v2 = Sha256::fill(0xA2);
182            // Proof should not verify against a different value.
183            assert!(!CurrentTest::verify_key_value_proof(
184                hasher.inner(),
185                k,
186                v2,
187                &proof,
188                &root,
189            ));
190            // Proof should not verify against a mangled next_key.
191            let mut mangled_proof = proof.clone();
192            mangled_proof.next_key = Sha256::fill(0xFF);
193            assert!(!CurrentTest::verify_key_value_proof(
194                hasher.inner(),
195                k,
196                v1,
197                &mangled_proof,
198                &root,
199            ));
200
201            // Update the key to a new value (v2), which inactivates the previous operation.
202            let finalized = {
203                let mut batch = db.new_batch();
204                batch.write(k, Some(v2));
205                batch.merkleize(None).await.unwrap().finalize()
206            };
207            db.apply_batch(finalized).await.unwrap();
208            let root = db.root();
209
210            // New value should not be verifiable against the old proof.
211            assert!(!CurrentTest::verify_key_value_proof(
212                hasher.inner(),
213                k,
214                v2,
215                &proof,
216                &root,
217            ));
218
219            // But the new value should verify against a new proof.
220            let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
221            assert!(CurrentTest::verify_key_value_proof(
222                hasher.inner(),
223                k,
224                v2,
225                &proof,
226                &root,
227            ));
228            // Old value will not verify against new proof.
229            assert!(!CurrentTest::verify_key_value_proof(
230                hasher.inner(),
231                k,
232                v1,
233                &proof,
234                &root,
235            ));
236
237            // Create a proof of the now-inactive update operation assigning v1 to k against the
238            // current root.
239            let (p, _, chunks) = db
240                .range_proof(hasher.inner(), op_loc, NZU64!(1))
241                .await
242                .unwrap();
243            let proof_inactive = KeyValueProof {
244                proof: OperationProof {
245                    loc: op_loc,
246                    chunk: chunks[0],
247                    range_proof: p,
248                },
249                next_key: k,
250            };
251            // This proof should verify using verify_range_proof which does not check activity
252            // status.
253            let op = Operation::Update(crate::qmdb::any::ordered::Update {
254                key: k,
255                value: v1,
256                next_key: k,
257            });
258            assert!(CurrentTest::verify_range_proof(
259                hasher.inner(),
260                &proof_inactive.proof.range_proof,
261                proof_inactive.proof.loc,
262                &[op],
263                &[proof_inactive.proof.chunk],
264                &root,
265            ));
266            // But this proof should *not* verify as a key value proof, since verification will see
267            // that the operation is inactive.
268            assert!(!CurrentTest::verify_key_value_proof(
269                hasher.inner(),
270                k,
271                v1,
272                &proof_inactive,
273                &root,
274            ));
275
276            // Attempt #1 to "fool" the verifier:  change the location to that of an active
277            // operation. This should not fool the verifier if we're properly validating the
278            // inclusion of the operation itself, and not just the chunk.
279            let (_, active_loc) = db.any.get_with_loc(&k).await.unwrap().unwrap();
280            // The new location should differ but still be in the same chunk.
281            assert_ne!(active_loc, proof_inactive.proof.loc);
282            assert_eq!(
283                BitMap::<32>::to_chunk_index(*active_loc),
284                BitMap::<32>::to_chunk_index(*proof_inactive.proof.loc)
285            );
286            let mut fake_proof = proof_inactive.clone();
287            fake_proof.proof.loc = active_loc;
288            assert!(!CurrentTest::verify_key_value_proof(
289                hasher.inner(),
290                k,
291                v1,
292                &fake_proof,
293                &root,
294            ));
295
296            // Attempt #2 to "fool" the verifier: Modify the chunk in the proof info to make it look
297            // like the operation is active by flipping its corresponding bit to 1. This should not
298            // fool the verifier if we are correctly incorporating the partial chunk information
299            // into the root computation.
300            let mut modified_chunk = proof_inactive.proof.chunk;
301            let bit_pos = *proof_inactive.proof.loc;
302            let byte_idx = bit_pos / 8;
303            let bit_idx = bit_pos % 8;
304            modified_chunk[byte_idx as usize] |= 1 << bit_idx;
305
306            let mut fake_proof = proof_inactive.clone();
307            fake_proof.proof.chunk = modified_chunk;
308            assert!(!CurrentTest::verify_key_value_proof(
309                hasher.inner(),
310                k,
311                v1,
312                &fake_proof,
313                &root,
314            ));
315
316            db.destroy().await.unwrap();
317        });
318    }
319
320    #[test_traced("DEBUG")]
321    pub fn test_current_db_range_proofs() {
322        let executor = deterministic::Runner::default();
323        executor.start(|mut context| async move {
324            let partition = "range-proofs".into();
325            let mut hasher = StandardHasher::<Sha256>::new();
326            let db = open_db(context.clone(), partition).await;
327            let root = db.root();
328
329            // Empty range proof should not crash or verify, since even an empty db has a single
330            // commit op.
331            let proof = RangeProof {
332                proof: crate::mmr::Proof::default(),
333                partial_chunk_digest: None,
334                ops_root: Digest::EMPTY,
335            };
336            assert!(!CurrentTest::verify_range_proof(
337                hasher.inner(),
338                &proof,
339                Location::new(0),
340                &[],
341                &[],
342                &root,
343            ));
344
345            let rng_seed = context.next_u64();
346            let mut db = apply_random_ops::<CurrentTest>(200, true, rng_seed, db)
347                .await
348                .unwrap();
349            let finalized = db.new_batch().merkleize(None).await.unwrap().finalize();
350            db.apply_batch(finalized).await.unwrap();
351            let root = db.root();
352
353            // Make sure size-constrained batches of operations are provable from the oldest
354            // retained op to tip.
355            let max_ops = 4;
356            let end_loc = db.size().await;
357            let start_loc = db.any.inactivity_floor_loc();
358
359            for loc in *start_loc..*end_loc {
360                let loc = Location::new(loc);
361                let (proof, ops, chunks) = db
362                    .range_proof(hasher.inner(), loc, NZU64!(max_ops))
363                    .await
364                    .unwrap();
365                assert!(
366                    CurrentTest::verify_range_proof(
367                        hasher.inner(),
368                        &proof,
369                        loc,
370                        &ops,
371                        &chunks,
372                        &root
373                    ),
374                    "failed to verify range at start_loc {start_loc}",
375                );
376                // Proof should not verify if we include extra chunks.
377                let mut chunks_with_extra = chunks.clone();
378                chunks_with_extra.push(chunks[chunks.len() - 1]);
379                assert!(!CurrentTest::verify_range_proof(
380                    hasher.inner(),
381                    &proof,
382                    loc,
383                    &ops,
384                    &chunks_with_extra,
385                    &root,
386                ));
387            }
388
389            db.destroy().await.unwrap();
390        });
391    }
392
393    #[test_traced("DEBUG")]
394    pub fn test_current_db_key_value_proof() {
395        let executor = deterministic::Runner::default();
396        executor.start(|mut context| async move {
397            let partition = "range-proofs".to_string();
398            let mut hasher = StandardHasher::<Sha256>::new();
399            let db = open_db(context.clone(), partition.clone()).await;
400            let mut db = apply_random_ops::<CurrentTest>(500, true, context.next_u64(), db)
401                .await
402                .unwrap();
403            let finalized = db.new_batch().merkleize(None).await.unwrap().finalize();
404            db.apply_batch(finalized).await.unwrap();
405            let root = db.root();
406
407            // Confirm bad keys produce the expected error.
408            let bad_key = Sha256::fill(0xAA);
409            let res = db.key_value_proof(hasher.inner(), bad_key).await;
410            assert!(matches!(res, Err(Error::KeyNotFound)));
411
412            let start = *db.inactivity_floor_loc();
413            for i in start..db.status.len() {
414                if !db.status.get_bit(i) {
415                    continue;
416                }
417                // Found an active operation! Create a proof for its active current key/value if
418                // it's a key-updating operation.
419                let op = db.any.log.read(Location::new(i)).await.unwrap();
420                let (key, value) = match op {
421                    Operation::Update(key_data) => (key_data.key, key_data.value),
422                    Operation::CommitFloor(_, _) => continue,
423                    _ => unreachable!("expected update or commit floor operation"),
424                };
425                let proof = db.key_value_proof(hasher.inner(), key).await.unwrap();
426
427                // Proof should validate against the current value and correct root.
428                assert!(CurrentTest::verify_key_value_proof(
429                    hasher.inner(),
430                    key,
431                    value,
432                    &proof,
433                    &root
434                ));
435                // Proof should fail against the wrong value. Use hash instead of fill to ensure
436                // the value differs from any key/value created by TestKey::from_seed (which uses
437                // fill patterns).
438                let wrong_val = Sha256::hash(&[0xFF]);
439                assert!(!CurrentTest::verify_key_value_proof(
440                    hasher.inner(),
441                    key,
442                    wrong_val,
443                    &proof,
444                    &root
445                ));
446                // Proof should fail against the wrong key.
447                let wrong_key = Sha256::hash(&[0xEE]);
448                assert!(!CurrentTest::verify_key_value_proof(
449                    hasher.inner(),
450                    wrong_key,
451                    value,
452                    &proof,
453                    &root
454                ));
455                // Proof should fail against the wrong root.
456                let wrong_root = Sha256::hash(&[0xDD]);
457                assert!(!CurrentTest::verify_key_value_proof(
458                    hasher.inner(),
459                    key,
460                    value,
461                    &proof,
462                    &wrong_root,
463                ));
464                // Proof should fail with the wrong next-key.
465                let mut bad_proof = proof.clone();
466                bad_proof.next_key = wrong_key;
467                assert!(!CurrentTest::verify_key_value_proof(
468                    hasher.inner(),
469                    key,
470                    value,
471                    &bad_proof,
472                    &root,
473                ));
474            }
475
476            db.destroy().await.unwrap();
477        });
478    }
479
480    /// Repeatedly update the same key to a new value and ensure we can prove its current value
481    /// after each update.
482    #[test_traced("WARN")]
483    pub fn test_current_db_proving_repeated_updates() {
484        let executor = deterministic::Runner::default();
485        executor.start(|context| async move {
486            let mut hasher = StandardHasher::<Sha256>::new();
487            let partition = "build-small".into();
488            let mut db = open_db(context, partition).await;
489
490            // Add one key.
491            let k = Sha256::fill(0x00);
492            let mut old_val = Sha256::fill(0x00);
493            for i in 1u8..=255 {
494                let v = Sha256::fill(i);
495                let finalized = {
496                    let mut batch = db.new_batch();
497                    batch.write(k, Some(v));
498                    batch.merkleize(None).await.unwrap().finalize()
499                };
500                db.apply_batch(finalized).await.unwrap();
501                assert_eq!(db.get(&k).await.unwrap().unwrap(), v);
502                let root = db.root();
503
504                // Create a proof for the current value of k.
505                let proof = db.key_value_proof(hasher.inner(), k).await.unwrap();
506                assert!(
507                    CurrentTest::verify_key_value_proof(hasher.inner(), k, v, &proof, &root),
508                    "proof of update {i} failed to verify"
509                );
510                // Ensure the proof does NOT verify if we use the previous value.
511                assert!(
512                    !CurrentTest::verify_key_value_proof(hasher.inner(), k, old_val, &proof, &root),
513                    "proof of update {i} verified when it should not have"
514                );
515                old_val = v;
516            }
517
518            db.destroy().await.unwrap();
519        });
520    }
521
522    /// Build a tiny database and confirm exclusion proofs work as expected with variable values.
523    #[test_traced("DEBUG")]
524    pub fn test_current_db_exclusion_proofs() {
525        let executor = deterministic::Runner::default();
526        executor.start(|context| async move {
527            let mut hasher = StandardHasher::<Sha256>::new();
528            let partition = "exclusion-proofs".into();
529            let mut db = open_db(context, partition).await;
530
531            let key_exists_1 = Sha256::fill(0x10);
532
533            // We should be able to prove exclusion for any key against an empty db.
534            let empty_root = db.root();
535            let empty_proof = db
536                .exclusion_proof(hasher.inner(), &key_exists_1)
537                .await
538                .unwrap();
539            assert!(CurrentTest::verify_exclusion_proof(
540                hasher.inner(),
541                &key_exists_1,
542                &empty_proof,
543                &empty_root,
544            ));
545
546            // Add `key_exists_1` and test exclusion proving over the single-key database case.
547            let v1 = Sha256::fill(0xA1);
548            let finalized = {
549                let mut batch = db.new_batch();
550                batch.write(key_exists_1, Some(v1));
551                batch.merkleize(None).await.unwrap().finalize()
552            };
553            db.apply_batch(finalized).await.unwrap();
554            let root = db.root();
555
556            // We shouldn't be able to generate an exclusion proof for a key already in the db.
557            let result = db.exclusion_proof(hasher.inner(), &key_exists_1).await;
558            assert!(matches!(result, Err(Error::KeyExists)));
559
560            // Generate some valid exclusion proofs for keys on either side.
561            let greater_key = Sha256::fill(0xFF);
562            let lesser_key = Sha256::fill(0x00);
563            let proof = db
564                .exclusion_proof(hasher.inner(), &greater_key)
565                .await
566                .unwrap();
567            let proof2 = db
568                .exclusion_proof(hasher.inner(), &lesser_key)
569                .await
570                .unwrap();
571
572            // Since there's only one span in the DB, the two exclusion proofs should be identical,
573            // and the proof should verify any key but the one that exists in the db.
574            assert_eq!(proof, proof2);
575            // Any key except the one that exists should verify against this proof.
576            assert!(CurrentTest::verify_exclusion_proof(
577                hasher.inner(),
578                &greater_key,
579                &proof,
580                &root,
581            ));
582            assert!(CurrentTest::verify_exclusion_proof(
583                hasher.inner(),
584                &lesser_key,
585                &proof,
586                &root,
587            ));
588            // Exclusion should fail if we test it on a key that exists.
589            assert!(!CurrentTest::verify_exclusion_proof(
590                hasher.inner(),
591                &key_exists_1,
592                &proof,
593                &root,
594            ));
595
596            // Add a second key and test exclusion proving over the two-key database case.
597            let key_exists_2 = Sha256::fill(0x30);
598            let v2 = Sha256::fill(0xB2);
599
600            let finalized = {
601                let mut batch = db.new_batch();
602                batch.write(key_exists_2, Some(v2));
603                batch.merkleize(None).await.unwrap().finalize()
604            };
605            db.apply_batch(finalized).await.unwrap();
606            let root = db.root();
607
608            // Use a lesser/greater key that has a translated-key conflict based
609            // on our use of OneCap translator.
610            let lesser_key = Sha256::fill(0x0F); // < k1=0x10
611            let greater_key = Sha256::fill(0x31); // > k2=0x30
612            let middle_key = Sha256::fill(0x20); // between k1=0x10 and k2=0x30
613            let proof = db
614                .exclusion_proof(hasher.inner(), &greater_key)
615                .await
616                .unwrap();
617            // Test the "cycle around" span. This should prove exclusion of greater_key & lesser
618            // key, but fail on middle_key.
619            assert!(CurrentTest::verify_exclusion_proof(
620                hasher.inner(),
621                &greater_key,
622                &proof,
623                &root,
624            ));
625            assert!(CurrentTest::verify_exclusion_proof(
626                hasher.inner(),
627                &lesser_key,
628                &proof,
629                &root,
630            ));
631            assert!(!CurrentTest::verify_exclusion_proof(
632                hasher.inner(),
633                &middle_key,
634                &proof,
635                &root,
636            ));
637
638            // Due to the cycle, lesser & greater keys should produce the same proof.
639            let new_proof = db
640                .exclusion_proof(hasher.inner(), &lesser_key)
641                .await
642                .unwrap();
643            assert_eq!(proof, new_proof);
644
645            // Test the inner span [k, k2).
646            let proof = db
647                .exclusion_proof(hasher.inner(), &middle_key)
648                .await
649                .unwrap();
650            // `k` should fail since it's in the db.
651            assert!(!CurrentTest::verify_exclusion_proof(
652                hasher.inner(),
653                &key_exists_1,
654                &proof,
655                &root,
656            ));
657            // `middle_key` should succeed since it's in range.
658            assert!(CurrentTest::verify_exclusion_proof(
659                hasher.inner(),
660                &middle_key,
661                &proof,
662                &root,
663            ));
664            assert!(!CurrentTest::verify_exclusion_proof(
665                hasher.inner(),
666                &key_exists_2,
667                &proof,
668                &root,
669            ));
670
671            let conflicting_middle_key = Sha256::fill(0x11); // between k1=0x10 and k2=0x30
672            assert!(CurrentTest::verify_exclusion_proof(
673                hasher.inner(),
674                &conflicting_middle_key,
675                &proof,
676                &root,
677            ));
678
679            // Using lesser/greater keys for the middle-proof should fail.
680            assert!(!CurrentTest::verify_exclusion_proof(
681                hasher.inner(),
682                &greater_key,
683                &proof,
684                &root,
685            ));
686            assert!(!CurrentTest::verify_exclusion_proof(
687                hasher.inner(),
688                &lesser_key,
689                &proof,
690                &root,
691            ));
692
693            // Make the DB empty again by deleting the keys and check the empty case
694            // again.
695            let finalized = {
696                let mut batch = db.new_batch();
697                batch.write(key_exists_1, None);
698                batch.write(key_exists_2, None);
699                batch.merkleize(None).await.unwrap().finalize()
700            };
701            db.apply_batch(finalized).await.unwrap();
702            db.sync().await.unwrap();
703            let root = db.root();
704            // This root should be different than the empty root from earlier since the DB now has a
705            // non-zero number of operations.
706            assert!(db.is_empty());
707            assert_ne!(db.bounds().await.end, 0);
708            assert_ne!(root, empty_root);
709
710            let proof = db
711                .exclusion_proof(hasher.inner(), &key_exists_1)
712                .await
713                .unwrap();
714            assert!(CurrentTest::verify_exclusion_proof(
715                hasher.inner(),
716                &key_exists_1,
717                &proof,
718                &root,
719            ));
720            assert!(CurrentTest::verify_exclusion_proof(
721                hasher.inner(),
722                &key_exists_2,
723                &proof,
724                &root,
725            ));
726
727            // Try fooling the verifier with improper values.
728            assert!(!CurrentTest::verify_exclusion_proof(
729                hasher.inner(),
730                &key_exists_1,
731                &empty_proof, // wrong proof
732                &root,
733            ));
734            assert!(!CurrentTest::verify_exclusion_proof(
735                hasher.inner(),
736                &key_exists_1,
737                &proof,
738                &empty_root, // wrong root
739            ));
740        });
741    }
742
743    #[allow(dead_code)]
744    fn assert_db_futures_are_send(db: &mut CurrentTest, key: Digest, loc: Location) {
745        assert_gettable(db, &key);
746        assert_log_store(db);
747        assert_prunable_store(db, loc);
748        assert_merkleized_store(db, loc);
749        assert_send(db.sync());
750    }
751}