-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Epoch-Changes tree pruning was lagging by one epoch #12567
Changes from 2 commits
7b3d159
8b85de4
7c26c35
ee7e76a
b5d23cc
3c6bc87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -518,8 +518,8 @@ where | |
let is_descendent_of = descendent_of_builder.build_is_descendent_of(None); | ||
|
||
let predicate = |epoch: &PersistedEpochHeader<E>| match *epoch { | ||
PersistedEpochHeader::Genesis(_, ref epoch_1) => slot >= epoch_1.end_slot, | ||
PersistedEpochHeader::Regular(ref epoch_n) => slot >= epoch_n.end_slot, | ||
PersistedEpochHeader::Genesis(ref epoch_0, _) => epoch_0.start_slot <= slot, | ||
PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot, | ||
}; | ||
|
||
// prune any epochs which could not be _live_ as of the children of the | ||
|
@@ -777,11 +777,6 @@ where | |
} | ||
} | ||
|
||
/// Return the inner fork tree. | ||
pub fn tree(&self) -> &ForkTree<Hash, Number, PersistedEpochHeader<E>> { | ||
&self.inner | ||
} | ||
|
||
/// Reset to a specified pair of epochs, as if they were announced at blocks `parent_hash` and | ||
/// `hash`. | ||
pub fn reset(&mut self, parent_hash: Hash, hash: Hash, number: Number, current: E, next: E) { | ||
|
@@ -832,6 +827,11 @@ where | |
self.epochs.remove(&(h, n)); | ||
}); | ||
} | ||
|
||
/// Return the inner fork tree (mostly useful for testing) | ||
pub fn tree(&self) -> &ForkTree<Hash, Number, PersistedEpochHeader<E>> { | ||
&self.inner | ||
} | ||
} | ||
|
||
/// Type alias to produce the epoch-changes tree from a block type. | ||
|
@@ -1114,6 +1114,88 @@ mod tests { | |
} | ||
} | ||
|
||
#[test] | ||
fn prune_removes_stale_nodes() { | ||
// +---D +-------F | ||
// | | | ||
// 0---A---B--(x)--C--(y)--G | ||
// | | ||
// +-------E | ||
// | ||
// Test parameters: | ||
// - epoch duration: 100 | ||
// | ||
// We are going to prune the tree at: | ||
// - 'x', a node between B and C | ||
// - 'y', a node between C and G | ||
|
||
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> { | ||
match (base, block) { | ||
(b"0", _) => Ok(true), | ||
(b"A", b) => Ok(b != b"0"), | ||
(b"B", b) => Ok(b != b"0" && b != b"A" && b != b"D"), | ||
(b"C", b) => Ok(b == b"F" || b == b"G" || b == b"y"), | ||
(b"x", b) => Ok(b == b"C" || b == b"F" || b == b"G" || b == b"y"), | ||
(b"y", b) => Ok(b == b"G"), | ||
_ => Ok(false), | ||
} | ||
}; | ||
|
||
let mut epoch_changes = EpochChanges::new(); | ||
|
||
let mut import_at = |slot, hash: &Hash, number, parent_hash, parent_number| { | ||
let make_genesis = |slot| Epoch { start_slot: slot, duration: 100 }; | ||
// Get epoch descriptor valid for 'slot' | ||
let epoch_descriptor = epoch_changes | ||
.epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot) | ||
.unwrap() | ||
.unwrap(); | ||
// Increment it | ||
let next_epoch_desc = epoch_changes | ||
.viable_epoch(&epoch_descriptor, &make_genesis) | ||
.unwrap() | ||
.increment(()); | ||
// Assign it to hash/number | ||
epoch_changes | ||
.import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc) | ||
.unwrap(); | ||
}; | ||
|
||
import_at(100, b"A", 10, b"0", 0); | ||
import_at(200, b"B", 20, b"A", 10); | ||
Comment on lines
+1164
to
+1165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This confuses me a bit, block "A" should set the genesis epoch which should last from slot 100 to 300. It would also be nice if there was a fork on the genesis epoch, to test that the predicate is doing the correct thing when pruning multiple genesis epochs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly.
I'm going to add a fork at genesis epoch as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @andresilva I've added a fork on genesis to the test. |
||
import_at(300, b"C", 30, b"B", 20); | ||
import_at(200, b"D", 20, b"A", 10); | ||
import_at(300, b"E", 30, b"B", 20); | ||
import_at(400, b"F", 40, b"C", 30); | ||
import_at(400, b"G", 40, b"C", 30); | ||
|
||
let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); | ||
nodes.sort(); | ||
assert_eq!(nodes, vec![b"A", b"B", b"C", b"D", b"E", b"F", b"G"]); | ||
|
||
// Finalize block 'x' @ number 25, slot 230 | ||
// This should prune all nodes imported by blocks with a number < 25 that are not | ||
// ancestors of 'x' and all nodes before the one holding the epoch information | ||
// to which 'x' belongs to. | ||
|
||
epoch_changes.prune_finalized(&is_descendent_of, b"x", 25, 230).unwrap(); | ||
|
||
let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); | ||
nodes.sort(); | ||
assert_eq!(nodes, vec![b"A", b"B", b"C", b"E", b"F", b"G"]); | ||
|
||
// Finalize block x @ number 35, slot 330 | ||
// This should prune all nodes imported by blocks with a number < 35 that are not | ||
// ancestors of 'y' and all nodes before the one holding the epoch information | ||
// to which 'y' belongs to. | ||
|
||
epoch_changes.prune_finalized(&is_descendent_of, b"y", 35, 330).unwrap(); | ||
|
||
let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); | ||
nodes.sort(); | ||
assert_eq!(nodes, vec![b"B", b"C", b"F", b"G"]); | ||
} | ||
|
||
/// Test that ensures that the gap is not enabled when we import multiple genesis blocks. | ||
#[test] | ||
fn gap_is_not_enabled_when_multiple_genesis_epochs_are_imported() { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the fix, the rest is just cruft
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for my understanding: we re-root the tree to the deepest node that passes the predicate, with the previous predicate this would only pass for an epoch that is already elapsed (the previous epoch). With the new predicate the only epoch that will pass the predicate is the one we're currently in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, it will pass for the epoch that we are in or the last one in the tree that is viable (if we skipped epochs).