commonware_sync/databases/
keyless.rs1use crate::{Hasher, Key, Value};
9use commonware_cryptography::{Hasher as CryptoHasher, Sha256};
10use commonware_parallel::Sequential;
11use commonware_runtime::{buffer, BufferPooler, Clock, Metrics, Storage};
12use commonware_storage::{
13 journal::contiguous::fixed::Config as FConfig,
14 merkle::{
15 full::Config as MmrConfig,
16 mmr::{self, Location, Proof},
17 },
18 qmdb::{
19 self,
20 keyless::{self, fixed},
21 operation::Committable,
22 sync::compact,
23 },
24};
25use commonware_utils::{NZUsize, NZU16, NZU64};
26use std::num::NonZeroU64;
27use tracing::error;
28
29pub type Database<E> = fixed::Db<mmr::Family, E, Value, Hasher, Sequential>;
31
32pub type Operation = fixed::Operation<mmr::Family, Value>;
34
35pub fn create_config(context: &impl BufferPooler) -> fixed::Config<Sequential> {
37 let page_cache = buffer::paged::CacheRef::from_pooler(context, NZU16!(2048), NZUsize!(10));
38 keyless::Config {
39 merkle: MmrConfig {
40 journal_partition: "mmr-journal".into(),
41 metadata_partition: "mmr-metadata".into(),
42 items_per_blob: NZU64!(4096),
43 write_buffer: NZUsize!(4096),
44 strategy: Sequential,
45 page_cache: page_cache.clone(),
46 },
47 log: FConfig {
48 partition: "log-journal".into(),
49 items_per_blob: NZU64!(4096),
50 write_buffer: NZUsize!(4096),
51 page_cache,
52 },
53 }
54}
55
56pub fn create_test_operations(count: usize, seed: u64, starting_loc: u64) -> Vec<Operation> {
62 let mut operations = Vec::new();
63 let mut hasher = <Hasher as CryptoHasher>::new();
64 let floor = Location::new(starting_loc);
65
66 for i in 0..count {
67 let value = {
68 hasher.update(&i.to_be_bytes());
69 hasher.update(&seed.to_be_bytes());
70 hasher.finalize()
71 };
72
73 operations.push(Operation::Append(value));
74
75 if (i + 1) % 10 == 0 {
76 operations.push(Operation::Commit(None, floor));
77 }
78 }
79
80 operations.push(Operation::Commit(Some(Sha256::fill(1)), floor));
82 operations
83}
84
85impl<E> super::ExampleDatabase for Database<E>
86where
87 E: Storage + Clock + Metrics,
88{
89 type Family = mmr::Family;
90 type Operation = Operation;
91
92 fn create_test_operations(count: usize, seed: u64, starting_loc: u64) -> Vec<Self::Operation> {
93 create_test_operations(count, seed, starting_loc)
94 }
95
96 async fn add_operations(
97 &mut self,
98 operations: Vec<Self::Operation>,
99 ) -> Result<(), qmdb::Error<mmr::Family>> {
100 if operations.last().is_none() || !operations.last().unwrap().is_commit() {
101 error!("operations must end with a commit");
103 return Ok(());
104 }
105
106 let mut batch = self.new_batch();
107 for operation in operations {
108 match operation {
109 Operation::Append(value) => {
110 batch = batch.append(value);
111 }
112 Operation::Commit(metadata, floor) => {
113 let merkleized = batch.merkleize(self, metadata, floor);
114 self.apply_batch(merkleized).await?;
115 self.commit().await?;
116 batch = self.new_batch();
117 }
118 }
119 }
120 Ok(())
121 }
122
123 fn current_floor(&self) -> u64 {
124 *self.last_commit_loc()
125 }
126
127 fn root(&self) -> Key {
128 self.root()
129 }
130
131 fn name() -> &'static str {
132 "keyless"
133 }
134}
135
136impl<E> super::Syncable for Database<E>
137where
138 E: Storage + Clock + Metrics,
139{
140 async fn size(&self) -> Location {
141 self.bounds().await.end
142 }
143
144 async fn sync_boundary(&self) -> Location {
145 self.sync_boundary()
146 }
147
148 async fn historical_proof(
149 &self,
150 op_count: Location,
151 start_loc: Location,
152 max_ops: NonZeroU64,
153 ) -> Result<(Proof<Key>, Vec<Self::Operation>), qmdb::Error<mmr::Family>> {
154 self.historical_proof(op_count, start_loc, max_ops).await
155 }
156
157 async fn pinned_nodes_at(&self, loc: Location) -> Result<Vec<Key>, qmdb::Error<mmr::Family>> {
158 self.pinned_nodes_at(loc).await
159 }
160}
161
162impl<E> super::CompactSyncable for Database<E>
163where
164 E: Storage + Clock + Metrics,
165{
166 async fn current_target(&self) -> compact::Target<Self::Family, Key> {
167 compact::Target::new(self.root(), self.bounds().await.end)
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use crate::databases::ExampleDatabase;
175 use commonware_runtime::deterministic;
176
177 type KeylessDb = Database<deterministic::Context>;
178
179 #[test]
180 fn test_create_test_operations() {
181 let ops = <KeylessDb as ExampleDatabase>::create_test_operations(5, 12345, 0);
182 assert_eq!(ops.len(), 6); if let Operation::Commit(Some(_), _) = &ops[5] {
185 } else {
187 panic!("last operation should be a commit with metadata");
188 }
189 }
190
191 #[test]
192 fn test_deterministic_operations() {
193 let ops1 = <KeylessDb as ExampleDatabase>::create_test_operations(3, 12345, 0);
195 let ops2 = <KeylessDb as ExampleDatabase>::create_test_operations(3, 12345, 0);
196 assert_eq!(ops1, ops2);
197
198 let ops3 = <KeylessDb as ExampleDatabase>::create_test_operations(3, 54321, 0);
200 assert_ne!(ops1, ops3);
201 }
202}