Skip to content

Commit ed7df57

Browse files
committed
add inactivity floor to qmdb::immutable Commit operation
1 parent 2cff773 commit ed7df57

13 files changed

Lines changed: 1368 additions & 351 deletions

File tree

examples/sync/src/databases/immutable.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use tracing::error;
2222
pub type Database<E> = immutable::variable::Db<mmr::Family, E, Key, Value, Hasher, Translator>;
2323

2424
/// Operation type alias.
25-
pub type Operation = immutable::variable::Operation<Key, Value>;
25+
pub type Operation = immutable::variable::Operation<mmr::Family, Key, Value>;
2626

2727
/// Create a database configuration with appropriate partitioning for Immutable.
2828
pub fn create_config(context: &impl BufferPooler) -> Config<Translator, VConfig<((), ())>> {
@@ -74,12 +74,12 @@ pub fn create_test_operations(count: usize, seed: u64) -> Vec<Operation> {
7474
operations.push(Operation::Set(key, value));
7575

7676
if (i + 1) % 10 == 0 {
77-
operations.push(Operation::Commit(None));
77+
operations.push(Operation::Commit(None, Location::new(0)));
7878
}
7979
}
8080

8181
// Always end with a commit
82-
operations.push(Operation::Commit(Some(Sha256::fill(1))));
82+
operations.push(Operation::Commit(Some(Sha256::fill(1)), Location::new(0)));
8383
operations
8484
}
8585

@@ -110,8 +110,8 @@ where
110110
Operation::Set(key, value) => {
111111
batch = batch.set(key, value);
112112
}
113-
Operation::Commit(metadata) => {
114-
let merkleized = batch.merkleize(self, metadata);
113+
Operation::Commit(metadata, floor) => {
114+
let merkleized = batch.merkleize(self, metadata, floor);
115115
self.apply_batch(merkleized).await?;
116116
self.commit().await?;
117117
batch = self.new_batch();
@@ -130,9 +130,7 @@ where
130130
}
131131

132132
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
133+
self.inactivity_floor_loc()
136134
}
137135

