pallet_sidechain_rpc/
lib.rs1#![deny(missing_docs)]
78use derive_new::new;
79use jsonrpsee::{
80 core::{RpcResult, async_trait},
81 proc_macros::rpc,
82 types::{ErrorObject, ErrorObjectOwned, error::ErrorCode},
83};
84#[cfg(feature = "legacy-slotapi-compat")]
85use legacy_compat::slots::*;
86use sidechain_domain::mainchain_epoch::{MainchainEpochConfig, MainchainEpochDerivation};
87use sidechain_domain::{MainchainBlock, UtxoId};
88use sp_api::ProvideRuntimeApi;
89use sp_core::offchain::{Duration, Timestamp};
90use sp_runtime::traits::Block as BlockT;
91use sp_sidechain::{GetEpochDurationApi, GetGenesisUtxo};
92use std::sync::Arc;
93use time_source::*;
94use types::*;
95
96pub mod types;
98
99#[cfg(feature = "legacy-slotapi-compat")]
100mod legacy_compat;
101
102#[cfg(test)]
103mod tests;
104
105#[cfg(any(test))]
106mod mock;
107
108#[derive(serde::Serialize, serde::Deserialize, Clone, PartialEq, Debug)]
110pub struct GetParamsOutput {
111 pub genesis_utxo: UtxoId,
113}
114
115#[rpc(client, server, namespace = "sidechain")]
117pub trait SidechainRpcApi {
118 #[method(name = "getParams")]
123 fn get_params(&self) -> RpcResult<GetParamsOutput>;
124
125 #[method(name = "getStatus")]
127 async fn get_status(&self) -> RpcResult<GetStatusResponse>;
128}
129
130#[async_trait]
132pub trait SidechainRpcDataSource {
133 async fn get_latest_block_info(
135 &self,
136 ) -> Result<MainchainBlock, Box<dyn std::error::Error + Send + Sync>>;
137}
138
139#[derive(new)]
141pub struct SidechainRpc<C, Block> {
142 client: Arc<C>,
143 mc_epoch_config: MainchainEpochConfig,
144 data_source: Arc<dyn SidechainRpcDataSource + Send + Sync>,
145 time_source: Arc<dyn TimeSource + Send + Sync>,
146 _marker: std::marker::PhantomData<Block>,
147}
148
149impl<C, B> SidechainRpc<C, B> {
150 fn get_current_timestamp(&self) -> Timestamp {
151 Timestamp::from_unix_millis(self.time_source.get_current_time_millis())
152 }
153}
154
155trait SidechainRpcClient<Block: BlockT> {
157 fn get_epoch_duration(
159 &self,
160 best_block: Block::Hash,
161 ) -> Result<Duration, Box<dyn std::error::Error + Send + Sync>>;
162
163 fn get_genesis_utxo(
165 &self,
166 best_block: Block::Hash,
167 ) -> Result<UtxoId, Box<dyn std::error::Error + Send + Sync>>;
168
169 #[cfg(feature = "legacy-slotapi-compat")]
170 fn get_maybe_slot_duration(&self, best_block: Block::Hash) -> Option<u64>;
172}
173
174impl<Block, Client> SidechainRpcClient<Block> for Client
175where
176 Block: BlockT,
177 Client: sp_api::CallApiAt<Block> + Send + Sync + 'static,
178 Client: ProvideRuntimeApi<Block>,
179 Client::Api: GetEpochDurationApi<Block>,
180 Client::Api: GetGenesisUtxo<Block>,
181{
182 fn get_epoch_duration(
183 &self,
184 best_block: Block::Hash,
185 ) -> Result<Duration, Box<dyn std::error::Error + Send + Sync>> {
186 #[cfg(feature = "legacy-slotapi-compat")]
187 if let Some(slot_config) = self.get_sc_slot_config(best_block) {
188 return Ok(Duration::from_millis(
189 u64::from(slot_config.slots_per_epoch) * slot_config.slot_duration_millis,
190 ));
191 }
192
193 Ok(Duration::from_millis(self.runtime_api().get_epoch_duration_millis(best_block)?))
194 }
195
196 fn get_genesis_utxo(
197 &self,
198 best_block: Block::Hash,
199 ) -> Result<UtxoId, Box<dyn std::error::Error + Send + Sync>> {
200 Ok(self.runtime_api().genesis_utxo(best_block)?)
201 }
202
203 #[cfg(feature = "legacy-slotapi-compat")]
204 fn get_maybe_slot_duration(&self, best_block: Block::Hash) -> Option<u64> {
205 let slot_config = self.get_sc_slot_config(best_block)?;
206 Some(slot_config.slot_duration_millis)
207 }
208}
209
210#[async_trait]
211impl<C, Block> SidechainRpcApiServer for SidechainRpc<C, Block>
212where
213 Block: BlockT,
214 C: Send + Sync + 'static,
215 C: SidechainRpcClient<Block>,
216 C: GetBestHash<Block>,
217{
218 fn get_params(&self) -> RpcResult<GetParamsOutput> {
219 let best_block = self.client.best_hash();
220
221 let genesis_utxo = self.client.get_genesis_utxo(best_block).map_err(error_object_from)?;
222
223 Ok(GetParamsOutput { genesis_utxo })
224 }
225
226 async fn get_status(&self) -> RpcResult<GetStatusResponse> {
227 let best_block = self.client.best_hash();
228
229 let current_timestamp = self.get_current_timestamp();
230
231 let sc_epoch_duration: Duration =
232 self.client.get_epoch_duration(best_block).map_err(error_object_from)?;
233
234 let current_sidechain_epoch = current_timestamp.unix_millis() / sc_epoch_duration.millis();
235
236 let next_sidechain_epoch_timestamp =
237 Timestamp::from((current_sidechain_epoch + 1) * sc_epoch_duration.millis());
238
239 let latest_mainchain_block =
240 self.data_source.get_latest_block_info().await.map_err(|err| {
241 ErrorObject::owned(
242 ErrorCode::InternalError.code(),
243 format!("Internal error: GetLatestBlockResponse error '{:?}", err),
244 None::<u8>,
245 )
246 })?;
247 let next_mainchain_epoch_timestamp = self
248 .mc_epoch_config
249 .mainchain_epoch_to_timestamp(latest_mainchain_block.epoch.next());
250
251 Ok(GetStatusResponse {
252 sidechain: SidechainData {
253 epoch: current_sidechain_epoch,
254 #[cfg(feature = "legacy-slotapi-compat")]
255 slot: (self.client.get_maybe_slot_duration(best_block))
256 .map(|duration| current_timestamp.unix_millis() / duration),
257 next_epoch_timestamp: next_sidechain_epoch_timestamp,
258 },
259 mainchain: MainchainData {
260 epoch: latest_mainchain_block.epoch.0,
261 slot: latest_mainchain_block.slot.0,
262 next_epoch_timestamp: next_mainchain_epoch_timestamp,
263 },
264 })
265 }
266}
267
268fn error_object_from<T: std::fmt::Debug>(err: T) -> ErrorObjectOwned {
269 ErrorObject::owned::<u8>(-1, format!("{err:?}"), None)
270}