mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 11:41:02 +00:00
Reorganising the repository - external renames and moves (#4074)
* Adding first rough ouline of the repository structure * Remove old CI stuff * add title * formatting fixes * move node-exits job's script to scripts dir * Move docs into subdir * move to bin * move maintainence scripts, configs and helpers into its own dir * add .local to ignore * move core->client * start up 'test' area * move test client * move test runtime * make test move compile * Add dependencies rule enforcement. * Fix indexing. * Update docs to reflect latest changes * Moving /srml->/paint * update docs * move client/sr-* -> primitives/ * clean old readme * remove old broken code in rhd * update lock * Step 1. * starting to untangle client * Fix after merge. * start splitting out client interfaces * move children and blockchain interfaces * Move trie and state-machine to primitives. * Fix WASM builds. * fixing broken imports * more interface moves * move backend and light to interfaces * move CallExecutor * move cli off client * moving around more interfaces * re-add consensus crates into the mix * fix subkey path * relieve client from executor * starting to pull out client from grandpa * move is_decendent_of out of client * grandpa still depends on client directly * lemme tests pass * rename srml->paint * Make it compile. * rename interfaces->client-api * Move keyring to primitives. * fixup libp2p dep * fix broken use * allow dependency enforcement to fail * move fork-tree * Moving wasm-builder * make env * move build-script-utils * fixup broken crate depdencies and names * fix imports for authority discovery * fix typo * update cargo.lock * fixing imports * Fix paths and add missing crates * re-add missing crates
This commit is contained in:
committed by
Bastian Köcher
parent
becc3b0a4f
commit
60e5011c72
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "substrate-finality-grandpa"
|
||||
version = "2.0.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
fork-tree = { path = "../../utils/fork-tree" }
|
||||
futures = "0.1.29"
|
||||
futures03 = { package = "futures-preview", version = "0.3.0-alpha.19", features = ["compat"] }
|
||||
log = "0.4.8"
|
||||
parking_lot = "0.9.0"
|
||||
tokio-executor = "0.1.8"
|
||||
tokio-timer = "0.2.11"
|
||||
rand = "0.7.2"
|
||||
codec = { package = "parity-scale-codec", version = "1.0.0", features = ["derive"] }
|
||||
sr-primitives = { path = "../../primitives/sr-primitives" }
|
||||
consensus_common = { package = "substrate-consensus-common", path = "../../primitives/consensus/common" }
|
||||
primitives = { package = "substrate-primitives", path = "../../primitives/core" }
|
||||
substrate-telemetry = { path = "../telemetry" }
|
||||
keystore = { package = "substrate-keystore", path = "../keystore" }
|
||||
serde_json = "1.0.41"
|
||||
client-api = { package = "substrate-client-api", path = "../api" }
|
||||
client = { package = "substrate-client", path = "../" }
|
||||
header-metadata = { package = "substrate-header-metadata", path = "../header-metadata" }
|
||||
inherents = { package = "substrate-inherents", path = "../../primitives/inherents" }
|
||||
network = { package = "substrate-network", path = "../network" }
|
||||
paint-finality-tracker = { path = "../../paint/finality-tracker" }
|
||||
fg_primitives = { package = "substrate-finality-grandpa-primitives", path = "../../primitives/finality-grandpa" }
|
||||
grandpa = { package = "finality-grandpa", version = "0.9.0", features = ["derive-codec"] }
|
||||
|
||||
[dev-dependencies]
|
||||
grandpa = { package = "finality-grandpa", version = "0.9.0", features = ["derive-codec", "test-helpers"] }
|
||||
network = { package = "substrate-network", path = "../network", features = ["test-helpers"] }
|
||||
keyring = { package = "substrate-keyring", path = "../../primitives/keyring" }
|
||||
test-client = { package = "substrate-test-runtime-client", path = "../../test/utils/runtime/client"}
|
||||
babe_primitives = { package = "substrate-consensus-babe-primitives", path = "../../primitives/consensus/babe" }
|
||||
state_machine = { package = "substrate-state-machine", path = "../../primitives/state-machine" }
|
||||
env_logger = "0.7.0"
|
||||
tokio = "0.1.22"
|
||||
tempfile = "3.1.0"
|
||||
sr-api = { path = "../../primitives/sr-api" }
|
||||
@@ -0,0 +1,787 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for dealing with authorities, authority sets, and handoffs.
|
||||
|
||||
use fork_tree::ForkTree;
|
||||
use parking_lot::RwLock;
|
||||
use grandpa::voter_set::VoterSet;
|
||||
use codec::{Encode, Decode};
|
||||
use log::{debug, info};
|
||||
use substrate_telemetry::{telemetry, CONSENSUS_INFO};
|
||||
use fg_primitives::{AuthorityId, AuthorityList};
|
||||
|
||||
use std::cmp::Ord;
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Add;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A shared authority set.
|
||||
pub(crate) struct SharedAuthoritySet<H, N> {
|
||||
inner: Arc<RwLock<AuthoritySet<H, N>>>,
|
||||
}
|
||||
|
||||
impl<H, N> Clone for SharedAuthoritySet<H, N> {
|
||||
fn clone(&self) -> Self {
|
||||
SharedAuthoritySet { inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> SharedAuthoritySet<H, N> {
|
||||
/// Acquire a reference to the inner read-write lock.
|
||||
pub(crate) fn inner(&self) -> &RwLock<AuthoritySet<H, N>> {
|
||||
&*self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Eq, N> SharedAuthoritySet<H, N>
|
||||
where N: Add<Output=N> + Ord + Clone + Debug,
|
||||
H: Clone + Debug
|
||||
{
|
||||
/// Get the earliest limit-block number, if any.
|
||||
pub(crate) fn current_limit(&self) -> Option<N> {
|
||||
self.inner.read().current_limit()
|
||||
}
|
||||
|
||||
/// Get the current set ID. This is incremented every time the set changes.
|
||||
pub(crate) fn set_id(&self) -> u64 {
|
||||
self.inner.read().set_id
|
||||
}
|
||||
|
||||
/// Get the current authorities and their weights (for the current set ID).
|
||||
pub(crate) fn current_authorities(&self) -> VoterSet<AuthorityId> {
|
||||
self.inner.read().current_authorities.iter().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> From<AuthoritySet<H, N>> for SharedAuthoritySet<H, N> {
|
||||
fn from(set: AuthoritySet<H, N>) -> Self {
|
||||
SharedAuthoritySet { inner: Arc::new(RwLock::new(set)) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Status of the set after changes were applied.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Status<H, N> {
|
||||
/// Whether internal changes were made.
|
||||
pub(crate) changed: bool,
|
||||
/// `Some` when underlying authority set has changed, containing the
|
||||
/// block where that set changed.
|
||||
pub(crate) new_set_block: Option<(H, N)>,
|
||||
}
|
||||
|
||||
/// A set of authorities.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
|
||||
pub(crate) struct AuthoritySet<H, N> {
|
||||
pub(crate) current_authorities: AuthorityList,
|
||||
pub(crate) set_id: u64,
|
||||
// Tree of pending standard changes across forks. Standard changes are
|
||||
// enacted on finality and must be enacted (i.e. finalized) in-order across
|
||||
// a given branch
|
||||
pub(crate) pending_standard_changes: ForkTree<H, N, PendingChange<H, N>>,
|
||||
// Pending forced changes across different forks (at most one per fork).
|
||||
// Forced changes are enacted on block depth (not finality), for this reason
|
||||
// only one forced change should exist per fork.
|
||||
pub(crate) pending_forced_changes: Vec<PendingChange<H, N>>,
|
||||
}
|
||||
|
||||
impl<H, N> AuthoritySet<H, N>
|
||||
where H: PartialEq,
|
||||
N: Ord,
|
||||
{
|
||||
/// Get a genesis set with given authorities.
|
||||
pub(crate) fn genesis(initial: AuthorityList) -> Self {
|
||||
AuthoritySet {
|
||||
current_authorities: initial,
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current set id and a reference to the current authority set.
|
||||
pub(crate) fn current(&self) -> (u64, &[(AuthorityId, u64)]) {
|
||||
(self.set_id, &self.current_authorities[..])
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Eq, N> AuthoritySet<H, N>
|
||||
where
|
||||
N: Add<Output=N> + Ord + Clone + Debug,
|
||||
H: Clone + Debug
|
||||
{
|
||||
fn add_standard_change<F, E>(
|
||||
&mut self,
|
||||
pending: PendingChange<H, N>,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<(), fork_tree::Error<E>> where
|
||||
F: Fn(&H, &H) -> Result<bool, E>,
|
||||
E: std::error::Error,
|
||||
{
|
||||
let hash = pending.canon_hash.clone();
|
||||
let number = pending.canon_height.clone();
|
||||
|
||||
debug!(target: "afg", "Inserting potential standard set change signaled at block {:?} \
|
||||
(delayed by {:?} blocks).",
|
||||
(&number, &hash), pending.delay);
|
||||
|
||||
self.pending_standard_changes.import(
|
||||
hash.clone(),
|
||||
number.clone(),
|
||||
pending,
|
||||
is_descendent_of,
|
||||
)?;
|
||||
|
||||
debug!(target: "afg", "There are now {} alternatives for the next pending standard change (roots), \
|
||||
and a total of {} pending standard changes (across all forks).",
|
||||
self.pending_standard_changes.roots().count(),
|
||||
self.pending_standard_changes.iter().count(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_forced_change<F, E>(
|
||||
&mut self,
|
||||
pending: PendingChange<H, N>,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<(), fork_tree::Error<E>> where
|
||||
F: Fn(&H, &H) -> Result<bool, E>,
|
||||
E: std::error::Error,
|
||||
{
|
||||
for change in self.pending_forced_changes.iter() {
|
||||
if change.canon_hash == pending.canon_hash ||
|
||||
is_descendent_of(&change.canon_hash, &pending.canon_hash)?
|
||||
{
|
||||
return Err(fork_tree::Error::UnfinalizedAncestor);
|
||||
}
|
||||
}
|
||||
|
||||
// ordered first by effective number and then by signal-block number.
|
||||
let key = (pending.effective_number(), pending.canon_height.clone());
|
||||
let idx = self.pending_forced_changes
|
||||
.binary_search_by_key(&key, |change| (
|
||||
change.effective_number(),
|
||||
change.canon_height.clone(),
|
||||
))
|
||||
.unwrap_or_else(|i| i);
|
||||
|
||||
debug!(target: "afg", "Inserting potential forced set change at block {:?} \
|
||||
(delayed by {:?} blocks).",
|
||||
(&pending.canon_height, &pending.canon_hash), pending.delay);
|
||||
|
||||
self.pending_forced_changes.insert(idx, pending);
|
||||
|
||||
debug!(target: "afg", "There are now {} pending forced changes.", self.pending_forced_changes.len());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Note an upcoming pending transition. Multiple pending standard changes
|
||||
/// on the same branch can be added as long as they don't overlap. Forced
|
||||
/// changes are restricted to one per fork. This method assumes that changes
|
||||
/// on the same branch will be added in-order. The given function
|
||||
/// `is_descendent_of` should return `true` if the second hash (target) is a
|
||||
/// descendent of the first hash (base).
|
||||
pub(crate) fn add_pending_change<F, E>(
|
||||
&mut self,
|
||||
pending: PendingChange<H, N>,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<(), fork_tree::Error<E>> where
|
||||
F: Fn(&H, &H) -> Result<bool, E>,
|
||||
E: std::error::Error,
|
||||
{
|
||||
match pending.delay_kind {
|
||||
DelayKind::Best { .. } => {
|
||||
self.add_forced_change(pending, is_descendent_of)
|
||||
},
|
||||
DelayKind::Finalized => {
|
||||
self.add_standard_change(pending, is_descendent_of)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspect pending changes. Standard pending changes are iterated first,
|
||||
/// and the changes in the tree are traversed in pre-order, afterwards all
|
||||
/// forced changes are iterated.
|
||||
pub(crate) fn pending_changes(&self) -> impl Iterator<Item=&PendingChange<H, N>> {
|
||||
self.pending_standard_changes.iter().map(|(_, _, c)| c)
|
||||
.chain(self.pending_forced_changes.iter())
|
||||
}
|
||||
|
||||
/// Get the earliest limit-block number, if any. If there are pending changes across
|
||||
/// different forks, this method will return the earliest effective number (across the
|
||||
/// different branches). Only standard changes are taken into account for the current
|
||||
/// limit, since any existing forced change should preclude the voter from voting.
|
||||
pub(crate) fn current_limit(&self) -> Option<N> {
|
||||
self.pending_standard_changes.roots()
|
||||
.min_by_key(|&(_, _, c)| c.effective_number())
|
||||
.map(|(_, _, c)| c.effective_number())
|
||||
}
|
||||
|
||||
/// Apply or prune any pending transitions based on a best-block trigger.
|
||||
///
|
||||
/// Returns `Ok((median, new_set))` when a forced change has occurred. The
|
||||
/// median represents the median last finalized block at the time the change
|
||||
/// was signaled, and it should be used as the canon block when starting the
|
||||
/// new grandpa voter. Only alters the internal state in this case.
|
||||
///
|
||||
/// These transitions are always forced and do not lead to justifications
|
||||
/// which light clients can follow.
|
||||
pub(crate) fn apply_forced_changes<F, E>(
|
||||
&self,
|
||||
best_hash: H,
|
||||
best_number: N,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<Option<(N, Self)>, E>
|
||||
where F: Fn(&H, &H) -> Result<bool, E>,
|
||||
{
|
||||
let mut new_set = None;
|
||||
|
||||
for change in self.pending_forced_changes.iter()
|
||||
.take_while(|c| c.effective_number() <= best_number) // to prevent iterating too far
|
||||
.filter(|c| c.effective_number() == best_number)
|
||||
{
|
||||
// check if the given best block is in the same branch as the block that signaled the change.
|
||||
if is_descendent_of(&change.canon_hash, &best_hash)? {
|
||||
// apply this change: make the set canonical
|
||||
info!(target: "finality", "Applying authority set change forced at block #{:?}",
|
||||
change.canon_height);
|
||||
telemetry!(CONSENSUS_INFO; "afg.applying_forced_authority_set_change";
|
||||
"block" => ?change.canon_height
|
||||
);
|
||||
|
||||
let median_last_finalized = match change.delay_kind {
|
||||
DelayKind::Best { ref median_last_finalized } => median_last_finalized.clone(),
|
||||
_ => unreachable!("pending_forced_changes only contains forced changes; forced changes have delay kind Best; qed."),
|
||||
};
|
||||
|
||||
new_set = Some((median_last_finalized, AuthoritySet {
|
||||
current_authorities: change.next_authorities.clone(),
|
||||
set_id: self.set_id + 1,
|
||||
pending_standard_changes: ForkTree::new(), // new set, new changes.
|
||||
pending_forced_changes: Vec::new(),
|
||||
}));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// we don't wipe forced changes until another change is
|
||||
// applied
|
||||
}
|
||||
|
||||
Ok(new_set)
|
||||
}
|
||||
|
||||
/// Apply or prune any pending transitions based on a finality trigger. This
|
||||
/// method ensures that if there are multiple changes in the same branch,
|
||||
/// finalizing this block won't finalize past multiple transitions (i.e.
|
||||
/// transitions must be finalized in-order). The given function
|
||||
/// `is_descendent_of` should return `true` if the second hash (target) is a
|
||||
/// descendent of the first hash (base).
|
||||
///
|
||||
/// When the set has changed, the return value will be `Ok(Some((H, N)))`
|
||||
/// which is the canonical block where the set last changed (i.e. the given
|
||||
/// hash and number).
|
||||
pub(crate) fn apply_standard_changes<F, E>(
|
||||
&mut self,
|
||||
finalized_hash: H,
|
||||
finalized_number: N,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<Status<H, N>, fork_tree::Error<E>>
|
||||
where F: Fn(&H, &H) -> Result<bool, E>,
|
||||
E: std::error::Error,
|
||||
{
|
||||
let mut status = Status {
|
||||
changed: false,
|
||||
new_set_block: None,
|
||||
};
|
||||
|
||||
match self.pending_standard_changes.finalize_with_descendent_if(
|
||||
&finalized_hash,
|
||||
finalized_number.clone(),
|
||||
is_descendent_of,
|
||||
|change| change.effective_number() <= finalized_number
|
||||
)? {
|
||||
fork_tree::FinalizationResult::Changed(change) => {
|
||||
status.changed = true;
|
||||
|
||||
// if we are able to finalize any standard change then we can
|
||||
// discard all pending forced changes (on different forks)
|
||||
self.pending_forced_changes.clear();
|
||||
|
||||
if let Some(change) = change {
|
||||
info!(target: "finality", "Applying authority set change scheduled at block #{:?}",
|
||||
change.canon_height);
|
||||
telemetry!(CONSENSUS_INFO; "afg.applying_scheduled_authority_set_change";
|
||||
"block" => ?change.canon_height
|
||||
);
|
||||
|
||||
self.current_authorities = change.next_authorities;
|
||||
self.set_id += 1;
|
||||
|
||||
status.new_set_block = Some((
|
||||
finalized_hash,
|
||||
finalized_number,
|
||||
));
|
||||
}
|
||||
},
|
||||
fork_tree::FinalizationResult::Unchanged => {},
|
||||
}
|
||||
|
||||
Ok(status)
|
||||
}
|
||||
|
||||
/// Check whether the given finalized block number enacts any standard
|
||||
/// authority set change (without triggering it), ensuring that if there are
|
||||
/// multiple changes in the same branch, finalizing this block won't
|
||||
/// finalize past multiple transitions (i.e. transitions must be finalized
|
||||
/// in-order). Returns `Some(true)` if the block being finalized enacts a
|
||||
/// change that can be immediately applied, `Some(false)` if the block being
|
||||
/// finalized enacts a change but it cannot be applied yet since there are
|
||||
/// other dependent changes, and `None` if no change is enacted. The given
|
||||
/// function `is_descendent_of` should return `true` if the second hash
|
||||
/// (target) is a descendent of the first hash (base).
|
||||
pub fn enacts_standard_change<F, E>(
|
||||
&self,
|
||||
finalized_hash: H,
|
||||
finalized_number: N,
|
||||
is_descendent_of: &F,
|
||||
) -> Result<Option<bool>, fork_tree::Error<E>>
|
||||
where F: Fn(&H, &H) -> Result<bool, E>,
|
||||
E: std::error::Error,
|
||||
{
|
||||
self.pending_standard_changes.finalizes_any_with_descendent_if(
|
||||
&finalized_hash,
|
||||
finalized_number.clone(),
|
||||
is_descendent_of,
|
||||
|change| change.effective_number() == finalized_number
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Kinds of delays for pending changes.
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
|
||||
pub(crate) enum DelayKind<N> {
|
||||
/// Depth in finalized chain.
|
||||
Finalized,
|
||||
/// Depth in best chain. The median last finalized block is calculated at the time the
|
||||
/// change was signaled.
|
||||
Best { median_last_finalized: N },
|
||||
}
|
||||
|
||||
/// A pending change to the authority set.
|
||||
///
|
||||
/// This will be applied when the announcing block is at some depth within
|
||||
/// the finalized or unfinalized chain.
|
||||
#[derive(Debug, Clone, Encode, PartialEq)]
|
||||
pub(crate) struct PendingChange<H, N> {
|
||||
/// The new authorities and weights to apply.
|
||||
pub(crate) next_authorities: AuthorityList,
|
||||
/// How deep in the chain the announcing block must be
|
||||
/// before the change is applied.
|
||||
pub(crate) delay: N,
|
||||
/// The announcing block's height.
|
||||
pub(crate) canon_height: N,
|
||||
/// The announcing block's hash.
|
||||
pub(crate) canon_hash: H,
|
||||
/// The delay kind.
|
||||
pub(crate) delay_kind: DelayKind<N>,
|
||||
}
|
||||
|
||||
impl<H: Decode, N: Decode> Decode for PendingChange<H, N> {
|
||||
fn decode<I: codec::Input>(value: &mut I) -> Result<Self, codec::Error> {
|
||||
let next_authorities = Decode::decode(value)?;
|
||||
let delay = Decode::decode(value)?;
|
||||
let canon_height = Decode::decode(value)?;
|
||||
let canon_hash = Decode::decode(value)?;
|
||||
|
||||
let delay_kind = DelayKind::decode(value).unwrap_or(DelayKind::Finalized);
|
||||
|
||||
Ok(PendingChange {
|
||||
next_authorities,
|
||||
delay,
|
||||
canon_height,
|
||||
canon_hash,
|
||||
delay_kind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N: Add<Output=N> + Clone> PendingChange<H, N> {
|
||||
/// Returns the effective number this change will be applied at.
|
||||
pub fn effective_number(&self) -> N {
|
||||
self.canon_height.clone() + self.delay.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use primitives::crypto::Public;
|
||||
|
||||
fn static_is_descendent_of<A>(value: bool)
|
||||
-> impl Fn(&A, &A) -> Result<bool, std::io::Error>
|
||||
{
|
||||
move |_, _| Ok(value)
|
||||
}
|
||||
|
||||
fn is_descendent_of<A, F>(f: F) -> impl Fn(&A, &A) -> Result<bool, std::io::Error>
|
||||
where F: Fn(&A, &A) -> bool
|
||||
{
|
||||
move |base, hash| Ok(f(base, hash))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn changes_iterated_in_pre_order() {
|
||||
let mut authorities = AuthoritySet {
|
||||
current_authorities: Vec::new(),
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
};
|
||||
|
||||
let change_a = PendingChange {
|
||||
next_authorities: Vec::new(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_a",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
let change_b = PendingChange {
|
||||
next_authorities: Vec::new(),
|
||||
delay: 0,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_b",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
let change_c = PendingChange {
|
||||
next_authorities: Vec::new(),
|
||||
delay: 5,
|
||||
canon_height: 10,
|
||||
canon_hash: "hash_c",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_a.clone(), &static_is_descendent_of(false)).unwrap();
|
||||
authorities.add_pending_change(change_b.clone(), &static_is_descendent_of(false)).unwrap();
|
||||
authorities.add_pending_change(change_c.clone(), &is_descendent_of(|base, hash| match (*base, *hash) {
|
||||
("hash_a", "hash_c") => true,
|
||||
("hash_b", "hash_c") => false,
|
||||
_ => unreachable!(),
|
||||
})).unwrap();
|
||||
|
||||
// forced changes are iterated last
|
||||
let change_d = PendingChange {
|
||||
next_authorities: Vec::new(),
|
||||
delay: 2,
|
||||
canon_height: 1,
|
||||
canon_hash: "hash_d",
|
||||
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
||||
};
|
||||
|
||||
let change_e = PendingChange {
|
||||
next_authorities: Vec::new(),
|
||||
delay: 2,
|
||||
canon_height: 0,
|
||||
canon_hash: "hash_e",
|
||||
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_d.clone(), &static_is_descendent_of(false)).unwrap();
|
||||
authorities.add_pending_change(change_e.clone(), &static_is_descendent_of(false)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
authorities.pending_changes().collect::<Vec<_>>(),
|
||||
vec![&change_b, &change_a, &change_c, &change_e, &change_d],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_change() {
|
||||
let mut authorities = AuthoritySet {
|
||||
current_authorities: Vec::new(),
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
};
|
||||
|
||||
let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)];
|
||||
let set_b = vec![(AuthorityId::from_slice(&[2; 32]), 5)];
|
||||
|
||||
// two competing changes at the same height on different forks
|
||||
let change_a = PendingChange {
|
||||
next_authorities: set_a.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_a",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
let change_b = PendingChange {
|
||||
next_authorities: set_b.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_b",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_a.clone(), &static_is_descendent_of(true)).unwrap();
|
||||
authorities.add_pending_change(change_b.clone(), &static_is_descendent_of(true)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
authorities.pending_changes().collect::<Vec<_>>(),
|
||||
vec![&change_b, &change_a],
|
||||
);
|
||||
|
||||
// finalizing "hash_c" won't enact the change signaled at "hash_a" but it will prune out "hash_b"
|
||||
let status = authorities.apply_standard_changes("hash_c", 11, &is_descendent_of(|base, hash| match (*base, *hash) {
|
||||
("hash_a", "hash_c") => true,
|
||||
("hash_b", "hash_c") => false,
|
||||
_ => unreachable!(),
|
||||
})).unwrap();
|
||||
|
||||
assert!(status.changed);
|
||||
assert_eq!(status.new_set_block, None);
|
||||
assert_eq!(
|
||||
authorities.pending_changes().collect::<Vec<_>>(),
|
||||
vec![&change_a],
|
||||
);
|
||||
|
||||
// finalizing "hash_d" will enact the change signaled at "hash_a"
|
||||
let status = authorities.apply_standard_changes("hash_d", 15, &is_descendent_of(|base, hash| match (*base, *hash) {
|
||||
("hash_a", "hash_d") => true,
|
||||
_ => unreachable!(),
|
||||
})).unwrap();
|
||||
|
||||
assert!(status.changed);
|
||||
assert_eq!(status.new_set_block, Some(("hash_d", 15)));
|
||||
|
||||
assert_eq!(authorities.current_authorities, set_a);
|
||||
assert_eq!(authorities.set_id, 1);
|
||||
assert_eq!(authorities.pending_changes().count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_multiple_changes_being_finalized_at_once() {
|
||||
let mut authorities = AuthoritySet {
|
||||
current_authorities: Vec::new(),
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
};
|
||||
|
||||
let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)];
|
||||
let set_c = vec![(AuthorityId::from_slice(&[2; 32]), 5)];
|
||||
|
||||
// two competing changes at the same height on different forks
|
||||
let change_a = PendingChange {
|
||||
next_authorities: set_a.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_a",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
let change_c = PendingChange {
|
||||
next_authorities: set_c.clone(),
|
||||
delay: 10,
|
||||
canon_height: 30,
|
||||
canon_hash: "hash_c",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_a.clone(), &static_is_descendent_of(true)).unwrap();
|
||||
authorities.add_pending_change(change_c.clone(), &static_is_descendent_of(true)).unwrap();
|
||||
|
||||
let is_descendent_of = is_descendent_of(|base, hash| match (*base, *hash) {
|
||||
("hash_a", "hash_b") => true,
|
||||
("hash_a", "hash_c") => true,
|
||||
("hash_a", "hash_d") => true,
|
||||
|
||||
("hash_c", "hash_b") => false,
|
||||
("hash_c", "hash_d") => true,
|
||||
|
||||
("hash_b", "hash_c") => true,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
// trying to finalize past `change_c` without finalizing `change_a` first
|
||||
match authorities.apply_standard_changes("hash_d", 40, &is_descendent_of) {
|
||||
Err(fork_tree::Error::UnfinalizedAncestor) => {},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let status = authorities.apply_standard_changes("hash_b", 15, &is_descendent_of).unwrap();
|
||||
assert!(status.changed);
|
||||
assert_eq!(status.new_set_block, Some(("hash_b", 15)));
|
||||
|
||||
assert_eq!(authorities.current_authorities, set_a);
|
||||
assert_eq!(authorities.set_id, 1);
|
||||
|
||||
// after finalizing `change_a` it should be possible to finalize `change_c`
|
||||
let status = authorities.apply_standard_changes("hash_d", 40, &is_descendent_of).unwrap();
|
||||
assert!(status.changed);
|
||||
assert_eq!(status.new_set_block, Some(("hash_d", 40)));
|
||||
|
||||
assert_eq!(authorities.current_authorities, set_c);
|
||||
assert_eq!(authorities.set_id, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enacts_standard_change_works() {
|
||||
let mut authorities = AuthoritySet {
|
||||
current_authorities: Vec::new(),
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
};
|
||||
|
||||
let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)];
|
||||
|
||||
let change_a = PendingChange {
|
||||
next_authorities: set_a.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_a",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
let change_b = PendingChange {
|
||||
next_authorities: set_a.clone(),
|
||||
delay: 10,
|
||||
canon_height: 20,
|
||||
canon_hash: "hash_b",
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_a.clone(), &static_is_descendent_of(false)).unwrap();
|
||||
authorities.add_pending_change(change_b.clone(), &static_is_descendent_of(true)).unwrap();
|
||||
|
||||
let is_descendent_of = is_descendent_of(|base, hash| match (*base, *hash) {
|
||||
("hash_a", "hash_d") => true,
|
||||
("hash_a", "hash_e") => true,
|
||||
("hash_b", "hash_d") => true,
|
||||
("hash_b", "hash_e") => true,
|
||||
("hash_a", "hash_c") => false,
|
||||
("hash_b", "hash_c") => false,
|
||||
_ => unreachable!(),
|
||||
});
|
||||
|
||||
// "hash_c" won't finalize the existing change since it isn't a descendent
|
||||
assert_eq!(
|
||||
authorities.enacts_standard_change("hash_c", 15, &is_descendent_of).unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
// "hash_d" at depth 14 won't work either
|
||||
assert_eq!(
|
||||
authorities.enacts_standard_change("hash_d", 14, &is_descendent_of).unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
// but it should work at depth 15 (change height + depth)
|
||||
assert_eq!(
|
||||
authorities.enacts_standard_change("hash_d", 15, &is_descendent_of).unwrap(),
|
||||
Some(true),
|
||||
);
|
||||
|
||||
// finalizing "hash_e" at depth 20 will trigger change at "hash_b", but
|
||||
// it can't be applied yet since "hash_a" must be applied first
|
||||
assert_eq!(
|
||||
authorities.enacts_standard_change("hash_e", 30, &is_descendent_of).unwrap(),
|
||||
Some(false),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forced_changes() {
|
||||
let mut authorities = AuthoritySet {
|
||||
current_authorities: Vec::new(),
|
||||
set_id: 0,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
};
|
||||
|
||||
let set_a = vec![(AuthorityId::from_slice(&[1; 32]), 5)];
|
||||
let set_b = vec![(AuthorityId::from_slice(&[2; 32]), 5)];
|
||||
|
||||
let change_a = PendingChange {
|
||||
next_authorities: set_a.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_a",
|
||||
delay_kind: DelayKind::Best { median_last_finalized: 42 },
|
||||
};
|
||||
|
||||
let change_b = PendingChange {
|
||||
next_authorities: set_b.clone(),
|
||||
delay: 10,
|
||||
canon_height: 5,
|
||||
canon_hash: "hash_b",
|
||||
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
||||
};
|
||||
|
||||
authorities.add_pending_change(change_a, &static_is_descendent_of(false)).unwrap();
|
||||
authorities.add_pending_change(change_b, &static_is_descendent_of(false)).unwrap();
|
||||
|
||||
// there's an effective change triggered at block 15 but not a standard one.
|
||||
// so this should do nothing.
|
||||
assert_eq!(
|
||||
authorities.enacts_standard_change("hash_c", 15, &static_is_descendent_of(true)).unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
// throw a standard change into the mix to prove that it's discarded
|
||||
// for being on the same fork.
|
||||
//
|
||||
// NOTE: after https://github.com/paritytech/substrate/issues/1861
|
||||
// this should still be rejected based on the "span" rule -- it overlaps
|
||||
// with another change on the same fork.
|
||||
let change_c = PendingChange {
|
||||
next_authorities: set_b.clone(),
|
||||
delay: 3,
|
||||
canon_height: 8,
|
||||
canon_hash: "hash_a8",
|
||||
delay_kind: DelayKind::Best { median_last_finalized: 0 },
|
||||
};
|
||||
|
||||
let is_descendent_of_a = is_descendent_of(|base: &&str, _| {
|
||||
base.starts_with("hash_a")
|
||||
});
|
||||
|
||||
assert!(authorities.add_pending_change(change_c, &is_descendent_of_a).is_err());
|
||||
|
||||
// too early.
|
||||
assert!(authorities.apply_forced_changes("hash_a10", 10, &static_is_descendent_of(true)).unwrap().is_none());
|
||||
|
||||
// too late.
|
||||
assert!(authorities.apply_forced_changes("hash_a16", 16, &static_is_descendent_of(true)).unwrap().is_none());
|
||||
|
||||
// on time -- chooses the right change.
|
||||
assert_eq!(
|
||||
authorities.apply_forced_changes("hash_a15", 15, &is_descendent_of_a).unwrap().unwrap(),
|
||||
(42, AuthoritySet {
|
||||
current_authorities: set_a,
|
||||
set_id: 1,
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Schema for stuff in the aux-db.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use codec::{Encode, Decode};
|
||||
use client_api::backend::AuxStore;
|
||||
use client_api::error::{Result as ClientResult, Error as ClientError};
|
||||
use fork_tree::ForkTree;
|
||||
use grandpa::round::State as RoundState;
|
||||
use sr_primitives::traits::{Block as BlockT, NumberFor};
|
||||
use log::{info, warn};
|
||||
use fg_primitives::{AuthorityList, SetId, RoundNumber};
|
||||
|
||||
use crate::authorities::{AuthoritySet, SharedAuthoritySet, PendingChange, DelayKind};
|
||||
use crate::consensus_changes::{SharedConsensusChanges, ConsensusChanges};
|
||||
use crate::environment::{
|
||||
CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState, VoterSetState,
|
||||
};
|
||||
use crate::NewAuthoritySet;
|
||||
|
||||
const VERSION_KEY: &[u8] = b"grandpa_schema_version";
|
||||
const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
|
||||
const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
|
||||
const CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes";
|
||||
|
||||
const CURRENT_VERSION: u32 = 2;
|
||||
|
||||
/// The voter set state.
|
||||
#[derive(Debug, Clone, Encode, Decode)]
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
pub enum V1VoterSetState<H, N> {
|
||||
/// The voter set state, currently paused.
|
||||
Paused(RoundNumber, RoundState<H, N>),
|
||||
/// The voter set state, currently live.
|
||||
Live(RoundNumber, RoundState<H, N>),
|
||||
}
|
||||
|
||||
type V0VoterSetState<H, N> = (RoundNumber, RoundState<H, N>);
|
||||
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
|
||||
struct V0PendingChange<H, N> {
|
||||
next_authorities: AuthorityList,
|
||||
delay: N,
|
||||
canon_height: N,
|
||||
canon_hash: H,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
|
||||
struct V0AuthoritySet<H, N> {
|
||||
current_authorities: AuthorityList,
|
||||
set_id: SetId,
|
||||
pending_changes: Vec<V0PendingChange<H, N>>,
|
||||
}
|
||||
|
||||
impl<H, N> Into<AuthoritySet<H, N>> for V0AuthoritySet<H, N>
|
||||
where H: Clone + Debug + PartialEq,
|
||||
N: Clone + Debug + Ord,
|
||||
{
|
||||
fn into(self) -> AuthoritySet<H, N> {
|
||||
let mut pending_standard_changes = ForkTree::new();
|
||||
|
||||
for old_change in self.pending_changes {
|
||||
let new_change = PendingChange {
|
||||
next_authorities: old_change.next_authorities,
|
||||
delay: old_change.delay,
|
||||
canon_height: old_change.canon_height,
|
||||
canon_hash: old_change.canon_hash,
|
||||
delay_kind: DelayKind::Finalized,
|
||||
};
|
||||
|
||||
if let Err(err) = pending_standard_changes.import::<_, ClientError>(
|
||||
new_change.canon_hash.clone(),
|
||||
new_change.canon_height.clone(),
|
||||
new_change,
|
||||
// previously we only supported at most one pending change per fork
|
||||
&|_, _| Ok(false),
|
||||
) {
|
||||
warn!(target: "afg", "Error migrating pending authority set change: {:?}.", err);
|
||||
warn!(target: "afg", "Node is in a potentially inconsistent state.");
|
||||
}
|
||||
}
|
||||
|
||||
AuthoritySet {
|
||||
current_authorities: self.current_authorities,
|
||||
set_id: self.set_id,
|
||||
pending_forced_changes: Vec::new(),
|
||||
pending_standard_changes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> ClientResult<Option<T>> {
|
||||
match backend.get_aux(key)? {
|
||||
None => Ok(None),
|
||||
Some(t) => T::decode(&mut &t[..])
|
||||
.map_err(
|
||||
|e| ClientError::Backend(format!("GRANDPA DB is corrupted: {}", e.what())),
|
||||
)
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
/// Persistent data kept between runs.
|
||||
pub(crate) struct PersistentData<Block: BlockT> {
|
||||
pub(crate) authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
pub(crate) consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
pub(crate) set_state: SharedVoterSetState<Block>,
|
||||
}
|
||||
|
||||
fn migrate_from_version0<Block: BlockT, B, G>(
|
||||
backend: &B,
|
||||
genesis_round: &G,
|
||||
) -> ClientResult<Option<(
|
||||
AuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
VoterSetState<Block>,
|
||||
)>> where B: AuxStore,
|
||||
G: Fn() -> RoundState<Block::Hash, NumberFor<Block>>,
|
||||
{
|
||||
CURRENT_VERSION.using_encoded(|s|
|
||||
backend.insert_aux(&[(VERSION_KEY, s)], &[])
|
||||
)?;
|
||||
|
||||
if let Some(old_set) = load_decode::<_, V0AuthoritySet<Block::Hash, NumberFor<Block>>>(
|
||||
backend,
|
||||
AUTHORITY_SET_KEY,
|
||||
)? {
|
||||
let new_set: AuthoritySet<Block::Hash, NumberFor<Block>> = old_set.into();
|
||||
backend.insert_aux(&[(AUTHORITY_SET_KEY, new_set.encode().as_slice())], &[])?;
|
||||
|
||||
let (last_round_number, last_round_state) = match load_decode::<_, V0VoterSetState<Block::Hash, NumberFor<Block>>>(
|
||||
backend,
|
||||
SET_STATE_KEY,
|
||||
)? {
|
||||
Some((number, state)) => (number, state),
|
||||
None => (0, genesis_round()),
|
||||
};
|
||||
|
||||
let set_id = new_set.current().0;
|
||||
|
||||
let base = last_round_state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
let mut current_rounds = CurrentRounds::new();
|
||||
current_rounds.insert(last_round_number + 1, HasVoted::No);
|
||||
|
||||
let set_state = VoterSetState::Live {
|
||||
completed_rounds: CompletedRounds::new(
|
||||
CompletedRound {
|
||||
number: last_round_number,
|
||||
state: last_round_state,
|
||||
votes: Vec::new(),
|
||||
base,
|
||||
},
|
||||
set_id,
|
||||
&new_set,
|
||||
),
|
||||
current_rounds,
|
||||
};
|
||||
|
||||
backend.insert_aux(&[(SET_STATE_KEY, set_state.encode().as_slice())], &[])?;
|
||||
|
||||
return Ok(Some((new_set, set_state)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn migrate_from_version1<Block: BlockT, B, G>(
|
||||
backend: &B,
|
||||
genesis_round: &G,
|
||||
) -> ClientResult<Option<(
|
||||
AuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
VoterSetState<Block>,
|
||||
)>> where B: AuxStore,
|
||||
G: Fn() -> RoundState<Block::Hash, NumberFor<Block>>,
|
||||
{
|
||||
CURRENT_VERSION.using_encoded(|s|
|
||||
backend.insert_aux(&[(VERSION_KEY, s)], &[])
|
||||
)?;
|
||||
|
||||
if let Some(set) = load_decode::<_, AuthoritySet<Block::Hash, NumberFor<Block>>>(
|
||||
backend,
|
||||
AUTHORITY_SET_KEY,
|
||||
)? {
|
||||
let set_id = set.current().0;
|
||||
|
||||
let completed_rounds = |number, state, base| CompletedRounds::new(
|
||||
CompletedRound {
|
||||
number,
|
||||
state,
|
||||
votes: Vec::new(),
|
||||
base,
|
||||
},
|
||||
set_id,
|
||||
&set,
|
||||
);
|
||||
|
||||
let set_state = match load_decode::<_, V1VoterSetState<Block::Hash, NumberFor<Block>>>(
|
||||
backend,
|
||||
SET_STATE_KEY,
|
||||
)? {
|
||||
Some(V1VoterSetState::Paused(last_round_number, set_state)) => {
|
||||
let base = set_state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
VoterSetState::Paused {
|
||||
completed_rounds: completed_rounds(last_round_number, set_state, base),
|
||||
}
|
||||
},
|
||||
Some(V1VoterSetState::Live(last_round_number, set_state)) => {
|
||||
let base = set_state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
let mut current_rounds = CurrentRounds::new();
|
||||
current_rounds.insert(last_round_number + 1, HasVoted::No);
|
||||
|
||||
VoterSetState::Live {
|
||||
completed_rounds: completed_rounds(last_round_number, set_state, base),
|
||||
current_rounds,
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let set_state = genesis_round();
|
||||
let base = set_state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
VoterSetState::live(
|
||||
set_id,
|
||||
&set,
|
||||
base,
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
backend.insert_aux(&[(SET_STATE_KEY, set_state.encode().as_slice())], &[])?;
|
||||
|
||||
return Ok(Some((set, set_state)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Load or initialize persistent data from backend.
|
||||
pub(crate) fn load_persistent<Block: BlockT, B, G>(
|
||||
backend: &B,
|
||||
genesis_hash: Block::Hash,
|
||||
genesis_number: NumberFor<Block>,
|
||||
genesis_authorities: G,
|
||||
)
|
||||
-> ClientResult<PersistentData<Block>>
|
||||
where
|
||||
B: AuxStore,
|
||||
G: FnOnce() -> ClientResult<AuthorityList>,
|
||||
{
|
||||
let version: Option<u32> = load_decode(backend, VERSION_KEY)?;
|
||||
let consensus_changes = load_decode(backend, CONSENSUS_CHANGES_KEY)?
|
||||
.unwrap_or_else(ConsensusChanges::<Block::Hash, NumberFor<Block>>::empty);
|
||||
|
||||
let make_genesis_round = move || RoundState::genesis((genesis_hash, genesis_number));
|
||||
|
||||
match version {
|
||||
None => {
|
||||
if let Some((new_set, set_state)) = migrate_from_version0::<Block, _, _>(backend, &make_genesis_round)? {
|
||||
return Ok(PersistentData {
|
||||
authority_set: new_set.into(),
|
||||
consensus_changes: Arc::new(consensus_changes.into()),
|
||||
set_state: set_state.into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Some(1) => {
|
||||
if let Some((new_set, set_state)) = migrate_from_version1::<Block, _, _>(backend, &make_genesis_round)? {
|
||||
return Ok(PersistentData {
|
||||
authority_set: new_set.into(),
|
||||
consensus_changes: Arc::new(consensus_changes.into()),
|
||||
set_state: set_state.into(),
|
||||
});
|
||||
}
|
||||
},
|
||||
Some(2) => {
|
||||
if let Some(set) = load_decode::<_, AuthoritySet<Block::Hash, NumberFor<Block>>>(
|
||||
backend,
|
||||
AUTHORITY_SET_KEY,
|
||||
)? {
|
||||
let set_state = match load_decode::<_, VoterSetState<Block>>(
|
||||
backend,
|
||||
SET_STATE_KEY,
|
||||
)? {
|
||||
Some(state) => state,
|
||||
None => {
|
||||
let state = make_genesis_round();
|
||||
let base = state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
VoterSetState::live(
|
||||
set.current().0,
|
||||
&set,
|
||||
base,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
return Ok(PersistentData {
|
||||
authority_set: set.into(),
|
||||
consensus_changes: Arc::new(consensus_changes.into()),
|
||||
set_state: set_state.into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(other) => return Err(ClientError::Backend(
|
||||
format!("Unsupported GRANDPA DB version: {:?}", other)
|
||||
).into()),
|
||||
}
|
||||
|
||||
// genesis.
|
||||
info!(target: "afg", "Loading GRANDPA authority set \
|
||||
from genesis on what appears to be first startup.");
|
||||
|
||||
let genesis_authorities = genesis_authorities()?;
|
||||
let genesis_set = AuthoritySet::genesis(genesis_authorities.clone());
|
||||
let state = make_genesis_round();
|
||||
let base = state.prevote_ghost
|
||||
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
|
||||
|
||||
let genesis_state = VoterSetState::live(
|
||||
0,
|
||||
&genesis_set,
|
||||
base,
|
||||
);
|
||||
|
||||
backend.insert_aux(
|
||||
&[
|
||||
(AUTHORITY_SET_KEY, genesis_set.encode().as_slice()),
|
||||
(SET_STATE_KEY, genesis_state.encode().as_slice()),
|
||||
],
|
||||
&[],
|
||||
)?;
|
||||
|
||||
Ok(PersistentData {
|
||||
authority_set: genesis_set.into(),
|
||||
set_state: genesis_state.into(),
|
||||
consensus_changes: Arc::new(consensus_changes.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Update the authority set on disk after a change.
|
||||
///
|
||||
/// If there has just been a handoff, pass a `new_set` parameter that describes the
|
||||
/// handoff. `set` in all cases should reflect the current authority set, with all
|
||||
/// changes and handoffs applied.
|
||||
pub(crate) fn update_authority_set<Block: BlockT, F, R>(
|
||||
set: &AuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
new_set: Option<&NewAuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||
write_aux: F
|
||||
) -> R where
|
||||
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
||||
{
|
||||
// write new authority set state to disk.
|
||||
let encoded_set = set.encode();
|
||||
|
||||
if let Some(new_set) = new_set {
|
||||
// we also overwrite the "last completed round" entry with a blank slate
|
||||
// because from the perspective of the finality gadget, the chain has
|
||||
// reset.
|
||||
let set_state = VoterSetState::<Block>::live(
|
||||
new_set.set_id,
|
||||
&set,
|
||||
(new_set.canon_hash, new_set.canon_number),
|
||||
);
|
||||
let encoded = set_state.encode();
|
||||
|
||||
write_aux(&[
|
||||
(AUTHORITY_SET_KEY, &encoded_set[..]),
|
||||
(SET_STATE_KEY, &encoded[..]),
|
||||
])
|
||||
} else {
|
||||
write_aux(&[(AUTHORITY_SET_KEY, &encoded_set[..])])
|
||||
}
|
||||
}
|
||||
|
||||
/// Write voter set state.
|
||||
pub(crate) fn write_voter_set_state<Block: BlockT, B: AuxStore>(
|
||||
backend: &B,
|
||||
state: &VoterSetState<Block>,
|
||||
) -> ClientResult<()> {
|
||||
backend.insert_aux(
|
||||
&[(SET_STATE_KEY, state.encode().as_slice())],
|
||||
&[]
|
||||
)
|
||||
}
|
||||
|
||||
/// Update the consensus changes.
|
||||
pub(crate) fn update_consensus_changes<H, N, F, R>(
|
||||
set: &ConsensusChanges<H, N>,
|
||||
write_aux: F
|
||||
) -> R where
|
||||
H: Encode + Clone,
|
||||
N: Encode + Clone,
|
||||
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
|
||||
{
|
||||
write_aux(&[(CONSENSUS_CHANGES_KEY, set.encode().as_slice())])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode>(backend: &B)
|
||||
-> Option<AuthoritySet<H, N>> {
|
||||
load_decode::<_, AuthoritySet<H, N>>(backend, AUTHORITY_SET_KEY)
|
||||
.expect("backend error")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use fg_primitives::AuthorityId;
|
||||
use primitives::H256;
|
||||
use test_client;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn load_decode_from_v0_migrates_data_format() {
|
||||
let client = test_client::new();
|
||||
|
||||
let authorities = vec![(AuthorityId::default(), 100)];
|
||||
let set_id = 3;
|
||||
let round_number: RoundNumber = 42;
|
||||
let round_state = RoundState::<H256, u64> {
|
||||
prevote_ghost: Some((H256::random(), 32)),
|
||||
finalized: None,
|
||||
estimate: None,
|
||||
completable: false,
|
||||
};
|
||||
|
||||
{
|
||||
let authority_set = V0AuthoritySet::<H256, u64> {
|
||||
current_authorities: authorities.clone(),
|
||||
pending_changes: Vec::new(),
|
||||
set_id,
|
||||
};
|
||||
|
||||
let voter_set_state = (round_number, round_state.clone());
|
||||
|
||||
client.insert_aux(
|
||||
&[
|
||||
(AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
|
||||
(SET_STATE_KEY, voter_set_state.encode().as_slice()),
|
||||
],
|
||||
&[],
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, VERSION_KEY).unwrap(),
|
||||
None,
|
||||
);
|
||||
|
||||
// should perform the migration
|
||||
load_persistent::<test_client::runtime::Block, _, _>(
|
||||
&client,
|
||||
H256::random(),
|
||||
0,
|
||||
|| unreachable!(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, VERSION_KEY).unwrap(),
|
||||
Some(2),
|
||||
);
|
||||
|
||||
let PersistentData { authority_set, set_state, .. } = load_persistent::<test_client::runtime::Block, _, _>(
|
||||
&client,
|
||||
H256::random(),
|
||||
0,
|
||||
|| unreachable!(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*authority_set.inner().read(),
|
||||
AuthoritySet {
|
||||
current_authorities: authorities.clone(),
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
set_id,
|
||||
},
|
||||
);
|
||||
|
||||
let mut current_rounds = CurrentRounds::new();
|
||||
current_rounds.insert(round_number + 1, HasVoted::No);
|
||||
|
||||
assert_eq!(
|
||||
&*set_state.read(),
|
||||
&VoterSetState::Live {
|
||||
completed_rounds: CompletedRounds::new(
|
||||
CompletedRound {
|
||||
number: round_number,
|
||||
state: round_state.clone(),
|
||||
base: round_state.prevote_ghost.unwrap(),
|
||||
votes: vec![],
|
||||
},
|
||||
set_id,
|
||||
&*authority_set.inner().read(),
|
||||
),
|
||||
current_rounds,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_decode_from_v1_migrates_data_format() {
|
||||
let client = test_client::new();
|
||||
|
||||
let authorities = vec![(AuthorityId::default(), 100)];
|
||||
let set_id = 3;
|
||||
let round_number: RoundNumber = 42;
|
||||
let round_state = RoundState::<H256, u64> {
|
||||
prevote_ghost: Some((H256::random(), 32)),
|
||||
finalized: None,
|
||||
estimate: None,
|
||||
completable: false,
|
||||
};
|
||||
|
||||
{
|
||||
let authority_set = AuthoritySet::<H256, u64> {
|
||||
current_authorities: authorities.clone(),
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
set_id,
|
||||
};
|
||||
|
||||
let voter_set_state = V1VoterSetState::Live(round_number, round_state.clone());
|
||||
|
||||
client.insert_aux(
|
||||
&[
|
||||
(AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
|
||||
(SET_STATE_KEY, voter_set_state.encode().as_slice()),
|
||||
(VERSION_KEY, 1u32.encode().as_slice()),
|
||||
],
|
||||
&[],
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, VERSION_KEY).unwrap(),
|
||||
Some(1),
|
||||
);
|
||||
|
||||
// should perform the migration
|
||||
load_persistent::<test_client::runtime::Block, _, _>(
|
||||
&client,
|
||||
H256::random(),
|
||||
0,
|
||||
|| unreachable!(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
load_decode::<_, u32>(&client, VERSION_KEY).unwrap(),
|
||||
Some(2),
|
||||
);
|
||||
|
||||
let PersistentData { authority_set, set_state, .. } = load_persistent::<test_client::runtime::Block, _, _>(
|
||||
&client,
|
||||
H256::random(),
|
||||
0,
|
||||
|| unreachable!(),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
*authority_set.inner().read(),
|
||||
AuthoritySet {
|
||||
current_authorities: authorities.clone(),
|
||||
pending_standard_changes: ForkTree::new(),
|
||||
pending_forced_changes: Vec::new(),
|
||||
set_id,
|
||||
},
|
||||
);
|
||||
|
||||
let mut current_rounds = CurrentRounds::new();
|
||||
current_rounds.insert(round_number + 1, HasVoted::No);
|
||||
|
||||
assert_eq!(
|
||||
&*set_state.read(),
|
||||
&VoterSetState::Live {
|
||||
completed_rounds: CompletedRounds::new(
|
||||
CompletedRound {
|
||||
number: round_number,
|
||||
state: round_state.clone(),
|
||||
base: round_state.prevote_ghost.unwrap(),
|
||||
votes: vec![],
|
||||
},
|
||||
set_id,
|
||||
&*authority_set.inner().read(),
|
||||
),
|
||||
current_rounds,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Periodic rebroadcast of neighbor packets.
|
||||
|
||||
use std::time::{Instant, Duration};
|
||||
|
||||
use codec::Encode;
|
||||
use futures::prelude::*;
|
||||
use futures::sync::mpsc;
|
||||
use log::{debug, warn};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use network::PeerId;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT};
|
||||
use super::{gossip::{NeighborPacket, GossipMessage}, Network};
|
||||
|
||||
// how often to rebroadcast, if no other
|
||||
const REBROADCAST_AFTER: Duration = Duration::from_secs(2 * 60);
|
||||
|
||||
fn rebroadcast_instant() -> Instant {
|
||||
Instant::now() + REBROADCAST_AFTER
|
||||
}
|
||||
|
||||
/// A sender used to send neighbor packets to a background job.
|
||||
#[derive(Clone)]
|
||||
pub(super) struct NeighborPacketSender<B: BlockT>(
|
||||
mpsc::UnboundedSender<(Vec<PeerId>, NeighborPacket<NumberFor<B>>)>
|
||||
);
|
||||
|
||||
impl<B: BlockT> NeighborPacketSender<B> {
|
||||
/// Send a neighbor packet for the background worker to gossip to peers.
|
||||
pub fn send(
|
||||
&self,
|
||||
who: Vec<network::PeerId>,
|
||||
neighbor_packet: NeighborPacket<NumberFor<B>>,
|
||||
) {
|
||||
if let Err(err) = self.0.unbounded_send((who, neighbor_packet)) {
|
||||
debug!(target: "afg", "Failed to send neighbor packet: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Does the work of sending neighbor packets, asynchronously.
|
||||
///
|
||||
/// It may rebroadcast the last neighbor packet periodically when no
|
||||
/// progress is made.
|
||||
pub(super) fn neighbor_packet_worker<B, N>(net: N) -> (
|
||||
impl Future<Item = (), Error = ()> + Send + 'static,
|
||||
NeighborPacketSender<B>,
|
||||
) where
|
||||
B: BlockT,
|
||||
N: Network<B>,
|
||||
{
|
||||
let mut last = None;
|
||||
let (tx, mut rx) = mpsc::unbounded::<(Vec<PeerId>, NeighborPacket<NumberFor<B>>)>();
|
||||
let mut delay = Delay::new(rebroadcast_instant());
|
||||
|
||||
let work = futures::future::poll_fn(move || {
|
||||
loop {
|
||||
match rx.poll().expect("unbounded receivers do not error; qed") {
|
||||
Async::Ready(None) => return Ok(Async::Ready(())),
|
||||
Async::Ready(Some((to, packet))) => {
|
||||
// send to peers.
|
||||
net.send_message(to.clone(), GossipMessage::<B>::from(packet.clone()).encode());
|
||||
|
||||
// rebroadcasting network.
|
||||
delay.reset(rebroadcast_instant());
|
||||
last = Some((to, packet));
|
||||
}
|
||||
Async::NotReady => break,
|
||||
}
|
||||
}
|
||||
|
||||
// has to be done in a loop because it needs to be polled after
|
||||
// re-scheduling.
|
||||
loop {
|
||||
match delay.poll() {
|
||||
Err(e) => {
|
||||
warn!(target: "afg", "Could not rebroadcast neighbor packets: {:?}", e);
|
||||
delay.reset(rebroadcast_instant());
|
||||
}
|
||||
Ok(Async::Ready(())) => {
|
||||
delay.reset(rebroadcast_instant());
|
||||
|
||||
if let Some((ref to, ref packet)) = last {
|
||||
// send to peers.
|
||||
net.send_message(to.clone(), GossipMessage::<B>::from(packet.clone()).encode());
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(work, NeighborPacketSender(tx))
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Tests for the communication portion of the GRANDPA crate.
|
||||
|
||||
use futures::sync::mpsc;
|
||||
use futures::prelude::*;
|
||||
use network::consensus_gossip as network_gossip;
|
||||
use network::test::{Block, Hash};
|
||||
use network_gossip::Validator;
|
||||
use tokio::runtime::current_thread;
|
||||
use std::sync::Arc;
|
||||
use keyring::Ed25519Keyring;
|
||||
use codec::Encode;
|
||||
use sr_primitives::traits::NumberFor;
|
||||
|
||||
use crate::environment::SharedVoterSetState;
|
||||
use fg_primitives::AuthorityList;
|
||||
use super::gossip::{self, GossipValidator};
|
||||
use super::{AuthorityId, VoterSet, Round, SetId};
|
||||
|
||||
enum Event {
|
||||
MessagesFor(Hash, mpsc::UnboundedSender<network_gossip::TopicNotification>),
|
||||
RegisterValidator(Arc<dyn network_gossip::Validator<Block>>),
|
||||
GossipMessage(Hash, Vec<u8>, bool),
|
||||
SendMessage(Vec<network::PeerId>, Vec<u8>),
|
||||
Report(network::PeerId, i32),
|
||||
Announce(Hash),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestNetwork {
|
||||
sender: mpsc::UnboundedSender<Event>,
|
||||
}
|
||||
|
||||
impl super::Network<Block> for TestNetwork {
|
||||
type In = mpsc::UnboundedReceiver<network_gossip::TopicNotification>;
|
||||
|
||||
/// Get a stream of messages for a specific gossip topic.
|
||||
fn messages_for(&self, topic: Hash) -> Self::In {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let _ = self.sender.unbounded_send(Event::MessagesFor(topic, tx));
|
||||
|
||||
rx
|
||||
}
|
||||
|
||||
/// Register a gossip validator.
|
||||
fn register_validator(&self, validator: Arc<dyn network_gossip::Validator<Block>>) {
|
||||
let _ = self.sender.unbounded_send(Event::RegisterValidator(validator));
|
||||
}
|
||||
|
||||
/// Gossip a message out to all connected peers.
|
||||
///
|
||||
/// Force causes it to be sent to all peers, even if they've seen it already.
|
||||
/// Only should be used in case of consensus stall.
|
||||
fn gossip_message(&self, topic: Hash, data: Vec<u8>, force: bool) {
|
||||
let _ = self.sender.unbounded_send(Event::GossipMessage(topic, data, force));
|
||||
}
|
||||
|
||||
/// Send a message to a bunch of specific peers, even if they've seen it already.
|
||||
fn send_message(&self, who: Vec<network::PeerId>, data: Vec<u8>) {
|
||||
let _ = self.sender.unbounded_send(Event::SendMessage(who, data));
|
||||
}
|
||||
|
||||
/// Register a message with the gossip service, it isn't broadcast right
|
||||
/// away to any peers, but may be sent to new peers joining or when asked to
|
||||
/// broadcast the topic. Useful to register previous messages on node
|
||||
/// startup.
|
||||
fn register_gossip_message(&self, _topic: Hash, _data: Vec<u8>) {
|
||||
// NOTE: only required to restore previous state on startup
|
||||
// not required for tests currently
|
||||
}
|
||||
|
||||
/// Report a peer's cost or benefit after some action.
|
||||
fn report(&self, who: network::PeerId, cost_benefit: i32) {
|
||||
let _ = self.sender.unbounded_send(Event::Report(who, cost_benefit));
|
||||
}
|
||||
|
||||
/// Inform peers that a block with given hash should be downloaded.
|
||||
fn announce(&self, block: Hash, _associated_data: Vec<u8>) {
|
||||
let _ = self.sender.unbounded_send(Event::Announce(block));
|
||||
}
|
||||
|
||||
/// Notify the sync service to try syncing the given chain.
|
||||
fn set_sync_fork_request(&self, _peers: Vec<network::PeerId>, _hash: Hash, _number: NumberFor<Block>) {}
|
||||
}
|
||||
|
||||
impl network_gossip::ValidatorContext<Block> for TestNetwork {
|
||||
fn broadcast_topic(&mut self, _: Hash, _: bool) { }
|
||||
|
||||
fn broadcast_message(&mut self, _: Hash, _: Vec<u8>, _: bool) { }
|
||||
|
||||
fn send_message(&mut self, who: &network::PeerId, data: Vec<u8>) {
|
||||
<Self as super::Network<Block>>::send_message(self, vec![who.clone()], data);
|
||||
}
|
||||
|
||||
fn send_topic(&mut self, _: &network::PeerId, _: Hash, _: bool) { }
|
||||
}
|
||||
|
||||
struct Tester {
|
||||
net_handle: super::NetworkBridge<Block, TestNetwork>,
|
||||
gossip_validator: Arc<GossipValidator<Block>>,
|
||||
events: mpsc::UnboundedReceiver<Event>,
|
||||
}
|
||||
|
||||
impl Tester {
|
||||
fn filter_network_events<F>(self, mut pred: F) -> impl Future<Item=Self,Error=()>
|
||||
where F: FnMut(Event) -> bool
|
||||
{
|
||||
let mut s = Some(self);
|
||||
futures::future::poll_fn(move || loop {
|
||||
match s.as_mut().unwrap().events.poll().expect("concluded early") {
|
||||
Async::Ready(None) => panic!("concluded early"),
|
||||
Async::Ready(Some(item)) => if pred(item) {
|
||||
return Ok(Async::Ready(s.take().unwrap()))
|
||||
},
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// some random config (not really needed)
|
||||
fn config() -> crate::Config {
|
||||
crate::Config {
|
||||
gossip_duration: std::time::Duration::from_millis(10),
|
||||
justification_period: 256,
|
||||
keystore: None,
|
||||
name: None,
|
||||
is_authority: true,
|
||||
observer_enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
// dummy voter set state
|
||||
fn voter_set_state() -> SharedVoterSetState<Block> {
|
||||
use crate::authorities::AuthoritySet;
|
||||
use crate::environment::VoterSetState;
|
||||
use grandpa::round::State as RoundState;
|
||||
use primitives::H256;
|
||||
|
||||
let state = RoundState::genesis((H256::zero(), 0));
|
||||
let base = state.prevote_ghost.unwrap();
|
||||
let voters = AuthoritySet::genesis(Vec::new());
|
||||
let set_state = VoterSetState::live(
|
||||
0,
|
||||
&voters,
|
||||
base,
|
||||
);
|
||||
|
||||
set_state.into()
|
||||
}
|
||||
|
||||
// needs to run in a tokio runtime.
|
||||
fn make_test_network() -> (
|
||||
impl Future<Item=Tester,Error=()>,
|
||||
TestNetwork,
|
||||
) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let net = TestNetwork { sender: tx };
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Exit;
|
||||
|
||||
impl Future for Exit {
|
||||
type Item = ();
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<(), ()> {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
let (bridge, startup_work) = super::NetworkBridge::new(
|
||||
net.clone(),
|
||||
config(),
|
||||
voter_set_state(),
|
||||
Exit,
|
||||
);
|
||||
|
||||
(
|
||||
startup_work.map(move |()| Tester {
|
||||
gossip_validator: bridge.validator.clone(),
|
||||
net_handle: bridge,
|
||||
events: rx,
|
||||
}),
|
||||
net,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList {
|
||||
keys.iter()
|
||||
.map(|key| key.clone().public().into())
|
||||
.map(|id| (id, 1))
|
||||
.collect()
|
||||
}
|
||||
|
||||
struct NoopContext;
|
||||
|
||||
impl network_gossip::ValidatorContext<Block> for NoopContext {
|
||||
fn broadcast_topic(&mut self, _: Hash, _: bool) { }
|
||||
fn broadcast_message(&mut self, _: Hash, _: Vec<u8>, _: bool) { }
|
||||
fn send_message(&mut self, _: &network::PeerId, _: Vec<u8>) { }
|
||||
fn send_topic(&mut self, _: &network::PeerId, _: Hash, _: bool) { }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn good_commit_leads_to_relay() {
|
||||
let private = [Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
|
||||
let public = make_ids(&private[..]);
|
||||
let voter_set = Arc::new(public.iter().cloned().collect::<VoterSet<AuthorityId>>());
|
||||
|
||||
let round = 1;
|
||||
let set_id = 1;
|
||||
|
||||
let commit = {
|
||||
let target_hash: Hash = [1; 32].into();
|
||||
let target_number = 500;
|
||||
|
||||
let precommit = grandpa::Precommit { target_hash: target_hash.clone(), target_number };
|
||||
let payload = super::localized_payload(
|
||||
round, set_id, &grandpa::Message::Precommit(precommit.clone())
|
||||
);
|
||||
|
||||
let mut precommits = Vec::new();
|
||||
let mut auth_data = Vec::new();
|
||||
|
||||
for (i, key) in private.iter().enumerate() {
|
||||
precommits.push(precommit.clone());
|
||||
|
||||
let signature = fg_primitives::AuthoritySignature::from(key.sign(&payload[..]));
|
||||
auth_data.push((signature, public[i].0.clone()))
|
||||
}
|
||||
|
||||
grandpa::CompactCommit {
|
||||
target_hash,
|
||||
target_number,
|
||||
precommits,
|
||||
auth_data,
|
||||
}
|
||||
};
|
||||
|
||||
let encoded_commit = gossip::GossipMessage::<Block>::Commit(gossip::FullCommitMessage {
|
||||
round: Round(round),
|
||||
set_id: SetId(set_id),
|
||||
message: commit,
|
||||
}).encode();
|
||||
|
||||
let id = network::PeerId::random();
|
||||
let global_topic = super::global_topic::<Block>(set_id);
|
||||
|
||||
let test = make_test_network().0
|
||||
.and_then(move |tester| {
|
||||
// register a peer.
|
||||
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::FULL);
|
||||
Ok((tester, id))
|
||||
})
|
||||
.and_then(move |(tester, id)| {
|
||||
// start round, dispatch commit, and wait for broadcast.
|
||||
let (commits_in, _) = tester.net_handle.global_communication(SetId(1), voter_set, false);
|
||||
|
||||
{
|
||||
let (action, ..) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
|
||||
match action {
|
||||
gossip::Action::ProcessAndDiscard(t, _) => assert_eq!(t, global_topic),
|
||||
_ => panic!("wrong expected outcome from initial commit validation"),
|
||||
}
|
||||
}
|
||||
|
||||
let commit_to_send = encoded_commit.clone();
|
||||
|
||||
// asking for global communication will cause the test network
|
||||
// to send us an event asking us for a stream. use it to
|
||||
// send a message.
|
||||
let sender_id = id.clone();
|
||||
let send_message = tester.filter_network_events(move |event| match event {
|
||||
Event::MessagesFor(topic, sender) => {
|
||||
if topic != global_topic { return false }
|
||||
let _ = sender.unbounded_send(network_gossip::TopicNotification {
|
||||
message: commit_to_send.clone(),
|
||||
sender: Some(sender_id.clone()),
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
// when the commit comes in, we'll tell the callback it was good.
|
||||
let handle_commit = commits_in.into_future()
|
||||
.map(|(item, _)| {
|
||||
match item.unwrap() {
|
||||
grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
|
||||
callback.run(grandpa::voter::CommitProcessingOutcome::good());
|
||||
},
|
||||
_ => panic!("commit expected"),
|
||||
}
|
||||
})
|
||||
.map_err(|_| panic!("could not process commit"));
|
||||
|
||||
// once the message is sent and commit is "handled" we should have
|
||||
// a repropagation event coming from the network.
|
||||
send_message.join(handle_commit).and_then(move |(tester, ())| {
|
||||
tester.filter_network_events(move |event| match event {
|
||||
Event::GossipMessage(topic, data, false) => {
|
||||
if topic == global_topic && data == encoded_commit {
|
||||
true
|
||||
} else {
|
||||
panic!("Trying to gossip something strange")
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
})
|
||||
.map_err(|_| panic!("could not watch for gossip message"))
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
current_thread::block_on_all(test).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_commit_leads_to_report() {
|
||||
let private = [Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
|
||||
let public = make_ids(&private[..]);
|
||||
let voter_set = Arc::new(public.iter().cloned().collect::<VoterSet<AuthorityId>>());
|
||||
|
||||
let round = 1;
|
||||
let set_id = 1;
|
||||
|
||||
let commit = {
|
||||
let target_hash: Hash = [1; 32].into();
|
||||
let target_number = 500;
|
||||
|
||||
let precommit = grandpa::Precommit { target_hash: target_hash.clone(), target_number };
|
||||
let payload = super::localized_payload(
|
||||
round, set_id, &grandpa::Message::Precommit(precommit.clone())
|
||||
);
|
||||
|
||||
let mut precommits = Vec::new();
|
||||
let mut auth_data = Vec::new();
|
||||
|
||||
for (i, key) in private.iter().enumerate() {
|
||||
precommits.push(precommit.clone());
|
||||
|
||||
let signature = fg_primitives::AuthoritySignature::from(key.sign(&payload[..]));
|
||||
auth_data.push((signature, public[i].0.clone()))
|
||||
}
|
||||
|
||||
grandpa::CompactCommit {
|
||||
target_hash,
|
||||
target_number,
|
||||
precommits,
|
||||
auth_data,
|
||||
}
|
||||
};
|
||||
|
||||
let encoded_commit = gossip::GossipMessage::<Block>::Commit(gossip::FullCommitMessage {
|
||||
round: Round(round),
|
||||
set_id: SetId(set_id),
|
||||
message: commit,
|
||||
}).encode();
|
||||
|
||||
let id = network::PeerId::random();
|
||||
let global_topic = super::global_topic::<Block>(set_id);
|
||||
|
||||
let test = make_test_network().0
|
||||
.and_then(move |tester| {
|
||||
// register a peer.
|
||||
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::FULL);
|
||||
Ok((tester, id))
|
||||
})
|
||||
.and_then(move |(tester, id)| {
|
||||
// start round, dispatch commit, and wait for broadcast.
|
||||
let (commits_in, _) = tester.net_handle.global_communication(SetId(1), voter_set, false);
|
||||
|
||||
{
|
||||
let (action, ..) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
|
||||
match action {
|
||||
gossip::Action::ProcessAndDiscard(t, _) => assert_eq!(t, global_topic),
|
||||
_ => panic!("wrong expected outcome from initial commit validation"),
|
||||
}
|
||||
}
|
||||
|
||||
let commit_to_send = encoded_commit.clone();
|
||||
|
||||
// asking for global communication will cause the test network
|
||||
// to send us an event asking us for a stream. use it to
|
||||
// send a message.
|
||||
let sender_id = id.clone();
|
||||
let send_message = tester.filter_network_events(move |event| match event {
|
||||
Event::MessagesFor(topic, sender) => {
|
||||
if topic != global_topic { return false }
|
||||
let _ = sender.unbounded_send(network_gossip::TopicNotification {
|
||||
message: commit_to_send.clone(),
|
||||
sender: Some(sender_id.clone()),
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
|
||||
// when the commit comes in, we'll tell the callback it was good.
|
||||
let handle_commit = commits_in.into_future()
|
||||
.map(|(item, _)| {
|
||||
match item.unwrap() {
|
||||
grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
|
||||
callback.run(grandpa::voter::CommitProcessingOutcome::bad());
|
||||
},
|
||||
_ => panic!("commit expected"),
|
||||
}
|
||||
})
|
||||
.map_err(|_| panic!("could not process commit"));
|
||||
|
||||
// once the message is sent and commit is "handled" we should have
|
||||
// a report event coming from the network.
|
||||
send_message.join(handle_commit).and_then(move |(tester, ())| {
|
||||
tester.filter_network_events(move |event| match event {
|
||||
Event::Report(who, cost_benefit) => {
|
||||
if who == id && cost_benefit == super::cost::INVALID_COMMIT {
|
||||
true
|
||||
} else {
|
||||
panic!("reported unknown peer or unexpected cost");
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
})
|
||||
.map_err(|_| panic!("could not watch for peer report"))
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
current_thread::block_on_all(test).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peer_with_higher_view_leads_to_catch_up_request() {
|
||||
let id = network::PeerId::random();
|
||||
|
||||
let (tester, mut net) = make_test_network();
|
||||
let test = tester
|
||||
.and_then(move |tester| {
|
||||
// register a peer with authority role.
|
||||
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::AUTHORITY);
|
||||
Ok((tester, id))
|
||||
})
|
||||
.and_then(move |(tester, id)| {
|
||||
// send neighbor message at round 10 and height 50
|
||||
let result = tester.gossip_validator.validate(
|
||||
&mut net,
|
||||
&id,
|
||||
&gossip::GossipMessage::<Block>::from(gossip::NeighborPacket {
|
||||
set_id: SetId(0),
|
||||
round: Round(10),
|
||||
commit_finalized_height: 50,
|
||||
}).encode(),
|
||||
);
|
||||
|
||||
// neighbor packets are always discard
|
||||
match result {
|
||||
network_gossip::ValidationResult::Discard => {},
|
||||
_ => panic!("wrong expected outcome from neighbor validation"),
|
||||
}
|
||||
|
||||
// a catch up request should be sent to the peer for round - 1
|
||||
tester.filter_network_events(move |event| match event {
|
||||
Event::SendMessage(peers, message) => {
|
||||
assert_eq!(
|
||||
peers,
|
||||
vec![id.clone()],
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
message,
|
||||
gossip::GossipMessage::<Block>::CatchUpRequest(
|
||||
gossip::CatchUpRequestMessage {
|
||||
set_id: SetId(0),
|
||||
round: Round(9),
|
||||
}
|
||||
).encode(),
|
||||
);
|
||||
|
||||
true
|
||||
},
|
||||
_ => false,
|
||||
})
|
||||
.map_err(|_| panic!("could not watch for peer send message"))
|
||||
.map(|_| ())
|
||||
});
|
||||
|
||||
current_thread::block_on_all(test).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
/// Consensus-related data changes tracker.
|
||||
#[derive(Clone, Debug, Encode, Decode)]
|
||||
pub(crate) struct ConsensusChanges<H, N> {
|
||||
pending_changes: Vec<(N, H)>,
|
||||
}
|
||||
|
||||
impl<H, N> ConsensusChanges<H, N> {
|
||||
/// Create empty consensus changes.
|
||||
pub(crate) fn empty() -> Self {
|
||||
ConsensusChanges { pending_changes: Vec::new(), }
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: Copy + PartialEq, N: Copy + Ord> ConsensusChanges<H, N> {
|
||||
|
||||
/// Returns reference to all pending changes.
|
||||
pub fn pending_changes(&self) -> &[(N, H)] {
|
||||
&self.pending_changes
|
||||
}
|
||||
|
||||
/// Note unfinalized change of consensus-related data.
|
||||
pub(crate) fn note_change(&mut self, at: (N, H)) {
|
||||
let idx = self.pending_changes
|
||||
.binary_search_by_key(&at.0, |change| change.0)
|
||||
.unwrap_or_else(|i| i);
|
||||
self.pending_changes.insert(idx, at);
|
||||
}
|
||||
|
||||
/// Finalize all pending consensus changes that are finalized by given block.
|
||||
/// Returns true if there any changes were finalized.
|
||||
pub(crate) fn finalize<F: Fn(N) -> ::client_api::error::Result<Option<H>>>(
|
||||
&mut self,
|
||||
block: (N, H),
|
||||
canonical_at_height: F,
|
||||
) -> ::client_api::error::Result<(bool, bool)> {
|
||||
let (split_idx, has_finalized_changes) = self.pending_changes.iter()
|
||||
.enumerate()
|
||||
.take_while(|(_, &(at_height, _))| at_height <= block.0)
|
||||
.fold((None, Ok(false)), |(_, has_finalized_changes), (idx, ref at)|
|
||||
(
|
||||
Some(idx),
|
||||
has_finalized_changes
|
||||
.and_then(|has_finalized_changes| if has_finalized_changes {
|
||||
Ok(has_finalized_changes)
|
||||
} else {
|
||||
canonical_at_height(at.0).map(|can_hash| Some(at.1) == can_hash)
|
||||
}),
|
||||
));
|
||||
|
||||
let altered_changes = split_idx.is_some();
|
||||
if let Some(split_idx) = split_idx {
|
||||
self.pending_changes = self.pending_changes.split_off(split_idx + 1);
|
||||
}
|
||||
has_finalized_changes.map(|has_finalized_changes| (altered_changes, has_finalized_changes))
|
||||
}
|
||||
}
|
||||
|
||||
/// Thread-safe consensus changes tracker reference.
|
||||
pub(crate) type SharedConsensusChanges<H, N> = Arc<parking_lot::Mutex<ConsensusChanges<H, N>>>;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,990 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! GRANDPA block finality proof generation and check.
|
||||
//!
|
||||
//! Finality of block B is proved by providing:
|
||||
//! 1) the justification for the descendant block F;
|
||||
//! 2) headers sub-chain (B; F] if B != F;
|
||||
//! 3) proof of GRANDPA::authorities() if the set changes at block F.
|
||||
//!
|
||||
//! Since earliest possible justification is returned, the GRANDPA authorities set
|
||||
//! at the block F is guaranteed to be the same as in the block B (this is because block
|
||||
//! that enacts new GRANDPA authorities set always comes with justification). It also
|
||||
//! means that the `set_id` is the same at blocks B and F.
|
||||
//!
|
||||
//! Let U be the last finalized block known to caller. If authorities set has changed several
|
||||
//! times in the (U; F] interval, multiple finality proof fragments are returned (one for each
|
||||
//! authority set change) and they must be verified in-order.
|
||||
//!
|
||||
//! Finality proof provider can choose how to provide finality proof on its own. The incomplete
|
||||
//! finality proof (that finalizes some block C that is ancestor of the B and descendant
|
||||
//! of the U) could be returned.
|
||||
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
use log::{trace, warn};
|
||||
|
||||
use client_api::{
|
||||
backend::Backend, blockchain::Backend as BlockchainBackend, CallExecutor,
|
||||
error::{Error as ClientError, Result as ClientResult},
|
||||
light::{FetchChecker, RemoteReadRequest},
|
||||
StorageProof,
|
||||
};
|
||||
use client::Client;
|
||||
use codec::{Encode, Decode};
|
||||
use grandpa::BlockNumberOps;
|
||||
use sr_primitives::{
|
||||
Justification, generic::BlockId,
|
||||
traits::{NumberFor, Block as BlockT, Header as HeaderT, One},
|
||||
};
|
||||
use primitives::{H256, Blake2Hasher, storage::StorageKey};
|
||||
use substrate_telemetry::{telemetry, CONSENSUS_INFO};
|
||||
use fg_primitives::{AuthorityId, AuthorityList, VersionedAuthorityList, GRANDPA_AUTHORITIES_KEY};
|
||||
|
||||
use crate::justification::GrandpaJustification;
|
||||
|
||||
/// Maximum number of fragments that we want to return in a single prove_finality call.
|
||||
const MAX_FRAGMENTS_IN_PROOF: usize = 8;
|
||||
|
||||
/// GRANDPA authority set related methods for the finality proof provider.
|
||||
pub trait AuthoritySetForFinalityProver<Block: BlockT>: Send + Sync {
|
||||
/// Read GRANDPA_AUTHORITIES_KEY from storage at given block.
|
||||
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList>;
|
||||
/// Prove storage read of GRANDPA_AUTHORITIES_KEY at given block.
|
||||
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof>;
|
||||
}
|
||||
|
||||
/// Client-based implementation of AuthoritySetForFinalityProver.
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> AuthoritySetForFinalityProver<Block> for Client<B, E, Block, RA>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList> {
|
||||
let storage_key = StorageKey(GRANDPA_AUTHORITIES_KEY.to_vec());
|
||||
self.storage(block, &storage_key)?
|
||||
.and_then(|encoded| VersionedAuthorityList::decode(&mut encoded.0.as_slice()).ok())
|
||||
.map(|versioned| versioned.into())
|
||||
.ok_or(ClientError::InvalidAuthoritiesSet)
|
||||
}
|
||||
|
||||
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof> {
|
||||
self.read_proof(block, iter::once(GRANDPA_AUTHORITIES_KEY))
|
||||
}
|
||||
}
|
||||
|
||||
/// GRANDPA authority set related methods for the finality proof checker.
|
||||
pub trait AuthoritySetForFinalityChecker<Block: BlockT>: Send + Sync {
|
||||
/// Check storage read proof of GRANDPA_AUTHORITIES_KEY at given block.
|
||||
fn check_authorities_proof(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
header: Block::Header,
|
||||
proof: StorageProof,
|
||||
) -> ClientResult<AuthorityList>;
|
||||
}
|
||||
|
||||
/// FetchChecker-based implementation of AuthoritySetForFinalityChecker.
|
||||
impl<Block: BlockT> AuthoritySetForFinalityChecker<Block> for Arc<dyn FetchChecker<Block>> {
|
||||
fn check_authorities_proof(
|
||||
&self,
|
||||
hash: Block::Hash,
|
||||
header: Block::Header,
|
||||
proof: StorageProof,
|
||||
) -> ClientResult<AuthorityList> {
|
||||
let storage_key = GRANDPA_AUTHORITIES_KEY.to_vec();
|
||||
let request = RemoteReadRequest {
|
||||
block: hash,
|
||||
header,
|
||||
keys: vec![storage_key.clone()],
|
||||
retry_count: None,
|
||||
};
|
||||
|
||||
self.check_read_proof(&request, proof)
|
||||
.and_then(|results| {
|
||||
let maybe_encoded = results.get(&storage_key)
|
||||
.expect(
|
||||
"storage_key is listed in the request keys; \
|
||||
check_read_proof must return a value for each requested key;
|
||||
qed"
|
||||
);
|
||||
maybe_encoded
|
||||
.as_ref()
|
||||
.and_then(|encoded| {
|
||||
VersionedAuthorityList::decode(&mut encoded.as_slice()).ok()
|
||||
})
|
||||
.map(|versioned| versioned.into())
|
||||
.ok_or(ClientError::InvalidAuthoritiesSet)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Finality proof provider for serving network requests.
|
||||
pub struct FinalityProofProvider<B, Block: BlockT<Hash=H256>> {
|
||||
backend: Arc<B>,
|
||||
authority_provider: Arc<dyn AuthoritySetForFinalityProver<Block>>,
|
||||
}
|
||||
|
||||
impl<B, Block: BlockT<Hash=H256>> FinalityProofProvider<B, Block>
|
||||
where B: Backend<Block, Blake2Hasher> + Send + Sync + 'static
|
||||
{
|
||||
/// Create new finality proof provider using:
|
||||
///
|
||||
/// - backend for accessing blockchain data;
|
||||
/// - authority_provider for calling and proving runtime methods.
|
||||
pub fn new(
|
||||
backend: Arc<B>,
|
||||
authority_provider: Arc<dyn AuthoritySetForFinalityProver<Block>>,
|
||||
) -> Self {
|
||||
FinalityProofProvider { backend, authority_provider }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, Block> network::FinalityProofProvider<Block> for FinalityProofProvider<B, Block>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
{
|
||||
fn prove_finality(
|
||||
&self,
|
||||
for_block: Block::Hash,
|
||||
request: &[u8],
|
||||
) -> Result<Option<Vec<u8>>, ClientError> {
|
||||
let request: FinalityProofRequest<Block::Hash> = Decode::decode(&mut &request[..])
|
||||
.map_err(|e| {
|
||||
warn!(target: "finality", "Unable to decode finality proof request: {}", e.what());
|
||||
ClientError::Backend(format!("Invalid finality proof request"))
|
||||
})?;
|
||||
match request {
|
||||
FinalityProofRequest::Original(request) => prove_finality::<_, _, GrandpaJustification<Block>>(
|
||||
&*self.backend.blockchain(),
|
||||
&*self.authority_provider,
|
||||
request.authorities_set_id,
|
||||
request.last_finalized,
|
||||
for_block,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The effects of block finality.
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct FinalityEffects<Header: HeaderT> {
|
||||
/// The (ordered) set of headers that could be imported.
|
||||
pub headers_to_import: Vec<Header>,
|
||||
/// The hash of the block that could be finalized.
|
||||
pub block: Header::Hash,
|
||||
/// The justification for the block.
|
||||
pub justification: Vec<u8>,
|
||||
/// New authorities set id that should be applied starting from block.
|
||||
pub new_set_id: u64,
|
||||
/// New authorities set that should be applied starting from block.
|
||||
pub new_authorities: AuthorityList,
|
||||
}
|
||||
|
||||
/// Single fragment of proof-of-finality.
|
||||
///
|
||||
/// Finality for block B is proved by providing:
|
||||
/// 1) the justification for the descendant block F;
|
||||
/// 2) headers sub-chain (B; F] if B != F;
|
||||
/// 3) proof of GRANDPA::authorities() if the set changes at block F.
|
||||
#[derive(Debug, PartialEq, Encode, Decode)]
|
||||
struct FinalityProofFragment<Header: HeaderT> {
|
||||
/// The hash of block F for which justification is provided.
|
||||
pub block: Header::Hash,
|
||||
/// Justification of the block F.
|
||||
pub justification: Vec<u8>,
|
||||
/// The set of headers in the range (U; F] that we believe are unknown to the caller. Ordered.
|
||||
pub unknown_headers: Vec<Header>,
|
||||
/// Optional proof of execution of GRANDPA::authorities().
|
||||
pub authorities_proof: Option<StorageProof>,
|
||||
}
|
||||
|
||||
/// Proof of finality is the ordered set of finality fragments, where:
|
||||
/// - last fragment provides justification for the best possible block from the requested range;
|
||||
/// - all other fragments provide justifications for GRANDPA authorities set changes within requested range.
|
||||
type FinalityProof<Header> = Vec<FinalityProofFragment<Header>>;
|
||||
|
||||
/// Finality proof request data.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
enum FinalityProofRequest<H: Encode + Decode> {
|
||||
/// Original version of the request.
|
||||
Original(OriginalFinalityProofRequest<H>),
|
||||
}
|
||||
|
||||
/// Original version of finality proof request.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct OriginalFinalityProofRequest<H: Encode + Decode> {
|
||||
/// The authorities set id we are waiting proof from.
|
||||
///
|
||||
/// The first justification in the proof must be signed by this authority set.
|
||||
pub authorities_set_id: u64,
|
||||
/// Hash of the last known finalized block.
|
||||
pub last_finalized: H,
|
||||
}
|
||||
|
||||
/// Prepare data blob associated with finality proof request.
|
||||
pub(crate) fn make_finality_proof_request<H: Encode + Decode>(last_finalized: H, authorities_set_id: u64) -> Vec<u8> {
|
||||
FinalityProofRequest::Original(OriginalFinalityProofRequest {
|
||||
authorities_set_id,
|
||||
last_finalized,
|
||||
}).encode()
|
||||
}
|
||||
|
||||
/// Prepare proof-of-finality for the best possible block in the range: (begin; end].
|
||||
///
|
||||
/// It is assumed that the caller already have a proof-of-finality for the block 'begin'.
|
||||
/// It is assumed that the caller already knows all blocks in the range (begin; end].
|
||||
///
|
||||
/// Returns None if there are no finalized blocks unknown to the caller.
|
||||
pub(crate) fn prove_finality<Block: BlockT<Hash=H256>, B: BlockchainBackend<Block>, J>(
|
||||
blockchain: &B,
|
||||
authorities_provider: &dyn AuthoritySetForFinalityProver<Block>,
|
||||
authorities_set_id: u64,
|
||||
begin: Block::Hash,
|
||||
end: Block::Hash,
|
||||
) -> ::client_api::error::Result<Option<Vec<u8>>>
|
||||
where
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
let begin_id = BlockId::Hash(begin);
|
||||
let begin_number = blockchain.expect_block_number_from_id(&begin_id)?;
|
||||
|
||||
// early-return if we sure that there are no blocks finalized AFTER begin block
|
||||
let info = blockchain.info();
|
||||
if info.finalized_number <= begin_number {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Requested finality proof for descendant of #{} while we only have finalized #{}. Returning empty proof.",
|
||||
begin_number,
|
||||
info.finalized_number,
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// check if blocks range is valid. It is the caller responsibility to ensure
|
||||
// that it only asks peers that know about whole blocks range
|
||||
let end_number = blockchain.expect_block_number_from_id(&BlockId::Hash(end))?;
|
||||
if begin_number + One::one() > end_number {
|
||||
return Err(ClientError::Backend(
|
||||
format!("Cannot generate finality proof for invalid range: {}..{}", begin_number, end_number),
|
||||
));
|
||||
}
|
||||
|
||||
// early-return if we sure that the block is NOT a part of canonical chain
|
||||
let canonical_begin = blockchain.expect_block_hash_from_id(&BlockId::Number(begin_number))?;
|
||||
if begin != canonical_begin {
|
||||
return Err(ClientError::Backend(
|
||||
format!("Cannot generate finality proof for non-canonical block: {}", begin),
|
||||
));
|
||||
}
|
||||
|
||||
// iterate justifications && try to prove finality
|
||||
let mut fragment_index = 0;
|
||||
let mut current_authorities = authorities_provider.authorities(&begin_id)?;
|
||||
let mut current_number = begin_number + One::one();
|
||||
let mut finality_proof = Vec::new();
|
||||
let mut unknown_headers = Vec::new();
|
||||
let mut latest_proof_fragment = None;
|
||||
loop {
|
||||
let current_id = BlockId::Number(current_number);
|
||||
|
||||
// check if header is unknown to the caller
|
||||
if current_number > end_number {
|
||||
let unknown_header = blockchain.expect_header(current_id)?;
|
||||
unknown_headers.push(unknown_header);
|
||||
}
|
||||
|
||||
if let Some(justification) = blockchain.justification(current_id)? {
|
||||
// check if the current block enacts new GRANDPA authorities set
|
||||
let parent_id = BlockId::Number(current_number - One::one());
|
||||
let new_authorities = authorities_provider.authorities(&parent_id)?;
|
||||
let new_authorities_proof = if current_authorities != new_authorities {
|
||||
current_authorities = new_authorities;
|
||||
Some(authorities_provider.prove_authorities(&parent_id)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// prepare finality proof for the current block
|
||||
let current = blockchain.expect_block_hash_from_id(&BlockId::Number(current_number))?;
|
||||
let proof_fragment = FinalityProofFragment {
|
||||
block: current,
|
||||
justification,
|
||||
unknown_headers: ::std::mem::replace(&mut unknown_headers, Vec::new()),
|
||||
authorities_proof: new_authorities_proof,
|
||||
};
|
||||
|
||||
// append justification to finality proof if required
|
||||
let justifies_end_block = current_number >= end_number;
|
||||
let justifies_authority_set_change = proof_fragment.authorities_proof.is_some();
|
||||
if justifies_end_block || justifies_authority_set_change {
|
||||
// check if the proof is generated by the requested authority set
|
||||
if finality_proof.is_empty() {
|
||||
let justification_check_result = J::decode_and_verify(
|
||||
&proof_fragment.justification,
|
||||
authorities_set_id,
|
||||
¤t_authorities,
|
||||
);
|
||||
if justification_check_result.is_err() {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Can not provide finality proof with requested set id #{}\
|
||||
(possible forced change?). Returning empty proof.",
|
||||
authorities_set_id,
|
||||
);
|
||||
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
finality_proof.push(proof_fragment);
|
||||
latest_proof_fragment = None;
|
||||
} else {
|
||||
latest_proof_fragment = Some(proof_fragment);
|
||||
}
|
||||
|
||||
// we don't need to provide more justifications
|
||||
if justifies_end_block {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// we can't provide more justifications
|
||||
if current_number == info.finalized_number {
|
||||
// append last justification - even if we can't generate finality proof for
|
||||
// the end block, we try to generate it for the latest possible block
|
||||
if let Some(latest_proof_fragment) = latest_proof_fragment.take() {
|
||||
finality_proof.push(latest_proof_fragment);
|
||||
|
||||
fragment_index += 1;
|
||||
if fragment_index == MAX_FRAGMENTS_IN_PROOF {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// else search for the next justification
|
||||
current_number = current_number + One::one();
|
||||
}
|
||||
|
||||
if finality_proof.is_empty() {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"No justifications found when making finality proof for {}. Returning empty proof.",
|
||||
end,
|
||||
);
|
||||
|
||||
Ok(None)
|
||||
} else {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Built finality proof for {} of {} fragments. Last fragment for {}.",
|
||||
end,
|
||||
finality_proof.len(),
|
||||
finality_proof.last().expect("checked that !finality_proof.is_empty(); qed").block,
|
||||
);
|
||||
|
||||
Ok(Some(finality_proof.encode()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Check GRANDPA proof-of-finality for the given block.
|
||||
///
|
||||
/// Returns the vector of headers that MUST be validated + imported
|
||||
/// AND if at least one of those headers is invalid, all other MUST be considered invalid.
|
||||
pub(crate) fn check_finality_proof<Block: BlockT<Hash=H256>, B>(
|
||||
blockchain: &B,
|
||||
current_set_id: u64,
|
||||
current_authorities: AuthorityList,
|
||||
authorities_provider: &dyn AuthoritySetForFinalityChecker<Block>,
|
||||
remote_proof: Vec<u8>,
|
||||
) -> ClientResult<FinalityEffects<Block::Header>>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
B: BlockchainBackend<Block>,
|
||||
{
|
||||
do_check_finality_proof::<_, _, GrandpaJustification<Block>>(
|
||||
blockchain,
|
||||
current_set_id,
|
||||
current_authorities,
|
||||
authorities_provider,
|
||||
remote_proof)
|
||||
}
|
||||
|
||||
fn do_check_finality_proof<Block: BlockT<Hash=H256>, B, J>(
|
||||
blockchain: &B,
|
||||
current_set_id: u64,
|
||||
current_authorities: AuthorityList,
|
||||
authorities_provider: &dyn AuthoritySetForFinalityChecker<Block>,
|
||||
remote_proof: Vec<u8>,
|
||||
) -> ClientResult<FinalityEffects<Block::Header>>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
B: BlockchainBackend<Block>,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
// decode finality proof
|
||||
let proof = FinalityProof::<Block::Header>::decode(&mut &remote_proof[..])
|
||||
.map_err(|_| ClientError::BadJustification("failed to decode finality proof".into()))?;
|
||||
|
||||
// empty proof can't prove anything
|
||||
if proof.is_empty() {
|
||||
return Err(ClientError::BadJustification("empty proof of finality".into()));
|
||||
}
|
||||
|
||||
// iterate and verify proof fragments
|
||||
let last_fragment_index = proof.len() - 1;
|
||||
let mut authorities = AuthoritiesOrEffects::Authorities(current_set_id, current_authorities);
|
||||
for (proof_fragment_index, proof_fragment) in proof.into_iter().enumerate() {
|
||||
// check that proof is non-redundant. The proof still can be valid, but
|
||||
// we do not want peer to spam us with redundant data
|
||||
if proof_fragment_index != last_fragment_index {
|
||||
let has_unknown_headers = !proof_fragment.unknown_headers.is_empty();
|
||||
let has_new_authorities = proof_fragment.authorities_proof.is_some();
|
||||
if has_unknown_headers || !has_new_authorities {
|
||||
return Err(ClientError::BadJustification("redundant proof of finality".into()));
|
||||
}
|
||||
}
|
||||
|
||||
authorities = check_finality_proof_fragment::<_, _, J>(
|
||||
blockchain,
|
||||
authorities,
|
||||
authorities_provider,
|
||||
proof_fragment)?;
|
||||
}
|
||||
|
||||
let effects = authorities.extract_effects().expect("at least one loop iteration is guaranteed
|
||||
because proof is not empty;\
|
||||
check_finality_proof_fragment is called on every iteration;\
|
||||
check_finality_proof_fragment always returns FinalityEffects;\
|
||||
qed");
|
||||
|
||||
telemetry!(CONSENSUS_INFO; "afg.finality_proof_ok";
|
||||
"set_id" => ?effects.new_set_id, "finalized_header_hash" => ?effects.block);
|
||||
|
||||
Ok(effects)
|
||||
}
|
||||
|
||||
/// Check finality proof for the single block.
|
||||
fn check_finality_proof_fragment<Block: BlockT<Hash=H256>, B, J>(
|
||||
blockchain: &B,
|
||||
authority_set: AuthoritiesOrEffects<Block::Header>,
|
||||
authorities_provider: &dyn AuthoritySetForFinalityChecker<Block>,
|
||||
proof_fragment: FinalityProofFragment<Block::Header>,
|
||||
) -> ClientResult<AuthoritiesOrEffects<Block::Header>>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
B: BlockchainBackend<Block>,
|
||||
J: Decode + ProvableJustification<Block::Header>,
|
||||
{
|
||||
// verify justification using previous authorities set
|
||||
let (mut current_set_id, mut current_authorities) = authority_set.extract_authorities();
|
||||
let justification: J = Decode::decode(&mut &proof_fragment.justification[..])
|
||||
.map_err(|_| ClientError::JustificationDecode)?;
|
||||
justification.verify(current_set_id, ¤t_authorities)?;
|
||||
|
||||
// and now verify new authorities proof (if provided)
|
||||
if let Some(new_authorities_proof) = proof_fragment.authorities_proof {
|
||||
// it is safe to query header here, because its non-finality proves that it can't be pruned
|
||||
let header = blockchain.expect_header(BlockId::Hash(proof_fragment.block))?;
|
||||
let parent_hash = *header.parent_hash();
|
||||
let parent_header = blockchain.expect_header(BlockId::Hash(parent_hash))?;
|
||||
current_authorities = authorities_provider.check_authorities_proof(
|
||||
parent_hash,
|
||||
parent_header,
|
||||
new_authorities_proof,
|
||||
)?;
|
||||
|
||||
current_set_id = current_set_id + 1;
|
||||
}
|
||||
|
||||
Ok(AuthoritiesOrEffects::Effects(FinalityEffects {
|
||||
headers_to_import: proof_fragment.unknown_headers,
|
||||
block: proof_fragment.block,
|
||||
justification: proof_fragment.justification,
|
||||
new_set_id: current_set_id,
|
||||
new_authorities: current_authorities,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Authorities set from initial authorities set or finality effects.
|
||||
enum AuthoritiesOrEffects<Header: HeaderT> {
|
||||
Authorities(u64, AuthorityList),
|
||||
Effects(FinalityEffects<Header>),
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AuthoritiesOrEffects<Header> {
|
||||
pub fn extract_authorities(self) -> (u64, AuthorityList) {
|
||||
match self {
|
||||
AuthoritiesOrEffects::Authorities(set_id, authorities) => (set_id, authorities),
|
||||
AuthoritiesOrEffects::Effects(effects) => (effects.new_set_id, effects.new_authorities),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_effects(self) -> Option<FinalityEffects<Header>> {
|
||||
match self {
|
||||
AuthoritiesOrEffects::Authorities(_, _) => None,
|
||||
AuthoritiesOrEffects::Effects(effects) => Some(effects),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Justification used to prove block finality.
|
||||
pub(crate) trait ProvableJustification<Header: HeaderT>: Encode + Decode {
|
||||
/// Verify justification with respect to authorities set and authorities set id.
|
||||
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()>;
|
||||
|
||||
/// Decode and verify justification.
|
||||
fn decode_and_verify(
|
||||
justification: &Justification,
|
||||
set_id: u64,
|
||||
authorities: &[(AuthorityId, u64)],
|
||||
) -> ClientResult<Self> {
|
||||
let justification = Self::decode(&mut &**justification)
|
||||
.map_err(|_| ClientError::JustificationDecode)?;
|
||||
justification.verify(set_id, authorities)?;
|
||||
Ok(justification)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT<Hash=H256>> ProvableJustification<Block::Header> for GrandpaJustification<Block>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()> {
|
||||
GrandpaJustification::verify(self, set_id, &authorities.iter().cloned().collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use test_client::runtime::{Block, Header, H256};
|
||||
use client_api::NewBlockState;
|
||||
use test_client::client::in_mem::Blockchain as InMemoryBlockchain;
|
||||
use super::*;
|
||||
use primitives::crypto::Public;
|
||||
|
||||
type FinalityProof = super::FinalityProof<Header>;
|
||||
|
||||
impl<GetAuthorities, ProveAuthorities> AuthoritySetForFinalityProver<Block> for (GetAuthorities, ProveAuthorities)
|
||||
where
|
||||
GetAuthorities: Send + Sync + Fn(BlockId<Block>) -> ClientResult<AuthorityList>,
|
||||
ProveAuthorities: Send + Sync + Fn(BlockId<Block>) -> ClientResult<StorageProof>,
|
||||
{
|
||||
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList> {
|
||||
self.0(*block)
|
||||
}
|
||||
|
||||
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof> {
|
||||
self.1(*block)
|
||||
}
|
||||
}
|
||||
|
||||
struct ClosureAuthoritySetForFinalityChecker<Closure>(pub Closure);
|
||||
|
||||
impl<Closure> AuthoritySetForFinalityChecker<Block> for ClosureAuthoritySetForFinalityChecker<Closure>
|
||||
where
|
||||
Closure: Send + Sync + Fn(H256, Header, StorageProof) -> ClientResult<AuthorityList>,
|
||||
{
|
||||
fn check_authorities_proof(
|
||||
&self,
|
||||
hash: H256,
|
||||
header: Header,
|
||||
proof: StorageProof
|
||||
) -> ClientResult<AuthorityList> {
|
||||
self.0(hash, header, proof)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Encode, Decode)]
|
||||
pub struct TestJustification(pub bool, pub Vec<u8>);
|
||||
|
||||
impl ProvableJustification<Header> for TestJustification {
|
||||
fn verify(&self, _set_id: u64, _authorities: &[(AuthorityId, u64)]) -> ClientResult<()> {
|
||||
if self.0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ClientError::BadJustification("test".into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn header(number: u64) -> Header {
|
||||
let parent_hash = match number {
|
||||
0 => Default::default(),
|
||||
_ => header(number - 1).hash(),
|
||||
};
|
||||
Header::new(number, H256::from_low_u64_be(0), H256::from_low_u64_be(0), parent_hash, Default::default())
|
||||
}
|
||||
|
||||
fn side_header(number: u64) -> Header {
|
||||
Header::new(
|
||||
number,
|
||||
H256::from_low_u64_be(0),
|
||||
H256::from_low_u64_be(1),
|
||||
header(number - 1).hash(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn second_side_header(number: u64) -> Header {
|
||||
Header::new(
|
||||
number,
|
||||
H256::from_low_u64_be(0),
|
||||
H256::from_low_u64_be(1),
|
||||
side_header(number - 1).hash(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
fn test_blockchain() -> InMemoryBlockchain<Block> {
|
||||
let blockchain = InMemoryBlockchain::<Block>::new();
|
||||
blockchain.insert(header(0).hash(), header(0), Some(vec![0]), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(1).hash(), header(1), Some(vec![1]), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(2).hash(), header(2), None, None, NewBlockState::Best).unwrap();
|
||||
blockchain.insert(header(3).hash(), header(3), Some(vec![3]), None, NewBlockState::Final).unwrap();
|
||||
blockchain
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_prove_fails_with_invalid_range() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
// their last finalized is: 2
|
||||
// they request for proof-of-finality of: 2
|
||||
// => range is invalid
|
||||
prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| unreachable!("should return before calling GetAuthorities"),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
header(2).hash(),
|
||||
header(2).hash(),
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_is_none_if_no_more_last_finalized_blocks() {
|
||||
let blockchain = test_blockchain();
|
||||
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
|
||||
|
||||
// our last finalized is: 3
|
||||
// their last finalized is: 3
|
||||
// => we can't provide any additional justifications
|
||||
let proof_of_4 = prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| unreachable!("should return before calling GetAuthorities"),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(4).hash(),
|
||||
).unwrap();
|
||||
assert_eq!(proof_of_4, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_fails_for_non_canonical_block() {
|
||||
let blockchain = test_blockchain();
|
||||
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
|
||||
blockchain.insert(side_header(4).hash(), side_header(4), None, None, NewBlockState::Best).unwrap();
|
||||
blockchain.insert(second_side_header(5).hash(), second_side_header(5), None, None, NewBlockState::Best)
|
||||
.unwrap();
|
||||
blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap();
|
||||
|
||||
// chain is 1 -> 2 -> 3 -> 4 -> 5
|
||||
// \> 4' -> 5'
|
||||
// and the best finalized is 5
|
||||
// => when requesting for (4'; 5'], error is returned
|
||||
prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| unreachable!("should return before calling GetAuthorities"),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
side_header(4).hash(),
|
||||
second_side_header(5).hash(),
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_is_none_if_no_justification_known() {
|
||||
let blockchain = test_blockchain();
|
||||
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Final).unwrap();
|
||||
|
||||
// block 4 is finalized without justification
|
||||
// => we can't prove finality
|
||||
let proof_of_4 = prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|
||||
|_| unreachable!("authorities didn't change => ProveAuthorities won't be called"),
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(4).hash(),
|
||||
).unwrap();
|
||||
assert_eq!(proof_of_4, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_works_without_authorities_change() {
|
||||
let blockchain = test_blockchain();
|
||||
let just4 = TestJustification(true, vec![4]).encode();
|
||||
let just5 = TestJustification(true, vec![5]).encode();
|
||||
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), None, NewBlockState::Final).unwrap();
|
||||
|
||||
// blocks 4 && 5 are finalized with justification
|
||||
// => since authorities are the same, we only need justification for 5
|
||||
let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(5).hash(),
|
||||
).unwrap().unwrap()[..]).unwrap();
|
||||
assert_eq!(proof_of_5, vec![FinalityProofFragment {
|
||||
block: header(5).hash(),
|
||||
justification: just5,
|
||||
unknown_headers: Vec::new(),
|
||||
authorities_proof: None,
|
||||
}]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_finalized_earlier_block_if_no_justification_for_target_is_known() {
|
||||
let blockchain = test_blockchain();
|
||||
blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(5).hash(), header(5), None, None, NewBlockState::Final).unwrap();
|
||||
|
||||
// block 4 is finalized with justification + we request for finality of 5
|
||||
// => we can't prove finality of 5, but providing finality for 4 is still useful for requester
|
||||
let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(5).hash(),
|
||||
).unwrap().unwrap()[..]).unwrap();
|
||||
assert_eq!(proof_of_5, vec![FinalityProofFragment {
|
||||
block: header(4).hash(),
|
||||
justification: vec![4],
|
||||
unknown_headers: Vec::new(),
|
||||
authorities_proof: None,
|
||||
}]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_works_with_authorities_change() {
|
||||
let blockchain = test_blockchain();
|
||||
let just4 = TestJustification(true, vec![4]).encode();
|
||||
let just5 = TestJustification(true, vec![5]).encode();
|
||||
let just7 = TestJustification(true, vec![7]).encode();
|
||||
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(6).hash(), header(6), None, None, NewBlockState::Final).unwrap();
|
||||
blockchain.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final).unwrap();
|
||||
|
||||
// when querying for finality of 6, we assume that the #6 is the last block known to the requester
|
||||
// => since we only have justification for #7, we provide #7
|
||||
let proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|block_id| match block_id {
|
||||
BlockId::Hash(h) if h == header(3).hash() => Ok(
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)]
|
||||
),
|
||||
BlockId::Number(3) => Ok(vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)]),
|
||||
BlockId::Number(4) => Ok(vec![(AuthorityId::from_slice(&[4u8; 32]), 1u64)]),
|
||||
BlockId::Number(6) => Ok(vec![(AuthorityId::from_slice(&[6u8; 32]), 1u64)]),
|
||||
_ => unreachable!("no other authorities should be fetched: {:?}", block_id),
|
||||
},
|
||||
|block_id| match block_id {
|
||||
BlockId::Number(4) => Ok(StorageProof::new(vec![vec![40]])),
|
||||
BlockId::Number(6) => Ok(StorageProof::new(vec![vec![60]])),
|
||||
_ => unreachable!("no other authorities should be proved: {:?}", block_id),
|
||||
},
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(6).hash(),
|
||||
).unwrap().unwrap()[..]).unwrap();
|
||||
// initial authorities set (which start acting from #4) is [3; 32]
|
||||
assert_eq!(proof_of_6, vec![
|
||||
// new authorities set starts acting from #5 => we do not provide fragment for #4
|
||||
// first fragment provides justification for #5 && authorities set that starts acting from #5
|
||||
FinalityProofFragment {
|
||||
block: header(5).hash(),
|
||||
justification: just5,
|
||||
unknown_headers: Vec::new(),
|
||||
authorities_proof: Some(StorageProof::new(vec![vec![40]])),
|
||||
},
|
||||
// last fragment provides justification for #7 && unknown#7
|
||||
FinalityProofFragment {
|
||||
block: header(7).hash(),
|
||||
justification: just7,
|
||||
unknown_headers: vec![header(7)],
|
||||
authorities_proof: Some(StorageProof::new(vec![vec![60]])),
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_proof_decode_fails() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
// when we can't decode proof from Vec<u8>
|
||||
do_check_finality_proof::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
1,
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
|
||||
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
|
||||
vec![42],
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_proof_is_empty() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
// when decoded proof has zero length
|
||||
do_check_finality_proof::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
1,
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
|
||||
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
|
||||
Vec::<TestJustification>::new().encode(),
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_intemediate_fragment_has_unknown_headers() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
// when intermediate (#0) fragment has non-empty unknown headers
|
||||
do_check_finality_proof::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
1,
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
|
||||
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
|
||||
vec![FinalityProofFragment {
|
||||
block: header(4).hash(),
|
||||
justification: TestJustification(true, vec![7]).encode(),
|
||||
unknown_headers: vec![header(4)],
|
||||
authorities_proof: Some(StorageProof::new(vec![vec![42]])),
|
||||
}, FinalityProofFragment {
|
||||
block: header(5).hash(),
|
||||
justification: TestJustification(true, vec![8]).encode(),
|
||||
unknown_headers: vec![header(5)],
|
||||
authorities_proof: None,
|
||||
}].encode(),
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_fails_when_intemediate_fragment_has_no_authorities_proof() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
// when intermediate (#0) fragment has empty authorities proof
|
||||
do_check_finality_proof::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
1,
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
|
||||
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
|
||||
vec![FinalityProofFragment {
|
||||
block: header(4).hash(),
|
||||
justification: TestJustification(true, vec![7]).encode(),
|
||||
unknown_headers: Vec::new(),
|
||||
authorities_proof: None,
|
||||
}, FinalityProofFragment {
|
||||
block: header(5).hash(),
|
||||
justification: TestJustification(true, vec![8]).encode(),
|
||||
unknown_headers: vec![header(5)],
|
||||
authorities_proof: None,
|
||||
}].encode(),
|
||||
).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_check_works() {
|
||||
let blockchain = test_blockchain();
|
||||
|
||||
let effects = do_check_finality_proof::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
1,
|
||||
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
|
||||
&ClosureAuthoritySetForFinalityChecker(|_, _, _| Ok(vec![(AuthorityId::from_slice(&[4u8; 32]), 1u64)])),
|
||||
vec![FinalityProofFragment {
|
||||
block: header(2).hash(),
|
||||
justification: TestJustification(true, vec![7]).encode(),
|
||||
unknown_headers: Vec::new(),
|
||||
authorities_proof: Some(StorageProof::new(vec![vec![42]])),
|
||||
}, FinalityProofFragment {
|
||||
block: header(4).hash(),
|
||||
justification: TestJustification(true, vec![8]).encode(),
|
||||
unknown_headers: vec![header(4)],
|
||||
authorities_proof: None,
|
||||
}].encode(),
|
||||
).unwrap();
|
||||
assert_eq!(effects, FinalityEffects {
|
||||
headers_to_import: vec![header(4)],
|
||||
block: header(4).hash(),
|
||||
justification: TestJustification(true, vec![8]).encode(),
|
||||
new_set_id: 2,
|
||||
new_authorities: vec![(AuthorityId::from_slice(&[4u8; 32]), 1u64)],
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_is_none_if_first_justification_is_generated_by_unknown_set() {
|
||||
// this is the case for forced change: set_id has been forcibly increased on full node
|
||||
// and ligh node missed that
|
||||
// => justification verification will fail on light node anyways, so we do not return
|
||||
// finality proof at all
|
||||
let blockchain = test_blockchain();
|
||||
let just4 = TestJustification(false, vec![4]).encode(); // false makes verification fail
|
||||
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
|
||||
|
||||
let proof_of_4 = prove_finality::<_, _, TestJustification>(
|
||||
&blockchain,
|
||||
&(
|
||||
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|
||||
|_| unreachable!("should return before calling ProveAuthorities"),
|
||||
),
|
||||
0,
|
||||
header(3).hash(),
|
||||
header(4).hash(),
|
||||
).unwrap();
|
||||
assert!(proof_of_4.is_none());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{sync::Arc, collections::HashMap};
|
||||
|
||||
use log::{debug, trace, info};
|
||||
use codec::Encode;
|
||||
use futures::sync::mpsc;
|
||||
use parking_lot::RwLockWriteGuard;
|
||||
|
||||
use client_api::{
|
||||
backend::Backend, blockchain,
|
||||
CallExecutor, blockchain::HeaderBackend, well_known_cache_keys,
|
||||
utils::is_descendent_of,
|
||||
};
|
||||
use client::Client;
|
||||
use consensus_common::{
|
||||
BlockImport, Error as ConsensusError,
|
||||
BlockCheckParams, BlockImportParams, ImportResult, JustificationImport,
|
||||
SelectChain,
|
||||
};
|
||||
use fg_primitives::{GRANDPA_ENGINE_ID, ScheduledChange, ConsensusLog};
|
||||
use sr_primitives::Justification;
|
||||
use sr_primitives::generic::{BlockId, OpaqueDigestItemId};
|
||||
use sr_primitives::traits::{
|
||||
Block as BlockT, DigestFor, Header as HeaderT, NumberFor, Zero,
|
||||
};
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
|
||||
use crate::{Error, CommandOrError, NewAuthoritySet, VoterCommand};
|
||||
use crate::authorities::{AuthoritySet, SharedAuthoritySet, DelayKind, PendingChange};
|
||||
use crate::consensus_changes::SharedConsensusChanges;
|
||||
use crate::environment::finalize_block;
|
||||
use crate::justification::GrandpaJustification;
|
||||
|
||||
/// A block-import handler for GRANDPA.
|
||||
///
|
||||
/// This scans each imported block for signals of changing authority set.
|
||||
/// If the block being imported enacts an authority set change then:
|
||||
/// - If the current authority set is still live: we import the block
|
||||
/// - Otherwise, the block must include a valid justification.
|
||||
///
|
||||
/// When using GRANDPA, the block import worker should be using this block import
|
||||
/// object.
|
||||
pub struct GrandpaBlockImport<B, E, Block: BlockT<Hash=H256>, RA, SC> {
|
||||
inner: Arc<Client<B, E, Block, RA>>,
|
||||
select_chain: SC,
|
||||
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
send_voter_commands: mpsc::UnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC: Clone> Clone for
|
||||
GrandpaBlockImport<B, E, Block, RA, SC>
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
GrandpaBlockImport {
|
||||
inner: self.inner.clone(),
|
||||
select_chain: self.select_chain.clone(),
|
||||
authority_set: self.authority_set.clone(),
|
||||
send_voter_commands: self.send_voter_commands.clone(),
|
||||
consensus_changes: self.consensus_changes.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC> JustificationImport<Block>
|
||||
for GrandpaBlockImport<B, E, Block, RA, SC> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
SC: SelectChain<Block>,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn on_start(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
|
||||
let mut out = Vec::new();
|
||||
let chain_info = self.inner.info().chain;
|
||||
|
||||
// request justifications for all pending changes for which change blocks have already been imported
|
||||
let authorities = self.authority_set.inner().read();
|
||||
for pending_change in authorities.pending_changes() {
|
||||
if pending_change.delay_kind == DelayKind::Finalized &&
|
||||
pending_change.effective_number() > chain_info.finalized_number &&
|
||||
pending_change.effective_number() <= chain_info.best_number
|
||||
{
|
||||
let effective_block_hash = if !pending_change.delay.is_zero() {
|
||||
self.select_chain.finality_target(
|
||||
pending_change.canon_hash,
|
||||
Some(pending_change.effective_number()),
|
||||
)
|
||||
} else {
|
||||
Ok(Some(pending_change.canon_hash))
|
||||
};
|
||||
|
||||
if let Ok(Some(hash)) = effective_block_hash {
|
||||
if let Ok(Some(header)) = self.inner.header(&BlockId::Hash(hash)) {
|
||||
if *header.number() == pending_change.effective_number() {
|
||||
out.push((header.hash(), *header.number()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn import_justification(
|
||||
&mut self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.import_justification(hash, number, justification, false)
|
||||
}
|
||||
}
|
||||
|
||||
enum AppliedChanges<H, N> {
|
||||
Standard(bool), // true if the change is ready to be applied (i.e. it's a root)
|
||||
Forced(NewAuthoritySet<H, N>),
|
||||
None,
|
||||
}
|
||||
|
||||
impl<H, N> AppliedChanges<H, N> {
|
||||
fn needs_justification(&self) -> bool {
|
||||
match *self {
|
||||
AppliedChanges::Standard(_) => true,
|
||||
AppliedChanges::Forced(_) | AppliedChanges::None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PendingSetChanges<'a, Block: 'a + BlockT> {
|
||||
just_in_case: Option<(
|
||||
AuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
RwLockWriteGuard<'a, AuthoritySet<Block::Hash, NumberFor<Block>>>,
|
||||
)>,
|
||||
applied_changes: AppliedChanges<Block::Hash, NumberFor<Block>>,
|
||||
do_pause: bool,
|
||||
}
|
||||
|
||||
impl<'a, Block: 'a + BlockT> PendingSetChanges<'a, Block> {
|
||||
// revert the pending set change explicitly.
|
||||
fn revert(self) { }
|
||||
|
||||
fn defuse(mut self) -> (AppliedChanges<Block::Hash, NumberFor<Block>>, bool) {
|
||||
self.just_in_case = None;
|
||||
let applied_changes = ::std::mem::replace(&mut self.applied_changes, AppliedChanges::None);
|
||||
(applied_changes, self.do_pause)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, Block: 'a + BlockT> Drop for PendingSetChanges<'a, Block> {
|
||||
fn drop(&mut self) {
|
||||
if let Some((old_set, mut authorities)) = self.just_in_case.take() {
|
||||
*authorities = old_set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_scheduled_change<B: BlockT>(header: &B::Header)
|
||||
-> Option<ScheduledChange<NumberFor<B>>>
|
||||
{
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
|
||||
let filter_log = |log: ConsensusLog<NumberFor<B>>| match log {
|
||||
ConsensusLog::ScheduledChange(change) => Some(change),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// find the first consensus digest with the right ID which converts to
|
||||
// the right kind of consensus log.
|
||||
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||
}
|
||||
|
||||
fn find_forced_change<B: BlockT>(header: &B::Header)
|
||||
-> Option<(NumberFor<B>, ScheduledChange<NumberFor<B>>)>
|
||||
{
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
|
||||
let filter_log = |log: ConsensusLog<NumberFor<B>>| match log {
|
||||
ConsensusLog::ForcedChange(delay, change) => Some((delay, change)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// find the first consensus digest with the right ID which converts to
|
||||
// the right kind of consensus log.
|
||||
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC>
|
||||
GrandpaBlockImport<B, E, Block, RA, SC>
|
||||
where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
// check for a new authority set change.
|
||||
fn check_new_change(&self, header: &Block::Header, hash: Block::Hash)
|
||||
-> Option<PendingChange<Block::Hash, NumberFor<Block>>>
|
||||
{
|
||||
// check for forced change.
|
||||
if let Some((median_last_finalized, change)) = find_forced_change::<Block>(header) {
|
||||
return Some(PendingChange {
|
||||
next_authorities: change.next_authorities,
|
||||
delay: change.delay,
|
||||
canon_height: *header.number(),
|
||||
canon_hash: hash,
|
||||
delay_kind: DelayKind::Best { median_last_finalized },
|
||||
});
|
||||
}
|
||||
|
||||
// check normal scheduled change.
|
||||
let change = find_scheduled_change::<Block>(header)?;
|
||||
Some(PendingChange {
|
||||
next_authorities: change.next_authorities,
|
||||
delay: change.delay,
|
||||
canon_height: *header.number(),
|
||||
canon_hash: hash,
|
||||
delay_kind: DelayKind::Finalized,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_authorities_changes<'a>(&'a self, block: &mut BlockImportParams<Block>, hash: Block::Hash)
|
||||
-> Result<PendingSetChanges<'a, Block>, ConsensusError>
|
||||
{
|
||||
// when we update the authorities, we need to hold the lock
|
||||
// until the block is written to prevent a race if we need to restore
|
||||
// the old authority set on error or panic.
|
||||
struct InnerGuard<'a, T: 'a> {
|
||||
old: Option<T>,
|
||||
guard: Option<RwLockWriteGuard<'a, T>>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> InnerGuard<'a, T> {
|
||||
fn as_mut(&mut self) -> &mut T {
|
||||
&mut **self.guard.as_mut().expect("only taken on deconstruction; qed")
|
||||
}
|
||||
|
||||
fn set_old(&mut self, old: T) {
|
||||
if self.old.is_none() {
|
||||
// ignore "newer" old changes.
|
||||
self.old = Some(old);
|
||||
}
|
||||
}
|
||||
|
||||
fn consume(mut self) -> Option<(T, RwLockWriteGuard<'a, T>)> {
|
||||
if let Some(old) = self.old.take() {
|
||||
Some((old, self.guard.take().expect("only taken on deconstruction; qed")))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a> Drop for InnerGuard<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if let (Some(mut guard), Some(old)) = (self.guard.take(), self.old.take()) {
|
||||
*guard = old;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let number = block.header.number().clone();
|
||||
let maybe_change = self.check_new_change(
|
||||
&block.header,
|
||||
hash,
|
||||
);
|
||||
|
||||
// returns a function for checking whether a block is a descendent of another
|
||||
// consistent with querying client directly after importing the block.
|
||||
let parent_hash = *block.header.parent_hash();
|
||||
let is_descendent_of = is_descendent_of(&*self.inner, Some((&hash, &parent_hash)));
|
||||
|
||||
let mut guard = InnerGuard {
|
||||
guard: Some(self.authority_set.inner().write()),
|
||||
old: None,
|
||||
};
|
||||
|
||||
// whether to pause the old authority set -- happens after import
|
||||
// of a forced change block.
|
||||
let mut do_pause = false;
|
||||
|
||||
// add any pending changes.
|
||||
if let Some(change) = maybe_change {
|
||||
let old = guard.as_mut().clone();
|
||||
guard.set_old(old);
|
||||
|
||||
if let DelayKind::Best { .. } = change.delay_kind {
|
||||
do_pause = true;
|
||||
}
|
||||
|
||||
guard.as_mut().add_pending_change(
|
||||
change,
|
||||
&is_descendent_of,
|
||||
).map_err(|e| ConsensusError::from(ConsensusError::ClientImport(e.to_string())))?;
|
||||
}
|
||||
|
||||
let applied_changes = {
|
||||
let forced_change_set = guard.as_mut().apply_forced_changes(hash, number, &is_descendent_of)
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))
|
||||
.map_err(ConsensusError::from)?;
|
||||
|
||||
if let Some((median_last_finalized_number, new_set)) = forced_change_set {
|
||||
let new_authorities = {
|
||||
let (set_id, new_authorities) = new_set.current();
|
||||
|
||||
// we will use the median last finalized number as a hint
|
||||
// for the canon block the new authority set should start
|
||||
// with. we use the minimum between the median and the local
|
||||
// best finalized block.
|
||||
let best_finalized_number = self.inner.info().chain.finalized_number;
|
||||
let canon_number = best_finalized_number.min(median_last_finalized_number);
|
||||
let canon_hash =
|
||||
self.inner.header(&BlockId::Number(canon_number))
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?
|
||||
.expect("the given block number is less or equal than the current best finalized number; \
|
||||
current best finalized number must exist in chain; qed.")
|
||||
.hash();
|
||||
|
||||
NewAuthoritySet {
|
||||
canon_number,
|
||||
canon_hash,
|
||||
set_id,
|
||||
authorities: new_authorities.to_vec(),
|
||||
}
|
||||
};
|
||||
let old = ::std::mem::replace(guard.as_mut(), new_set);
|
||||
guard.set_old(old);
|
||||
|
||||
AppliedChanges::Forced(new_authorities)
|
||||
} else {
|
||||
let did_standard = guard.as_mut().enacts_standard_change(hash, number, &is_descendent_of)
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))
|
||||
.map_err(ConsensusError::from)?;
|
||||
|
||||
if let Some(root) = did_standard {
|
||||
AppliedChanges::Standard(root)
|
||||
} else {
|
||||
AppliedChanges::None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// consume the guard safely and write necessary changes.
|
||||
let just_in_case = guard.consume();
|
||||
if let Some((_, ref authorities)) = just_in_case {
|
||||
let authorities_change = match applied_changes {
|
||||
AppliedChanges::Forced(ref new) => Some(new),
|
||||
AppliedChanges::Standard(_) => None, // the change isn't actually applied yet.
|
||||
AppliedChanges::None => None,
|
||||
};
|
||||
|
||||
crate::aux_schema::update_authority_set::<Block, _, _>(
|
||||
authorities,
|
||||
authorities_change,
|
||||
|insert| block.auxiliary.extend(
|
||||
insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Ok(PendingSetChanges { just_in_case, applied_changes, do_pause })
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC> BlockImport<Block>
|
||||
for GrandpaBlockImport<B, E, Block, RA, SC> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn import_block(
|
||||
&mut self,
|
||||
mut block: BlockImportParams<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
let hash = block.post_header().hash();
|
||||
let number = block.header.number().clone();
|
||||
|
||||
// early exit if block already in chain, otherwise the check for
|
||||
// authority changes will error when trying to re-import a change block
|
||||
match self.inner.status(BlockId::Hash(hash)) {
|
||||
Ok(blockchain::BlockStatus::InChain) => return Ok(ImportResult::AlreadyInChain),
|
||||
Ok(blockchain::BlockStatus::Unknown) => {},
|
||||
Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()),
|
||||
}
|
||||
|
||||
let pending_changes = self.make_authorities_changes(&mut block, hash)?;
|
||||
|
||||
// we don't want to finalize on `inner.import_block`
|
||||
let mut justification = block.justification.take();
|
||||
let enacts_consensus_change = !new_cache.is_empty();
|
||||
let import_result = (&*self.inner).import_block(block, new_cache);
|
||||
|
||||
let mut imported_aux = {
|
||||
match import_result {
|
||||
Ok(ImportResult::Imported(aux)) => aux,
|
||||
Ok(r) => {
|
||||
debug!(target: "afg", "Restoring old authority set after block import result: {:?}", r);
|
||||
pending_changes.revert();
|
||||
return Ok(r);
|
||||
},
|
||||
Err(e) => {
|
||||
debug!(target: "afg", "Restoring old authority set after block import error: {:?}", e);
|
||||
pending_changes.revert();
|
||||
return Err(ConsensusError::ClientImport(e.to_string()).into());
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let (applied_changes, do_pause) = pending_changes.defuse();
|
||||
|
||||
// Send the pause signal after import but BEFORE sending a `ChangeAuthorities` message.
|
||||
if do_pause {
|
||||
let _ = self.send_voter_commands.unbounded_send(
|
||||
VoterCommand::Pause(format!("Forced change scheduled after inactivity"))
|
||||
);
|
||||
}
|
||||
|
||||
let needs_justification = applied_changes.needs_justification();
|
||||
|
||||
match applied_changes {
|
||||
AppliedChanges::Forced(new) => {
|
||||
// NOTE: when we do a force change we are "discrediting" the old set so we
|
||||
// ignore any justifications from them. this block may contain a justification
|
||||
// which should be checked and imported below against the new authority
|
||||
// triggered by this forced change. the new grandpa voter will start at the
|
||||
// last median finalized block (which is before the block that enacts the
|
||||
// change), full nodes syncing the chain will not be able to successfully
|
||||
// import justifications for those blocks since their local authority set view
|
||||
// is still of the set before the forced change was enacted, still after #1867
|
||||
// they should import the block and discard the justification, and they will
|
||||
// then request a justification from sync if it's necessary (which they should
|
||||
// then be able to successfully validate).
|
||||
let _ = self.send_voter_commands.unbounded_send(VoterCommand::ChangeAuthorities(new));
|
||||
|
||||
// we must clear all pending justifications requests, presumably they won't be
|
||||
// finalized hence why this forced changes was triggered
|
||||
imported_aux.clear_justification_requests = true;
|
||||
},
|
||||
AppliedChanges::Standard(false) => {
|
||||
// we can't apply this change yet since there are other dependent changes that we
|
||||
// need to apply first, drop any justification that might have been provided with
|
||||
// the block to make sure we request them from `sync` which will ensure they'll be
|
||||
// applied in-order.
|
||||
justification.take();
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match justification {
|
||||
Some(justification) => {
|
||||
self.import_justification(hash, number, justification, needs_justification).unwrap_or_else(|err| {
|
||||
if needs_justification || enacts_consensus_change {
|
||||
debug!(target: "finality", "Imported block #{} that enacts authority set change with \
|
||||
invalid justification: {:?}, requesting justification from peers.", number, err);
|
||||
imported_aux.bad_justification = true;
|
||||
imported_aux.needs_justification = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
None => {
|
||||
if needs_justification {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Imported unjustified block #{} that enacts authority set change, waiting for finality for enactment.",
|
||||
number,
|
||||
);
|
||||
|
||||
imported_aux.needs_justification = true;
|
||||
}
|
||||
|
||||
// we have imported block with consensus data changes, but without justification
|
||||
// => remember to create justification when next block will be finalized
|
||||
if enacts_consensus_change {
|
||||
self.consensus_changes.lock().note_change((number, hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImportResult::Imported(imported_aux))
|
||||
}
|
||||
|
||||
fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<Block>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
self.inner.check_block(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC>
|
||||
GrandpaBlockImport<B, E, Block, RA, SC>
|
||||
{
|
||||
pub(crate) fn new(
|
||||
inner: Arc<Client<B, E, Block, RA>>,
|
||||
select_chain: SC,
|
||||
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
send_voter_commands: mpsc::UnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
consensus_changes: SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
) -> GrandpaBlockImport<B, E, Block, RA, SC> {
|
||||
GrandpaBlockImport {
|
||||
inner,
|
||||
select_chain,
|
||||
authority_set,
|
||||
send_voter_commands,
|
||||
consensus_changes,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA, SC>
|
||||
GrandpaBlockImport<B, E, Block, RA, SC>
|
||||
where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
|
||||
/// Import a block justification and finalize the block.
|
||||
///
|
||||
/// If `enacts_change` is set to true, then finalizing this block *must*
|
||||
/// enact an authority set change, the function will panic otherwise.
|
||||
fn import_justification(
|
||||
&mut self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
enacts_change: bool,
|
||||
) -> Result<(), ConsensusError> {
|
||||
let justification = GrandpaJustification::decode_and_verify_finalizes(
|
||||
&justification,
|
||||
(hash, number),
|
||||
self.authority_set.set_id(),
|
||||
&self.authority_set.current_authorities(),
|
||||
);
|
||||
|
||||
let justification = match justification {
|
||||
Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()),
|
||||
Ok(justification) => justification,
|
||||
};
|
||||
|
||||
let result = finalize_block(
|
||||
&*self.inner,
|
||||
&self.authority_set,
|
||||
&self.consensus_changes,
|
||||
None,
|
||||
hash,
|
||||
number,
|
||||
justification.into(),
|
||||
);
|
||||
|
||||
match result {
|
||||
Err(CommandOrError::VoterCommand(command)) => {
|
||||
info!(target: "finality", "Imported justification for block #{} that triggers \
|
||||
command {}, signaling voter.", number, command);
|
||||
|
||||
// send the command to the voter
|
||||
let _ = self.send_voter_commands.unbounded_send(command);
|
||||
},
|
||||
Err(CommandOrError::Error(e)) => {
|
||||
return Err(match e {
|
||||
Error::Grandpa(error) => ConsensusError::ClientImport(error.to_string()),
|
||||
Error::Network(error) => ConsensusError::ClientImport(error),
|
||||
Error::Blockchain(error) => ConsensusError::ClientImport(error),
|
||||
Error::Client(error) => ConsensusError::ClientImport(error.to_string()),
|
||||
Error::Safety(error) => ConsensusError::ClientImport(error),
|
||||
Error::Timer(error) => ConsensusError::ClientImport(error.to_string()),
|
||||
}.into());
|
||||
},
|
||||
Ok(_) => {
|
||||
assert!(!enacts_change, "returns Ok when no authority set change should be enacted; qed;");
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use client::Client;
|
||||
use client_api::{CallExecutor, backend::Backend, error::Error as ClientError};
|
||||
use codec::{Encode, Decode};
|
||||
use grandpa::voter_set::VoterSet;
|
||||
use grandpa::{Error as GrandpaError};
|
||||
use sr_primitives::generic::BlockId;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT, Header as HeaderT};
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
use fg_primitives::AuthorityId;
|
||||
|
||||
use crate::{Commit, Error};
|
||||
use crate::communication;
|
||||
|
||||
/// A GRANDPA justification for block finality, it includes a commit message and
|
||||
/// an ancestry proof including all headers routing all precommit target blocks
|
||||
/// to the commit target block. Due to the current voting strategy the precommit
|
||||
/// targets should be the same as the commit target, since honest voters don't
|
||||
/// vote past authority set change blocks.
|
||||
///
|
||||
/// This is meant to be stored in the db and passed around the network to other
|
||||
/// nodes, and are used by syncing nodes to prove authority set handoffs.
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct GrandpaJustification<Block: BlockT> {
|
||||
round: u64,
|
||||
pub(crate) commit: Commit<Block>,
|
||||
votes_ancestries: Vec<Block::Header>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT<Hash=H256>> GrandpaJustification<Block> {
|
||||
/// Create a GRANDPA justification from the given commit. This method
|
||||
/// assumes the commit is valid and well-formed.
|
||||
pub(crate) fn from_commit<B, E, RA>(
|
||||
client: &Client<B, E, Block, RA>,
|
||||
round: u64,
|
||||
commit: Commit<Block>,
|
||||
) -> Result<GrandpaJustification<Block>, Error> where
|
||||
B: Backend<Block, Blake2Hasher>,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
let mut votes_ancestries_hashes = HashSet::new();
|
||||
let mut votes_ancestries = Vec::new();
|
||||
|
||||
let error = || {
|
||||
let msg = "invalid precommits for target commit".to_string();
|
||||
Err(Error::Client(ClientError::BadJustification(msg)))
|
||||
};
|
||||
|
||||
for signed in commit.precommits.iter() {
|
||||
let mut current_hash = signed.precommit.target_hash.clone();
|
||||
loop {
|
||||
if current_hash == commit.target_hash { break; }
|
||||
|
||||
match client.header(&BlockId::Hash(current_hash))? {
|
||||
Some(current_header) => {
|
||||
if *current_header.number() <= commit.target_number {
|
||||
return error();
|
||||
}
|
||||
|
||||
let parent_hash = current_header.parent_hash().clone();
|
||||
if votes_ancestries_hashes.insert(current_hash) {
|
||||
votes_ancestries.push(current_header);
|
||||
}
|
||||
current_hash = parent_hash;
|
||||
},
|
||||
_ => return error(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(GrandpaJustification { round, commit, votes_ancestries })
|
||||
}
|
||||
|
||||
/// Decode a GRANDPA justification and validate the commit and the votes'
|
||||
/// ancestry proofs finalize the given block.
|
||||
pub(crate) fn decode_and_verify_finalizes(
|
||||
encoded: &[u8],
|
||||
finalized_target: (Block::Hash, NumberFor<Block>),
|
||||
set_id: u64,
|
||||
voters: &VoterSet<AuthorityId>,
|
||||
) -> Result<GrandpaJustification<Block>, ClientError> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
{
|
||||
|
||||
let justification = GrandpaJustification::<Block>::decode(&mut &*encoded)
|
||||
.map_err(|_| ClientError::JustificationDecode)?;
|
||||
|
||||
if (justification.commit.target_hash, justification.commit.target_number) != finalized_target {
|
||||
let msg = "invalid commit target in grandpa justification".to_string();
|
||||
Err(ClientError::BadJustification(msg))
|
||||
} else {
|
||||
justification.verify(set_id, voters).map(|_| justification)
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate the commit and the votes' ancestry proofs.
|
||||
pub(crate) fn verify(&self, set_id: u64, voters: &VoterSet<AuthorityId>) -> Result<(), ClientError>
|
||||
where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
{
|
||||
use grandpa::Chain;
|
||||
|
||||
let ancestry_chain = AncestryChain::<Block>::new(&self.votes_ancestries);
|
||||
|
||||
match grandpa::validate_commit(
|
||||
&self.commit,
|
||||
voters,
|
||||
&ancestry_chain,
|
||||
) {
|
||||
Ok(ref result) if result.ghost().is_some() => {},
|
||||
_ => {
|
||||
let msg = "invalid commit in grandpa justification".to_string();
|
||||
return Err(ClientError::BadJustification(msg));
|
||||
}
|
||||
}
|
||||
|
||||
let mut visited_hashes = HashSet::new();
|
||||
for signed in self.commit.precommits.iter() {
|
||||
if let Err(_) = communication::check_message_sig::<Block>(
|
||||
&grandpa::Message::Precommit(signed.precommit.clone()),
|
||||
&signed.id,
|
||||
&signed.signature,
|
||||
self.round,
|
||||
set_id,
|
||||
) {
|
||||
return Err(ClientError::BadJustification(
|
||||
"invalid signature for precommit in grandpa justification".to_string()).into());
|
||||
}
|
||||
|
||||
if self.commit.target_hash == signed.precommit.target_hash {
|
||||
continue;
|
||||
}
|
||||
|
||||
match ancestry_chain.ancestry(self.commit.target_hash, signed.precommit.target_hash) {
|
||||
Ok(route) => {
|
||||
// ancestry starts from parent hash but the precommit target hash has been visited
|
||||
visited_hashes.insert(signed.precommit.target_hash);
|
||||
for hash in route {
|
||||
visited_hashes.insert(hash);
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return Err(ClientError::BadJustification(
|
||||
"invalid precommit ancestry proof in grandpa justification".to_string()).into());
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let ancestry_hashes = self.votes_ancestries
|
||||
.iter()
|
||||
.map(|h: &Block::Header| h.hash())
|
||||
.collect();
|
||||
|
||||
if visited_hashes != ancestry_hashes {
|
||||
return Err(ClientError::BadJustification(
|
||||
"invalid precommit ancestries in grandpa justification with unused headers".to_string()).into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A utility trait implementing `grandpa::Chain` using a given set of headers.
|
||||
/// This is useful when validating commits, using the given set of headers to
|
||||
/// verify a valid ancestry route to the target commit block.
|
||||
struct AncestryChain<Block: BlockT> {
|
||||
ancestry: HashMap<Block::Hash, Block::Header>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> AncestryChain<Block> {
|
||||
fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
|
||||
let ancestry: HashMap<_, _> = ancestry
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|h: Block::Header| (h.hash(), h))
|
||||
.collect();
|
||||
|
||||
AncestryChain { ancestry }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT> grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps
|
||||
{
|
||||
fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> {
|
||||
let mut route = Vec::new();
|
||||
let mut current_hash = block;
|
||||
loop {
|
||||
if current_hash == base { break; }
|
||||
match self.ancestry.get(¤t_hash) {
|
||||
Some(current_header) => {
|
||||
current_hash = *current_header.parent_hash();
|
||||
route.push(current_hash);
|
||||
},
|
||||
_ => return Err(GrandpaError::NotDescendent),
|
||||
}
|
||||
}
|
||||
route.pop(); // remove the base
|
||||
|
||||
Ok(route)
|
||||
}
|
||||
|
||||
fn best_chain_containing(&self, _block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,957 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Integration of the GRANDPA finality gadget into substrate.
|
||||
//!
|
||||
//! This crate is unstable and the API and usage may change.
|
||||
//!
|
||||
//! This crate provides a long-running future that produces finality notifications.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! First, create a block-import wrapper with the `block_import` function. The
|
||||
//! GRANDPA worker needs to be linked together with this block import object, so
|
||||
//! a `LinkHalf` is returned as well. All blocks imported (from network or
|
||||
//! consensus or otherwise) must pass through this wrapper, otherwise consensus
|
||||
//! is likely to break in unexpected ways.
|
||||
//!
|
||||
//! Next, use the `LinkHalf` and a local configuration to `run_grandpa_voter`.
|
||||
//! This requires a `Network` implementation. The returned future should be
|
||||
//! driven to completion and will finalize blocks in the background.
|
||||
//!
|
||||
//! # Changing authority sets
|
||||
//!
|
||||
//! The rough idea behind changing authority sets in GRANDPA is that at some point,
|
||||
//! we obtain agreement for some maximum block height that the current set can
|
||||
//! finalize, and once a block with that height is finalized the next set will
|
||||
//! pick up finalization from there.
|
||||
//!
|
||||
//! Technically speaking, this would be implemented as a voting rule which says,
|
||||
//! "if there is a signal for a change in N blocks in block B, only vote on
|
||||
//! chains with length NUM(B) + N if they contain B". This conditional-inclusion
|
||||
//! logic is complex to compute because it requires looking arbitrarily far
|
||||
//! back in the chain.
|
||||
//!
|
||||
//! Instead, we keep track of a list of all signals we've seen so far (across
|
||||
//! all forks), sorted ascending by the block number they would be applied at.
|
||||
//! We never vote on chains with number higher than the earliest handoff block
|
||||
//! number (this is num(signal) + N). When finalizing a block, we either apply
|
||||
//! or prune any signaled changes based on whether the signaling block is
|
||||
//! included in the newly-finalized chain.
|
||||
|
||||
use futures::prelude::*;
|
||||
use log::{debug, error, info};
|
||||
use futures::sync::mpsc;
|
||||
use client_api::{
|
||||
BlockchainEvents, CallExecutor, backend::Backend, error::Error as ClientError,
|
||||
ExecutionStrategy, HeaderBackend
|
||||
};
|
||||
use client::Client;
|
||||
use codec::{Decode, Encode};
|
||||
use sr_primitives::generic::BlockId;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT, DigestFor, Zero};
|
||||
use keystore::KeyStorePtr;
|
||||
use inherents::InherentDataProviders;
|
||||
use consensus_common::SelectChain;
|
||||
use primitives::{H256, Blake2Hasher, Pair};
|
||||
use substrate_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG, CONSENSUS_WARN};
|
||||
use serde_json;
|
||||
|
||||
use paint_finality_tracker;
|
||||
|
||||
use grandpa::Error as GrandpaError;
|
||||
use grandpa::{voter, BlockNumberOps, voter_set::VoterSet};
|
||||
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
mod authorities;
|
||||
mod aux_schema;
|
||||
mod communication;
|
||||
mod consensus_changes;
|
||||
mod environment;
|
||||
mod finality_proof;
|
||||
mod import;
|
||||
mod justification;
|
||||
mod light_import;
|
||||
mod observer;
|
||||
mod until_imported;
|
||||
mod voting_rule;
|
||||
|
||||
pub use communication::Network;
|
||||
pub use finality_proof::FinalityProofProvider;
|
||||
pub use justification::GrandpaJustification;
|
||||
pub use light_import::light_block_import;
|
||||
pub use observer::run_grandpa_observer;
|
||||
pub use voting_rule::{
|
||||
BeforeBestBlock, ThreeQuartersOfTheUnfinalizedChain, VotingRule, VotingRulesBuilder
|
||||
};
|
||||
|
||||
use aux_schema::PersistentData;
|
||||
use environment::{Environment, VoterSetState};
|
||||
use import::GrandpaBlockImport;
|
||||
use until_imported::UntilGlobalMessageBlocksImported;
|
||||
use communication::NetworkBridge;
|
||||
use fg_primitives::{AuthorityList, AuthorityPair, AuthoritySignature, SetId};
|
||||
|
||||
// Re-export these two because it's just so damn convenient.
|
||||
pub use fg_primitives::{AuthorityId, ScheduledChange};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// A GRANDPA message for a substrate chain.
|
||||
pub type Message<Block> = grandpa::Message<<Block as BlockT>::Hash, NumberFor<Block>>;
|
||||
/// A signed message.
|
||||
pub type SignedMessage<Block> = grandpa::SignedMessage<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// A primary propose message for this chain's block type.
|
||||
pub type PrimaryPropose<Block> = grandpa::PrimaryPropose<<Block as BlockT>::Hash, NumberFor<Block>>;
|
||||
/// A prevote message for this chain's block type.
|
||||
pub type Prevote<Block> = grandpa::Prevote<<Block as BlockT>::Hash, NumberFor<Block>>;
|
||||
/// A precommit message for this chain's block type.
|
||||
pub type Precommit<Block> = grandpa::Precommit<<Block as BlockT>::Hash, NumberFor<Block>>;
|
||||
/// A catch up message for this chain's block type.
|
||||
pub type CatchUp<Block> = grandpa::CatchUp<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
/// A commit message for this chain's block type.
|
||||
pub type Commit<Block> = grandpa::Commit<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
/// A compact commit message for this chain's block type.
|
||||
pub type CompactCommit<Block> = grandpa::CompactCommit<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
/// A global communication input stream for commits and catch up messages. Not
|
||||
/// exposed publicly, used internally to simplify types in the communication
|
||||
/// layer.
|
||||
type CommunicationIn<Block> = grandpa::voter::CommunicationIn<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Global communication input stream for commits and catch up messages, with
|
||||
/// the hash type not being derived from the block, useful for forcing the hash
|
||||
/// to some type (e.g. `H256`) when the compiler can't do the inference.
|
||||
type CommunicationInH<Block, H> = grandpa::voter::CommunicationIn<
|
||||
H,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// A global communication sink for commits. Not exposed publicly, used
|
||||
/// internally to simplify types in the communication layer.
|
||||
type CommunicationOut<Block> = grandpa::voter::CommunicationOut<
|
||||
<Block as BlockT>::Hash,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Global communication sink for commits with the hash type not being derived
|
||||
/// from the block, useful for forcing the hash to some type (e.g. `H256`) when
|
||||
/// the compiler can't do the inference.
|
||||
type CommunicationOutH<Block, H> = grandpa::voter::CommunicationOut<
|
||||
H,
|
||||
NumberFor<Block>,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Configuration for the GRANDPA service.
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
/// The expected duration for a message to be gossiped across the network.
|
||||
pub gossip_duration: Duration,
|
||||
/// Justification generation period (in blocks). GRANDPA will try to generate justifications
|
||||
/// at least every justification_period blocks. There are some other events which might cause
|
||||
/// justification generation.
|
||||
pub justification_period: u32,
|
||||
/// Whether the GRANDPA observer protocol is live on the network and thereby
|
||||
/// a full-node not running as a validator is running the GRANDPA observer
|
||||
/// protocol (we will only issue catch-up requests to authorities when the
|
||||
/// observer protocol is enabled).
|
||||
pub observer_enabled: bool,
|
||||
/// Whether the node is running as an authority (i.e. running the full GRANDPA protocol).
|
||||
pub is_authority: bool,
|
||||
/// Some local identifier of the voter.
|
||||
pub name: Option<String>,
|
||||
/// The keystore that manages the keys of this node.
|
||||
pub keystore: Option<keystore::KeyStorePtr>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn name(&self) -> &str {
|
||||
self.name.as_ref().map(|s| s.as_str()).unwrap_or("<unknown>")
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can occur while voting in GRANDPA.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
/// An error within grandpa.
|
||||
Grandpa(GrandpaError),
|
||||
/// A network error.
|
||||
Network(String),
|
||||
/// A blockchain error.
|
||||
Blockchain(String),
|
||||
/// Could not complete a round on disk.
|
||||
Client(ClientError),
|
||||
/// An invariant has been violated (e.g. not finalizing pending change blocks in-order)
|
||||
Safety(String),
|
||||
/// A timer failed to fire.
|
||||
Timer(tokio_timer::Error),
|
||||
}
|
||||
|
||||
impl From<GrandpaError> for Error {
|
||||
fn from(e: GrandpaError) -> Self {
|
||||
Error::Grandpa(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ClientError> for Error {
|
||||
fn from(e: ClientError) -> Self {
|
||||
Error::Client(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// Something which can determine if a block is known.
|
||||
pub(crate) trait BlockStatus<Block: BlockT> {
|
||||
/// Return `Ok(Some(number))` or `Ok(None)` depending on whether the block
|
||||
/// is definitely known and has been imported.
|
||||
/// If an unexpected error occurs, return that.
|
||||
fn block_number(&self, hash: Block::Hash) -> Result<Option<NumberFor<Block>>, Error>;
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockStatus<Block> for Arc<Client<B, E, Block, RA>> where
|
||||
B: Backend<Block, Blake2Hasher>,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
fn block_number(&self, hash: Block::Hash) -> Result<Option<NumberFor<Block>>, Error> {
|
||||
self.block_number_from_id(&BlockId::Hash(hash))
|
||||
.map_err(|e| Error::Blockchain(format!("{:?}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Something that one can ask to do a block sync request.
|
||||
pub(crate) trait BlockSyncRequester<Block: BlockT> {
|
||||
/// Notifies the sync service to try and sync the given block from the given
|
||||
/// peers.
|
||||
///
|
||||
/// If the given vector of peers is empty then the underlying implementation
|
||||
/// should make a best effort to fetch the block from any peers it is
|
||||
/// connected to (NOTE: this assumption will change in the future #3629).
|
||||
fn set_sync_fork_request(&self, peers: Vec<network::PeerId>, hash: Block::Hash, number: NumberFor<Block>);
|
||||
}
|
||||
|
||||
impl<Block, N> BlockSyncRequester<Block> for NetworkBridge<Block, N> where
|
||||
Block: BlockT,
|
||||
N: communication::Network<Block>,
|
||||
{
|
||||
fn set_sync_fork_request(&self, peers: Vec<network::PeerId>, hash: Block::Hash, number: NumberFor<Block>) {
|
||||
NetworkBridge::set_sync_fork_request(self, peers, hash, number)
|
||||
}
|
||||
}
|
||||
|
||||
/// A new authority set along with the canonical block it changed at.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NewAuthoritySet<H, N> {
|
||||
pub(crate) canon_number: N,
|
||||
pub(crate) canon_hash: H,
|
||||
pub(crate) set_id: SetId,
|
||||
pub(crate) authorities: AuthorityList,
|
||||
}
|
||||
|
||||
/// Commands issued to the voter.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum VoterCommand<H, N> {
|
||||
/// Pause the voter for given reason.
|
||||
Pause(String),
|
||||
/// New authorities.
|
||||
ChangeAuthorities(NewAuthoritySet<H, N>)
|
||||
}
|
||||
|
||||
impl<H, N> fmt::Display for VoterCommand<H, N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
VoterCommand::Pause(ref reason) => write!(f, "Pausing voter: {}", reason),
|
||||
VoterCommand::ChangeAuthorities(_) => write!(f, "Changing authorities"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Signals either an early exit of a voter or an error.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum CommandOrError<H, N> {
|
||||
/// An error occurred.
|
||||
Error(Error),
|
||||
/// A command to the voter.
|
||||
VoterCommand(VoterCommand<H, N>),
|
||||
}
|
||||
|
||||
impl<H, N> From<Error> for CommandOrError<H, N> {
|
||||
fn from(e: Error) -> Self {
|
||||
CommandOrError::Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> From<ClientError> for CommandOrError<H, N> {
|
||||
fn from(e: ClientError) -> Self {
|
||||
CommandOrError::Error(Error::Client(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> From<grandpa::Error> for CommandOrError<H, N> {
|
||||
fn from(e: grandpa::Error) -> Self {
|
||||
CommandOrError::Error(Error::from(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> From<VoterCommand<H, N>> for CommandOrError<H, N> {
|
||||
fn from(e: VoterCommand<H, N>) -> Self {
|
||||
CommandOrError::VoterCommand(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: fmt::Debug, N: fmt::Debug> ::std::error::Error for CommandOrError<H, N> { }
|
||||
|
||||
impl<H, N> fmt::Display for CommandOrError<H, N> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
CommandOrError::Error(ref e) => write!(f, "{:?}", e),
|
||||
CommandOrError::VoterCommand(ref cmd) => write!(f, "{}", cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LinkHalf<B, E, Block: BlockT<Hash=H256>, RA, SC> {
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
select_chain: SC,
|
||||
persistent_data: PersistentData<Block>,
|
||||
voter_commands_rx: mpsc::UnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
}
|
||||
|
||||
/// Provider for the Grandpa authority set configured on the genesis block.
|
||||
pub trait GenesisAuthoritySetProvider<Block: BlockT> {
|
||||
/// Get the authority set at the genesis block.
|
||||
fn get(&self) -> Result<AuthorityList, ClientError>;
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> GenesisAuthoritySetProvider<Block> for Client<B, E, Block, RA>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
fn get(&self) -> Result<AuthorityList, ClientError> {
|
||||
// This implementation uses the Grandpa runtime API instead of reading directly from the
|
||||
// `GRANDPA_AUTHORITIES_KEY` as the data may have been migrated since the genesis block of
|
||||
// the chain, whereas the runtime API is backwards compatible.
|
||||
self.executor()
|
||||
.call(
|
||||
&BlockId::Number(Zero::zero()),
|
||||
"GrandpaApi_grandpa_authorities",
|
||||
&[],
|
||||
ExecutionStrategy::NativeElseWasm,
|
||||
None,
|
||||
)
|
||||
.and_then(|call_result| {
|
||||
Decode::decode(&mut &call_result[..])
|
||||
.map_err(|err| ClientError::CallResultDecode(
|
||||
"failed to decode GRANDPA authorities set proof".into(), err
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Make block importer and link half necessary to tie the background voter
|
||||
/// to it.
|
||||
pub fn block_import<B, E, Block: BlockT<Hash=H256>, RA, SC>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
||||
select_chain: SC,
|
||||
) -> Result<(
|
||||
GrandpaBlockImport<B, E, Block, RA, SC>,
|
||||
LinkHalf<B, E, Block, RA, SC>
|
||||
), ClientError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
SC: SelectChain<Block>,
|
||||
{
|
||||
let chain_info = client.info();
|
||||
let genesis_hash = chain_info.chain.genesis_hash;
|
||||
|
||||
let persistent_data = aux_schema::load_persistent(
|
||||
&*client,
|
||||
genesis_hash,
|
||||
<NumberFor<Block>>::zero(),
|
||||
|| {
|
||||
let authorities = genesis_authorities_provider.get()?;
|
||||
telemetry!(CONSENSUS_DEBUG; "afg.loading_authorities";
|
||||
"authorities_len" => ?authorities.len()
|
||||
);
|
||||
Ok(authorities)
|
||||
}
|
||||
)?;
|
||||
|
||||
let (voter_commands_tx, voter_commands_rx) = mpsc::unbounded();
|
||||
|
||||
Ok((
|
||||
GrandpaBlockImport::new(
|
||||
client.clone(),
|
||||
select_chain.clone(),
|
||||
persistent_data.authority_set.clone(),
|
||||
voter_commands_tx,
|
||||
persistent_data.consensus_changes.clone(),
|
||||
),
|
||||
LinkHalf {
|
||||
client,
|
||||
select_chain,
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn global_communication<Block: BlockT<Hash=H256>, B, E, N, RA>(
|
||||
set_id: SetId,
|
||||
voters: &Arc<VoterSet<AuthorityId>>,
|
||||
client: &Arc<Client<B, E, Block, RA>>,
|
||||
network: &NetworkBridge<Block, N>,
|
||||
keystore: &Option<KeyStorePtr>,
|
||||
) -> (
|
||||
impl Stream<
|
||||
Item = CommunicationInH<Block, H256>,
|
||||
Error = CommandOrError<H256, NumberFor<Block>>,
|
||||
>,
|
||||
impl Sink<
|
||||
SinkItem = CommunicationOutH<Block, H256>,
|
||||
SinkError = CommandOrError<H256, NumberFor<Block>>,
|
||||
>,
|
||||
) where
|
||||
B: Backend<Block, Blake2Hasher>,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
|
||||
N: Network<Block>,
|
||||
RA: Send + Sync,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
let is_voter = is_voter(voters, keystore).is_some();
|
||||
|
||||
// verification stream
|
||||
let (global_in, global_out) = network.global_communication(
|
||||
communication::SetId(set_id),
|
||||
voters.clone(),
|
||||
is_voter,
|
||||
);
|
||||
|
||||
// block commit and catch up messages until relevant blocks are imported.
|
||||
let global_in = UntilGlobalMessageBlocksImported::new(
|
||||
client.import_notification_stream(),
|
||||
network.clone(),
|
||||
client.clone(),
|
||||
global_in,
|
||||
"global",
|
||||
);
|
||||
|
||||
let global_in = global_in.map_err(CommandOrError::from);
|
||||
let global_out = global_out.sink_map_err(CommandOrError::from);
|
||||
|
||||
(global_in, global_out)
|
||||
}
|
||||
|
||||
/// Register the finality tracker inherent data provider (which is used by
|
||||
/// GRANDPA), if not registered already.
|
||||
fn register_finality_tracker_inherent_data_provider<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
inherent_data_providers: &InherentDataProviders,
|
||||
) -> Result<(), consensus_common::Error> where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
RA: Send + Sync + 'static,
|
||||
{
|
||||
if !inherent_data_providers.has_provider(&paint_finality_tracker::INHERENT_IDENTIFIER) {
|
||||
inherent_data_providers
|
||||
.register_provider(paint_finality_tracker::InherentDataProvider::new(move || {
|
||||
#[allow(deprecated)]
|
||||
{
|
||||
let info = client.info().chain;
|
||||
telemetry!(CONSENSUS_INFO; "afg.finalized";
|
||||
"finalized_number" => ?info.finalized_number,
|
||||
"finalized_hash" => ?info.finalized_hash,
|
||||
);
|
||||
Ok(info.finalized_number)
|
||||
}
|
||||
}))
|
||||
.map_err(|err| consensus_common::Error::InherentData(err.into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameters used to run Grandpa.
|
||||
pub struct GrandpaParams<B, E, Block: BlockT<Hash=H256>, N, RA, SC, VR, X> {
|
||||
/// Configuration for the GRANDPA service.
|
||||
pub config: Config,
|
||||
/// A link to the block import worker.
|
||||
pub link: LinkHalf<B, E, Block, RA, SC>,
|
||||
/// The Network instance.
|
||||
pub network: N,
|
||||
/// The inherent data providers.
|
||||
pub inherent_data_providers: InherentDataProviders,
|
||||
/// Handle to a future that will resolve on exit.
|
||||
pub on_exit: X,
|
||||
/// If supplied, can be used to hook on telemetry connection established events.
|
||||
pub telemetry_on_connect: Option<mpsc::UnboundedReceiver<()>>,
|
||||
/// A voting rule used to potentially restrict target votes.
|
||||
pub voting_rule: VR,
|
||||
}
|
||||
|
||||
/// Run a GRANDPA voter as a task. Provide configuration and a link to a
|
||||
/// block import worker that has already been instantiated with `block_import`.
|
||||
pub fn run_grandpa_voter<B, E, Block: BlockT<Hash=H256>, N, RA, SC, VR, X>(
|
||||
grandpa_params: GrandpaParams<B, E, Block, N, RA, SC, VR, X>,
|
||||
) -> client_api::error::Result<impl Future<Item=(),Error=()> + Send + 'static> where
|
||||
Block::Hash: Ord,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
N: Network<Block> + Send + Sync + 'static,
|
||||
N::In: Send + 'static,
|
||||
SC: SelectChain<Block> + 'static,
|
||||
VR: VotingRule<Block, Client<B, E, Block, RA>> + Clone + 'static,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync + 'static,
|
||||
X: Future<Item=(),Error=()> + Clone + Send + 'static,
|
||||
{
|
||||
let GrandpaParams {
|
||||
config,
|
||||
link,
|
||||
network,
|
||||
inherent_data_providers,
|
||||
on_exit,
|
||||
telemetry_on_connect,
|
||||
voting_rule,
|
||||
} = grandpa_params;
|
||||
|
||||
let LinkHalf {
|
||||
client,
|
||||
select_chain,
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
} = link;
|
||||
|
||||
let (network, network_startup) = NetworkBridge::new(
|
||||
network,
|
||||
config.clone(),
|
||||
persistent_data.set_state.clone(),
|
||||
on_exit.clone(),
|
||||
);
|
||||
|
||||
register_finality_tracker_inherent_data_provider(client.clone(), &inherent_data_providers)?;
|
||||
|
||||
let conf = config.clone();
|
||||
let telemetry_task = if let Some(telemetry_on_connect) = telemetry_on_connect {
|
||||
let authorities = persistent_data.authority_set.clone();
|
||||
let events = telemetry_on_connect
|
||||
.for_each(move |_| {
|
||||
let curr = authorities.current_authorities();
|
||||
let mut auths = curr.voters().into_iter().map(|(p, _)| p);
|
||||
let maybe_authority_id = authority_id(&mut auths, &conf.keystore)
|
||||
.unwrap_or(Default::default());
|
||||
|
||||
telemetry!(CONSENSUS_INFO; "afg.authority_set";
|
||||
"authority_id" => maybe_authority_id.to_string(),
|
||||
"authority_set_id" => ?authorities.set_id(),
|
||||
"authorities" => {
|
||||
let authorities: Vec<String> = curr.voters()
|
||||
.iter().map(|(id, _)| id.to_string()).collect();
|
||||
serde_json::to_string(&authorities)
|
||||
.expect("authorities is always at least an empty vector; elements are always of type string")
|
||||
}
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
.then(|_| -> Result<(), ()> { Ok(()) });
|
||||
futures::future::Either::A(events)
|
||||
} else {
|
||||
futures::future::Either::B(futures::future::empty())
|
||||
};
|
||||
|
||||
let voter_work = VoterWork::new(
|
||||
client,
|
||||
config,
|
||||
network,
|
||||
select_chain,
|
||||
voting_rule,
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
);
|
||||
|
||||
let voter_work = voter_work
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
error!("GRANDPA Voter failed: {:?}", e);
|
||||
telemetry!(CONSENSUS_WARN; "afg.voter_failed"; "e" => ?e);
|
||||
});
|
||||
|
||||
let voter_work = network_startup.and_then(move |()| voter_work);
|
||||
|
||||
// Make sure that `telemetry_task` doesn't accidentally finish and kill grandpa.
|
||||
let telemetry_task = telemetry_task
|
||||
.then(|_| futures::future::empty::<(), ()>());
|
||||
|
||||
Ok(voter_work.select(on_exit).select2(telemetry_task).then(|_| Ok(())))
|
||||
}
|
||||
|
||||
/// Future that powers the voter.
|
||||
#[must_use]
|
||||
struct VoterWork<B, E, Block: BlockT, N: Network<Block>, RA, SC, VR> {
|
||||
voter: Box<dyn Future<Item = (), Error = CommandOrError<Block::Hash, NumberFor<Block>>> + Send>,
|
||||
env: Arc<Environment<B, E, Block, N, RA, SC, VR>>,
|
||||
voter_commands_rx: mpsc::UnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
}
|
||||
|
||||
impl<B, E, Block, N, RA, SC, VR> VoterWork<B, E, Block, N, RA, SC, VR>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
N: Network<Block> + Sync,
|
||||
N::In: Send + 'static,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
RA: 'static + Send + Sync,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
SC: SelectChain<Block> + 'static,
|
||||
VR: VotingRule<Block, Client<B, E, Block, RA>> + Clone + 'static,
|
||||
{
|
||||
fn new(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
config: Config,
|
||||
network: NetworkBridge<Block, N>,
|
||||
select_chain: SC,
|
||||
voting_rule: VR,
|
||||
persistent_data: PersistentData<Block>,
|
||||
voter_commands_rx: mpsc::UnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
) -> Self {
|
||||
|
||||
let voters = persistent_data.authority_set.current_authorities();
|
||||
let env = Arc::new(Environment {
|
||||
client,
|
||||
select_chain,
|
||||
voting_rule,
|
||||
voters: Arc::new(voters),
|
||||
config,
|
||||
network,
|
||||
set_id: persistent_data.authority_set.set_id(),
|
||||
authority_set: persistent_data.authority_set.clone(),
|
||||
consensus_changes: persistent_data.consensus_changes.clone(),
|
||||
voter_set_state: persistent_data.set_state.clone(),
|
||||
});
|
||||
|
||||
let mut work = VoterWork {
|
||||
// `voter` is set to a temporary value and replaced below when
|
||||
// calling `rebuild_voter`.
|
||||
voter: Box::new(futures::empty()) as Box<_>,
|
||||
env,
|
||||
voter_commands_rx,
|
||||
};
|
||||
work.rebuild_voter();
|
||||
work
|
||||
}
|
||||
|
||||
/// Rebuilds the `self.voter` field using the current authority set
|
||||
/// state. This method should be called when we know that the authority set
|
||||
/// has changed (e.g. as signalled by a voter command).
|
||||
fn rebuild_voter(&mut self) {
|
||||
debug!(target: "afg", "{}: Starting new voter with set ID {}", self.env.config.name(), self.env.set_id);
|
||||
|
||||
let authority_id = is_voter(&self.env.voters, &self.env.config.keystore)
|
||||
.map(|ap| ap.public())
|
||||
.unwrap_or(Default::default());
|
||||
|
||||
telemetry!(CONSENSUS_DEBUG; "afg.starting_new_voter";
|
||||
"name" => ?self.env.config.name(),
|
||||
"set_id" => ?self.env.set_id,
|
||||
"authority_id" => authority_id.to_string(),
|
||||
);
|
||||
|
||||
let chain_info = self.env.client.info();
|
||||
telemetry!(CONSENSUS_INFO; "afg.authority_set";
|
||||
"number" => ?chain_info.chain.finalized_number,
|
||||
"hash" => ?chain_info.chain.finalized_hash,
|
||||
"authority_id" => authority_id.to_string(),
|
||||
"authority_set_id" => ?self.env.set_id,
|
||||
"authorities" => {
|
||||
let authorities: Vec<String> = self.env.voters.voters()
|
||||
.iter().map(|(id, _)| id.to_string()).collect();
|
||||
serde_json::to_string(&authorities)
|
||||
.expect("authorities is always at least an empty vector; elements are always of type string")
|
||||
},
|
||||
);
|
||||
|
||||
match &*self.env.voter_set_state.read() {
|
||||
VoterSetState::Live { completed_rounds, .. } => {
|
||||
let last_finalized = (
|
||||
chain_info.chain.finalized_hash,
|
||||
chain_info.chain.finalized_number,
|
||||
);
|
||||
|
||||
let global_comms = global_communication(
|
||||
self.env.set_id,
|
||||
&self.env.voters,
|
||||
&self.env.client,
|
||||
&self.env.network,
|
||||
&self.env.config.keystore,
|
||||
);
|
||||
|
||||
let last_completed_round = completed_rounds.last();
|
||||
|
||||
let voter = voter::Voter::new(
|
||||
self.env.clone(),
|
||||
(*self.env.voters).clone(),
|
||||
global_comms,
|
||||
last_completed_round.number,
|
||||
last_completed_round.state.clone(),
|
||||
last_finalized,
|
||||
);
|
||||
|
||||
self.voter = Box::new(voter);
|
||||
},
|
||||
VoterSetState::Paused { .. } =>
|
||||
self.voter = Box::new(futures::empty()),
|
||||
};
|
||||
}
|
||||
|
||||
fn handle_voter_command(
|
||||
&mut self,
|
||||
command: VoterCommand<Block::Hash, NumberFor<Block>>
|
||||
) -> Result<(), Error> {
|
||||
match command {
|
||||
VoterCommand::ChangeAuthorities(new) => {
|
||||
let voters: Vec<String> = new.authorities.iter().map(move |(a, _)| {
|
||||
format!("{}", a)
|
||||
}).collect();
|
||||
telemetry!(CONSENSUS_INFO; "afg.voter_command_change_authorities";
|
||||
"number" => ?new.canon_number,
|
||||
"hash" => ?new.canon_hash,
|
||||
"voters" => ?voters,
|
||||
"set_id" => ?new.set_id,
|
||||
);
|
||||
|
||||
self.env.update_voter_set_state(|_| {
|
||||
// start the new authority set using the block where the
|
||||
// set changed (not where the signal happened!) as the base.
|
||||
let set_state = VoterSetState::live(
|
||||
new.set_id,
|
||||
&*self.env.authority_set.inner().read(),
|
||||
(new.canon_hash, new.canon_number),
|
||||
);
|
||||
|
||||
aux_schema::write_voter_set_state(&*self.env.client, &set_state)?;
|
||||
Ok(Some(set_state))
|
||||
})?;
|
||||
|
||||
self.env = Arc::new(Environment {
|
||||
voters: Arc::new(new.authorities.into_iter().collect()),
|
||||
set_id: new.set_id,
|
||||
voter_set_state: self.env.voter_set_state.clone(),
|
||||
// Fields below are simply transferred and not updated.
|
||||
client: self.env.client.clone(),
|
||||
select_chain: self.env.select_chain.clone(),
|
||||
config: self.env.config.clone(),
|
||||
authority_set: self.env.authority_set.clone(),
|
||||
consensus_changes: self.env.consensus_changes.clone(),
|
||||
network: self.env.network.clone(),
|
||||
voting_rule: self.env.voting_rule.clone(),
|
||||
});
|
||||
|
||||
self.rebuild_voter();
|
||||
Ok(())
|
||||
}
|
||||
VoterCommand::Pause(reason) => {
|
||||
info!(target: "afg", "Pausing old validator set: {}", reason);
|
||||
|
||||
// not racing because old voter is shut down.
|
||||
self.env.update_voter_set_state(|voter_set_state| {
|
||||
let completed_rounds = voter_set_state.completed_rounds();
|
||||
let set_state = VoterSetState::Paused { completed_rounds };
|
||||
|
||||
aux_schema::write_voter_set_state(&*self.env.client, &set_state)?;
|
||||
Ok(Some(set_state))
|
||||
})?;
|
||||
|
||||
self.rebuild_voter();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block, N, RA, SC, VR> Future for VoterWork<B, E, Block, N, RA, SC, VR>
|
||||
where
|
||||
Block: BlockT<Hash=H256>,
|
||||
N: Network<Block> + Sync,
|
||||
N::In: Send + 'static,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
RA: 'static + Send + Sync,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
SC: SelectChain<Block> + 'static,
|
||||
VR: VotingRule<Block, Client<B, E, Block, RA>> + Clone + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.voter.poll() {
|
||||
Ok(Async::NotReady) => {}
|
||||
Ok(Async::Ready(())) => {
|
||||
// voters don't conclude naturally
|
||||
return Err(Error::Safety("GRANDPA voter has concluded.".into()))
|
||||
}
|
||||
Err(CommandOrError::Error(e)) => {
|
||||
// return inner observer error
|
||||
return Err(e)
|
||||
}
|
||||
Err(CommandOrError::VoterCommand(command)) => {
|
||||
// some command issued internally
|
||||
self.handle_voter_command(command)?;
|
||||
futures::task::current().notify();
|
||||
}
|
||||
}
|
||||
|
||||
match self.voter_commands_rx.poll() {
|
||||
Ok(Async::NotReady) => {}
|
||||
Err(_) => {
|
||||
// the `voter_commands_rx` stream should not fail.
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
// the `voter_commands_rx` stream should never conclude since it's never closed.
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
Ok(Async::Ready(Some(command))) => {
|
||||
// some command issued externally
|
||||
self.handle_voter_command(command)?;
|
||||
futures::task::current().notify();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated(since = "1.1.0", note = "Please switch to run_grandpa_voter.")]
|
||||
pub fn run_grandpa<B, E, Block: BlockT<Hash=H256>, N, RA, SC, VR, X>(
|
||||
grandpa_params: GrandpaParams<B, E, Block, N, RA, SC, VR, X>,
|
||||
) -> ::client_api::error::Result<impl Future<Item=(),Error=()> + Send + 'static> where
|
||||
Block::Hash: Ord,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
N: Network<Block> + Send + Sync + 'static,
|
||||
N::In: Send + 'static,
|
||||
SC: SelectChain<Block> + 'static,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync + 'static,
|
||||
VR: VotingRule<Block, Client<B, E, Block, RA>> + Clone + 'static,
|
||||
X: Future<Item=(),Error=()> + Clone + Send + 'static,
|
||||
{
|
||||
run_grandpa_voter(grandpa_params)
|
||||
}
|
||||
|
||||
/// When GRANDPA is not initialized we still need to register the finality
|
||||
/// tracker inherent provider which might be expected by the runtime for block
|
||||
/// authoring. Additionally, we register a gossip message validator that
|
||||
/// discards all GRANDPA messages (otherwise, we end up banning nodes that send
|
||||
/// us a `Neighbor` message, since there is no registered gossip validator for
|
||||
/// the engine id defined in the message.)
|
||||
pub fn setup_disabled_grandpa<B, E, Block: BlockT<Hash=H256>, RA, N>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
inherent_data_providers: &InherentDataProviders,
|
||||
network: N,
|
||||
) -> Result<(), consensus_common::Error> where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
RA: Send + Sync + 'static,
|
||||
N: Network<Block> + Send + Sync + 'static,
|
||||
N::In: Send + 'static,
|
||||
{
|
||||
register_finality_tracker_inherent_data_provider(
|
||||
client,
|
||||
inherent_data_providers,
|
||||
)?;
|
||||
|
||||
network.register_validator(Arc::new(network::consensus_gossip::DiscardAll));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks if this node is a voter in the given voter set.
|
||||
///
|
||||
/// Returns the key pair of the node that is being used in the current voter set or `None`.
|
||||
fn is_voter(
|
||||
voters: &Arc<VoterSet<AuthorityId>>,
|
||||
keystore: &Option<KeyStorePtr>,
|
||||
) -> Option<AuthorityPair> {
|
||||
match keystore {
|
||||
Some(keystore) => voters.voters().iter()
|
||||
.find_map(|(p, _)| keystore.read().key_pair::<AuthorityPair>(&p).ok()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the authority id of this node, if available.
|
||||
fn authority_id<'a, I>(
|
||||
authorities: &mut I,
|
||||
keystore: &Option<KeyStorePtr>,
|
||||
) -> Option<AuthorityId> where
|
||||
I: Iterator<Item = &'a AuthorityId>,
|
||||
{
|
||||
match keystore {
|
||||
Some(keystore) => {
|
||||
authorities
|
||||
.find_map(|p| {
|
||||
keystore.read().key_pair::<AuthorityPair>(&p)
|
||||
.ok()
|
||||
.map(|ap| ap.public())
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,778 @@
|
||||
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use log::{info, trace, warn};
|
||||
use parking_lot::RwLock;
|
||||
use client::Client;
|
||||
use client_api::{
|
||||
CallExecutor,
|
||||
backend::{AuxStore, Backend, Finalizer},
|
||||
blockchain::HeaderBackend,
|
||||
error::Error as ClientError,
|
||||
well_known_cache_keys,
|
||||
};
|
||||
use codec::{Encode, Decode};
|
||||
use consensus_common::{
|
||||
import_queue::Verifier,
|
||||
BlockOrigin, BlockImport, FinalityProofImport, BlockImportParams, ImportResult, ImportedAux,
|
||||
BlockCheckParams, Error as ConsensusError,
|
||||
};
|
||||
use network::config::{BoxFinalityProofRequestBuilder, FinalityProofRequestBuilder};
|
||||
use sr_primitives::Justification;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT, Header as HeaderT, DigestFor};
|
||||
use fg_primitives::{self, AuthorityList};
|
||||
use sr_primitives::generic::BlockId;
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
|
||||
use crate::GenesisAuthoritySetProvider;
|
||||
use crate::aux_schema::load_decode;
|
||||
use crate::consensus_changes::ConsensusChanges;
|
||||
use crate::environment::canonical_at_height;
|
||||
use crate::finality_proof::{
|
||||
AuthoritySetForFinalityChecker, ProvableJustification, make_finality_proof_request,
|
||||
};
|
||||
use crate::justification::GrandpaJustification;
|
||||
|
||||
/// LightAuthoritySet is saved under this key in aux storage.
|
||||
const LIGHT_AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
|
||||
/// ConsensusChanges is saver under this key in aux storage.
|
||||
const LIGHT_CONSENSUS_CHANGES_KEY: &[u8] = b"grandpa_consensus_changes";
|
||||
|
||||
/// Create light block importer.
|
||||
pub fn light_block_import<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
backend: Arc<B>,
|
||||
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
||||
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
||||
) -> Result<GrandpaLightBlockImport<B, E, Block, RA>, ClientError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
let info = client.info();
|
||||
let import_data = load_aux_import_data(
|
||||
info.chain.finalized_hash,
|
||||
&*client,
|
||||
genesis_authorities_provider,
|
||||
)?;
|
||||
Ok(GrandpaLightBlockImport {
|
||||
client,
|
||||
backend,
|
||||
authority_set_provider,
|
||||
data: Arc::new(RwLock::new(import_data)),
|
||||
})
|
||||
}
|
||||
|
||||
/// A light block-import handler for GRANDPA.
|
||||
///
|
||||
/// It is responsible for:
|
||||
/// - checking GRANDPA justifications;
|
||||
/// - fetching finality proofs for blocks that are enacting consensus changes.
|
||||
pub struct GrandpaLightBlockImport<B, E, Block: BlockT<Hash=H256>, RA> {
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
backend: Arc<B>,
|
||||
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
||||
data: Arc<RwLock<LightImportData<Block>>>,
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> Clone for GrandpaLightBlockImport<B, E, Block, RA> {
|
||||
fn clone(&self) -> Self {
|
||||
GrandpaLightBlockImport {
|
||||
client: self.client.clone(),
|
||||
backend: self.backend.clone(),
|
||||
authority_set_provider: self.authority_set_provider.clone(),
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable data of light block importer.
|
||||
struct LightImportData<Block: BlockT<Hash=H256>> {
|
||||
last_finalized: Block::Hash,
|
||||
authority_set: LightAuthoritySet,
|
||||
consensus_changes: ConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
}
|
||||
|
||||
/// Latest authority set tracker.
|
||||
#[derive(Debug, Encode, Decode)]
|
||||
struct LightAuthoritySet {
|
||||
set_id: u64,
|
||||
authorities: AuthorityList,
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> GrandpaLightBlockImport<B, E, Block, RA> {
|
||||
/// Create finality proof request builder.
|
||||
pub fn create_finality_proof_request_builder(&self) -> BoxFinalityProofRequestBuilder<Block> {
|
||||
Box::new(GrandpaFinalityProofRequestBuilder(self.data.clone())) as _
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockImport<Block>
|
||||
for GrandpaLightBlockImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn import_block(
|
||||
&mut self,
|
||||
block: BlockImportParams<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
do_import_block::<_, _, _, GrandpaJustification<Block>>(
|
||||
&*self.client, &mut *self.data.write(), block, new_cache
|
||||
)
|
||||
}
|
||||
|
||||
fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<Block>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
self.client.check_block(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
||||
for GrandpaLightBlockImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn on_start(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
|
||||
let mut out = Vec::new();
|
||||
let chain_info = self.client.info().chain;
|
||||
|
||||
let data = self.data.read();
|
||||
for (pending_number, pending_hash) in data.consensus_changes.pending_changes() {
|
||||
if *pending_number > chain_info.finalized_number && *pending_number <= chain_info.best_number {
|
||||
out.push((pending_hash.clone(), *pending_number));
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn import_finality_proof(
|
||||
&mut self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &mut dyn Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), Self::Error> {
|
||||
do_import_finality_proof::<_, _, _, GrandpaJustification<Block>>(
|
||||
&*self.client,
|
||||
self.backend.clone(),
|
||||
&*self.authority_set_provider,
|
||||
&mut *self.data.write(),
|
||||
hash,
|
||||
number,
|
||||
finality_proof,
|
||||
verifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl LightAuthoritySet {
|
||||
/// Get a genesis set with given authorities.
|
||||
pub fn genesis(initial: AuthorityList) -> Self {
|
||||
LightAuthoritySet {
|
||||
set_id: fg_primitives::SetId::default(),
|
||||
authorities: initial,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get latest set id.
|
||||
pub fn set_id(&self) -> u64 {
|
||||
self.set_id
|
||||
}
|
||||
|
||||
/// Get latest authorities set.
|
||||
pub fn authorities(&self) -> AuthorityList {
|
||||
self.authorities.clone()
|
||||
}
|
||||
|
||||
/// Set new authorities set.
|
||||
pub fn update(&mut self, set_id: u64, authorities: AuthorityList) {
|
||||
self.set_id = set_id;
|
||||
std::mem::replace(&mut self.authorities, authorities);
|
||||
}
|
||||
}
|
||||
|
||||
struct GrandpaFinalityProofRequestBuilder<B: BlockT<Hash=H256>>(Arc<RwLock<LightImportData<B>>>);
|
||||
|
||||
impl<B: BlockT<Hash=H256>> FinalityProofRequestBuilder<B> for GrandpaFinalityProofRequestBuilder<B> {
|
||||
fn build_request_data(&mut self, _hash: &B::Hash) -> Vec<u8> {
|
||||
let data = self.0.read();
|
||||
make_finality_proof_request(
|
||||
data.last_finalized,
|
||||
data.authority_set.set_id(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to import new block.
|
||||
fn do_import_block<B, C, Block: BlockT<Hash=H256>, J>(
|
||||
mut client: C,
|
||||
data: &mut LightImportData<Block>,
|
||||
mut block: BlockImportParams<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
C: HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ Finalizer<Block, Blake2Hasher, B>
|
||||
+ BlockImport<Block>
|
||||
+ Clone,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
DigestFor<Block>: Encode,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
let hash = block.post_header().hash();
|
||||
let number = block.header.number().clone();
|
||||
|
||||
// we don't want to finalize on `inner.import_block`
|
||||
let justification = block.justification.take();
|
||||
let enacts_consensus_change = !new_cache.is_empty();
|
||||
let import_result = client.import_block(block, new_cache);
|
||||
|
||||
let mut imported_aux = match import_result {
|
||||
Ok(ImportResult::Imported(aux)) => aux,
|
||||
Ok(r) => return Ok(r),
|
||||
Err(e) => return Err(ConsensusError::ClientImport(e.to_string()).into()),
|
||||
};
|
||||
|
||||
match justification {
|
||||
Some(justification) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Imported block {}{}. Importing justification.",
|
||||
if enacts_consensus_change { " which enacts consensus changes" } else { "" },
|
||||
hash,
|
||||
);
|
||||
|
||||
do_import_justification::<_, _, _, J>(client, data, hash, number, justification)
|
||||
},
|
||||
None if enacts_consensus_change => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Imported block {} which enacts consensus changes. Requesting finality proof.",
|
||||
hash,
|
||||
);
|
||||
|
||||
// remember that we need finality proof for this block
|
||||
imported_aux.needs_finality_proof = true;
|
||||
data.consensus_changes.note_change((number, hash));
|
||||
Ok(ImportResult::Imported(imported_aux))
|
||||
},
|
||||
None => Ok(ImportResult::Imported(imported_aux)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to import finality proof.
|
||||
fn do_import_finality_proof<B, C, Block: BlockT<Hash=H256>, J>(
|
||||
client: C,
|
||||
backend: Arc<B>,
|
||||
authority_set_provider: &dyn AuthoritySetForFinalityChecker<Block>,
|
||||
data: &mut LightImportData<Block>,
|
||||
_hash: Block::Hash,
|
||||
_number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &mut dyn Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), ConsensusError>
|
||||
where
|
||||
C: HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ Finalizer<Block, Blake2Hasher, B>
|
||||
+ BlockImport<Block>
|
||||
+ Clone,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
DigestFor<Block>: Encode,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
let authority_set_id = data.authority_set.set_id();
|
||||
let authorities = data.authority_set.authorities();
|
||||
let finality_effects = crate::finality_proof::check_finality_proof(
|
||||
backend.blockchain(),
|
||||
authority_set_id,
|
||||
authorities,
|
||||
authority_set_provider,
|
||||
finality_proof,
|
||||
).map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
|
||||
|
||||
// try to import all new headers
|
||||
let block_origin = BlockOrigin::NetworkBroadcast;
|
||||
for header_to_import in finality_effects.headers_to_import {
|
||||
let (block_to_import, new_authorities) = verifier.verify(block_origin, header_to_import, None, None)
|
||||
.map_err(|e| ConsensusError::ClientImport(e))?;
|
||||
assert!(block_to_import.justification.is_none(), "We have passed None as justification to verifier.verify");
|
||||
|
||||
let mut cache = HashMap::new();
|
||||
if let Some(authorities) = new_authorities {
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, authorities.encode());
|
||||
}
|
||||
do_import_block::<_, _, _, J>(client.clone(), data, block_to_import, cache)?;
|
||||
}
|
||||
|
||||
// try to import latest justification
|
||||
let finalized_block_hash = finality_effects.block;
|
||||
let finalized_block_number = backend.blockchain()
|
||||
.expect_block_number_from_id(&BlockId::Hash(finality_effects.block))
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
|
||||
do_finalize_block(
|
||||
client,
|
||||
data,
|
||||
finalized_block_hash,
|
||||
finalized_block_number,
|
||||
finality_effects.justification.encode(),
|
||||
)?;
|
||||
|
||||
// apply new authorities set
|
||||
data.authority_set.update(
|
||||
finality_effects.new_set_id,
|
||||
finality_effects.new_authorities,
|
||||
);
|
||||
|
||||
Ok((finalized_block_hash, finalized_block_number))
|
||||
}
|
||||
|
||||
/// Try to import justification.
|
||||
fn do_import_justification<B, C, Block: BlockT<Hash=H256>, J>(
|
||||
client: C,
|
||||
data: &mut LightImportData<Block>,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
C: HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ Finalizer<Block, Blake2Hasher, B>
|
||||
+ Clone,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
J: ProvableJustification<Block::Header>,
|
||||
{
|
||||
// with justification, we have two cases
|
||||
//
|
||||
// optimistic: the same GRANDPA authorities set has generated intermediate justification
|
||||
// => justification is verified using current authorities set + we could proceed further
|
||||
//
|
||||
// pessimistic scenario: the GRANDPA authorities set has changed
|
||||
// => we need to fetch new authorities set (i.e. finality proof) from remote node
|
||||
|
||||
// first, try to behave optimistically
|
||||
let authority_set_id = data.authority_set.set_id();
|
||||
let justification = J::decode_and_verify(
|
||||
&justification,
|
||||
authority_set_id,
|
||||
&data.authority_set.authorities(),
|
||||
);
|
||||
|
||||
// BadJustification error means that justification has been successfully decoded, but
|
||||
// it isn't valid within current authority set
|
||||
let justification = match justification {
|
||||
Err(ClientError::BadJustification(_)) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is not valid within current authorities set. Requesting finality proof.",
|
||||
hash,
|
||||
);
|
||||
|
||||
let mut imported_aux = ImportedAux::default();
|
||||
imported_aux.needs_finality_proof = true;
|
||||
return Ok(ImportResult::Imported(imported_aux));
|
||||
},
|
||||
Err(e) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is not valid. Bailing.",
|
||||
hash,
|
||||
);
|
||||
|
||||
return Err(ConsensusError::ClientImport(e.to_string()).into());
|
||||
},
|
||||
Ok(justification) => {
|
||||
trace!(
|
||||
target: "finality",
|
||||
"Justification for {} is valid. Finalizing the block.",
|
||||
hash,
|
||||
);
|
||||
|
||||
justification
|
||||
},
|
||||
};
|
||||
|
||||
// finalize the block
|
||||
do_finalize_block(client, data, hash, number, justification.encode())
|
||||
}
|
||||
|
||||
/// Finalize the block.
|
||||
fn do_finalize_block<B, C, Block: BlockT<Hash=H256>>(
|
||||
client: C,
|
||||
data: &mut LightImportData<Block>,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
justification: Justification,
|
||||
) -> Result<ImportResult, ConsensusError>
|
||||
where
|
||||
C: HeaderBackend<Block>
|
||||
+ AuxStore
|
||||
+ Finalizer<Block, Blake2Hasher, B>
|
||||
+ Clone,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
{
|
||||
// finalize the block
|
||||
client.finalize_block(BlockId::Hash(hash), Some(justification), true).map_err(|e| {
|
||||
warn!(target: "finality", "Error applying finality to block {:?}: {:?}", (hash, number), e);
|
||||
ConsensusError::ClientImport(e.to_string())
|
||||
})?;
|
||||
|
||||
// forget obsoleted consensus changes
|
||||
let consensus_finalization_res = data.consensus_changes
|
||||
.finalize((number, hash), |at_height| canonical_at_height(client.clone(), (hash, number), true, at_height));
|
||||
match consensus_finalization_res {
|
||||
Ok((true, _)) => require_insert_aux(
|
||||
&client,
|
||||
LIGHT_CONSENSUS_CHANGES_KEY,
|
||||
&data.consensus_changes,
|
||||
"consensus changes",
|
||||
)?,
|
||||
Ok(_) => (),
|
||||
Err(error) => return Err(on_post_finalization_error(error, "consensus changes")),
|
||||
}
|
||||
|
||||
// update last finalized block reference
|
||||
data.last_finalized = hash;
|
||||
|
||||
// we just finalized this block, so if we were importing it, it is now the new best
|
||||
Ok(ImportResult::imported(true))
|
||||
}
|
||||
|
||||
/// Load light import aux data from the store.
|
||||
fn load_aux_import_data<B, Block: BlockT<Hash=H256>>(
|
||||
last_finalized: Block::Hash,
|
||||
aux_store: &B,
|
||||
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
||||
) -> Result<LightImportData<Block>, ClientError>
|
||||
where
|
||||
B: AuxStore,
|
||||
{
|
||||
let authority_set = match load_decode(aux_store, LIGHT_AUTHORITY_SET_KEY)? {
|
||||
Some(authority_set) => authority_set,
|
||||
None => {
|
||||
info!(target: "afg", "Loading GRANDPA authorities \
|
||||
from genesis on what appears to be first startup.");
|
||||
|
||||
// no authority set on disk: fetch authorities from genesis state
|
||||
let genesis_authorities = genesis_authorities_provider.get()?;
|
||||
|
||||
let authority_set = LightAuthoritySet::genesis(genesis_authorities);
|
||||
let encoded = authority_set.encode();
|
||||
aux_store.insert_aux(&[(LIGHT_AUTHORITY_SET_KEY, &encoded[..])], &[])?;
|
||||
|
||||
authority_set
|
||||
},
|
||||
};
|
||||
|
||||
let consensus_changes = match load_decode(aux_store, LIGHT_CONSENSUS_CHANGES_KEY)? {
|
||||
Some(consensus_changes) => consensus_changes,
|
||||
None => {
|
||||
let consensus_changes = ConsensusChanges::<Block::Hash, NumberFor<Block>>::empty();
|
||||
|
||||
let encoded = authority_set.encode();
|
||||
aux_store.insert_aux(&[(LIGHT_CONSENSUS_CHANGES_KEY, &encoded[..])], &[])?;
|
||||
|
||||
consensus_changes
|
||||
},
|
||||
};
|
||||
|
||||
Ok(LightImportData {
|
||||
last_finalized,
|
||||
authority_set,
|
||||
consensus_changes,
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert into aux store. If failed, return error && show inconsistency warning.
|
||||
fn require_insert_aux<T: Encode, A: AuxStore>(
|
||||
store: &A,
|
||||
key: &[u8],
|
||||
value: &T,
|
||||
value_type: &str,
|
||||
) -> Result<(), ConsensusError> {
|
||||
let encoded = value.encode();
|
||||
let update_res = store.insert_aux(&[(key, &encoded[..])], &[]);
|
||||
if let Err(error) = update_res {
|
||||
return Err(on_post_finalization_error(error, value_type));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Display inconsistency warning.
|
||||
fn on_post_finalization_error(error: ClientError, value_type: &str) -> ConsensusError {
|
||||
warn!(target: "finality", "Failed to write updated {} to disk. Bailing.", value_type);
|
||||
warn!(target: "finality", "Node is in a potentially inconsistent state.");
|
||||
ConsensusError::ClientImport(error.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use consensus_common::ForkChoiceStrategy;
|
||||
use fg_primitives::AuthorityId;
|
||||
use primitives::{H256, crypto::Public};
|
||||
use test_client::client::in_mem::Blockchain as InMemoryAuxStore;
|
||||
use test_client::runtime::{Block, Header};
|
||||
use crate::tests::TestApi;
|
||||
use crate::finality_proof::tests::TestJustification;
|
||||
|
||||
pub struct NoJustificationsImport<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
pub GrandpaLightBlockImport<B, E, Block, RA>
|
||||
);
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> Clone
|
||||
for NoJustificationsImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
NoJustificationsImport(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> BlockImport<Block>
|
||||
for NoJustificationsImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn import_block(
|
||||
&mut self,
|
||||
mut block: BlockImportParams<Block>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
block.justification.take();
|
||||
self.0.import_block(block, new_cache)
|
||||
}
|
||||
|
||||
fn check_block(
|
||||
&mut self,
|
||||
block: BlockCheckParams<Block>,
|
||||
) -> Result<ImportResult, Self::Error> {
|
||||
self.0.check_block(block)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, E, Block: BlockT<Hash=H256>, RA> FinalityProofImport<Block>
|
||||
for NoJustificationsImport<B, E, Block, RA> where
|
||||
NumberFor<Block>: grandpa::BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
DigestFor<Block>: Encode,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
type Error = ConsensusError;
|
||||
|
||||
fn on_start(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
|
||||
self.0.on_start()
|
||||
}
|
||||
|
||||
fn import_finality_proof(
|
||||
&mut self,
|
||||
hash: Block::Hash,
|
||||
number: NumberFor<Block>,
|
||||
finality_proof: Vec<u8>,
|
||||
verifier: &mut dyn Verifier<Block>,
|
||||
) -> Result<(Block::Hash, NumberFor<Block>), Self::Error> {
|
||||
self.0.import_finality_proof(hash, number, finality_proof, verifier)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates light block import that ignores justifications that came outside of finality proofs.
|
||||
pub fn light_block_import_without_justifications<B, E, Block: BlockT<Hash=H256>, RA>(
|
||||
client: Arc<Client<B, E, Block, RA>>,
|
||||
backend: Arc<B>,
|
||||
genesis_authorities_provider: &dyn GenesisAuthoritySetProvider<Block>,
|
||||
authority_set_provider: Arc<dyn AuthoritySetForFinalityChecker<Block>>,
|
||||
) -> Result<NoJustificationsImport<B, E, Block, RA>, ClientError>
|
||||
where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + 'static + Clone + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
{
|
||||
light_block_import(client, backend, genesis_authorities_provider, authority_set_provider)
|
||||
.map(NoJustificationsImport)
|
||||
}
|
||||
|
||||
fn import_block(
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
justification: Option<Justification>,
|
||||
) -> ImportResult {
|
||||
let (client, _backend) = test_client::new_light();
|
||||
let mut import_data = LightImportData {
|
||||
last_finalized: Default::default(),
|
||||
authority_set: LightAuthoritySet::genesis(vec![(AuthorityId::from_slice(&[1; 32]), 1)]),
|
||||
consensus_changes: ConsensusChanges::empty(),
|
||||
};
|
||||
let block = BlockImportParams {
|
||||
origin: BlockOrigin::Own,
|
||||
header: Header {
|
||||
number: 1,
|
||||
parent_hash: client.info().chain.best_hash,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root: Default::default(),
|
||||
},
|
||||
justification,
|
||||
post_digests: Vec::new(),
|
||||
body: None,
|
||||
finalized: false,
|
||||
auxiliary: Vec::new(),
|
||||
fork_choice: ForkChoiceStrategy::LongestChain,
|
||||
allow_missing_state: true,
|
||||
};
|
||||
do_import_block::<_, _, _, TestJustification>(
|
||||
&client,
|
||||
&mut import_data,
|
||||
block,
|
||||
new_cache,
|
||||
).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_no_justification_provided() {
|
||||
assert_eq!(import_block(HashMap::new(), None), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: false,
|
||||
is_new_best: true,
|
||||
header_only: false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_not_required_when_consensus_data_does_not_changes_and_correct_justification_provided() {
|
||||
let justification = TestJustification(true, Vec::new()).encode();
|
||||
assert_eq!(import_block(HashMap::new(), Some(justification)), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: false,
|
||||
is_new_best: true,
|
||||
header_only: false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_required_when_consensus_data_changes_and_no_justification_provided() {
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, vec![AuthorityId::from_slice(&[2; 32])].encode());
|
||||
assert_eq!(import_block(cache, None), ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: true,
|
||||
is_new_best: true,
|
||||
header_only: false,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finality_proof_required_when_consensus_data_changes_and_incorrect_justification_provided() {
|
||||
let justification = TestJustification(false, Vec::new()).encode();
|
||||
let mut cache = HashMap::new();
|
||||
cache.insert(well_known_cache_keys::AUTHORITIES, vec![AuthorityId::from_slice(&[2; 32])].encode());
|
||||
assert_eq!(
|
||||
import_block(cache, Some(justification)),
|
||||
ImportResult::Imported(ImportedAux {
|
||||
clear_justification_requests: false,
|
||||
needs_justification: false,
|
||||
bad_justification: false,
|
||||
needs_finality_proof: true,
|
||||
is_new_best: false,
|
||||
header_only: false,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn aux_data_updated_on_start() {
|
||||
let aux_store = InMemoryAuxStore::<Block>::new();
|
||||
let api = TestApi::new(vec![(AuthorityId::from_slice(&[1; 32]), 1)]);
|
||||
|
||||
// when aux store is empty initially
|
||||
assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_none());
|
||||
assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_none());
|
||||
|
||||
// it is updated on importer start
|
||||
load_aux_import_data(Default::default(), &aux_store, &api).unwrap();
|
||||
assert!(aux_store.get_aux(LIGHT_AUTHORITY_SET_KEY).unwrap().is_some());
|
||||
assert!(aux_store.get_aux(LIGHT_CONSENSUS_CHANGES_KEY).unwrap().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aux_data_loaded_on_restart() {
|
||||
let aux_store = InMemoryAuxStore::<Block>::new();
|
||||
let api = TestApi::new(vec![(AuthorityId::from_slice(&[1; 32]), 1)]);
|
||||
|
||||
// when aux store is non-empty initially
|
||||
let mut consensus_changes = ConsensusChanges::<H256, u64>::empty();
|
||||
consensus_changes.note_change((42, Default::default()));
|
||||
aux_store.insert_aux(
|
||||
&[
|
||||
(
|
||||
LIGHT_AUTHORITY_SET_KEY,
|
||||
LightAuthoritySet::genesis(
|
||||
vec![(AuthorityId::from_slice(&[42; 32]), 2)]
|
||||
).encode().as_slice(),
|
||||
),
|
||||
(
|
||||
LIGHT_CONSENSUS_CHANGES_KEY,
|
||||
consensus_changes.encode().as_slice(),
|
||||
),
|
||||
],
|
||||
&[],
|
||||
).unwrap();
|
||||
|
||||
// importer uses it on start
|
||||
let data = load_aux_import_data(Default::default(), &aux_store, &api).unwrap();
|
||||
assert_eq!(data.authority_set.authorities(), vec![(AuthorityId::from_slice(&[42; 32]), 2)]);
|
||||
assert_eq!(data.consensus_changes.pending_changes(), &[(42, Default::default())]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,378 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::{future, sync::mpsc};
|
||||
|
||||
use grandpa::{
|
||||
BlockNumberOps, Error as GrandpaError, voter, voter_set::VoterSet
|
||||
};
|
||||
use log::{debug, info, warn};
|
||||
|
||||
use consensus_common::SelectChain;
|
||||
use client_api::{CallExecutor, backend::Backend};
|
||||
use client::Client;
|
||||
use sr_primitives::traits::{NumberFor, Block as BlockT};
|
||||
use primitives::{H256, Blake2Hasher};
|
||||
|
||||
use crate::{
|
||||
global_communication, CommandOrError, CommunicationIn, Config, environment,
|
||||
LinkHalf, Network, Error, aux_schema::PersistentData, VoterCommand, VoterSetState,
|
||||
};
|
||||
use crate::authorities::SharedAuthoritySet;
|
||||
use crate::communication::NetworkBridge;
|
||||
use crate::consensus_changes::SharedConsensusChanges;
|
||||
use fg_primitives::AuthorityId;
|
||||
|
||||
struct ObserverChain<'a, Block: BlockT, B, E, RA>(&'a Client<B, E, Block, RA>);
|
||||
|
||||
impl<'a, Block: BlockT<Hash=H256>, B, E, RA> grandpa::Chain<Block::Hash, NumberFor<Block>>
|
||||
for ObserverChain<'a, Block, B, E, RA> where
|
||||
B: Backend<Block, Blake2Hasher>,
|
||||
E: CallExecutor<Block, Blake2Hasher>,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
fn ancestry(&self, base: Block::Hash, block: Block::Hash) -> Result<Vec<Block::Hash>, GrandpaError> {
|
||||
environment::ancestry(&self.0, base, block)
|
||||
}
|
||||
|
||||
fn best_chain_containing(&self, _block: Block::Hash) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
// only used by voter
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn grandpa_observer<B, E, Block: BlockT<Hash=H256>, RA, S, F>(
|
||||
client: &Arc<Client<B, E, Block, RA>>,
|
||||
authority_set: &SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
consensus_changes: &SharedConsensusChanges<Block::Hash, NumberFor<Block>>,
|
||||
voters: &Arc<VoterSet<AuthorityId>>,
|
||||
last_finalized_number: NumberFor<Block>,
|
||||
commits: S,
|
||||
note_round: F,
|
||||
) -> impl Future<Item=(), Error=CommandOrError<H256, NumberFor<Block>>> where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
B: Backend<Block, Blake2Hasher>,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync,
|
||||
RA: Send + Sync,
|
||||
S: Stream<
|
||||
Item = CommunicationIn<Block>,
|
||||
Error = CommandOrError<Block::Hash, NumberFor<Block>>,
|
||||
>,
|
||||
F: Fn(u64),
|
||||
{
|
||||
let authority_set = authority_set.clone();
|
||||
let consensus_changes = consensus_changes.clone();
|
||||
let client = client.clone();
|
||||
let voters = voters.clone();
|
||||
|
||||
let observer = commits.fold(last_finalized_number, move |last_finalized_number, global| {
|
||||
let (round, commit, callback) = match global {
|
||||
voter::CommunicationIn::Commit(round, commit, callback) => {
|
||||
let commit = grandpa::Commit::from(commit);
|
||||
(round, commit, callback)
|
||||
},
|
||||
voter::CommunicationIn::CatchUp(..) => {
|
||||
// ignore catch up messages
|
||||
return future::ok(last_finalized_number);
|
||||
},
|
||||
};
|
||||
|
||||
// if the commit we've received targets a block lower or equal to the last
|
||||
// finalized, ignore it and continue with the current state
|
||||
if commit.target_number <= last_finalized_number {
|
||||
return future::ok(last_finalized_number);
|
||||
}
|
||||
|
||||
let validation_result = match grandpa::validate_commit(
|
||||
&commit,
|
||||
&voters,
|
||||
&ObserverChain(&*client),
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return future::err(e.into()),
|
||||
};
|
||||
|
||||
if let Some(_) = validation_result.ghost() {
|
||||
let finalized_hash = commit.target_hash;
|
||||
let finalized_number = commit.target_number;
|
||||
|
||||
// commit is valid, finalize the block it targets
|
||||
match environment::finalize_block(
|
||||
&client,
|
||||
&authority_set,
|
||||
&consensus_changes,
|
||||
None,
|
||||
finalized_hash,
|
||||
finalized_number,
|
||||
(round, commit).into(),
|
||||
) {
|
||||
Ok(_) => {},
|
||||
Err(e) => return future::err(e),
|
||||
};
|
||||
|
||||
// note that we've observed completion of this round through the commit,
|
||||
// and that implies that the next round has started.
|
||||
note_round(round + 1);
|
||||
|
||||
grandpa::process_commit_validation_result(validation_result, callback);
|
||||
|
||||
// proceed processing with new finalized block number
|
||||
future::ok(finalized_number)
|
||||
} else {
|
||||
debug!(target: "afg", "Received invalid commit: ({:?}, {:?})", round, commit);
|
||||
|
||||
grandpa::process_commit_validation_result(validation_result, callback);
|
||||
|
||||
// commit is invalid, continue processing commits with the current state
|
||||
future::ok(last_finalized_number)
|
||||
}
|
||||
});
|
||||
|
||||
observer.map(|_| ())
|
||||
}
|
||||
|
||||
/// Run a GRANDPA observer as a task, the observer will finalize blocks only by
|
||||
/// listening for and validating GRANDPA commits instead of following the full
|
||||
/// protocol. Provide configuration and a link to a block import worker that has
|
||||
/// already been instantiated with `block_import`.
|
||||
pub fn run_grandpa_observer<B, E, Block: BlockT<Hash=H256>, N, RA, SC>(
|
||||
config: Config,
|
||||
link: LinkHalf<B, E, Block, RA, SC>,
|
||||
network: N,
|
||||
on_exit: impl Future<Item=(),Error=()> + Clone + Send + 'static,
|
||||
) -> ::client_api::error::Result<impl Future<Item=(),Error=()> + Send + 'static> where
|
||||
B: Backend<Block, Blake2Hasher> + 'static,
|
||||
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + 'static,
|
||||
N: Network<Block> + Send + Sync + 'static,
|
||||
N::In: Send + 'static,
|
||||
SC: SelectChain<Block> + 'static,
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
RA: Send + Sync + 'static,
|
||||
{
|
||||
let LinkHalf {
|
||||
client,
|
||||
select_chain: _,
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
} = link;
|
||||
|
||||
let (network, network_startup) = NetworkBridge::new(
|
||||
network,
|
||||
config.clone(),
|
||||
persistent_data.set_state.clone(),
|
||||
on_exit.clone(),
|
||||
);
|
||||
|
||||
let observer_work = ObserverWork::new(
|
||||
client,
|
||||
network,
|
||||
persistent_data,
|
||||
config.keystore.clone(),
|
||||
voter_commands_rx
|
||||
);
|
||||
|
||||
let observer_work = observer_work
|
||||
.map(|_| ())
|
||||
.map_err(|e| {
|
||||
warn!("GRANDPA Observer failed: {:?}", e);
|
||||
});
|
||||
|
||||
let observer_work = network_startup.and_then(move |()| observer_work);
|
||||
|
||||
Ok(observer_work.select(on_exit).map(|_| ()).map_err(|_| ()))
|
||||
}
|
||||
|
||||
/// Future that powers the observer.
|
||||
#[must_use]
|
||||
struct ObserverWork<B: BlockT<Hash=H256>, N: Network<B>, E, Backend, RA> {
|
||||
observer: Box<dyn Future<Item = (), Error = CommandOrError<B::Hash, NumberFor<B>>> + Send>,
|
||||
client: Arc<Client<Backend, E, B, RA>>,
|
||||
network: NetworkBridge<B, N>,
|
||||
persistent_data: PersistentData<B>,
|
||||
keystore: Option<keystore::KeyStorePtr>,
|
||||
voter_commands_rx: mpsc::UnboundedReceiver<VoterCommand<B::Hash, NumberFor<B>>>,
|
||||
}
|
||||
|
||||
impl<B, N, E, Bk, RA> ObserverWork<B, N, E, Bk, RA>
|
||||
where
|
||||
B: BlockT<Hash=H256>,
|
||||
N: Network<B>,
|
||||
N::In: Send + 'static,
|
||||
NumberFor<B>: BlockNumberOps,
|
||||
RA: 'static + Send + Sync,
|
||||
E: CallExecutor<B, Blake2Hasher> + Send + Sync + 'static,
|
||||
Bk: Backend<B, Blake2Hasher> + 'static,
|
||||
{
|
||||
fn new(
|
||||
client: Arc<Client<Bk, E, B, RA>>,
|
||||
network: NetworkBridge<B, N>,
|
||||
persistent_data: PersistentData<B>,
|
||||
keystore: Option<keystore::KeyStorePtr>,
|
||||
voter_commands_rx: mpsc::UnboundedReceiver<VoterCommand<B::Hash, NumberFor<B>>>,
|
||||
) -> Self {
|
||||
|
||||
let mut work = ObserverWork {
|
||||
// `observer` is set to a temporary value and replaced below when
|
||||
// calling `rebuild_observer`.
|
||||
observer: Box::new(futures::empty()) as Box<_>,
|
||||
client,
|
||||
network,
|
||||
persistent_data,
|
||||
keystore,
|
||||
voter_commands_rx,
|
||||
};
|
||||
work.rebuild_observer();
|
||||
work
|
||||
}
|
||||
|
||||
/// Rebuilds the `self.observer` field using the current authority set
|
||||
/// state. This method should be called when we know that the authority set
|
||||
/// has changed (e.g. as signalled by a voter command).
|
||||
fn rebuild_observer(&mut self) {
|
||||
let set_id = self.persistent_data.authority_set.set_id();
|
||||
let voters = Arc::new(self.persistent_data.authority_set.current_authorities());
|
||||
|
||||
// start global communication stream for the current set
|
||||
let (global_in, _) = global_communication(
|
||||
set_id,
|
||||
&voters,
|
||||
&self.client,
|
||||
&self.network,
|
||||
&self.keystore,
|
||||
);
|
||||
|
||||
let last_finalized_number = self.client.info().chain.finalized_number;
|
||||
|
||||
// NOTE: since we are not using `round_communication` we have to
|
||||
// manually note the round with the gossip validator, otherwise we won't
|
||||
// relay round messages. we want all full nodes to contribute to vote
|
||||
// availability.
|
||||
let note_round = {
|
||||
let network = self.network.clone();
|
||||
let voters = voters.clone();
|
||||
|
||||
move |round| network.note_round(
|
||||
crate::communication::Round(round),
|
||||
crate::communication::SetId(set_id),
|
||||
&*voters,
|
||||
)
|
||||
};
|
||||
|
||||
// create observer for the current set
|
||||
let observer = grandpa_observer(
|
||||
&self.client,
|
||||
&self.persistent_data.authority_set,
|
||||
&self.persistent_data.consensus_changes,
|
||||
&voters,
|
||||
last_finalized_number,
|
||||
global_in,
|
||||
note_round,
|
||||
);
|
||||
|
||||
self.observer = Box::new(observer);
|
||||
}
|
||||
|
||||
fn handle_voter_command(
|
||||
&mut self,
|
||||
command: VoterCommand<B::Hash, NumberFor<B>>,
|
||||
) -> Result<(), Error> {
|
||||
// the observer doesn't use the voter set state, but we need to
|
||||
// update it on-disk in case we restart as validator in the future.
|
||||
self.persistent_data.set_state = match command {
|
||||
VoterCommand::Pause(reason) => {
|
||||
info!(target: "afg", "Pausing old validator set: {}", reason);
|
||||
|
||||
let completed_rounds = self.persistent_data.set_state.read().completed_rounds();
|
||||
let set_state = VoterSetState::Paused { completed_rounds };
|
||||
|
||||
crate::aux_schema::write_voter_set_state(&*self.client, &set_state)?;
|
||||
|
||||
set_state
|
||||
},
|
||||
VoterCommand::ChangeAuthorities(new) => {
|
||||
// start the new authority set using the block where the
|
||||
// set changed (not where the signal happened!) as the base.
|
||||
let set_state = VoterSetState::live(
|
||||
new.set_id,
|
||||
&*self.persistent_data.authority_set.inner().read(),
|
||||
(new.canon_hash, new.canon_number),
|
||||
);
|
||||
|
||||
crate::aux_schema::write_voter_set_state(&*self.client, &set_state)?;
|
||||
|
||||
set_state
|
||||
},
|
||||
}.into();
|
||||
|
||||
self.rebuild_observer();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, N, E, Bk, RA> Future for ObserverWork<B, N, E, Bk, RA>
|
||||
where
|
||||
B: BlockT<Hash=H256>,
|
||||
N: Network<B>,
|
||||
N::In: Send + 'static,
|
||||
NumberFor<B>: BlockNumberOps,
|
||||
RA: 'static + Send + Sync,
|
||||
E: CallExecutor<B, Blake2Hasher> + Send + Sync + 'static,
|
||||
Bk: Backend<B, Blake2Hasher> + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.observer.poll() {
|
||||
Ok(Async::NotReady) => {}
|
||||
Ok(Async::Ready(())) => {
|
||||
// observer commit stream doesn't conclude naturally; this could reasonably be an error.
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
Err(CommandOrError::Error(e)) => {
|
||||
// return inner observer error
|
||||
return Err(e)
|
||||
}
|
||||
Err(CommandOrError::VoterCommand(command)) => {
|
||||
// some command issued internally
|
||||
self.handle_voter_command(command)?;
|
||||
futures::task::current().notify();
|
||||
}
|
||||
}
|
||||
|
||||
match self.voter_commands_rx.poll() {
|
||||
Ok(Async::NotReady) => {}
|
||||
Err(_) => {
|
||||
// the `voter_commands_rx` stream should not fail.
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
// the `voter_commands_rx` stream should never conclude since it's never closed.
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
Ok(Async::Ready(Some(command))) => {
|
||||
// some command issued externally
|
||||
self.handle_voter_command(command)?;
|
||||
futures::task::current().notify();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,940 @@
|
||||
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Helper stream for waiting until one or more blocks are imported before
|
||||
//! passing through inner items. This is done in a generic way to support
|
||||
//! many different kinds of items.
|
||||
//!
|
||||
//! This is used for votes and commit messages currently.
|
||||
|
||||
use super::{
|
||||
BlockStatus as BlockStatusT,
|
||||
BlockSyncRequester as BlockSyncRequesterT,
|
||||
CommunicationIn,
|
||||
Error,
|
||||
SignedMessage,
|
||||
};
|
||||
|
||||
use log::{debug, warn};
|
||||
use client_api::{BlockImportNotification, ImportNotifications};
|
||||
use futures::prelude::*;
|
||||
use futures::stream::Fuse;
|
||||
use futures03::{StreamExt as _, TryStreamExt as _};
|
||||
use grandpa::voter;
|
||||
use parking_lot::Mutex;
|
||||
use sr_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use tokio_timer::Interval;
|
||||
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::sync::{atomic::{AtomicUsize, Ordering}, Arc};
|
||||
use std::time::{Duration, Instant};
|
||||
use fg_primitives::AuthorityId;
|
||||
|
||||
const LOG_PENDING_INTERVAL: Duration = Duration::from_secs(15);
|
||||
|
||||
// something which will block until imported.
|
||||
pub(crate) trait BlockUntilImported<Block: BlockT>: Sized {
|
||||
// the type that is blocked on.
|
||||
type Blocked;
|
||||
|
||||
/// new incoming item. For all internal items,
|
||||
/// check if they require to be waited for.
|
||||
/// if so, call the `Wait` closure.
|
||||
/// if they are ready, call the `Ready` closure.
|
||||
fn schedule_wait<S, Wait, Ready>(
|
||||
input: Self::Blocked,
|
||||
status_check: &S,
|
||||
wait: Wait,
|
||||
ready: Ready,
|
||||
) -> Result<(), Error> where
|
||||
S: BlockStatusT<Block>,
|
||||
Wait: FnMut(Block::Hash, NumberFor<Block>, Self),
|
||||
Ready: FnMut(Self::Blocked);
|
||||
|
||||
/// called when the wait has completed. The canonical number is passed through
|
||||
/// for further checks.
|
||||
fn wait_completed(self, canon_number: NumberFor<Block>) -> Option<Self::Blocked>;
|
||||
}
|
||||
|
||||
/// Buffering imported messages until blocks with given hashes are imported.
|
||||
pub(crate) struct UntilImported<Block: BlockT, BlockStatus, BlockSyncRequester, I, M: BlockUntilImported<Block>> {
|
||||
import_notifications: Fuse<Box<dyn Stream<Item = BlockImportNotification<Block>, Error = ()> + Send>>,
|
||||
block_sync_requester: BlockSyncRequester,
|
||||
status_check: BlockStatus,
|
||||
inner: Fuse<I>,
|
||||
ready: VecDeque<M::Blocked>,
|
||||
check_pending: Interval,
|
||||
/// Mapping block hashes to their block number, the point in time it was
|
||||
/// first encountered (Instant) and a list of GRANDPA messages referencing
|
||||
/// the block hash.
|
||||
pending: HashMap<Block::Hash, (NumberFor<Block>, Instant, Vec<M>)>,
|
||||
identifier: &'static str,
|
||||
}
|
||||
|
||||
impl<Block, BlockStatus, BlockSyncRequester, I, M> UntilImported<Block, BlockStatus, BlockSyncRequester, I, M> where
|
||||
Block: BlockT,
|
||||
BlockStatus: BlockStatusT<Block>,
|
||||
M: BlockUntilImported<Block>,
|
||||
I: Stream,
|
||||
{
|
||||
/// Create a new `UntilImported` wrapper.
|
||||
pub(crate) fn new(
|
||||
import_notifications: ImportNotifications<Block>,
|
||||
block_sync_requester: BlockSyncRequester,
|
||||
status_check: BlockStatus,
|
||||
stream: I,
|
||||
identifier: &'static str,
|
||||
) -> Self {
|
||||
// how often to check if pending messages that are waiting for blocks to be
|
||||
// imported can be checked.
|
||||
//
|
||||
// the import notifications interval takes care of most of this; this is
|
||||
// used in the event of missed import notifications
|
||||
const CHECK_PENDING_INTERVAL: Duration = Duration::from_secs(5);
|
||||
let now = Instant::now();
|
||||
|
||||
let check_pending = Interval::new(now + CHECK_PENDING_INTERVAL, CHECK_PENDING_INTERVAL);
|
||||
UntilImported {
|
||||
import_notifications: {
|
||||
let stream = import_notifications.map::<_, fn(_) -> _>(|v| Ok::<_, ()>(v)).compat();
|
||||
Box::new(stream) as Box<dyn Stream<Item = _, Error = _> + Send>
|
||||
}.fuse(),
|
||||
block_sync_requester,
|
||||
status_check,
|
||||
inner: stream.fuse(),
|
||||
ready: VecDeque::new(),
|
||||
check_pending,
|
||||
pending: HashMap::new(),
|
||||
identifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, BStatus, BSyncRequester, I, M> Stream for UntilImported<Block, BStatus, BSyncRequester, I, M> where
|
||||
Block: BlockT,
|
||||
BStatus: BlockStatusT<Block>,
|
||||
BSyncRequester: BlockSyncRequesterT<Block>,
|
||||
I: Stream<Item=M::Blocked,Error=Error>,
|
||||
M: BlockUntilImported<Block>,
|
||||
{
|
||||
type Item = M::Blocked;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<M::Blocked>, Error> {
|
||||
loop {
|
||||
match self.inner.poll()? {
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::Ready(Some(input)) => {
|
||||
// new input: schedule wait of any parts which require
|
||||
// blocks to be known.
|
||||
let ready = &mut self.ready;
|
||||
let pending = &mut self.pending;
|
||||
M::schedule_wait(
|
||||
input,
|
||||
&self.status_check,
|
||||
|target_hash, target_number, wait| pending
|
||||
.entry(target_hash)
|
||||
.or_insert_with(|| (target_number, Instant::now(), Vec::new()))
|
||||
.2
|
||||
.push(wait),
|
||||
|ready_item| ready.push_back(ready_item),
|
||||
)?;
|
||||
}
|
||||
Async::NotReady => break,
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.import_notifications.poll() {
|
||||
Err(_) => return Err(Error::Network(format!("Failed to get new message"))),
|
||||
Ok(Async::Ready(None)) => return Ok(Async::Ready(None)),
|
||||
Ok(Async::Ready(Some(notification))) => {
|
||||
// new block imported. queue up all messages tied to that hash.
|
||||
if let Some((_, _, messages)) = self.pending.remove(¬ification.hash) {
|
||||
let canon_number = notification.header.number().clone();
|
||||
let ready_messages = messages.into_iter()
|
||||
.filter_map(|m| m.wait_completed(canon_number));
|
||||
|
||||
self.ready.extend(ready_messages);
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => break,
|
||||
}
|
||||
}
|
||||
|
||||
let mut update_interval = false;
|
||||
while let Async::Ready(Some(_)) = self.check_pending.poll().map_err(Error::Timer)? {
|
||||
update_interval = true;
|
||||
}
|
||||
|
||||
if update_interval {
|
||||
let mut known_keys = Vec::new();
|
||||
for (&block_hash, &mut (block_number, ref mut last_log, ref v)) in &mut self.pending {
|
||||
if let Some(number) = self.status_check.block_number(block_hash)? {
|
||||
known_keys.push((block_hash, number));
|
||||
} else {
|
||||
let next_log = *last_log + LOG_PENDING_INTERVAL;
|
||||
if Instant::now() >= next_log {
|
||||
debug!(
|
||||
target: "afg",
|
||||
"Waiting to import block {} before {} {} messages can be imported. \
|
||||
Requesting network sync service to retrieve block from. \
|
||||
Possible fork?",
|
||||
block_hash,
|
||||
v.len(),
|
||||
self.identifier,
|
||||
);
|
||||
|
||||
// NOTE: when sending an empty vec of peers the
|
||||
// underlying should make a best effort to sync the
|
||||
// block from any peers it knows about.
|
||||
self.block_sync_requester.set_sync_fork_request(
|
||||
vec![],
|
||||
block_hash,
|
||||
block_number,
|
||||
);
|
||||
|
||||
*last_log = next_log;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (known_hash, canon_number) in known_keys {
|
||||
if let Some((_, _, pending_messages)) = self.pending.remove(&known_hash) {
|
||||
let ready_messages = pending_messages.into_iter()
|
||||
.filter_map(|m| m.wait_completed(canon_number));
|
||||
|
||||
self.ready.extend(ready_messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ready) = self.ready.pop_front() {
|
||||
return Ok(Async::Ready(Some(ready)))
|
||||
}
|
||||
|
||||
if self.import_notifications.is_done() && self.inner.is_done() {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn warn_authority_wrong_target<H: ::std::fmt::Display>(hash: H, id: AuthorityId) {
|
||||
warn!(
|
||||
target: "afg",
|
||||
"Authority {:?} signed GRANDPA message with \
|
||||
wrong block number for hash {}",
|
||||
id,
|
||||
hash,
|
||||
);
|
||||
}
|
||||
|
||||
impl<Block: BlockT> BlockUntilImported<Block> for SignedMessage<Block> {
|
||||
type Blocked = Self;
|
||||
|
||||
fn schedule_wait<BlockStatus, Wait, Ready>(
|
||||
msg: Self::Blocked,
|
||||
status_check: &BlockStatus,
|
||||
mut wait: Wait,
|
||||
mut ready: Ready,
|
||||
) -> Result<(), Error> where
|
||||
BlockStatus: BlockStatusT<Block>,
|
||||
Wait: FnMut(Block::Hash, NumberFor<Block>, Self),
|
||||
Ready: FnMut(Self::Blocked),
|
||||
{
|
||||
let (&target_hash, target_number) = msg.target();
|
||||
|
||||
if let Some(number) = status_check.block_number(target_hash)? {
|
||||
if number != target_number {
|
||||
warn_authority_wrong_target(target_hash, msg.id);
|
||||
} else {
|
||||
ready(msg);
|
||||
}
|
||||
} else {
|
||||
wait(target_hash, target_number, msg)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_completed(self, canon_number: NumberFor<Block>) -> Option<Self::Blocked> {
|
||||
let (&target_hash, target_number) = self.target();
|
||||
if canon_number != target_number {
|
||||
warn_authority_wrong_target(target_hash, self.id);
|
||||
|
||||
None
|
||||
} else {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type definition for the stream which waits until vote targets for
|
||||
/// signed messages are imported.
|
||||
pub(crate) type UntilVoteTargetImported<Block, BlockStatus, BlockSyncRequester, I> = UntilImported<
|
||||
Block,
|
||||
BlockStatus,
|
||||
BlockSyncRequester,
|
||||
I,
|
||||
SignedMessage<Block>,
|
||||
>;
|
||||
|
||||
/// This blocks a global message import, i.e. a commit or catch up messages,
|
||||
/// until all blocks referenced in its votes are known.
|
||||
///
|
||||
/// This is used for compact commits and catch up messages which have already
|
||||
/// been checked for structural soundness (e.g. valid signatures).
|
||||
pub(crate) struct BlockGlobalMessage<Block: BlockT> {
|
||||
inner: Arc<(AtomicUsize, Mutex<Option<CommunicationIn<Block>>>)>,
|
||||
target_number: NumberFor<Block>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> BlockUntilImported<Block> for BlockGlobalMessage<Block> {
|
||||
type Blocked = CommunicationIn<Block>;
|
||||
|
||||
fn schedule_wait<BlockStatus, Wait, Ready>(
|
||||
input: Self::Blocked,
|
||||
status_check: &BlockStatus,
|
||||
mut wait: Wait,
|
||||
mut ready: Ready,
|
||||
) -> Result<(), Error> where
|
||||
BlockStatus: BlockStatusT<Block>,
|
||||
Wait: FnMut(Block::Hash, NumberFor<Block>, Self),
|
||||
Ready: FnMut(Self::Blocked),
|
||||
{
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
enum KnownOrUnknown<N> {
|
||||
Known(N),
|
||||
Unknown(N),
|
||||
}
|
||||
|
||||
impl<N> KnownOrUnknown<N> {
|
||||
fn number(&self) -> &N {
|
||||
match *self {
|
||||
KnownOrUnknown::Known(ref n) => n,
|
||||
KnownOrUnknown::Unknown(ref n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut checked_hashes: HashMap<_, KnownOrUnknown<NumberFor<Block>>> = HashMap::new();
|
||||
let mut unknown_count = 0;
|
||||
|
||||
{
|
||||
// returns false when should early exit.
|
||||
let mut query_known = |target_hash, perceived_number| -> Result<bool, Error> {
|
||||
// check integrity: all votes for same hash have same number.
|
||||
let canon_number = match checked_hashes.entry(target_hash) {
|
||||
Entry::Occupied(entry) => entry.get().number().clone(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Some(number) = status_check.block_number(target_hash)? {
|
||||
entry.insert(KnownOrUnknown::Known(number));
|
||||
number
|
||||
|
||||
} else {
|
||||
entry.insert(KnownOrUnknown::Unknown(perceived_number));
|
||||
unknown_count += 1;
|
||||
perceived_number
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if canon_number != perceived_number {
|
||||
// invalid global message: messages targeting wrong number
|
||||
// or at least different from other vote in same global
|
||||
// message.
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
};
|
||||
|
||||
match input {
|
||||
voter::CommunicationIn::Commit(_, ref commit, ..) => {
|
||||
// add known hashes from all precommits.
|
||||
let precommit_targets = commit.precommits
|
||||
.iter()
|
||||
.map(|c| (c.target_number, c.target_hash));
|
||||
|
||||
for (target_number, target_hash) in precommit_targets {
|
||||
if !query_known(target_hash, target_number)? {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
voter::CommunicationIn::CatchUp(ref catch_up, ..) => {
|
||||
// add known hashes from all prevotes and precommits.
|
||||
let prevote_targets = catch_up.prevotes
|
||||
.iter()
|
||||
.map(|s| (s.prevote.target_number, s.prevote.target_hash));
|
||||
|
||||
let precommit_targets = catch_up.precommits
|
||||
.iter()
|
||||
.map(|s| (s.precommit.target_number, s.precommit.target_hash));
|
||||
|
||||
let targets = prevote_targets.chain(precommit_targets);
|
||||
|
||||
for (target_number, target_hash) in targets {
|
||||
if !query_known(target_hash, target_number)? {
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// none of the hashes in the global message were unknown.
|
||||
// we can just return the message directly.
|
||||
if unknown_count == 0 {
|
||||
ready(input);
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let locked_global = Arc::new((AtomicUsize::new(unknown_count), Mutex::new(Some(input))));
|
||||
|
||||
// schedule waits for all unknown messages.
|
||||
// when the last one of these has `wait_completed` called on it,
|
||||
// the global message will be returned.
|
||||
//
|
||||
// in the future, we may want to issue sync requests to the network
|
||||
// if this is taking a long time.
|
||||
for (hash, is_known) in checked_hashes {
|
||||
if let KnownOrUnknown::Unknown(target_number) = is_known {
|
||||
wait(hash, target_number, BlockGlobalMessage {
|
||||
inner: locked_global.clone(),
|
||||
target_number,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn wait_completed(self, canon_number: NumberFor<Block>) -> Option<Self::Blocked> {
|
||||
if self.target_number != canon_number {
|
||||
// if we return without deducting the counter, then none of the other
|
||||
// handles can return the commit message.
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut last_count = self.inner.0.load(Ordering::Acquire);
|
||||
|
||||
// CAS loop to ensure that we always have a last reader.
|
||||
loop {
|
||||
if last_count == 1 { // we are the last one left.
|
||||
return self.inner.1.lock().take();
|
||||
}
|
||||
|
||||
let prev_value = self.inner.0.compare_and_swap(
|
||||
last_count,
|
||||
last_count - 1,
|
||||
Ordering::SeqCst,
|
||||
);
|
||||
|
||||
if prev_value == last_count {
|
||||
return None;
|
||||
} else {
|
||||
last_count = prev_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A stream which gates off incoming global messages, i.e. commit and catch up
|
||||
/// messages, until all referenced block hashes have been imported.
|
||||
pub(crate) type UntilGlobalMessageBlocksImported<Block, BlockStatus, BlockSyncRequester, I> = UntilImported<
|
||||
Block,
|
||||
BlockStatus,
|
||||
BlockSyncRequester,
|
||||
I,
|
||||
BlockGlobalMessage<Block>,
|
||||
>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{CatchUp, CompactCommit};
|
||||
use tokio::runtime::current_thread::Runtime;
|
||||
use tokio_timer::Delay;
|
||||
use test_client::runtime::{Block, Hash, Header};
|
||||
use consensus_common::BlockOrigin;
|
||||
use client_api::BlockImportNotification;
|
||||
use futures::future::Either;
|
||||
use futures03::channel::mpsc;
|
||||
use grandpa::Precommit;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestChainState {
|
||||
sender: mpsc::UnboundedSender<BlockImportNotification<Block>>,
|
||||
known_blocks: Arc<Mutex<HashMap<Hash, u64>>>,
|
||||
}
|
||||
|
||||
impl TestChainState {
|
||||
fn new() -> (Self, ImportNotifications<Block>) {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let state = TestChainState {
|
||||
sender: tx,
|
||||
known_blocks: Arc::new(Mutex::new(HashMap::new())),
|
||||
};
|
||||
|
||||
(state, rx)
|
||||
}
|
||||
|
||||
fn block_status(&self) -> TestBlockStatus {
|
||||
TestBlockStatus { inner: self.known_blocks.clone() }
|
||||
}
|
||||
|
||||
fn import_header(&self, header: Header) {
|
||||
let hash = header.hash();
|
||||
let number = header.number().clone();
|
||||
|
||||
self.known_blocks.lock().insert(hash, number);
|
||||
self.sender.unbounded_send(BlockImportNotification {
|
||||
hash,
|
||||
origin: BlockOrigin::File,
|
||||
header,
|
||||
is_new_best: false,
|
||||
retracted: vec![],
|
||||
}).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
struct TestBlockStatus {
|
||||
inner: Arc<Mutex<HashMap<Hash, u64>>>,
|
||||
}
|
||||
|
||||
impl BlockStatusT<Block> for TestBlockStatus {
|
||||
fn block_number(&self, hash: Hash) -> Result<Option<u64>, Error> {
|
||||
Ok(self.inner.lock().get(&hash).map(|x| x.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct TestBlockSyncRequester {
|
||||
requests: Arc<Mutex<Vec<(Hash, NumberFor<Block>)>>>,
|
||||
}
|
||||
|
||||
impl Default for TestBlockSyncRequester {
|
||||
fn default() -> Self {
|
||||
TestBlockSyncRequester {
|
||||
requests: Arc::new(Mutex::new(Vec::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockSyncRequesterT<Block> for TestBlockSyncRequester {
|
||||
fn set_sync_fork_request(&self, _peers: Vec<network::PeerId>, hash: Hash, number: NumberFor<Block>) {
|
||||
self.requests.lock().push((hash, number));
|
||||
}
|
||||
}
|
||||
|
||||
fn make_header(number: u64) -> Header {
|
||||
Header::new(
|
||||
number,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
|
||||
// unwrap the commit from `CommunicationIn` returning its fields in a tuple,
|
||||
// panics if the given message isn't a commit
|
||||
fn unapply_commit(msg: CommunicationIn<Block>) -> (u64, CompactCommit::<Block>) {
|
||||
match msg {
|
||||
voter::CommunicationIn::Commit(round, commit, ..) => (round, commit),
|
||||
_ => panic!("expected commit"),
|
||||
}
|
||||
}
|
||||
|
||||
// unwrap the catch up from `CommunicationIn` returning its inner representation,
|
||||
// panics if the given message isn't a catch up
|
||||
fn unapply_catch_up(msg: CommunicationIn<Block>) -> CatchUp<Block> {
|
||||
match msg {
|
||||
voter::CommunicationIn::CatchUp(catch_up, ..) => catch_up,
|
||||
_ => panic!("expected catch up"),
|
||||
}
|
||||
}
|
||||
|
||||
fn message_all_dependencies_satisfied<F>(
|
||||
msg: CommunicationIn<Block>,
|
||||
enact_dependencies: F,
|
||||
) -> CommunicationIn<Block> where
|
||||
F: FnOnce(&TestChainState),
|
||||
{
|
||||
let (chain_state, import_notifications) = TestChainState::new();
|
||||
let block_status = chain_state.block_status();
|
||||
|
||||
// enact all dependencies before importing the message
|
||||
enact_dependencies(&chain_state);
|
||||
|
||||
let (global_tx, global_rx) = futures::sync::mpsc::unbounded();
|
||||
|
||||
let until_imported = UntilGlobalMessageBlocksImported::new(
|
||||
import_notifications,
|
||||
TestBlockSyncRequester::default(),
|
||||
block_status,
|
||||
global_rx.map_err(|_| panic!("should never error")),
|
||||
"global",
|
||||
);
|
||||
|
||||
global_tx.unbounded_send(msg).unwrap();
|
||||
|
||||
let work = until_imported.into_future();
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime.block_on(work).map_err(|(e, _)| e).unwrap().0.unwrap()
|
||||
}
|
||||
|
||||
fn blocking_message_on_dependencies<F>(
|
||||
msg: CommunicationIn<Block>,
|
||||
enact_dependencies: F,
|
||||
) -> CommunicationIn<Block> where
|
||||
F: FnOnce(&TestChainState),
|
||||
{
|
||||
let (chain_state, import_notifications) = TestChainState::new();
|
||||
let block_status = chain_state.block_status();
|
||||
|
||||
let (global_tx, global_rx) = futures::sync::mpsc::unbounded();
|
||||
|
||||
let until_imported = UntilGlobalMessageBlocksImported::new(
|
||||
import_notifications,
|
||||
TestBlockSyncRequester::default(),
|
||||
block_status,
|
||||
global_rx.map_err(|_| panic!("should never error")),
|
||||
"global",
|
||||
);
|
||||
|
||||
global_tx.unbounded_send(msg).unwrap();
|
||||
|
||||
// NOTE: needs to be cloned otherwise it is moved to the stream and
|
||||
// dropped too early.
|
||||
let inner_chain_state = chain_state.clone();
|
||||
let work = until_imported
|
||||
.into_future()
|
||||
.select2(Delay::new(Instant::now() + Duration::from_millis(100)))
|
||||
.then(move |res| match res {
|
||||
Err(_) => panic!("neither should have had error"),
|
||||
Ok(Either::A(_)) => panic!("timeout should have fired first"),
|
||||
Ok(Either::B((_, until_imported))) => {
|
||||
// timeout fired. push in the headers.
|
||||
enact_dependencies(&inner_chain_state);
|
||||
|
||||
until_imported
|
||||
}
|
||||
});
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime.block_on(work).map_err(|(e, _)| e).unwrap().0.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_commit_message() {
|
||||
let h1 = make_header(5);
|
||||
let h2 = make_header(6);
|
||||
let h3 = make_header(7);
|
||||
|
||||
let unknown_commit = CompactCommit::<Block> {
|
||||
target_hash: h1.hash(),
|
||||
target_number: 5,
|
||||
precommits: vec![
|
||||
Precommit {
|
||||
target_hash: h2.hash(),
|
||||
target_number: 6,
|
||||
},
|
||||
Precommit {
|
||||
target_hash: h3.hash(),
|
||||
target_number: 7,
|
||||
},
|
||||
],
|
||||
auth_data: Vec::new(), // not used
|
||||
};
|
||||
|
||||
let unknown_commit = || voter::CommunicationIn::Commit(
|
||||
0,
|
||||
unknown_commit.clone(),
|
||||
voter::Callback::Blank,
|
||||
);
|
||||
|
||||
let res = blocking_message_on_dependencies(
|
||||
unknown_commit(),
|
||||
|chain_state| {
|
||||
chain_state.import_header(h1);
|
||||
chain_state.import_header(h2);
|
||||
chain_state.import_header(h3);
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unapply_commit(res),
|
||||
unapply_commit(unknown_commit()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commit_message_all_known() {
|
||||
let h1 = make_header(5);
|
||||
let h2 = make_header(6);
|
||||
let h3 = make_header(7);
|
||||
|
||||
let known_commit = CompactCommit::<Block> {
|
||||
target_hash: h1.hash(),
|
||||
target_number: 5,
|
||||
precommits: vec![
|
||||
Precommit {
|
||||
target_hash: h2.hash(),
|
||||
target_number: 6,
|
||||
},
|
||||
Precommit {
|
||||
target_hash: h3.hash(),
|
||||
target_number: 7,
|
||||
},
|
||||
],
|
||||
auth_data: Vec::new(), // not used
|
||||
};
|
||||
|
||||
let known_commit = || voter::CommunicationIn::Commit(
|
||||
0,
|
||||
known_commit.clone(),
|
||||
voter::Callback::Blank,
|
||||
);
|
||||
|
||||
let res = message_all_dependencies_satisfied(
|
||||
known_commit(),
|
||||
|chain_state| {
|
||||
chain_state.import_header(h1);
|
||||
chain_state.import_header(h2);
|
||||
chain_state.import_header(h3);
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unapply_commit(res),
|
||||
unapply_commit(known_commit()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_catch_up_message() {
|
||||
let h1 = make_header(5);
|
||||
let h2 = make_header(6);
|
||||
let h3 = make_header(7);
|
||||
|
||||
let signed_prevote = |header: &Header| {
|
||||
grandpa::SignedPrevote {
|
||||
id: Default::default(),
|
||||
signature: Default::default(),
|
||||
prevote: grandpa::Prevote {
|
||||
target_hash: header.hash(),
|
||||
target_number: *header.number(),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let signed_precommit = |header: &Header| {
|
||||
grandpa::SignedPrecommit {
|
||||
id: Default::default(),
|
||||
signature: Default::default(),
|
||||
precommit: grandpa::Precommit {
|
||||
target_hash: header.hash(),
|
||||
target_number: *header.number(),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let prevotes = vec![
|
||||
signed_prevote(&h1),
|
||||
signed_prevote(&h3),
|
||||
];
|
||||
|
||||
let precommits = vec![
|
||||
signed_precommit(&h1),
|
||||
signed_precommit(&h2),
|
||||
];
|
||||
|
||||
let unknown_catch_up = grandpa::CatchUp {
|
||||
round_number: 1,
|
||||
prevotes,
|
||||
precommits,
|
||||
base_hash: h1.hash(),
|
||||
base_number: *h1.number(),
|
||||
};
|
||||
|
||||
let unknown_catch_up = || voter::CommunicationIn::CatchUp(
|
||||
unknown_catch_up.clone(),
|
||||
voter::Callback::Blank,
|
||||
);
|
||||
|
||||
let res = blocking_message_on_dependencies(
|
||||
unknown_catch_up(),
|
||||
|chain_state| {
|
||||
chain_state.import_header(h1);
|
||||
chain_state.import_header(h2);
|
||||
chain_state.import_header(h3);
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unapply_catch_up(res),
|
||||
unapply_catch_up(unknown_catch_up()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catch_up_message_all_known() {
|
||||
let h1 = make_header(5);
|
||||
let h2 = make_header(6);
|
||||
let h3 = make_header(7);
|
||||
|
||||
let signed_prevote = |header: &Header| {
|
||||
grandpa::SignedPrevote {
|
||||
id: Default::default(),
|
||||
signature: Default::default(),
|
||||
prevote: grandpa::Prevote {
|
||||
target_hash: header.hash(),
|
||||
target_number: *header.number(),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let signed_precommit = |header: &Header| {
|
||||
grandpa::SignedPrecommit {
|
||||
id: Default::default(),
|
||||
signature: Default::default(),
|
||||
precommit: grandpa::Precommit {
|
||||
target_hash: header.hash(),
|
||||
target_number: *header.number(),
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let prevotes = vec![
|
||||
signed_prevote(&h1),
|
||||
signed_prevote(&h3),
|
||||
];
|
||||
|
||||
let precommits = vec![
|
||||
signed_precommit(&h1),
|
||||
signed_precommit(&h2),
|
||||
];
|
||||
|
||||
let unknown_catch_up = grandpa::CatchUp {
|
||||
round_number: 1,
|
||||
prevotes,
|
||||
precommits,
|
||||
base_hash: h1.hash(),
|
||||
base_number: *h1.number(),
|
||||
};
|
||||
|
||||
let unknown_catch_up = || voter::CommunicationIn::CatchUp(
|
||||
unknown_catch_up.clone(),
|
||||
voter::Callback::Blank,
|
||||
);
|
||||
|
||||
let res = message_all_dependencies_satisfied(
|
||||
unknown_catch_up(),
|
||||
|chain_state| {
|
||||
chain_state.import_header(h1);
|
||||
chain_state.import_header(h2);
|
||||
chain_state.import_header(h3);
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unapply_catch_up(res),
|
||||
unapply_catch_up(unknown_catch_up()),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_block_sync_for_needed_blocks() {
|
||||
let (chain_state, import_notifications) = TestChainState::new();
|
||||
let block_status = chain_state.block_status();
|
||||
|
||||
let (global_tx, global_rx) = futures::sync::mpsc::unbounded();
|
||||
|
||||
let block_sync_requester = TestBlockSyncRequester::default();
|
||||
|
||||
let until_imported = UntilGlobalMessageBlocksImported::new(
|
||||
import_notifications,
|
||||
block_sync_requester.clone(),
|
||||
block_status,
|
||||
global_rx.map_err(|_| panic!("should never error")),
|
||||
"global",
|
||||
);
|
||||
|
||||
let h1 = make_header(5);
|
||||
let h2 = make_header(6);
|
||||
let h3 = make_header(7);
|
||||
|
||||
// we create a commit message, with precommits for blocks 6 and 7 which
|
||||
// we haven't imported.
|
||||
let unknown_commit = CompactCommit::<Block> {
|
||||
target_hash: h1.hash(),
|
||||
target_number: 5,
|
||||
precommits: vec![
|
||||
Precommit {
|
||||
target_hash: h2.hash(),
|
||||
target_number: 6,
|
||||
},
|
||||
Precommit {
|
||||
target_hash: h3.hash(),
|
||||
target_number: 7,
|
||||
},
|
||||
],
|
||||
auth_data: Vec::new(), // not used
|
||||
};
|
||||
|
||||
let unknown_commit = || voter::CommunicationIn::Commit(
|
||||
0,
|
||||
unknown_commit.clone(),
|
||||
voter::Callback::Blank,
|
||||
);
|
||||
|
||||
// we send the commit message and spawn the until_imported stream
|
||||
global_tx.unbounded_send(unknown_commit()).unwrap();
|
||||
|
||||
let mut runtime = Runtime::new().unwrap();
|
||||
runtime.spawn(until_imported.into_future().map(|_| ()).map_err(|_| ()));
|
||||
|
||||
// assert that we will make sync requests
|
||||
let assert = futures::future::poll_fn::<(), (), _>(|| {
|
||||
let block_sync_requests = block_sync_requester.requests.lock();
|
||||
|
||||
// we request blocks targeted by the precommits that aren't imported
|
||||
if block_sync_requests.contains(&(h2.hash(), *h2.number())) &&
|
||||
block_sync_requests.contains(&(h3.hash(), *h3.number()))
|
||||
{
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
});
|
||||
|
||||
// the `until_imported` stream doesn't request the blocks immediately,
|
||||
// but it should request them after a small timeout
|
||||
let timeout = Delay::new(Instant::now() + Duration::from_secs(60));
|
||||
let test = assert.select2(timeout).map(|res| match res {
|
||||
Either::A(_) => {},
|
||||
Either::B(_) => panic!("timed out waiting for block sync request"),
|
||||
}).map_err(|_| ());
|
||||
|
||||
runtime.block_on(test).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
// Copyright 2018-2019 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate 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.
|
||||
|
||||
// Substrate 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 Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Handling custom voting rules for GRANDPA.
|
||||
//!
|
||||
//! This exposes the `VotingRule` trait used to implement arbitrary voting
|
||||
//! restrictions that are taken into account by the GRANDPA environment when
|
||||
//! selecting a finality target to vote on.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use client_api::blockchain::HeaderBackend;
|
||||
use sr_primitives::generic::BlockId;
|
||||
use sr_primitives::traits::{Block as BlockT, Header, NumberFor, One, Zero};
|
||||
|
||||
/// A trait for custom voting rules in GRANDPA.
|
||||
pub trait VotingRule<Block, B>: Send + Sync where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
/// Restrict the given `current_target` vote, returning the block hash and
|
||||
/// number of the block to vote on, and `None` in case the vote should not
|
||||
/// be restricted. `base` is the block that we're basing our votes on in
|
||||
/// order to pick our target (e.g. last round estimate), and `best_target`
|
||||
/// is the initial best vote target before any vote rules were applied. When
|
||||
/// applying multiple `VotingRule`s both `base` and `best_target` should
|
||||
/// remain unchanged.
|
||||
///
|
||||
/// The contract of this interface requires that when restricting a vote, the
|
||||
/// returned value **must** be an ancestor of the given `current_target`,
|
||||
/// this also means that a variant must be maintained throughout the
|
||||
/// execution of voting rules wherein `current_target <= best_target`.
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
backend: &B,
|
||||
base: &Block::Header,
|
||||
best_target: &Block::Header,
|
||||
current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)>;
|
||||
}
|
||||
|
||||
impl<Block, B> VotingRule<Block, B> for () where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
_backend: &B,
|
||||
_base: &Block::Header,
|
||||
_best_target: &Block::Header,
|
||||
_current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom voting rule that guarantees that our vote is always behind the best
|
||||
/// block, in the best case exactly one block behind it.
|
||||
#[derive(Clone)]
|
||||
pub struct BeforeBestBlock;
|
||||
impl<Block, B> VotingRule<Block, B> for BeforeBestBlock where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
_backend: &B,
|
||||
_base: &Block::Header,
|
||||
best_target: &Block::Header,
|
||||
current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
if current_target.number().is_zero() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if current_target.number() == best_target.number() {
|
||||
return Some((
|
||||
current_target.parent_hash().clone(),
|
||||
*current_target.number() - One::one(),
|
||||
));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A custom voting rule that limits votes towards 3/4 of the unfinalized chain,
|
||||
/// using the given `base` and `best_target` to figure where the 3/4 target
|
||||
/// should fall.
|
||||
pub struct ThreeQuartersOfTheUnfinalizedChain;
|
||||
|
||||
impl<Block, B> VotingRule<Block, B> for ThreeQuartersOfTheUnfinalizedChain where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
backend: &B,
|
||||
base: &Block::Header,
|
||||
best_target: &Block::Header,
|
||||
current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
// target a vote towards 3/4 of the unfinalized chain (rounding up)
|
||||
let target_number = {
|
||||
let two = NumberFor::<Block>::one() + One::one();
|
||||
let three = two + One::one();
|
||||
let four = three + One::one();
|
||||
|
||||
let diff = *best_target.number() - *base.number();
|
||||
let diff = ((diff * three) + two) / four;
|
||||
|
||||
*base.number() + diff
|
||||
};
|
||||
|
||||
// our current target is already lower than this rule would restrict
|
||||
if target_number >= *current_target.number() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut target_header = current_target.clone();
|
||||
let mut target_hash = current_target.hash();
|
||||
|
||||
// walk backwards until we find the target block
|
||||
loop {
|
||||
if *target_header.number() < target_number {
|
||||
unreachable!(
|
||||
"we are traversing backwards from a known block; \
|
||||
blocks are stored contiguously; \
|
||||
qed"
|
||||
);
|
||||
}
|
||||
if *target_header.number() == target_number {
|
||||
return Some((target_hash, target_number));
|
||||
}
|
||||
|
||||
target_hash = *target_header.parent_hash();
|
||||
target_header = backend.header(BlockId::Hash(target_hash)).ok()?
|
||||
.expect("Header known to exist due to the existence of one of its descendents; qed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct VotingRules<Block, B> {
|
||||
rules: Arc<Vec<Box<dyn VotingRule<Block, B>>>>,
|
||||
}
|
||||
|
||||
impl<B, Block> Clone for VotingRules<B, Block> {
|
||||
fn clone(&self) -> Self {
|
||||
VotingRules {
|
||||
rules: self.rules.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B> VotingRule<Block, B> for VotingRules<Block, B> where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
backend: &B,
|
||||
base: &Block::Header,
|
||||
best_target: &Block::Header,
|
||||
current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
let restricted_target = self.rules.iter().fold(
|
||||
current_target.clone(),
|
||||
|current_target, rule| {
|
||||
rule.restrict_vote(
|
||||
backend,
|
||||
base,
|
||||
best_target,
|
||||
¤t_target,
|
||||
)
|
||||
.and_then(|(hash, _)| backend.header(BlockId::Hash(hash)).ok())
|
||||
.and_then(std::convert::identity)
|
||||
.unwrap_or(current_target)
|
||||
},
|
||||
);
|
||||
|
||||
let restricted_hash = restricted_target.hash();
|
||||
|
||||
if restricted_hash != current_target.hash() {
|
||||
Some((restricted_hash, *restricted_target.number()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A builder of a composite voting rule that applies a set of rules to
|
||||
/// progressively restrict the vote.
|
||||
pub struct VotingRulesBuilder<Block, B> {
|
||||
rules: Vec<Box<dyn VotingRule<Block, B>>>,
|
||||
}
|
||||
|
||||
impl<Block, B> Default for VotingRulesBuilder<Block, B> where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn default() -> Self {
|
||||
VotingRulesBuilder::new()
|
||||
.add(BeforeBestBlock)
|
||||
.add(ThreeQuartersOfTheUnfinalizedChain)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B> VotingRulesBuilder<Block, B> where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
/// Return a new voting rule builder using the given backend.
|
||||
pub fn new() -> Self {
|
||||
VotingRulesBuilder {
|
||||
rules: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new voting rule to the builder.
|
||||
pub fn add<R>(mut self, rule: R) -> Self where
|
||||
R: VotingRule<Block, B> + 'static,
|
||||
{
|
||||
self.rules.push(Box::new(rule));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add all given voting rules to the builder.
|
||||
pub fn add_all<I>(mut self, rules: I) -> Self where
|
||||
I: IntoIterator<Item=Box<dyn VotingRule<Block, B>>>,
|
||||
{
|
||||
self.rules.extend(rules);
|
||||
self
|
||||
}
|
||||
|
||||
/// Return a new `VotingRule` that applies all of the previously added
|
||||
/// voting rules in-order.
|
||||
pub fn build(self) -> impl VotingRule<Block, B> + Clone {
|
||||
VotingRules {
|
||||
rules: Arc::new(self.rules),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block, B> VotingRule<Block, B> for Box<dyn VotingRule<Block, B>> where
|
||||
Block: BlockT,
|
||||
B: HeaderBackend<Block>,
|
||||
{
|
||||
fn restrict_vote(
|
||||
&self,
|
||||
backend: &B,
|
||||
base: &Block::Header,
|
||||
best_target: &Block::Header,
|
||||
current_target: &Block::Header,
|
||||
) -> Option<(Block::Hash, NumberFor<Block>)> {
|
||||
(**self).restrict_vote(backend, base, best_target, current_target)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user