referrerpolicy=no-referrer-when-downgrade

sc_consensus_grandpa_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 GRANDPA.
20#![warn(missing_docs)]
21
22use futures::StreamExt;
23use log::warn;
24use std::sync::Arc;
25
26use jsonrpsee::{
27	core::{async_trait, server::PendingSubscriptionSink},
28	proc_macros::rpc,
29};
30
31mod error;
32mod finality;
33mod notification;
34mod report;
35
36use error::Error;
37use finality::{EncodedFinalityProof, RpcFinalityProofProvider};
38use notification::JustificationNotification;
39use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates};
40use sc_consensus_grandpa::GrandpaJustificationStream;
41use sc_rpc::{
42	utils::{BoundedVecDeque, PendingSubscription},
43	SubscriptionTaskExecutor,
44};
45use sp_runtime::traits::{Block as BlockT, NumberFor};
46
47/// Provides RPC methods for interacting with GRANDPA.
48#[rpc(client, server)]
49pub trait GrandpaApi<Notification, Hash, Number> {
50	/// Returns the state of the current best round state as well as the
51	/// ongoing background rounds.
52	#[method(name = "grandpa_roundState")]
53	async fn round_state(&self) -> Result<ReportedRoundStates, Error>;
54
55	/// Returns the block most recently finalized by Grandpa, alongside
56	/// side its justification.
57	#[subscription(
58		name = "grandpa_subscribeJustifications" => "grandpa_justifications",
59		unsubscribe = "grandpa_unsubscribeJustifications",
60		item = Notification
61	)]
62	fn subscribe_justifications(&self);
63
64	/// Prove finality for the given block number by returning the Justification for the last block
65	/// in the set and all the intermediary headers to link them together.
66	#[method(name = "grandpa_proveFinality")]
67	async fn prove_finality(&self, block: Number) -> Result<Option<EncodedFinalityProof>, Error>;
68}
69
70/// Provides RPC methods for interacting with GRANDPA.
71pub struct Grandpa<AuthoritySet, VoterState, Block: BlockT, ProofProvider> {
72	executor: SubscriptionTaskExecutor,
73	authority_set: AuthoritySet,
74	voter_state: VoterState,
75	justification_stream: GrandpaJustificationStream<Block>,
76	finality_proof_provider: Arc<ProofProvider>,
77}
78impl<AuthoritySet, VoterState, Block: BlockT, ProofProvider>
79	Grandpa<AuthoritySet, VoterState, Block, ProofProvider>
80{
81	/// Prepare a new [`Grandpa`] Rpc handler.
82	pub fn new(
83		executor: SubscriptionTaskExecutor,
84		authority_set: AuthoritySet,
85		voter_state: VoterState,
86		justification_stream: GrandpaJustificationStream<Block>,
87		finality_proof_provider: Arc<ProofProvider>,
88	) -> Self {
89		Self { executor, authority_set, voter_state, justification_stream, finality_proof_provider }
90	}
91}
92
93#[async_trait]
94impl<AuthoritySet, VoterState, Block, ProofProvider>
95	GrandpaApiServer<JustificationNotification, Block::Hash, NumberFor<Block>>
96	for Grandpa<AuthoritySet, VoterState, Block, ProofProvider>
97where
98	VoterState: ReportVoterState + Send + Sync + 'static,
99	AuthoritySet: ReportAuthoritySet + Send + Sync + 'static,
100	Block: BlockT,
101	ProofProvider: RpcFinalityProofProvider<Block> + Send + Sync + 'static,
102{
103	async fn round_state(&self) -> Result<ReportedRoundStates, Error> {
104		ReportedRoundStates::from(&self.authority_set, &self.voter_state)
105	}
106
107	fn subscribe_justifications(&self, pending: PendingSubscriptionSink) {
108		let stream = self.justification_stream.subscribe(100_000).map(
109			|x: sc_consensus_grandpa::GrandpaJustification<Block>| {
110				JustificationNotification::from(x)
111			},
112		);
113
114		sc_rpc::utils::spawn_subscription_task(
115			&self.executor,
116			PendingSubscription::from(pending).pipe_from_stream(stream, BoundedVecDeque::default()),
117		);
118	}
119
120	async fn prove_finality(
121		&self,
122		block: NumberFor<Block>,
123	) -> Result<Option<EncodedFinalityProof>, Error> {
124		self.finality_proof_provider.rpc_prove_finality(block).map_err(|e| {
125			warn!("Error proving finality: {}", e);
126			error::Error::ProveFinalityFailed(e)
127		})
128	}
129}
130
131#[cfg(test)]
132mod tests {
133	use super::*;
134	use std::{collections::HashSet, sync::Arc};
135
136	use codec::{Decode, Encode};
137	use jsonrpsee::{core::EmptyServerParams as EmptyParams, types::SubscriptionId, RpcModule};
138	use sc_block_builder::BlockBuilderBuilder;
139	use sc_consensus_grandpa::{
140		report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender,
141	};
142	use sc_rpc::testing::test_executor;
143	use sp_blockchain::HeaderBackend;
144	use sp_core::crypto::ByteArray;
145	use sp_keyring::Ed25519Keyring;
146	use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
147	use substrate_test_runtime_client::{
148		runtime::{Block, Header, H256},
149		DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt,
150	};
151
152	struct TestAuthoritySet;
153	struct TestVoterState;
154	struct EmptyVoterState;
155
156	struct TestFinalityProofProvider {
157		finality_proof: Option<FinalityProof<Header>>,
158	}
159
160	fn voters() -> HashSet<AuthorityId> {
161		let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap();
162		let voter_id_2 = AuthorityId::from_slice(&[2; 32]).unwrap();
163
164		vec![voter_id_1, voter_id_2].into_iter().collect()
165	}
166
167	impl ReportAuthoritySet for TestAuthoritySet {
168		fn get(&self) -> (u64, HashSet<AuthorityId>) {
169			(1, voters())
170		}
171	}
172
173	impl ReportVoterState for EmptyVoterState {
174		fn get(&self) -> Option<report::VoterState<AuthorityId>> {
175			None
176		}
177	}
178
179	fn header(number: u64) -> Header {
180		let parent_hash = match number {
181			0 => Default::default(),
182			_ => header(number - 1).hash(),
183		};
184		Header::new(
185			number,
186			H256::from_low_u64_be(0),
187			H256::from_low_u64_be(0),
188			parent_hash,
189			Default::default(),
190		)
191	}
192
193	impl<Block: BlockT> RpcFinalityProofProvider<Block> for TestFinalityProofProvider {
194		fn rpc_prove_finality(
195			&self,
196			_block: NumberFor<Block>,
197		) -> Result<Option<EncodedFinalityProof>, sc_consensus_grandpa::FinalityProofError> {
198			Ok(Some(EncodedFinalityProof(
199				self.finality_proof
200					.as_ref()
201					.expect("Don't call rpc_prove_finality without setting the FinalityProof")
202					.encode()
203					.into(),
204			)))
205		}
206	}
207
208	impl ReportVoterState for TestVoterState {
209		fn get(&self) -> Option<report::VoterState<AuthorityId>> {
210			let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap();
211			let voters_best: HashSet<_> = vec![voter_id_1].into_iter().collect();
212
213			let best_round_state = sc_consensus_grandpa::report::RoundState {
214				total_weight: 100_u64.try_into().unwrap(),
215				threshold_weight: 67_u64.try_into().unwrap(),
216				prevote_current_weight: 50.into(),
217				prevote_ids: voters_best,
218				precommit_current_weight: 0.into(),
219				precommit_ids: HashSet::new(),
220			};
221
222			let past_round_state = sc_consensus_grandpa::report::RoundState {
223				total_weight: 100_u64.try_into().unwrap(),
224				threshold_weight: 67_u64.try_into().unwrap(),
225				prevote_current_weight: 100.into(),
226				prevote_ids: voters(),
227				precommit_current_weight: 100.into(),
228				precommit_ids: voters(),
229			};
230
231			let background_rounds = vec![(1, past_round_state)].into_iter().collect();
232
233			Some(report::VoterState { background_rounds, best_round: (2, best_round_state) })
234		}
235	}
236
237	fn setup_io_handler<VoterState>(
238		voter_state: VoterState,
239	) -> (
240		RpcModule<Grandpa<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
241		GrandpaJustificationSender<Block>,
242	)
243	where
244		VoterState: ReportVoterState + Send + Sync + 'static,
245	{
246		setup_io_handler_with_finality_proofs(voter_state, None)
247	}
248
249	fn setup_io_handler_with_finality_proofs<VoterState>(
250		voter_state: VoterState,
251		finality_proof: Option<FinalityProof<Header>>,
252	) -> (
253		RpcModule<Grandpa<TestAuthoritySet, VoterState, Block, TestFinalityProofProvider>>,
254		GrandpaJustificationSender<Block>,
255	)
256	where
257		VoterState: ReportVoterState + Send + Sync + 'static,
258	{
259		let (justification_sender, justification_stream) = GrandpaJustificationStream::channel();
260		let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof });
261		let executor = test_executor();
262
263		let rpc = Grandpa::new(
264			executor,
265			TestAuthoritySet,
266			voter_state,
267			justification_stream,
268			finality_proof_provider,
269		)
270		.into_rpc();
271
272		(rpc, justification_sender)
273	}
274
275	#[tokio::test]
276	async fn uninitialized_rpc_handler() {
277		let (rpc, _) = setup_io_handler(EmptyVoterState);
278		let expected_response = r#"{"jsonrpc":"2.0","id":0,"error":{"code":1,"message":"GRANDPA RPC endpoint not ready"}}"#.to_string();
279		let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
280		let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
281
282		assert_eq!(expected_response, response);
283	}
284
285	#[tokio::test]
286	async fn working_rpc_handler() {
287		let (rpc, _) = setup_io_handler(TestVoterState);
288		let expected_response = "{\"jsonrpc\":\"2.0\",\"id\":0,\"result\":{\
289			\"setId\":1,\
290			\"best\":{\
291				\"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\
292				\"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\
293				\"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]}\
294			},\
295				\"background\":[{\
296				\"round\":1,\"totalWeight\":100,\"thresholdWeight\":67,\
297				\"prevotes\":{\"currentWeight\":100,\"missing\":[]},\
298				\"precommits\":{\"currentWeight\":100,\"missing\":[]}\
299			}]\
300		}}".to_string();
301
302		let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
303		let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
304		assert_eq!(expected_response, response);
305	}
306
307	#[tokio::test]
308	async fn subscribe_and_unsubscribe_with_wrong_id() {
309		let (rpc, _) = setup_io_handler(TestVoterState);
310		// Subscribe call.
311		let _sub = rpc
312			.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
313			.await
314			.unwrap();
315
316		// Unsubscribe with wrong ID
317		let (response, _) = rpc
318			.raw_json_request(
319				r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#,
320				1,
321			)
322			.await
323			.unwrap();
324		let expected = r#"{"jsonrpc":"2.0","id":1,"result":false}"#;
325
326		assert_eq!(response, expected);
327	}
328
329	fn create_justification() -> GrandpaJustification<Block> {
330		let peers = &[Ed25519Keyring::Alice];
331
332		let builder = TestClientBuilder::new();
333		let client = builder.build();
334		let client = Arc::new(client);
335
336		let built_block = BlockBuilderBuilder::new(&*client)
337			.on_parent_block(client.info().best_hash)
338			.with_parent_block_number(client.info().best_number)
339			.build()
340			.unwrap()
341			.build()
342			.unwrap();
343
344		let block = built_block.block;
345		let block_hash = block.hash();
346
347		let justification = {
348			let round = 1;
349			let set_id = 0;
350
351			let precommit = finality_grandpa::Precommit {
352				target_hash: block_hash,
353				target_number: *block.header.number(),
354			};
355
356			let msg = finality_grandpa::Message::Precommit(precommit.clone());
357			let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg);
358			let signature = peers[0].sign(&encoded[..]).into();
359
360			let precommit = finality_grandpa::SignedPrecommit {
361				precommit,
362				signature,
363				id: peers[0].public().into(),
364			};
365
366			let commit = finality_grandpa::Commit {
367				target_hash: block_hash,
368				target_number: *block.header.number(),
369				precommits: vec![precommit],
370			};
371
372			GrandpaJustification::from_commit(&client, round, commit).unwrap()
373		};
374
375		justification
376	}
377
378	#[tokio::test]
379	async fn subscribe_and_listen_to_one_justification() {
380		let (rpc, justification_sender) = setup_io_handler(TestVoterState);
381
382		let mut sub = rpc
383			.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
384			.await
385			.unwrap();
386
387		// Notify with a header and justification
388		let justification = create_justification();
389		justification_sender.notify(|| Ok::<_, ()>(justification.clone())).unwrap();
390
391		// Inspect what we received
392		let (recv_justification, recv_sub_id): (sp_core::Bytes, SubscriptionId) =
393			sub.next().await.unwrap().unwrap();
394		let recv_justification: GrandpaJustification<Block> =
395			Decode::decode(&mut &recv_justification[..]).unwrap();
396
397		assert_eq!(&recv_sub_id, sub.subscription_id());
398		assert_eq!(recv_justification, justification);
399	}
400
401	#[tokio::test]
402	async fn prove_finality_with_test_finality_proof_provider() {
403		let finality_proof = FinalityProof {
404			block: header(42).hash(),
405			justification: create_justification().encode(),
406			unknown_headers: vec![header(2)],
407		};
408		let (rpc, _) =
409			setup_io_handler_with_finality_proofs(TestVoterState, Some(finality_proof.clone()));
410
411		let bytes: sp_core::Bytes = rpc.call("grandpa_proveFinality", [42]).await.unwrap();
412		let finality_proof_rpc: FinalityProof<Header> = Decode::decode(&mut &bytes[..]).unwrap();
413		assert_eq!(finality_proof_rpc, finality_proof);
414	}
415}