// 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 . //! RPC api for babe. use sc_consensus_babe::{Epoch, authorship, Config}; use futures::{FutureExt as _, TryFutureExt as _}; use jsonrpc_core::{ Error as RpcError, futures::future as rpc_future, }; use jsonrpc_derive::rpc; use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges}; use sp_consensus_babe::{ AuthorityId, BabeApi as BabeRuntimeApi, digests::PreDigest, }; use serde::{Deserialize, Serialize}; use sc_keystore::KeyStorePtr; use sp_api::{ProvideRuntimeApi, BlockId}; use sp_core::crypto::Pair; use sp_runtime::traits::{Block as BlockT, Header as _}; use sp_consensus::{SelectChain, Error as ConsensusError}; use sp_blockchain::{HeaderBackend, HeaderMetadata, Error as BlockChainError}; use std::{collections::HashMap, fmt, sync::Arc}; type FutureResult = Box + Send>; /// Provides rpc methods for interacting with Babe. #[rpc] pub trait BabeApi { /// Returns data about which slots (primary or secondary) can be claimed in the current epoch /// with the keys in the keystore. #[rpc(name = "babe_epochAuthorship")] fn epoch_authorship(&self) -> FutureResult>; } /// Implements the BabeRPC trait for interacting with Babe. pub struct BabeRPCHandler { /// shared reference to the client. client: Arc, /// shared reference to EpochChanges shared_epoch_changes: SharedEpochChanges, /// shared reference to the Keystore keystore: KeyStorePtr, /// config (actually holds the slot duration) babe_config: Config, /// The SelectChain strategy select_chain: SC, } impl BabeRPCHandler { /// Creates a new instance of the BabeRpc handler. pub fn new( client: Arc, shared_epoch_changes: SharedEpochChanges, keystore: KeyStorePtr, babe_config: Config, select_chain: SC, ) -> Self { Self { client, shared_epoch_changes, keystore, babe_config, select_chain, } } } impl BabeApi for BabeRPCHandler where B: BlockT, C: ProvideRuntimeApi + HeaderBackend + HeaderMetadata + 'static, C::Api: BabeRuntimeApi, ::Error: fmt::Debug, SC: SelectChain + Clone + 'static, { fn epoch_authorship(&self) -> FutureResult> { let ( babe_config, keystore, shared_epoch, client, select_chain, ) = ( self.babe_config.clone(), self.keystore.clone(), self.shared_epoch_changes.clone(), self.client.clone(), self.select_chain.clone(), ); let future = async move { let header = select_chain.best_chain().map_err(Error::Consensus)?; let epoch_start = client.runtime_api() .current_epoch_start(&BlockId::Hash(header.hash())) .map_err(|err| { Error::StringError(format!("{:?}", err)) })?; let epoch = epoch_data(&shared_epoch, &client, &babe_config, epoch_start, &select_chain)?; let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); let mut claims: HashMap = HashMap::new(); for slot_number in epoch_start..epoch_end { let epoch = epoch_data(&shared_epoch, &client, &babe_config, slot_number, &select_chain)?; if let Some((claim, key)) = authorship::claim_slot(slot_number, &epoch, &babe_config, &keystore) { match claim { PreDigest::Primary { .. } => { claims.entry(key.public()).or_default().primary.push(slot_number); } PreDigest::Secondary { .. } => { claims.entry(key.public()).or_default().secondary.push(slot_number); } }; } } Ok(claims) }.boxed(); Box::new(future.compat()) } } /// Holds information about the `slot_number`'s that can be claimed by a given key. #[derive(Default, Debug, Deserialize, Serialize)] pub struct EpochAuthorship { /// the array of primary slots that can be claimed primary: Vec, /// the array of secondary slots that can be claimed secondary: Vec, } /// Errors encountered by the RPC #[derive(Debug, derive_more::Display, derive_more::From)] pub enum Error { /// Consensus error Consensus(ConsensusError), /// Errors that can be formatted as a String StringError(String) } impl From for jsonrpc_core::Error { fn from(error: Error) -> Self { jsonrpc_core::Error { message: format!("{}", error).into(), code: jsonrpc_core::ErrorCode::ServerError(1234), data: None, } } } /// fetches the epoch data for a given slot_number. fn epoch_data( epoch_changes: &SharedEpochChanges, client: &Arc, babe_config: &Config, slot_number: u64, select_chain: &SC, ) -> Result where B: BlockT, C: HeaderBackend + HeaderMetadata + 'static, SC: SelectChain, { let parent = select_chain.best_chain()?; epoch_changes.lock().epoch_data_for_child_of( descendent_query(&**client), &parent.hash(), parent.number().clone(), slot_number, |slot| babe_config.genesis_epoch(slot), ) .map_err(|e| Error::Consensus(ConsensusError::ChainLookup(format!("{:?}", e))))? .ok_or(Error::Consensus(ConsensusError::InvalidAuthoritiesSet)) } #[cfg(test)] mod tests { use super::*; use substrate_test_runtime_client::{ DefaultTestClientBuilderExt, TestClientBuilderExt, TestClientBuilder, }; use sp_application_crypto::AppPair; use sp_keyring::Ed25519Keyring; use sc_keystore::Store; use std::sync::Arc; use sc_consensus_babe::{Config, block_import, AuthorityPair}; use jsonrpc_core::IoHandler; /// creates keystore backed by a temp file fn create_temp_keystore(authority: Ed25519Keyring) -> (KeyStorePtr, tempfile::TempDir) { let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = Store::open(keystore_path.path(), None).expect("Creates keystore"); keystore.write().insert_ephemeral_from_seed::

(&authority.to_seed()) .expect("Creates authority key"); (keystore, keystore_path) } #[test] fn rpc() { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); let config = Config::get_or_compute(&*client).expect("config available"); let (_, link) = block_import( config.clone(), client.clone(), client.clone(), ).expect("can initialize block-import"); let epoch_changes = link.epoch_changes().clone(); let select_chain = longest_chain; let keystore = create_temp_keystore::(Ed25519Keyring::Alice).0; let handler = BabeRPCHandler::new(client.clone(), epoch_changes, keystore, config, select_chain); let mut io = IoHandler::new(); io.extend_with(BabeApi::to_delegate(handler)); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4]}},"id":1}"#; assert_eq!(Some(response.into()), io.handle_request_sync(request)); } }