pub mod migration;
use codec::{Decode, Encode};
use fork_tree::{FilterAction, ForkTree};
use sc_client_api::utils::is_descendent_of;
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata};
use sp_runtime::traits::{Block as BlockT, NumberFor, One, Zero};
use std::{
borrow::{Borrow, BorrowMut},
collections::BTreeMap,
ops::{Add, Sub},
};
pub trait IsDescendentOfBuilder<Hash> {
type Error: std::error::Error;
type IsDescendentOf: Fn(&Hash, &Hash) -> Result<bool, Self::Error>;
fn build_is_descendent_of(&self, current: Option<(Hash, Hash)>) -> Self::IsDescendentOf;
}
pub fn descendent_query<H, Block>(client: &H) -> HeaderBackendDescendentBuilder<&H, Block> {
HeaderBackendDescendentBuilder(client, std::marker::PhantomData)
}
pub struct HeaderBackendDescendentBuilder<H, Block>(H, std::marker::PhantomData<Block>);
impl<'a, H, Block> IsDescendentOfBuilder<Block::Hash>
for HeaderBackendDescendentBuilder<&'a H, Block>
where
H: HeaderBackend<Block> + HeaderMetadata<Block, Error = ClientError>,
Block: BlockT,
{
type Error = ClientError;
type IsDescendentOf = Box<dyn Fn(&Block::Hash, &Block::Hash) -> Result<bool, ClientError> + 'a>;
fn build_is_descendent_of(
&self,
current: Option<(Block::Hash, Block::Hash)>,
) -> Self::IsDescendentOf {
Box::new(is_descendent_of(self.0, current))
}
}
pub trait Epoch: std::fmt::Debug {
type NextEpochDescriptor;
type Slot: Ord + Copy + std::fmt::Debug;
fn start_slot(&self) -> Self::Slot;
fn end_slot(&self) -> Self::Slot;
fn increment(&self, descriptor: Self::NextEpochDescriptor) -> Self;
}
impl<'a, E: Epoch> From<&'a E> for EpochHeader<E> {
fn from(epoch: &'a E) -> EpochHeader<E> {
Self { start_slot: epoch.start_slot(), end_slot: epoch.end_slot() }
}
}
#[derive(Eq, PartialEq, Encode, Decode, Debug)]
pub struct EpochHeader<E: Epoch> {
pub start_slot: E::Slot,
pub end_slot: E::Slot,
}
impl<E: Epoch> Clone for EpochHeader<E> {
fn clone(&self) -> Self {
Self { start_slot: self.start_slot, end_slot: self.end_slot }
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
pub enum EpochIdentifierPosition {
Genesis0,
Genesis1,
Regular,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
pub struct EpochIdentifier<Hash, Number> {
pub position: EpochIdentifierPosition,
pub hash: Hash,
pub number: Number,
}
pub enum ViableEpoch<E, ERef = E> {
UnimportedGenesis(E),
Signaled(ERef),
}
impl<E, ERef> AsRef<E> for ViableEpoch<E, ERef>
where
ERef: Borrow<E>,
{
fn as_ref(&self) -> &E {
match *self {
ViableEpoch::UnimportedGenesis(ref e) => e,
ViableEpoch::Signaled(ref e) => e.borrow(),
}
}
}
impl<E, ERef> AsMut<E> for ViableEpoch<E, ERef>
where
ERef: BorrowMut<E>,
{
fn as_mut(&mut self) -> &mut E {
match *self {
ViableEpoch::UnimportedGenesis(ref mut e) => e,
ViableEpoch::Signaled(ref mut e) => e.borrow_mut(),
}
}
}
impl<E, ERef> ViableEpoch<E, ERef>
where
E: Epoch + Clone,
ERef: Borrow<E>,
{
pub fn into_cloned_inner(self) -> E {
match self {
ViableEpoch::UnimportedGenesis(e) => e,
ViableEpoch::Signaled(e) => e.borrow().clone(),
}
}
pub fn into_cloned(self) -> ViableEpoch<E, E> {
match self {
ViableEpoch::UnimportedGenesis(e) => ViableEpoch::UnimportedGenesis(e),
ViableEpoch::Signaled(e) => ViableEpoch::Signaled(e.borrow().clone()),
}
}
pub fn increment(&self, next_descriptor: E::NextEpochDescriptor) -> IncrementedEpoch<E> {
let next = self.as_ref().increment(next_descriptor);
let to_persist = match *self {
ViableEpoch::UnimportedGenesis(ref epoch_0) =>
PersistedEpoch::Genesis(epoch_0.clone(), next),
ViableEpoch::Signaled(_) => PersistedEpoch::Regular(next),
};
IncrementedEpoch(to_persist)
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum ViableEpochDescriptor<Hash, Number, E: Epoch> {
UnimportedGenesis(E::Slot),
Signaled(EpochIdentifier<Hash, Number>, EpochHeader<E>),
}
impl<Hash, Number, E: Epoch> ViableEpochDescriptor<Hash, Number, E> {
pub fn start_slot(&self) -> E::Slot {
match self {
Self::UnimportedGenesis(start_slot) => *start_slot,
Self::Signaled(_, header) => header.start_slot,
}
}
}
#[derive(Clone, Encode, Decode, Debug)]
pub enum PersistedEpoch<E> {
Genesis(E, E),
Regular(E),
}
impl<E> PersistedEpoch<E> {
pub fn is_genesis(&self) -> bool {
matches!(self, Self::Genesis(_, _))
}
}
impl<'a, E: Epoch> From<&'a PersistedEpoch<E>> for PersistedEpochHeader<E> {
fn from(epoch: &'a PersistedEpoch<E>) -> Self {
match epoch {
PersistedEpoch::Genesis(ref epoch_0, ref epoch_1) =>
PersistedEpochHeader::Genesis(epoch_0.into(), epoch_1.into()),
PersistedEpoch::Regular(ref epoch_n) => PersistedEpochHeader::Regular(epoch_n.into()),
}
}
}
impl<E: Epoch> PersistedEpoch<E> {
pub fn map<B, F, Hash, Number>(self, h: &Hash, n: &Number, f: &mut F) -> PersistedEpoch<B>
where
B: Epoch<Slot = E::Slot>,
F: FnMut(&Hash, &Number, E) -> B,
{
match self {
PersistedEpoch::Genesis(epoch_0, epoch_1) =>
PersistedEpoch::Genesis(f(h, n, epoch_0), f(h, n, epoch_1)),
PersistedEpoch::Regular(epoch_n) => PersistedEpoch::Regular(f(h, n, epoch_n)),
}
}
}
#[derive(Encode, Decode, PartialEq, Eq, Debug)]
pub enum PersistedEpochHeader<E: Epoch> {
Genesis(EpochHeader<E>, EpochHeader<E>),
Regular(EpochHeader<E>),
}
impl<E: Epoch> Clone for PersistedEpochHeader<E> {
fn clone(&self) -> Self {
match self {
Self::Genesis(epoch_0, epoch_1) => Self::Genesis(epoch_0.clone(), epoch_1.clone()),
Self::Regular(epoch_n) => Self::Regular(epoch_n.clone()),
}
}
}
impl<E: Epoch> PersistedEpochHeader<E> {
pub fn map<B>(self) -> PersistedEpochHeader<B>
where
B: Epoch<Slot = E::Slot>,
{
match self {
PersistedEpochHeader::Genesis(epoch_0, epoch_1) => PersistedEpochHeader::Genesis(
EpochHeader { start_slot: epoch_0.start_slot, end_slot: epoch_0.end_slot },
EpochHeader { start_slot: epoch_1.start_slot, end_slot: epoch_1.end_slot },
),
PersistedEpochHeader::Regular(epoch_n) => PersistedEpochHeader::Regular(EpochHeader {
start_slot: epoch_n.start_slot,
end_slot: epoch_n.end_slot,
}),
}
}
}
#[must_use = "Freshly-incremented epoch must be imported with `EpochChanges::import`"]
pub struct IncrementedEpoch<E: Epoch>(PersistedEpoch<E>);
impl<E: Epoch> AsRef<E> for IncrementedEpoch<E> {
fn as_ref(&self) -> &E {
match self.0 {
PersistedEpoch::Genesis(_, ref epoch_1) => epoch_1,
PersistedEpoch::Regular(ref epoch_n) => epoch_n,
}
}
}
#[derive(Clone, Encode, Decode, Debug)]
pub struct EpochChanges<Hash, Number, E: Epoch> {
inner: ForkTree<Hash, Number, PersistedEpochHeader<E>>,
epochs: BTreeMap<(Hash, Number), PersistedEpoch<E>>,
}
fn fake_head_hash<H: AsRef<[u8]> + AsMut<[u8]> + Clone>(parent_hash: &H) -> H {
let mut h = parent_hash.clone();
h.as_mut()[0] ^= 0b10000000;
h
}
impl<Hash, Number, E: Epoch> Default for EpochChanges<Hash, Number, E>
where
Hash: PartialEq + Ord,
Number: Ord,
{
fn default() -> Self {
EpochChanges { inner: ForkTree::new(), epochs: BTreeMap::new() }
}
}
impl<Hash, Number, E: Epoch> EpochChanges<Hash, Number, E>
where
Hash: PartialEq + Ord + AsRef<[u8]> + AsMut<[u8]> + Copy + std::fmt::Debug,
Number: Ord + One + Zero + Add<Output = Number> + Sub<Output = Number> + Copy + std::fmt::Debug,
{
pub fn new() -> Self {
Self::default()
}
pub fn rebalance(&mut self) {
self.inner.rebalance()
}
pub fn map<B, F>(self, mut f: F) -> EpochChanges<Hash, Number, B>
where
B: Epoch<Slot = E::Slot>,
F: FnMut(&Hash, &Number, E) -> B,
{
EpochChanges {
inner: self.inner.map(&mut |_, _, header: PersistedEpochHeader<E>| header.map()),
epochs: self
.epochs
.into_iter()
.map(|((hash, number), epoch)| ((hash, number), epoch.map(&hash, &number, &mut f)))
.collect(),
}
}
pub fn prune_finalized<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: &Hash,
number: Number,
slot: E::Slot,
) -> Result<(), fork_tree::Error<D::Error>> {
let is_descendent_of = descendent_of_builder.build_is_descendent_of(None);
let predicate = |epoch: &PersistedEpochHeader<E>| match *epoch {
PersistedEpochHeader::Genesis(_, ref epoch_1) => epoch_1.start_slot <= slot,
PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot,
};
let removed = self.inner.prune(hash, &number, &is_descendent_of, &predicate)?;
for (hash, number, _) in removed {
self.epochs.remove(&(hash, number));
}
Ok(())
}
pub fn epoch(&self, id: &EpochIdentifier<Hash, Number>) -> Option<&E> {
self.epochs.get(&(id.hash, id.number)).and_then(|v| match v {
PersistedEpoch::Genesis(ref epoch_0, _)
if id.position == EpochIdentifierPosition::Genesis0 =>
Some(epoch_0),
PersistedEpoch::Genesis(_, ref epoch_1)
if id.position == EpochIdentifierPosition::Genesis1 =>
Some(epoch_1),
PersistedEpoch::Regular(ref epoch_n)
if id.position == EpochIdentifierPosition::Regular =>
Some(epoch_n),
_ => None,
})
}
pub fn viable_epoch<G>(
&self,
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
make_genesis: G,
) -> Option<ViableEpoch<E, &E>>
where
G: FnOnce(E::Slot) -> E,
{
match descriptor {
ViableEpochDescriptor::UnimportedGenesis(slot) =>
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))),
ViableEpochDescriptor::Signaled(identifier, _) =>
self.epoch(identifier).map(ViableEpoch::Signaled),
}
}
pub fn epoch_mut(&mut self, id: &EpochIdentifier<Hash, Number>) -> Option<&mut E> {
self.epochs.get_mut(&(id.hash, id.number)).and_then(|v| match v {
PersistedEpoch::Genesis(ref mut epoch_0, _)
if id.position == EpochIdentifierPosition::Genesis0 =>
Some(epoch_0),
PersistedEpoch::Genesis(_, ref mut epoch_1)
if id.position == EpochIdentifierPosition::Genesis1 =>
Some(epoch_1),
PersistedEpoch::Regular(ref mut epoch_n)
if id.position == EpochIdentifierPosition::Regular =>
Some(epoch_n),
_ => None,
})
}
pub fn viable_epoch_mut<G>(
&mut self,
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
make_genesis: G,
) -> Option<ViableEpoch<E, &mut E>>
where
G: FnOnce(E::Slot) -> E,
{
match descriptor {
ViableEpochDescriptor::UnimportedGenesis(slot) =>
Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))),
ViableEpochDescriptor::Signaled(identifier, _) =>
self.epoch_mut(identifier).map(ViableEpoch::Signaled),
}
}
pub fn epoch_data<G>(
&self,
descriptor: &ViableEpochDescriptor<Hash, Number, E>,
make_genesis: G,
) -> Option<E>
where
G: FnOnce(E::Slot) -> E,
E: Clone,
{
match descriptor {
ViableEpochDescriptor::UnimportedGenesis(slot) => Some(make_genesis(*slot)),
ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(identifier).cloned(),
}
}
pub fn epoch_data_for_child_of<D: IsDescendentOfBuilder<Hash>, G>(
&self,
descendent_of_builder: D,
parent_hash: &Hash,
parent_number: Number,
slot: E::Slot,
make_genesis: G,
) -> Result<Option<E>, fork_tree::Error<D::Error>>
where
G: FnOnce(E::Slot) -> E,
E: Clone,
{
let descriptor = self.epoch_descriptor_for_child_of(
descendent_of_builder,
parent_hash,
parent_number,
slot,
)?;
Ok(descriptor.and_then(|des| self.epoch_data(&des, make_genesis)))
}
pub fn epoch_descriptor_for_child_of<D: IsDescendentOfBuilder<Hash>>(
&self,
descendent_of_builder: D,
parent_hash: &Hash,
parent_number: Number,
slot: E::Slot,
) -> Result<Option<ViableEpochDescriptor<Hash, Number, E>>, fork_tree::Error<D::Error>> {
if parent_number == Zero::zero() {
return Ok(Some(ViableEpochDescriptor::UnimportedGenesis(slot)))
}
let fake_head_hash = fake_head_hash(parent_hash);
let is_descendent_of =
descendent_of_builder.build_is_descendent_of(Some((fake_head_hash, *parent_hash)));
let predicate = |epoch: &PersistedEpochHeader<E>| match *epoch {
PersistedEpochHeader::Genesis(ref epoch_0, _) => epoch_0.start_slot <= slot,
PersistedEpochHeader::Regular(ref epoch_n) => epoch_n.start_slot <= slot,
};
self.inner
.find_node_where(
&fake_head_hash,
&(parent_number + One::one()),
&is_descendent_of,
&predicate,
)
.map(|n| {
n.map(|node| {
(
match node.data {
PersistedEpochHeader::Genesis(ref epoch_0, ref epoch_1) => {
if epoch_1.start_slot <= slot {
(EpochIdentifierPosition::Genesis1, epoch_1.clone())
} else {
(EpochIdentifierPosition::Genesis0, epoch_0.clone())
}
},
PersistedEpochHeader::Regular(ref epoch_n) =>
(EpochIdentifierPosition::Regular, epoch_n.clone()),
},
node,
)
})
.map(|((position, header), node)| {
ViableEpochDescriptor::Signaled(
EpochIdentifier { position, hash: node.hash, number: node.number },
header,
)
})
})
}
pub fn import<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: Hash,
number: Number,
parent_hash: Hash,
epoch: IncrementedEpoch<E>,
) -> Result<(), fork_tree::Error<D::Error>> {
let is_descendent_of =
descendent_of_builder.build_is_descendent_of(Some((hash, parent_hash)));
let IncrementedEpoch(epoch) = epoch;
let header = PersistedEpochHeader::<E>::from(&epoch);
let res = self.inner.import(hash, number, header, &is_descendent_of);
match res {
Ok(_) | Err(fork_tree::Error::Duplicate) => {
self.epochs.insert((hash, number), epoch);
Ok(())
},
Err(e) => Err(e),
}
}
pub fn reset(&mut self, parent_hash: Hash, hash: Hash, number: Number, current: E, next: E) {
self.inner = ForkTree::new();
self.epochs.clear();
let persisted = PersistedEpoch::Regular(current);
let header = PersistedEpochHeader::from(&persisted);
let _res = self.inner.import(parent_hash, number - One::one(), header, &|_, _| {
Ok(false) as Result<bool, fork_tree::Error<ClientError>>
});
self.epochs.insert((parent_hash, number - One::one()), persisted);
let persisted = PersistedEpoch::Regular(next);
let header = PersistedEpochHeader::from(&persisted);
let _res = self.inner.import(hash, number, header, &|_, _| {
Ok(true) as Result<bool, fork_tree::Error<ClientError>>
});
self.epochs.insert((hash, number), persisted);
}
pub fn revert<D: IsDescendentOfBuilder<Hash>>(
&mut self,
descendent_of_builder: D,
hash: Hash,
number: Number,
) {
let is_descendent_of = descendent_of_builder.build_is_descendent_of(None);
let filter = |node_hash: &Hash, node_num: &Number, _: &PersistedEpochHeader<E>| {
if number >= *node_num &&
(is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash)
{
FilterAction::KeepNode
} else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() {
FilterAction::Remove
} else {
FilterAction::KeepTree
}
};
self.inner.drain_filter(filter).for_each(|(h, n, _)| {
self.epochs.remove(&(h, n));
});
}
pub fn tree(&self) -> &ForkTree<Hash, Number, PersistedEpochHeader<E>> {
&self.inner
}
}
pub type EpochChangesFor<Block, Epoch> =
EpochChanges<<Block as BlockT>::Hash, NumberFor<Block>, Epoch>;
pub type SharedEpochChanges<Block, Epoch> =
sc_consensus::shared_data::SharedData<EpochChangesFor<Block, Epoch>>;
#[cfg(test)]
mod tests {
use super::{Epoch as EpochT, *};
#[derive(Debug, PartialEq)]
pub struct TestError;
impl std::fmt::Display for TestError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "TestError")
}
}
impl std::error::Error for TestError {}
impl<'a, F: 'a, H: 'a + PartialEq + std::fmt::Debug> IsDescendentOfBuilder<H> for &'a F
where
F: Fn(&H, &H) -> Result<bool, TestError>,
{
type Error = TestError;
type IsDescendentOf = Box<dyn Fn(&H, &H) -> Result<bool, TestError> + 'a>;
fn build_is_descendent_of(&self, current: Option<(H, H)>) -> Self::IsDescendentOf {
let f = *self;
Box::new(move |base, head| {
let mut head = head;
if let Some((ref c_head, ref c_parent)) = current {
if head == c_head {
if base == c_parent {
return Ok(true)
} else {
head = c_parent;
}
}
}
f(base, head)
})
}
}
type Hash = [u8; 1];
type Slot = u64;
#[derive(Debug, Clone, Eq, PartialEq)]
struct Epoch {
start_slot: Slot,
duration: Slot,
}
impl EpochT for Epoch {
type NextEpochDescriptor = ();
type Slot = Slot;
fn increment(&self, _: ()) -> Self {
Epoch { start_slot: self.start_slot + self.duration, duration: self.duration }
}
fn end_slot(&self) -> Slot {
self.start_slot + self.duration
}
fn start_slot(&self) -> Slot {
self.start_slot
}
}
#[test]
fn genesis_epoch_is_created_but_not_imported() {
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let epoch_changes = EpochChanges::<_, _, Epoch>::new();
let genesis_epoch = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 10101)
.unwrap()
.unwrap();
match genesis_epoch {
ViableEpochDescriptor::UnimportedGenesis(slot) => {
assert_eq!(slot, 10101u64);
},
_ => panic!("should be unimported genesis"),
};
let genesis_epoch_2 = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 10102)
.unwrap()
.unwrap();
match genesis_epoch_2 {
ViableEpochDescriptor::UnimportedGenesis(slot) => {
assert_eq!(slot, 10102u64);
},
_ => panic!("should be unimported genesis"),
};
}
#[test]
fn epoch_changes_between_blocks() {
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B" || b == *b"C" || b == *b"D"),
(b"B", b) | (b"C", b) => Ok(b == *b"D"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let make_genesis = |slot| Epoch { start_slot: slot, duration: 100 };
let mut epoch_changes = EpochChanges::<_, _, Epoch>::new();
let genesis_epoch = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100)
.unwrap()
.unwrap();
assert_eq!(genesis_epoch, ViableEpochDescriptor::UnimportedGenesis(100));
let import_epoch_1 =
epoch_changes.viable_epoch(&genesis_epoch, &make_genesis).unwrap().increment(());
let epoch_1 = import_epoch_1.as_ref().clone();
epoch_changes
.import(&is_descendent_of, *b"A", 1, *b"0", import_epoch_1)
.unwrap();
let genesis_epoch = epoch_changes.epoch_data(&genesis_epoch, &make_genesis).unwrap();
assert!(is_descendent_of(b"0", b"A").unwrap());
let end_slot = genesis_epoch.end_slot();
assert_eq!(end_slot, epoch_1.start_slot);
{
let x = epoch_changes
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, end_slot - 1, &make_genesis)
.unwrap()
.unwrap();
assert_eq!(x, genesis_epoch);
}
{
let x = epoch_changes
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, end_slot, &make_genesis)
.unwrap()
.unwrap();
assert_eq!(x, epoch_1);
}
{
let x = epoch_changes
.epoch_data_for_child_of(
&is_descendent_of,
b"A",
1,
epoch_1.end_slot() - 1,
&make_genesis,
)
.unwrap()
.unwrap();
assert_eq!(x, epoch_1);
}
}
#[test]
fn two_block_ones_dont_conflict() {
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (base, *block) {
(b"A", b) => Ok(b == *b"B"),
(b"X", b) => Ok(b == *b"Y"),
(b"0", _) => Ok(true),
_ => Ok(false),
}
};
let duration = 100;
let make_genesis = |slot| Epoch { start_slot: slot, duration };
let mut epoch_changes = EpochChanges::new();
let next_descriptor = ();
{
let genesis_epoch_a_descriptor = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 100)
.unwrap()
.unwrap();
let incremented_epoch = epoch_changes
.viable_epoch(&genesis_epoch_a_descriptor, &make_genesis)
.unwrap()
.increment(next_descriptor);
epoch_changes
.import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch)
.unwrap();
}
{
let genesis_epoch_x_descriptor = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000)
.unwrap()
.unwrap();
let incremented_epoch = epoch_changes
.viable_epoch(&genesis_epoch_x_descriptor, &make_genesis)
.unwrap()
.increment(next_descriptor);
epoch_changes
.import(&is_descendent_of, *b"X", 1, *b"0", incremented_epoch)
.unwrap();
}
{
let epoch_for_a_child = epoch_changes
.epoch_data_for_child_of(&is_descendent_of, b"A", 1, 101, &make_genesis)
.unwrap()
.unwrap();
assert_eq!(epoch_for_a_child, make_genesis(100));
let epoch_for_x_child = epoch_changes
.epoch_data_for_child_of(&is_descendent_of, b"X", 1, 1001, &make_genesis)
.unwrap()
.unwrap();
assert_eq!(epoch_for_x_child, make_genesis(1000));
let epoch_for_x_child_before_genesis = epoch_changes
.epoch_data_for_child_of(&is_descendent_of, b"X", 1, 101, &make_genesis)
.unwrap();
assert!(epoch_for_x_child_before_genesis.is_none());
}
}
#[test]
fn prune_removes_stale_nodes() {
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 };
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot)
.unwrap()
.unwrap();
let next_epoch_desc = epoch_changes
.viable_epoch(&epoch_descriptor, &make_genesis)
.unwrap()
.increment(());
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);
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);
import_at(100, b"H", 10, b"0", 0);
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", b"H"]);
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"F", b"G"]);
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"G"]);
}
#[test]
fn near_genesis_prune_works() {
let is_descendent_of = |base: &Hash, block: &Hash| -> Result<bool, TestError> {
match (block, base) {
| (b"A", b"0") |
(b"B", b"0" | b"A") |
(b"C", b"0" | b"A" | b"B") |
(b"D", b"0" | b"A" | b"B" | b"C") |
(b"E", b"0" | b"A" | b"B" | b"C" | b"D") |
(b"F", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") |
(b"G", b"0" | b"A" | b"B" | b"C" | b"D" | b"E") |
(b"H", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G") |
(b"I", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H") |
(b"J", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I") |
(b"K", b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J") |
(
b"L",
b"0" | b"A" | b"B" | b"C" | b"D" | b"E" | b"G" | b"H" | b"I" | b"J" | b"K",
) => Ok(true),
_ => Ok(false),
}
};
let mut epoch_changes = EpochChanges::new();
let epoch = Epoch { start_slot: 278183811, duration: 5 };
let epoch = PersistedEpoch::Genesis(epoch.clone(), epoch.increment(()));
epoch_changes
.import(&is_descendent_of, *b"A", 1, Default::default(), IncrementedEpoch(epoch))
.unwrap();
let import_at = |epoch_changes: &mut EpochChanges<_, _, Epoch>,
slot,
hash: &Hash,
number,
parent_hash,
parent_number| {
let make_genesis = |slot| Epoch { start_slot: slot, duration: 5 };
let epoch_descriptor = epoch_changes
.epoch_descriptor_for_child_of(&is_descendent_of, parent_hash, parent_number, slot)
.unwrap()
.unwrap();
let next_epoch_desc = epoch_changes
.viable_epoch(&epoch_descriptor, &make_genesis)
.unwrap()
.increment(());
epoch_changes
.import(&is_descendent_of, *hash, number, *parent_hash, next_epoch_desc)
.unwrap();
};
epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap();
import_at(&mut epoch_changes, 278183816, b"G", 6, b"E", 5);
import_at(&mut epoch_changes, 278183816, b"F", 6, b"E", 5);
epoch_changes.prune_finalized(&is_descendent_of, b"C", 3, 278183813).unwrap();
let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect();
list.sort();
assert_eq!(list, vec![b"A", b"F", b"G"]);
import_at(&mut epoch_changes, 278183821, b"L", 11, b"K", 10);
epoch_changes.prune_finalized(&is_descendent_of, b"J", 9, 278183819).unwrap();
let mut list: Vec<_> = epoch_changes.inner.iter().map(|e| e.0).collect();
list.sort();
assert_eq!(list, vec![b"A", b"G", b"L"]);
}
}