Skip to main content

commonware_sync/databases/
any.rs

1//! Any database types and helpers for the sync example.
2
3use crate::{Hasher, Key, Translator, Value};
4use commonware_cryptography::Hasher as CryptoHasher;
5use commonware_parallel::Sequential;
6use commonware_runtime::{buffer, BufferPooler};
7use commonware_storage::{
8    journal::contiguous::fixed::Config as FConfig,
9    mmr::{self, full::Config as MmrConfig, Location, Proof},
10    qmdb::{
11        self,
12        any::{
13            unordered::{
14                fixed::{Db, Operation as FixedOperation},
15                Update,
16            },
17            FixedConfig as Config,
18        },
19        operation::Committable,
20    },
21    Context,
22};
23use commonware_utils::{NZUsize, NZU16, NZU64};
24use std::{future::Future, num::NonZeroU64};
25use tracing::error;
26
27/// Database type alias.
28pub type Database<E> = Db<mmr::Family, E, Key, Value, Hasher, Translator, Sequential>;
29
30/// Operation type alias.
31pub type Operation = FixedOperation<mmr::Family, Key, Value>;
32
33/// Create a database configuration for use in tests.
34pub fn create_config(context: &impl BufferPooler) -> Config<Translator, Sequential> {
35    let page_cache = buffer::paged::CacheRef::from_pooler(context, NZU16!(2048), NZUsize!(10));
36    Config {
37        merkle_config: MmrConfig {
38            journal_partition: "mmr-journal".into(),
39            metadata_partition: "mmr-metadata".into(),
40            items_per_blob: NZU64!(4096),
41            write_buffer: NZUsize!(4096),
42            strategy: Sequential,
43            page_cache: page_cache.clone(),
44        },
45        journal_config: FConfig {
46            partition: "log-journal".into(),
47            items_per_blob: NZU64!(4096),
48            write_buffer: NZUsize!(4096),
49            page_cache,
50        },
51        translator: Translator::default(),
52        init_cache_size: Some(NZUsize!(1 << 16)),
53    }
54}
55
56impl<E> crate::databases::ExampleDatabase for Database<E>
57where
58    E: Context,
59{
60    type Family = mmr::Family;
61    type Operation = Operation;
62
63    fn create_test_operations(count: usize, seed: u64, _starting_loc: u64) -> Vec<Self::Operation> {
64        let mut hasher = <Hasher as CryptoHasher>::new();
65        let mut operations = Vec::new();
66        for i in 0..count {
67            let key = {
68                hasher.update(&i.to_be_bytes());
69                hasher.update(&seed.to_be_bytes());
70                hasher.finalize()
71            };
72
73            let value = {
74                hasher.update(&key);
75                hasher.update(b"value");
76                hasher.finalize()
77            };
78
79            operations.push(Operation::Update(Update(key, value)));
80
81            if (i + 1) % 10 == 0 {
82                operations.push(Operation::CommitFloor(None, Location::from(i + 1)));
83            }
84        }
85
86        // Always end with a commit
87        operations.push(Operation::CommitFloor(None, Location::from(count)));
88        operations
89    }
90
91    async fn add_operations(
92        &mut self,
93        operations: Vec<Self::Operation>,
94    ) -> Result<(), qmdb::Error<mmr::Family>> {
95        if operations.last().is_none() || !operations.last().unwrap().is_commit() {
96            // Ignore bad inputs rather than return errors.
97            error!("operations must end with a commit");
98            return Ok(());
99        }
100
101        let mut batch = self.new_batch();
102        for operation in operations {
103            match operation {
104                Operation::Update(Update(key, value)) => {
105                    batch = batch.write(key, Some(value));
106                }
107                Operation::Delete(key) => {
108                    batch = batch.write(key, None);
109                }
110                Operation::CommitFloor(metadata, _) => {
111                    let merkleized = batch.merkleize(self, metadata).await?;
112                    self.apply_batch(merkleized).await?;
113                    self.commit().await?;
114                    batch = self.new_batch();
115                }
116            }
117        }
118        Ok(())
119    }
120
121    fn current_floor(&self) -> u64 {
122        // `any`'s `merkleize` derives the floor internally; the `starting_loc` passed to
123        // `create_test_operations` is unused, so any value is safe.
124        0
125    }
126
127    fn root(&self) -> Key {
128        self.root()
129    }
130
131    fn name() -> &'static str {
132        "any"
133    }
134}
135
136impl<E> crate::databases::Syncable for Database<E>
137where
138    E: Context,
139{
140    fn size(&self) -> Location {
141        self.bounds().end
142    }
143
144    fn sync_boundary(&self) -> Location {
145        self.sync_boundary()
146    }
147
148    fn historical_proof(
149        &self,
150        op_count: Location,
151        start_loc: Location,
152        max_ops: NonZeroU64,
153    ) -> impl Future<Output = Result<(Proof<Key>, Vec<Self::Operation>), qmdb::Error<mmr::Family>>> + Send
154    {
155        self.historical_proof(op_count, start_loc, max_ops)
156    }
157
158    fn pinned_nodes_at(
159        &self,
160        loc: Location,
161    ) -> impl Future<Output = Result<Vec<Key>, qmdb::Error<mmr::Family>>> + Send {
162        self.pinned_nodes_at(loc)
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169    use crate::databases::ExampleDatabase;
170    use commonware_runtime::deterministic;
171
172    type AnyDb = Database<deterministic::Context>;
173
174    #[test]
175    fn test_create_test_operations() {
176        let ops = <AnyDb as ExampleDatabase>::create_test_operations(5, 12345, 0);
177        assert_eq!(ops.len(), 6); // 5 operations + 1 commit
178
179        if let Operation::CommitFloor(_, loc) = &ops[5] {
180            assert_eq!(*loc, 5);
181        } else {
182            panic!("Last operation should be a commit");
183        }
184    }
185
186    #[test]
187    fn test_deterministic_operations() {
188        // Operations should be deterministic based on seed
189        let ops1 = <AnyDb as ExampleDatabase>::create_test_operations(3, 12345, 0);
190        let ops2 = <AnyDb as ExampleDatabase>::create_test_operations(3, 12345, 0);
191        assert_eq!(ops1, ops2);
192
193        // Different seeds should produce different operations
194        let ops3 = <AnyDb as ExampleDatabase>::create_test_operations(3, 54321, 0);
195        assert_ne!(ops1, ops3);
196    }
197}