referrerpolicy=no-referrer-when-downgrade

sc_sync_state_rpc/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! A RPC handler to create sync states for light clients.
19//!
20//! Currently only usable with BABE + GRANDPA.
21//!
22//! # Usage
23//!
24//! To use the light sync state, it needs to be added as an extension to the chain spec:
25//!
26//! ```
27//! use sc_sync_state_rpc::LightSyncStateExtension;
28//!
29//! #[derive(Default, Clone, serde::Serialize, serde::Deserialize, sc_chain_spec::ChainSpecExtension)]
30//! #[serde(rename_all = "camelCase")]
31//! pub struct Extensions {
32//!    light_sync_state: LightSyncStateExtension,
33//! }
34//!
35//! type ChainSpec = sc_chain_spec::GenericChainSpec<(), Extensions>;
36//! ```
37//!
38//! If the [`LightSyncStateExtension`] is not added as an extension to the chain spec,
39//! the [`SyncState`] will fail at instantiation.
40
41#![deny(unused_crate_dependencies)]
42
43use std::sync::Arc;
44
45use jsonrpsee::{
46	core::async_trait,
47	proc_macros::rpc,
48	types::{ErrorObject, ErrorObjectOwned},
49};
50
51use sc_client_api::StorageData;
52use sc_consensus_babe::{BabeWorkerHandle, Error as BabeError};
53use sp_blockchain::HeaderBackend;
54use sp_runtime::traits::{Block as BlockT, NumberFor};
55
56type SharedAuthoritySet<TBl> =
57	sc_consensus_grandpa::SharedAuthoritySet<<TBl as BlockT>::Hash, NumberFor<TBl>>;
58
59/// Error type used by this crate.
60#[derive(Debug, thiserror::Error)]
61#[allow(missing_docs)]
62pub enum Error<Block: BlockT> {
63	#[error(transparent)]
64	Blockchain(#[from] sp_blockchain::Error),
65
66	#[error("Failed to load the block weight for block {0:?}")]
67	LoadingBlockWeightFailed(Block::Hash),
68
69	#[error("Failed to load the BABE epoch data: {0}")]
70	LoadingEpochDataFailed(BabeError<Block>),
71
72	#[error("JsonRpc error: {0}")]
73	JsonRpc(String),
74
75	#[error(
76		"The light sync state extension is not provided by the chain spec. \
77		Read the `sc-sync-state-rpc` crate docs on how to do this!"
78	)]
79	LightSyncStateExtensionNotFound,
80}
81
82impl<Block: BlockT> From<Error<Block>> for ErrorObjectOwned {
83	fn from(error: Error<Block>) -> Self {
84		let message = match error {
85			Error::JsonRpc(s) => s,
86			_ => error.to_string(),
87		};
88		ErrorObject::owned(1, message, None::<()>)
89	}
90}
91
92/// Serialize the given `val` by encoding it with SCALE codec and serializing it as hex.
93fn serialize_encoded<S: serde::Serializer, T: codec::Encode>(
94	val: &T,
95	s: S,
96) -> Result<S::Ok, S::Error> {
97	let encoded = StorageData(val.encode());
98	serde::Serialize::serialize(&encoded, s)
99}
100
101/// The light sync state extension.
102///
103/// This represents a JSON serialized [`LightSyncState`]. It is required to be added to the
104/// chain-spec as an extension.
105pub type LightSyncStateExtension = Option<serde_json::Value>;
106
107/// Hardcoded information that allows light clients to sync quickly.
108#[derive(serde::Serialize, Clone)]
109#[serde(rename_all = "camelCase")]
110#[serde(deny_unknown_fields)]
111pub struct LightSyncState<Block: BlockT> {
112	/// The header of the best finalized block.
113	#[serde(serialize_with = "serialize_encoded")]
114	pub finalized_block_header: <Block as BlockT>::Header,
115	/// The epoch changes tree for babe.
116	#[serde(serialize_with = "serialize_encoded")]
117	pub babe_epoch_changes: sc_consensus_epochs::EpochChangesFor<Block, sc_consensus_babe::Epoch>,
118	/// The babe weight of the finalized block.
119	pub babe_finalized_block_weight: sc_consensus_babe::BabeBlockWeight,
120	/// The authority set for grandpa.
121	#[serde(serialize_with = "serialize_encoded")]
122	pub grandpa_authority_set:
123		sc_consensus_grandpa::AuthoritySet<<Block as BlockT>::Hash, NumberFor<Block>>,
124}
125
126/// An api for sync state RPC calls.
127#[rpc(client, server)]
128pub trait SyncStateApi<B: BlockT> {
129	/// Returns the JSON serialized chainspec running the node, with a sync state.
130	#[method(name = "sync_state_genSyncSpec")]
131	async fn system_gen_sync_spec(&self, raw: bool) -> Result<serde_json::Value, Error<B>>;
132}
133
134/// An api for sync state RPC calls.
135pub struct SyncState<Block: BlockT, Client> {
136	chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
137	client: Arc<Client>,
138	shared_authority_set: SharedAuthoritySet<Block>,
139	babe_worker_handle: BabeWorkerHandle<Block>,
140}
141
142impl<Block, Client> SyncState<Block, Client>
143where
144	Block: BlockT,
145	Client: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
146{
147	/// Create a new sync state RPC helper.
148	pub fn new(
149		chain_spec: Box<dyn sc_chain_spec::ChainSpec>,
150		client: Arc<Client>,
151		shared_authority_set: SharedAuthoritySet<Block>,
152		babe_worker_handle: BabeWorkerHandle<Block>,
153	) -> Result<Self, Error<Block>> {
154		if sc_chain_spec::get_extension::<LightSyncStateExtension>(chain_spec.extensions())
155			.is_some()
156		{
157			Ok(Self { chain_spec, client, shared_authority_set, babe_worker_handle })
158		} else {
159			Err(Error::<Block>::LightSyncStateExtensionNotFound)
160		}
161	}
162
163	async fn build_sync_state(&self) -> Result<LightSyncState<Block>, Error<Block>> {
164		let epoch_changes = self
165			.babe_worker_handle
166			.epoch_data()
167			.await
168			.map_err(Error::LoadingEpochDataFailed)?;
169
170		let finalized_hash = self.client.info().finalized_hash;
171		let finalized_header = self
172			.client
173			.header(finalized_hash)?
174			.ok_or_else(|| sp_blockchain::Error::MissingHeader(finalized_hash.to_string()))?;
175
176		let finalized_block_weight =
177			sc_consensus_babe::aux_schema::load_block_weight(&*self.client, finalized_hash)?
178				.ok_or(Error::LoadingBlockWeightFailed(finalized_hash))?;
179
180		Ok(LightSyncState {
181			finalized_block_header: finalized_header,
182			babe_epoch_changes: epoch_changes,
183			babe_finalized_block_weight: finalized_block_weight,
184			grandpa_authority_set: self.shared_authority_set.clone_inner(),
185		})
186	}
187}
188
189#[async_trait]
190impl<Block, Backend> SyncStateApiServer<Block> for SyncState<Block, Backend>
191where
192	Block: BlockT,
193	Backend: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
194{
195	async fn system_gen_sync_spec(&self, raw: bool) -> Result<serde_json::Value, Error<Block>> {
196		let current_sync_state = self.build_sync_state().await?;
197		let mut chain_spec = self.chain_spec.cloned_box();
198
199		let extension = sc_chain_spec::get_extension_mut::<LightSyncStateExtension>(
200			chain_spec.extensions_mut(),
201		)
202		.ok_or(Error::<Block>::LightSyncStateExtensionNotFound)?;
203
204		let val = serde_json::to_value(&current_sync_state)
205			.map_err(|e| Error::<Block>::JsonRpc(e.to_string()))?;
206		*extension = Some(val);
207
208		let json_str = chain_spec.as_json(raw).map_err(|e| Error::<Block>::JsonRpc(e))?;
209		serde_json::from_str(&json_str).map_err(|e| Error::<Block>::JsonRpc(e.to_string()))
210	}
211}