diff --git a/Cargo.lock b/Cargo.lock index d8602d2c6f3f0..18e690b65f92a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3822,6 +3822,7 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-consensus-babe", + "sp-runtime", "sp-transaction-pool", "substrate-frame-rpc-system", ] @@ -6832,6 +6833,7 @@ dependencies = [ "log", "parity-scale-codec", "sc-block-builder", + "sc-client-api", "sc-finality-grandpa", "sc-network-test", "sc-rpc", diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 03347e455e6a3..b15ace6181a8f 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -58,7 +58,10 @@ pub fn new_partial(config: &Configuration) -> Result, sc_consensus_babe::BabeLink, ), - grandpa::SharedVoterState, + ( + grandpa::SharedVoterState, + Arc>, + ), ) >, ServiceError> { let (client, backend, keystore, task_manager) = @@ -108,8 +111,10 @@ pub fn new_partial(config: &Configuration) -> Result Result { /// Voting round info. pub shared_voter_state: SharedVoterState, /// Authority set info. @@ -80,10 +82,12 @@ pub struct GrandpaDeps { pub justification_stream: GrandpaJustificationStream, /// Executor to drive the subscription manager in the Grandpa RPC handler. pub subscription_executor: SubscriptionTaskExecutor, + /// Finality proof provider. + pub finality_provider: Arc>, } /// Full client dependencies. -pub struct FullDeps { +pub struct FullDeps { /// The client instance to use. pub client: Arc, /// Transaction pool instance. @@ -95,15 +99,15 @@ pub struct FullDeps { /// BABE specific dependencies. pub babe: BabeDeps, /// GRANDPA specific dependencies. - pub grandpa: GrandpaDeps, + pub grandpa: GrandpaDeps, } /// A IO handler that uses all Full RPC extensions. pub type IoHandler = jsonrpc_core::IoHandler; /// Instantiate all Full RPC extensions. -pub fn create_full( - deps: FullDeps, +pub fn create_full( + deps: FullDeps, ) -> jsonrpc_core::IoHandler where C: ProvideRuntimeApi, C: HeaderBackend + HeaderMetadata + 'static, @@ -115,6 +119,8 @@ pub fn create_full( C::Api: BlockBuilder, P: TransactionPool + 'static, SC: SelectChain +'static, + B: sc_client_api::Backend + Send + Sync + 'static, + B::State: sc_client_api::backend::StateBackend>, { use substrate_frame_rpc_system::{FullSystem, SystemApi}; use pallet_contracts_rpc::{Contracts, ContractsApi}; @@ -140,6 +146,7 @@ pub fn create_full( shared_authority_set, justification_stream, subscription_executor, + finality_provider, } = grandpa; io.extend_with( @@ -173,6 +180,7 @@ pub fn create_full( shared_voter_state, justification_stream, subscription_executor, + finality_provider, ) ) ); diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 6f3014644eaa4..0112ddd420c99 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -10,6 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] sc-finality-grandpa = { version = "0.8.0-rc6", path = "../" } sc-rpc = { version = "2.0.0-rc6", path = "../../rpc" } +sp-blockchain = { version = "2.0.0-rc6", path = "../../../primitives/blockchain" } sp-core = { version = "2.0.0-rc6", path = "../../../primitives/core" } sp-runtime = { version = "2.0.0-rc6", path = "../../../primitives/runtime" } finality-grandpa = { version = "0.12.3", features = ["derive-codec"] } @@ -23,12 +24,12 @@ serde_json = "1.0.50" log = "0.4.8" derive_more = "0.99.2" parity-scale-codec = { version = "1.3.0", features = ["derive"] } +sc-client-api = { version = "2.0.0-rc6", path = "../../api" } [dev-dependencies] sc-block-builder = { version = "0.8.0-rc6", path = "../../block-builder" } sc-network-test = { version = "0.8.0-rc6", path = "../../network/test" } sc-rpc = { version = "2.0.0-rc6", path = "../../rpc", features = ["test-helpers"] } -sp-blockchain = { version = "2.0.0-rc6", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.8.0-rc6", path = "../../../primitives/consensus/common" } sp-core = { version = "2.0.0-rc6", path = "../../../primitives/core" } sp-finality-grandpa = { version = "2.0.0-rc6", path = "../../../primitives/finality-grandpa" } diff --git a/client/finality-grandpa/rpc/src/error.rs b/client/finality-grandpa/rpc/src/error.rs index bfd0596fdf320..6464acbe10ea0 100644 --- a/client/finality-grandpa/rpc/src/error.rs +++ b/client/finality-grandpa/rpc/src/error.rs @@ -16,8 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::NOT_READY_ERROR_CODE; - #[derive(derive_more::Display, derive_more::From)] /// Top-level error type for the RPC handler pub enum Error { @@ -30,13 +28,41 @@ pub enum Error { /// GRANDPA reports voter state with round id or weights larger than 32-bits. #[display(fmt = "GRANDPA reports voter state as unreasonably large")] VoterStateReportsUnreasonablyLargeNumbers, + /// GRANDPA prove finality failed. + #[display(fmt = "GRANDPA prove finality rpc failed: {}", _0)] + ProveFinalityFailed(sp_blockchain::Error), +} + +/// 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 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 for jsonrpc_core::Error { fn from(error: Error) -> Self { + let message = format!("{}", error); + let code = ErrorCode::from(error); jsonrpc_core::Error { - message: format!("{}", error), - code: jsonrpc_core::ErrorCode::ServerError(NOT_READY_ERROR_CODE), + message, + code: jsonrpc_core::ErrorCode::ServerError(code as i64), data: None, } } diff --git a/client/finality-grandpa/rpc/src/finality.rs b/client/finality-grandpa/rpc/src/finality.rs new file mode 100644 index 0000000000000..1f288b86a0e46 --- /dev/null +++ b/client/finality-grandpa/rpc/src/finality.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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 . + +use serde::{Serialize, Deserialize}; + +use sc_finality_grandpa::FinalityProofProvider; +use sp_runtime::traits::{Block as BlockT, NumberFor}; + +#[derive(Serialize, Deserialize)] +pub struct EncodedFinalityProofs(pub sp_core::Bytes); + +/// Local trait mainly to allow mocking in tests. +pub trait RpcFinalityProofProvider { + /// Return finality proofs for the given authorities set id, if it is provided, otherwise the + /// current one will be used. + fn rpc_prove_finality( + &self, + begin: Block::Hash, + end: Block::Hash, + authorities_set_id: u64, + ) -> Result, sp_blockchain::Error>; +} + +impl RpcFinalityProofProvider for FinalityProofProvider +where + Block: BlockT, + NumberFor: finality_grandpa::BlockNumberOps, + B: sc_client_api::backend::Backend + Send + Sync + 'static, +{ + fn rpc_prove_finality( + &self, + begin: Block::Hash, + end: Block::Hash, + authorities_set_id: u64, + ) -> Result, sp_blockchain::Error> { + self.prove_finality(begin, end, authorities_set_id) + .map(|x| x.map(|y| EncodedFinalityProofs(y.into()))) + } +} diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index fedd7220d3115..172473ad6518b 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -32,24 +32,23 @@ use jsonrpc_core::futures::{ }; mod error; +mod finality; mod notification; mod report; use sc_finality_grandpa::GrandpaJustificationStream; use sp_runtime::traits::Block as BlockT; +use finality::{EncodedFinalityProofs, RpcFinalityProofProvider}; use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates}; use notification::JustificationNotification; -/// Returned when Grandpa RPC endpoint is not ready. -pub const NOT_READY_ERROR_CODE: i64 = 1; - type FutureResult = Box + Send>; /// Provides RPC methods for interacting with GRANDPA. #[rpc] -pub trait GrandpaApi { +pub trait GrandpaApi { /// RPC Metadata type Metadata; @@ -82,23 +81,37 @@ pub trait GrandpaApi { metadata: Option, id: SubscriptionId ) -> jsonrpc_core::Result; + + /// Prove finality for the range (begin; end] hash. Returns None if there are no finalized blocks + /// unknown in the range. If no authorities set is provided, the current one will be attempted. + #[rpc(name = "grandpa_proveFinality")] + fn prove_finality( + &self, + begin: Hash, + end: Hash, + authorities_set_id: Option, + ) -> FutureResult>; } /// Implements the GrandpaApi RPC trait for interacting with GRANDPA. -pub struct GrandpaRpcHandler { +pub struct GrandpaRpcHandler { authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, manager: SubscriptionManager, + finality_proof_provider: Arc, } -impl GrandpaRpcHandler { +impl + GrandpaRpcHandler +{ /// Creates a new GrandpaRpcHandler instance. pub fn new( authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, executor: E, + finality_proof_provider: Arc, ) -> Self where E: Executor01 + Send>> + Send + Sync + 'static, @@ -109,16 +122,18 @@ impl GrandpaRpcHandler GrandpaApi - for GrandpaRpcHandler +impl GrandpaApi + for GrandpaRpcHandler where VoterState: ReportVoterState + Send + Sync + 'static, AuthoritySet: ReportAuthoritySet + Send + Sync + 'static, Block: BlockT, + ProofProvider: RpcFinalityProofProvider + Send + Sync + 'static, { type Metadata = sc_rpc::Metadata; @@ -153,6 +168,30 @@ where ) -> jsonrpc_core::Result { Ok(self.manager.cancel(id)) } + + fn prove_finality( + &self, + begin: Block::Hash, + end: Block::Hash, + authorities_set_id: Option, + ) -> FutureResult> { + // If we are not provided a set_id, try with the current one. + let authorities_set_id = authorities_set_id + .unwrap_or_else(|| self.authority_set.get().0); + let result = self + .finality_proof_provider + .rpc_prove_finality(begin, end, authorities_set_id); + let future = async move { result }.boxed(); + Box::new( + future + .map_err(|e| { + warn!("Error proving finality: {}", e); + error::Error::ProveFinalityFailed(e) + }) + .map_err(jsonrpc_core::Error::from) + .compat() + ) + } } #[cfg(test)] @@ -161,16 +200,19 @@ mod tests { use std::{collections::HashSet, convert::TryInto, sync::Arc}; use jsonrpc_core::{Notification, Output, types::Params}; - use parity_scale_codec::Decode; + use parity_scale_codec::{Encode, Decode}; use sc_block_builder::BlockBuilder; - use sc_finality_grandpa::{report, AuthorityId, GrandpaJustificationSender, GrandpaJustification}; + use sc_finality_grandpa::{ + report, AuthorityId, GrandpaJustificationSender, GrandpaJustification, + FinalityProofFragment, + }; use sp_blockchain::HeaderBackend; use sp_consensus::RecordProof; use sp_core::crypto::Public; use sp_keyring::Ed25519Keyring; - use sp_runtime::traits::Header as HeaderT; + use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use substrate_test_runtime_client::{ - runtime::Block, + runtime::{Block, Header, H256}, DefaultTestClientBuilderExt, TestClientBuilderExt, TestClientBuilder, @@ -180,6 +222,10 @@ mod tests { struct TestVoterState; struct EmptyVoterState; + struct TestFinalityProofProvider { + finality_proofs: Vec>, + } + fn voters() -> HashSet { let voter_id_1 = AuthorityId::from_slice(&[1; 32]); let voter_id_2 = AuthorityId::from_slice(&[2; 32]); @@ -199,6 +245,31 @@ mod tests { } } + 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 RpcFinalityProofProvider for TestFinalityProofProvider { + fn rpc_prove_finality( + &self, + _begin: Block::Hash, + _end: Block::Hash, + _authoritites_set_id: u64, + ) -> Result, sp_blockchain::Error> { + Ok(Some(EncodedFinalityProofs(self.finality_proofs.encode().into()))) + } + } + impl ReportVoterState for TestVoterState { fn get(&self) -> Option> { let voter_id_1 = AuthorityId::from_slice(&[1; 32]); @@ -236,14 +307,28 @@ mod tests { GrandpaJustificationSender, ) where VoterState: ReportVoterState + Send + Sync + 'static, + { + setup_io_handler_with_finality_proofs(voter_state, Default::default()) + } + + fn setup_io_handler_with_finality_proofs( + voter_state: VoterState, + finality_proofs: Vec>, + ) -> ( + jsonrpc_core::MetaIoHandler, + GrandpaJustificationSender, + ) where + VoterState: ReportVoterState + Send + Sync + 'static, { let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); + let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proofs }); let handler = GrandpaRpcHandler::new( TestAuthoritySet, voter_state, justification_stream, sc_rpc::testing::TaskExecutor, + finality_proof_provider, ); let mut io = jsonrpc_core::MetaIoHandler::default(); @@ -432,4 +517,32 @@ mod tests { assert_eq!(recv_sub_id, sub_id); assert_eq!(recv_justification, justification); } + + #[test] + fn prove_finality_with_test_finality_proof_provider() { + let finality_proofs = vec![FinalityProofFragment { + block: header(42).hash(), + justification: create_justification().encode(), + unknown_headers: vec![header(2)], + authorities_proof: None, + }]; + let (io, _) = setup_io_handler_with_finality_proofs( + TestVoterState, + finality_proofs.clone(), + ); + + let request = "{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_proveFinality\",\"params\":[\ + \"0x0000000000000000000000000000000000000000000000000000000000000000\",\ + \"0x0000000000000000000000000000000000000000000000000000000000000001\",\ + 42\ + ],\"id\":1}"; + + let meta = sc_rpc::Metadata::default(); + let resp = io.handle_request_sync(request, meta); + let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); + let result: sp_core::Bytes = serde_json::from_value(resp["result"].take()).unwrap(); + let fragments: Vec> = + Decode::decode(&mut &result[..]).unwrap(); + assert_eq!(fragments, finality_proofs); + } } diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index 2ac9ec57f3df4..33dd69cc11d6e 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -180,7 +180,30 @@ impl FinalityProofProvider ) -> Arc { Arc::new(Self::new(backend, storage_and_proof_provider)) } +} +impl FinalityProofProvider + where + Block: BlockT, + NumberFor: BlockNumberOps, + B: Backend + Send + Sync + 'static, +{ + /// Prove finality for the range (begin; end] hash. Returns None if there are no finalized blocks + /// unknown in the range. + pub fn prove_finality( + &self, + begin: Block::Hash, + end: Block::Hash, + authorities_set_id: u64, + ) -> Result>, ClientError> { + prove_finality::<_, _, GrandpaJustification>( + &*self.backend.blockchain(), + &*self.authority_provider, + authorities_set_id, + begin, + end, + ) + } } impl sc_network::config::FinalityProofProvider for FinalityProofProvider @@ -232,8 +255,8 @@ pub struct FinalityEffects { /// 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. -#[derive(Debug, PartialEq, Encode, Decode)] -pub(crate) struct FinalityProofFragment { +#[derive(Debug, PartialEq, Encode, Decode, Clone)] +pub struct FinalityProofFragment { /// The hash of block F for which justification is provided. pub block: Header::Hash, /// Justification of the block F. diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index ab84591f9cdec..a15130942c30f 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -125,7 +125,7 @@ mod until_imported; mod voting_rule; pub use authorities::SharedAuthoritySet; -pub use finality_proof::{FinalityProofProvider, StorageAndProofProvider}; +pub use finality_proof::{FinalityProofFragment, FinalityProofProvider, StorageAndProofProvider}; pub use notification::{GrandpaJustificationSender, GrandpaJustificationStream}; pub use import::GrandpaBlockImport; pub use justification::GrandpaJustification;