Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Fork-Tree import requires post-order DFS traversal #11531

Merged
merged 3 commits into from
May 27, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 44 additions & 36 deletions utils/fork-tree/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ where
/// imported in order.
///
/// Returns `true` if the imported node is a root.
// WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently
// rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method
// then the `is_descendent_of` closure, when used after a warp-sync, may end up querying the
// backend for a block (the one corresponding to the root) that is not present and thus will
// return a wrong result.
pub fn import<F, E>(
&mut self,
hash: H,
Expand All @@ -143,29 +148,20 @@ where
}
}

let mut children = &mut self.roots;
let mut i = 0;
while i < children.len() {
let child = &children[i];
if child.hash == hash {
return Err(Error::Duplicate)
}
if child.number < number && is_descendent_of(&child.hash, &hash)? {
children = &mut children[i].children;
i = 0;
} else {
i += 1;
}
let (children, is_root) =
match self.find_node_where_mut(&hash, &number, is_descendent_of, &|_| true)? {
Some(parent) => (&mut parent.children, false),
None => (&mut self.roots, true),
};

if children.iter().any(|elem| elem.hash == hash) {
return Err(Error::Duplicate)
}

let is_first = children.is_empty();
children.push(Node { data, hash, number, children: Default::default() });

// Quick way to check if the pushed node is a root
let is_root = children.as_ptr() == self.roots.as_ptr();

if is_first {
// Rebalance is required only if we've extended the branch depth.
if children.len() == 1 {
// Rebalance may be required only if we've extended the branch depth.
self.rebalance();
}

Expand Down Expand Up @@ -293,7 +289,7 @@ where
// rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method
// then the `is_descendent_of` closure, when used after a warp-sync, will end up querying the
// backend for a block (the one corresponding to the root) that is not present and thus will
// return a wrong result. Here we are implementing a post-order DFS.
// return a wrong result.
pub fn find_node_index_where<F, E, P>(
&self,
hash: &H,
Expand Down Expand Up @@ -1441,22 +1437,6 @@ mod test {
.unwrap()
.unwrap();
assert_eq!(path, [0, 1, 0, 0, 0]);

// Test for the post-order DFS requirement as specified by the `find_node_index_where`
// comment. Once (and if) post-order traversal requirement is removed, then this test
// can be removed as well. In practice this test should fail with a pre-order DFS.
let is_descendent_of_for_post_order = |parent: &&str, child: &&str| {
if *parent == "A" {
Err(TestError)
} else {
is_descendent_of(parent, child)
}
};
let path = tree
.find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true)
.unwrap()
.unwrap();
assert_eq!(path, [0, 1, 0, 0, 0]);
}

#[test]
Expand Down Expand Up @@ -1521,4 +1501,32 @@ mod test {
let node = tree.find_node_where(&"N", &6, &is_descendent_of, &|_| true).unwrap().unwrap();
assert_eq!((node.hash, node.number), ("M", 5));
}

#[test]
fn post_order_traversal_requirement() {
let (mut tree, is_descendent_of) = test_fork_tree();

// Test for the post-order DFS traversal requirement as specified by the
// `find_node_index_where` and `import` comments.
let is_descendent_of_for_post_order = |parent: &&str, child: &&str| match *parent {
"A" => Err(TestError),
"K" if *child == "Z" => Ok(true),
_ => is_descendent_of(parent, child),
};

// Post order traversal requirement for `find_node_index_where`
let path = tree
.find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true)
.unwrap()
.unwrap();
assert_eq!(path, [0, 1, 0, 0, 0]);

// Post order traversal requirement for `import`
let res = tree.import(&"Z", 100, (), &is_descendent_of_for_post_order);
assert_eq!(res, Ok(false));
assert_eq!(
tree.iter().map(|node| *node.0).collect::<Vec<_>>(),
vec!["A", "B", "C", "D", "E", "F", "H", "L", "M", "O", "I", "G", "J", "K", "Z"],
);
}
}