Skip to content

Commit 3d023b0

Browse files
authored
[Storage] Support fixed-length values in qmdb::immutable (#3461)
1 parent b93bfea commit 3d023b0

15 files changed

Lines changed: 2932 additions & 1445 deletions

File tree

examples/sync/src/databases/immutable.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use commonware_cryptography::{Hasher as CryptoHasher, Sha256};
55
use commonware_runtime::{BufferPooler, Clock, Metrics, Storage};
66
use commonware_storage::{
77
journal::contiguous::variable::Config as VConfig,
8-
mmr::{self, journaled::Config as MmrConfig, Location, Proof},
8+
merkle::{
9+
journaled::Config as MmrConfig,
10+
mmr::{self, Location, Proof},
11+
},
912
qmdb::{
1013
self,
1114
immutable::{self, Config},
@@ -16,13 +19,13 @@ use std::{future::Future, num::NonZeroU64};
1619
use tracing::error;
1720

1821
/// Database type alias.
19-
pub type Database<E> = immutable::Immutable<mmr::Family, E, Key, Value, Hasher, Translator>;
22+
pub type Database<E> = immutable::variable::Db<mmr::Family, E, Key, Value, Hasher, Translator>;
2023

2124
/// Operation type alias.
22-
pub type Operation = immutable::Operation<Key, Value>;
25+
pub type Operation = immutable::variable::Operation<Key, Value>;
2326

2427
/// Create a database configuration with appropriate partitioning for Immutable.
25-
pub fn create_config(context: &impl BufferPooler) -> Config<Translator, ((), ())> {
28+
pub fn create_config(context: &impl BufferPooler) -> Config<Translator, VConfig<((), ())>> {
2629
let page_cache = commonware_runtime::buffer::paged::CacheRef::from_pooler(
2730
context,
2831
NZU16!(2048),

storage/conformance.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,18 @@ hash = "f5456580eb69727a23bfb0281e33f467acc0c91273efebd334d3e82c9770ae76"
138138
n_cases = 65536
139139
hash = "a3fbb5f749fa5b73a684e9f0bebd8973c456e5ee43a0a3db75a99aa550dee302"
140140

141-
["commonware_storage::qmdb::immutable::operation::tests::conformance::CodecConformance<Operation<U64,U64>>"]
141+
["commonware_storage::qmdb::immutable::operation::fixed::tests::conformance::CodecConformance<FixedOp>"]
142142
n_cases = 65536
143-
hash = "fdca5df62d243b28676ee15034663694cd219d5ef80749079126b0ed73effe0d"
143+
hash = "4f75cdf8952431729e7a3dfad38a8d5bfbb92bf6b54b1ca180a66745aed618d5"
144144

145-
["commonware_storage::qmdb::immutable::operation::tests::conformance::CodecConformance<Operation<Vec<u8>,U64>>"]
145+
["commonware_storage::qmdb::immutable::operation::variable::tests::conformance::CodecConformance<VarKeyOp>"]
146146
n_cases = 65536
147147
hash = "cce5f888e506282f861e0e49e176b26c65f92e457f0c5f5353fc9b7196f07478"
148148

149+
["commonware_storage::qmdb::immutable::operation::variable::tests::conformance::CodecConformance<VarOp>"]
150+
n_cases = 65536
151+
hash = "fdca5df62d243b28676ee15034663694cd219d5ef80749079126b0ed73effe0d"
152+
149153
["commonware_storage::qmdb::keyless::operation::tests::conformance::CodecConformance<Operation<U64>>"]
150154
n_cases = 65536
151155
hash = "b41d6c6ec560bde9caf2e206526864c618a0721af367585a1719617ca7ce9291"

storage/fuzz/fuzz_targets/qmdb_immutable.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use commonware_storage::{
99
merkle::{hasher::Standard, mmb, mmr, Family as MerkleFamily, Location},
1010
mmr::journaled::Config as MerkleConfig,
1111
qmdb::{
12-
immutable::{Config, Immutable},
12+
immutable::{variable::Db as Immutable, Config},
1313
verify_proof,
1414
},
1515
translator::TwoCap,
@@ -92,10 +92,11 @@ fn generate_value(rng: &mut StdRng, size: usize) -> Vec<u8> {
9292
(0..actual_size).map(|_| rng.gen()).collect()
9393
}
9494

95+
#[allow(clippy::type_complexity)]
9596
fn db_config(
9697
suffix: &str,
9798
pooler: &impl BufferPooler,
98-
) -> Config<TwoCap, ((), (RangeCfg<usize>, ()))> {
99+
) -> Config<TwoCap, VConfig<((), (RangeCfg<usize>, ()))>> {
99100
let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, NZUsize!(PAGE_CACHE_SIZE));
100101
Config {
101102
merkle_config: MerkleConfig {

storage/src/qmdb/any/unordered/variable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,7 +660,7 @@ pub(crate) mod test {
660660
/// Regression test for https://github.com/commonwarexyz/monorepo/issues/2787
661661
#[allow(dead_code, clippy::manual_async_fn)]
662662
fn issue_2787_regression(
663-
db: &crate::qmdb::immutable::Immutable<
663+
db: &crate::qmdb::immutable::variable::Db<
664664
mmr::Family,
665665
deterministic::Context,
666666
Digest,

storage/src/qmdb/immutable/batch.rs

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
33
use super::Immutable;
44
use crate::{
5-
journal::authenticated,
5+
journal::{authenticated, contiguous::Mutable, Error as JournalError},
66
merkle::{Family, Location, Position},
7-
qmdb::{any::VariableValue, immutable::operation::Operation, operation::Key},
7+
qmdb::{any::ValueEncoding, immutable::operation::Operation, operation::Key, Error},
88
translator::Translator,
9-
Context,
9+
Context, Persistable,
1010
};
11+
use commonware_codec::EncodeShared;
1112
use commonware_cryptography::{Digest, Hasher as CHasher};
1213
use std::{collections::BTreeMap, sync::Arc};
1314

@@ -33,17 +34,17 @@ pub struct UnmerkleizedBatch<F, H, K, V>
3334
where
3435
F: Family,
3536
K: Key,
36-
V: VariableValue,
37+
V: ValueEncoding,
3738
H: CHasher,
3839
{
3940
/// Authenticated journal batch for computing the speculative Merkle root.
4041
journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<K, V>>,
4142

4243
/// Pending mutations.
43-
mutations: BTreeMap<K, V>,
44+
mutations: BTreeMap<K, V::Value>,
4445

4546
/// Uncommitted key-level changes accumulated by prior batches in the chain.
46-
base_diff: Arc<BTreeMap<K, DiffEntry<F, V>>>,
47+
base_diff: Arc<BTreeMap<K, DiffEntry<F, V::Value>>>,
4748

4849
/// Total operation count before this batch (committed DB + prior batches).
4950
/// This batch's i-th operation lands at location `base_size + i`.
@@ -55,12 +56,12 @@ where
5556

5657
/// A speculative batch of operations whose root digest has been computed,
5758
/// in contrast to [`UnmerkleizedBatch`].
58-
pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: VariableValue> {
59+
pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding> {
5960
/// Journal batch (Merkle state + accumulated operation segments).
6061
journal: authenticated::MerkleizedBatch<F, D, Operation<K, V>>,
6162

6263
/// All uncommitted key-level changes from the batch chain.
63-
diff: Arc<BTreeMap<K, DiffEntry<F, V>>>,
64+
diff: Arc<BTreeMap<K, DiffEntry<F, V::Value>>>,
6465

6566
/// Total operation count after this batch.
6667
total_size: u64,
@@ -70,7 +71,7 @@ pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: VariableValue> {
7071
}
7172

7273
/// An owned changeset that can be applied to the database.
73-
pub struct Changeset<F: Family, K: Key, D: Digest, V: VariableValue> {
74+
pub struct Changeset<F: Family, K: Key, D: Digest, V: ValueEncoding> {
7475
/// The finalized authenticated journal batch (Merkle changeset + item chain).
7576
pub(super) journal_finalized: authenticated::Changeset<F, D, Operation<K, V>>,
7677

@@ -88,13 +89,19 @@ impl<F, H, K, V> UnmerkleizedBatch<F, H, K, V>
8889
where
8990
F: Family,
9091
K: Key,
91-
V: VariableValue,
92+
V: ValueEncoding,
9293
H: CHasher,
94+
Operation<K, V>: EncodeShared,
9395
{
9496
/// Create a batch from a committed DB (no parent chain).
95-
pub(super) fn new<E, T>(immutable: &Immutable<F, E, K, V, H, T>, journal_size: u64) -> Self
97+
pub(super) fn new<E, C, T>(
98+
immutable: &Immutable<F, E, K, V, C, H, T>,
99+
journal_size: u64,
100+
) -> Self
96101
where
97102
E: Context,
103+
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
104+
C::Item: EncodeShared,
98105
T: Translator,
99106
{
100107
Self {
@@ -108,21 +115,23 @@ where
108115

109116
/// Set a key to a value.
110117
///
111-
/// Duplicate keys are not supported. The key must not already exist in the database or in any
112-
/// ancestor batch in the chain. Setting a key that already exists is undefined behavior.
113-
pub fn set(mut self, key: K, value: V) -> Self {
118+
/// The key must not already exist in the database or in any ancestor batch
119+
/// in the chain. Setting a key that already exists causes undefined behavior.
120+
pub fn set(mut self, key: K, value: V::Value) -> Self {
114121
self.mutations.insert(key, value);
115122
self
116123
}
117124

118125
/// Read through: mutations -> base diff -> committed DB.
119-
pub async fn get<E, T>(
126+
pub async fn get<E, C, T>(
120127
&self,
121128
key: &K,
122-
db: &Immutable<F, E, K, V, H, T>,
123-
) -> Result<Option<V>, crate::qmdb::Error<F>>
129+
db: &Immutable<F, E, K, V, C, H, T>,
130+
) -> Result<Option<V::Value>, Error<F>>
124131
where
125132
E: Context,
133+
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
134+
C::Item: EncodeShared,
126135
T: Translator,
127136
{
128137
// Check this batch's pending mutations.
@@ -138,12 +147,12 @@ where
138147
}
139148

140149
/// Resolve mutations into operations, merkleize, and return a [`MerkleizedBatch`].
141-
pub fn merkleize(self, metadata: Option<V>) -> MerkleizedBatch<F, H::Digest, K, V> {
150+
pub fn merkleize(self, metadata: Option<V::Value>) -> MerkleizedBatch<F, H::Digest, K, V> {
142151
let base = self.base_size;
143152

144153
// Build operations: one Set per key (BTreeMap iterates in sorted order), then Commit.
145154
let mut ops: Vec<Operation<K, V>> = Vec::with_capacity(self.mutations.len() + 1);
146-
let mut diff: BTreeMap<K, DiffEntry<F, V>> = BTreeMap::new();
155+
let mut diff: BTreeMap<K, DiffEntry<F, V::Value>> = BTreeMap::new();
147156

148157
for (key, value) in self.mutations {
149158
let loc = Location::new(base + ops.len() as u64);
@@ -179,20 +188,25 @@ where
179188
}
180189
}
181190

182-
impl<F: Family, D: Digest, K: Key, V: VariableValue> MerkleizedBatch<F, D, K, V> {
191+
impl<F: Family, D: Digest, K: Key, V: ValueEncoding> MerkleizedBatch<F, D, K, V>
192+
where
193+
Operation<K, V>: EncodeShared,
194+
{
183195
/// Return the speculative root.
184196
pub fn root(&self) -> D {
185197
self.journal.root()
186198
}
187199

188200
/// Read through: diff -> committed DB.
189-
pub async fn get<E, H, T>(
201+
pub async fn get<E, C, H, T>(
190202
&self,
191203
key: &K,
192-
db: &Immutable<F, E, K, V, H, T>,
193-
) -> Result<Option<V>, crate::qmdb::Error<F>>
204+
db: &Immutable<F, E, K, V, C, H, T>,
205+
) -> Result<Option<V::Value>, Error<F>>
194206
where
195207
E: Context,
208+
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
209+
C::Item: EncodeShared,
196210
H: CHasher<Digest = D>,
197211
T: Translator,
198212
{
@@ -277,12 +291,14 @@ impl<F: Family, D: Digest, K: Key, V: VariableValue> MerkleizedBatch<F, D, K, V>
277291
}
278292
}
279293

280-
impl<F, E, K, V, H, T> Immutable<F, E, K, V, H, T>
294+
impl<F, E, K, V, C, H, T> Immutable<F, E, K, V, C, H, T>
281295
where
282296
F: Family,
283297
E: Context,
284298
K: Key,
285-
V: VariableValue,
299+
V: ValueEncoding,
300+
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
301+
C::Item: EncodeShared,
286302
H: CHasher,
287303
T: Translator,
288304
{

0 commit comments

Comments
 (0)