sc_rpc_spec_v2/archive/
archive.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//! API implementation for `archive`.
20
21use crate::{
22	archive::{error::Error as ArchiveError, ArchiveApiServer},
23	common::events::{ArchiveStorageResult, PaginatedStorageQuery},
24	hex_string, MethodResult,
25};
26
27use codec::Encode;
28use jsonrpsee::core::{async_trait, RpcResult};
29use sc_client_api::{
30	Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey,
31	StorageProvider,
32};
33use sp_api::{CallApiAt, CallContext};
34use sp_blockchain::{
35	Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata,
36};
37use sp_core::{Bytes, U256};
38use sp_runtime::{
39	traits::{Block as BlockT, Header as HeaderT, NumberFor},
40	SaturatedConversion,
41};
42use std::{collections::HashSet, marker::PhantomData, sync::Arc};
43
44use super::archive_storage::ArchiveStorage;
45
46/// The configuration of [`Archive`].
47pub struct ArchiveConfig {
48	/// The maximum number of items the `archive_storage` can return for a descendant query before
49	/// pagination is required.
50	pub max_descendant_responses: usize,
51	/// The maximum number of queried items allowed for the `archive_storage` at a time.
52	pub max_queried_items: usize,
53}
54
55/// The maximum number of items the `archive_storage` can return for a descendant query before
56/// pagination is required.
57///
58/// Note: this is identical to the `chainHead` value.
59const MAX_DESCENDANT_RESPONSES: usize = 5;
60
61/// The maximum number of queried items allowed for the `archive_storage` at a time.
62///
63/// Note: A queried item can also be a descendant query which can return up to
64/// `MAX_DESCENDANT_RESPONSES`.
65const MAX_QUERIED_ITEMS: usize = 8;
66
67impl Default for ArchiveConfig {
68	fn default() -> Self {
69		Self {
70			max_descendant_responses: MAX_DESCENDANT_RESPONSES,
71			max_queried_items: MAX_QUERIED_ITEMS,
72		}
73	}
74}
75
76/// An API for archive RPC calls.
77pub struct Archive<BE: Backend<Block>, Block: BlockT, Client> {
78	/// Substrate client.
79	client: Arc<Client>,
80	/// Backend of the chain.
81	backend: Arc<BE>,
82	/// The hexadecimal encoded hash of the genesis block.
83	genesis_hash: String,
84	/// The maximum number of items the `archive_storage` can return for a descendant query before
85	/// pagination is required.
86	storage_max_descendant_responses: usize,
87	/// The maximum number of queried items allowed for the `archive_storage` at a time.
88	storage_max_queried_items: usize,
89	/// Phantom member to pin the block type.
90	_phantom: PhantomData<Block>,
91}
92
93impl<BE: Backend<Block>, Block: BlockT, Client> Archive<BE, Block, Client> {
94	/// Create a new [`Archive`].
95	pub fn new<GenesisHash: AsRef<[u8]>>(
96		client: Arc<Client>,
97		backend: Arc<BE>,
98		genesis_hash: GenesisHash,
99		config: ArchiveConfig,
100	) -> Self {
101		let genesis_hash = hex_string(&genesis_hash.as_ref());
102		Self {
103			client,
104			backend,
105			genesis_hash,
106			storage_max_descendant_responses: config.max_descendant_responses,
107			storage_max_queried_items: config.max_queried_items,
108			_phantom: PhantomData,
109		}
110	}
111}
112
113/// Parse hex-encoded string parameter as raw bytes.
114///
115/// If the parsing fails, returns an error propagated to the RPC method.
116fn parse_hex_param(param: String) -> Result<Vec<u8>, ArchiveError> {
117	// Methods can accept empty parameters.
118	if param.is_empty() {
119		return Ok(Default::default())
120	}
121
122	array_bytes::hex2bytes(&param).map_err(|_| ArchiveError::InvalidParam(param))
123}
124
125#[async_trait]
126impl<BE, Block, Client> ArchiveApiServer<Block::Hash> for Archive<BE, Block, Client>
127where
128	Block: BlockT + 'static,
129	Block::Header: Unpin,
130	BE: Backend<Block> + 'static,
131	Client: BlockBackend<Block>
132		+ ExecutorProvider<Block>
133		+ HeaderBackend<Block>
134		+ HeaderMetadata<Block, Error = BlockChainError>
135		+ BlockchainEvents<Block>
136		+ CallApiAt<Block>
137		+ StorageProvider<Block, BE>
138		+ 'static,
139{
140	fn archive_unstable_body(&self, hash: Block::Hash) -> RpcResult<Option<Vec<String>>> {
141		let Ok(Some(signed_block)) = self.client.block(hash) else { return Ok(None) };
142
143		let extrinsics = signed_block
144			.block
145			.extrinsics()
146			.iter()
147			.map(|extrinsic| hex_string(&extrinsic.encode()))
148			.collect();
149
150		Ok(Some(extrinsics))
151	}
152
153	fn archive_unstable_genesis_hash(&self) -> RpcResult<String> {
154		Ok(self.genesis_hash.clone())
155	}
156
157	fn archive_unstable_header(&self, hash: Block::Hash) -> RpcResult<Option<String>> {
158		let Ok(Some(header)) = self.client.header(hash) else { return Ok(None) };
159
160		Ok(Some(hex_string(&header.encode())))
161	}
162
163	fn archive_unstable_finalized_height(&self) -> RpcResult<u64> {
164		Ok(self.client.info().finalized_number.saturated_into())
165	}
166
167	fn archive_unstable_hash_by_height(&self, height: u64) -> RpcResult<Vec<String>> {
168		let height: NumberFor<Block> = U256::from(height)
169			.try_into()
170			.map_err(|_| ArchiveError::InvalidParam(format!("Invalid block height: {}", height)))?;
171
172		let finalized_num = self.client.info().finalized_number;
173
174		if finalized_num >= height {
175			let Ok(Some(hash)) = self.client.block_hash(height) else { return Ok(vec![]) };
176			return Ok(vec![hex_string(&hash.as_ref())])
177		}
178
179		let blockchain = self.backend.blockchain();
180		// Fetch all the leaves of the blockchain that are on a higher or equal height.
181		let mut headers: Vec<_> = blockchain
182			.leaves()
183			.map_err(|error| ArchiveError::FetchLeaves(error.to_string()))?
184			.into_iter()
185			.filter_map(|hash| {
186				let Ok(Some(header)) = self.client.header(hash) else { return None };
187
188				if header.number() < &height {
189					return None
190				}
191
192				Some(header)
193			})
194			.collect();
195
196		let mut result = Vec::new();
197		let mut visited = HashSet::new();
198
199		while let Some(header) = headers.pop() {
200			if header.number() == &height {
201				result.push(hex_string(&header.hash().as_ref()));
202				continue
203			}
204
205			let parent_hash = *header.parent_hash();
206
207			// Continue the iteration for unique hashes.
208			// Forks might intersect on a common chain that is not yet finalized.
209			if visited.insert(parent_hash) {
210				let Ok(Some(next_header)) = self.client.header(parent_hash) else { continue };
211				headers.push(next_header);
212			}
213		}
214
215		Ok(result)
216	}
217
218	fn archive_unstable_call(
219		&self,
220		hash: Block::Hash,
221		function: String,
222		call_parameters: String,
223	) -> RpcResult<MethodResult> {
224		let call_parameters = Bytes::from(parse_hex_param(call_parameters)?);
225
226		let result =
227			self.client
228				.executor()
229				.call(hash, &function, &call_parameters, CallContext::Offchain);
230
231		Ok(match result {
232			Ok(result) => MethodResult::ok(hex_string(&result)),
233			Err(error) => MethodResult::err(error.to_string()),
234		})
235	}
236
237	fn archive_unstable_storage(
238		&self,
239		hash: Block::Hash,
240		items: Vec<PaginatedStorageQuery<String>>,
241		child_trie: Option<String>,
242	) -> RpcResult<ArchiveStorageResult> {
243		let items = items
244			.into_iter()
245			.map(|query| {
246				let key = StorageKey(parse_hex_param(query.key)?);
247				let pagination_start_key = query
248					.pagination_start_key
249					.map(|key| parse_hex_param(key).map(|key| StorageKey(key)))
250					.transpose()?;
251
252				// Paginated start key is only supported
253				if pagination_start_key.is_some() && !query.query_type.is_descendant_query() {
254					return Err(ArchiveError::InvalidParam(
255						"Pagination start key is only supported for descendants queries"
256							.to_string(),
257					))
258				}
259
260				Ok(PaginatedStorageQuery {
261					key,
262					query_type: query.query_type,
263					pagination_start_key,
264				})
265			})
266			.collect::<Result<Vec<_>, ArchiveError>>()?;
267
268		let child_trie = child_trie
269			.map(|child_trie| parse_hex_param(child_trie))
270			.transpose()?
271			.map(ChildInfo::new_default_from_vec);
272
273		let storage_client = ArchiveStorage::new(
274			self.client.clone(),
275			self.storage_max_descendant_responses,
276			self.storage_max_queried_items,
277		);
278		Ok(storage_client.handle_query(hash, items, child_trie))
279	}
280}