BABE Epochs (#3028)

* Add `epoch` field to `SlotInfo`

* Add slot calculations

* More work on epochs in BABE

* Apply suggestions from code review

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Typo: `/` not `%` for division

* Delete useless `LastSlotInEpoch::put(false)`

* Bump `spec_version`

* Make test suite pass again

* Implement BABE epoch randomness signing

* Try to fix compilation

Currently causes a stack overflow in the compiler

* Fix rustc stack overflow

* Add missing `PartialEq` and `Eq` implementations

* Fix compile errors in test suite

* Another silly compile error

* Clone `epoch`

* Fix compile error in benchmarks

* Implement `clone` for `Epoch`

* Merge master

* AUTHORING TEST PASSES!!!

* Fix compilation

* Bump `spec_version`

* Fix compilation

* Fix compilation (again)

* Remove an outdated FIXME

* Fix run.sh and move it to scripts/

* Delete commented-out code

* Fix documentation

Co-Authored-By: André Silva <andre.beat@gmail.com>

* Fix BABE initialization and refactor

* Respond to review

* typo

* Remove useless data in `CheckedHeader::Deferred`

* Remove `slot_number` from Epoch

It is not needed, and only served to waste space and cause confusion.

* Remove epoch from BABE digests

* Move digest.rs to primitives

* Fix incorrect warning names

* Fix compile error

* Consistent field naming for BABE digests

* More compiler error fixex

* Unbound variable

* more compile errors

* another compile error

* Fix compile errors in runtime

* another compile error

* Another compile error

* Fix wasm build

* missing import

* Fix more compile errors

* yet another compile error

* compile fix in test runtime

* Fix and simplify the BABE runtime

The BABE runtime was massively overcomplicated and also wrong.  It
assumed it needed to:

1. delay new authorities taking effect until the next epoch
2. not delay emitting `Consensus` digests to mark epoch changes

However, the first is handled by the `srml_session` crate, and the
second is flat-out incorrect: `Consensus` digests take effect
immediately.  Furthermore, `srml_babe` tried to duplicate the
functionality of `srml_session::PeriodicSession`, but did it both
clumsily and incorrectly.  Fortunately, the new code is simpler and far
more likely to be correct.

* Use `system` to get the test authorities

The genesis block used by tests defines no authorities.  Only the test
suite is affected.

* Fix test runtime impl for BabeApi::epoch() with std

* Fix compilation

* Cached authorities are in the form of an epoch

not a `Vec<AuthorityId>`.

* `slots_per_epoch` is not fixed in general

The BABE code previously assumed `slots_per_epoch` to be a constant,
but that assumption is false in general.  Furthermore, removing this
assumption also allows a lot of code to go away.

* fix compile error

* Implement epoch checker

* Fix runtime compilation

* fork-tree: add method for finding a node in the tree

* babe: register epoch transitions in fork tree and validate them

* fork-tree: add method for arbitrary pruning

* Expose the queued validator set to SRML modules

BABE needs to know not only what the current validator set is, but also
what the next validator set will be.  Expose this to clients of the
session module.

* Bump hex-literal

Hopefully this will fix the panic

* babe: prune epoch change fork tree on finality

* babe: validate epoch index on transition

* babe: persist epoch changes tree

* Fix compile error in tests

* Fix compile error in tests

* Another compile error in tests

* Fix compilation of tests

* core: move grandpa::is_descendent_of to client utils

* babe: use is_descendent_of from client utils

* babe: extract slot_number from pre_digest in import_block

* Move BABE testsuite to its own file

* Initial part of test code

* Missing `WeightMultiplierUpdate` in test-runtime

* bump `spec_version`

* Add a test that a very bogus is rejected

* Run the tests again

* Fix compiler diagnostics

* Bump `spec_version`

* Initial infrastructure for mutation testing

* Mutation testing of block import

* babe: revert epoch changes in case of block import error

* babe: fix logging target

* babe: BabeBlockImport doesn't box inner BlockImport

* babe: fix epoch check in block import

* babe: populate authorities cache on block authorship

* babe: remove unused functions

* babe: use RANDOMNESS_LENGTH const

* babe: remove unneeded config parameters

* core: revert change to hex dependency version

* cleanup gitignore

* babe: add docs to aux_schema

* babe: remove useless drops in tests

* babe: remove annoying macos smart quotes

* fork-tree: docs

* fork-tree: add tests

* babe: style

* babe: rename randomness config variable

* babe: remove randomness helper function

* babe: style fixes

* babe: add docs

* babe: fix tests

* node: bump spec_version

* babe: fix tests
This commit is contained in:
DemiMarie-parity
2019-07-23 04:36:16 -04:00
committed by Robert Habermeier
parent 78bc5edc14
commit f78a780790
30 changed files with 1368 additions and 571 deletions
+164 -1
View File
@@ -81,6 +81,62 @@ pub struct ForkTree<H, N, V> {
best_finalized_number: Option<N>,
}
impl<H, N, V> ForkTree<H, N, V> where
H: PartialEq + Clone,
N: Ord + Clone,
V: Clone,
{
/// Prune all nodes that are not descendents of `hash` according to
/// `is_descendent_of`. The given function `is_descendent_of` should return
/// `true` if the second hash (target) is a descendent of the first hash
/// (base). After pruning the tree it should have one or zero roots. The
/// number and order of calls to `is_descendent_of` is unspecified and
/// subject to change.
pub fn prune<F, E>(
&mut self,
hash: &H,
number: N,
is_descendent_of: &F
) -> Result<(), Error<E>>
where E: std::error::Error,
F: Fn(&H, &H) -> Result<bool, E>
{
let mut new_root = None;
for node in self.node_iter() {
// if the node has a lower number than the one being finalized then
// we only keep if it has no children and the finalized block is a
// descendent of this node
if node.number < number {
if !node.children.is_empty() || !is_descendent_of(&node.hash, hash)? {
continue;
}
}
// if the node has the same number as the finalized block then it
// must have the same hash
if node.number == number && node.hash != *hash {
continue;
}
// if the node has a higher number then we keep it if it is a
// descendent of the finalized block
if node.number > number && !is_descendent_of(hash, &node.hash)? {
continue;
}
new_root = Some(node);
break;
}
if let Some(root) = new_root {
self.roots = vec![root.clone()];
}
Ok(())
}
}
impl<H, N, V> ForkTree<H, N, V> where
H: PartialEq,
N: Ord,
@@ -152,6 +208,34 @@ impl<H, N, V> ForkTree<H, N, V> where
self.node_iter().map(|node| (&node.hash, &node.number, &node.data))
}
/// Find a node in the tree that is the lowest ancestor of the given
/// block hash and which passes the given predicate. The given function
/// `is_descendent_of` should return `true` if the second hash (target)
/// is a descendent of the first hash (base).
pub fn find_node_where<F, E, P>(
&self,
hash: &H,
number: &N,
is_descendent_of: &F,
predicate: &P,
) -> Result<Option<&Node<H, N, V>>, Error<E>>
where E: std::error::Error,
F: Fn(&H, &H) -> Result<bool, E>,
P: Fn(&V) -> bool,
{
// search for node starting from all roots
for root in self.roots.iter() {
let node = root.find_node_where(hash, number, is_descendent_of, predicate)?;
// found the node, early exit
if node.is_some() {
return Ok(node);
}
}
Ok(None)
}
/// Finalize a root in the tree and return it, return `None` in case no root
/// with the given hash exists. All other roots are pruned, and the children
/// of the finalized node become the new roots.
@@ -167,7 +251,7 @@ impl<H, N, V> ForkTree<H, N, V> where
}
/// Finalize a node in the tree. This method will make sure that the node
/// being finalized is either an existing root (an return its data), or a
/// being finalized is either an existing root (and return its data), or a
/// node from a competing branch (not in the tree), tree pruning is done
/// accordingly. The given function `is_descendent_of` should return `true`
/// if the second hash (target) is a descendent of the first hash (base).
@@ -400,6 +484,49 @@ mod node_implementation {
Ok(Some((hash, number, data)))
}
}
/// Find a node in the tree that is the lowest ancestor of the given
/// block hash and which passes the given predicate. The given function
/// `is_descendent_of` should return `true` if the second hash (target)
/// is a descendent of the first hash (base).
// FIXME: it would be useful if this returned a mutable reference but
// rustc can't deal with lifetimes properly. an option would be to try
// an iterative definition instead.
pub fn find_node_where<F, P, E>(
&self,
hash: &H,
number: &N,
is_descendent_of: &F,
predicate: &P,
) -> Result<Option<&Node<H, N, V>>, Error<E>>
where E: std::error::Error,
F: Fn(&H, &H) -> Result<bool, E>,
P: Fn(&V) -> bool,
{
// stop searching this branch
if *number < self.number {
return Ok(None);
}
// continue depth-first search through all children
for node in self.children.iter() {
let node = node.find_node_where(hash, number, is_descendent_of, predicate)?;
// found node, early exit
if node.is_some() {
return Ok(node);
}
}
// node not found in any of the descendents, if the node we're
// searching for is a descendent of this node and it passes the
// predicate, then it is this one.
if predicate(&self.data) && is_descendent_of(&self.hash, hash)? {
Ok(Some(self))
} else {
Ok(None)
}
}
}
}
@@ -870,4 +997,40 @@ mod test {
);
}
}
#[test]
fn find_node_works() {
let (tree, is_descendent_of) = test_fork_tree();
let node = tree.find_node_where(
&"D",
&4,
&is_descendent_of,
&|_| true,
).unwrap().unwrap();
assert_eq!(node.hash, "C");
assert_eq!(node.number, 3);
}
#[test]
fn prune_works() {
let (mut tree, is_descendent_of) = test_fork_tree();
tree.prune(
&"C",
3,
&is_descendent_of,
).unwrap();
assert_eq!(
tree.roots.iter().map(|node| node.hash).collect::<Vec<_>>(),
vec!["C"],
);
assert_eq!(
tree.iter().map(|(hash, _, _)| *hash).collect::<Vec<_>>(),
vec!["C", "D", "E"],
);
}
}