// Copyright 2019-2023 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. use std::iter; use super::{ background::{BackgroundTask, FromSubxt, MethodResponse}, LightClientRpcError, }; use serde_json::value::RawValue; use tokio::sync::{mpsc, mpsc::error::SendError, oneshot}; use super::platform::build_platform; pub const LOG_TARGET: &str = "subxt-light-client"; /// A raw light-client RPC implementation that can connect to multiple chains. #[derive(Clone)] pub struct RawLightClientRpc { /// Communicate with the backend task that multiplexes the responses /// back to the frontend. to_backend: mpsc::UnboundedSender, } impl RawLightClientRpc { /// Construct a [`LightClientRpc`] that can communicated with the provided chain. /// /// # Note /// /// This uses the same underlying instance created by [`LightClientRpc::new_from_client`]. pub fn for_chain(&self, chain_id: smoldot_light::ChainId) -> LightClientRpc { LightClientRpc { to_backend: self.to_backend.clone(), chain_id, } } } /// The light-client RPC implementation that is used to connect with the chain. #[derive(Clone)] pub struct LightClientRpc { /// Communicate with the backend task that multiplexes the responses /// back to the frontend. to_backend: mpsc::UnboundedSender, /// The chain ID to target for requests. chain_id: smoldot_light::ChainId, } impl LightClientRpc { /// Constructs a new [`LightClientRpc`], providing the chain specification. /// /// The chain specification can be downloaded from a trusted network via /// the `sync_state_genSyncSpec` RPC method. This parameter expects the /// chain spec in text format (ie not in hex-encoded scale-encoded as RPC methods /// will provide). /// /// ## Panics /// /// The panic behaviour depends on the feature flag being used: /// /// ### Native /// /// Panics when called outside of a `tokio` runtime context. /// /// ### Web /// /// If smoldot panics, then the promise created will be leaked. For more details, see /// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html. pub fn new( config: smoldot_light::AddChainConfig< '_, (), impl IntoIterator, >, ) -> Result { tracing::trace!(target: LOG_TARGET, "Create light client"); let mut client = smoldot_light::Client::new(build_platform()); let config = smoldot_light::AddChainConfig { specification: config.specification, json_rpc: config.json_rpc, database_content: config.database_content, potential_relay_chains: config.potential_relay_chains.into_iter(), user_data: config.user_data, }; let smoldot_light::AddChainSuccess { chain_id, json_rpc_responses, } = client .add_chain(config) .map_err(|err| LightClientRpcError::AddChainError(err.to_string()))?; let rpc_responses = json_rpc_responses.expect("Light client RPC configured; qed"); let raw_client = Self::new_from_client( client, iter::once(AddedChain { chain_id, rpc_responses, }), ); Ok(raw_client.for_chain(chain_id)) } /// Constructs a new [`RawLightClientRpc`] from the raw smoldot client. /// /// Receives a list of RPC objects as a result of calling `smoldot_light::Client::add_chain`. /// This [`RawLightClientRpc`] can target different chains using [`RawLightClientRpc::for_chain`] method. /// /// ## Panics /// /// The panic behaviour depends on the feature flag being used: /// /// ### Native /// /// Panics when called outside of a `tokio` runtime context. /// /// ### Web /// /// If smoldot panics, then the promise created will be leaked. For more details, see /// https://docs.rs/wasm-bindgen-futures/latest/wasm_bindgen_futures/fn.future_to_promise.html. pub fn new_from_client( client: smoldot_light::Client, chains: impl IntoIterator, ) -> RawLightClientRpc where TPlat: smoldot_light::platform::PlatformRef + Clone, { let (to_backend, backend) = mpsc::unbounded_channel(); let chains = chains.into_iter().collect(); let future = async move { let mut task = BackgroundTask::new(client); task.start_task(backend, chains).await; }; #[cfg(feature = "native")] tokio::spawn(future); #[cfg(feature = "web")] wasm_bindgen_futures::spawn_local(future); RawLightClientRpc { to_backend } } /// Returns the chain ID of the current light-client. pub fn chain_id(&self) -> smoldot_light::ChainId { self.chain_id } /// Submits an RPC method request to the light-client. /// /// This method sends a request to the light-client to execute an RPC method with the provided parameters. /// The parameters are parsed into a valid JSON object in the background. pub fn method_request( &self, method: String, params: String, ) -> Result, SendError> { let (sender, receiver) = oneshot::channel(); self.to_backend.send(FromSubxt::Request { method, params, sender, chain_id: self.chain_id, })?; Ok(receiver) } /// Makes an RPC subscription call to the light-client. /// /// This method sends a request to the light-client to establish an RPC subscription with the provided parameters. /// The parameters are parsed into a valid JSON object in the background. #[allow(clippy::type_complexity)] pub fn subscription_request( &self, method: String, params: String, unsubscribe_method: String, ) -> Result< ( oneshot::Receiver, mpsc::UnboundedReceiver>, ), SendError, > { let (sub_id, sub_id_rx) = oneshot::channel(); let (sender, receiver) = mpsc::unbounded_channel(); self.to_backend.send(FromSubxt::Subscription { method, unsubscribe_method, params, sub_id, sender, chain_id: self.chain_id, })?; Ok((sub_id_rx, receiver)) } } /// The added chain of the light-client. pub struct AddedChain { /// The id of the chain. pub chain_id: smoldot_light::ChainId, /// Producer of RPC responses for the chain. pub rpc_responses: smoldot_light::JsonRpcResponses, }