Skip to content

Commit 2cb89be

Browse files
davxybkchr
andauthored
Fix leaf block removal in the backend (paritytech#12005)
* Fix leaf block removal in the backend The fix introduced the new 'removal' method for the backend leaves set and the improvement of the undo features. * Update docs * Apply suggestions from code review Co-authored-by: Bastian Köcher <[email protected]> * Fix docs typo * On block block removal the new children list should be persisted. * Align leaves set removal tests to the new interface Co-authored-by: Bastian Köcher <[email protected]>
1 parent 0fd1998 commit 2cb89be

File tree

2 files changed

+252
-95
lines changed

2 files changed

+252
-95
lines changed

client/api/src/leaves.rs

Lines changed: 171 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,36 @@ struct LeafSetItem<H, N> {
3232
number: Reverse<N>,
3333
}
3434

35-
/// A displaced leaf after import.
36-
#[must_use = "Displaced items from the leaf set must be handled."]
37-
pub struct ImportDisplaced<H, N> {
38-
new_hash: H,
39-
displaced: LeafSetItem<H, N>,
35+
/// Inserted and removed leaves after an import action.
36+
pub struct ImportOutcome<H, N> {
37+
inserted: LeafSetItem<H, N>,
38+
removed: Option<H>,
4039
}
4140

42-
/// Displaced leaves after finalization.
43-
#[must_use = "Displaced items from the leaf set must be handled."]
44-
pub struct FinalizationDisplaced<H, N> {
45-
leaves: BTreeMap<Reverse<N>, Vec<H>>,
41+
/// Inserted and removed leaves after a remove action.
42+
pub struct RemoveOutcome<H, N> {
43+
inserted: Option<H>,
44+
removed: LeafSetItem<H, N>,
4645
}
4746

48-
impl<H, N: Ord> FinalizationDisplaced<H, N> {
47+
/// Removed leaves after a finalization action.
48+
pub struct FinalizationOutcome<H, N> {
49+
removed: BTreeMap<Reverse<N>, Vec<H>>,
50+
}
51+
52+
impl<H, N: Ord> FinalizationOutcome<H, N> {
4953
/// Merge with another. This should only be used for displaced items that
5054
/// are produced within one transaction of each other.
5155
pub fn merge(&mut self, mut other: Self) {
5256
// this will ignore keys that are in duplicate, however
5357
// if these are actually produced correctly via the leaf-set within
5458
// one transaction, then there will be no overlap in the keys.
55-
self.leaves.append(&mut other.leaves);
59+
self.removed.append(&mut other.removed);
5660
}
5761

5862
/// Iterate over all displaced leaves.
5963
pub fn leaves(&self) -> impl Iterator<Item = &H> {
60-
self.leaves.values().flatten()
64+
self.removed.values().flatten()
6165
}
6266
}
6367

@@ -99,27 +103,52 @@ where
99103
}
100104

101105
/// Update the leaf list on import.
102-
/// Returns a displaced leaf if there was one.
103-
pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> Option<ImportDisplaced<H, N>> {
104-
// avoid underflow for genesis.
105-
let displaced = if number != N::zero() {
106-
let parent_number = Reverse(number.clone() - N::one());
107-
let was_displaced = self.remove_leaf(&parent_number, &parent_hash);
108-
109-
if was_displaced {
110-
Some(ImportDisplaced {
111-
new_hash: hash.clone(),
112-
displaced: LeafSetItem { hash: parent_hash, number: parent_number },
113-
})
114-
} else {
115-
None
116-
}
106+
pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> ImportOutcome<H, N> {
107+
let number = Reverse(number);
108+
109+
let removed = if number.0 != N::zero() {
110+
let parent_number = Reverse(number.0.clone() - N::one());
111+
self.remove_leaf(&parent_number, &parent_hash).then(|| parent_hash)
117112
} else {
118113
None
119114
};
120115

121-
self.insert_leaf(Reverse(number.clone()), hash.clone());
122-
displaced
116+
self.insert_leaf(number.clone(), hash.clone());
117+
118+
ImportOutcome { inserted: LeafSetItem { hash, number }, removed }
119+
}
120+
121+
/// Update the leaf list on removal.
122+
///
123+
/// Note that the leaves set structure doesn't have the information to decide if the
124+
/// leaf we're removing is the last children of the parent. Follows that this method requires
125+
/// the caller to check this condition and optionally pass the `parent_hash` if `hash` is
126+
/// its last child.
127+
///
128+
/// Returns `None` if no modifications are applied.
129+
pub fn remove(
130+
&mut self,
131+
hash: H,
132+
number: N,
133+
parent_hash: Option<H>,
134+
) -> Option<RemoveOutcome<H, N>> {
135+
let number = Reverse(number);
136+
137+
if !self.remove_leaf(&number, &hash) {
138+
return None
139+
}
140+
141+
let inserted = parent_hash.and_then(|parent_hash| {
142+
if number.0 != N::zero() {
143+
let parent_number = Reverse(number.0.clone() - N::one());
144+
self.insert_leaf(parent_number, parent_hash.clone());
145+
Some(parent_hash)
146+
} else {
147+
None
148+
}
149+
});
150+
151+
Some(RemoveOutcome { inserted, removed: LeafSetItem { hash, number } })
123152
}
124153

125154
/// Note a block height finalized, displacing all leaves with number less than the finalized
@@ -129,32 +158,32 @@ where
129158
/// same number as the finalized block, but with different hashes, the current behavior
130159
/// is simpler and our assumptions about how finalization works means that those leaves
131160
/// will be pruned soon afterwards anyway.
132-
pub fn finalize_height(&mut self, number: N) -> FinalizationDisplaced<H, N> {
161+
pub fn finalize_height(&mut self, number: N) -> FinalizationOutcome<H, N> {
133162
let boundary = if number == N::zero() {
134-
return FinalizationDisplaced { leaves: BTreeMap::new() }
163+
return FinalizationOutcome { removed: BTreeMap::new() }
135164
} else {
136165
number - N::one()
137166
};
138167

139168
let below_boundary = self.storage.split_off(&Reverse(boundary));
140-
FinalizationDisplaced { leaves: below_boundary }
169+
FinalizationOutcome { removed: below_boundary }
141170
}
142171

143172
/// The same as [`Self::finalize_height`], but it only simulates the operation.
144173
///
145174
/// This means that no changes are done.
146175
///
147176
/// Returns the leaves that would be displaced by finalizing the given block.
148-
pub fn displaced_by_finalize_height(&self, number: N) -> FinalizationDisplaced<H, N> {
177+
pub fn displaced_by_finalize_height(&self, number: N) -> FinalizationOutcome<H, N> {
149178
let boundary = if number == N::zero() {
150-
return FinalizationDisplaced { leaves: BTreeMap::new() }
179+
return FinalizationOutcome { removed: BTreeMap::new() }
151180
} else {
152181
number - N::one()
153182
};
154183

155184
let below_boundary = self.storage.range(&Reverse(boundary)..);
156-
FinalizationDisplaced {
157-
leaves: below_boundary.map(|(k, v)| (k.clone(), v.clone())).collect(),
185+
FinalizationOutcome {
186+
removed: below_boundary.map(|(k, v)| (k.clone(), v.clone())).collect(),
158187
}
159188
}
160189

@@ -276,16 +305,30 @@ where
276305
H: Clone + PartialEq + Decode + Encode,
277306
N: std::fmt::Debug + Clone + AtLeast32Bit + Decode + Encode,
278307
{
279-
/// Undo an imported block by providing the displaced leaf.
280-
pub fn undo_import(&mut self, displaced: ImportDisplaced<H, N>) {
281-
let new_number = Reverse(displaced.displaced.number.0.clone() + N::one());
282-
self.inner.remove_leaf(&new_number, &displaced.new_hash);
283-
self.inner.insert_leaf(displaced.displaced.number, displaced.displaced.hash);
308+
/// Undo an imported block by providing the import operation outcome.
309+
/// No additional operations should be performed between import and undo.
310+
pub fn undo_import(&mut self, outcome: ImportOutcome<H, N>) {
311+
if let Some(removed_hash) = outcome.removed {
312+
let removed_number = Reverse(outcome.inserted.number.0.clone() - N::one());
313+
self.inner.insert_leaf(removed_number, removed_hash);
314+
}
315+
self.inner.remove_leaf(&outcome.inserted.number, &outcome.inserted.hash);
316+
}
317+
318+
/// Undo a removed block by providing the displaced leaf.
319+
/// No additional operations should be performed between remove and undo.
320+
pub fn undo_remove(&mut self, outcome: RemoveOutcome<H, N>) {
321+
if let Some(inserted_hash) = outcome.inserted {
322+
let inserted_number = Reverse(outcome.removed.number.0.clone() - N::one());
323+
self.inner.remove_leaf(&inserted_number, &inserted_hash);
324+
}
325+
self.inner.insert_leaf(outcome.removed.number, outcome.removed.hash);
284326
}
285327

286328
/// Undo a finalization operation by providing the displaced leaves.
287-
pub fn undo_finalization(&mut self, mut displaced: FinalizationDisplaced<H, N>) {
288-
self.inner.storage.append(&mut displaced.leaves);
329+
/// No additional operations should be performed between finalization and undo.
330+
pub fn undo_finalization(&mut self, mut outcome: FinalizationOutcome<H, N>) {
331+
self.inner.storage.append(&mut outcome.removed);
289332
}
290333
}
291334

@@ -295,7 +338,7 @@ mod tests {
295338
use std::sync::Arc;
296339

297340
#[test]
298-
fn it_works() {
341+
fn import_works() {
299342
let mut set = LeafSet::new();
300343
set.import(0u32, 0u32, 0u32);
301344

@@ -317,6 +360,90 @@ mod tests {
317360
assert!(set.contains(3, 3_1));
318361
assert!(set.contains(2, 2_2));
319362
assert!(set.contains(2, 2_3));
363+
364+
// Finally test the undo feature
365+
366+
let outcome = set.import(2_4, 2, 1_1);
367+
assert_eq!(outcome.inserted.hash, 2_4);
368+
assert_eq!(outcome.removed, None);
369+
assert_eq!(set.count(), 4);
370+
assert!(set.contains(2, 2_4));
371+
372+
set.undo().undo_import(outcome);
373+
assert_eq!(set.count(), 3);
374+
assert!(set.contains(3, 3_1));
375+
assert!(set.contains(2, 2_2));
376+
assert!(set.contains(2, 2_3));
377+
378+
let outcome = set.import(3_2, 3, 2_3);
379+
assert_eq!(outcome.inserted.hash, 3_2);
380+
assert_eq!(outcome.removed, Some(2_3));
381+
assert_eq!(set.count(), 3);
382+
assert!(set.contains(3, 3_2));
383+
384+
set.undo().undo_import(outcome);
385+
assert_eq!(set.count(), 3);
386+
assert!(set.contains(3, 3_1));
387+
assert!(set.contains(2, 2_2));
388+
assert!(set.contains(2, 2_3));
389+
}
390+
391+
#[test]
392+
fn removal_works() {
393+
let mut set = LeafSet::new();
394+
set.import(10_1u32, 10u32, 0u32);
395+
set.import(11_1, 11, 10_1);
396+
set.import(11_2, 11, 10_1);
397+
set.import(12_1, 12, 11_1);
398+
399+
let outcome = set.remove(12_1, 12, Some(11_1)).unwrap();
400+
assert_eq!(outcome.removed.hash, 12_1);
401+
assert_eq!(outcome.inserted, Some(11_1));
402+
assert_eq!(set.count(), 2);
403+
assert!(set.contains(11, 11_1));
404+
assert!(set.contains(11, 11_2));
405+
406+
let outcome = set.remove(11_1, 11, None).unwrap();
407+
assert_eq!(outcome.removed.hash, 11_1);
408+
assert_eq!(outcome.inserted, None);
409+
assert_eq!(set.count(), 1);
410+
assert!(set.contains(11, 11_2));
411+
412+
let outcome = set.remove(11_2, 11, Some(10_1)).unwrap();
413+
assert_eq!(outcome.removed.hash, 11_2);
414+
assert_eq!(outcome.inserted, Some(10_1));
415+
assert_eq!(set.count(), 1);
416+
assert!(set.contains(10, 10_1));
417+
418+
set.undo().undo_remove(outcome);
419+
assert_eq!(set.count(), 1);
420+
assert!(set.contains(11, 11_2));
421+
}
422+
423+
#[test]
424+
fn finalization_works() {
425+
let mut set = LeafSet::new();
426+
set.import(9_1u32, 9u32, 0u32);
427+
set.import(10_1, 10, 9_1);
428+
set.import(10_2, 10, 9_1);
429+
set.import(11_1, 11, 10_1);
430+
set.import(11_2, 11, 10_1);
431+
set.import(12_1, 12, 11_2);
432+
433+
let outcome = set.finalize_height(11);
434+
assert_eq!(set.count(), 2);
435+
assert!(set.contains(11, 11_1));
436+
assert!(set.contains(12, 12_1));
437+
assert_eq!(
438+
outcome.removed,
439+
[(Reverse(10), vec![10_2])].into_iter().collect::<BTreeMap<_, _>>(),
440+
);
441+
442+
set.undo().undo_finalization(outcome);
443+
assert_eq!(set.count(), 3);
444+
assert!(set.contains(11, 11_1));
445+
assert!(set.contains(12, 12_1));
446+
assert!(set.contains(10, 10_2));
320447
}
321448

322449
#[test]
@@ -383,44 +510,4 @@ mod tests {
383510
let set2 = LeafSet::read_from_db(&*db, 0, PREFIX).unwrap();
384511
assert_eq!(set, set2);
385512
}
386-
387-
#[test]
388-
fn undo_import() {
389-
let mut set = LeafSet::new();
390-
set.import(10_1u32, 10u32, 0u32);
391-
set.import(11_1, 11, 10_1);
392-
set.import(11_2, 11, 10_1);
393-
394-
let displaced = set.import(12_1, 12, 11_1).unwrap();
395-
assert_eq!(set.count(), 2);
396-
assert!(set.contains(11, 11_2));
397-
assert!(set.contains(12, 12_1));
398-
399-
set.undo().undo_import(displaced);
400-
assert_eq!(set.count(), 2);
401-
assert!(set.contains(11, 11_1));
402-
assert!(set.contains(11, 11_2));
403-
}
404-
405-
#[test]
406-
fn undo_finalization() {
407-
let mut set = LeafSet::new();
408-
set.import(9_1u32, 9u32, 0u32);
409-
set.import(10_1, 10, 9_1);
410-
set.import(10_2, 10, 9_1);
411-
set.import(11_1, 11, 10_1);
412-
set.import(11_2, 11, 10_1);
413-
set.import(12_1, 12, 11_2);
414-
415-
let displaced = set.finalize_height(11);
416-
assert_eq!(set.count(), 2);
417-
assert!(set.contains(11, 11_1));
418-
assert!(set.contains(12, 12_1));
419-
420-
set.undo().undo_finalization(displaced);
421-
assert_eq!(set.count(), 3);
422-
assert!(set.contains(11, 11_1));
423-
assert!(set.contains(12, 12_1));
424-
assert!(set.contains(10, 10_2));
425-
}
426513
}

0 commit comments

Comments
 (0)