1#![deny(missing_docs)]
17
18use anyhow::anyhow;
67use codec::Encode;
68use ismp::{
69 consensus::{ConsensusClientId, StateMachineHeight, StateMachineId},
70 events::Event,
71 router::{Request, Response},
72};
73use jsonrpsee::{
74 core::RpcResult,
75 proc_macros::rpc,
76 types::{ErrorObject, ErrorObjectOwned},
77};
78use pallet_ismp::{child_trie::CHILD_TRIE_PREFIX, offchain::LeafIndexQuery};
79use pallet_ismp_runtime_api::IsmpRuntimeApi;
80use polkadot_sdk::*;
81use sc_client_api::{Backend, BlockBackend, ChildInfo, ProofProvider, StateBackend};
82use serde::{Deserialize, Serialize};
83use sp_api::{ApiExt, ProvideRuntimeApi};
84use sp_blockchain::HeaderBackend;
85use sp_core::{
86 offchain::{storage::OffchainDb, OffchainDbExt, OffchainStorage},
87 H256,
88};
89use sp_runtime::traits::{Block as BlockT, Hash, Header};
90use sp_trie::LayoutV0;
91use std::{collections::HashMap, fmt::Display, sync::Arc};
92use trie_db::{Recorder, Trie, TrieDBBuilder};
93
94#[derive(Clone, Hash, Debug, PartialEq, Eq, Copy, Serialize, Deserialize)]
96#[serde(untagged)]
97pub enum BlockNumberOrHash<Hash> {
98 Hash(Hash),
100 Number(u32),
102}
103
104impl<Hash: std::fmt::Debug> Display for BlockNumberOrHash<Hash> {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 match self {
107 BlockNumberOrHash::Hash(hash) => write!(f, "{:?}", hash),
108 BlockNumberOrHash::Number(block_num) => write!(f, "{}", block_num),
109 }
110 }
111}
112
113#[derive(Serialize, Deserialize, Clone)]
115pub struct Proof {
116 pub proof: Vec<u8>,
118 pub height: u32,
120}
121
122pub fn runtime_error_into_rpc_error(e: impl std::fmt::Display) -> ErrorObjectOwned {
124 ErrorObject::owned(
125 9876, format!("{}", e),
127 None::<String>,
128 )
129}
130
131#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Default)]
133pub struct EventMetadata {
134 pub block_hash: H256,
136 pub transaction_hash: H256,
138 pub block_number: u64,
140}
141
142#[derive(Serialize, Deserialize, Clone)]
144pub struct EventWithMetadata {
145 pub meta: EventMetadata,
147 pub event: Event,
149}
150
151#[rpc(client, server)]
153pub trait IsmpApi<Hash>
154where
155 Hash: PartialEq + Eq + std::hash::Hash,
156{
157 #[method(name = "ismp_queryRequests")]
159 fn query_requests(&self, query: Vec<LeafIndexQuery>) -> RpcResult<Vec<Request>>;
160
161 #[method(name = "ismp_queryResponses")]
163 fn query_responses(&self, query: Vec<LeafIndexQuery>) -> RpcResult<Vec<Response>>;
164
165 #[method(name = "ismp_queryStateProof")]
167 fn query_state_proof(&self, height: u32, keys: Vec<Vec<u8>>) -> RpcResult<Proof>;
168
169 #[method(name = "ismp_queryChildTrieProof")]
171 fn query_child_trie_proof(&self, height: u32, keys: Vec<Vec<u8>>) -> RpcResult<Proof>;
172
173 #[method(name = "ismp_queryConsensusState")]
175 fn query_consensus_state(
176 &self,
177 height: Option<u32>,
178 client_id: ConsensusClientId,
179 ) -> RpcResult<Vec<u8>>;
180
181 #[method(name = "ismp_queryStateMachineUpdateTime")]
183 fn query_state_machine_update_time(&self, height: StateMachineHeight) -> RpcResult<u64>;
184
185 #[method(name = "ismp_queryChallengePeriod")]
187 fn query_challenge_period(&self, client_id: StateMachineId) -> RpcResult<u64>;
188
189 #[method(name = "ismp_queryStateMachineLatestHeight")]
191 fn query_state_machine_latest_height(&self, id: StateMachineId) -> RpcResult<u64>;
192
193 #[method(name = "ismp_queryEvents")]
196 fn query_events(
197 &self,
198 from: BlockNumberOrHash<Hash>,
199 to: BlockNumberOrHash<Hash>,
200 ) -> RpcResult<HashMap<String, Vec<Event>>>;
201
202 #[method(name = "ismp_queryEventsWithMetadata")]
205 fn query_events_with_metadata(
206 &self,
207 from: BlockNumberOrHash<Hash>,
208 to: BlockNumberOrHash<Hash>,
209 ) -> RpcResult<HashMap<String, Vec<EventWithMetadata>>>;
210}
211
212pub struct IsmpRpcHandler<C, B, S, T> {
214 client: Arc<C>,
215 backend: Arc<T>,
216 offchain_db: OffchainDb<S>,
217 _marker: std::marker::PhantomData<B>,
218}
219
220impl<C, B, S, T> IsmpRpcHandler<C, B, S, T>
221where
222 B: BlockT,
223 S: OffchainStorage + Clone + Send + Sync + 'static,
224 T: Backend<B, OffchainStorage = S> + Send + Sync + 'static,
225{
226 pub fn new(client: Arc<C>, backend: Arc<T>) -> Result<Self, anyhow::Error> {
228 let offchain_db = OffchainDb::new(
229 backend
230 .offchain_storage()
231 .ok_or_else(|| anyhow!("Offchain Storage not present in backend!"))?,
232 );
233
234 Ok(Self { client, offchain_db, backend, _marker: Default::default() })
235 }
236}
237
238impl<C, Block, S, T> IsmpApiServer<Block::Hash> for IsmpRpcHandler<C, Block, S, T>
239where
240 Block: BlockT,
241 S: OffchainStorage + Clone + Send + Sync + 'static,
242 T: Backend<Block> + Send + Sync + 'static,
243 C: Send
244 + Sync
245 + 'static
246 + ProvideRuntimeApi<Block>
247 + HeaderBackend<Block>
248 + ProofProvider<Block>
249 + BlockBackend<Block>,
250 C::Api: IsmpRuntimeApi<Block, Block::Hash>,
251 Block::Hash: Into<H256>,
252 u64: From<<Block::Header as Header>::Number>,
253{
254 fn query_requests(&self, query: Vec<LeafIndexQuery>) -> RpcResult<Vec<Request>> {
255 let mut api = self.client.runtime_api();
256 api.register_extension(OffchainDbExt::new(self.offchain_db.clone()));
257 let at = self.client.info().best_hash;
258 api.requests(at, query.into_iter().map(|query| query.commitment).collect())
259 .map_err(|_| runtime_error_into_rpc_error("Error fetching requests"))
260 }
261
262 fn query_responses(&self, query: Vec<LeafIndexQuery>) -> RpcResult<Vec<Response>> {
263 let mut api = self.client.runtime_api();
264 api.register_extension(OffchainDbExt::new(self.offchain_db.clone()));
265 let at = self.client.info().best_hash;
266 api.responses(at, query.into_iter().map(|query| query.commitment).collect())
267 .map_err(|_| runtime_error_into_rpc_error("Error fetching responses"))
268 }
269
270 fn query_state_proof(&self, height: u32, keys: Vec<Vec<u8>>) -> RpcResult<Proof> {
271 let at = self.client.block_hash(height.into()).ok().flatten().ok_or_else(|| {
272 runtime_error_into_rpc_error("Could not find valid blockhash for provided height")
273 })?;
274 let proof: Vec<_> = self
275 .client
276 .read_proof(at, &mut keys.iter().map(|key| key.as_slice()))
277 .map(|proof| proof.into_iter_nodes().collect())
278 .map_err(|e| {
279 runtime_error_into_rpc_error(format!("Error generating state proof: {e:?}"))
280 })?;
281 Ok(Proof { proof: proof.encode(), height })
282 }
283
284 fn query_child_trie_proof(&self, height: u32, keys: Vec<Vec<u8>>) -> RpcResult<Proof> {
285 let at = self.client.block_hash(height.into()).ok().flatten().ok_or_else(|| {
286 runtime_error_into_rpc_error("Could not find valid blockhash for provided height")
287 })?;
288 let child_info = ChildInfo::new_default(CHILD_TRIE_PREFIX);
289 let storage_proof = self
290 .client
291 .read_child_proof(at, &child_info, &mut keys.iter().map(|key| key.as_slice()))
292 .map_err(|e| {
293 runtime_error_into_rpc_error(format!("Error generating child trie proof: {e:?}"))
294 })?;
295 let state =
296 self.backend
297 .state_at(at, sc_client_api::TrieCacheContext::Untrusted)
298 .map_err(|e| {
299 runtime_error_into_rpc_error(format!("Error accessing state backend: {e:?}"))
300 })?;
301 let child_root = state
302 .storage(child_info.prefixed_storage_key().as_slice())
303 .map_err(|err| runtime_error_into_rpc_error(format!("Storage Read Error: {err:?}")))?
304 .map(|r| {
305 let mut hash = <<Block::Header as Header>::Hashing as Hash>::Output::default();
306
307 hash.as_mut().copy_from_slice(&r[..]);
309
310 hash
311 })
312 .ok_or_else(|| runtime_error_into_rpc_error("Child trie root storage returned None"))?;
313
314 let db = storage_proof.into_memory_db::<<Block::Header as Header>::Hashing>();
315
316 let mut recorder = Recorder::<LayoutV0<<Block::Header as Header>::Hashing>>::default();
317 let trie =
318 TrieDBBuilder::<LayoutV0<<Block::Header as Header>::Hashing>>::new(&db, &child_root)
319 .with_recorder(&mut recorder)
320 .build();
321 for key in keys {
322 let _ = trie.get(&key).map_err(|e| {
323 runtime_error_into_rpc_error(format!("Error generating child trie proof: {e:?}"))
324 })?;
325 }
326
327 let proof_nodes = recorder.drain().into_iter().map(|f| f.data).collect::<Vec<_>>();
328 Ok(Proof { proof: proof_nodes.encode(), height })
329 }
330
331 fn query_consensus_state(
332 &self,
333 height: Option<u32>,
334 client_id: ConsensusClientId,
335 ) -> RpcResult<Vec<u8>> {
336 let api = self.client.runtime_api();
337 let at = height
338 .and_then(|height| self.client.block_hash(height.into()).ok().flatten())
339 .unwrap_or(self.client.info().best_hash);
340 api.consensus_state(at, client_id)
341 .ok()
342 .flatten()
343 .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Consensus state"))
344 }
345
346 fn query_state_machine_update_time(&self, height: StateMachineHeight) -> RpcResult<u64> {
347 let api = self.client.runtime_api();
348 let at = self.client.info().best_hash;
349 api.state_machine_update_time(at, height)
350 .ok()
351 .flatten()
352 .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Consensus update time"))
353 }
354
355 fn query_challenge_period(&self, state_machine_id: StateMachineId) -> RpcResult<u64> {
356 let api = self.client.runtime_api();
357 let at = self.client.info().best_hash;
358 api.challenge_period(at, state_machine_id)
359 .ok()
360 .flatten()
361 .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Challenge period"))
362 }
363
364 fn query_state_machine_latest_height(&self, id: StateMachineId) -> RpcResult<u64> {
365 let api = self.client.runtime_api();
366 let at = self.client.info().best_hash;
367 api.latest_state_machine_height(at, id).ok().flatten().ok_or_else(|| {
368 runtime_error_into_rpc_error("Error fetching latest state machine height")
369 })
370 }
371
372 fn query_events(
373 &self,
374 from: BlockNumberOrHash<Block::Hash>,
375 to: BlockNumberOrHash<Block::Hash>,
376 ) -> RpcResult<HashMap<String, Vec<Event>>> {
377 let mut events = HashMap::new();
378 let to =
379 match to {
380 BlockNumberOrHash::Hash(block_hash) => block_hash,
381 BlockNumberOrHash::Number(block_number) =>
382 self.client.block_hash(block_number.into()).ok().flatten().ok_or_else(|| {
383 runtime_error_into_rpc_error("Invalid block number provided")
384 })?,
385 };
386
387 let from =
388 match from {
389 BlockNumberOrHash::Hash(block_hash) => block_hash,
390 BlockNumberOrHash::Number(block_number) =>
391 self.client.block_hash(block_number.into()).ok().flatten().ok_or_else(|| {
392 runtime_error_into_rpc_error("Invalid block number provided")
393 })?,
394 };
395
396 let from_block = self
397 .client
398 .header(from)
399 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
400 .ok_or_else(|| runtime_error_into_rpc_error("Invalid block number or hash provided"))?;
401
402 let mut header = self
403 .client
404 .header(to)
405 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
406 .ok_or_else(|| runtime_error_into_rpc_error("Invalid block number or hash provided"))?;
407
408 while header.number() >= from_block.number() {
409 let mut api = self.client.runtime_api();
410 api.register_extension(OffchainDbExt::new(self.offchain_db.clone()));
411 let at = header.hash();
412
413 let temp: Vec<Event> = api.block_events(at).map_err(|e| {
414 runtime_error_into_rpc_error(format!("failed to read block events {:?}", e))
415 })?;
416
417 events.insert(format!("{:?}", header.hash()), temp);
418 header = self
419 .client
420 .header(*header.parent_hash())
421 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
422 .ok_or_else(|| {
423 runtime_error_into_rpc_error("Invalid block number or hash provided")
424 })?;
425 }
426 Ok(events)
427 }
428
429 fn query_events_with_metadata(
430 &self,
431 from: BlockNumberOrHash<Block::Hash>,
432 to: BlockNumberOrHash<Block::Hash>,
433 ) -> RpcResult<HashMap<String, Vec<EventWithMetadata>>> {
434 let mut events = HashMap::new();
435 let to =
436 match to {
437 BlockNumberOrHash::Hash(block_hash) => block_hash,
438 BlockNumberOrHash::Number(block_number) =>
439 self.client.block_hash(block_number.into()).ok().flatten().ok_or_else(|| {
440 runtime_error_into_rpc_error("Invalid block number provided")
441 })?,
442 };
443
444 let from =
445 match from {
446 BlockNumberOrHash::Hash(block_hash) => block_hash,
447 BlockNumberOrHash::Number(block_number) =>
448 self.client.block_hash(block_number.into()).ok().flatten().ok_or_else(|| {
449 runtime_error_into_rpc_error("Invalid block number provided")
450 })?,
451 };
452
453 let from_block = self
454 .client
455 .header(from)
456 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
457 .ok_or_else(|| runtime_error_into_rpc_error("Invalid block number or hash provided"))?;
458
459 let mut header = self
460 .client
461 .header(to)
462 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
463 .ok_or_else(|| runtime_error_into_rpc_error("Invalid block number or hash provided"))?;
464
465 while header.number() >= from_block.number() {
466 let mut api = self.client.runtime_api();
467 api.register_extension(OffchainDbExt::new(self.offchain_db.clone()));
468 let at = header.hash();
469
470 let block_events = api.block_events_with_metadata(at).map_err(|e| {
471 runtime_error_into_rpc_error(format!("failed to read block events {:?}", e))
472 })?;
473
474 let mut temp = vec![];
475
476 for (event, index) in block_events {
477 let extrinsic_hash = if let Some(index) = index {
478 let extrinsic = self
479 .client
480 .block_body(at)
481 .map_err(|err| {
482 runtime_error_into_rpc_error(format!(
483 "Error fetching extrinsic for block {at:?}: {err:?}"
484 ))
485 })?
486 .ok_or_else(|| {
487 runtime_error_into_rpc_error(format!(
488 "No extrinsics found for block {at:?}"
489 ))
490 })?
491 .swap_remove(index as usize);
494 let ext_bytes = json::to_string(&extrinsic).map_err(|err| {
495 runtime_error_into_rpc_error(format!(
496 "Failed to serialize extrinsic: {err:?}"
497 ))
498 })?;
499 let len = ext_bytes.as_bytes().len() - 1;
500 let extrinsic =
501 hex::decode(ext_bytes.as_bytes()[3..len].to_vec()).map_err(|err| {
502 runtime_error_into_rpc_error(format!(
503 "Failed to decode extrinsic: {err:?}"
504 ))
505 })?;
506 <Block::Header as Header>::Hashing::hash(extrinsic.as_slice())
507 } else {
508 Default::default()
509 };
510
511 temp.push(EventWithMetadata {
512 meta: EventMetadata {
513 block_hash: at.into(),
514 transaction_hash: extrinsic_hash.into(),
515 block_number: u64::from(*header.number()),
516 },
517 event,
518 });
519 }
520
521 events.insert(format!("{:?}", header.hash()), temp);
523 header = self
524 .client
525 .header(*header.parent_hash())
526 .map_err(|e| runtime_error_into_rpc_error(e.to_string()))?
527 .ok_or_else(|| {
528 runtime_error_into_rpc_error("Invalid block number or hash provided")
529 })?;
530 }
531 Ok(events)
532 }
533}