pallet_sidechain_rpc/
lib.rs

1//! Json RPC for the Sidechain pallet
2//!
3//! # Usage
4//!
5//! ## Implementing runtime APIs
6//!
7//! Your runtime should implement the [SlotApi] and [GetGenesisUtxo] runtime APIs. For example, if
8//! your chain uses Aura for consensus, they may be implemented similar to this:
9//! ```rust,ignore
10//! impl sidechain_slots::SlotApi<Block> for Runtime {
11//!    fn slot_config() -> sidechain_slots::ScSlotConfig {
12//!      sidechain_slots::ScSlotConfig {
13//!        slots_per_epoch: Sidechain::slots_per_epoch(),
14//!        slot_duration: SlotDuration::from(Aura::slot_duration())
15//!      }
16//!    }
17//!  }
18//! impl sp_sidechain::GetGenesisUtxo<Block> for Runtime {
19//!   fn genesis_utxo() -> UtxoId {
20//!     Sidechain::genesis_utxo()
21//!   }
22//! }
23//! ```
24//!
25//! ## Adding to the RPC stack
26//!
27//! Once the runtime APIs are in place, the RPC can be added to the node:
28//!
29//! ```rust
30//! # use jsonrpsee::RpcModule;
31//! # use pallet_sidechain_rpc::{*, types::GetBestHash};
32//! # use sidechain_domain::mainchain_epoch::MainchainEpochConfig;
33//! # use sidechain_slots::SlotApi;
34//! # use sp_api::ProvideRuntimeApi;
35//! # use sp_runtime::traits::Block as BlockT;
36//! # use sp_sidechain::GetGenesisUtxo;
37//! # use std::sync::Arc;
38//! # use time_source::TimeSource;
39//! fn create_rpc<B: BlockT, C: Send + Sync + 'static>(
40//!    client: Arc<C>,
41//!    time_source: Arc<dyn TimeSource + Send + Sync>,
42//!    data_source: Arc<dyn SidechainRpcDataSource + Send + Sync>,
43//! ) -> Result<RpcModule<()>, Box<dyn std::error::Error + Send + Sync>>
44//! where
45//!     C: ProvideRuntimeApi<B> + GetBestHash<B>,
46//!     C::Api: SlotApi<B> + GetGenesisUtxo<B>
47//! {
48//!
49//!     let mut module = RpcModule::new(());
50//!     module.merge(
51//!         SidechainRpc::new(
52//!             client.clone(),
53//!             MainchainEpochConfig::read_from_env().unwrap(),
54//!             data_source,
55//!             time_source,
56//!         )
57//!         .into_rpc(),
58//!     )?;
59//!
60//!     // ... other RPCs
61//!     Ok(module)
62//! }
63//! ```
64//!
65//! Note that your node should already have necessary time and data sources wired in. A Db-Sync-based
66//! data source is provided by the Partner Chain toolkit in the `partner_chains_db_sync_data_sources`
67//! crate.
68//!
69//! [GetGenesisUtxo]: sp_sidechain::GetGenesisUtxo
70//! [SlotApi]: sidechain_slots::SlotApi
71#![deny(missing_docs)]
72use derive_new::new;
73use jsonrpsee::{
74	core::{RpcResult, async_trait},
75	proc_macros::rpc,
76	types::{ErrorObject, ErrorObjectOwned, error::ErrorCode},
77};
78use sidechain_domain::mainchain_epoch::{MainchainEpochConfig, MainchainEpochDerivation};
79use sidechain_domain::{MainchainBlock, UtxoId};
80use sidechain_slots::SlotApi;
81use sp_api::ProvideRuntimeApi;
82use sp_core::offchain::Timestamp;
83use sp_runtime::traits::Block as BlockT;
84use sp_sidechain::GetGenesisUtxo;
85use std::sync::Arc;
86use time_source::*;
87use types::*;
88
89/// Response types returned by RPC endpoints for Sidechain pallet
90pub mod types;
91
92#[cfg(test)]
93mod tests;
94
95#[cfg(any(test))]
96mod mock;
97
98/// Response type of the [SidechainRpcApi::get_params] method
99#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Debug)]
100pub struct GetParamsOutput {
101	/// Genesis UTXO of the queried Partner Chain
102	pub genesis_utxo: UtxoId,
103}
104
105/// Json RPC API for querying basic information about a Partner Chain
106#[rpc(client, server, namespace = "sidechain")]
107pub trait SidechainRpcApi {
108	/// Gets the genesis UTXO of the Partner Chain
109	///
110	/// note: the legacy name `get_params` comes from the times when there were more parameters that
111	///       defined a Partner Chain than a single genesis UTXO
112	#[method(name = "getParams")]
113	fn get_params(&self) -> RpcResult<GetParamsOutput>;
114
115	/// Gets information about current Partner Chain and Cardano slot and epoch number
116	#[method(name = "getStatus")]
117	async fn get_status(&self) -> RpcResult<GetStatusResponse>;
118}
119
120/// Data source used by [SidechainRpc] for querying latest block
121#[async_trait]
122pub trait SidechainRpcDataSource {
123	/// Returns the latest Partner Chain block info
124	async fn get_latest_block_info(
125		&self,
126	) -> Result<MainchainBlock, Box<dyn std::error::Error + Send + Sync>>;
127}
128
129/// Json RPC service implementing [SidechainRpcApiServer]
130#[derive(new)]
131pub struct SidechainRpc<C, Block> {
132	client: Arc<C>,
133	mc_epoch_config: MainchainEpochConfig,
134	data_source: Arc<dyn SidechainRpcDataSource + Send + Sync>,
135	time_source: Arc<dyn TimeSource + Send + Sync>,
136	_marker: std::marker::PhantomData<Block>,
137}
138
139impl<C, B> SidechainRpc<C, B> {
140	fn get_current_timestamp(&self) -> Timestamp {
141		Timestamp::from_unix_millis(self.time_source.get_current_time_millis())
142	}
143}
144
145#[async_trait]
146impl<C, Block> SidechainRpcApiServer for SidechainRpc<C, Block>
147where
148	Block: BlockT,
149	C: Send + Sync + 'static,
150	C: ProvideRuntimeApi<Block>,
151	C: GetBestHash<Block>,
152	C::Api: SlotApi<Block> + GetGenesisUtxo<Block>,
153{
154	fn get_params(&self) -> RpcResult<GetParamsOutput> {
155		let api = self.client.runtime_api();
156		let best_block = self.client.best_hash();
157
158		let genesis_utxo = api.genesis_utxo(best_block).map_err(error_object_from)?;
159
160		Ok(GetParamsOutput { genesis_utxo })
161	}
162
163	async fn get_status(&self) -> RpcResult<GetStatusResponse> {
164		let api = self.client.runtime_api();
165		let best_block = self.client.best_hash();
166
167		let slot_config = api.slot_config(best_block).map_err(error_object_from)?;
168
169		let current_timestamp = self.get_current_timestamp();
170		let current_sidechain_slot =
171			slot_config.slot_from_timestamp(current_timestamp.unix_millis());
172		let current_sidechain_epoch = slot_config.epoch_number(current_sidechain_slot);
173		let next_sidechain_epoch_timestamp = slot_config
174			.epoch_start_time(current_sidechain_epoch.next())
175			.ok_or(GetStatusRpcError::CannotConvertSidechainSlotToTimestamp)?;
176
177		let latest_mainchain_block =
178			self.data_source.get_latest_block_info().await.map_err(|err| {
179				ErrorObject::owned(
180					ErrorCode::InternalError.code(),
181					format!("Internal error: GetLatestBlockResponse error '{:?}", err),
182					None::<u8>,
183				)
184			})?;
185		let next_mainchain_epoch_timestamp = self
186			.mc_epoch_config
187			.mainchain_epoch_to_timestamp(latest_mainchain_block.epoch.next());
188
189		Ok(GetStatusResponse {
190			sidechain: SidechainData {
191				epoch: current_sidechain_epoch.0,
192				slot: current_sidechain_slot.into(),
193				next_epoch_timestamp: next_sidechain_epoch_timestamp,
194			},
195			mainchain: MainchainData {
196				epoch: latest_mainchain_block.epoch.0,
197				slot: latest_mainchain_block.slot.0,
198				next_epoch_timestamp: next_mainchain_epoch_timestamp,
199			},
200		})
201	}
202}
203
204fn error_object_from<T: std::fmt::Debug>(err: T) -> ErrorObjectOwned {
205	ErrorObject::owned::<u8>(-1, format!("{err:?}"), None)
206}