Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions client/consensus/babe/src/aux_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ pub(crate) fn load_epoch_changes<Block: BlockT, B: AuxStore>(
SharedEpochChanges::new()
});

// rebalance the tree after deserialization. this isn't strictly necessary
// since the tree is now rebalanced on every update operation. but since the
// tree wasn't rebalanced initially it's useful to temporarily leave it here
// to avoid having to wait until an import for rebalancing.
epoch_changes.lock().rebalance();

Ok(epoch_changes)
}

Expand Down
6 changes: 6 additions & 0 deletions client/consensus/babe/src/epoch_changes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ impl<Hash, Number> EpochChanges<Hash, Number> where
EpochChanges { inner: ForkTree::new() }
}

/// Rebalances the tree of epoch changes so that it is sorted by length of
/// fork (longest fork first).
pub fn rebalance(&mut self) {
self.inner.rebalance()
}

/// Prune out finalized epochs, except for the ancestor of the finalized
/// block. The given slot should be the slot number at which the finalized
/// block was authored.
Expand Down
75 changes: 69 additions & 6 deletions utils/fork-tree/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#![warn(missing_docs)]

use std::cmp::Reverse;
use std::fmt;
use codec::{Decode, Encode};

Expand Down Expand Up @@ -124,6 +125,8 @@ impl<H, N, V> ForkTree<H, N, V> where
self.roots = vec![root];
}

self.rebalance();

Ok(())
}
}
Expand All @@ -140,6 +143,22 @@ impl<H, N, V> ForkTree<H, N, V> where
}
}

/// Rebalance the tree, i.e. sort child nodes by max branch depth
/// (decreasing).
///
/// Most operations in the tree are performed with depth-first search
/// starting from the leftmost node at every level, since this tree is meant
/// to be used in a blockchain context, a good heuristic is that the node
/// we'll be looking
/// for at any point will likely be in one of the deepest chains (i.e. the
/// longest ones).
pub fn rebalance(&mut self) {
self.roots.sort_by_key(|n| Reverse(n.max_depth()));
for root in &mut self.roots {
root.rebalance();
}
}

/// Import a new node into the tree. The given function `is_descendent_of`
/// should return `true` if the second hash (target) is a descendent of the
/// first hash (base). This method assumes that nodes in the same branch are
Expand Down Expand Up @@ -184,6 +203,8 @@ impl<H, N, V> ForkTree<H, N, V> where
children: Vec::new(),
});

self.rebalance();

Ok(true)
}

Expand Down Expand Up @@ -523,6 +544,25 @@ mod node_implementation {
}

impl<H: PartialEq, N: Ord, V> Node<H, N, V> {
/// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing).
pub fn rebalance(&mut self) {
self.children.sort_by_key(|n| Reverse(n.max_depth()));
for child in &mut self.children {
child.rebalance();
}
}

/// Finds the max depth among all branches descendent from this node.
pub fn max_depth(&self) -> usize {
let mut max = 0;

for node in &self.children {
max = node.max_depth().max(max)
}

max + 1
}

pub fn import<F, E: std::error::Error>(
&mut self,
mut hash: H,
Expand Down Expand Up @@ -638,7 +678,10 @@ impl<'a, H, N, V> Iterator for ForkTreeIterator<'a, H, N, V> {

fn next(&mut self) -> Option<Self::Item> {
self.stack.pop().map(|node| {
self.stack.extend(node.children.iter());
// child nodes are stored ordered by max branch height (decreasing),
// we want to keep this ordering while iterating but since we're
// using a stack for iterator state we need to reverse it.
self.stack.extend(node.children.iter().rev());
node
})
}
Expand Down Expand Up @@ -1091,12 +1134,12 @@ mod test {
tree.iter().map(|(h, n, _)| (h.clone(), n.clone())).collect::<Vec<_>>(),
vec![
("A", 1),
("J", 2), ("K", 3),
("F", 2), ("H", 3), ("L", 4), ("O", 5),
("M", 5),
("I", 4),
("G", 3),
("B", 2), ("C", 3), ("D", 4), ("E", 5),
("F", 2),
("G", 3),
("H", 3), ("I", 4),
("L", 4), ("M", 5), ("O", 5),
("J", 2), ("K", 3)
],
);
}
Expand Down Expand Up @@ -1261,4 +1304,24 @@ mod test {

assert_eq!(node.unwrap().hash, "B");
}

#[test]
fn tree_rebalance() {
let (mut tree, _) = test_fork_tree();

assert_eq!(
tree.iter().map(|(h, _, _)| *h).collect::<Vec<_>>(),
vec!["A", "B", "C", "D", "E", "F", "G", "H", "I", "L", "M", "O", "J", "K"],
);

// after rebalancing the tree we should iterate in preorder exploring
// the longest forks first. check the ascii art above to understand the
// expected output below.
tree.rebalance();

assert_eq!(
tree.iter().map(|(h, _, _)| *h).collect::<Vec<_>>(),
["A", "B", "C", "D", "E", "F", "H", "L", "M", "O", "I", "G", "J", "K"]
);
}
}