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