Skip to main content

commonware_sync/databases/
immutable.rs

1//! Immutable database types and helpers for the sync example.
2
3use crate::{Hasher, Key, Translator, Value};
4use commonware_cryptography::{Hasher as CryptoHasher, Sha256};
5use commonware_runtime::{BufferPooler, Clock, Metrics, Storage};
6use commonware_storage::{
7    mmr::{Location, Proof},
8    qmdb::{
9        self,
10        immutable::{self, Config},
11        store::LogStore,
12    },
13};
14use commonware_utils::{NZUsize, NZU16, NZU64};
15use std::{future::Future, num::NonZeroU64};
16use tracing::error;
17
18/// Database type alias.
19pub type Database<E> = immutable::Immutable<E, Key, Value, Hasher, Translator>;
20
21/// Operation type alias.
22pub type Operation = immutable::Operation<Key, Value>;
23
24/// Create a database configuration with appropriate partitioning for Immutable.
25pub fn create_config(context: &impl BufferPooler) -> Config<Translator, ()> {
26    Config {
27        mmr_journal_partition: "mmr-journal".into(),
28        mmr_metadata_partition: "mmr-metadata".into(),
29        mmr_items_per_blob: NZU64!(4096),
30        mmr_write_buffer: NZUsize!(4096),
31        log_partition: "log".into(),
32        log_items_per_section: NZU64!(4096),
33        log_compression: None,
34        log_codec_config: (),
35        log_write_buffer: NZUsize!(4096),
36        translator: commonware_storage::translator::EightCap,
37        thread_pool: None,
38        page_cache: commonware_runtime::buffer::paged::CacheRef::from_pooler(
39            context,
40            NZU16!(2048),
41            NZUsize!(10),
42        ),
43    }
44}
45
46/// Create deterministic test operations for demonstration purposes.
47/// Generates Set operations and periodic Commit operations.
48pub fn create_test_operations(count: usize, seed: u64) -> Vec<Operation> {
49    let mut operations = Vec::new();
50    let mut hasher = <Hasher as CryptoHasher>::new();
51
52    for i in 0..count {
53        let key = {
54            hasher.update(&i.to_be_bytes());
55            hasher.update(&seed.to_be_bytes());
56            hasher.finalize()
57        };
58
59        let value = {
60            hasher.update(&key);
61            hasher.update(b"value");
62            hasher.finalize()
63        };
64
65        operations.push(Operation::Set(key, value));
66
67        if (i + 1) % 10 == 0 {
68            operations.push(Operation::Commit(None));
69        }
70    }
71
72    // Always end with a commit
73    operations.push(Operation::Commit(Some(Sha256::fill(1))));
74    operations
75}
76
77impl<E> super::Syncable for Database<E>
78where
79    E: Storage + Clock + Metrics,
80{
81    type Operation = Operation;
82
83    fn create_test_operations(count: usize, seed: u64) -> Vec<Self::Operation> {
84        create_test_operations(count, seed)
85    }
86
87    async fn add_operations(
88        &mut self,
89        operations: Vec<Self::Operation>,
90    ) -> Result<(), commonware_storage::qmdb::Error> {
91        if operations.last().is_none() || !operations.last().unwrap().is_commit() {
92            // Ignore bad inputs rather than return errors.
93            error!("operations must end with a commit");
94            return Ok(());
95        }
96
97        let mut batch = self.new_batch();
98        for operation in operations {
99            match operation {
100                Operation::Set(key, value) => {
101                    batch.set(key, value);
102                }
103                Operation::Commit(metadata) => {
104                    let finalized = batch.merkleize(metadata).finalize();
105                    self.apply_batch(finalized).await?;
106                    batch = self.new_batch();
107                }
108            }
109        }
110        Ok(())
111    }
112
113    fn root(&self) -> Key {
114        self.root()
115    }
116
117    async fn size(&self) -> Location {
118        LogStore::bounds(self).await.end
119    }
120
121    async fn inactivity_floor(&self) -> Location {
122        // For Immutable databases, all retained operations are active,
123        // so the inactivity floor equals the pruning boundary.
124        LogStore::bounds(self).await.start
125    }
126
127    fn historical_proof(
128        &self,
129        op_count: Location,
130        start_loc: Location,
131        max_ops: NonZeroU64,
132    ) -> impl Future<Output = Result<(Proof<Key>, Vec<Self::Operation>), qmdb::Error>> + Send {
133        self.historical_proof(op_count, start_loc, max_ops)
134    }
135
136    fn name() -> &'static str {
137        "immutable"
138    }
139}