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:
Jon Häggblad
2020-05-04 21:37:22 +02:00
committed by GitHub
parent 8df33e50af
commit 1f7f8abb33
15 changed files with 552 additions and 19 deletions
+23 -2
View File
@@ -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.
+1
View File
@@ -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" }
+23 -5
View File
@@ -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.
+2
View File
@@ -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" }
+24 -2
View File
@@ -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
}
+2 -2
View File
@@ -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, &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,
})
}
}
@@ -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; \
+66 -1
View File
@@ -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"))