feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,794 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Schema for stuff in the aux-db.
use std::fmt::Debug;
use codec::{Decode, Encode};
use finality_grandpa::round::State as RoundState;
use log::{info, warn};
use fork_tree::ForkTree;
use pezsc_client_api::backend::AuxStore;
use pezsp_blockchain::{Error as ClientError, Result as ClientResult};
use pezsp_consensus_grandpa::{AuthorityList, RoundNumber, SetId};
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
use crate::{
authorities::{
AuthoritySet, AuthoritySetChanges, DelayKind, PendingChange, SharedAuthoritySet,
},
environment::{
CompletedRound, CompletedRounds, CurrentRounds, HasVoted, SharedVoterSetState,
VoterSetState,
},
GrandpaJustification, NewAuthoritySet, LOG_TARGET,
};
const VERSION_KEY: &[u8] = b"grandpa_schema_version";
const SET_STATE_KEY: &[u8] = b"grandpa_completed_round";
const CONCLUDED_ROUNDS: &[u8] = b"grandpa_concluded_rounds";
const AUTHORITY_SET_KEY: &[u8] = b"grandpa_voters";
const BEST_JUSTIFICATION: &[u8] = b"grandpa_best_justification";
const CURRENT_VERSION: u32 = 3;
/// 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: LOG_TARGET, "Error migrating pending authority set change: {}", err);
warn!(target: LOG_TARGET, "Node is in a potentially inconsistent state.");
}
}
let authority_set = AuthoritySet::new(
self.current_authorities,
self.set_id,
pending_standard_changes,
Vec::new(),
AuthoritySetChanges::empty(),
);
authority_set.expect("current_authorities is non-empty and weights are non-zero; qed.")
}
}
impl<H, N> Into<AuthoritySet<H, N>> for V2AuthoritySet<H, N>
where
H: Clone + Debug + PartialEq,
N: Clone + Debug + Ord,
{
fn into(self) -> AuthoritySet<H, N> {
AuthoritySet::new(
self.current_authorities,
self.set_id,
self.pending_standard_changes,
self.pending_forced_changes,
AuthoritySetChanges::empty(),
)
.expect("current_authorities is non-empty and weights are non-zero; qed.")
}
}
#[derive(Debug, Clone, Encode, Decode, PartialEq)]
struct V2AuthoritySet<H, N> {
current_authorities: AuthorityList,
set_id: u64,
pending_standard_changes: ForkTree<H, N, PendingChange<H, N>>,
pending_forced_changes: Vec<PendingChange<H, N>>,
}
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)))
.map(Some),
}
}
/// Persistent data kept between runs.
pub(crate) struct PersistentData<Block: BlockT> {
pub(crate) authority_set: SharedAuthoritySet<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.set_id;
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::<Block>::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.set_id;
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::<Block>::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)
}
fn migrate_from_version2<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::<_, V2AuthoritySet<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 set_state = match load_decode::<_, VoterSetState<Block>>(backend, SET_STATE_KEY)? {
Some(state) => state,
None => {
let state = genesis_round();
let base = state.prevote_ghost
.expect("state is for completed round; completed rounds must have a prevote ghost; qed.");
VoterSetState::live(new_set.set_id, &new_set, base)
},
};
return Ok(Some((new_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 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(),
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(),
set_state: set_state.into(),
});
}
},
Some(2) => {
if let Some((new_set, set_state)) =
migrate_from_version2::<Block, _, _>(backend, &make_genesis_round)?
{
return Ok(PersistentData {
authority_set: new_set.into(),
set_state: set_state.into(),
});
}
},
Some(3) => {
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.set_id, &set, base)
},
};
return Ok(PersistentData {
authority_set: set.into(),
set_state: set_state.into(),
});
}
},
Some(other) =>
return Err(ClientError::Backend(format!("Unsupported GRANDPA DB version: {:?}", other))),
}
// genesis.
info!(
target: LOG_TARGET,
"👴 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)
.expect("genesis authorities is non-empty; all weights are non-zero; qed.");
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() })
}
/// 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[..])])
}
}
/// Update the justification for the latest finalized block on-disk.
///
/// We always keep around the justification for the best finalized block and overwrite it
/// as we finalize new blocks, this makes sure that we don't store useless justifications
/// but can always prove finality of the latest block.
pub(crate) fn update_best_justification<Block: BlockT, F, R>(
justification: &GrandpaJustification<Block>,
write_aux: F,
) -> R
where
F: FnOnce(&[(&'static [u8], &[u8])]) -> R,
{
let encoded_justification = justification.encode();
write_aux(&[(BEST_JUSTIFICATION, &encoded_justification[..])])
}
/// Fetch the justification for the latest block finalized by GRANDPA, if any.
pub fn best_justification<B, Block>(
backend: &B,
) -> ClientResult<Option<GrandpaJustification<Block>>>
where
B: AuxStore,
Block: BlockT,
{
load_decode::<_, GrandpaJustification<Block>>(backend, BEST_JUSTIFICATION)
}
/// 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())], &[])
}
/// Write concluded round.
pub(crate) fn write_concluded_round<Block: BlockT, B: AuxStore>(
backend: &B,
round_data: &CompletedRound<Block>,
) -> ClientResult<()> {
let mut key = CONCLUDED_ROUNDS.to_vec();
let round_number = round_data.number;
round_number.using_encoded(|n| key.extend(n));
backend.insert_aux(&[(&key[..], round_data.encode().as_slice())], &[])
}
#[cfg(test)]
pub(crate) fn load_authorities<B: AuxStore, H: Decode, N: Decode + Clone + Ord>(
backend: &B,
) -> Option<AuthoritySet<H, N>> {
load_decode::<_, AuthoritySet<H, N>>(backend, AUTHORITY_SET_KEY).expect("backend error")
}
#[cfg(test)]
mod test {
use super::*;
use pezsp_consensus_grandpa::AuthorityId;
use pezsp_core::{crypto::UncheckedFrom, H256};
use bizinikiwi_test_runtime_client::{self, runtime::Block};
fn dummy_id() -> AuthorityId {
AuthorityId::unchecked_from([1; 32])
}
#[test]
fn load_decode_from_v0_migrates_data_format() {
let client = bizinikiwi_test_runtime_client::new();
let authorities = vec![(dummy_id(), 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::<bizinikiwi_test_runtime_client::runtime::Block, _, _>(
&client,
H256::random(),
0,
|| unreachable!(),
)
.unwrap();
assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
let PersistentData { authority_set, set_state, .. } =
load_persistent::<bizinikiwi_test_runtime_client::runtime::Block, _, _>(
&client,
H256::random(),
0,
|| unreachable!(),
)
.unwrap();
assert_eq!(
*authority_set.inner(),
AuthoritySet::new(
authorities.clone(),
set_id,
ForkTree::new(),
Vec::new(),
AuthoritySetChanges::empty(),
)
.unwrap(),
);
let mut current_rounds = CurrentRounds::<Block>::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(),
),
current_rounds,
},
);
}
#[test]
fn load_decode_from_v1_migrates_data_format() {
let client = bizinikiwi_test_runtime_client::new();
let authorities = vec![(dummy_id(), 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>::new(
authorities.clone(),
set_id,
ForkTree::new(),
Vec::new(),
AuthoritySetChanges::empty(),
)
.unwrap();
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::<bizinikiwi_test_runtime_client::runtime::Block, _, _>(
&client,
H256::random(),
0,
|| unreachable!(),
)
.unwrap();
assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
let PersistentData { authority_set, set_state, .. } =
load_persistent::<bizinikiwi_test_runtime_client::runtime::Block, _, _>(
&client,
H256::random(),
0,
|| unreachable!(),
)
.unwrap();
assert_eq!(
*authority_set.inner(),
AuthoritySet::new(
authorities.clone(),
set_id,
ForkTree::new(),
Vec::new(),
AuthoritySetChanges::empty(),
)
.unwrap(),
);
let mut current_rounds = CurrentRounds::<Block>::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(),
),
current_rounds,
},
);
}
#[test]
fn load_decode_from_v2_migrates_data_format() {
let client = bizinikiwi_test_runtime_client::new();
let authorities = vec![(dummy_id(), 100)];
let set_id = 3;
{
let authority_set = V2AuthoritySet::<H256, u64> {
current_authorities: authorities.clone(),
set_id,
pending_standard_changes: ForkTree::new(),
pending_forced_changes: Vec::new(),
};
let genesis_state = (H256::random(), 32);
let voter_set_state: VoterSetState<bizinikiwi_test_runtime_client::runtime::Block> =
VoterSetState::live(
set_id,
&authority_set.clone().into(), // Note the conversion!
genesis_state,
);
client
.insert_aux(
&[
(AUTHORITY_SET_KEY, authority_set.encode().as_slice()),
(SET_STATE_KEY, voter_set_state.encode().as_slice()),
(VERSION_KEY, 2u32.encode().as_slice()),
],
&[],
)
.unwrap();
}
assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(2));
// should perform the migration
load_persistent::<bizinikiwi_test_runtime_client::runtime::Block, _, _>(
&client,
H256::random(),
0,
|| unreachable!(),
)
.unwrap();
assert_eq!(load_decode::<_, u32>(&client, VERSION_KEY).unwrap(), Some(3));
let PersistentData { authority_set, .. } = load_persistent::<
bizinikiwi_test_runtime_client::runtime::Block,
_,
_,
>(
&client, H256::random(), 0, || unreachable!()
)
.unwrap();
assert_eq!(
*authority_set.inner(),
AuthoritySet::new(
authorities.clone(),
set_id,
ForkTree::new(),
Vec::new(),
AuthoritySetChanges::empty(),
)
.unwrap(),
);
}
#[test]
fn write_read_concluded_rounds() {
let client = bizinikiwi_test_runtime_client::new();
let hash = H256::random();
let round_state = RoundState::genesis((hash, 0));
let completed_round = CompletedRound::<bizinikiwi_test_runtime_client::runtime::Block> {
number: 42,
state: round_state.clone(),
base: round_state.prevote_ghost.unwrap(),
votes: vec![],
};
assert!(write_concluded_round(&client, &completed_round).is_ok());
let round_number = completed_round.number;
let mut key = CONCLUDED_ROUNDS.to_vec();
round_number.using_encoded(|n| key.extend(n));
assert_eq!(
load_decode::<_, CompletedRound::<bizinikiwi_test_runtime_client::runtime::Block>>(
&client, &key
)
.unwrap(),
Some(completed_round),
);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,119 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Periodic rebroadcast of neighbor packets.
use futures::{future::FutureExt as _, prelude::*, ready, stream::Stream};
use futures_timer::Delay;
use log::debug;
use std::{
pin::Pin,
task::{Context, Poll},
time::Duration,
};
use pezsc_network_types::PeerId;
use pezsc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
use super::gossip::{GossipMessage, NeighborPacket};
use crate::LOG_TARGET;
/// A sender used to send neighbor packets to a background job.
#[derive(Clone)]
pub(super) struct NeighborPacketSender<B: BlockT>(
TracingUnboundedSender<(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<pezsc_network_types::PeerId>,
neighbor_packet: NeighborPacket<NumberFor<B>>,
) {
if let Err(err) = self.0.unbounded_send((who, neighbor_packet)) {
debug!(target: LOG_TARGET, "Failed to send neighbor packet: {:?}", err);
}
}
}
/// NeighborPacketWorker is listening on a channel for new neighbor packets being produced by
/// components within `finality-grandpa` and forwards those packets to the underlying
/// `NetworkEngine` through the `NetworkBridge` that it is being polled by (see `Stream`
/// implementation). Periodically it sends out the last packet in cases where no new ones arrive.
pub(super) struct NeighborPacketWorker<B: BlockT> {
last: Option<(Vec<PeerId>, NeighborPacket<NumberFor<B>>)>,
rebroadcast_period: Duration,
delay: Delay,
rx: TracingUnboundedReceiver<(Vec<PeerId>, NeighborPacket<NumberFor<B>>)>,
}
impl<B: BlockT> Unpin for NeighborPacketWorker<B> {}
impl<B: BlockT> NeighborPacketWorker<B> {
pub(super) fn new(rebroadcast_period: Duration) -> (Self, NeighborPacketSender<B>) {
let (tx, rx) = tracing_unbounded::<(Vec<PeerId>, NeighborPacket<NumberFor<B>>)>(
"mpsc_grandpa_neighbor_packet_worker",
100_000,
);
let delay = Delay::new(rebroadcast_period);
(
NeighborPacketWorker { last: None, rebroadcast_period, delay, rx },
NeighborPacketSender(tx),
)
}
}
impl<B: BlockT> Stream for NeighborPacketWorker<B> {
type Item = (Vec<PeerId>, GossipMessage<B>);
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
let this = &mut *self;
match this.rx.poll_next_unpin(cx) {
Poll::Ready(None) => return Poll::Ready(None),
Poll::Ready(Some((to, packet))) => {
this.delay.reset(this.rebroadcast_period);
this.last = Some((to.clone(), packet.clone()));
return Poll::Ready(Some((to, GossipMessage::<B>::from(packet))));
},
// Don't return yet, maybe the timer fired.
Poll::Pending => {},
};
ready!(this.delay.poll_unpin(cx));
// Getting this far here implies that the timer fired.
this.delay.reset(this.rebroadcast_period);
// Make sure the underlying task is scheduled for wake-up.
//
// Note: In case poll_unpin is called after the reset delay fires again, this
// will drop one tick. Deemed as very unlikely and also not critical.
while this.delay.poll_unpin(cx).is_ready() {}
if let Some((ref to, ref packet)) = this.last {
return Poll::Ready(Some((to.clone(), GossipMessage::<B>::from(packet.clone()))));
}
Poll::Pending
}
}
@@ -0,0 +1,720 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests for the communication portion of the GRANDPA crate.
use super::{
gossip::{self, GossipValidator},
Round, SetId, VoterSet,
};
use crate::{communication::grandpa_protocol_name, environment::SharedVoterSetState};
use codec::{DecodeAll, Encode};
use futures::prelude::*;
use pezsc_network::{
config::{MultiaddrWithPeerId, Role},
event::Event as NetworkEvent,
service::traits::{Direction, MessageSink, NotificationEvent, NotificationService},
types::ProtocolName,
Multiaddr, NetworkBlock, NetworkEventStream, NetworkPeers, NetworkSyncForkRequest,
ReputationChange,
};
use pezsc_network_common::role::{ObservedRole, Roles};
use pezsc_network_gossip::Validator;
use pezsc_network_sync::{SyncEvent as SyncStreamEvent, SyncEventStream};
use pezsc_network_test::{Block, Hash};
use pezsc_network_types::PeerId;
use pezsc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
use pezsp_consensus_grandpa::AuthorityList;
use pezsp_keyring::Ed25519Keyring;
use pezsp_runtime::traits::NumberFor;
use std::{collections::HashSet, pin::Pin, sync::Arc, task::Poll};
#[derive(Debug)]
pub(crate) enum Event {
WriteNotification(PeerId, Vec<u8>),
Report(PeerId, ReputationChange),
}
#[derive(Clone)]
pub(crate) struct TestNetwork {
sender: TracingUnboundedSender<Event>,
}
#[async_trait::async_trait]
impl NetworkPeers for TestNetwork {
fn set_authorized_peers(&self, _peers: HashSet<PeerId>) {
unimplemented!();
}
fn set_authorized_only(&self, _reserved_only: bool) {
unimplemented!();
}
fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) {
unimplemented!();
}
fn report_peer(&self, peer_id: PeerId, cost_benefit: ReputationChange) {
let _ = self.sender.unbounded_send(Event::Report(peer_id, cost_benefit));
}
fn peer_reputation(&self, _peer_id: &PeerId) -> i32 {
unimplemented!()
}
fn disconnect_peer(&self, _peer_id: PeerId, _protocol: ProtocolName) {}
fn accept_unreserved_peers(&self) {
unimplemented!();
}
fn deny_unreserved_peers(&self) {
unimplemented!();
}
fn add_reserved_peer(&self, _peer: MultiaddrWithPeerId) -> Result<(), String> {
unimplemented!();
}
fn remove_reserved_peer(&self, _peer_id: PeerId) {
unimplemented!();
}
fn set_reserved_peers(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn add_peers_to_reserved_set(
&self,
_protocol: ProtocolName,
_peers: HashSet<Multiaddr>,
) -> Result<(), String> {
unimplemented!();
}
fn remove_peers_from_reserved_set(
&self,
_protocol: ProtocolName,
_peers: Vec<PeerId>,
) -> Result<(), String> {
unimplemented!();
}
fn sync_num_connected(&self) -> usize {
unimplemented!();
}
fn peer_role(&self, _peer_id: PeerId, handshake: Vec<u8>) -> Option<ObservedRole> {
Roles::decode_all(&mut &handshake[..])
.ok()
.and_then(|role| Some(ObservedRole::from(role)))
}
async fn reserved_peers(&self) -> Result<Vec<PeerId>, ()> {
unimplemented!();
}
}
impl NetworkEventStream for TestNetwork {
fn event_stream(
&self,
_name: &'static str,
) -> Pin<Box<dyn Stream<Item = NetworkEvent> + Send>> {
futures::stream::pending().boxed()
}
}
impl NetworkBlock<Hash, NumberFor<Block>> for TestNetwork {
fn announce_block(&self, _: Hash, _data: Option<Vec<u8>>) {
unimplemented!();
}
fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor<Block>) {
unimplemented!();
}
}
impl NetworkSyncForkRequest<Hash, NumberFor<Block>> for TestNetwork {
fn set_sync_fork_request(&self, _peers: Vec<PeerId>, _hash: Hash, _number: NumberFor<Block>) {}
}
impl pezsc_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: &PeerId, data: Vec<u8>) {
let _ = self.sender.unbounded_send(Event::WriteNotification(*who, data));
}
fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {}
}
#[derive(Clone)]
pub(crate) struct TestSync;
impl SyncEventStream for TestSync {
fn event_stream(
&self,
_name: &'static str,
) -> Pin<Box<dyn Stream<Item = SyncStreamEvent> + Send>> {
Box::pin(futures::stream::pending())
}
}
impl NetworkBlock<Hash, NumberFor<Block>> for TestSync {
fn announce_block(&self, _hash: Hash, _data: Option<Vec<u8>>) {
unimplemented!();
}
fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor<Block>) {
unimplemented!();
}
}
impl NetworkSyncForkRequest<Hash, NumberFor<Block>> for TestSync {
fn set_sync_fork_request(&self, _peers: Vec<PeerId>, _hash: Hash, _number: NumberFor<Block>) {}
}
#[derive(Debug)]
pub(crate) struct TestNotificationService {
sender: TracingUnboundedSender<Event>,
rx: TracingUnboundedReceiver<NotificationEvent>,
}
#[async_trait::async_trait]
impl NotificationService for TestNotificationService {
/// Instruct `Notifications` to open a new substream for `peer`.
async fn open_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Instruct `Notifications` to close substream for `peer`.
async fn close_substream(&mut self, _peer: PeerId) -> Result<(), ()> {
unimplemented!();
}
/// Send synchronous `notification` to `peer`.
fn send_sync_notification(&mut self, peer: &PeerId, notification: Vec<u8>) {
let _ = self.sender.unbounded_send(Event::WriteNotification(*peer, notification));
}
/// Send asynchronous `notification` to `peer`, allowing sender to exercise backpressure.
async fn send_async_notification(
&mut self,
_peer: &PeerId,
_notification: Vec<u8>,
) -> Result<(), pezsc_network::error::Error> {
unimplemented!();
}
/// Set handshake for the notification protocol replacing the old handshake.
async fn set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
fn try_set_handshake(&mut self, _handshake: Vec<u8>) -> Result<(), ()> {
unimplemented!();
}
/// Get next event from the `Notifications` event stream.
async fn next_event(&mut self) -> Option<NotificationEvent> {
self.rx.next().await
}
fn clone(&mut self) -> Result<Box<dyn NotificationService>, ()> {
unimplemented!();
}
fn protocol(&self) -> &ProtocolName {
unimplemented!();
}
fn message_sink(&self, _peer: &PeerId) -> Option<Box<dyn MessageSink>> {
unimplemented!();
}
}
pub(crate) struct Tester {
pub(crate) net_handle: super::NetworkBridge<Block, TestNetwork, TestSync>,
gossip_validator: Arc<GossipValidator<Block>>,
pub(crate) events: TracingUnboundedReceiver<Event>,
pub(crate) notification_tx: TracingUnboundedSender<NotificationEvent>,
}
impl Tester {
fn filter_network_events<F>(self, mut pred: F) -> impl Future<Output = Self>
where
F: FnMut(Event) -> bool,
{
let mut s = Some(self);
futures::future::poll_fn(move |cx| loop {
match Stream::poll_next(Pin::new(&mut s.as_mut().unwrap().events), cx) {
Poll::Ready(None) => panic!("concluded early"),
Poll::Ready(Some(item)) =>
if pred(item) {
return Poll::Ready(s.take().unwrap());
},
Poll::Pending => return Poll::Pending,
}
})
}
pub(crate) fn trigger_gossip_validator_reputation_change(&self, p: &PeerId) {
self.gossip_validator.validate(
&mut crate::communication::tests::NoopContext,
p,
&vec![1, 2, 3],
);
}
}
// some random config (not really needed)
fn config() -> crate::Config {
crate::Config {
gossip_duration: std::time::Duration::from_millis(10),
justification_generation_period: 256,
keystore: None,
name: None,
local_role: Role::Authority,
observer_enabled: true,
telemetry: None,
protocol_name: grandpa_protocol_name::NAME.into(),
}
}
// dummy voter set state
fn voter_set_state() -> SharedVoterSetState<Block> {
use crate::{authorities::AuthoritySet, environment::VoterSetState};
use finality_grandpa::round::State as RoundState;
use pezsp_consensus_grandpa::AuthorityId;
use pezsp_core::{crypto::ByteArray, H256};
let state = RoundState::genesis((H256::zero(), 0));
let base = state.prevote_ghost.unwrap();
let voters = vec![(AuthorityId::from_slice(&[1; 32]).unwrap(), 1)];
let voters = AuthoritySet::genesis(voters).unwrap();
let set_state = VoterSetState::live(0, &voters, base);
set_state.into()
}
// needs to run in a tokio runtime.
pub(crate) fn make_test_network() -> (impl Future<Output = Tester>, TestNetwork) {
let (tx, rx) = tracing_unbounded("test", 100_000);
let (notification_tx, notification_rx) = tracing_unbounded("test-notification", 100_000);
let notification_service = TestNotificationService { rx: notification_rx, sender: tx.clone() };
let net = TestNetwork { sender: tx };
let sync = TestSync {};
let bridge = super::NetworkBridge::new(
net.clone(),
sync,
Box::new(notification_service),
config(),
voter_set_state(),
None,
None,
);
(
futures::future::ready(Tester {
gossip_validator: bridge.validator.clone(),
net_handle: bridge,
events: rx,
notification_tx,
}),
net,
)
}
fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList {
keys.iter().map(|&key| key.public().into()).map(|id| (id, 1)).collect()
}
struct NoopContext;
impl pezsc_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, _: &PeerId, _: Vec<u8>) {}
fn send_topic(&mut self, _: &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(VoterSet::new(public.iter().cloned()).unwrap());
let round = 1;
let set_id = 1;
let commit = {
let target_hash: Hash = [1; 32].into();
let target_number = 500;
let precommit = finality_grandpa::Precommit { target_hash, target_number };
let payload = pezsp_consensus_grandpa::localized_payload(
round,
set_id,
&finality_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 = pezsp_consensus_grandpa::AuthoritySignature::from(key.sign(&payload[..]));
auth_data.push((signature, public[i].0.clone()))
}
finality_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 = PeerId::random();
let global_topic = super::global_topic::<Block>(set_id);
let test = make_test_network()
.0
.then(move |tester| {
// register a peer.
tester.gossip_validator.new_peer(&mut NoopContext, &id, ObservedRole::Full);
future::ready((tester, id))
})
.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();
let network_bridge = tester.net_handle.clone();
// `NetworkBridge` will be operational as soon as it's created and it's
// waiting for events from the network. Send it events that inform that
// a notification stream was opened and that a notification was received.
//
// Since each protocol has its own notification stream, events need not be filtered.
let sender_id = id;
let send_message = async move {
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationStreamOpened {
peer: sender_id,
direction: Direction::Inbound,
negotiated_fallback: None,
handshake: Roles::FULL.encode(),
},
);
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationReceived {
peer: sender_id,
notification: commit_to_send.clone(),
},
);
// Add a random peer which will be the recipient of this message
let receiver_id = PeerId::random();
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationStreamOpened {
peer: receiver_id,
direction: Direction::Inbound,
negotiated_fallback: None,
handshake: Roles::FULL.encode(),
},
);
// Announce its local set being on the current set id through a neighbor
// packet, otherwise it won't be eligible to receive the commit
let _ = {
let update = gossip::VersionedNeighborPacket::V1(gossip::NeighborPacket {
round: Round(round),
set_id: SetId(set_id),
commit_finalized_height: 1,
});
let msg = gossip::GossipMessage::<Block>::Neighbor(update);
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationReceived {
peer: receiver_id,
notification: msg.encode(),
},
);
};
tester
}
.boxed();
// 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() {
finality_grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
callback.run(finality_grandpa::voter::CommitProcessingOutcome::good());
},
_ => panic!("commit expected"),
});
// once the message is sent and commit is "handled" we should have
// a repropagation event coming from the network.
let fut = future::join(send_message, handle_commit)
.then(move |(tester, ())| {
tester.filter_network_events(move |event| match event {
Event::WriteNotification(_, data) => data == encoded_commit,
_ => false,
})
})
.map(|_| ());
// Poll both the future sending and handling the commit, as well as the underlying
// NetworkBridge. Complete once the former completes.
future::select(fut, network_bridge)
});
futures::executor::block_on(test);
}
#[test]
fn bad_commit_leads_to_report() {
pezsp_tracing::try_init_simple();
let private = [Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
let public = make_ids(&private[..]);
let voter_set = Arc::new(VoterSet::new(public.iter().cloned()).unwrap());
let round = 1;
let set_id = 1;
let commit = {
let target_hash: Hash = [1; 32].into();
let target_number = 500;
let precommit = finality_grandpa::Precommit { target_hash, target_number };
let payload = pezsp_consensus_grandpa::localized_payload(
round,
set_id,
&finality_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 = pezsp_consensus_grandpa::AuthoritySignature::from(key.sign(&payload[..]));
auth_data.push((signature, public[i].0.clone()))
}
finality_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 = PeerId::random();
let global_topic = super::global_topic::<Block>(set_id);
let test = make_test_network()
.0
.map(move |tester| {
// register a peer.
tester.gossip_validator.new_peer(&mut NoopContext, &id, ObservedRole::Full);
(tester, id)
})
.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();
let network_bridge = tester.net_handle.clone();
// `NetworkBridge` will be operational as soon as it's created and it's
// waiting for events from the network. Send it events that inform that
// a notification stream was opened and that a notification was received.
//
// Since each protocol has its own notification stream, events need not be filtered.
let sender_id = id;
let send_message = async move {
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationStreamOpened {
peer: sender_id,
direction: Direction::Inbound,
negotiated_fallback: None,
handshake: Roles::FULL.encode(),
},
);
let _ = tester.notification_tx.unbounded_send(
NotificationEvent::NotificationReceived {
peer: sender_id,
notification: commit_to_send.clone(),
},
);
tester
}
.boxed();
// when the commit comes in, we'll tell the callback it was bad.
let handle_commit = commits_in.into_future().map(|(item, _)| match item.unwrap() {
finality_grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
callback.run(finality_grandpa::voter::CommitProcessingOutcome::bad());
},
_ => panic!("commit expected"),
});
// once the message is sent and commit is "handled" we should have
// a report event coming from the network.
let fut = future::join(send_message, handle_commit)
.then(move |(tester, ())| {
tester.filter_network_events(move |event| match event {
Event::Report(who, cost_benefit) =>
who == id && cost_benefit == super::cost::INVALID_COMMIT,
_ => false,
})
})
.map(|_| ());
// Poll both the future sending and handling the commit, as well as the underlying
// NetworkBridge. Complete once the former completes.
future::select(fut, network_bridge)
});
futures::executor::block_on(test);
}
#[test]
fn peer_with_higher_view_leads_to_catch_up_request() {
let id = PeerId::random();
let (tester, mut net) = make_test_network();
let test = tester
.map(move |tester| {
// register a peer with authority role.
tester.gossip_validator.new_peer(&mut NoopContext, &id, ObservedRole::Authority);
(tester, id)
})
.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 {
pezsc_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::WriteNotification(peer, message) => {
assert_eq!(peer, id);
assert_eq!(
message,
gossip::GossipMessage::<Block>::CatchUpRequest(
gossip::CatchUpRequestMessage { set_id: SetId(0), round: Round(9) }
)
.encode(),
);
true
},
_ => false,
})
.map(|_| ())
});
futures::executor::block_on(test);
}
fn local_chain_spec() -> Box<dyn pezsc_chain_spec::ChainSpec> {
let chain_spec =
pezsc_chain_spec::GenericChainSpec::<pezsc_chain_spec::NoExtension, ()>::from_json_bytes(
&include_bytes!("../../../../chain-spec/res/chain_spec.json")[..],
)
.unwrap();
pezsc_chain_spec::ChainSpec::cloned_box(&chain_spec)
}
#[test]
fn grandpa_protocol_name() {
let chain_spec = local_chain_spec();
// Create protocol name using random genesis hash.
let genesis_hash = pezsp_core::H256::random();
let expected = format!("/{}/grandpa/1", array_bytes::bytes2hex("", genesis_hash));
let proto_name = grandpa_protocol_name::standard_name(&genesis_hash, &chain_spec);
assert_eq!(proto_name.to_string(), expected);
// Create protocol name using hardcoded genesis hash. Verify exact representation.
let genesis_hash = [
53, 79, 112, 97, 119, 217, 39, 202, 147, 138, 225, 38, 88, 182, 215, 185, 110, 88, 8, 53,
125, 210, 158, 151, 50, 113, 102, 59, 245, 199, 221, 240,
];
let expected =
"/354f706177d927ca938ae12658b6d7b96e5808357dd29e973271663bf5c7ddf0/grandpa/1".to_string();
let proto_name = grandpa_protocol_name::standard_name(&genesis_hash, &chain_spec);
assert_eq!(proto_name.to_string(), expected);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,598 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://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 log::{trace, warn};
use std::sync::Arc;
use codec::{Decode, Encode};
use pezsc_client_api::backend::Backend;
use pezsp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
use pezsp_consensus_grandpa::GRANDPA_ENGINE_ID;
use pezsp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, One},
};
use crate::{
authorities::{AuthoritySetChangeId, AuthoritySetChanges},
best_justification,
justification::GrandpaJustification,
SharedAuthoritySet, LOG_TARGET,
};
const MAX_UNKNOWN_HEADERS: usize = 100_000;
/// Finality proof provider for serving network requests.
#[derive(Clone)]
pub struct FinalityProofProvider<BE, Block: BlockT> {
backend: Arc<BE>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
}
impl<B, Block> FinalityProofProvider<B, Block>
where
Block: BlockT,
B: Backend<Block>,
{
/// Create new finality proof provider using:
///
/// - backend for accessing blockchain data;
/// - authority_provider for calling and proving runtime methods.
/// - shared_authority_set for accessing authority set data
pub fn new(
backend: Arc<B>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
) -> Self {
FinalityProofProvider { backend, shared_authority_set }
}
/// Create new finality proof provider for the service using:
///
/// - backend for accessing blockchain data;
/// - storage_provider, which is generally a client.
/// - shared_authority_set for accessing authority set data
pub fn new_for_service(
backend: Arc<B>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
) -> Arc<Self> {
Arc::new(Self::new(backend, shared_authority_set))
}
}
impl<B, Block> FinalityProofProvider<B, Block>
where
Block: BlockT,
B: Backend<Block>,
{
/// Prove finality for the given block number by returning a Justification for the last block of
/// the authority set in bytes.
pub fn prove_finality(
&self,
block: NumberFor<Block>,
) -> Result<Option<Vec<u8>>, FinalityProofError> {
Ok(self.prove_finality_proof(block, true)?.map(|proof| proof.encode()))
}
/// Prove finality for the given block number by returning a Justification for the last block of
/// the authority set.
///
/// If `collect_unknown_headers` is true, the finality proof will include all headers from the
/// requested block until the block the justification refers to.
pub fn prove_finality_proof(
&self,
block: NumberFor<Block>,
collect_unknown_headers: bool,
) -> Result<Option<FinalityProof<Block::Header>>, FinalityProofError> {
let authority_set_changes = if let Some(changes) = self
.shared_authority_set
.as_ref()
.map(SharedAuthoritySet::authority_set_changes)
{
changes
} else {
return Ok(None);
};
prove_finality(&*self.backend, authority_set_changes, block, collect_unknown_headers)
}
}
/// 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;
#[derive(Debug, PartialEq, Encode, Decode, Clone)]
pub struct FinalityProof<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 (B; F] that we believe are unknown to the caller. Ordered.
pub unknown_headers: Vec<Header>,
}
/// Errors occurring when trying to prove finality
#[derive(Debug, thiserror::Error)]
pub enum FinalityProofError {
/// The requested block has not yet been finalized.
#[error("Block not yet finalized")]
BlockNotYetFinalized,
/// The requested block is not covered by authority set changes. Likely this means the block is
/// in the latest authority set, and the subscription API is more appropriate.
#[error("Block not covered by authority set changes")]
BlockNotInAuthoritySetChanges,
/// Errors originating from the client.
#[error(transparent)]
Client(#[from] pezsp_blockchain::Error),
}
/// Prove finality for the given block number by returning a justification for the last block of
/// the authority set of which the given block is part of, or a justification for the latest
/// finalized block if the given block is part of the current authority set.
///
/// If `collect_unknown_headers` is true, the finality proof will include all headers from the
/// requested block until the block the justification refers to.
fn prove_finality<Block, B>(
backend: &B,
authority_set_changes: AuthoritySetChanges<NumberFor<Block>>,
block: NumberFor<Block>,
collect_unknown_headers: bool,
) -> Result<Option<FinalityProof<Block::Header>>, FinalityProofError>
where
Block: BlockT,
B: Backend<Block>,
{
// Early-return if we are sure that there are no blocks finalized that cover the requested
// block.
let finalized_number = backend.blockchain().info().finalized_number;
if finalized_number < block {
let err = format!(
"Requested finality proof for descendant of #{} while we only have finalized #{}.",
block, finalized_number,
);
trace!(target: LOG_TARGET, "{}", &err);
return Err(FinalityProofError::BlockNotYetFinalized);
}
let (justification, just_block) = match authority_set_changes.get_set_id(block) {
AuthoritySetChangeId::Latest => {
if let Some(justification) = best_justification(backend)?
.map(|j: GrandpaJustification<Block>| (j.encode(), j.target().0))
{
justification
} else {
trace!(
target: LOG_TARGET,
"No justification found for the latest finalized block. \
Returning empty proof.",
);
return Ok(None);
}
},
AuthoritySetChangeId::Set(_, last_block_for_set) => {
let last_block_for_set_id = backend
.blockchain()
.expect_block_hash_from_id(&BlockId::Number(last_block_for_set))?;
let justification = if let Some(grandpa_justification) = backend
.blockchain()
.justifications(last_block_for_set_id)?
.and_then(|justifications| justifications.into_justification(GRANDPA_ENGINE_ID))
{
grandpa_justification
} else {
trace!(
target: LOG_TARGET,
"No justification found when making finality proof for {}. \
Returning empty proof.",
block,
);
return Ok(None);
};
(justification, last_block_for_set)
},
AuthoritySetChangeId::Unknown => {
warn!(
target: LOG_TARGET,
"AuthoritySetChanges does not cover the requested block #{} due to missing data. \
You need to resync to populate AuthoritySetChanges properly.",
block,
);
return Err(FinalityProofError::BlockNotInAuthoritySetChanges);
},
};
let mut headers = Vec::new();
if collect_unknown_headers {
// Collect all headers from the requested block until the last block of the set
let mut current = block + One::one();
loop {
if current > just_block || headers.len() >= MAX_UNKNOWN_HEADERS {
break;
}
let hash = backend.blockchain().expect_block_hash_from_id(&BlockId::Number(current))?;
headers.push(backend.blockchain().expect_header(hash)?);
current += One::one();
}
};
Ok(Some(FinalityProof {
block: backend.blockchain().expect_block_hash_from_id(&BlockId::Number(just_block))?,
justification,
unknown_headers: headers,
}))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{authorities::AuthoritySetChanges, BlockNumberOps, ClientError, SetId};
use futures::executor::block_on;
use pezsc_block_builder::BlockBuilderBuilder;
use pezsc_client_api::{apply_aux, LockImportRun};
use pezsp_consensus::BlockOrigin;
use pezsp_consensus_grandpa::GRANDPA_ENGINE_ID as ID;
use pezsp_core::crypto::UncheckedFrom;
use pezsp_keyring::Ed25519Keyring;
use bizinikiwi_test_runtime_client::{
runtime::{Block, Header, H256},
Backend as TestBackend, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt,
TestClient, TestClientBuilder, TestClientBuilderExt,
};
/// 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.
fn check_finality_proof<Block: BlockT>(
current_set_id: SetId,
current_authorities: pezsp_consensus_grandpa::AuthorityList,
remote_proof: Vec<u8>,
) -> pezsp_blockchain::Result<super::FinalityProof<Block::Header>>
where
NumberFor<Block>: BlockNumberOps,
{
let proof = super::FinalityProof::<Block::Header>::decode(&mut &remote_proof[..])
.map_err(|_| ClientError::BadJustification("failed to decode finality proof".into()))?;
let justification: GrandpaJustification<Block> =
Decode::decode(&mut &proof.justification[..])
.map_err(|_| ClientError::JustificationDecode)?;
justification.verify(current_set_id, &current_authorities)?;
Ok(proof)
}
pub(crate) type FinalityProof = super::FinalityProof<Header>;
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 test_blockchain(
number_of_blocks: u64,
to_finalize: &[u64],
) -> (Arc<TestClient>, Arc<TestBackend>, Vec<Block>) {
let builder = TestClientBuilder::new();
let backend = builder.backend();
let client = Arc::new(builder.build());
let mut blocks = Vec::new();
for _ in 0..number_of_blocks {
let block = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().best_hash)
.with_parent_block_number(client.chain_info().best_number)
.build()
.unwrap()
.build()
.unwrap()
.block;
block_on(client.import(BlockOrigin::Own, block.clone())).unwrap();
blocks.push(block);
}
for block in to_finalize {
let hash = blocks[*block as usize - 1].hash();
client.finalize_block(hash, None).unwrap();
}
(client, backend, blocks)
}
fn store_best_justification(client: &TestClient, just: &GrandpaJustification<Block>) {
client
.lock_import_and_run(|import_op| {
crate::aux_schema::update_best_justification(just, |insert| {
apply_aux(import_op, insert, &[])
})
})
.unwrap();
}
#[test]
fn finality_proof_fails_if_no_more_last_finalized_blocks() {
let (_, backend, _) = test_blockchain(6, &[4]);
let authority_set_changes = AuthoritySetChanges::empty();
// The last finalized block is 4, so we cannot provide further justifications.
let proof_of_5 = prove_finality(&*backend, authority_set_changes, 5, true);
assert!(matches!(proof_of_5, Err(FinalityProofError::BlockNotYetFinalized)));
}
#[test]
fn finality_proof_is_none_if_no_justification_known() {
let (_, backend, _) = test_blockchain(6, &[4]);
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 4);
// Block 4 is finalized without justification
// => we can't prove finality of 3
let proof_of_3 = prove_finality(&*backend, authority_set_changes, 3, true).unwrap();
assert_eq!(proof_of_3, None);
}
#[test]
fn finality_proof_check_fails_when_proof_decode_fails() {
// When we can't decode proof from Vec<u8>
check_finality_proof::<Block>(
1,
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
vec![42],
)
.unwrap_err();
}
#[test]
fn finality_proof_check_fails_when_proof_is_empty() {
// When decoded proof has zero length
check_finality_proof::<Block>(
1,
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
Vec::<GrandpaJustification<Block>>::new().encode(),
)
.unwrap_err();
}
#[test]
fn finality_proof_check_fails_with_incomplete_justification() {
let (_, _, blocks) = test_blockchain(8, &[4, 5, 8]);
// Create a commit without precommits
let commit = finality_grandpa::Commit {
target_hash: blocks[7].hash(),
target_number: *blocks[7].header().number(),
precommits: Vec::new(),
};
let grandpa_just: GrandpaJustification<Block> =
pezsp_consensus_grandpa::GrandpaJustification::<Header> {
round: 8,
votes_ancestries: Vec::new(),
commit,
}
.into();
let finality_proof = FinalityProof {
block: header(2).hash(),
justification: grandpa_just.encode(),
unknown_headers: Vec::new(),
};
check_finality_proof::<Block>(
1,
vec![(UncheckedFrom::unchecked_from([3u8; 32]), 1u64)],
finality_proof.encode(),
)
.unwrap_err();
}
fn create_commit<S, Id>(
block: Block,
round: u64,
set_id: SetId,
auth: &[Ed25519Keyring],
) -> finality_grandpa::Commit<H256, u64, S, Id>
where
Id: From<pezsp_core::ed25519::Public>,
S: From<pezsp_core::ed25519::Signature>,
{
let mut precommits = Vec::new();
for voter in auth {
let precommit = finality_grandpa::Precommit {
target_hash: block.hash(),
target_number: *block.header().number(),
};
let msg = finality_grandpa::Message::Precommit(precommit.clone());
let encoded = pezsp_consensus_grandpa::localized_payload(round, set_id, &msg);
let signature = voter.sign(&encoded[..]).into();
let signed_precommit = finality_grandpa::SignedPrecommit {
precommit,
signature,
id: voter.public().into(),
};
precommits.push(signed_precommit);
}
finality_grandpa::Commit {
target_hash: block.hash(),
target_number: *block.header().number(),
precommits,
}
}
#[test]
fn finality_proof_check_works_with_correct_justification() {
let (client, _, blocks) = test_blockchain(8, &[4, 5, 8]);
let alice = Ed25519Keyring::Alice;
let set_id = 1;
let round = 8;
let commit = create_commit(blocks[7].clone(), round, set_id, &[alice]);
let grandpa_just = GrandpaJustification::from_commit(&client, round, commit).unwrap();
let finality_proof = FinalityProof {
block: header(2).hash(),
justification: grandpa_just.encode(),
unknown_headers: Vec::new(),
};
assert_eq!(
finality_proof,
check_finality_proof::<Block>(
set_id,
vec![(alice.public().into(), 1u64)],
finality_proof.encode(),
)
.unwrap(),
);
}
#[test]
fn finality_proof_using_authority_set_changes_fails_with_undefined_start() {
let (_, backend, _) = test_blockchain(8, &[4, 5, 8]);
// We have stored the correct block number for the relevant set, but as we are missing the
// block for the preceding set the start is not well-defined.
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(1, 8);
let proof_of_6 = prove_finality(&*backend, authority_set_changes, 6, true);
assert!(matches!(proof_of_6, Err(FinalityProofError::BlockNotInAuthoritySetChanges)));
}
#[test]
fn finality_proof_using_authority_set_changes_works() {
let (client, backend, blocks) = test_blockchain(8, &[4, 5]);
let block7 = &blocks[6];
let block8 = &blocks[7];
let round = 8;
let commit = create_commit(block8.clone(), round, 1, &[Ed25519Keyring::Alice]);
let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap();
client
.finalize_block(block8.hash(), Some((ID, grandpa_just8.encode().clone())))
.unwrap();
// Authority set change at block 8, so the justification stored there will be used in the
// FinalityProof for block 6
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 5);
authority_set_changes.append(1, 8);
let proof_of_6: FinalityProof =
prove_finality(&*backend, authority_set_changes.clone(), 6, true)
.unwrap()
.unwrap();
assert_eq!(
proof_of_6,
FinalityProof {
block: block8.hash(),
justification: grandpa_just8.encode(),
unknown_headers: vec![block7.header().clone(), block8.header().clone()],
},
);
let proof_of_6_without_unknown: FinalityProof =
prove_finality(&*backend, authority_set_changes.clone(), 6, false)
.unwrap()
.unwrap();
assert_eq!(
proof_of_6_without_unknown,
FinalityProof {
block: block8.hash(),
justification: grandpa_just8.encode(),
unknown_headers: vec![],
},
);
}
#[test]
fn finality_proof_in_last_set_fails_without_latest() {
let (_, backend, _) = test_blockchain(8, &[4, 5, 8]);
// No recent authority set change, so we are in the latest set, and we will try to pickup
// the best stored justification, for which there is none in this case.
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 5);
assert!(matches!(prove_finality(&*backend, authority_set_changes, 6, true), Ok(None)));
}
#[test]
fn finality_proof_in_last_set_using_latest_justification_works() {
let (client, backend, blocks) = test_blockchain(8, &[4, 5, 8]);
let block7 = &blocks[6];
let block8 = &blocks[7];
let round = 8;
let commit = create_commit(block8.clone(), round, 1, &[Ed25519Keyring::Alice]);
let grandpa_just8 = GrandpaJustification::from_commit(&client, round, commit).unwrap();
store_best_justification(&client, &grandpa_just8);
// No recent authority set change, so we are in the latest set, and will pickup the best
// stored justification
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 5);
let proof_of_6: FinalityProof =
prove_finality(&*backend, authority_set_changes, 6, true).unwrap().unwrap();
assert_eq!(
proof_of_6,
FinalityProof {
block: block8.hash(),
justification: grandpa_just8.encode(),
unknown_headers: vec![block7.header().clone(), block8.header().clone()],
}
);
}
}
@@ -0,0 +1,854 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
use codec::Decode;
use log::debug;
use parking_lot::Mutex;
use pezsc_client_api::{backend::Backend, utils::is_descendent_of};
use pezsc_consensus::{
shared_data::{SharedDataLocked, SharedDataLockedUpgradable},
BlockCheckParams, BlockImport, BlockImportParams, ImportResult, JustificationImport,
};
use pezsc_telemetry::TelemetryHandle;
use pezsc_utils::mpsc::TracingUnboundedSender;
use pezsp_api::{Core, RuntimeApiInfo};
use pezsp_blockchain::BlockStatus;
use pezsp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain};
use pezsp_consensus_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID};
use pezsp_runtime::{
generic::OpaqueDigestItemId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero},
Justification,
};
use crate::{
authorities::{AuthoritySet, DelayKind, PendingChange, SharedAuthoritySet},
environment,
justification::GrandpaJustification,
notification::GrandpaJustificationSender,
AuthoritySetChanges, ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand,
LOG_TARGET,
};
/// 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<Backend, Block: BlockT, Client, SC> {
inner: Arc<Client>,
justification_import_period: u32,
select_chain: SC,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
send_voter_commands: TracingUnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
authority_set_hard_forks:
Mutex<HashMap<Block::Hash, PendingChange<Block::Hash, NumberFor<Block>>>>,
justification_sender: GrandpaJustificationSender<Block>,
telemetry: Option<TelemetryHandle>,
_phantom: PhantomData<Backend>,
}
impl<Backend, Block: BlockT, Client, SC: Clone> Clone
for GrandpaBlockImport<Backend, Block, Client, SC>
{
fn clone(&self) -> Self {
GrandpaBlockImport {
inner: self.inner.clone(),
justification_import_period: self.justification_import_period,
select_chain: self.select_chain.clone(),
authority_set: self.authority_set.clone(),
send_voter_commands: self.send_voter_commands.clone(),
authority_set_hard_forks: Mutex::new(self.authority_set_hard_forks.lock().clone()),
justification_sender: self.justification_sender.clone(),
telemetry: self.telemetry.clone(),
_phantom: PhantomData,
}
}
}
#[async_trait::async_trait]
impl<BE, Block: BlockT, Client, SC> JustificationImport<Block>
for GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
SC: SelectChain<Block>,
{
type Error = ConsensusError;
async fn on_start(&mut self) -> Vec<(Block::Hash, NumberFor<Block>)> {
let mut out = Vec::new();
let chain_info = self.inner.info();
// request justifications for all pending changes for which change blocks have already been
// imported
let pending_changes: Vec<_> =
self.authority_set.inner().pending_changes().cloned().collect();
for pending_change in 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()),
)
.await
} else {
Ok(pending_change.canon_hash)
};
if let Ok(hash) = effective_block_hash {
if let Ok(Some(header)) = self.inner.header(hash) {
if *header.number() == pending_change.effective_number() {
out.push((header.hash(), *header.number()));
}
}
}
}
}
out
}
async fn import_justification(
&mut self,
hash: Block::Hash,
number: NumberFor<Block>,
justification: Justification,
) -> Result<(), Self::Error> {
// this justification was requested by the sync service, therefore we
// are not sure if it should enact a change or not. it could have been a
// request made as part of initial sync but that means the justification
// wasn't part of the block and was requested asynchronously, probably
// makes sense to log in that case.
GrandpaBlockImport::import_justification(self, hash, number, justification, false, 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<Block: BlockT> {
just_in_case: Option<(
AuthoritySet<Block::Hash, NumberFor<Block>>,
SharedDataLockedUpgradable<AuthoritySet<Block::Hash, NumberFor<Block>>>,
)>,
applied_changes: AppliedChanges<Block::Hash, NumberFor<Block>>,
do_pause: bool,
}
impl<Block: BlockT> PendingSetChanges<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<Block: BlockT> Drop for PendingSetChanges<Block> {
fn drop(&mut self) {
if let Some((old_set, mut authorities)) = self.just_in_case.take() {
*authorities.upgrade() = old_set;
}
}
}
/// Checks the given header for a consensus digest signalling a **standard** scheduled change and
/// extracts it.
pub 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))
}
/// Checks the given header for a consensus digest signalling a **forced** scheduled change and
/// extracts it.
pub 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<BE, Block: BlockT, Client, SC> GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
Client::Api: GrandpaApi<Block>,
for<'a> &'a Client: BlockImport<Block, Error = ConsensusError>,
{
// 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 authority set hard forks
if let Some(change) = self.authority_set_hard_forks.lock().get(&hash) {
return Some(change.clone());
}
// 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(
&self,
block: &mut BlockImportParams<Block>,
hash: Block::Hash,
initial_sync: bool,
) -> Result<PendingSetChanges<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, H, N> {
old: Option<AuthoritySet<H, N>>,
guard: Option<SharedDataLocked<'a, AuthoritySet<H, N>>>,
}
impl<'a, H, N> InnerGuard<'a, H, N> {
fn as_mut(&mut self) -> &mut AuthoritySet<H, N> {
self.guard.as_mut().expect("only taken on deconstruction; qed")
}
fn set_old(&mut self, old: AuthoritySet<H, N>) {
if self.old.is_none() {
// ignore "newer" old changes.
self.old = Some(old);
}
}
fn consume(
mut self,
) -> Option<(AuthoritySet<H, N>, SharedDataLocked<'a, AuthoritySet<H, N>>)> {
self.old
.take()
.map(|old| (old, self.guard.take().expect("only taken on deconstruction; qed")))
}
}
impl<'a, H, N> Drop for InnerGuard<'a, H, N> {
fn drop(&mut self) {
if let (Some(mut guard), Some(old)) = (self.guard.take(), self.old.take()) {
*guard = old;
}
}
}
let number = *(block.header.number());
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_locked()), 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::ClientImport(e.to_string()))?;
}
let applied_changes = {
let forced_change_set = guard
.as_mut()
.apply_forced_changes(
hash,
number,
&is_descendent_of,
initial_sync,
self.telemetry.clone(),
)
.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().finalized_number;
let canon_number = best_finalized_number.min(median_last_finalized_number);
let canon_hash = self.inner.hash(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."
);
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()))))
},
);
}
let just_in_case = just_in_case.map(|(o, i)| (o, i.release_mutex()));
Ok(PendingSetChanges { just_in_case, applied_changes, do_pause })
}
/// Read current set id form a given state.
fn current_set_id(&self, hash: Block::Hash) -> Result<SetId, ConsensusError> {
let runtime_version = self.inner.runtime_api().version(hash).map_err(|e| {
ConsensusError::ClientImport(format!(
"Unable to retrieve current runtime version. {}",
e
))
})?;
if runtime_version
.api_version(&<dyn GrandpaApi<Block>>::ID)
.map_or(false, |v| v < 3)
{
// The new API is not supported in this runtime. Try reading directly from storage.
// This code may be removed once warp sync to an old runtime is no longer needed.
for prefix in ["GrandpaFinality", "Grandpa"] {
let k = [
pezsp_crypto_hashing::twox_128(prefix.as_bytes()),
pezsp_crypto_hashing::twox_128(b"CurrentSetId"),
]
.concat();
if let Ok(Some(id)) =
self.inner.storage(hash, &pezsc_client_api::StorageKey(k.to_vec()))
{
if let Ok(id) = SetId::decode(&mut id.0.as_ref()) {
return Ok(id);
}
}
}
Err(ConsensusError::ClientImport("Unable to retrieve current set id.".into()))
} else {
self.inner
.runtime_api()
.current_set_id(hash)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))
}
}
/// Import whole new state and reset authority set.
async fn import_state(
&self,
mut block: BlockImportParams<Block>,
) -> Result<ImportResult, ConsensusError> {
let hash = block.post_hash();
let number = *block.header.number();
// Force imported state finality.
block.finalized = true;
let import_result = (&*self.inner).import_block(block).await;
match import_result {
Ok(ImportResult::Imported(aux)) => {
// We've just imported a new state. We trust the sync module has verified
// finality proofs and that the state is correct and final.
// So we can read the authority list and set id from the state.
self.authority_set_hard_forks.lock().clear();
let authorities = self
.inner
.runtime_api()
.grandpa_authorities(hash)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
let set_id = self.current_set_id(hash)?;
let authority_set = AuthoritySet::new(
authorities.clone(),
set_id,
fork_tree::ForkTree::new(),
Vec::new(),
AuthoritySetChanges::empty(),
)
.ok_or_else(|| ConsensusError::ClientImport("Invalid authority list".into()))?;
*self.authority_set.inner_locked() = authority_set.clone();
crate::aux_schema::update_authority_set::<Block, _, _>(
&authority_set,
None,
|insert| self.inner.insert_aux(insert, []),
)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
let new_set =
NewAuthoritySet { canon_number: number, canon_hash: hash, set_id, authorities };
let _ = self
.send_voter_commands
.unbounded_send(VoterCommand::ChangeAuthorities(new_set));
Ok(ImportResult::Imported(aux))
},
Ok(r) => Ok(r),
Err(e) => Err(ConsensusError::ClientImport(e.to_string())),
}
}
}
#[async_trait::async_trait]
impl<BE, Block: BlockT, Client, SC> BlockImport<Block> for GrandpaBlockImport<BE, Block, Client, SC>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
Client::Api: GrandpaApi<Block>,
for<'a> &'a Client: BlockImport<Block, Error = ConsensusError>,
SC: Send + Sync,
{
type Error = ConsensusError;
async fn import_block(
&self,
mut block: BlockImportParams<Block>,
) -> Result<ImportResult, Self::Error> {
let hash = block.post_hash();
let number = *block.header.number();
// 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(hash) {
Ok(BlockStatus::InChain) => {
// Strip justifications when re-importing an existing block.
let _justifications = block.justifications.take();
return (&*self.inner).import_block(block).await;
},
Ok(BlockStatus::Unknown) => {},
Err(e) => return Err(ConsensusError::ClientImport(e.to_string())),
}
if block.with_state() {
return self.import_state(block).await;
}
if number <= self.inner.info().finalized_number {
// Importing an old block. Just save justifications and authority set changes
if self.check_new_change(&block.header, hash).is_some() {
if block.justifications.is_none() {
return Err(ConsensusError::ClientImport(
"Justification required when importing \
an old block with authority set change."
.into(),
));
}
let mut authority_set = self.authority_set.inner_locked();
authority_set.authority_set_changes.insert(number);
crate::aux_schema::update_authority_set::<Block, _, _>(
&authority_set,
None,
|insert| {
block
.auxiliary
.extend(insert.iter().map(|(k, v)| (k.to_vec(), Some(v.to_vec()))))
},
);
}
return (&*self.inner).import_block(block).await;
}
// on initial sync we will restrict logging under info to avoid spam.
let initial_sync = block.origin == BlockOrigin::NetworkInitialSync;
let pending_changes = self.make_authorities_changes(&mut block, hash, initial_sync)?;
// we don't want to finalize on `inner.import_block`
let mut justifications = block.justifications.take();
let import_result = (&*self.inner).import_block(block).await;
let mut imported_aux = {
match import_result {
Ok(ImportResult::Imported(aux)) => aux,
Ok(r) => {
debug!(
target: LOG_TARGET,
"Restoring old authority set after block import result: {:?}", r,
);
pending_changes.revert();
return Ok(r);
},
Err(e) => {
debug!(
target: LOG_TARGET,
"Restoring old authority set after block import error: {}", e,
);
pending_changes.revert();
return Err(ConsensusError::ClientImport(e.to_string()));
},
}
};
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(
"Forced change scheduled after inactivity".to_string(),
));
}
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.
justifications.take();
},
_ => {},
}
let grandpa_justification =
justifications.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID));
match grandpa_justification {
Some(justification) => {
if environment::should_process_justification(
&*self.inner,
self.justification_import_period,
number,
needs_justification,
) {
let import_res = self.import_justification(
hash,
number,
(GRANDPA_ENGINE_ID, justification),
needs_justification,
initial_sync,
);
import_res.unwrap_or_else(|err| {
if needs_justification {
debug!(
target: LOG_TARGET,
"Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}",
number,
err
);
imported_aux.bad_justification = true;
imported_aux.needs_justification = true;
}
});
} else {
debug!(
target: LOG_TARGET,
"Ignoring unnecessary justification for block #{}",
number,
);
}
},
None =>
if needs_justification {
debug!(
target: LOG_TARGET,
"Imported unjustified block #{} that enacts authority set change, waiting for finality for enactment.",
number,
);
imported_aux.needs_justification = true;
},
}
Ok(ImportResult::Imported(imported_aux))
}
async fn check_block(
&self,
block: BlockCheckParams<Block>,
) -> Result<ImportResult, Self::Error> {
self.inner.check_block(block).await
}
}
impl<Backend, Block: BlockT, Client, SC> GrandpaBlockImport<Backend, Block, Client, SC> {
pub(crate) fn new(
inner: Arc<Client>,
justification_import_period: u32,
select_chain: SC,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
send_voter_commands: TracingUnboundedSender<VoterCommand<Block::Hash, NumberFor<Block>>>,
authority_set_hard_forks: Vec<(SetId, PendingChange<Block::Hash, NumberFor<Block>>)>,
justification_sender: GrandpaJustificationSender<Block>,
telemetry: Option<TelemetryHandle>,
) -> GrandpaBlockImport<Backend, Block, Client, SC> {
// check for and apply any forced authority set hard fork that applies
// to the *current* authority set.
if let Some((_, change)) = authority_set_hard_forks
.iter()
.find(|(set_id, _)| *set_id == authority_set.set_id())
{
authority_set.inner().current_authorities = change.next_authorities.clone();
}
// index authority set hard forks by block hash so that they can be used
// by any node syncing the chain and importing a block hard fork
// authority set changes.
let authority_set_hard_forks = authority_set_hard_forks
.into_iter()
.map(|(_, change)| (change.canon_hash, change))
.collect::<HashMap<_, _>>();
// check for and apply any forced authority set hard fork that apply to
// any *pending* standard changes, checking by the block hash at which
// they were announced.
{
let mut authority_set = authority_set.inner();
authority_set.pending_standard_changes =
authority_set.pending_standard_changes.clone().map(&mut |hash, _, original| {
authority_set_hard_forks.get(hash).cloned().unwrap_or(original)
});
}
GrandpaBlockImport {
inner,
justification_import_period,
select_chain,
authority_set,
send_voter_commands,
authority_set_hard_forks: Mutex::new(authority_set_hard_forks),
justification_sender,
telemetry,
_phantom: PhantomData,
}
}
}
impl<BE, Block: BlockT, Client, SC> GrandpaBlockImport<BE, Block, Client, SC>
where
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
/// 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(
&self,
hash: Block::Hash,
number: NumberFor<Block>,
justification: Justification,
enacts_change: bool,
initial_sync: bool,
) -> Result<(), ConsensusError> {
if justification.0 != GRANDPA_ENGINE_ID {
// TODO: the import queue needs to be refactored to be able dispatch to the correct
// `JustificationImport` instance based on `ConsensusEngineId`, or we need to build a
// justification import pipeline similar to what we do for `BlockImport`. In the
// meantime we'll just drop the justification, since this is only used for BEEFY which
// is still WIP.
return Ok(());
}
let justification = GrandpaJustification::decode_and_verify_finalizes(
&justification.1,
(hash, number),
self.authority_set.set_id(),
&self.authority_set.current_authorities(),
);
let justification = match justification {
Err(e) => {
return match e {
pezsp_blockchain::Error::OutdatedJustification =>
Err(ConsensusError::OutdatedJustification),
_ => Err(ConsensusError::ClientImport(e.to_string())),
};
},
Ok(justification) => justification,
};
let result = environment::finalize_block(
self.inner.clone(),
&self.authority_set,
None,
hash,
number,
justification.into(),
initial_sync,
Some(&self.justification_sender),
self.telemetry.clone(),
);
match result {
Err(CommandOrError::VoterCommand(command)) => {
grandpa_log!(
initial_sync,
"👴 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::Signing(error) => ConsensusError::ClientImport(error),
Error::Timer(error) => ConsensusError::ClientImport(error.to_string()),
Error::RuntimeApi(error) => ConsensusError::ClientImport(error.to_string()),
}),
Ok(_) => {
assert!(
!enacts_change,
"returns Ok when no authority set change should be enacted; qed;"
);
},
}
Ok(())
}
}
@@ -0,0 +1,312 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use std::{
collections::{HashMap, HashSet},
marker::PhantomData,
sync::Arc,
};
use codec::{Decode, DecodeAll, Encode};
use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError};
use pezsp_blockchain::{Error as ClientError, HeaderBackend};
use pezsp_consensus_grandpa::AuthorityId;
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use crate::{AuthorityList, Commit, Error};
/// 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(Clone, Encode, Decode, PartialEq, Eq, Debug)]
pub struct GrandpaJustification<Block: BlockT> {
/// The GRANDPA justification for block finality.
pub justification: pezsp_consensus_grandpa::GrandpaJustification<Block::Header>,
_block: PhantomData<Block>,
}
impl<Block: BlockT> From<pezsp_consensus_grandpa::GrandpaJustification<Block::Header>>
for GrandpaJustification<Block>
{
fn from(justification: pezsp_consensus_grandpa::GrandpaJustification<Block::Header>) -> Self {
Self { justification, _block: Default::default() }
}
}
impl<Block: BlockT> Into<pezsp_consensus_grandpa::GrandpaJustification<Block::Header>>
for GrandpaJustification<Block>
{
fn into(self) -> pezsp_consensus_grandpa::GrandpaJustification<Block::Header> {
self.justification
}
}
impl<Block: BlockT> GrandpaJustification<Block> {
/// Create a GRANDPA justification from the given commit. This method
/// assumes the commit is valid and well-formed.
pub fn from_commit<C>(
client: &Arc<C>,
round: u64,
commit: Commit<Block::Header>,
) -> Result<Self, Error>
where
C: HeaderBackend<Block>,
{
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)))
};
// we pick the precommit for the lowest block as the base that
// should serve as the root block for populating ancestry (i.e.
// collect all headers from all precommit blocks to the base)
let (base_hash, base_number) = match commit
.precommits
.iter()
.map(|signed| &signed.precommit)
.min_by_key(|precommit| precommit.target_number)
.map(|precommit| (precommit.target_hash, precommit.target_number))
{
None => return error(),
Some(base) => base,
};
for signed in commit.precommits.iter() {
let mut current_hash = signed.precommit.target_hash;
loop {
if current_hash == base_hash {
break;
}
match client.header(current_hash)? {
Some(current_header) => {
// NOTE: this should never happen as we pick the lowest block
// as base and only traverse backwards from the other blocks
// in the commit. but better be safe to avoid an unbound loop.
if *current_header.number() <= base_number {
return error();
}
let parent_hash = *current_header.parent_hash();
if votes_ancestries_hashes.insert(current_hash) {
votes_ancestries.push(current_header);
}
current_hash = parent_hash;
},
_ => return error(),
}
}
}
Ok(pezsp_consensus_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into())
}
/// Decode a GRANDPA justification and validate the commit and the votes'
/// ancestry proofs finalize the given block.
pub fn decode_and_verify_finalizes(
encoded: &[u8],
finalized_target: (Block::Hash, NumberFor<Block>),
set_id: u64,
voters: &VoterSet<AuthorityId>,
) -> Result<Self, ClientError>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
let justification = GrandpaJustification::<Block>::decode_all(&mut &*encoded)
.map_err(|_| ClientError::JustificationDecode)?;
if (
justification.justification.commit.target_hash,
justification.justification.commit.target_number,
) != finalized_target
{
let msg = "invalid commit target in grandpa justification".to_string();
Err(ClientError::BadJustification(msg))
} else {
justification.verify_with_voter_set(set_id, voters).map(|_| justification)
}
}
/// Validate the commit and the votes' ancestry proofs.
pub fn verify(&self, set_id: u64, authorities: &AuthorityList) -> Result<(), ClientError>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
let voters = VoterSet::new(authorities.iter().cloned())
.ok_or(ClientError::Consensus(pezsp_consensus::Error::InvalidAuthoritiesSet))?;
self.verify_with_voter_set(set_id, &voters)
}
/// Validate the commit and the votes' ancestry proofs.
pub(crate) fn verify_with_voter_set(
&self,
set_id: u64,
voters: &VoterSet<AuthorityId>,
) -> Result<(), ClientError>
where
NumberFor<Block>: finality_grandpa::BlockNumberOps,
{
use finality_grandpa::Chain;
let ancestry_chain = AncestryChain::<Block>::new(&self.justification.votes_ancestries);
match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain)
{
Ok(ref result) if result.is_valid() => {},
_ => {
let msg = "invalid commit in grandpa justification".to_string();
return Err(ClientError::BadJustification(msg));
},
}
// we pick the precommit for the lowest block as the base that
// should serve as the root block for populating ancestry (i.e.
// collect all headers from all precommit blocks to the base)
let base_hash = self
.justification
.commit
.precommits
.iter()
.map(|signed| &signed.precommit)
.min_by_key(|precommit| precommit.target_number)
.map(|precommit| precommit.target_hash)
.expect(
"can only fail if precommits is empty; \
commit has been validated above; \
valid commits must include precommits; \
qed.",
);
let mut buf = Vec::new();
let mut visited_hashes = HashSet::new();
for signed in self.justification.commit.precommits.iter() {
let signature_result = pezsp_consensus_grandpa::check_message_signature_with_buffer(
&finality_grandpa::Message::Precommit(signed.precommit.clone()),
&signed.id,
&signed.signature,
self.justification.round,
set_id,
&mut buf,
);
match signature_result {
pezsp_consensus_grandpa::SignatureResult::Invalid =>
return Err(ClientError::BadJustification(
"invalid signature for precommit in grandpa justification".to_string(),
)),
pezsp_consensus_grandpa::SignatureResult::OutdatedSet =>
return Err(ClientError::OutdatedJustification),
pezsp_consensus_grandpa::SignatureResult::Valid => {},
}
if base_hash == signed.precommit.target_hash {
continue;
}
match ancestry_chain.ancestry(base_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(),
)),
}
}
let ancestry_hashes: HashSet<_> = self
.justification
.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(),
));
}
Ok(())
}
/// The target block number and hash that this justifications proves finality for.
pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
(self.justification.commit.target_number, self.justification.commit.target_hash)
}
}
/// A utility trait implementing `finality_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> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
where
NumberFor<Block>: finality_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(&current_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)
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,43 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use pezsc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr};
use crate::justification::GrandpaJustification;
/// The sending half of the Grandpa justification channel(s).
///
/// Used to send notifications about justifications generated
/// at the end of a Grandpa round.
pub type GrandpaJustificationSender<Block> = NotificationSender<GrandpaJustification<Block>>;
/// The receiving half of the Grandpa justification channel.
///
/// Used to receive notifications about justifications generated
/// at the end of a Grandpa round.
/// The `GrandpaJustificationStream` entity stores the `SharedJustificationSenders`
/// so it can be used to add more subscriptions.
pub type GrandpaJustificationStream<Block> =
NotificationStream<GrandpaJustification<Block>, GrandpaJustificationsTracingKey>;
/// Provides tracing key for GRANDPA justifications stream.
#[derive(Clone)]
pub struct GrandpaJustificationsTracingKey;
impl TracingKeyStr for GrandpaJustificationsTracingKey {
const TRACING_KEY: &'static str = "mpsc_grandpa_justification_notification_stream";
}
@@ -0,0 +1,472 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use std::{
marker::{PhantomData, Unpin},
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use finality_grandpa::{voter, voter_set::VoterSet, BlockNumberOps, Error as GrandpaError};
use futures::prelude::*;
use log::{debug, info, warn};
use pezsc_client_api::backend::Backend;
use pezsc_network::NotificationService;
use pezsc_telemetry::TelemetryHandle;
use pezsc_utils::mpsc::TracingUnboundedReceiver;
use pezsp_blockchain::HeaderMetadata;
use pezsp_consensus::SelectChain;
use pezsp_consensus_grandpa::AuthorityId;
use pezsp_keystore::KeystorePtr;
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
use crate::{
authorities::SharedAuthoritySet,
aux_schema::PersistentData,
communication::{Network as NetworkT, NetworkBridge, Syncing as SyncingT},
environment, global_communication,
notification::GrandpaJustificationSender,
ClientForGrandpa, CommandOrError, CommunicationIn, Config, Error, LinkHalf, VoterCommand,
VoterSetState, LOG_TARGET,
};
struct ObserverChain<'a, Block: BlockT, Client> {
client: &'a Arc<Client>,
_phantom: PhantomData<Block>,
}
impl<'a, Block, Client> finality_grandpa::Chain<Block::Hash, NumberFor<Block>>
for ObserverChain<'a, Block, Client>
where
Block: BlockT,
Client: HeaderMetadata<Block, Error = pezsp_blockchain::Error>,
NumberFor<Block>: BlockNumberOps,
{
fn ancestry(
&self,
base: Block::Hash,
block: Block::Hash,
) -> Result<Vec<Block::Hash>, GrandpaError> {
environment::ancestry(self.client, base, block)
}
}
fn grandpa_observer<BE, Block: BlockT, Client, S, F>(
client: &Arc<Client>,
authority_set: &SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
voters: &Arc<VoterSet<AuthorityId>>,
justification_sender: &Option<GrandpaJustificationSender<Block>>,
last_finalized_number: NumberFor<Block>,
commits: S,
note_round: F,
telemetry: Option<TelemetryHandle>,
) -> impl Future<Output = Result<(), CommandOrError<Block::Hash, NumberFor<Block>>>>
where
NumberFor<Block>: BlockNumberOps,
S: Stream<Item = Result<CommunicationIn<Block>, CommandOrError<Block::Hash, NumberFor<Block>>>>,
F: Fn(u64),
BE: Backend<Block>,
Client: ClientForGrandpa<Block, BE>,
{
let authority_set = authority_set.clone();
let client = client.clone();
let voters = voters.clone();
let justification_sender = justification_sender.clone();
let observer = commits.try_fold(last_finalized_number, move |last_finalized_number, global| {
let (round, commit, callback) = match global {
voter::CommunicationIn::Commit(round, commit, callback) => {
let commit = finality_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 finality_grandpa::validate_commit(
&commit,
&voters,
&ObserverChain { client: &client, _phantom: PhantomData },
) {
Ok(r) => r,
Err(e) => return future::err(e.into()),
};
if validation_result.is_valid() {
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.clone(),
&authority_set,
None,
finalized_hash,
finalized_number,
(round, commit).into(),
false,
justification_sender.as_ref(),
telemetry.clone(),
) {
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);
finality_grandpa::process_commit_validation_result(validation_result, callback);
// proceed processing with new finalized block number
future::ok(finalized_number)
} else {
debug!(target: LOG_TARGET, "Received invalid commit: ({:?}, {:?})", round, commit);
finality_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_ok(|_| ())
}
/// 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`.
/// NOTE: this is currently not part of the crate's public API since we don't consider
/// it stable enough to use on a live network.
pub fn run_grandpa_observer<BE, Block: BlockT, Client, N, S, SC>(
config: Config,
link: LinkHalf<Block, Client, SC>,
network: N,
sync: S,
notification_service: Box<dyn NotificationService>,
) -> pezsp_blockchain::Result<impl Future<Output = ()> + Send>
where
BE: Backend<Block> + Unpin + 'static,
N: NetworkT<Block>,
S: SyncingT<Block>,
SC: SelectChain<Block>,
NumberFor<Block>: BlockNumberOps,
Client: ClientForGrandpa<Block, BE> + 'static,
{
let LinkHalf {
client,
persistent_data,
voter_commands_rx,
justification_sender,
telemetry,
..
} = link;
let network = NetworkBridge::new(
network,
sync,
notification_service,
config.clone(),
persistent_data.set_state.clone(),
None,
telemetry.clone(),
);
let observer_work = ObserverWork::new(
client,
network,
persistent_data,
config.keystore,
voter_commands_rx,
Some(justification_sender),
telemetry,
);
let observer_work = observer_work.map_ok(|_| ()).map_err(|e| {
warn!("GRANDPA Observer failed: {}", e);
});
Ok(observer_work.map(drop))
}
/// Future that powers the observer.
#[must_use]
struct ObserverWork<B: BlockT, BE, Client, N: NetworkT<B>, S: SyncingT<B>> {
observer:
Pin<Box<dyn Future<Output = Result<(), CommandOrError<B::Hash, NumberFor<B>>>> + Send>>,
client: Arc<Client>,
network: NetworkBridge<B, N, S>,
persistent_data: PersistentData<B>,
keystore: Option<KeystorePtr>,
voter_commands_rx: TracingUnboundedReceiver<VoterCommand<B::Hash, NumberFor<B>>>,
justification_sender: Option<GrandpaJustificationSender<B>>,
telemetry: Option<TelemetryHandle>,
_phantom: PhantomData<BE>,
}
impl<B, BE, Client, Network, Syncing> ObserverWork<B, BE, Client, Network, Syncing>
where
B: BlockT,
BE: Backend<B> + 'static,
Client: ClientForGrandpa<B, BE> + 'static,
Network: NetworkT<B>,
Syncing: SyncingT<B>,
NumberFor<B>: BlockNumberOps,
{
fn new(
client: Arc<Client>,
network: NetworkBridge<B, Network, Syncing>,
persistent_data: PersistentData<B>,
keystore: Option<KeystorePtr>,
voter_commands_rx: TracingUnboundedReceiver<VoterCommand<B::Hash, NumberFor<B>>>,
justification_sender: Option<GrandpaJustificationSender<B>>,
telemetry: Option<TelemetryHandle>,
) -> Self {
let mut work = ObserverWork {
// `observer` is set to a temporary value and replaced below when
// calling `rebuild_observer`.
observer: Box::pin(future::pending()) as Pin<Box<_>>,
client,
network,
persistent_data,
keystore: keystore.clone(),
voter_commands_rx,
justification_sender,
telemetry,
_phantom: PhantomData,
};
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.clone(),
&self.network,
self.keystore.as_ref(),
None,
);
let last_finalized_number = self.client.info().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,
&voters,
&self.justification_sender,
last_finalized_number,
global_in,
note_round,
self.telemetry.clone(),
);
self.observer = Box::pin(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: LOG_TARGET, "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(),
(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, BE, C, N, S> Future for ObserverWork<B, BE, C, N, S>
where
B: BlockT,
BE: Backend<B> + Unpin + 'static,
C: ClientForGrandpa<B, BE> + 'static,
N: NetworkT<B>,
S: SyncingT<B>,
NumberFor<B>: BlockNumberOps,
{
type Output = Result<(), Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match Future::poll(Pin::new(&mut self.observer), cx) {
Poll::Pending => {},
Poll::Ready(Ok(())) => {
// observer commit stream doesn't conclude naturally; this could reasonably be an
// error.
return Poll::Ready(Ok(()));
},
Poll::Ready(Err(CommandOrError::Error(e))) => {
// return inner observer error
return Poll::Ready(Err(e));
},
Poll::Ready(Err(CommandOrError::VoterCommand(command))) => {
// some command issued internally
self.handle_voter_command(command)?;
cx.waker().wake_by_ref();
},
}
match Stream::poll_next(Pin::new(&mut self.voter_commands_rx), cx) {
Poll::Pending => {},
Poll::Ready(None) => {
// the `voter_commands_rx` stream should never conclude since it's never closed.
return Poll::Ready(Ok(()));
},
Poll::Ready(Some(command)) => {
// some command issued externally
self.handle_voter_command(command)?;
cx.waker().wake_by_ref();
},
}
Future::poll(Pin::new(&mut self.network), cx)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
aux_schema,
communication::tests::{make_test_network, Event},
};
use assert_matches::assert_matches;
use pezsc_network_types::PeerId;
use pezsc_utils::mpsc::tracing_unbounded;
use pezsp_blockchain::HeaderBackend as _;
use bizinikiwi_test_runtime_client::{TestClientBuilder, TestClientBuilderExt};
use futures::executor;
/// Ensure `Future` implementation of `ObserverWork` is polling its `NetworkBridge`.
/// Regression test for bug introduced in d4fbb897c and fixed in b7af8b339.
///
/// When polled, `NetworkBridge` forwards reputation change requests from the
/// `GossipValidator` to the underlying `dyn Network`. This test triggers a reputation change
/// by calling `GossipValidator::validate` with an invalid gossip message. After polling the
/// `ObserverWork` which should poll the `NetworkBridge`, the reputation change should be
/// forwarded to the test network.
#[test]
fn observer_work_polls_underlying_network_bridge() {
// Create a test network.
let (tester_fut, _network) = make_test_network();
let mut tester = executor::block_on(tester_fut);
// Create an observer.
let (client, backend) = {
let builder = TestClientBuilder::with_default_backend();
let backend = builder.backend();
let (client, _) = builder.build_with_longest_chain();
(Arc::new(client), backend)
};
let voters = vec![(pezsp_keyring::Ed25519Keyring::Alice.public().into(), 1)];
let persistent_data =
aux_schema::load_persistent(&*backend, client.info().genesis_hash, 0, || Ok(voters))
.unwrap();
let (_tx, voter_command_rx) = tracing_unbounded("test_mpsc_voter_command", 100_000);
let observer = ObserverWork::new(
client,
tester.net_handle.clone(),
persistent_data,
None,
voter_command_rx,
None,
None,
);
// Trigger a reputation change through the gossip validator.
let peer_id = PeerId::random();
tester.trigger_gossip_validator_reputation_change(&peer_id);
executor::block_on(async move {
// Poll the observer once and have it forward the reputation change from the gossip
// validator to the test network.
assert!(observer.now_or_never().is_none());
assert_matches!(tester.events.next().now_or_never(), Some(Some(Event::Report(_, _))));
});
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,454 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://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::{future::Future, pin::Pin, sync::Arc};
use dyn_clone::DynClone;
use pezsc_client_api::blockchain::HeaderBackend;
use pezsp_runtime::traits::{Block as BlockT, Header, NumberFor, One, Zero};
/// A future returned by a `VotingRule` to restrict a given vote, if any restriction is necessary.
pub type VotingRuleResult<Block> =
Pin<Box<dyn Future<Output = Option<(<Block as BlockT>::Hash, NumberFor<Block>)>> + Send>>;
/// A trait for custom voting rules in GRANDPA.
pub trait VotingRule<Block, B>: DynClone + 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: Arc<B>,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<Block>;
}
impl<Block, B> VotingRule<Block, B> for ()
where
Block: BlockT,
B: HeaderBackend<Block>,
{
fn restrict_vote(
&self,
_backend: Arc<B>,
_base: &Block::Header,
_best_target: &Block::Header,
_current_target: &Block::Header,
) -> VotingRuleResult<Block> {
Box::pin(async { None })
}
}
/// A custom voting rule that guarantees that our vote is always behind the best
/// block by at least N blocks, unless the base number is < N blocks behind the
/// best, in which case it votes for the base.
///
/// In the best case our vote is exactly N blocks
/// behind the best block, but if there is a scenario where either
/// \>34% of validators run without this rule or the fork-choice rule
/// can prioritize shorter chains over longer ones, the vote may be
/// closer to the best block than N.
#[derive(Clone)]
pub struct BeforeBestBlockBy<N>(pub N);
impl<Block, B> VotingRule<Block, B> for BeforeBestBlockBy<NumberFor<Block>>
where
Block: BlockT,
B: HeaderBackend<Block>,
{
fn restrict_vote(
&self,
backend: Arc<B>,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<Block> {
use pezsp_arithmetic::traits::Saturating;
if current_target.number().is_zero() {
return Box::pin(async { None });
}
// Constrain to the base number, if that's the minimal
// vote that can be placed.
if *base.number() + self.0 > *best_target.number() {
return Box::pin(std::future::ready(Some((base.hash(), *base.number()))));
}
// find the target number restricted by this rule
let target_number = best_target.number().saturating_sub(self.0);
// our current target is already lower than this rule would restrict
if target_number >= *current_target.number() {
return Box::pin(async { None });
}
let current_target = current_target.clone();
// find the block at the given target height
Box::pin(std::future::ready(find_target(&*backend, target_number, &current_target)))
}
}
/// 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.
#[derive(Clone)]
pub struct ThreeQuartersOfTheUnfinalizedChain;
impl<Block, B> VotingRule<Block, B> for ThreeQuartersOfTheUnfinalizedChain
where
Block: BlockT,
B: HeaderBackend<Block>,
{
fn restrict_vote(
&self,
backend: Arc<B>,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<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 Box::pin(async { None });
}
// find the block at the given target height
Box::pin(std::future::ready(find_target(&*backend, target_number, current_target)))
}
}
// walk backwards until we find the target block
fn find_target<Block, B>(
backend: &B,
target_number: NumberFor<Block>,
current_header: &Block::Header,
) -> Option<(Block::Hash, NumberFor<Block>)>
where
Block: BlockT,
B: HeaderBackend<Block>,
{
let mut target_hash = current_header.hash();
let mut target_header = current_header.clone();
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(target_hash)
.ok()?
.expect("Header known to exist due to the existence of one of its descendants; 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> + 'static,
{
fn restrict_vote(
&self,
backend: Arc<B>,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<Block> {
let rules = self.rules.clone();
let base = base.clone();
let best_target = best_target.clone();
let current_target = current_target.clone();
Box::pin(async move {
let mut restricted_target = current_target.clone();
for rule in rules.iter() {
if let Some(header) = rule
.restrict_vote(backend.clone(), &base, &best_target, &restricted_target)
.await
.filter(|(_, restricted_number)| {
// NOTE: we can only restrict votes within the interval [base, target)
restricted_number >= base.number() &&
restricted_number < restricted_target.number()
})
.and_then(|(hash, _)| backend.header(hash).ok())
.and_then(std::convert::identity)
{
restricted_target = header;
}
}
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> + 'static,
{
fn default() -> Self {
VotingRulesBuilder::new()
.add(BeforeBestBlockBy(2u32.into()))
.add(ThreeQuartersOfTheUnfinalizedChain)
}
}
impl<Block, B> VotingRulesBuilder<Block, B>
where
Block: BlockT,
B: HeaderBackend<Block> + 'static,
{
/// 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>,
Self: Clone,
{
fn restrict_vote(
&self,
backend: Arc<B>,
base: &Block::Header,
best_target: &Block::Header,
current_target: &Block::Header,
) -> VotingRuleResult<Block> {
(**self).restrict_vote(backend, base, best_target, current_target)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsc_block_builder::BlockBuilderBuilder;
use pezsp_consensus::BlockOrigin;
use pezsp_runtime::traits::Header as _;
use bizinikiwi_test_runtime_client::{
runtime::{Block, Header},
Backend, Client, ClientBlockImportExt, DefaultTestClientBuilderExt, TestClientBuilder,
TestClientBuilderExt,
};
/// A mock voting rule that subtracts a static number of block from the `current_target`.
#[derive(Clone)]
struct Subtract(u64);
impl VotingRule<Block, Client<Backend>> for Subtract {
fn restrict_vote(
&self,
backend: Arc<Client<Backend>>,
_base: &Header,
_best_target: &Header,
current_target: &Header,
) -> VotingRuleResult<Block> {
let target_number = current_target.number() - self.0;
let res = backend
.hash(target_number)
.unwrap()
.map(|target_hash| (target_hash, target_number));
Box::pin(std::future::ready(res))
}
}
#[test]
fn multiple_voting_rules_cannot_restrict_past_base() {
// setup an aggregate voting rule composed of two voting rules
// where each subtracts 50 blocks from the current target
let rule = VotingRulesBuilder::new().add(Subtract(50)).add(Subtract(50)).build();
let client = Arc::new(TestClientBuilder::new().build());
let mut hashes = Vec::with_capacity(200);
for _ in 0..200 {
let block = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().best_hash)
.with_parent_block_number(client.chain_info().best_number)
.build()
.unwrap()
.build()
.unwrap()
.block;
hashes.push(block.hash());
futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
let genesis = client.header(client.info().genesis_hash).unwrap().unwrap();
let best = client.header(client.info().best_hash).unwrap().unwrap();
let (_, number) =
futures::executor::block_on(rule.restrict_vote(client.clone(), &genesis, &best, &best))
.unwrap();
// we apply both rules which should subtract 100 blocks from best block (#200)
// which means that we should be voting for block #100
assert_eq!(number, 100);
let block110 = client.header(hashes[109]).unwrap().unwrap();
let (_, number) = futures::executor::block_on(rule.restrict_vote(
client.clone(),
&block110,
&best,
&best,
))
.unwrap();
// base block is #110 while best block is #200, applying both rules would make
// would make the target block (#100) be lower than the base block, therefore
// only one of the rules is applied.
assert_eq!(number, 150);
}
#[test]
fn before_best_by_has_cutoff_at_base() {
let rule = BeforeBestBlockBy(2);
let client = Arc::new(TestClientBuilder::new().build());
let n = 5;
let mut hashes = Vec::with_capacity(n);
for _ in 0..n {
let block = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().best_hash)
.with_parent_block_number(client.chain_info().best_number)
.build()
.unwrap()
.build()
.unwrap()
.block;
hashes.push(block.hash());
futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
}
let best = client.header(client.info().best_hash).unwrap().unwrap();
let best_number = *best.number();
for i in 0..n {
let base = client.header(hashes[i]).unwrap().unwrap();
let (_, number) = futures::executor::block_on(rule.restrict_vote(
client.clone(),
&base,
&best,
&best,
))
.unwrap();
let expected = std::cmp::max(best_number - 2, *base.number());
assert_eq!(number, expected, "best = {}, lag = 2, base = {}", best_number, i);
}
}
}
@@ -0,0 +1,476 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Bizinikiwi.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// Bizinikiwi 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.
// Bizinikiwi 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 Bizinikiwi. If not, see <https://www.gnu.org/licenses/>.
//! Utilities for generating and verifying GRANDPA warp sync proofs.
use codec::{Decode, DecodeAll, Encode};
use crate::{
best_justification, find_scheduled_change, AuthoritySetChanges, AuthoritySetHardFork,
BlockNumberOps, GrandpaJustification, SharedAuthoritySet,
};
use pezsc_client_api::Backend as ClientBackend;
use pezsc_network_sync::strategy::warp::{EncodedProof, VerificationResult, WarpSyncProvider};
use pezsp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
use pezsp_consensus_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
use pezsp_runtime::{
generic::BlockId,
traits::{Block as BlockT, Header as HeaderT, NumberFor, One},
Justifications,
};
use std::{collections::HashMap, sync::Arc};
/// Warp proof processing error.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Decoding error.
#[error("Failed to decode block hash: {0}.")]
DecodeScale(#[from] codec::Error),
/// Client backend error.
#[error("{0}")]
Client(#[from] pezsp_blockchain::Error),
/// Invalid request data.
#[error("{0}")]
InvalidRequest(String),
/// Invalid warp proof.
#[error("{0}")]
InvalidProof(String),
/// Missing header or authority set change data.
#[error("Missing required data to be able to answer request.")]
MissingData,
}
/// The maximum size in bytes of the `WarpSyncProof`.
pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024;
/// A proof of an authority set change.
#[derive(Decode, Encode, Debug)]
pub struct WarpSyncFragment<Block: BlockT> {
/// The last block that the given authority set finalized. This block should contain a digest
/// signaling an authority set change from which we can fetch the next authority set.
pub header: Block::Header,
/// A justification for the header above which proves its finality. In order to validate it the
/// verifier must be aware of the authorities and set id for which the justification refers to.
pub justification: GrandpaJustification<Block>,
}
/// An accumulated proof of multiple authority set changes.
#[derive(Decode, Encode)]
pub struct WarpSyncProof<Block: BlockT> {
proofs: Vec<WarpSyncFragment<Block>>,
is_finished: bool,
}
impl<Block: BlockT> WarpSyncProof<Block> {
/// Generates a warp sync proof starting at the given block. It will generate authority set
/// change proofs for all changes that happened from `begin` until the current authority set
/// (capped by MAX_WARP_SYNC_PROOF_SIZE).
fn generate<Backend>(
backend: &Backend,
begin: Block::Hash,
set_changes: &AuthoritySetChanges<NumberFor<Block>>,
) -> Result<WarpSyncProof<Block>, Error>
where
Backend: ClientBackend<Block>,
{
// TODO: cache best response (i.e. the one with lowest begin_number)
let blockchain = backend.blockchain();
let begin_number = blockchain
.block_number_from_id(&BlockId::Hash(begin))?
.ok_or_else(|| Error::InvalidRequest("Missing start block".to_string()))?;
if begin_number > blockchain.info().finalized_number {
return Err(Error::InvalidRequest("Start block is not finalized".to_string()));
}
let canon_hash = blockchain.hash(begin_number)?.expect(
"begin number is lower than finalized number; \
all blocks below finalized number must have been imported; \
qed.",
);
if canon_hash != begin {
return Err(Error::InvalidRequest(
"Start block is not in the finalized chain".to_string(),
));
}
let mut proofs = Vec::new();
let mut proofs_encoded_len = 0;
let mut proof_limit_reached = false;
let set_changes = set_changes.iter_from(begin_number).ok_or(Error::MissingData)?;
for (_, last_block) in set_changes {
let hash = blockchain.block_hash_from_id(&BlockId::Number(*last_block))?
.expect("header number comes from previously applied set changes; corresponding hash must exist in db; qed.");
let header = blockchain
.header(hash)?
.expect("header hash obtained from header number exists in db; corresponding header must exist in db too; qed.");
// the last block in a set is the one that triggers a change to the next set,
// therefore the block must have a digest that signals the authority set change
if find_scheduled_change::<Block>(&header).is_none() {
// if it doesn't contain a signal for standard change then the set must have changed
// through a forced changed, in which case we stop collecting proofs as the chain of
// trust in authority handoffs was broken.
break;
}
let justification = blockchain
.justifications(header.hash())?
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
.ok_or_else(|| Error::MissingData)?;
let justification = GrandpaJustification::<Block>::decode_all(&mut &justification[..])?;
let proof = WarpSyncFragment { header: header.clone(), justification };
let proof_size = proof.encoded_size();
// Check for the limit. We remove some bytes from the maximum size, because we're only
// counting the size of the `WarpSyncFragment`s. The extra margin is here to leave
// room for rest of the data (the size of the `Vec` and the boolean).
if proofs_encoded_len + proof_size >= MAX_WARP_SYNC_PROOF_SIZE - 50 {
proof_limit_reached = true;
break;
}
proofs_encoded_len += proof_size;
proofs.push(proof);
}
let is_finished = if proof_limit_reached {
false
} else {
let latest_justification = best_justification(backend)?.filter(|justification| {
// the existing best justification must be for a block higher than the
// last authority set change. if we didn't prove any authority set
// change then we fallback to make sure it's higher or equal to the
// initial warp sync block.
let limit = proofs
.last()
.map(|proof| proof.justification.target().0 + One::one())
.unwrap_or(begin_number);
justification.target().0 >= limit
});
if let Some(latest_justification) = latest_justification {
let header = blockchain.header(latest_justification.target().1)?
.expect("header hash corresponds to a justification in db; must exist in db as well; qed.");
let proof = WarpSyncFragment { header, justification: latest_justification };
// Check for the limit. We remove some bytes from the maximum size, because we're
// only counting the size of the `WarpSyncFragment`s. The extra margin is here
// to leave room for rest of the data (the size of the `Vec` and the boolean).
if proofs_encoded_len + proof.encoded_size() >= MAX_WARP_SYNC_PROOF_SIZE - 50 {
false
} else {
proofs.push(proof);
true
}
} else {
true
}
};
let final_outcome = WarpSyncProof { proofs, is_finished };
debug_assert!(final_outcome.encoded_size() <= MAX_WARP_SYNC_PROOF_SIZE);
Ok(final_outcome)
}
/// Verifies the warp sync proof starting at the given set id and with the given authorities.
/// Verification stops when either the proof is exhausted or finality for the target header can
/// be proven. If the proof is valid the new set id and authorities is returned.
fn verify(
&self,
set_id: SetId,
authorities: AuthorityList,
hard_forks: &HashMap<(Block::Hash, NumberFor<Block>), (SetId, AuthorityList)>,
) -> Result<(SetId, AuthorityList), Error>
where
NumberFor<Block>: BlockNumberOps,
{
let mut current_set_id = set_id;
let mut current_authorities = authorities;
for (fragment_num, proof) in self.proofs.iter().enumerate() {
let hash = proof.header.hash();
let number = *proof.header.number();
if let Some((set_id, list)) = hard_forks.get(&(hash, number)) {
current_set_id = *set_id;
current_authorities = list.clone();
} else {
proof
.justification
.verify(current_set_id, &current_authorities)
.map_err(|err| Error::InvalidProof(err.to_string()))?;
if proof.justification.target().1 != hash {
return Err(Error::InvalidProof(
"Mismatch between header and justification".to_owned(),
));
}
if let Some(scheduled_change) = find_scheduled_change::<Block>(&proof.header) {
current_authorities = scheduled_change.next_authorities;
current_set_id += 1;
} else if fragment_num != self.proofs.len() - 1 || !self.is_finished {
// Only the last fragment of the last proof message is allowed to be missing the
// authority set change.
return Err(Error::InvalidProof(
"Header is missing authority set change digest".to_string(),
));
}
}
}
Ok((current_set_id, current_authorities))
}
}
/// Implements network API for warp sync.
pub struct NetworkProvider<Block: BlockT, Backend: ClientBackend<Block>>
where
NumberFor<Block>: BlockNumberOps,
{
backend: Arc<Backend>,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
hard_forks: HashMap<(Block::Hash, NumberFor<Block>), (SetId, AuthorityList)>,
}
impl<Block: BlockT, Backend: ClientBackend<Block>> NetworkProvider<Block, Backend>
where
NumberFor<Block>: BlockNumberOps,
{
/// Create a new instance for a given backend and authority set.
pub fn new(
backend: Arc<Backend>,
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
hard_forks: Vec<AuthoritySetHardFork<Block>>,
) -> Self {
NetworkProvider {
backend,
authority_set,
hard_forks: hard_forks
.into_iter()
.map(|fork| (fork.block, (fork.set_id, fork.authorities)))
.collect(),
}
}
}
impl<Block: BlockT, Backend: ClientBackend<Block>> WarpSyncProvider<Block>
for NetworkProvider<Block, Backend>
where
NumberFor<Block>: BlockNumberOps,
{
fn generate(
&self,
start: Block::Hash,
) -> Result<EncodedProof, Box<dyn std::error::Error + Send + Sync>> {
let proof = WarpSyncProof::<Block>::generate(
&*self.backend,
start,
&self.authority_set.authority_set_changes(),
)
.map_err(Box::new)?;
Ok(EncodedProof(proof.encode()))
}
fn verify(
&self,
proof: &EncodedProof,
set_id: SetId,
authorities: AuthorityList,
) -> Result<VerificationResult<Block>, Box<dyn std::error::Error + Send + Sync>> {
let EncodedProof(proof) = proof;
let proof = WarpSyncProof::<Block>::decode_all(&mut proof.as_slice())
.map_err(|e| format!("Proof decoding error: {:?}", e))?;
let last_header = proof
.proofs
.last()
.map(|p| p.header.clone())
.ok_or_else(|| "Empty proof".to_string())?;
let (next_set_id, next_authorities) =
proof.verify(set_id, authorities, &self.hard_forks).map_err(Box::new)?;
let justifications = proof
.proofs
.into_iter()
.map(|p| {
let justifications =
Justifications::new(vec![(GRANDPA_ENGINE_ID, p.justification.encode())]);
(p.header, justifications)
})
.collect::<Vec<_>>();
if proof.is_finished {
Ok(VerificationResult::<Block>::Complete(
next_set_id,
next_authorities,
last_header,
justifications,
))
} else {
Ok(VerificationResult::<Block>::Partial(
next_set_id,
next_authorities,
last_header.hash(),
justifications,
))
}
}
fn current_authorities(&self) -> AuthorityList {
self.authority_set.inner().current_authorities.clone()
}
}
#[cfg(test)]
mod tests {
use super::WarpSyncProof;
use crate::{AuthoritySetChanges, GrandpaJustification};
use codec::Encode;
use rand::prelude::*;
use pezsc_block_builder::BlockBuilderBuilder;
use pezsp_blockchain::HeaderBackend;
use pezsp_consensus::BlockOrigin;
use pezsp_consensus_grandpa::GRANDPA_ENGINE_ID;
use pezsp_keyring::Ed25519Keyring;
use std::sync::Arc;
use bizinikiwi_test_runtime_client::{
BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt,
TestClientBuilder, TestClientBuilderExt,
};
#[test]
fn warp_sync_proof_generate_verify() {
let mut rng = rand::rngs::StdRng::from_seed([0; 32]);
let builder = TestClientBuilder::new();
let backend = builder.backend();
let client = Arc::new(builder.build());
let available_authorities = Ed25519Keyring::iter().collect::<Vec<_>>();
let genesis_authorities = vec![(Ed25519Keyring::Alice.public().into(), 1)];
let mut current_authorities = vec![Ed25519Keyring::Alice];
let mut current_set_id = 0;
let mut authority_set_changes = Vec::new();
for n in 1..=100 {
let mut builder = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().best_hash)
.with_parent_block_number(client.chain_info().best_number)
.build()
.unwrap();
let mut new_authorities = None;
// we will trigger an authority set change every 10 blocks
if n != 0 && n % 10 == 0 {
// pick next authorities and add digest for the set change
let n_authorities = rng.gen_range(1..available_authorities.len());
let next_authorities = available_authorities
.choose_multiple(&mut rng, n_authorities)
.cloned()
.collect::<Vec<_>>();
new_authorities = Some(next_authorities.clone());
let next_authorities = next_authorities
.iter()
.map(|keyring| (keyring.public().into(), 1))
.collect::<Vec<_>>();
let digest = pezsp_runtime::generic::DigestItem::Consensus(
pezsp_consensus_grandpa::GRANDPA_ENGINE_ID,
pezsp_consensus_grandpa::ConsensusLog::ScheduledChange(
pezsp_consensus_grandpa::ScheduledChange { delay: 0u64, next_authorities },
)
.encode(),
);
builder.push_deposit_log_digest_item(digest).unwrap();
}
let block = builder.build().unwrap().block;
futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
if let Some(new_authorities) = new_authorities {
// generate a justification for this block, finalize it and note the authority set
// change
let (target_hash, target_number) = {
let info = client.info();
(info.best_hash, info.best_number)
};
let mut precommits = Vec::new();
for keyring in &current_authorities {
let precommit = finality_grandpa::Precommit { target_hash, target_number };
let msg = finality_grandpa::Message::Precommit(precommit.clone());
let encoded = pezsp_consensus_grandpa::localized_payload(42, current_set_id, &msg);
let signature = keyring.sign(&encoded[..]).into();
let precommit = finality_grandpa::SignedPrecommit {
precommit,
signature,
id: keyring.public().into(),
};
precommits.push(precommit);
}
let commit = finality_grandpa::Commit { target_hash, target_number, precommits };
let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap();
client
.finalize_block(target_hash, Some((GRANDPA_ENGINE_ID, justification.encode())))
.unwrap();
authority_set_changes.push((current_set_id, n));
current_set_id += 1;
current_authorities = new_authorities;
}
}
let authority_set_changes = AuthoritySetChanges::from(authority_set_changes);
// generate a warp sync proof
let genesis_hash = client.hash(0).unwrap().unwrap();
let warp_sync_proof =
WarpSyncProof::generate(&*backend, genesis_hash, &authority_set_changes).unwrap();
// verifying the proof should yield the last set id and authorities
let (new_set_id, new_authorities) =
warp_sync_proof.verify(0, genesis_authorities, &Default::default()).unwrap();
let expected_authorities = current_authorities
.iter()
.map(|keyring| (keyring.public().into(), 1))
.collect::<Vec<_>>();
assert_eq!(new_set_id, current_set_id);
assert_eq!(new_authorities, expected_authorities);
}
}