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