mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Expose GRANDPA round state through RPC (#5375)
* grandpa: wire up basic RPC call * grandpa: make it compile against GRANDPA with expose round state * grandpa: use shared voter state to expose RPC endpoint * grandpa: restructure into nested structs * grandpa: return background rounds too * grandpa: return error when endpoint not ready * grandpa: collect grandpa rpc deps * grandpa: decide to use concrete AuthorityId in finality-grandpa-rpc * grandpa: remove unncessary type annotation * grandpa: move error code to const * grandpa: remove unnecessary WIP comment * grandpa: remove Id type parameter for SharedVoterState * grandpa: update tests to add shared_voter_state in parameters * grandpa: remove old deprecated test * grandpa: fix getting the correct set_id * grandpa: make SharedVoterState a struct * grandpa: wrap shared_voter_state in rpc_setup * grandpa: replace spaces with tabs * grandpa: limit RwLock write attempt to 1 sec * grandpa: add missing doc comments and remove some pub * Apply suggestions from code review Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> Co-Authored-By: Hernando Castano <HCastano@users.noreply.github.com> * grandpa: update function name call after change in finality-grandpa * grandpa: group pub use and only export voter::report * grandpa: add missing docs * grandpa: extract out structs used for json serialization * grandpa: stick to u32 for fields intended for js * grandpa: move Error type to its own file * grandpa: group pub use better * Apply code review suggestion Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * grandpa: use correct version of finality-granpda in rpc crate * grandpa: add back basic rpc unit test * grandpa: replace SharedVoterState::new() with empty() * node: cleanup grandpa::SharedVoterState usage in macro * grandpa: remove VoterState error variant * grandpa: enable missing futures compat feature * grandpa: fix typo in error variant * grandpa: remove test_utils * grandpa: allow mocking rpc handler components * grandpa: rename serialized to report in rpc module * grandpa: add proper test for RPC * grandpa: update to finality-grandpa v0.12.1 Co-authored-by: André Silva <andre.beat@gmail.com> Co-authored-by: Demi Obenour <demi@parity.io> Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Generated
+23
-2
@@ -1352,9 +1352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "finality-grandpa"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d7907cc24468e29b5d3ea2097e78104492b6650c55f96af1f14e7915dc155ad"
|
||||
checksum = "5ab32971efbe776e46bfbc34d5b662d8e1de51fd14e26a2eba522c0f3470fc0f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures 0.3.4",
|
||||
@@ -3447,6 +3447,7 @@ dependencies = [
|
||||
"pallet-timestamp",
|
||||
"pallet-transaction-payment",
|
||||
"parity-scale-codec",
|
||||
"parking_lot 0.10.2",
|
||||
"platforms",
|
||||
"rand 0.7.3",
|
||||
"regex",
|
||||
@@ -3565,6 +3566,8 @@ dependencies = [
|
||||
"sc-consensus-babe",
|
||||
"sc-consensus-babe-rpc",
|
||||
"sc-consensus-epochs",
|
||||
"sc-finality-grandpa",
|
||||
"sc-finality-grandpa-rpc",
|
||||
"sc-keystore",
|
||||
"sp-api",
|
||||
"sp-blockchain",
|
||||
@@ -3660,6 +3663,7 @@ dependencies = [
|
||||
"futures 0.3.4",
|
||||
"log",
|
||||
"node-template-runtime",
|
||||
"parking_lot 0.10.2",
|
||||
"sc-basic-authorship",
|
||||
"sc-cli",
|
||||
"sc-client-api",
|
||||
@@ -6400,6 +6404,23 @@ dependencies = [
|
||||
"tokio 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sc-finality-grandpa-rpc"
|
||||
version = "0.8.0-dev"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"finality-grandpa",
|
||||
"futures 0.3.4",
|
||||
"jsonrpc-core",
|
||||
"jsonrpc-core-client",
|
||||
"jsonrpc-derive",
|
||||
"log",
|
||||
"sc-finality-grandpa",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sp-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sc-informant"
|
||||
version = "0.8.0-dev"
|
||||
|
||||
@@ -19,6 +19,7 @@ name = "node-template"
|
||||
futures = "0.3.4"
|
||||
log = "0.4.8"
|
||||
structopt = "0.3.8"
|
||||
parking_lot = "0.10.0"
|
||||
|
||||
sc-cli = { version = "0.8.0-dev", path = "../../../client/cli" }
|
||||
sp-core = { version = "2.0.0-dev", path = "../../../primitives/core" }
|
||||
|
||||
@@ -10,7 +10,9 @@ use sp_inherents::InherentDataProviders;
|
||||
use sc_executor::native_executor_instance;
|
||||
pub use sc_executor::NativeExecutor;
|
||||
use sp_consensus_aura::sr25519::{AuthorityPair as AuraPair};
|
||||
use sc_finality_grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider};
|
||||
use sc_finality_grandpa::{
|
||||
FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider, SharedVoterState,
|
||||
};
|
||||
|
||||
// Our native executor instance.
|
||||
native_executor_instance!(
|
||||
@@ -157,7 +159,8 @@ pub fn new_full(config: Configuration) -> Result<impl AbstractService, ServiceEr
|
||||
inherent_data_providers: inherent_data_providers.clone(),
|
||||
telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
|
||||
voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(),
|
||||
prometheus_registry: service.prometheus_registry()
|
||||
prometheus_registry: service.prometheus_registry(),
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
|
||||
// the GRANDPA voter task is considered infallible, i.e.
|
||||
|
||||
@@ -43,6 +43,7 @@ log = "0.4.8"
|
||||
rand = "0.7.2"
|
||||
structopt = { version = "0.3.8", optional = true }
|
||||
tracing = "0.1.10"
|
||||
parking_lot = "0.10.0"
|
||||
|
||||
# primitives
|
||||
sp-authority-discovery = { version = "2.0.0-dev", path = "../../../primitives/authority-discovery" }
|
||||
|
||||
@@ -21,7 +21,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use sc_consensus_babe;
|
||||
use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider};
|
||||
use grandpa::{
|
||||
self, FinalityProofProvider as GrandpaFinalityProofProvider, StorageAndProofProvider,
|
||||
};
|
||||
use node_executor;
|
||||
use node_primitives::Block;
|
||||
use node_runtime::RuntimeApi;
|
||||
@@ -38,8 +40,10 @@ use sc_consensus::LongestChain;
|
||||
macro_rules! new_full_start {
|
||||
($config:expr) => {{
|
||||
use std::sync::Arc;
|
||||
|
||||
type RpcExtension = jsonrpc_core::IoHandler<sc_rpc::Metadata>;
|
||||
let mut import_setup = None;
|
||||
let mut rpc_setup = None;
|
||||
let inherent_data_providers = sp_inherents::InherentDataProviders::new();
|
||||
|
||||
let builder = sc_service::ServiceBuilder::new_full::<
|
||||
@@ -88,6 +92,10 @@ macro_rules! new_full_start {
|
||||
.with_rpc_extensions(|builder| -> std::result::Result<RpcExtension, _> {
|
||||
let babe_link = import_setup.as_ref().map(|s| &s.2)
|
||||
.expect("BabeLink is present for full services or set up failed; qed.");
|
||||
let grandpa_link = import_setup.as_ref().map(|s| &s.1)
|
||||
.expect("GRANDPA LinkHalf is present for full services or set up failed; qed.");
|
||||
let shared_authority_set = grandpa_link.shared_authority_set();
|
||||
let shared_voter_state = grandpa::SharedVoterState::empty();
|
||||
let deps = node_rpc::FullDeps {
|
||||
client: builder.client().clone(),
|
||||
pool: builder.pool(),
|
||||
@@ -97,12 +105,17 @@ macro_rules! new_full_start {
|
||||
keystore: builder.keystore(),
|
||||
babe_config: sc_consensus_babe::BabeLink::config(babe_link).clone(),
|
||||
shared_epoch_changes: sc_consensus_babe::BabeLink::epoch_changes(babe_link).clone()
|
||||
}
|
||||
},
|
||||
grandpa: node_rpc::GrandpaDeps {
|
||||
shared_voter_state: shared_voter_state.clone(),
|
||||
shared_authority_set: shared_authority_set.clone(),
|
||||
},
|
||||
};
|
||||
rpc_setup = Some((shared_voter_state));
|
||||
Ok(node_rpc::create_full(deps))
|
||||
})?;
|
||||
|
||||
(builder, import_setup, inherent_data_providers)
|
||||
(builder, import_setup, inherent_data_providers, rpc_setup)
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -128,7 +141,8 @@ macro_rules! new_full {
|
||||
$config.disable_grandpa,
|
||||
);
|
||||
|
||||
let (builder, mut import_setup, inherent_data_providers) = new_full_start!($config);
|
||||
let (builder, mut import_setup, inherent_data_providers, mut rpc_setup) =
|
||||
new_full_start!($config);
|
||||
|
||||
let service = builder
|
||||
.with_finality_proof_provider(|client, backend| {
|
||||
@@ -139,7 +153,10 @@ macro_rules! new_full {
|
||||
.build()?;
|
||||
|
||||
let (block_import, grandpa_link, babe_link) = import_setup.take()
|
||||
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
|
||||
.expect("Link Half and Block Import are present for Full Services or setup failed before. qed");
|
||||
|
||||
let shared_voter_state = rpc_setup.take()
|
||||
.expect("The SharedVoterState is present for Full Services or setup failed before. qed");
|
||||
|
||||
($with_startup_data)(&block_import, &babe_link);
|
||||
|
||||
@@ -240,6 +257,7 @@ macro_rules! new_full {
|
||||
telemetry_on_connect: Some(service.telemetry_on_connect_stream()),
|
||||
voting_rule: grandpa::VotingRulesBuilder::default().build(),
|
||||
prometheus_registry: service.prometheus_registry(),
|
||||
shared_voter_state,
|
||||
};
|
||||
|
||||
// the GRANDPA voter task is considered infallible, i.e.
|
||||
|
||||
@@ -28,3 +28,5 @@ sc-keystore = { version = "2.0.0-dev", path = "../../../client/keystore" }
|
||||
sc-consensus-epochs = { version = "0.8.0-dev", path = "../../../client/consensus/epochs" }
|
||||
sp-consensus = { version = "0.8.0-dev", path = "../../../primitives/consensus/common" }
|
||||
sp-blockchain = { version = "2.0.0-dev", path = "../../../primitives/blockchain" }
|
||||
sc-finality-grandpa = { version = "0.8.0-dev", path = "../../../client/finality-grandpa" }
|
||||
sc-finality-grandpa-rpc = { version = "0.8.0-dev", path = "../../../client/finality-grandpa/rpc" }
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
use std::{sync::Arc, fmt};
|
||||
|
||||
use node_primitives::{Block, BlockNumber, AccountId, Index, Balance};
|
||||
use node_primitives::{Block, BlockNumber, AccountId, Index, Balance, Hash};
|
||||
use node_runtime::UncheckedExtrinsic;
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_transaction_pool::TransactionPool;
|
||||
@@ -42,6 +42,8 @@ use sp_consensus_babe::BabeApi;
|
||||
use sc_consensus_epochs::SharedEpochChanges;
|
||||
use sc_consensus_babe::{Config, Epoch};
|
||||
use sc_consensus_babe_rpc::BabeRPCHandler;
|
||||
use sc_finality_grandpa::{SharedVoterState, SharedAuthoritySet};
|
||||
use sc_finality_grandpa_rpc::GrandpaRpcHandler;
|
||||
|
||||
/// Light client extra dependencies.
|
||||
pub struct LightDeps<C, F, P> {
|
||||
@@ -65,6 +67,14 @@ pub struct BabeDeps {
|
||||
pub keystore: KeyStorePtr,
|
||||
}
|
||||
|
||||
/// Extra dependencies for GRANDPA
|
||||
pub struct GrandpaDeps {
|
||||
/// Voting round info.
|
||||
pub shared_voter_state: SharedVoterState,
|
||||
/// Authority set info.
|
||||
pub shared_authority_set: SharedAuthoritySet<Hash, BlockNumber>,
|
||||
}
|
||||
|
||||
/// Full client dependencies.
|
||||
pub struct FullDeps<C, P, SC> {
|
||||
/// The client instance to use.
|
||||
@@ -75,6 +85,8 @@ pub struct FullDeps<C, P, SC> {
|
||||
pub select_chain: SC,
|
||||
/// BABE specific dependencies.
|
||||
pub babe: BabeDeps,
|
||||
/// GRANDPA specific dependencies.
|
||||
pub grandpa: GrandpaDeps,
|
||||
}
|
||||
|
||||
/// Instantiate all Full RPC extensions.
|
||||
@@ -102,13 +114,18 @@ pub fn create_full<C, P, M, SC>(
|
||||
client,
|
||||
pool,
|
||||
select_chain,
|
||||
babe
|
||||
babe,
|
||||
grandpa,
|
||||
} = deps;
|
||||
let BabeDeps {
|
||||
keystore,
|
||||
babe_config,
|
||||
shared_epoch_changes,
|
||||
} = babe;
|
||||
let GrandpaDeps {
|
||||
shared_voter_state,
|
||||
shared_authority_set,
|
||||
} = grandpa;
|
||||
|
||||
io.extend_with(
|
||||
SystemApi::to_delegate(FullSystem::new(client.clone(), pool))
|
||||
@@ -127,6 +144,11 @@ pub fn create_full<C, P, M, SC>(
|
||||
BabeRPCHandler::new(client, shared_epoch_changes, keystore, babe_config, select_chain)
|
||||
)
|
||||
);
|
||||
io.extend_with(
|
||||
sc_finality_grandpa_rpc::GrandpaApi::to_delegate(
|
||||
GrandpaRpcHandler::new(shared_authority_set, shared_voter_state)
|
||||
)
|
||||
);
|
||||
|
||||
io
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ sp-finality-tracker = { version = "2.0.0-dev", path = "../../primitives/finality
|
||||
sp-finality-grandpa = { version = "2.0.0-dev", path = "../../primitives/finality-grandpa" }
|
||||
prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.8.0-dev"}
|
||||
sc-block-builder = { version = "0.8.0-dev", path = "../block-builder" }
|
||||
finality-grandpa = { version = "0.12.0", features = ["derive-codec"] }
|
||||
finality-grandpa = { version = "0.12.1", features = ["derive-codec"] }
|
||||
pin-project = "0.4.6"
|
||||
|
||||
[dev-dependencies]
|
||||
finality-grandpa = { version = "0.12.0", features = ["derive-codec", "test-helpers"] }
|
||||
finality-grandpa = { version = "0.12.1", features = ["derive-codec", "test-helpers"] }
|
||||
sc-network = { version = "0.8.0-dev", path = "../network" }
|
||||
sc-network-test = { version = "0.8.0-dev", path = "../network/test" }
|
||||
sp-keyring = { version = "2.0.0-dev", path = "../../primitives/keyring" }
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "sc-finality-grandpa-rpc"
|
||||
version = "0.8.0-dev"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
description = "RPC extensions for the GRANDPA finality gadget"
|
||||
edition = "2018"
|
||||
license = "GPL-3.0"
|
||||
|
||||
[dependencies]
|
||||
sc-finality-grandpa = { version = "0.8.0-dev", path = "../" }
|
||||
finality-grandpa = { version = "0.12.1", features = ["derive-codec"] }
|
||||
jsonrpc-core = "14.0.3"
|
||||
jsonrpc-core-client = "14.0.3"
|
||||
jsonrpc-derive = "14.0.3"
|
||||
futures = { version = "0.3.4", features = ["compat"] }
|
||||
serde = { version = "1.0.105", features = ["derive"] }
|
||||
serde_json = "1.0.50"
|
||||
log = "0.4.8"
|
||||
derive_more = "0.99.2"
|
||||
|
||||
[dev-dependencies]
|
||||
sp-core = { version = "2.0.0-dev", path = "../../../primitives/core" }
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use crate::NOT_READY_ERROR_CODE;
|
||||
|
||||
#[derive(derive_more::Display, derive_more::From)]
|
||||
/// Top-level error type for the RPC handler
|
||||
pub enum Error {
|
||||
/// The GRANDPA RPC endpoint is not ready.
|
||||
#[display(fmt = "GRANDPA RPC endpoint not ready")]
|
||||
EndpointNotReady,
|
||||
/// GRANDPA reports the authority set id to be larger than 32-bits.
|
||||
#[display(fmt = "GRANDPA reports authority set id unreasonably large")]
|
||||
AuthoritySetIdReportedAsUnreasonablyLarge,
|
||||
/// GRANDPA reports voter state with round id or weights larger than 32-bits.
|
||||
#[display(fmt = "GRANDPA reports voter state as unreasonably large")]
|
||||
VoterStateReportsUnreasonablyLargeNumbers,
|
||||
}
|
||||
|
||||
impl From<Error> for jsonrpc_core::Error {
|
||||
fn from(error: Error) -> Self {
|
||||
jsonrpc_core::Error {
|
||||
message: format!("{}", error).into(),
|
||||
code: jsonrpc_core::ErrorCode::ServerError(NOT_READY_ERROR_CODE),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::num::TryFromIntError> for Error {
|
||||
fn from(_error: std::num::TryFromIntError) -> Self {
|
||||
Error::VoterStateReportsUnreasonablyLargeNumbers
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! RPC API for GRANDPA.
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use jsonrpc_derive::rpc;
|
||||
|
||||
mod error;
|
||||
mod report;
|
||||
|
||||
use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates};
|
||||
|
||||
/// Returned when Grandpa RPC endpoint is not ready.
|
||||
pub const NOT_READY_ERROR_CODE: i64 = 1;
|
||||
|
||||
type FutureResult<T> =
|
||||
Box<dyn jsonrpc_core::futures::Future<Item = T, Error = jsonrpc_core::Error> + Send>;
|
||||
|
||||
/// Provides RPC methods for interacting with GRANDPA.
|
||||
#[rpc]
|
||||
pub trait GrandpaApi {
|
||||
/// Returns the state of the current best round state as well as the
|
||||
/// ongoing background rounds.
|
||||
#[rpc(name = "grandpa_roundState")]
|
||||
fn round_state(&self) -> FutureResult<ReportedRoundStates>;
|
||||
}
|
||||
|
||||
/// Implements the GrandpaApi RPC trait for interacting with GRANDPA.
|
||||
pub struct GrandpaRpcHandler<AuthoritySet, VoterState> {
|
||||
authority_set: AuthoritySet,
|
||||
voter_state: VoterState,
|
||||
}
|
||||
|
||||
impl<AuthoritySet, VoterState> GrandpaRpcHandler<AuthoritySet, VoterState> {
|
||||
/// Creates a new GrandpaRpcHander instance.
|
||||
pub fn new(authority_set: AuthoritySet, voter_state: VoterState) -> Self {
|
||||
Self {
|
||||
authority_set,
|
||||
voter_state,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<AuthoritySet, VoterState> GrandpaApi for GrandpaRpcHandler<AuthoritySet, VoterState>
|
||||
where
|
||||
VoterState: ReportVoterState + Send + Sync + 'static,
|
||||
AuthoritySet: ReportAuthoritySet + Send + Sync + 'static,
|
||||
{
|
||||
fn round_state(&self) -> FutureResult<ReportedRoundStates> {
|
||||
let round_states = ReportedRoundStates::from(&self.authority_set, &self.voter_state);
|
||||
let future = async move { round_states }.boxed();
|
||||
Box::new(future.map_err(jsonrpc_core::Error::from).compat())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use jsonrpc_core::IoHandler;
|
||||
use sc_finality_grandpa::{report, AuthorityId};
|
||||
use sp_core::crypto::Public;
|
||||
use std::{collections::HashSet, convert::TryInto};
|
||||
|
||||
struct TestAuthoritySet;
|
||||
struct TestVoterState;
|
||||
struct EmptyVoterState;
|
||||
|
||||
fn voters() -> HashSet<AuthorityId> {
|
||||
let voter_id_1 = AuthorityId::from_slice(&[1; 32]);
|
||||
let voter_id_2 = AuthorityId::from_slice(&[2; 32]);
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl ReportVoterState for TestVoterState {
|
||||
fn get(&self) -> Option<report::VoterState<AuthorityId>> {
|
||||
let voter_id_1 = AuthorityId::from_slice(&[1; 32]);
|
||||
let voters_best: HashSet<_> = vec![voter_id_1].into_iter().collect();
|
||||
|
||||
let best_round_state = 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 = 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uninitialized_rpc_handler() {
|
||||
let handler = GrandpaRpcHandler::new(TestAuthoritySet, EmptyVoterState);
|
||||
let mut io = IoHandler::new();
|
||||
io.extend_with(GrandpaApi::to_delegate(handler));
|
||||
|
||||
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#;
|
||||
let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":1}"#;
|
||||
|
||||
assert_eq!(Some(response.into()), io.handle_request_sync(request));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn working_rpc_handler() {
|
||||
let handler = GrandpaRpcHandler::new(TestAuthoritySet, TestVoterState);
|
||||
let mut io = IoHandler::new();
|
||||
io.extend_with(GrandpaApi::to_delegate(handler));
|
||||
|
||||
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#;
|
||||
let response = "{\"jsonrpc\":\"2.0\",\"result\":{\
|
||||
\"background\":[{\
|
||||
\"precommits\":{\"currentWeight\":100,\"missing\":[]},\
|
||||
\"prevotes\":{\"currentWeight\":100,\"missing\":[]},\
|
||||
\"round\":1,\"thresholdWeight\":67,\"totalWeight\":100\
|
||||
}],\
|
||||
\"best\":{\
|
||||
\"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
|
||||
\"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
|
||||
\"round\":2,\"thresholdWeight\":67,\"totalWeight\":100\
|
||||
},\
|
||||
\"setId\":1\
|
||||
},\"id\":1}";
|
||||
|
||||
assert_eq!(io.handle_request_sync(request), Some(response.into()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use std::{
|
||||
collections::{BTreeSet, HashSet},
|
||||
fmt::Debug,
|
||||
ops::Add,
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sc_finality_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(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Prevotes {
|
||||
current_weight: u32,
|
||||
missing: BTreeSet<AuthorityId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Precommits {
|
||||
current_weight: u32,
|
||||
missing: BTreeSet<AuthorityId>,
|
||||
}
|
||||
|
||||
#[derive(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> {
|
||||
use std::convert::TryInto;
|
||||
|
||||
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(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,
|
||||
{
|
||||
use std::convert::TryFrom;
|
||||
|
||||
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, ¤t_voters)?
|
||||
};
|
||||
|
||||
let background = voter_state
|
||||
.background_rounds
|
||||
.iter()
|
||||
.map(|(round, round_state)| RoundState::from(*round, round_state, ¤t_voters))
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
Ok(Self {
|
||||
set_id,
|
||||
best,
|
||||
background,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ pub enum Error<E> {
|
||||
}
|
||||
|
||||
/// A shared authority set.
|
||||
pub(crate) struct SharedAuthoritySet<H, N> {
|
||||
pub struct SharedAuthoritySet<H, N> {
|
||||
inner: Arc<RwLock<AuthoritySet<H, N>>>,
|
||||
}
|
||||
|
||||
@@ -67,12 +67,12 @@ where N: Add<Output=N> + Ord + Clone + Debug,
|
||||
}
|
||||
|
||||
/// Get the current set ID. This is incremented every time the set changes.
|
||||
pub(crate) fn set_id(&self) -> u64 {
|
||||
pub fn set_id(&self) -> u64 {
|
||||
self.inner.read().set_id
|
||||
}
|
||||
|
||||
/// Get the current authorities and their weights (for the current set ID).
|
||||
pub(crate) fn current_authorities(&self) -> VoterSet<AuthorityId> {
|
||||
pub fn current_authorities(&self) -> VoterSet<AuthorityId> {
|
||||
VoterSet::new(self.inner.read().current_authorities.iter().cloned()).expect(
|
||||
"current_authorities is non-empty and weights are non-zero; \
|
||||
constructor and all mutating operations on `AuthoritySet` ensure this; \
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
//! or prune any signaled changes based on whether the signaling block is
|
||||
//! included in the newly-finalized chain.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, info};
|
||||
@@ -72,6 +74,7 @@ use sp_core::Pair;
|
||||
use sp_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver};
|
||||
use sc_telemetry::{telemetry, CONSENSUS_INFO, CONSENSUS_DEBUG};
|
||||
use serde_json;
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use sp_finality_tracker;
|
||||
|
||||
@@ -114,12 +117,14 @@ mod observer;
|
||||
mod until_imported;
|
||||
mod voting_rule;
|
||||
|
||||
pub use authorities::SharedAuthoritySet;
|
||||
pub use finality_proof::{FinalityProofProvider, StorageAndProofProvider};
|
||||
pub use justification::GrandpaJustification;
|
||||
pub use light_import::light_block_import;
|
||||
pub use voting_rule::{
|
||||
BeforeBestBlockBy, ThreeQuartersOfTheUnfinalizedChain, VotingRule, VotingRulesBuilder
|
||||
};
|
||||
pub use finality_grandpa::voter::report;
|
||||
|
||||
use aux_schema::PersistentData;
|
||||
use environment::{Environment, VoterSetState};
|
||||
@@ -203,7 +208,44 @@ type CommunicationOutH<Block, H> = finality_grandpa::voter::CommunicationOut<
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// Configuration for the GRANDPA service.
|
||||
/// Shared voter state for querying.
|
||||
pub struct SharedVoterState {
|
||||
inner: Arc<RwLock<Option<Box<dyn voter::VoterState<AuthorityId> + Sync + Send>>>>,
|
||||
}
|
||||
|
||||
impl SharedVoterState {
|
||||
/// Create a new empty `SharedVoterState` instance.
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
inner: Arc::new(RwLock::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(
|
||||
&self,
|
||||
voter_state: Box<dyn voter::VoterState<AuthorityId> + Sync + Send>,
|
||||
) -> Option<()> {
|
||||
let mut shared_voter_state = self
|
||||
.inner
|
||||
.try_write_for(Duration::from_secs(1))?;
|
||||
|
||||
*shared_voter_state = Some(voter_state);
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Get the inner `VoterState` instance.
|
||||
pub fn voter_state(&self) -> Option<voter::report::VoterState<AuthorityId>> {
|
||||
self.inner.read().as_ref().map(|vs| vs.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for SharedVoterState {
|
||||
fn clone(&self) -> Self {
|
||||
SharedVoterState { inner: self.inner.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for the GRANDPA service
|
||||
#[derive(Clone)]
|
||||
pub struct Config {
|
||||
/// The expected duration for a message to be gossiped across the network.
|
||||
@@ -392,6 +434,7 @@ impl<H, N> fmt::Display for CommandOrError<H, N> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Link between the block importer and the background voter.
|
||||
pub struct LinkHalf<Block: BlockT, C, SC> {
|
||||
client: Arc<C>,
|
||||
select_chain: SC,
|
||||
@@ -399,6 +442,13 @@ pub struct LinkHalf<Block: BlockT, C, SC> {
|
||||
voter_commands_rx: TracingUnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT, C, SC> LinkHalf<Block, C, SC> {
|
||||
/// Get the shared authority set.
|
||||
pub fn shared_authority_set(&self) -> &SharedAuthoritySet<Block::Hash, NumberFor<Block>> {
|
||||
&self.persistent_data.authority_set
|
||||
}
|
||||
}
|
||||
|
||||
/// Provider for the Grandpa authority set configured on the genesis block.
|
||||
pub trait GenesisAuthoritySetProvider<Block: BlockT> {
|
||||
/// Get the authority set at the genesis block.
|
||||
@@ -620,6 +670,8 @@ pub struct GrandpaParams<Block: BlockT, C, N, SC, VR> {
|
||||
pub voting_rule: VR,
|
||||
/// The prometheus metrics registry.
|
||||
pub prometheus_registry: Option<prometheus_endpoint::Registry>,
|
||||
/// The voter state is exposed at an RPC endpoint.
|
||||
pub shared_voter_state: SharedVoterState,
|
||||
}
|
||||
|
||||
/// Run a GRANDPA voter as a task. Provide configuration and a link to a
|
||||
@@ -644,6 +696,7 @@ pub fn run_grandpa_voter<Block: BlockT, BE: 'static, C, N, SC, VR>(
|
||||
telemetry_on_connect,
|
||||
voting_rule,
|
||||
prometheus_registry,
|
||||
shared_voter_state,
|
||||
} = grandpa_params;
|
||||
|
||||
// NOTE: we have recently removed `run_grandpa_observer` from the public
|
||||
@@ -704,6 +757,7 @@ pub fn run_grandpa_voter<Block: BlockT, BE: 'static, C, N, SC, VR>(
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
prometheus_registry,
|
||||
shared_voter_state,
|
||||
);
|
||||
|
||||
let voter_work = voter_work
|
||||
@@ -734,6 +788,7 @@ impl Metrics {
|
||||
#[must_use]
|
||||
struct VoterWork<B, Block: BlockT, C, N: NetworkT<Block>, SC, VR> {
|
||||
voter: Pin<Box<dyn Future<Output = Result<(), CommandOrError<Block::Hash, NumberFor<Block>>>> + Send>>,
|
||||
shared_voter_state: SharedVoterState,
|
||||
env: Arc<Environment<B, Block, C, N, SC, VR>>,
|
||||
voter_commands_rx: TracingUnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
network: NetworkBridge<Block, N>,
|
||||
@@ -761,6 +816,7 @@ where
|
||||
persistent_data: PersistentData<Block>,
|
||||
voter_commands_rx: TracingUnboundedReceiver<VoterCommand<Block::Hash, NumberFor<Block>>>,
|
||||
prometheus_registry: Option<prometheus_endpoint::Registry>,
|
||||
shared_voter_state: SharedVoterState,
|
||||
) -> Self {
|
||||
let metrics = match prometheus_registry.as_ref().map(Metrics::register) {
|
||||
Some(Ok(metrics)) => Some(metrics),
|
||||
@@ -791,6 +847,7 @@ where
|
||||
// `voter` is set to a temporary value and replaced below when
|
||||
// calling `rebuild_voter`.
|
||||
voter: Box::pin(future::pending()),
|
||||
shared_voter_state,
|
||||
env,
|
||||
voter_commands_rx,
|
||||
network,
|
||||
@@ -858,6 +915,14 @@ where
|
||||
last_finalized,
|
||||
);
|
||||
|
||||
// Repoint shared_voter_state so that the RPC endpoint can query the state
|
||||
if let None = self.shared_voter_state.reset(voter.voter_state()) {
|
||||
info!(target: "afg",
|
||||
"Timed out trying to update shared GRANDPA voter state. \
|
||||
RPC endpoints may return stale data."
|
||||
);
|
||||
}
|
||||
|
||||
self.voter = Box::pin(voter);
|
||||
},
|
||||
VoterSetState::Paused { .. } =>
|
||||
|
||||
@@ -295,8 +295,6 @@ fn run_to_completion_with<F>(
|
||||
) -> u64 where
|
||||
F: FnOnce(Handle) -> Option<Pin<Box<dyn Future<Output = ()>>>>
|
||||
{
|
||||
use parking_lot::RwLock;
|
||||
|
||||
let mut wait_for = Vec::new();
|
||||
|
||||
let highest_finalized = Arc::new(RwLock::new(0));
|
||||
@@ -354,6 +352,7 @@ fn run_to_completion_with<F>(
|
||||
telemetry_on_connect: None,
|
||||
voting_rule: (),
|
||||
prometheus_registry: None,
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
let voter = run_grandpa_voter(grandpa_params).expect("all in order with client and network");
|
||||
|
||||
@@ -485,6 +484,7 @@ fn finalize_3_voters_1_full_observer() {
|
||||
telemetry_on_connect: None,
|
||||
voting_rule: (),
|
||||
prometheus_registry: None,
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
|
||||
voters.push(run_grandpa_voter(grandpa_params).expect("all in order with client and network"));
|
||||
@@ -648,6 +648,7 @@ fn transition_3_voters_twice_1_full_observer() {
|
||||
telemetry_on_connect: None,
|
||||
voting_rule: (),
|
||||
prometheus_registry: None,
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
let voter = run_grandpa_voter(grandpa_params).expect("all in order with client and network");
|
||||
|
||||
@@ -1072,6 +1073,7 @@ fn voter_persists_its_votes() {
|
||||
telemetry_on_connect: None,
|
||||
voting_rule: VotingRulesBuilder::default().build(),
|
||||
prometheus_registry: None,
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
|
||||
let voter = run_grandpa_voter(grandpa_params)
|
||||
@@ -1417,6 +1419,7 @@ fn voter_catches_up_to_latest_round_when_behind() {
|
||||
telemetry_on_connect: None,
|
||||
voting_rule: (),
|
||||
prometheus_registry: None,
|
||||
shared_voter_state: SharedVoterState::empty(),
|
||||
};
|
||||
|
||||
Box::pin(run_grandpa_voter(grandpa_params).expect("all in order with client and network"))
|
||||
|
||||
Reference in New Issue
Block a user