referrerpolicy=no-referrer-when-downgrade

sc_consensus_babe_rpc/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! RPC api for babe.
20
21use std::{collections::HashMap, sync::Arc};
22
23use futures::TryFutureExt;
24use jsonrpsee::{
25	core::async_trait,
26	proc_macros::rpc,
27	types::{ErrorObject, ErrorObjectOwned},
28	Extensions,
29};
30use serde::{Deserialize, Serialize};
31
32use sc_consensus_babe::{authorship, BabeWorkerHandle};
33use sc_consensus_epochs::Epoch as EpochT;
34use sc_rpc_api::{check_if_safe, UnsafeRpcError};
35use sp_api::ProvideRuntimeApi;
36use sp_application_crypto::AppCrypto;
37use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
38use sp_consensus::{Error as ConsensusError, SelectChain};
39use sp_consensus_babe::{digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi};
40use sp_core::crypto::ByteArray;
41use sp_keystore::KeystorePtr;
42use sp_runtime::traits::{Block as BlockT, Header as _};
43
44const BABE_ERROR: i32 = 9000;
45
46/// Provides rpc methods for interacting with Babe.
47#[rpc(client, server)]
48pub trait BabeApi {
49	/// Returns data about which slots (primary or secondary) can be claimed in the current epoch
50	/// with the keys in the keystore.
51	#[method(name = "babe_epochAuthorship", with_extensions)]
52	async fn epoch_authorship(&self) -> Result<HashMap<AuthorityId, EpochAuthorship>, Error>;
53}
54
55/// Provides RPC methods for interacting with Babe.
56pub struct Babe<B: BlockT, C, SC> {
57	/// shared reference to the client.
58	client: Arc<C>,
59	/// A handle to the BABE worker for issuing requests.
60	babe_worker_handle: BabeWorkerHandle<B>,
61	/// shared reference to the Keystore
62	keystore: KeystorePtr,
63	/// The SelectChain strategy
64	select_chain: SC,
65}
66
67impl<B: BlockT, C, SC> Babe<B, C, SC> {
68	/// Creates a new instance of the Babe Rpc handler.
69	pub fn new(
70		client: Arc<C>,
71		babe_worker_handle: BabeWorkerHandle<B>,
72		keystore: KeystorePtr,
73		select_chain: SC,
74	) -> Self {
75		Self { client, babe_worker_handle, keystore, select_chain }
76	}
77}
78
79#[async_trait]
80impl<B: BlockT, C, SC> BabeApiServer for Babe<B, C, SC>
81where
82	B: BlockT,
83	C: ProvideRuntimeApi<B>
84		+ HeaderBackend<B>
85		+ HeaderMetadata<B, Error = BlockChainError>
86		+ 'static,
87	C::Api: BabeRuntimeApi<B>,
88	SC: SelectChain<B> + Clone + 'static,
89{
90	async fn epoch_authorship(
91		&self,
92		ext: &Extensions,
93	) -> Result<HashMap<AuthorityId, EpochAuthorship>, Error> {
94		check_if_safe(ext)?;
95
96		let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?;
97
98		let epoch_start = self
99			.client
100			.runtime_api()
101			.current_epoch_start(best_header.hash())
102			.map_err(|_| Error::FetchEpoch)?;
103
104		let epoch = self
105			.babe_worker_handle
106			.epoch_data_for_child_of(best_header.hash(), *best_header.number(), epoch_start)
107			.await
108			.map_err(|_| Error::FetchEpoch)?;
109
110		let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot());
111		let mut claims: HashMap<AuthorityId, EpochAuthorship> = HashMap::new();
112
113		let keys = {
114			epoch
115				.authorities
116				.iter()
117				.enumerate()
118				.filter_map(|(i, a)| {
119					if self.keystore.has_keys(&[(a.0.to_raw_vec(), AuthorityId::ID)]) {
120						Some((a.0.clone(), i))
121					} else {
122						None
123					}
124				})
125				.collect::<Vec<_>>()
126		};
127
128		for slot in *epoch_start..*epoch_end {
129			if let Some((claim, key)) =
130				authorship::claim_slot_using_keys(slot.into(), &epoch, &self.keystore, &keys)
131			{
132				match claim {
133					PreDigest::Primary { .. } => {
134						claims.entry(key).or_default().primary.push(slot);
135					},
136					PreDigest::SecondaryPlain { .. } => {
137						claims.entry(key).or_default().secondary.push(slot);
138					},
139					PreDigest::SecondaryVRF { .. } => {
140						claims.entry(key).or_default().secondary_vrf.push(slot.into());
141					},
142				};
143			}
144		}
145
146		Ok(claims)
147	}
148}
149
150/// Holds information about the `slot`'s that can be claimed by a given key.
151#[derive(Clone, Default, Debug, Deserialize, Serialize)]
152pub struct EpochAuthorship {
153	/// the array of primary slots that can be claimed
154	primary: Vec<u64>,
155	/// the array of secondary slots that can be claimed
156	secondary: Vec<u64>,
157	/// The array of secondary VRF slots that can be claimed.
158	secondary_vrf: Vec<u64>,
159}
160
161/// Top-level error type for the RPC handler.
162#[derive(Debug, thiserror::Error)]
163pub enum Error {
164	/// Failed to fetch the current best header.
165	#[error("Failed to fetch the current best header: {0}")]
166	SelectChain(ConsensusError),
167	/// Failed to fetch epoch data.
168	#[error("Failed to fetch epoch data")]
169	FetchEpoch,
170	/// Consensus error
171	#[error(transparent)]
172	Consensus(#[from] ConsensusError),
173	/// Errors that can be formatted as a String
174	#[error("{0}")]
175	StringError(String),
176	/// Call to an unsafe RPC was denied.
177	#[error(transparent)]
178	UnsafeRpcCalled(#[from] UnsafeRpcError),
179}
180
181impl From<Error> for ErrorObjectOwned {
182	fn from(error: Error) -> Self {
183		match error {
184			Error::SelectChain(e) => ErrorObject::owned(BABE_ERROR + 1, e.to_string(), None::<()>),
185			Error::FetchEpoch => ErrorObject::owned(BABE_ERROR + 2, error.to_string(), None::<()>),
186			Error::Consensus(e) => ErrorObject::owned(BABE_ERROR + 3, e.to_string(), None::<()>),
187			Error::StringError(e) => ErrorObject::owned(BABE_ERROR + 4, e, None::<()>),
188			Error::UnsafeRpcCalled(e) => e.into(),
189		}
190	}
191}
192
193#[cfg(test)]
194mod tests {
195	use super::*;
196	use sc_consensus_babe::ImportQueueParams;
197	use sc_rpc_api::DenyUnsafe;
198	use sc_transaction_pool_api::{OffchainTransactionPoolFactory, RejectAllTxPool};
199	use sp_consensus_babe::inherents::InherentDataProvider;
200	use sp_core::{crypto::key_types::BABE, testing::TaskExecutor};
201	use sp_keyring::Sr25519Keyring;
202	use sp_keystore::{testing::MemoryKeystore, Keystore};
203	use substrate_test_runtime_client::{
204		runtime::Block, Backend, DefaultTestClientBuilderExt, TestClient, TestClientBuilder,
205		TestClientBuilderExt,
206	};
207
208	fn create_keystore(authority: Sr25519Keyring) -> KeystorePtr {
209		let keystore = MemoryKeystore::new();
210		keystore
211			.sr25519_generate_new(BABE, Some(&authority.to_seed()))
212			.expect("Creates authority key");
213		keystore.into()
214	}
215
216	fn test_babe_rpc_module() -> Babe<Block, TestClient, sc_consensus::LongestChain<Backend, Block>>
217	{
218		let builder = TestClientBuilder::new();
219		let (client, longest_chain) = builder.build_with_longest_chain();
220		let client = Arc::new(client);
221		let task_executor = TaskExecutor::new();
222		let keystore = create_keystore(Sr25519Keyring::Alice);
223
224		let config = sc_consensus_babe::configuration(&*client).expect("config available");
225		let slot_duration = config.slot_duration();
226
227		let (block_import, link) = sc_consensus_babe::block_import(
228			config.clone(),
229			client.clone(),
230			client.clone(),
231			move |_, _| async move {
232				Ok((InherentDataProvider::from_timestamp_and_slot_duration(
233					0.into(),
234					slot_duration,
235				),))
236			},
237			longest_chain.clone(),
238			OffchainTransactionPoolFactory::new(RejectAllTxPool::default()),
239		)
240		.expect("can initialize block-import");
241
242		let (_, babe_worker_handle) = sc_consensus_babe::import_queue(ImportQueueParams {
243			link: link.clone(),
244			block_import: block_import.clone(),
245			justification_import: None,
246			client: client.clone(),
247			slot_duration,
248			spawner: &task_executor,
249			registry: None,
250			telemetry: None,
251		})
252		.unwrap();
253
254		Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain)
255	}
256
257	#[tokio::test]
258	async fn epoch_authorship_works() {
259		let babe_rpc = test_babe_rpc_module();
260		let mut api = babe_rpc.into_rpc();
261		api.extensions_mut().insert(DenyUnsafe::No);
262
263		let request = r#"{"jsonrpc":"2.0","id":1,"method":"babe_epochAuthorship","params":[]}"#;
264		let (response, _) = api.raw_json_request(request, 1).await.unwrap();
265		let expected = r#"{"jsonrpc":"2.0","id":1,"result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[],"secondary_vrf":[1,2,4]}}}"#;
266
267		assert_eq!(response, expected);
268	}
269
270	#[tokio::test]
271	async fn epoch_authorship_is_unsafe() {
272		let babe_rpc = test_babe_rpc_module();
273		let mut api = babe_rpc.into_rpc();
274		api.extensions_mut().insert(DenyUnsafe::Yes);
275
276		let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#;
277		let (response, _) = api.raw_json_request(request, 1).await.unwrap();
278		let expected = r#"{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"RPC call is unsafe to be called externally"}}"#;
279
280		assert_eq!(response, expected);
281	}
282}