138136
fn historical_proof(

storage/conformance.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,15 @@ hash = "a3fbb5f749fa5b73a684e9f0bebd8973c456e5ee43a0a3db75a99aa550dee302"
140140

141141
["commonware_storage::qmdb::immutable::operation::fixed::tests::conformance::CodecConformance<FixedOp>"]
142142
n_cases = 65536
143-
hash = "4f75cdf8952431729e7a3dfad38a8d5bfbb92bf6b54b1ca180a66745aed618d5"
143+
hash = "1737c49c112fc9dfdc4dde3626d8ab08a9df8353b9a702e488d3a1fd8e9428dc"
144144

145145
["commonware_storage::qmdb::immutable::operation::variable::tests::conformance::CodecConformance<VarKeyOp>"]
146146
n_cases = 65536
147-
hash = "cce5f888e506282f861e0e49e176b26c65f92e457f0c5f5353fc9b7196f07478"
147+
hash = "bdee433c53489ee67a42ad2029081d3f233799bd360d6e5e7f29cbaec87c9064"
148148

149149
["commonware_storage::qmdb::immutable::operation::variable::tests::conformance::CodecConformance<VarOp>"]
150150
n_cases = 65536
151-
hash = "fdca5df62d243b28676ee15034663694cd219d5ef80749079126b0ed73effe0d"
151+
hash = "7952a9bb3a9cec87a95af6dd96a5b88b535106f0241770e0bfdb8aeaf9421056"
152152

153153
["commonware_storage::qmdb::keyless::operation::tests::conformance::CodecConformance<Operation<FixedEncoding<U64>>>"]
154154
n_cases = 65536

storage/fuzz/fuzz_targets/qmdb_immutable.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ enum ImmutableOperation {
4040
Commit {
4141
has_metadata: bool,
4242
metadata_size: usize,
43+
advance_floor: bool,
4344
},
4445
Prune {
4546
loc: u64,
@@ -179,24 +180,35 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
179180
ImmutableOperation::Commit {
180181
has_metadata,
181182
metadata_size,
183+
advance_floor,
182184
} => {
183185
let metadata = if has_metadata {
184186
Some(generate_value(&mut rng, metadata_size))
185187
} else {
186188
None
187189
};
188190

191+
let end = db.bounds().await.end;
192+
let pending_count = pending_sets.len() as u64;
189193
assign_pending_locations(
190194
&pending_sets,
191-
db.bounds().await.end,
195+
end,
192196
&mut keys_set,
193197
&mut set_locations,
194198
);
195199
let mut batch = db.new_batch();
196200
for (k, v) in pending_sets.drain(..) {
197201
batch = batch.set(k, v);
198202
}
199-
let merkleized = batch.merkleize(&db, metadata);
203+
let floor = if advance_floor {
204+
// Advance floor to the commit location (end of this batch).
205+
// total_size = end + pending_count + 1 (commit op).
206+
// Floor at the commit op is the maximum valid value.
207+
Location::new(*end + pending_count)
208+
} else {
209+
db.inactivity_floor_loc()
210+
};
211+
let merkleized = batch.merkleize(&db, metadata, floor);
200212
db.apply_batch(merkleized).await.unwrap();
201213
db.commit().await.unwrap();
202214
last_commit_loc = Some(db.bounds().await.end - 1);
@@ -216,7 +228,10 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
216228
for (k, v) in pending_sets.drain(..) {
217229
batch = batch.set(k, v);
218230
}
219-
let merkleized = batch.merkleize(&db, None);
231+
// Set the floor to at least safe_loc so the prune succeeds,
232+
// but never below the current floor (monotonicity).
233+
let floor = safe_loc.max(db.inactivity_floor_loc());
234+
let merkleized = batch.merkleize(&db, None, floor);
220235
db.apply_batch(merkleized).await.unwrap();
221236
db.commit().await.unwrap();
222237
last_commit_loc = Some(db.bounds().await.end - 1);
@@ -247,7 +262,8 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
247262
for (k, v) in pending_sets.drain(..) {
248263
batch = batch.set(k, v);
249264
}
250-
let merkleized = batch.merkleize(&db, None);
265+
let floor = db.inactivity_floor_loc();
266+
let merkleized = batch.merkleize(&db, None, floor);
251267
db.apply_batch(merkleized).await.unwrap();
252268
db.commit().await.unwrap();
253269
last_commit_loc = Some(db.bounds().await.end - 1);
@@ -272,7 +288,8 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
272288
let safe_max_ops =
273289
NonZeroU64::new((max_ops % MAX_PROOF_OPS).max(1)).unwrap();
274290

275-
let batch = db.new_batch().merkleize(&db, None);
291+
let floor = db.inactivity_floor_loc();
292+
let batch = db.new_batch().merkleize(&db, None, floor);
276293
db.apply_batch(batch).await.unwrap();
277294
db.commit().await.unwrap();
278295
last_commit_loc = Some(db.bounds().await.end - 1);
@@ -307,7 +324,8 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
307324
for (k, v) in pending_sets.drain(..) {
308325
batch = batch.set(k, v);
309326
}
310-
let merkleized = batch.merkleize(&db, None);
327+
let floor = db.inactivity_floor_loc();
328+
let merkleized = batch.merkleize(&db, None, floor);
311329
db.apply_batch(merkleized).await.unwrap();
312330
db.commit().await.unwrap();
313331
last_commit_loc = Some(db.bounds().await.end - 1);
@@ -326,7 +344,8 @@ fn fuzz_family<F: MerkleFamily>(input: &FuzzInput, suffix: &str) {
326344
for (k, v) in pending_sets.drain(..) {
327345
batch = batch.set(k, v);
328346
}
329-
let merkleized = batch.merkleize(&db, None);
347+
let floor = db.inactivity_floor_loc();
348+
let merkleized = batch.merkleize(&db, None, floor);
330349
db.apply_batch(merkleized).await.unwrap();
331350
db.destroy().await.unwrap();
332351
}

storage/src/qmdb/immutable/batch.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ where
4444
H: CHasher,
4545
{
4646
/// Authenticated journal batch for computing the speculative Merkle root.
47-
journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<K, V>>,
47+
journal_batch: authenticated::UnmerkleizedBatch<F, H, Operation<F, K, V>>,
4848

4949
/// Pending mutations.
5050
mutations: BTreeMap<K, V::Value>,
@@ -65,7 +65,7 @@ where
6565
#[derive(Clone)]
6666
pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding> {
6767
/// Authenticated journal batch (Merkle state + local items).
68-
pub(super) journal_batch: Arc<authenticated::MerkleizedBatch<F, D, Operation<K, V>>>,
68+
pub(super) journal_batch: Arc<authenticated::MerkleizedBatch<F, D, Operation<F, K, V>>>,
6969

7070
/// This batch's local key-level changes only (not accumulated from ancestors).
7171
/// Sorted by key with no duplicates; queried via `lookup_sorted` (binary search).
@@ -92,6 +92,9 @@ pub struct MerkleizedBatch<F: Family, D: Digest, K: Key, V: ValueEncoding> {
9292
/// 1:1 with `ancestor_diffs`: `ancestor_diff_ends[i]` is the boundary for
9393
/// `ancestor_diffs[i]`. A batch is committed when `ancestor_diff_ends[i] <= db_size`.
9494
pub(super) ancestor_diff_ends: Vec<u64>,
95+
96+
/// The inactivity floor declared by this batch's commit operation.
97+
pub(super) new_inactivity_floor_loc: Location<F>,
9598
}
9699

97100
impl<F, H, K, V> UnmerkleizedBatch<F, H, K, V>
@@ -100,7 +103,7 @@ where
100103
K: Key,
101104
V: ValueEncoding,
102105
H: CHasher,
103-
Operation<K, V>: EncodeShared,
106+
Operation<F, K, V>: EncodeShared,
104107
{
105108
/// Create a batch from a committed DB (no parent chain).
106109
pub(super) fn new<E, C, T>(
@@ -109,7 +112,7 @@ where
109112
) -> Self
110113
where
111114
E: Context,
112-
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
115+
C: Mutable<Item = Operation<F, K, V>> + Persistable<Error = JournalError>,
113116
C::Item: EncodeShared,
114117
T: Translator,
115118
{
@@ -139,7 +142,7 @@ where
139142
) -> Result<Option<V::Value>, Error<F>>
140143
where
141144
E: Context,
142-
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
145+
C: Mutable<Item = Operation<F, K, V>> + Persistable<Error = JournalError>,
143146
C::Item: EncodeShared,
144147
T: Translator,
145148
{
@@ -164,22 +167,26 @@ where
164167
}
165168

166169
/// Resolve mutations into operations, merkleize, and return an `Arc<MerkleizedBatch>`.
170+
///
171+
/// `inactivity_floor` declares that all operations before this location are inactive.
172+
/// It must be >= the database's current inactivity floor (monotonically non-decreasing).
167173
pub fn merkleize<E, C, T>(
168174
self,
169175
db: &Immutable<F, E, K, V, C, H, T>,
170176
metadata: Option<V::Value>,
177+
inactivity_floor: Location<F>,
171178
) -> Arc<MerkleizedBatch<F, H::Digest, K, V>>
172179
where
173180
E: Context,
174-
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
181+
C: Mutable<Item = Operation<F, K, V>> + Persistable<Error = JournalError>,
175182
C::Item: EncodeShared,
176183
T: Translator,
177184
{
178185
let base = self.base_size;
179186

180187
// Build operations: one Set per key, then Commit. `self.mutations` is a BTreeMap, so
181188
// iteration yields keys in sorted order, which `diff` relies on for binary search.
182-
let mut ops: Vec<Operation<K, V>> = Vec::with_capacity(self.mutations.len() + 1);
189+
let mut ops: Vec<Operation<F, K, V>> = Vec::with_capacity(self.mutations.len() + 1);
183190
let mut diff: DiffVec<K, F, V::Value> = Vec::with_capacity(self.mutations.len());
184191

185192
for (key, value) in self.mutations {
@@ -189,7 +196,7 @@ where
189196
}
190197
debug_assert!(diff.is_sorted_by(|a, b| a.0 < b.0));
191198

192-
ops.push(Operation::Commit(metadata));
199+
ops.push(Operation::Commit(metadata, inactivity_floor));
193200

194201
let total_size = base + ops.len() as u64;
195202

@@ -220,13 +227,14 @@ where
220227
db_size: self.db_size,
221228
ancestor_diffs,
222229
ancestor_diff_ends,
230+
new_inactivity_floor_loc: inactivity_floor,
223231
})
224232
}
225233
}
226234

227235
impl<F: Family, D: Digest, K: Key, V: ValueEncoding> MerkleizedBatch<F, D, K, V>
228236
where
229-
Operation<K, V>: EncodeShared,
237+
Operation<F, K, V>: EncodeShared,
230238
{
231239
/// Return the speculative root.
232240
pub fn root(&self) -> D {
@@ -251,7 +259,7 @@ where
251259
) -> Result<Option<V::Value>, Error<F>>
252260
where
253261
E: Context,
254-
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
262+
C: Mutable<Item = Operation<F, K, V>> + Persistable<Error = JournalError>,
255263
C::Item: EncodeShared,
256264
H: CHasher<Digest = D>,
257265
T: Translator,
@@ -292,7 +300,7 @@ where
292300
E: Context,
293301
K: Key,
294302
V: ValueEncoding,
295-
C: Mutable<Item = Operation<K, V>> + Persistable<Error = JournalError>,
303+
C: Mutable<Item = Operation<F, K, V>> + Persistable<Error = JournalError>,
296304
C::Item: EncodeShared,
297305
H: CHasher,
298306
T: Translator,
@@ -309,6 +317,7 @@ where
309317
db_size: journal_size,
310318
ancestor_diffs: Vec::new(),
311319
ancestor_diff_ends: Vec::new(),
320+
new_inactivity_floor_loc: self.inactivity_floor_loc,
312321
})
313322
}
314323
}

0 commit comments

Comments
 (0)