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
@@ -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()));
}
}