From 1de5a06a8a9ac41361445900e500402811ccc1c0 Mon Sep 17 00:00:00 2001 From: Alexandru Vasile Date: Tue, 9 Apr 2024 14:33:27 +0300 Subject: [PATCH] Solve merge conflicts Signed-off-by: Alexandru Vasile --- lightclient/src/client.rs | 212 -------------- subxt/src/client/light_client/builder.rs | 336 ----------------------- subxt/src/client/light_client/mod.rs | 194 ------------- 3 files changed, 742 deletions(-) delete mode 100644 lightclient/src/client.rs delete mode 100644 subxt/src/client/light_client/builder.rs delete mode 100644 subxt/src/client/light_client/mod.rs diff --git a/lightclient/src/client.rs b/lightclient/src/client.rs deleted file mode 100644 index ca7b5f8760..0000000000 --- a/lightclient/src/client.rs +++ /dev/null @@ -1,212 +0,0 @@ -// 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, -} diff --git a/subxt/src/client/light_client/builder.rs b/subxt/src/client/light_client/builder.rs deleted file mode 100644 index 8553cedea4..0000000000 --- a/subxt/src/client/light_client/builder.rs +++ /dev/null @@ -1,336 +0,0 @@ -// 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 super::{rpc::LightClientRpc, LightClient, LightClientError}; -use crate::backend::rpc::RpcClient; -use crate::client::RawLightClient; -use crate::macros::{cfg_jsonrpsee_native, cfg_jsonrpsee_web}; -use crate::utils::validate_url_is_secure; -use crate::{config::Config, error::Error, OnlineClient}; -use std::num::NonZeroU32; -use subxt_lightclient::{smoldot, AddedChain}; - -/// Builder for [`LightClient`]. -#[derive(Clone, Debug)] -pub struct LightClientBuilder { - max_pending_requests: NonZeroU32, - max_subscriptions: u32, - bootnodes: Option>, - potential_relay_chains: Option>, - _marker: std::marker::PhantomData, -} - -impl Default for LightClientBuilder { - fn default() -> Self { - Self { - max_pending_requests: NonZeroU32::new(128) - .expect("Valid number is greater than zero; qed"), - max_subscriptions: 1024, - bootnodes: None, - potential_relay_chains: None, - _marker: std::marker::PhantomData, - } - } -} - -impl LightClientBuilder { - /// Create a new [`LightClientBuilder`]. - pub fn new() -> LightClientBuilder { - LightClientBuilder::default() - } - - /// Overwrite the bootnodes of the chain specification. - /// - /// Can be used to provide trusted entities to the chain spec, or for - /// testing environments. - pub fn bootnodes<'a>(mut self, bootnodes: impl IntoIterator) -> Self { - self.bootnodes = Some(bootnodes.into_iter().map(Into::into).collect()); - self - } - - /// Maximum number of JSON-RPC in the queue of requests waiting to be processed. - /// This parameter is necessary for situations where the JSON-RPC clients aren't - /// trusted. If you control all the requests that are sent out and don't want them - /// to fail, feel free to pass `u32::max_value()`. - /// - /// Default is 128. - pub fn max_pending_requests(mut self, max_pending_requests: NonZeroU32) -> Self { - self.max_pending_requests = max_pending_requests; - self - } - - /// Maximum number of active subscriptions before new ones are automatically - /// rejected. Any JSON-RPC request that causes the server to generate notifications - /// counts as a subscription. - /// - /// Default is 1024. - pub fn max_subscriptions(mut self, max_subscriptions: u32) -> Self { - self.max_subscriptions = max_subscriptions; - self - } - - /// If the chain spec defines a parachain, contains the list of relay chains to choose - /// from. Ignored if not a parachain. - /// - /// This field is necessary because multiple different chain can have the same identity. - /// - /// For example: if user A adds a chain named "Kusama", then user B adds a different chain - /// also named "Kusama", then user B adds a parachain whose relay chain is "Kusama", it would - /// be wrong to connect to the "Kusama" created by user A. - pub fn potential_relay_chains( - mut self, - potential_relay_chains: impl IntoIterator, - ) -> Self { - self.potential_relay_chains = Some(potential_relay_chains.into_iter().collect()); - self - } - - /// Build the light client with specified URL to connect to. - /// You must provide the port number in the URL. - /// - /// ## 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. - #[cfg(feature = "jsonrpsee")] - #[cfg_attr(docsrs, doc(cfg(feature = "jsonrpsee")))] - pub async fn build_from_url>(self, url: Url) -> Result, Error> { - validate_url_is_secure(url.as_ref())?; - self.build_from_insecure_url(url).await - } - - /// Build the light client with specified URL to connect to. Allows insecure URLs (no SSL, ws:// or http://). - /// - /// For secure connections only, please use [`crate::LightClientBuilder::build_from_url`]. - #[cfg(feature = "jsonrpsee")] - pub async fn build_from_insecure_url>( - self, - url: Url, - ) -> Result, Error> { - let chain_spec = fetch_url(url.as_ref()).await?; - self.build_client(chain_spec).await - } - - /// Build the light client from chain spec. - /// - /// The most important field of the configuration is the chain specification. - /// This is a JSON document containing all the information necessary for the client to - /// connect to said chain. - /// - /// The chain spec must be obtained from a trusted entity. - /// - /// It can be fetched from a trusted node with the following command: - /// ```bash - /// curl -H "Content-Type: application/json" -d '{"id":1, "jsonrpc":"2.0", "method": "sync_state_genSyncSpec", "params":[true]}' http://localhost:9944/ | jq .result > res.spec - /// ``` - /// - /// # Note - /// - /// For testing environments, please populate the "bootNodes" if the not already provided. - /// See [`Self::bootnodes`] for more details. - /// - /// ## 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 async fn build(self, chain_spec: &str) -> Result, Error> { - let chain_spec = serde_json::from_str(chain_spec) - .map_err(|_| Error::LightClient(LightClientError::InvalidChainSpec))?; - - self.build_client(chain_spec).await - } - - /// Build the light client. - async fn build_client( - self, - mut chain_spec: serde_json::Value, - ) -> Result, Error> { - // Set custom bootnodes if provided. - if let Some(bootnodes) = self.bootnodes { - if let serde_json::Value::Object(map) = &mut chain_spec { - map.insert("bootNodes".to_string(), serde_json::Value::Array(bootnodes)); - } - } - - let config = smoldot::AddChainConfig { - specification: &chain_spec.to_string(), - json_rpc: smoldot::AddChainConfigJsonRpc::Enabled { - max_pending_requests: self.max_pending_requests, - max_subscriptions: self.max_subscriptions, - }, - potential_relay_chains: self.potential_relay_chains.unwrap_or_default().into_iter(), - database_content: "", - user_data: (), - }; - - let raw_rpc = LightClientRpc::new(config)?; - build_client_from_rpc(raw_rpc).await - } -} - -/// Raw builder for [`RawLightClient`]. -pub struct RawLightClientBuilder { - chains: Vec>, -} - -impl Default for RawLightClientBuilder { - fn default() -> Self { - Self { chains: Vec::new() } - } -} - -impl RawLightClientBuilder { - /// Create a new [`RawLightClientBuilder`]. - pub fn new() -> RawLightClientBuilder { - RawLightClientBuilder::default() - } - - /// Adds a new chain to the list of chains synchronized by the light client. - pub fn add_chain( - mut self, - chain_id: smoldot::ChainId, - rpc_responses: smoldot::JsonRpcResponses, - ) -> Self { - self.chains.push(AddedChain { - chain_id, - rpc_responses, - }); - self - } - - /// Construct a [`RawLightClient`] from a raw smoldot client. - /// - /// The provided `chain_id` is the chain with which the current instance of light client will interact. - /// To target a different chain call the [`LightClient::target_chain`] method. - pub async fn build(self, client: smoldot::Client) -> Result { - // The raw subxt light client that spawns the smoldot background task. - let raw_rpc: subxt_lightclient::RawLightClientRpc = - subxt_lightclient::LightClientRpc::new_from_client(client, self.chains.into_iter()); - - // The crate implementation of `RpcClientT` over the raw subxt light client. - let raw_rpc = crate::client::light_client::rpc::RawLightClientRpc::from_inner(raw_rpc); - - Ok(RawLightClient { raw_rpc }) - } -} - -/// Build the light client from a raw rpc client. -async fn build_client_from_rpc( - raw_rpc: LightClientRpc, -) -> Result, Error> { - let chain_id = raw_rpc.chain_id(); - let rpc_client = RpcClient::new(raw_rpc); - let client = OnlineClient::::from_rpc_client(rpc_client).await?; - - Ok(LightClient { client, chain_id }) -} - -/// Fetch the chain spec from the URL. -#[cfg(feature = "jsonrpsee")] -async fn fetch_url(url: impl AsRef) -> Result { - use jsonrpsee::core::client::{ClientT, SubscriptionClientT}; - use jsonrpsee::rpc_params; - use serde_json::value::RawValue; - - let client = jsonrpsee_helpers::client(url.as_ref()).await?; - - let result = client - .request("sync_state_genSyncSpec", jsonrpsee::rpc_params![true]) - .await - .map_err(|err| Error::Rpc(crate::error::RpcError::ClientError(Box::new(err))))?; - - // Subscribe to the finalized heads of the chain. - let mut subscription = SubscriptionClientT::subscribe::, _>( - &client, - "chain_subscribeFinalizedHeads", - rpc_params![], - "chain_unsubscribeFinalizedHeads", - ) - .await - .map_err(|err| Error::Rpc(crate::error::RpcError::ClientError(Box::new(err))))?; - - // We must ensure that the finalized block of the chain is not the block included - // in the chainSpec. - // This is a temporary workaround for: https://github.com/smol-dot/smoldot/issues/1562. - // The first finalized block that is received might by the finalized block could be the one - // included in the chainSpec. Decoding the chainSpec for this purpose is too complex. - let _ = subscription.next().await; - let _ = subscription.next().await; - - Ok(result) -} - -cfg_jsonrpsee_native! { - mod jsonrpsee_helpers { - use crate::error::{Error, LightClientError}; - use tokio_util::compat::Compat; - - pub use jsonrpsee::{ - client_transport::ws::{self, EitherStream, Url, WsTransportClientBuilder}, - core::client::Client, - }; - - pub type Sender = ws::Sender>; - pub type Receiver = ws::Receiver>; - - /// Build WS RPC client from URL - pub async fn client(url: &str) -> Result { - let url = Url::parse(url).map_err(|_| Error::LightClient(LightClientError::InvalidUrl))?; - - if url.scheme() != "ws" && url.scheme() != "wss" { - return Err(Error::LightClient(LightClientError::InvalidScheme)); - } - - let (sender, receiver) = ws_transport(url).await?; - - Ok(Client::builder() - .max_buffer_capacity_per_subscription(4096) - .build_with_tokio(sender, receiver)) - } - - async fn ws_transport(url: Url) -> Result<(Sender, Receiver), Error> { - WsTransportClientBuilder::default() - .build(url) - .await - .map_err(|_| Error::LightClient(LightClientError::Handshake)) - } - } -} - -cfg_jsonrpsee_web! { - mod jsonrpsee_helpers { - use crate::error::{Error, LightClientError}; - pub use jsonrpsee::{ - client_transport::web, - core::client::{Client, ClientBuilder}, - }; - - /// Build web RPC client from URL - pub async fn client(url: &str) -> Result { - let (sender, receiver) = web::connect(url) - .await - .map_err(|_| Error::LightClient(LightClientError::Handshake))?; - - Ok(ClientBuilder::default() - .max_buffer_capacity_per_subscription(4096) - .build_with_wasm(sender, receiver)) - } - } -} diff --git a/subxt/src/client/light_client/mod.rs b/subxt/src/client/light_client/mod.rs deleted file mode 100644 index 76264e93f0..0000000000 --- a/subxt/src/client/light_client/mod.rs +++ /dev/null @@ -1,194 +0,0 @@ -// 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. - -//! This module provides support for light clients. - -mod builder; -mod rpc; - -use crate::{ - backend::rpc::RpcClient, - blocks::BlocksClient, - client::{OfflineClientT, OnlineClientT}, - config::Config, - constants::ConstantsClient, - custom_values::CustomValuesClient, - events::EventsClient, - runtime_api::RuntimeApiClient, - storage::StorageClient, - tx::TxClient, - OnlineClient, -}; -pub use builder::{LightClientBuilder, RawLightClientBuilder}; -use derivative::Derivative; -use subxt_lightclient::LightClientRpcError; - -// Re-export smoldot related objects. -pub use subxt_lightclient::smoldot; - -/// Light client error. -#[derive(Debug, thiserror::Error)] -pub enum LightClientError { - /// Error originated from the low-level RPC layer. - #[error("Rpc error: {0}")] - Rpc(LightClientRpcError), - /// The background task is closed. - #[error("Failed to communicate with the background task.")] - BackgroundClosed, - /// Invalid RPC parameters cannot be serialized as JSON string. - #[error("RPC parameters cannot be serialized as JSON string.")] - InvalidParams, - /// The provided URL scheme is invalid. - /// - /// Supported versions: WS, WSS. - #[error("The provided URL scheme is invalid.")] - InvalidScheme, - /// The provided URL is invalid. - #[error("The provided URL scheme is invalid.")] - InvalidUrl, - /// The provided chain spec is invalid. - #[error("The provided chain spec is not a valid JSON object.")] - InvalidChainSpec, - /// Handshake error while connecting to a node. - #[error("WS handshake failed.")] - Handshake, -} - -/// The raw light-client RPC implementation that is used to connect with the chain. -#[derive(Clone)] -pub struct RawLightClient { - raw_rpc: rpc::RawLightClientRpc, -} - -impl RawLightClient { - /// Construct a [`RawLightClient`] using its builder interface. - /// - /// The raw builder is utilized for constructing light-clients from a low - /// level smoldot client. - /// - /// This is especially useful when you want to gain access to the smoldot client. - /// For example, you may want to connect to multiple chains and/or parachains while reusing the - /// same smoldot client under the hood. Or you may want to configure different values for - /// smoldot internal buffers, number of subscriptions and relay chains. - /// - /// # Note - /// - /// If you are unsure, please use [`LightClient::builder`] instead. - pub fn builder() -> RawLightClientBuilder { - RawLightClientBuilder::default() - } - - /// Target a different chain identified by the provided chain ID for requests. - /// - /// The provided chain ID is provided by the `smoldot_light::Client::add_chain` and it must - /// match one of the `smoldot_light::JsonRpcResponses` provided in [`RawLightClientBuilder::add_chain`]. - /// - /// # Note - /// - /// This uses the same underlying instance spawned by the builder. - pub async fn for_chain( - &self, - chain_id: smoldot::ChainId, - ) -> Result, crate::Error> { - let raw_rpc = self.raw_rpc.for_chain(chain_id); - let rpc_client = RpcClient::new(raw_rpc.clone()); - let client = OnlineClient::::from_rpc_client(rpc_client).await?; - - Ok(LightClient { client, chain_id }) - } -} - -/// The light-client RPC implementation that is used to connect with the chain. -#[derive(Derivative)] -#[derivative(Clone(bound = ""))] -pub struct LightClient { - client: OnlineClient, - chain_id: smoldot::ChainId, -} - -impl LightClient { - /// Construct a [`LightClient`] using its builder interface. - pub fn builder() -> LightClientBuilder { - LightClientBuilder::new() - } - - // We add the below impls so that we don't need to - // think about importing the OnlineClientT/OfflineClientT - // traits to use these things: - - /// Return the [`crate::Metadata`] used in this client. - fn metadata(&self) -> crate::Metadata { - self.client.metadata() - } - - /// Return the genesis hash. - fn genesis_hash(&self) -> ::Hash { - self.client.genesis_hash() - } - - /// Return the runtime version. - fn runtime_version(&self) -> crate::backend::RuntimeVersion { - self.client.runtime_version() - } - - /// Work with transactions. - pub fn tx(&self) -> TxClient { - >::tx(self) - } - - /// Work with events. - pub fn events(&self) -> EventsClient { - >::events(self) - } - - /// Work with storage. - pub fn storage(&self) -> StorageClient { - >::storage(self) - } - - /// Access constants. - pub fn constants(&self) -> ConstantsClient { - >::constants(self) - } - - /// Access custom types. - pub fn custom_values(&self) -> CustomValuesClient { - >::custom_values(self) - } - - /// Work with blocks. - pub fn blocks(&self) -> BlocksClient { - >::blocks(self) - } - - /// Work with runtime API. - pub fn runtime_api(&self) -> RuntimeApiClient { - >::runtime_api(self) - } - - /// Returns the chain ID of the current light-client. - pub fn chain_id(&self) -> smoldot::ChainId { - self.chain_id - } -} - -impl OnlineClientT for LightClient { - fn backend(&self) -> &dyn crate::backend::Backend { - self.client.backend() - } -} - -impl OfflineClientT for LightClient { - fn metadata(&self) -> crate::Metadata { - self.metadata() - } - - fn genesis_hash(&self) -> ::Hash { - self.genesis_hash() - } - - fn runtime_version(&self) -> crate::backend::RuntimeVersion { - self.runtime_version() - } -}