Skip to content

Commit fa90b2e

Browse files
committed
fix overlay deletions bug
1 parent ced9667 commit fa90b2e

File tree

2 files changed

+144
-8
lines changed

2 files changed

+144
-8
lines changed

nomt/src/merkle/seek.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use nomt_core::{
2525
trie_pos::TriePosition,
2626
};
2727

28+
use std::cmp::Ordering;
2829
use std::collections::{
2930
hash_map::{Entry, HashMap},
3031
VecDeque,
@@ -204,6 +205,27 @@ impl SeekRequest {
204205
beatree_iterator.provide_leaf(leaf);
205206
}
206207

208+
let manage_deletions = |deletions: &[KeyPath], mut cur_idx: usize, key: &KeyPath| {
209+
while cur_idx < deletions.len() {
210+
match key.cmp(&deletions[cur_idx]) {
211+
Ordering::Less => {
212+
// key is before the deletion
213+
break;
214+
}
215+
Ordering::Equal => {
216+
cur_idx += 1;
217+
return (cur_idx, true);
218+
}
219+
Ordering::Greater => {
220+
// deletion is before the key. move on until we find a match or go past
221+
cur_idx += 1;
222+
}
223+
}
224+
}
225+
226+
(cur_idx, false)
227+
};
228+
207229
let mut deletions_idx = 0;
208230
let (key, value_hash) = loop {
209231
match beatree_iterator.next() {
@@ -212,15 +234,26 @@ impl SeekRequest {
212234
overlay_deletions.drain(..deletions_idx);
213235
return;
214236
}
215-
Some(IterOutput::Item(key, _value))
216-
if deletions_idx < overlay_deletions.len()
217-
&& overlay_deletions[deletions_idx] == key =>
218-
{
219-
deletions_idx += 1;
220-
continue;
237+
Some(IterOutput::Item(key, value)) => {
238+
let (new_d_idx, should_skip) =
239+
manage_deletions(&overlay_deletions, deletions_idx, &key);
240+
deletions_idx = new_d_idx;
241+
if should_skip {
242+
continue;
243+
}
244+
245+
break (key, H::hash_value(&value));
246+
}
247+
Some(IterOutput::OverflowItem(key, value_hash, _)) => {
248+
let (new_d_idx, should_skip) =
249+
manage_deletions(&overlay_deletions, deletions_idx, &key);
250+
deletions_idx = new_d_idx;
251+
if should_skip {
252+
continue;
253+
}
254+
255+
break (key, value_hash);
221256
}
222-
Some(IterOutput::Item(key, value)) => break (key, H::hash_value(&value)),
223-
Some(IterOutput::OverflowItem(key, value_hash, _)) => break (key, value_hash),
224257
}
225258
};
226259

nomt/tests/overlay.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
mod common;
22

3+
use bitvec::prelude::*;
34
use common::Test;
45

56
fn expected_root(items: Vec<([u8; 32], Vec<u8>)>) -> nomt_core::trie::Node {
@@ -252,3 +253,105 @@ fn overlay_deletions() {
252253

253254
let _overlay_b = test.update().0;
254255
}
256+
257+
macro_rules! key_path {
258+
($($t:tt)+) => {{
259+
let mut path = [0u8; 32];
260+
let slice = bits![u8, Msb0; $($t)+];
261+
path.view_bits_mut::<Msb0>()[..slice.len()].copy_from_bitslice(&slice);
262+
path
263+
}}
264+
}
265+
266+
#[test]
267+
fn overlay_deletions_respected_in_seek() {
268+
let mut test = Test::new("overlay_deletions_respected_in_seek");
269+
270+
// key_a will be on disk, deleted in overlay.
271+
let key_a = key_path![0, 0, 1, 0, 1];
272+
273+
// key_b will be on disk, after key_a
274+
let key_b = key_path![0, 0, 1, 0, 1, 1];
275+
276+
// key_c constrains the leaf node to the path [0, 0, 1]
277+
let key_c = key_path![0, 0, 0];
278+
279+
// populate. [0, 0, 1] is an internal node with children for key_a and key_b
280+
test.write(key_a, Some(vec![42]));
281+
test.write(key_b, Some(vec![43]));
282+
test.write(key_c, Some(vec![44]));
283+
let _ = test.commit();
284+
285+
// first overlay: delete key_a. [0, 0, 1] is now a leaf for key_b
286+
test.start_overlay_session(None);
287+
test.write(key_a, None);
288+
let overlay_a = test.update().0;
289+
290+
// second overlay: update key_b's value.
291+
// the b-tree iterator contains key_a (deleted in overlay) and key_b,
292+
// so seek must skip over key_a.
293+
test.start_overlay_session([&overlay_a]);
294+
test.write(key_b, Some(vec![22]));
295+
let overlay_b = test.update().0;
296+
297+
// Now ensure that the trie root is accurate for overlay_c.
298+
assert_eq!(
299+
overlay_b.root().into_inner(),
300+
expected_root(vec![(key_b, vec![22]), (key_c, vec![44]),]),
301+
)
302+
}
303+
304+
#[test]
305+
fn overlay_earlier_deletions_respected_in_seek() {
306+
let mut test = Test::new("overlay_earlier_deletions_respected_in_seek");
307+
308+
// key_a will be introduced in one overlay and deleted in the next
309+
let key_a = key_path![0, 0, 1, 0, 1];
310+
311+
// key_b will have a leaf on disk, deleted in an overlay. after key_a
312+
let key_b = key_path![0, 0, 1, 0, 1, 1];
313+
314+
// key_c will have a leaf on disk. after key_b
315+
let key_c = key_path![0, 0, 1, 1, 1, 1, 1];
316+
317+
// key_d will have a leaf on disk with the goal of constraining the leaf node to the path
318+
// [0, 0, 1]
319+
let key_d = key_path![0, 0, 0];
320+
321+
// populate. [0, 0, 1] is an internal node with children for key_b and key_c
322+
test.write(key_b, Some(vec![42]));
323+
test.write(key_c, Some(vec![43]));
324+
test.write(key_d, Some(vec![44]));
325+
let _ = test.commit();
326+
327+
// First overlay: introduce key_a and delete key_b. [0, 0, 1] is
328+
// now internal with children for key_a and key_c
329+
test.start_overlay_session(None);
330+
test.write(key_a, Some(vec![24]));
331+
test.write(key_b, None);
332+
let overlay_a = test.update().0;
333+
334+
// Second overlay: delete key_a. [0, 0, 1] is now a leaf for key_c
335+
test.start_overlay_session([&overlay_a]);
336+
test.write(key_a, None);
337+
let overlay_b = test.update().0;
338+
339+
// Third update: update key_c. Seek should skip over deleted key_a to find key_b.
340+
// At this point, the ground truth beatree iterator contains key_b and key_c.
341+
// Seek will:
342+
// 1. encounter the leaf for key_c and initiate a leaf fetch
343+
// 2. gather overlay deletions [key_a, key_b]
344+
// 3. get first item from btree: key_b > key_a.
345+
// 4. ignore the deleted key_a and continue to next deletion, key_b.
346+
// 5. find that key_b is deleted, continue to next item.
347+
// 6. get next item from btree: key_c == key_c, and return it as the leaf.
348+
test.start_overlay_session([&overlay_b, &overlay_a]);
349+
test.write(key_c, Some(vec![22]));
350+
let overlay_c = test.update().0;
351+
352+
// Now ensure that the trie root is accurate for overlay_c.
353+
assert_eq!(
354+
overlay_c.root().into_inner(),
355+
expected_root(vec![(key_c, vec![22]), (key_d, vec![44]),]),
356+
)
357+
}

0 commit comments

Comments
 (0)