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