// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see .
//! Implements the tree-view over the data backend which we use to determine
//! viable leaves.
//!
//! The metadata is structured as a tree, with the root implicitly being the
//! finalized block, which is not stored as part of the tree.
//!
//! Each direct descendant of the finalized block acts as its own sub-tree,
//! and as the finalized block advances, orphaned sub-trees are entirely pruned.
use polkadot_node_primitives::BlockWeight;
use polkadot_node_subsystem::ChainApiError;
use polkadot_primitives::v2::{BlockNumber, Hash};
use std::collections::HashMap;
use super::{Approval, BlockEntry, Error, LeafEntry, Timestamp, ViabilityCriteria, LOG_TARGET};
use crate::backend::{Backend, OverlayedBackend};
// A viability update to be applied to a block.
struct ViabilityUpdate(Option);
impl ViabilityUpdate {
// Apply the viability update to a single block, yielding the updated
// block entry along with a vector of children and the updates to apply
// to them.
fn apply(self, mut entry: BlockEntry) -> (BlockEntry, Vec<(Hash, ViabilityUpdate)>) {
// 1. When an ancestor has changed from unviable to viable,
// we erase the `earliest_unviable_ancestor` of all descendants
// until encountering a explicitly unviable descendant D.
//
// We then update the `earliest_unviable_ancestor` for all
// descendants of D to be equal to D.
//
// 2. When an ancestor A has changed from viable to unviable,
// we update the `earliest_unviable_ancestor` for all blocks
// to A.
//
// The following algorithm covers both cases.
//
// Furthermore, if there has been any change in viability,
// it is necessary to visit every single descendant of the root
// block.
//
// If a block B was unviable and is now viable, then every descendant
// has an `earliest_unviable_ancestor` which must be updated either
// to nothing or to the new earliest unviable ancestor.
//
// If a block B was viable and is now unviable, then every descendant
// has an `earliest_unviable_ancestor` which needs to be set to B.
let maybe_earliest_unviable = self.0;
let next_earliest_unviable = {
if maybe_earliest_unviable.is_none() && !entry.viability.is_explicitly_viable() {
Some(entry.block_hash)
} else {
maybe_earliest_unviable
}
};
entry.viability.earliest_unviable_ancestor = maybe_earliest_unviable;
let recurse = entry
.children
.iter()
.cloned()
.map(move |c| (c, ViabilityUpdate(next_earliest_unviable)))
.collect();
(entry, recurse)
}
}
// Propagate viability update to descendants of the given block. This writes
// the `base` entry as well as all descendants. If the parent of the block
// entry is not viable, this will not affect any descendants.
//
// If the block entry provided is self-unviable, then it's assumed that an
// unviability update needs to be propagated to descendants.
//
// If the block entry provided is self-viable, then it's assumed that a
// viability update needs to be propagated to descendants.
fn propagate_viability_update(
backend: &mut OverlayedBackend,
base: BlockEntry,
) -> Result<(), Error> {
enum BlockEntryRef {
Explicit(BlockEntry),
Hash(Hash),
}
if !base.viability.is_parent_viable() {
// If the parent of the block is still unviable,
// then the `earliest_viable_ancestor` will not change
// regardless of the change in the block here.
//
// Furthermore, in such cases, the set of viable leaves
// does not change at all.
backend.write_block_entry(base);
return Ok(())
}
let mut viable_leaves = backend.load_leaves()?;
// A mapping of Block Hash -> number
// Where the hash is the hash of a viable block which has
// at least 1 unviable child.
//
// The number is the number of known unviable children which is known
// as the pivot count.
let mut viability_pivots = HashMap::new();
// If the base block is itself explicitly unviable,
// this will change to a `Some(base_hash)` after the first
// invocation.
let viability_update = ViabilityUpdate(None);
// Recursively apply update to tree.
//
// As we go, we remove any blocks from the leaves which are no longer viable
// leaves. We also add blocks to the leaves-set which are obviously viable leaves.
// And we build up a frontier of blocks which may either be viable leaves or
// the ancestors of one.
let mut tree_frontier = vec![(BlockEntryRef::Explicit(base), viability_update)];
while let Some((entry_ref, update)) = tree_frontier.pop() {
let entry = match entry_ref {
BlockEntryRef::Explicit(entry) => entry,
BlockEntryRef::Hash(hash) => match backend.load_block_entry(&hash)? {
None => {
gum::warn!(
target: LOG_TARGET,
block_hash = ?hash,
"Missing expected block entry"
);
continue
},
Some(entry) => entry,
},
};
let (new_entry, children) = update.apply(entry);
if new_entry.viability.is_viable() {
// A block which is viable has a parent which is obviously not
// in the viable leaves set.
viable_leaves.remove(&new_entry.parent_hash);
// Furthermore, if the block is viable and has no children,
// it is viable by definition.
if new_entry.children.is_empty() {
viable_leaves.insert(new_entry.leaf_entry());
}
} else {
// A block which is not viable is certainly not a viable leaf.
viable_leaves.remove(&new_entry.block_hash);
// When the parent is viable but the entry itself is not, that means
// that the parent is a viability pivot. As we visit the children
// of a viability pivot, we build up an exhaustive pivot count.
if new_entry.viability.is_parent_viable() {
*viability_pivots.entry(new_entry.parent_hash).or_insert(0) += 1;
}
}
backend.write_block_entry(new_entry);
tree_frontier
.extend(children.into_iter().map(|(h, update)| (BlockEntryRef::Hash(h), update)));
}
// Revisit the viability pivots now that we've traversed the entire subtree.
// After this point, the viable leaves set is fully updated. A proof follows.
//
// If the base has become unviable, then we've iterated into all descendants,
// made them unviable and removed them from the set. We know that the parent is
// viable as this function is a no-op otherwise, so we need to see if the parent
// has other children or not.
//
// If the base has become viable, then we've iterated into all descendants,
// and found all blocks which are viable and have no children. We've already added
// those blocks to the leaf set, but what we haven't detected
// is blocks which are viable and have children, but all of the children are
// unviable.
//
// The solution of viability pivots addresses both of these:
//
// When the base has become unviable, the parent's viability is unchanged and therefore
// any leaves descending from parent but not base are still in the viable leaves set.
// If the parent has only one child which is the base, the parent is now a viable leaf.
// We've already visited the base in recursive search so the set of pivots should
// contain only a single entry `(parent, 1)`. qed.
//
// When the base has become viable, we've already iterated into every descendant
// of the base and thus have collected a set of pivots whose corresponding pivot
// counts have already been exhaustively computed from their children. qed.
for (pivot, pivot_count) in viability_pivots {
match backend.load_block_entry(&pivot)? {
None => {
// This means the block is finalized. We might reach this
// code path when the base is a child of the finalized block
// and has become unviable.
//
// Each such child is the root of its own tree
// which, as an invariant, does not depend on the viability
// of the finalized block. So no siblings need to be inspected
// and we can ignore it safely.
//
// Furthermore, if the set of viable leaves is empty, the
// finalized block is implicitly the viable leaf.
continue
},
Some(entry) =>
if entry.children.len() == pivot_count {
viable_leaves.insert(entry.leaf_entry());
},
}
}
backend.write_leaves(viable_leaves);
Ok(())
}
/// Imports a new block and applies any reversions to ancestors.
pub(crate) fn import_block(
backend: &mut OverlayedBackend,
block_hash: Hash,
block_number: BlockNumber,
parent_hash: Hash,
reversion_logs: Vec,
weight: BlockWeight,
stagnant_at: Timestamp,
) -> Result<(), Error> {
add_block(backend, block_hash, block_number, parent_hash, weight, stagnant_at)?;
apply_reversions(backend, block_hash, block_number, reversion_logs)?;
Ok(())
}
// Load the given ancestor's block entry, in descending order from the `block_hash`.
// The ancestor_number must be at least one block less than the `block_number`.
//
// The returned entry will be `None` if the range is invalid or any block in the path had
// no entry present. If any block entry was missing, it can safely be assumed to
// be finalized.
fn load_ancestor(
backend: &mut OverlayedBackend,
block_hash: Hash,
block_number: BlockNumber,
ancestor_number: BlockNumber,
) -> Result