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
@@ -0,0 +1,91 @@
[package]
name = "pezsc-consensus-grandpa"
version = "0.19.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Integration of the GRANDPA finality gadget into bizinikiwi."
documentation = "https://docs.rs/pezsc-consensus-grandpa"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
ahash = { workspace = true }
array-bytes = { workspace = true, default-features = true }
async-trait = { workspace = true }
codec = { features = ["derive"], workspace = true, default-features = true }
dyn-clone = { workspace = true }
finality-grandpa = { features = [
"derive-codec",
], workspace = true, default-features = true }
fork-tree = { workspace = true, default-features = true }
futures = { workspace = true }
futures-timer = { workspace = true }
log = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
rand = { workspace = true, default-features = true }
pezsc-block-builder = { workspace = true, default-features = true }
pezsc-chain-spec = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-consensus = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-network-common = { workspace = true, default-features = true }
pezsc-network-gossip = { workspace = true, default-features = true }
pezsc-network-sync = { workspace = true, default-features = true }
pezsc-network-types = { workspace = true, default-features = true }
pezsc-telemetry = { workspace = true, default-features = true }
pezsc-transaction-pool-api = { workspace = true, default-features = true }
pezsc-utils = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-application-crypto = { workspace = true, default-features = true }
pezsp-arithmetic = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-consensus-grandpa = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-crypto-hashing = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
thiserror = { workspace = true }
[dev-dependencies]
assert_matches = { workspace = true }
finality-grandpa = { features = [
"derive-codec",
"test-helpers",
], workspace = true, default-features = true }
pezsc-network-test = { workspace = true }
pezsp-keyring = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
tokio = { workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsc-block-builder/runtime-benchmarks",
"pezsc-chain-spec/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-consensus/runtime-benchmarks",
"pezsc-network-common/runtime-benchmarks",
"pezsc-network-gossip/runtime-benchmarks",
"pezsc-network-sync/runtime-benchmarks",
"pezsc-network-test/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsc-transaction-pool-api/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus-grandpa/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,39 @@
Integration of the GRANDPA finality gadget into Bizinikiwi.
This crate is unstable and the API and usage may change.
This crate provides a long-running future that produces finality notifications.
# Usage
First, create a block-import wrapper with the `block_import` function. The
GRANDPA worker needs to be linked together with this block import object, so
a `LinkHalf` is returned as well. All blocks imported (from network or
consensus or otherwise) must pass through this wrapper, otherwise consensus
is likely to break in unexpected ways.
Next, use the `LinkHalf` and a local configuration to `run_grandpa_voter`.
This requires a `Network` implementation. The returned future should be
driven to completion and will finalize blocks in the background.
# Changing authority sets
The rough idea behind changing authority sets in GRANDPA is that at some point,
we obtain agreement for some maximum block height that the current set can
finalize, and once a block with that height is finalized the next set will
pick up finalization from there.
Technically speaking, this would be implemented as a voting rule which says,
"if there is a signal for a change in N blocks in block B, only vote on
chains with length NUM(B) + N if they contain B". This conditional-inclusion
logic is complex to compute because it requires looking arbitrarily far
back in the chain.
Instead, we keep track of a list of all signals we've seen so far (across
all forks), sorted ascending by the block number they would be applied at.
We never vote on chains with number higher than the earliest handoff block
number (this is num(signal) + N). When finalizing a block, we either apply
or prune any signaled changes based on whether the signaling block is
included in the newly-finalized chain.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,57 @@
[package]
name = "pezsc-consensus-grandpa-rpc"
version = "0.19.0"
authors.workspace = true
description = "RPC extensions for the GRANDPA finality gadget"
repository.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
readme = "README.md"
homepage.workspace = true
[lints]
workspace = true
[dependencies]
codec = { features = ["derive"], workspace = true, default-features = true }
finality-grandpa = { features = [
"derive-codec",
], workspace = true, default-features = true }
futures = { workspace = true }
jsonrpsee = { features = [
"client-core",
"macros",
"server-core",
], workspace = true }
log = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-consensus-grandpa = { workspace = true, default-features = true }
pezsc-rpc = { workspace = true, default-features = true }
serde = { features = ["derive"], workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
thiserror = { workspace = true }
[dev-dependencies]
pezsc-block-builder = { workspace = true, default-features = true }
pezsc-rpc = { features = [
"test-helpers",
], workspace = true, default-features = true }
pezsp-consensus-grandpa = { workspace = true, default-features = true }
pezsp-keyring = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
tokio = { features = ["macros"], workspace = true, default-features = true }
[features]
runtime-benchmarks = [
"pezsc-block-builder/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-consensus-grandpa/runtime-benchmarks",
"pezsc-rpc/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus-grandpa/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,3 @@
RPC API for GRANDPA.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,73 @@
// 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 jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
#[derive(Debug, thiserror::Error)]
/// Top-level error type for the RPC handler
pub enum Error {
/// The GRANDPA RPC endpoint is not ready.
#[error("GRANDPA RPC endpoint not ready")]
EndpointNotReady,
/// GRANDPA reports the authority set id to be larger than 32-bits.
#[error("GRANDPA reports authority set id unreasonably large")]
AuthoritySetIdReportedAsUnreasonablyLarge,
/// GRANDPA reports voter state with round id or weights larger than 32-bits.
#[error("GRANDPA reports voter state as unreasonably large")]
VoterStateReportsUnreasonablyLargeNumbers,
/// GRANDPA prove finality failed.
#[error("GRANDPA prove finality rpc failed: {0}")]
ProveFinalityFailed(#[from] pezsc_consensus_grandpa::FinalityProofError),
}
/// The error codes returned by jsonrpc.
pub enum ErrorCode {
/// Returned when Grandpa RPC endpoint is not ready.
NotReady = 1,
/// Authority set ID is larger than 32-bits.
AuthoritySetTooLarge,
/// Voter state with round id or weights larger than 32-bits.
VoterStateTooLarge,
/// Failed to prove finality.
ProveFinality,
}
impl From<Error> for ErrorCode {
fn from(error: Error) -> Self {
match error {
Error::EndpointNotReady => ErrorCode::NotReady,
Error::AuthoritySetIdReportedAsUnreasonablyLarge => ErrorCode::AuthoritySetTooLarge,
Error::VoterStateReportsUnreasonablyLargeNumbers => ErrorCode::VoterStateTooLarge,
Error::ProveFinalityFailed(_) => ErrorCode::ProveFinality,
}
}
}
impl From<Error> for ErrorObjectOwned {
fn from(error: Error) -> Self {
let message = error.to_string();
let code = ErrorCode::from(error);
ErrorObject::owned(code as i32, message, None::<()>)
}
}
impl From<std::num::TryFromIntError> for Error {
fn from(_error: std::num::TryFromIntError) -> Self {
Error::VoterStateReportsUnreasonablyLargeNumbers
}
}
@@ -0,0 +1,49 @@
// 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 serde::{Deserialize, Serialize};
use pezsc_consensus_grandpa::FinalityProofProvider;
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
#[derive(Clone, Serialize, Deserialize)]
pub struct EncodedFinalityProof(pub pezsp_core::Bytes);
/// Local trait mainly to allow mocking in tests.
pub trait RpcFinalityProofProvider<Block: BlockT> {
/// Prove finality for the given block number by returning a Justification for the last block of
/// the authority set.
fn rpc_prove_finality(
&self,
block: NumberFor<Block>,
) -> Result<Option<EncodedFinalityProof>, pezsc_consensus_grandpa::FinalityProofError>;
}
impl<B, Block> RpcFinalityProofProvider<Block> for FinalityProofProvider<B, Block>
where
Block: BlockT,
NumberFor<Block>: finality_grandpa::BlockNumberOps,
B: pezsc_client_api::backend::Backend<Block> + Send + Sync + 'static,
{
fn rpc_prove_finality(
&self,
block: NumberFor<Block>,
) -> Result<Option<EncodedFinalityProof>, pezsc_consensus_grandpa::FinalityProofError> {
self.prove_finality(block).map(|x| x.map(|y| EncodedFinalityProof(y.into())))
}
}
@@ -0,0 +1,415 @@
// 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/>.
//! RPC API for GRANDPA.
#![warn(missing_docs)]
use futures::StreamExt;
use log::warn;
use std::sync::Arc;
use jsonrpsee::{
core::{async_trait, server::PendingSubscriptionSink},
proc_macros::rpc,
};
mod error;
mod finality;
mod notification;
mod report;
use error::Error;
use finality::{EncodedFinalityProof, RpcFinalityProofProvider};
use notification::JustificationNotification;
use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates};
use pezsc_consensus_grandpa::GrandpaJustificationStream;
use pezsc_rpc::{
utils::{BoundedVecDeque, PendingSubscription},
SubscriptionTaskExecutor,
};
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
/// Provides RPC methods for interacting with GRANDPA.
#[rpc(client, server)]
pub trait GrandpaApi<Notification, Hash, Number> {
/// Returns the state of the current best round state as well as the
/// ongoing background rounds.
#[method(name = "grandpa_roundState")]
async fn round_state(&self) -> Result<ReportedRoundStates, Error>;
/// Returns the block most recently finalized by Grandpa, alongside
/// side its justification.
#[subscription(
name = "grandpa_subscribeJustifications" => "grandpa_justifications",
unsubscribe = "grandpa_unsubscribeJustifications",
item = Notification
)]
fn subscribe_justifications(&self);
/// Prove finality for the given block number by returning the Justification for the last block
/// in the set and all the intermediary headers to link them together.
#[method(name = "grandpa_proveFinality")]
async fn prove_finality(&self, block: Number) -> Result<Option<EncodedFinalityProof>, Error>;
}
/// Provides RPC methods for interacting with GRANDPA.
pub struct Grandpa<AuthoritySet, VoterState, Block: BlockT, ProofProvider> {
executor: SubscriptionTaskExecutor,
authority_set: AuthoritySet,
voter_state: VoterState,
justification_stream: GrandpaJustificationStream<Block>,
finality_proof_provider: Arc<ProofProvider>,
}
impl<AuthoritySet, VoterState, Block: BlockT, ProofProvider>
Grandpa<AuthoritySet, VoterState, Block, ProofProvider>
{
/// Prepare a new [`Grandpa`] Rpc handler.
pub fn new(
executor: SubscriptionTaskExecutor,
authority_set: AuthoritySet,
voter_state: VoterState,
justification_stream: GrandpaJustificationStream<Block>,
finality_proof_provider: Arc<ProofProvider>,
) -> Self {
Self { executor, authority_set, voter_state, justification_stream, finality_proof_provider }
}
}
#[async_trait]
impl<AuthoritySet, VoterState, Block, ProofProvider>
GrandpaApiServer<JustificationNotification, Block::Hash, NumberFor<Block>>
for Grandpa<AuthoritySet, VoterState, Block, ProofProvider>
where
VoterState: ReportVoterState + Send + Sync + 'static,
AuthoritySet: ReportAuthoritySet + Send + Sync + 'static,
Block: BlockT,
ProofProvider: RpcFinalityProofProvider<Block> + Send + Sync + 'static,
{
async fn round_state(&self) -> Result<ReportedRoundStates, Error> {
ReportedRoundStates::from(&self.authority_set, &self.voter_state)
}
fn subscribe_justifications(&self, pending: PendingSubscriptionSink) {
let stream = self.justification_stream.subscribe(100_000).map(
|x: pezsc_consensus_grandpa::GrandpaJustification<Block>| {
JustificationNotification::from(x)
},
);
pezsc_rpc::utils::spawn_subscription_task(
&self.executor,
PendingSubscription::from(pending).pipe_from_stream(stream, BoundedVecDeque::default()),
);
}
async fn prove_finality(
&self,
block: NumberFor<Block>,
) -> Result<Option<EncodedFinalityProof>, Error> {
self.finality_proof_provider.rpc_prove_finality(block).map_err(|e| {
warn!("Error proving finality: {}", e);
error::Error::ProveFinalityFailed(e)
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{collections::HashSet, sync::Arc};
use codec::{Decode, Encode};
use jsonrpsee::{core::EmptyServerParams as EmptyParams, types::SubscriptionId, RpcModule};
use pezsc_block_builder::BlockBuilderBuilder;
use pezsc_consensus_grandpa::{
report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender,
};
use pezsc_rpc::testing::test_executor;
use pezsp_blockchain::HeaderBackend;
use pezsp_core::crypto::ByteArray;
use pezsp_keyring::Ed25519Keyring;
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use bizinikiwi_test_runtime_client::{
runtime::{Block, Header, H256},
DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt,
};
struct TestAuthoritySet;
struct TestVoterState;
struct EmptyVoterState;
struct TestFinalityProofProvider {
finality_proof: Option<FinalityProof<Header>>,
}
fn voters() -> HashSet<AuthorityId> {
let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap();
let voter_id_2 = AuthorityId::from_slice(&[2; 32]).unwrap();
vec![voter_id_1, voter_id_2].into_iter().collect()
}
impl ReportAuthoritySet for TestAuthoritySet {
fn get(&self) -> (u64, HashSet<AuthorityId>) {
(1, voters())
}
}
impl ReportVoterState for EmptyVoterState {
fn get(&self) -> Option<report::VoterState<AuthorityId>> {
None
}
}
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(),
)
}
impl<Block: BlockT> RpcFinalityProofProvider<Block> for TestFinalityProofProvider {
fn rpc_prove_finality(
&self,
_block: NumberFor<Block>,
) -> Result<Option<EncodedFinalityProof>, pezsc_consensus_grandpa::FinalityProofError> {
Ok(Some(EncodedFinalityProof(
self.finality_proof
.as_ref()
.expect("Don't call rpc_prove_finality without setting the FinalityProof")
.encode()
.into(),
)))
}
}
impl ReportVoterState for TestVoterState {
fn get(&self) -> Option<report::VoterState<AuthorityId>> {
let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap();
let voters_best: HashSet<_> = vec![voter_id_1].into_iter().collect();
let best_round_state = pezsc_consensus_grandpa::report::RoundState {
total_weight: 100_u64.try_into().unwrap(),
threshold_weight: 67_u64.try_into().unwrap(),
prevote_current_weight: 50.into(),
prevote_ids: voters_best,
precommit_current_weight: 0.into(),
precommit_ids: HashSet::new(),
};
let past_round_state = pezsc_consensus_grandpa::report::RoundState {
total_weight: 100_u64.try_into().unwrap(),
threshold_weight: 67_u64.try_into().unwrap(),
prevote_current_weight: 100.into(),
prevote_ids: voters(),
precommit_current_weight: 100.into(),
precommit_ids: voters(),
};
let background_rounds = vec![(1, past_round_state)].into_iter().collect();
Some(report::VoterState { background_rounds, best_round: (2, best_round_state) })
}
}
fn setup_io_handler<VoterState>(
voter_state: VoterState,
) -> (
RpcModule<Grandpa<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
GrandpaJustificationSender<Block>,
)
where
VoterState: ReportVoterState + Send + Sync + 'static,
{
setup_io_handler_with_finality_proofs(voter_state, None)
}
fn setup_io_handler_with_finality_proofs<VoterState>(
voter_state: VoterState,
finality_proof: Option<FinalityProof<Header>>,
) -> (
RpcModule<Grandpa<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
GrandpaJustificationSender<Block>,
)
where
VoterState: ReportVoterState + Send + Sync + 'static,
{
let (justification_sender, justification_stream) = GrandpaJustificationStream::channel();
let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof });
let executor = test_executor();
let rpc = Grandpa::new(
executor,
TestAuthoritySet,
voter_state,
justification_stream,
finality_proof_provider,
)
.into_rpc();
(rpc, justification_sender)
}
#[tokio::test]
async fn uninitialized_rpc_handler() {
let (rpc, _) = setup_io_handler(EmptyVoterState);
let expected_response = r#"{"jsonrpc":"2.0","id":0,"error":{"code":1,"message":"GRANDPA RPC endpoint not ready"}}"#.to_string();
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
assert_eq!(expected_response, response);
}
#[tokio::test]
async fn working_rpc_handler() {
let (rpc, _) = setup_io_handler(TestVoterState);
let expected_response = "{\"jsonrpc\":\"2.0\",\"id\":0,\"result\":{\
\"setId\":1,\
\"best\":{\
\"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\
\"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
\"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]}\
},\
\"background\":[{\
\"round\":1,\"totalWeight\":100,\"thresholdWeight\":67,\
\"prevotes\":{\"currentWeight\":100,\"missing\":[]},\
\"precommits\":{\"currentWeight\":100,\"missing\":[]}\
}]\
}}".to_string();
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
assert_eq!(expected_response, response);
}
#[tokio::test]
async fn subscribe_and_unsubscribe_with_wrong_id() {
let (rpc, _) = setup_io_handler(TestVoterState);
// Subscribe call.
let _sub = rpc
.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Unsubscribe with wrong ID
let (response, _) = rpc
.raw_json_request(
r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#,
1,
)
.await
.unwrap();
let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#;
assert_eq!(response, expected);
}
fn create_justification() -> GrandpaJustification<Block> {
let peers = &[Ed25519Keyring::Alice];
let builder = TestClientBuilder::new();
let client = builder.build();
let client = Arc::new(client);
let built_block = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.info().best_hash)
.with_parent_block_number(client.info().best_number)
.build()
.unwrap()
.build()
.unwrap();
let block = built_block.block;
let block_hash = block.hash();
let justification = {
let round = 1;
let set_id = 0;
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 = peers[0].sign(&encoded[..]).into();
let precommit = finality_grandpa::SignedPrecommit {
precommit,
signature,
id: peers[0].public().into(),
};
let commit = finality_grandpa::Commit {
target_hash: block_hash,
target_number: *block.header.number(),
precommits: vec![precommit],
};
GrandpaJustification::from_commit(&client, round, commit).unwrap()
};
justification
}
#[tokio::test]
async fn subscribe_and_listen_to_one_justification() {
let (rpc, justification_sender) = setup_io_handler(TestVoterState);
let mut sub = rpc
.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
.await
.unwrap();
// Notify with a header and justification
let justification = create_justification();
justification_sender.notify(|| Ok::<_, ()>(justification.clone())).unwrap();
// Inspect what we received
let (recv_justification, recv_sub_id): (pezsp_core::Bytes, SubscriptionId) =
sub.next().await.unwrap().unwrap();
let recv_justification: GrandpaJustification<Block> =
Decode::decode(&mut &recv_justification[..]).unwrap();
assert_eq!(&recv_sub_id, sub.subscription_id());
assert_eq!(recv_justification, justification);
}
#[tokio::test]
async fn prove_finality_with_test_finality_proof_provider() {
let finality_proof = FinalityProof {
block: header(42).hash(),
justification: create_justification().encode(),
unknown_headers: vec![header(2)],
};
let (rpc, _) =
setup_io_handler_with_finality_proofs(TestVoterState, Some(finality_proof.clone()));
let bytes: pezsp_core::Bytes = rpc.call("grandpa_proveFinality", [42]).await.unwrap();
let finality_proof_rpc: FinalityProof<Header> = Decode::decode(&mut &bytes[..]).unwrap();
assert_eq!(finality_proof_rpc, finality_proof);
}
}
@@ -0,0 +1,32 @@
// 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 codec::Encode;
use pezsc_consensus_grandpa::GrandpaJustification;
use serde::{Deserialize, Serialize};
use pezsp_runtime::traits::Block as BlockT;
/// An encoded justification proving that the given header has been finalized
#[derive(Clone, Serialize, Deserialize)]
pub struct JustificationNotification(pezsp_core::Bytes);
impl<Block: BlockT> From<GrandpaJustification<Block>> for JustificationNotification {
fn from(notification: GrandpaJustification<Block>) -> Self {
JustificationNotification(notification.encode().into())
}
}
@@ -0,0 +1,150 @@
// 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::{BTreeSet, HashSet},
fmt::Debug,
ops::Add,
};
use serde::{Deserialize, Serialize};
use pezsc_consensus_grandpa::{report, AuthorityId, SharedAuthoritySet, SharedVoterState};
use crate::error::Error;
/// Utility trait to get reporting data for the current GRANDPA authority set.
pub trait ReportAuthoritySet {
fn get(&self) -> (u64, HashSet<AuthorityId>);
}
/// Utility trait to get reporting data for the current GRANDPA voter state.
pub trait ReportVoterState {
fn get(&self) -> Option<report::VoterState<AuthorityId>>;
}
impl<H, N> ReportAuthoritySet for SharedAuthoritySet<H, N>
where
N: Add<Output = N> + Ord + Clone + Debug,
H: Clone + Debug + Eq,
{
fn get(&self) -> (u64, HashSet<AuthorityId>) {
let current_voters: HashSet<AuthorityId> =
self.current_authorities().iter().map(|p| p.0.clone()).collect();
(self.set_id(), current_voters)
}
}
impl ReportVoterState for SharedVoterState {
fn get(&self) -> Option<report::VoterState<AuthorityId>> {
self.voter_state()
}
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Prevotes {
current_weight: u32,
missing: BTreeSet<AuthorityId>,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Precommits {
current_weight: u32,
missing: BTreeSet<AuthorityId>,
}
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct RoundState {
round: u32,
total_weight: u32,
threshold_weight: u32,
prevotes: Prevotes,
precommits: Precommits,
}
impl RoundState {
fn from(
round: u64,
round_state: &report::RoundState<AuthorityId>,
voters: &HashSet<AuthorityId>,
) -> Result<Self, Error> {
let prevotes = &round_state.prevote_ids;
let missing_prevotes = voters.difference(prevotes).cloned().collect();
let precommits = &round_state.precommit_ids;
let missing_precommits = voters.difference(precommits).cloned().collect();
Ok(Self {
round: round.try_into()?,
total_weight: round_state.total_weight.get().try_into()?,
threshold_weight: round_state.threshold_weight.get().try_into()?,
prevotes: Prevotes {
current_weight: round_state.prevote_current_weight.0.try_into()?,
missing: missing_prevotes,
},
precommits: Precommits {
current_weight: round_state.precommit_current_weight.0.try_into()?,
missing: missing_precommits,
},
})
}
}
/// The state of the current best round, as well as the background rounds in a
/// form suitable for serialization.
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReportedRoundStates {
set_id: u32,
best: RoundState,
background: Vec<RoundState>,
}
impl ReportedRoundStates {
pub fn from<AuthoritySet, VoterState>(
authority_set: &AuthoritySet,
voter_state: &VoterState,
) -> Result<Self, Error>
where
AuthoritySet: ReportAuthoritySet,
VoterState: ReportVoterState,
{
let voter_state = voter_state.get().ok_or(Error::EndpointNotReady)?;
let (set_id, current_voters) = authority_set.get();
let set_id =
u32::try_from(set_id).map_err(|_| Error::AuthoritySetIdReportedAsUnreasonablyLarge)?;
let best = {
let (round, round_state) = voter_state.best_round;
RoundState::from(round, &round_state, &current_voters)?
};
let background = voter_state
.background_rounds
.iter()
.map(|(round, round_state)| RoundState::from(*round, round_state, &current_voters))
.collect::<Result<Vec<_>, Error>>()?;
Ok(Self { set_id, best, background })
}
}
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);
}
}