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