pezkuwi_subxt_lightclient/
lib.rs1#![deny(missing_docs)]
9#![cfg_attr(docsrs, feature(doc_cfg))]
10
11#[cfg(not(any(feature = "web", feature = "native")))]
14compile_error!(
15 "subxt-lightclient: at least one of the 'web' or 'native' features must be enabled."
16);
17
18mod platform;
19mod shared_client;
20mod background;
22mod chain_config;
23mod rpc;
24
25use background::{BackgroundTask, BackgroundTaskHandle};
26use futures::Stream;
27use platform::DefaultPlatform;
28use serde_json::value::RawValue;
29use shared_client::SharedClient;
30use std::future::Future;
31use tokio::sync::mpsc;
32
33pub use chain_config::{ChainConfig, ChainConfigError};
34
35#[derive(Debug, thiserror::Error)]
37pub enum LightClientError {
38 #[error("Failed to add the chain to the light client: {0}.")]
40 AddChainError(String),
41}
42
43#[derive(Debug, thiserror::Error)]
45pub enum LightClientRpcError {
46 #[error(transparent)]
48 JsonRpcError(JsonRpcError),
49 #[error("Smoldot could not handle the RPC call: {0}.")]
51 SmoldotError(String),
52 #[error("The background task was dropped.")]
54 BackgroundTaskDropped,
55}
56
57#[derive(Debug, thiserror::Error)]
60#[error("RPC Error: {0}.")]
61pub struct JsonRpcError(Box<RawValue>);
62
63impl JsonRpcError {
64 pub fn try_deserialize<'a, T: serde::de::Deserialize<'a>>(
66 &'a self,
67 ) -> Result<T, serde_json::Error> {
68 serde_json::from_str(self.0.get())
69 }
70}
71
72#[derive(Clone)]
76pub struct LightClient {
77 client: SharedClient<DefaultPlatform>,
78 relay_chain_id: smoldot_light::ChainId,
79}
80
81impl LightClient {
82 pub fn relay_chain<'a>(
98 chain_config: impl Into<ChainConfig<'a>>,
99 ) -> Result<(Self, LightClientRpc), LightClientError> {
100 let mut client = smoldot_light::Client::new(platform::build_platform());
101 let chain_config = chain_config.into();
102 let chain_spec = chain_config.as_chain_spec();
103
104 let config = smoldot_light::AddChainConfig {
105 specification: chain_spec,
106 json_rpc: smoldot_light::AddChainConfigJsonRpc::Enabled {
107 max_pending_requests: u32::MAX.try_into().unwrap(),
108 max_subscriptions: u32::MAX,
109 },
110 database_content: "",
111 potential_relay_chains: std::iter::empty(),
112 user_data: (),
113 };
114
115 let added_chain = client
116 .add_chain(config)
117 .map_err(|err| LightClientError::AddChainError(err.to_string()))?;
118
119 let relay_chain_id = added_chain.chain_id;
120 let rpc_responses =
121 added_chain.json_rpc_responses.expect("Light client RPC configured; qed");
122 let shared_client: SharedClient<_> = client.into();
123
124 let light_client_rpc =
125 LightClientRpc::new_raw(shared_client.clone(), relay_chain_id, rpc_responses);
126 let light_client = Self { client: shared_client, relay_chain_id };
127
128 Ok((light_client, light_client_rpc))
129 }
130
131 pub fn parachain<'a>(
146 &self,
147 chain_config: impl Into<ChainConfig<'a>>,
148 ) -> Result<LightClientRpc, LightClientError> {
149 let chain_config = chain_config.into();
150 let chain_spec = chain_config.as_chain_spec();
151
152 let config = smoldot_light::AddChainConfig {
153 specification: chain_spec,
154 json_rpc: smoldot_light::AddChainConfigJsonRpc::Enabled {
155 max_pending_requests: u32::MAX.try_into().unwrap(),
156 max_subscriptions: u32::MAX,
157 },
158 database_content: "",
159 potential_relay_chains: std::iter::once(self.relay_chain_id),
160 user_data: (),
161 };
162
163 let added_chain = self
164 .client
165 .add_chain(config)
166 .map_err(|err| LightClientError::AddChainError(err.to_string()))?;
167
168 let chain_id = added_chain.chain_id;
169 let rpc_responses =
170 added_chain.json_rpc_responses.expect("Light client RPC configured; qed");
171
172 Ok(LightClientRpc::new_raw(self.client.clone(), chain_id, rpc_responses))
173 }
174}
175
176#[derive(Clone, Debug)]
179pub struct LightClientRpc {
180 handle: BackgroundTaskHandle,
181}
182
183impl LightClientRpc {
184 pub(crate) fn new_raw<TPlat, TChain>(
187 client: impl Into<SharedClient<TPlat, TChain>>,
188 chain_id: smoldot_light::ChainId,
189 rpc_responses: smoldot_light::JsonRpcResponses<TPlat>,
190 ) -> Self
191 where
192 TPlat: smoldot_light::platform::PlatformRef + Send + 'static,
193 TChain: Send + 'static,
194 {
195 let (background_task, background_handle) =
196 BackgroundTask::new(client.into(), chain_id, rpc_responses);
197
198 spawn(async move { background_task.run().await });
201
202 LightClientRpc { handle: background_handle }
203 }
204
205 pub async fn request(
207 &self,
208 method: String,
209 params: Option<Box<RawValue>>,
210 ) -> Result<Box<RawValue>, LightClientRpcError> {
211 self.handle.request(method, params).await
212 }
213
214 pub async fn subscribe(
216 &self,
217 method: String,
218 params: Option<Box<RawValue>>,
219 unsub: String,
220 ) -> Result<LightClientRpcSubscription, LightClientRpcError> {
221 let (id, notifications) = self.handle.subscribe(method, params, unsub).await?;
222 Ok(LightClientRpcSubscription { id, notifications })
223 }
224}
225
226pub struct LightClientRpcSubscription {
228 notifications: mpsc::UnboundedReceiver<Result<Box<RawValue>, JsonRpcError>>,
229 id: String,
230}
231
232impl LightClientRpcSubscription {
233 pub fn id(&self) -> &str {
235 &self.id
236 }
237}
238
239impl Stream for LightClientRpcSubscription {
240 type Item = Result<Box<RawValue>, JsonRpcError>;
241 fn poll_next(
242 mut self: std::pin::Pin<&mut Self>,
243 cx: &mut std::task::Context<'_>,
244 ) -> std::task::Poll<Option<Self::Item>> {
245 self.notifications.poll_recv(cx)
246 }
247}
248
249fn spawn<F: Future + Send + 'static>(future: F) {
252 #[cfg(feature = "native")]
253 tokio::spawn(async move {
254 future.await;
255 });
256 #[cfg(all(feature = "web", not(feature = "native")))]
257 wasm_bindgen_futures::spawn_local(async move {
258 future.await;
259 });
260}