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