From c4cdedb303da3c5487aa034cb92c7a0aea634bbc Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 27 Jan 2025 15:44:31 +0000 Subject: [PATCH 01/32] WIP extract RPCs into separate crate --- Cargo.lock | 29 + Cargo.toml | 9 +- core/Cargo.toml | 2 +- core/src/config/mod.rs | 2 +- rpcs/Cargo.toml | 83 + .../rpc => rpcs/src/client}/jsonrpsee_impl.rs | 10 +- .../src/client}/lightclient_impl.rs | 6 +- .../backend/rpc => rpcs/src/client}/mod.rs | 6 +- .../client}/reconnecting_rpc_client/mod.rs | 6 +- .../reconnecting_rpc_client/platform.rs | 4 +- .../client}/reconnecting_rpc_client/tests.rs | 2 +- .../client}/reconnecting_rpc_client/utils.rs | 4 +- .../rpc => rpcs/src/client}/rpc_client.rs | 15 +- .../rpc => rpcs/src/client}/rpc_client_t.rs | 8 +- rpcs/src/lib.rs | 81 + rpcs/src/macros.rs | 66 + .../src/methods/chain_head.rs | 13 +- .../src/methods/legacy.rs | 95 +- rpcs/src/methods/mod.rs | 20 + rpcs/src/utils.rs | 33 + signer/Cargo.toml | 2 +- signer/tests/no-std/.gitignore | 1 + signer/tests/no-std/Cargo.lock | 1194 +++++++++++++ signer/tests/wasm/.gitignore | 1 + signer/tests/wasm/Cargo.lock | 1507 +++++++++++++++++ subxt/Cargo.toml | 12 +- subxt/src/backend/chain_head/follow_stream.rs | 10 +- .../chain_head/follow_stream_driver.rs | 8 +- .../backend/chain_head/follow_stream_unpin.rs | 6 +- subxt/src/backend/chain_head/mod.rs | 40 +- subxt/src/backend/chain_head/storage_items.rs | 11 +- .../src/backend/{legacy/mod.rs => legacy.rs} | 90 +- subxt/src/backend/mod.rs | 67 +- subxt/src/backend/utils.rs | 4 +- subxt/src/client/online_client.rs | 2 +- subxt/src/error/mod.rs | 16 +- subxt/src/lib.rs | 1 + subxt/src/macros.rs | 13 +- subxt/src/utils/mod.rs | 29 +- testing/integration-tests/Cargo.toml | 1 + .../src/full_client/blocks/mod.rs | 4 +- .../src/full_client/client/mod.rs | 2 +- .../src/full_client/client/unstable_rpcs.rs | 11 +- 43 files changed, 3261 insertions(+), 265 deletions(-) create mode 100644 rpcs/Cargo.toml rename {subxt/src/backend/rpc => rpcs/src/client}/jsonrpsee_impl.rs (85%) rename {subxt/src/backend/rpc => rpcs/src/client}/lightclient_impl.rs (88%) rename {subxt/src/backend/rpc => rpcs/src/client}/mod.rs (90%) rename {subxt/src/backend/rpc => rpcs/src/client}/reconnecting_rpc_client/mod.rs (99%) rename {subxt/src/backend/rpc => rpcs/src/client}/reconnecting_rpc_client/platform.rs (94%) rename {subxt/src/backend/rpc => rpcs/src/client}/reconnecting_rpc_client/tests.rs (99%) rename {subxt/src/backend/rpc => rpcs/src/client}/reconnecting_rpc_client/utils.rs (70%) rename {subxt/src/backend/rpc => rpcs/src/client}/rpc_client.rs (95%) rename {subxt/src/backend/rpc => rpcs/src/client}/rpc_client_t.rs (94%) create mode 100644 rpcs/src/lib.rs create mode 100644 rpcs/src/macros.rs rename subxt/src/backend/chain_head/rpc_methods.rs => rpcs/src/methods/chain_head.rs (99%) rename subxt/src/backend/legacy/rpc_methods.rs => rpcs/src/methods/legacy.rs (88%) create mode 100644 rpcs/src/methods/mod.rs create mode 100644 rpcs/src/utils.rs create mode 100644 signer/tests/no-std/.gitignore create mode 100644 signer/tests/no-std/Cargo.lock create mode 100644 signer/tests/wasm/.gitignore create mode 100644 signer/tests/wasm/Cargo.lock rename subxt/src/backend/{legacy/mod.rs => legacy.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index f1dcb981ccf..a0341b865a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3976,6 +3976,7 @@ dependencies = [ "subxt", "subxt-codegen", "subxt-metadata", + "subxt-rpcs", "subxt-signer", "subxt-test-macro", "syn 2.0.87", @@ -10574,6 +10575,7 @@ dependencies = [ "subxt-lightclient", "subxt-macro", "subxt-metadata", + "subxt-rpcs", "subxt-signer", "thiserror 2.0.0", "tokio", @@ -10720,6 +10722,33 @@ dependencies = [ "thiserror 2.0.0", ] +[[package]] +name = "subxt-rpcs" +version = "0.38.0" +dependencies = [ + "derive-where", + "finito", + "frame-metadata 18.0.0", + "futures", + "hex", + "http-body", + "hyper", + "impl-serde 0.5.0", + "jsonrpsee", + "parity-scale-codec", + "primitive-types 0.13.1", + "serde", + "serde_json", + "subxt-core", + "subxt-lightclient", + "thiserror 2.0.0", + "tokio", + "tokio-util", + "tower", + "tracing", + "url", +] + [[package]] name = "subxt-signer" version = "0.38.0" diff --git a/Cargo.toml b/Cargo.toml index 3700d0cddae..df2c517ae35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,11 @@ members = [ "testing/generate-custom-metadata", "macro", "metadata", + "rpcs", "signer", "subxt", "scripts/artifacts", - "utils/fetch-metadata", + "utils/fetch-metadata", "rpcs", ] # We exclude any crates that would depend on non mutually @@ -82,7 +83,7 @@ frame-metadata = { version = "18.0.0", default-features = false } futures = { version = "0.3.31", default-features = false, features = ["std"] } getrandom = { version = "0.2", default-features = false } hashbrown = "0.14.5" -hex = { version = "0.4.3", default-features = false } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } heck = "0.5.0" impl-serde = { version = "0.5.0", default-features = false } indoc = "2" @@ -116,6 +117,9 @@ which = "6.0.3" strip-ansi-escapes = "0.2.0" proptest = "1.5.0" hex-literal = "0.4.1" +tower = "0.4" +hyper = "1" +http-body = "1" # Light client support: smoldot = { version = "0.18.0", default-features = false } @@ -145,6 +149,7 @@ subxt-macro = { version = "0.38.0", path = "macro" } subxt-metadata = { version = "0.38.0", path = "metadata", default-features = false } subxt-codegen = { version = "0.38.0", path = "codegen" } subxt-signer = { version = "0.38.0", path = "signer", default-features = false } +subxt-rpcs = { version = "0.38.0", path = "rpcs", default-features = false } subxt-lightclient = { version = "0.38.0", path = "lightclient", default-features = false } subxt-utils-fetchmetadata = { version = "0.38.0", path = "utils/fetch-metadata", default-features = false } test-runtime = { path = "testing/test-runtime" } diff --git a/core/Cargo.toml b/core/Cargo.toml index ca0f63f2167..663800c7ef0 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -40,7 +40,7 @@ scale-encode = { workspace = true, default-features = false, features = ["derive frame-metadata = { workspace = true, default-features = false } subxt-metadata = { workspace = true, default-features = false } derive-where = { workspace = true } -hex = { workspace = true, default-features = false, features = ["alloc"] } +hex = { workspace = true } serde = { workspace = true, default-features = false, features = ["derive"] } serde_json = { workspace = true, default-features = false, features = ["raw_value", "alloc"] } tracing = { workspace = true, default-features = false } diff --git a/core/src/config/mod.rs b/core/src/config/mod.rs index c083283b4ce..e1711067502 100644 --- a/core/src/config/mod.rs +++ b/core/src/config/mod.rs @@ -39,7 +39,7 @@ pub trait Config: Sized + Send + Sync + 'static { type Hash: BlockHash; /// The account ID type. - type AccountId: Debug + Clone + Encode; + type AccountId: Debug + Clone + Encode + Serialize; /// The address type. type Address: Debug + Encode + From; diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml new file mode 100644 index 00000000000..876b374106b --- /dev/null +++ b/rpcs/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "subxt-rpcs" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +publish = true + +license.workspace = true +readme = "README.md" +repository.workspace = true +documentation.workspace = true +homepage.workspace = true +description = "Make RPC calls to Substrate based nodes" +keywords = ["parity", "subxt", "rpcs"] + +[features] +default = ["jsonrpsee", "native"] + +subxt-core = ["dep:subxt-core"] +jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] +unstable-light-client = ["dep:subxt-lightclient"] +reconnecting-rpc-client = [ + "jsonrpsee", + "dep:finito", + "dep:tokio", + "tokio/sync", +] + +# Enable this for native (ie non web/wasm builds). +# Exactly 1 of "web" and "native" is expected. +native = [ + "jsonrpsee?/async-client", + "jsonrpsee?/client-ws-transport-tls", + "jsonrpsee?/ws-client", + "subxt-lightclient?/native", +] + +# Enable this for web/wasm builds. +# Exactly 1 of "web" and "native" is expected. +web = [ + "jsonrpsee?/async-wasm-client", + "jsonrpsee?/client-web-transport", + "jsonrpsee?/wasm-client", + "subxt-lightclient?/web", + "finito?/wasm-bindgen", +] + +[dependencies] +codec = { workspace = true } +derive-where = { workspace = true } +futures = { workspace = true } +hex = { workspace = true } +impl-serde = { workspace = true } +primitive-types = { workspace = true, features = ["serde"] } +serde = { workspace = true } +serde_json = { workspace = true, features = ["default", "raw_value"] } +thiserror = { workspace = true } +frame-metadata = { workspace = true, features = ["decode"] } +url = { workspace = true } +tracing = { workspace = true } + +# Included with the jsonrpsee feature +jsonrpsee = { workspace = true, optional = true } +tokio-util = { workspace = true, features = ["compat"], optional = true } + +# Included with the reconnecting-rpc-client feature +finito = { workspace = true, optional = true } +tokio = { workspace = true, optional = true } + +# Included with the lightclient feature +subxt-lightclient = { workspace = true, optional = true, default-features = false } + +# Included with the subxt-core feature to impl Config for RpcConfig +subxt-core = { workspace = true, optional = true } + +[dev-dependencies] +tower = { workspace = true } +hyper = { workspace = true } +http-body = { workspace = true } + +[lints] +workspace = true diff --git a/subxt/src/backend/rpc/jsonrpsee_impl.rs b/rpcs/src/client/jsonrpsee_impl.rs similarity index 85% rename from subxt/src/backend/rpc/jsonrpsee_impl.rs rename to rpcs/src/client/jsonrpsee_impl.rs index 5db8864ef61..dd5e864d675 100644 --- a/subxt/src/backend/rpc/jsonrpsee_impl.rs +++ b/rpcs/src/client/jsonrpsee_impl.rs @@ -1,9 +1,9 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. use super::{RawRpcFuture, RawRpcSubscription, RpcClientT}; -use crate::error::RpcError; +use crate::Error; use futures::stream::{StreamExt, TryStreamExt}; use jsonrpsee::{ core::{ @@ -31,7 +31,7 @@ impl RpcClientT for Client { Box::pin(async move { let res = ClientT::request(self, method, Params(params)) .await - .map_err(|e| RpcError::ClientError(Box::new(e)))?; + .map_err(|e| Error::Client(Box::new(e)))?; Ok(res) }) } @@ -50,7 +50,7 @@ impl RpcClientT for Client { unsub, ) .await - .map_err(|e| RpcError::ClientError(Box::new(e)))?; + .map_err(|e| Error::Client(Box::new(e)))?; let id = match stream.kind() { SubscriptionKind::Subscription(SubscriptionId::Str(id)) => { @@ -60,7 +60,7 @@ impl RpcClientT for Client { }; let stream = stream - .map_err(|e| RpcError::ClientError(Box::new(e))) + .map_err(|e| Error::Client(Box::new(e))) .boxed(); Ok(RawRpcSubscription { stream, id }) }) diff --git a/subxt/src/backend/rpc/lightclient_impl.rs b/rpcs/src/client/lightclient_impl.rs similarity index 88% rename from subxt/src/backend/rpc/lightclient_impl.rs rename to rpcs/src/client/lightclient_impl.rs index f4e0deec6a3..b6a736450f5 100644 --- a/subxt/src/backend/rpc/lightclient_impl.rs +++ b/rpcs/src/client/lightclient_impl.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. @@ -47,7 +47,7 @@ impl RpcClientT for LightClientRpc { fn lc_err_to_rpc_err(err: LightClientRpcError) -> RpcError { match err { LightClientRpcError::JsonRpcError(e) => RpcError::ClientError(Box::new(e)), - LightClientRpcError::SmoldotError(e) => RpcError::RequestRejected(e), - LightClientRpcError::BackgroundTaskDropped => RpcError::SubscriptionDropped, + LightClientRpcError::SmoldotError(e) => RpcError::ClientError(Box::new(e)), + LightClientRpcError::BackgroundTaskDropped => RpcError::ClientError(Box::new("Smoldot background task was dropped")), } } \ No newline at end of file diff --git a/subxt/src/backend/rpc/mod.rs b/rpcs/src/client/mod.rs similarity index 90% rename from subxt/src/backend/rpc/mod.rs rename to rpcs/src/client/mod.rs index bec5d9d86e9..056ed946813 100644 --- a/subxt/src/backend/rpc/mod.rs +++ b/rpcs/src/client/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. @@ -58,15 +58,17 @@ crate::macros::cfg_jsonrpsee! { mod jsonrpsee_impl; + pub use jsonrpsee::core::client::Client as JsonrpseeRpcClient; } crate::macros::cfg_unstable_light_client! { mod lightclient_impl; + pub use lightclient_impl::LightClientRpc as LightClientRpcClient; } crate::macros::cfg_reconnecting_rpc_client! { - /// reconnecting rpc client. pub mod reconnecting_rpc_client; + pub use reconnecting_rpc_client::RpcClient as ReconnectingRpcClient; } mod rpc_client; diff --git a/subxt/src/backend/rpc/reconnecting_rpc_client/mod.rs b/rpcs/src/client/reconnecting_rpc_client/mod.rs similarity index 99% rename from subxt/src/backend/rpc/reconnecting_rpc_client/mod.rs rename to rpcs/src/client/reconnecting_rpc_client/mod.rs index 41f7555f805..69df9d8f131 100644 --- a/subxt/src/backend/rpc/reconnecting_rpc_client/mod.rs +++ b/rpcs/src/client/reconnecting_rpc_client/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. @@ -60,7 +60,7 @@ use std::{ }; use super::{RawRpcFuture, RawRpcSubscription, RpcClientT}; -use crate::error::RpcError as SubxtRpcError; +use crate::Error as SubxtRpcError; use finito::Retry; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; @@ -448,7 +448,7 @@ impl RpcClientT for RpcClient { let sub = self .subscribe(sub.to_string(), params, unsub.to_string()) .await - .map_err(|e| SubxtRpcError::ClientError(Box::new(e)))?; + .map_err(|e| SubxtRpcError::Client(Box::new(e)))?; let id = match sub.id() { SubscriptionId::Num(n) => n.to_string(), diff --git a/subxt/src/backend/rpc/reconnecting_rpc_client/platform.rs b/rpcs/src/client/reconnecting_rpc_client/platform.rs similarity index 94% rename from subxt/src/backend/rpc/reconnecting_rpc_client/platform.rs rename to rpcs/src/client/reconnecting_rpc_client/platform.rs index 6248fcafdd6..2fc9965bb6e 100644 --- a/subxt/src/backend/rpc/reconnecting_rpc_client/platform.rs +++ b/rpcs/src/client/reconnecting_rpc_client/platform.rs @@ -1,8 +1,8 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::backend::rpc::reconnecting_rpc_client::{RpcClientBuilder, RpcError}; +use super::{RpcClientBuilder, RpcError}; use jsonrpsee::core::client::Client; use std::sync::Arc; use url::Url; diff --git a/subxt/src/backend/rpc/reconnecting_rpc_client/tests.rs b/rpcs/src/client/reconnecting_rpc_client/tests.rs similarity index 99% rename from subxt/src/backend/rpc/reconnecting_rpc_client/tests.rs rename to rpcs/src/client/reconnecting_rpc_client/tests.rs index 7159cd4792c..ae370a4425e 100644 --- a/subxt/src/backend/rpc/reconnecting_rpc_client/tests.rs +++ b/rpcs/src/client/reconnecting_rpc_client/tests.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. diff --git a/subxt/src/backend/rpc/reconnecting_rpc_client/utils.rs b/rpcs/src/client/reconnecting_rpc_client/utils.rs similarity index 70% rename from subxt/src/backend/rpc/reconnecting_rpc_client/utils.rs rename to rpcs/src/client/reconnecting_rpc_client/utils.rs index 880708dcdfc..54304036619 100644 --- a/subxt/src/backend/rpc/reconnecting_rpc_client/utils.rs +++ b/rpcs/src/client/reconnecting_rpc_client/utils.rs @@ -1,10 +1,10 @@ -// Copyright 2019-2024 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. //! Utils. -use crate::backend::rpc::reconnecting_rpc_client::RpcError; +use super::RpcError; pub fn display_close_reason(err: &RpcError) -> String { match err { diff --git a/subxt/src/backend/rpc/rpc_client.rs b/rpcs/src/client/rpc_client.rs similarity index 95% rename from subxt/src/backend/rpc/rpc_client.rs rename to rpcs/src/client/rpc_client.rs index 16dba9e6fba..94bcc2ba209 100644 --- a/subxt/src/backend/rpc/rpc_client.rs +++ b/rpcs/src/client/rpc_client.rs @@ -1,9 +1,9 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. use super::{RawRpcSubscription, RpcClientT}; -use crate::error::Error; +use crate::Error; use futures::{Stream, StreamExt}; use serde::{de::DeserializeOwned, Serialize}; use serde_json::value::RawValue; @@ -35,7 +35,7 @@ impl RpcClient { pub async fn from_insecure_url>(url: U) -> Result { let client = jsonrpsee_helpers::client(url.as_ref()) .await - .map_err(|e| crate::error::RpcError::ClientError(Box::new(e)))?; + .map_err(|e| Error::Client(Box::new(e)))?; Ok(Self::new(client)) } @@ -56,7 +56,7 @@ impl RpcClient { params: RpcParams, ) -> Result { let res = self.client.request_raw(method, params.build()).await?; - let val = serde_json::from_str(res.get())?; + let val = serde_json::from_str(res.get()).map_err(Error::Deserialization)?; Ok(val) } @@ -123,7 +123,7 @@ macro_rules! rpc_params { ($($p:expr), *) => {{ // May be unused if empty; no params. #[allow(unused_mut)] - let mut params = $crate::backend::rpc::RpcParams::new(); + let mut params = $crate::client::RpcParams::new(); $( params.push($p).expect("values passed to rpc_params! must be serializable to JSON"); )* @@ -165,7 +165,8 @@ impl RpcParams { } else { self.0.push(b',') } - serde_json::to_writer(&mut self.0, ¶m)?; + serde_json::to_writer(&mut self.0, ¶m) + .map_err(Error::Deserialization)?; Ok(()) } /// Build a [`RawValue`] from our params, returning `None` if no parameters @@ -236,7 +237,7 @@ impl Stream for RpcSubscription { // any errors to the right shape: let res = res.map(|r| { r.map_err(|e| e.into()) - .and_then(|raw_val| serde_json::from_str(raw_val.get()).map_err(|e| e.into())) + .and_then(|raw_val| serde_json::from_str(raw_val.get()).map_err(Error::Deserialization)) }); Poll::Ready(res) diff --git a/subxt/src/backend/rpc/rpc_client_t.rs b/rpcs/src/client/rpc_client_t.rs similarity index 94% rename from subxt/src/backend/rpc/rpc_client_t.rs rename to rpcs/src/client/rpc_client_t.rs index 98d349d17c1..68fce963128 100644 --- a/subxt/src/backend/rpc/rpc_client_t.rs +++ b/rpcs/src/client/rpc_client_t.rs @@ -1,10 +1,10 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use crate::error::RpcError; use futures::Stream; use std::{future::Future, pin::Pin}; +use crate::Error; // Re-exporting for simplicity since it's used a bunch in the trait definition. pub use serde_json::value::RawValue; @@ -54,12 +54,12 @@ pub trait RpcClientT: Send + Sync + 'static { } /// A boxed future that is returned from the [`RpcClientT`] methods. -pub type RawRpcFuture<'a, T> = Pin> + Send + 'a>>; +pub type RawRpcFuture<'a, T, E = Error> = Pin> + Send + 'a>>; /// The RPC subscription returned from [`RpcClientT`]'s `subscription` method. pub struct RawRpcSubscription { /// The subscription stream. - pub stream: Pin, RpcError>> + Send + 'static>>, + pub stream: Pin, Error>> + Send + 'static>>, /// The ID associated with the subscription. pub id: Option, } diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs new file mode 100644 index 00000000000..65acf3cd0dc --- /dev/null +++ b/rpcs/src/lib.rs @@ -0,0 +1,81 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! This crate provides a low level RPC interface to Substrate based nodes. + +#[cfg(any( + all(feature = "web", feature = "native"), + not(any(feature = "web", feature = "native")) +))] +compile_error!("subxt-rpcs: exactly one of the 'web' and 'native' features should be used."); + + +mod macros; + +pub mod utils; +pub mod client; +pub mod methods; + +// Expose the most common things at the top level: +pub use client::{ RpcClient, RpcClientT }; +pub use methods::{ ChainHeadRpcMethods, LegacyRpcMethods }; + +/// Configuration used by some of the RPC methods to determine the shape of +/// some of the inputs or responses. +pub trait RpcConfig { + /// The block header type. + type Header: Header; + /// The block hash type. + type Hash: BlockHash; + /// The Account ID type. + type AccountId: AccountId; +} + +/// A trait which is applied to any type that is a valid block header. +pub trait Header: std::fmt::Debug + codec::Decode + serde::de::DeserializeOwned {} +impl Header for T where T: std::fmt::Debug + codec::Decode + serde::de::DeserializeOwned {} + +/// A trait which is applied to any type that is a valid block hash. +pub trait BlockHash: serde::de::DeserializeOwned + serde::Serialize {} +impl BlockHash for T where T: serde::de::DeserializeOwned + serde::Serialize {} + +/// A trait which is applied to any type that is a valid Account ID. +pub trait AccountId: serde::Serialize {} +impl AccountId for T where T: serde::Serialize {} + +#[cfg(feature = "subxt-core")] +mod impl_config { + use super::*; + impl RpcConfig for T where T: subxt_core::Config { + type Header = T::Header; + type Hash = T::Hash; + type AccountId = T::AccountId; + } +} + +/// This encapsulates any errors that could be emitted in this crate. +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + // Dev note: We need the error to be safely sent between threads + // for `subscribe_to_block_headers_filling_in_gaps` and friends. + /// An error coming from the underlying RPC Client. + #[error("RPC error: client error: {0}")] + Client(Box), + /// The connection was lost and automatically reconnected. Reconnecting clients + /// should only emit this when they plan to try reconnecting internally, in which + /// case they should buffer anycalls made to them until they have reconnected and + /// then send them off in the order given. + #[error("RPC error: the connection was lost ({0}); reconnect automatically initiated")] + DisconnectedWillReconnect(String), + /// Cannot deserialize the response. + #[error("RPC error: cannot deserialize response: {0}")] + Deserialization(serde_json::Error), + /// Cannot SCALE decode some part of the response. + #[error("RPC error: cannot SCALE decode some part of the response: {0}")] + Decode(codec::Error), + /// The requested URL is insecure. + #[error("RPC error: insecure URL: {0}")] + InsecureUrl(String), +} diff --git a/rpcs/src/macros.rs b/rpcs/src/macros.rs new file mode 100644 index 00000000000..c8b1eb7ed0a --- /dev/null +++ b/rpcs/src/macros.rs @@ -0,0 +1,66 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +macro_rules! cfg_feature { + ($feature:literal, $($item:item)*) => { + $( + #[cfg(feature = $feature)] + #[cfg_attr(docsrs, doc(cfg(feature = $feature)))] + $item + )* + } +} + +macro_rules! cfg_unstable_light_client { + ($($item:item)*) => { + crate::macros::cfg_feature!("unstable-light-client", $($item)*); + }; +} + +macro_rules! cfg_jsonrpsee { + ($($item:item)*) => { + crate::macros::cfg_feature!("jsonrpsee", $($item)*); + }; +} + +#[allow(unused)] +macro_rules! cfg_jsonrpsee_native { + ($($item:item)*) => { + $( + #[cfg(all(feature = "jsonrpsee", feature = "native"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "jsonrpsee", feature = "native"))))] + $item + )* + } +} + +#[allow(unused)] +macro_rules! cfg_jsonrpsee_web { + ($($item:item)*) => { + $( + #[cfg(all(feature = "jsonrpsee", feature = "web"))] + #[cfg_attr(docsrs, doc(cfg(all(feature = "jsonrpsee", feature = "web"))))] + $item + )* + } +} + +#[allow(unused)] +macro_rules! cfg_reconnecting_rpc_client { + ($($item:item)*) => { + $( + #[cfg(all(feature = "reconnecting-rpc-client", any(feature = "native", feature = "web")))] + #[cfg_attr(docsrs, doc(cfg(feature = "reconnecting-rpc-client")))] + $item + )* + } +} + +pub(crate) use { + cfg_feature, cfg_jsonrpsee, cfg_reconnecting_rpc_client, cfg_unstable_light_client, +}; + +// Only used by light-client. +#[allow(unused)] +pub(crate) use {cfg_jsonrpsee_native, cfg_jsonrpsee_web}; diff --git a/subxt/src/backend/chain_head/rpc_methods.rs b/rpcs/src/methods/chain_head.rs similarity index 99% rename from subxt/src/backend/chain_head/rpc_methods.rs rename to rpcs/src/methods/chain_head.rs index a9779788a30..6d1faeacdea 100644 --- a/subxt/src/backend/chain_head/rpc_methods.rs +++ b/rpcs/src/methods/chain_head.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. @@ -6,9 +6,9 @@ //! for details of the API //! methods exposed here. -use crate::backend::rpc::{rpc_params, RpcClient, RpcSubscription}; -use crate::config::BlockHash; -use crate::{Config, Error}; +use crate::client::{rpc_params, RpcClient, RpcSubscription}; +use crate::BlockHash; +use crate::{RpcConfig, Error}; use derive_where::derive_where; use futures::{Stream, StreamExt}; use serde::{Deserialize, Deserializer, Serialize}; @@ -24,7 +24,7 @@ pub struct ChainHeadRpcMethods { _marker: std::marker::PhantomData, } -impl ChainHeadRpcMethods { +impl ChainHeadRpcMethods { /// Instantiate the legacy RPC method interface. pub fn new(client: RpcClient) -> Self { ChainHeadRpcMethods { @@ -139,7 +139,8 @@ impl ChainHeadRpcMethods { let header = header .map(|h| codec::Decode::decode(&mut &*h.0)) - .transpose()?; + .transpose() + .map_err(Error::Decode)?; Ok(header) } diff --git a/subxt/src/backend/legacy/rpc_methods.rs b/rpcs/src/methods/legacy.rs similarity index 88% rename from subxt/src/backend/legacy/rpc_methods.rs rename to rpcs/src/methods/legacy.rs index 1beae0694e3..0125ee1ec04 100644 --- a/subxt/src/backend/legacy/rpc_methods.rs +++ b/rpcs/src/methods/legacy.rs @@ -1,13 +1,13 @@ -// Copyright 2019-2023 Parity Technologies (UK) Ltd. +// Copyright 2019-2025 Parity Technologies (UK) Ltd. // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. //! An interface to call the raw legacy RPC methods. -use crate::backend::rpc::{rpc_params, RpcClient, RpcSubscription}; -use crate::metadata::Metadata; -use crate::{Config, Error}; +use crate::client::{rpc_params, RpcClient, RpcSubscription}; +use crate::{RpcConfig, Error}; use codec::Decode; +use frame_metadata::RuntimeMetadataPrefixed; use derive_where::derive_where; use primitive_types::U256; use serde::{Deserialize, Serialize}; @@ -21,7 +21,7 @@ pub struct LegacyRpcMethods { _marker: std::marker::PhantomData, } -impl LegacyRpcMethods { +impl LegacyRpcMethods { /// Instantiate the legacy RPC method interface. pub fn new(client: RpcClient) -> Self { LegacyRpcMethods { @@ -97,17 +97,16 @@ impl LegacyRpcMethods { let params = rpc_params![block_zero]; let genesis_hash: Option = self.client.request("chain_getBlockHash", params).await?; - genesis_hash.ok_or_else(|| "Genesis hash not found".into()) + genesis_hash.ok_or_else(|| Error::Client("Genesis hash not found".into())) } /// Fetch the metadata via the legacy `state_getMetadata` RPC method. - pub async fn state_get_metadata(&self, at: Option) -> Result { + pub async fn state_get_metadata(&self, at: Option) -> Result { let bytes: Bytes = self .client .request("state_getMetadata", rpc_params![at]) .await?; - let metadata = Metadata::decode(&mut &bytes[..])?; - Ok(metadata) + Ok(StateGetMetadataResponse(bytes.0)) } /// Fetch system health @@ -140,10 +139,7 @@ impl LegacyRpcMethods { /// Fetch next nonce for an Account /// /// Return account nonce adjusted for extrinsics currently in transaction pool - pub async fn system_account_next_index(&self, account_id: &T::AccountId) -> Result - where - T::AccountId: Serialize, - { + pub async fn system_account_next_index(&self, account_id: &T::AccountId) -> Result { self.client .request("system_accountNextIndex", rpc_params![&account_id]) .await @@ -391,10 +387,24 @@ impl LegacyRpcMethods { &self, encoded_signed: &[u8], at: Option, - ) -> Result { + ) -> Result, Error> { let params = rpc_params![to_hex(encoded_signed), at]; let result_bytes: Bytes = self.client.request("system_dryRun", params).await?; - Ok(DryRunResultBytes(result_bytes.0)) + Ok(result_bytes.0) + } +} + +/// Response from the legacy `state_get_metadata` RPC call. +pub struct StateGetMetadataResponse(Vec); + +impl StateGetMetadataResponse { + /// Return the raw SCALE encoded metadata bytes + pub fn into_raw(self) -> Vec { + self.0 + } + /// Decode and return [`frame_metadata::RuntimeMetadataPrefixed`]. + pub fn to_frame_metadata(&self) -> Result { + RuntimeMetadataPrefixed::decode(&mut &*self.0) } } @@ -426,8 +436,8 @@ pub type BlockNumber = NumberOrHex; /// The response from `chain_getBlock` #[derive(Debug, Deserialize)] -#[serde(bound = "T: Config")] -pub struct BlockDetails { +#[serde(bound = "T: RpcConfig")] +pub struct BlockDetails { /// The block itself. pub block: Block, /// Block justification. @@ -436,7 +446,7 @@ pub struct BlockDetails { /// Block details in the [`BlockDetails`]. #[derive(Debug, Deserialize)] -pub struct Block { +pub struct Block { /// The block header. pub header: T::Header, /// The accompanying extrinsics. @@ -509,55 +519,6 @@ pub enum TransactionStatus { Invalid, } -/// The decoded result returned from calling `system_dryRun` on some extrinsic. -#[derive(Debug, PartialEq, Eq)] -pub enum DryRunResult { - /// The transaction could be included in the block and executed. - Success, - /// The transaction could be included in the block, but the call failed to dispatch. - DispatchError(crate::error::DispatchError), - /// The transaction could not be included in the block. - TransactionValidityError, -} - -/// The bytes representing an error dry running an extrinsic. call [`DryRunResultBytes::into_dry_run_result`] -/// to attempt to decode this into something more meaningful. -pub struct DryRunResultBytes(pub Vec); - -impl DryRunResultBytes { - /// Attempt to decode the error bytes into a [`DryRunResult`] using the provided [`Metadata`]. - pub fn into_dry_run_result( - self, - metadata: &crate::metadata::Metadata, - ) -> Result { - // dryRun returns an ApplyExtrinsicResult, which is basically a - // `Result, TransactionValidityError>`. - let bytes = self.0; - - // We expect at least 2 bytes. In case we got a naff response back (or - // manually constructed this struct), just error to avoid a panic: - if bytes.len() < 2 { - return Err(crate::Error::Unknown(bytes)); - } - - if bytes[0] == 0 && bytes[1] == 0 { - // Ok(Ok(())); transaction is valid and executed ok - Ok(DryRunResult::Success) - } else if bytes[0] == 0 && bytes[1] == 1 { - // Ok(Err(dispatch_error)); transaction is valid but execution failed - let dispatch_error = - crate::error::DispatchError::decode_from(&bytes[2..], metadata.clone())?; - Ok(DryRunResult::DispatchError(dispatch_error)) - } else if bytes[0] == 1 { - // Err(transaction_error); some transaction validity error (we ignore the details at the moment) - Ok(DryRunResult::TransactionValidityError) - } else { - // unable to decode the bytes; they aren't what we expect. - Err(crate::Error::Unknown(bytes)) - } - } -} - /// Storage change set #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] #[serde(rename_all = "camelCase")] diff --git a/rpcs/src/methods/mod.rs b/rpcs/src/methods/mod.rs new file mode 100644 index 00000000000..98b4252ea75 --- /dev/null +++ b/rpcs/src/methods/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! RPC methods are defined in this module. At the moment we have: +//! +//! - [`ChainHeadRpcMethods`] (and the types in [`chain_head`]): these methods +//! implement the RPC spec at +//! +//! We also have (although their use is not advised): +//! +//! - [`LegacyRpcMethods`] (and the types in [`legacy`]): a collection of legacy RPCs. +//! These are not well specified and may change in implementations without warning, +//! but for those methods we expose, we make a best effort to work against latest Substrate versions. + +pub mod chain_head; +pub mod legacy; + +pub use chain_head::ChainHeadRpcMethods; +pub use legacy::LegacyRpcMethods; \ No newline at end of file diff --git a/rpcs/src/utils.rs b/rpcs/src/utils.rs new file mode 100644 index 00000000000..1d61c7854f3 --- /dev/null +++ b/rpcs/src/utils.rs @@ -0,0 +1,33 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! A couple of utility methods that we make use of. + +use url::Url; +use crate::Error; + +/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. +/// +/// Returns an error if the string could not be parsed into a URL. +pub fn url_is_secure(url: &str) -> Result { + let url = Url::parse(url).map_err(|e| Error::Client(Box::new(e)))?; + + let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; + let is_localhost = url.host().is_some_and(|e| match e { + url::Host::Domain(e) => e == "localhost", + url::Host::Ipv4(e) => e.is_loopback(), + url::Host::Ipv6(e) => e.is_loopback(), + }); + + Ok(secure_scheme || is_localhost) +} + +/// Validates, that the given Url is secure ("https" or "wss" scheme) or is referring to localhost. +pub fn validate_url_is_secure(url: &str) -> Result<(), Error> { + if !url_is_secure(url)? { + Err(Error::InsecureUrl(url.into())) + } else { + Ok(()) + } +} \ No newline at end of file diff --git a/signer/Cargo.toml b/signer/Cargo.toml index acc6e66890b..8845712fae0 100644 --- a/signer/Cargo.toml +++ b/signer/Cargo.toml @@ -54,7 +54,7 @@ web = ["getrandom/js"] subxt-core = { workspace = true, optional = true, default-features = false } secrecy = { workspace = true } regex = { workspace = true, features = ["unicode"] } -hex = { workspace = true, features = ["alloc"] } +hex = { workspace = true } cfg-if = { workspace = true } codec = { package = "parity-scale-codec", workspace = true, features = [ "derive", diff --git a/signer/tests/no-std/.gitignore b/signer/tests/no-std/.gitignore new file mode 100644 index 00000000000..1de565933b0 --- /dev/null +++ b/signer/tests/no-std/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/signer/tests/no-std/Cargo.lock b/signer/tests/no-std/Cargo.lock new file mode 100644 index 00000000000..99012dc1a2d --- /dev/null +++ b/signer/tests/no-std/Cargo.lock @@ -0,0 +1,1194 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac", + "k256", + "rand_core", + "ripemd", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "bip39" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +dependencies = [ + "bitcoin_hashes", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand_core", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-hash" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1b8590eb6148af2ea2d75f38e7d29f5ca970d5a4df456b3ef19b8b415d0264" +dependencies = [ + "primitive-types", + "tiny-keccak", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "nostd-tests" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "subxt-signer", + "tracing-wasm", + "wasm-bindgen-test", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parity-scale-codec" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8781a75c6205af67215f382092b6e0a4ff3734798523e69073d4bcd294ec767b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "polkadot-sdk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775" +dependencies = [ + "sp-crypto-hashing", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest", + "sha2", + "sha3", + "twox-hash", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subxt-signer" +version = "0.38.0" +dependencies = [ + "bip32", + "bip39", + "cfg-if", + "hex", + "hmac", + "keccak-hash", + "parity-scale-codec", + "pbkdf2", + "polkadot-sdk", + "regex", + "schnorrkel", + "secp256k1", + "secrecy", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/signer/tests/wasm/.gitignore b/signer/tests/wasm/.gitignore new file mode 100644 index 00000000000..1de565933b0 --- /dev/null +++ b/signer/tests/wasm/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/signer/tests/wasm/Cargo.lock b/signer/tests/wasm/Cargo.lock new file mode 100644 index 00000000000..4d99c79e567 --- /dev/null +++ b/signer/tests/wasm/Cargo.lock @@ -0,0 +1,1507 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bip32" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa13fae8b6255872fd86f7faf4b41168661d7d78609f7bfe6771b85c6739a15b" +dependencies = [ + "bs58", + "hmac", + "k256", + "rand_core", + "ripemd", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "bip39" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33415e24172c1b7d6066f6d999545375ab8e1d95421d6784bdfff9496f292387" +dependencies = [ + "bitcoin_hashes 0.13.0", + "serde", + "unicode-normalization", +] + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative 0.1.2", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.1", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aeb932158bd710538c73702db6945cb68a8fb08c519e6e12706b94263b36db8" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto_secretbox" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d6cf87adf719ddf43a805e92c6870a531aedda35ff640442cbaf8674e141e1" +dependencies = [ + "aead", + "cipher", + "generic-array", + "poly1305", + "salsa20", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand", + "rand_core", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hex-conservative" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-hash" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1b8590eb6148af2ea2d75f38e7d29f5ca970d5a4df456b3ef19b8b415d0264" +dependencies = [ + "primitive-types", + "tiny-keccak", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "minicov" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27fe9f1cc3c22e1687f9446c2083c4c5fc7f0bcf1c7a86bdbded14985895b4b" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", + "password-hash", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" + +[[package]] +name = "polkadot-sdk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb819108697967452fa6d8d96ab4c0d48cbaa423b3156499dcb24f1cf95d6775" +dependencies = [ + "sp-crypto-hashing", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead", + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "serde_bytes", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "password-hash", + "pbkdf2", + "salsa20", + "sha2", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes 0.14.0", + "rand", + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.215" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest", + "sha2", + "sha3", + "twox-hash", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subxt-signer" +version = "0.38.0" +dependencies = [ + "base64", + "bip32", + "bip39", + "cfg-if", + "crypto_secretbox", + "getrandom", + "hex", + "hmac", + "keccak-hash", + "parity-scale-codec", + "pbkdf2", + "polkadot-sdk", + "regex", + "schnorrkel", + "scrypt", + "secp256k1", + "secrecy", + "serde", + "serde_json", + "sha2", + "thiserror", + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "tracing-wasm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" +dependencies = [ + "tracing", + "tracing-subscriber", + "wasm-bindgen", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "wasm-tests" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "subxt-signer", + "tracing-wasm", + "wasm-bindgen-test", +] + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 82c614b1e0b..c3ef650dcfb 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -29,6 +29,7 @@ native = [ "jsonrpsee?/client-ws-transport-tls", "jsonrpsee?/ws-client", "subxt-lightclient?/native", + "subxt-rpcs/native", "tokio-util", "tokio?/sync", "polkadot-sdk/std", @@ -43,6 +44,7 @@ web = [ "getrandom/js", "subxt-lightclient?/web", "subxt-macro/web", + "subxt-rpcs/web", "tokio?/sync", "finito?/wasm-bindgen", ] @@ -56,11 +58,12 @@ web = [ runtime = ["tokio/rt", "wasm-bindgen-futures"] # Enable this to use the reconnecting rpc client -reconnecting-rpc-client = ["dep:finito", "jsonrpsee"] +reconnecting-rpc-client = ["subxt-rpcs/reconnecting-rpc-client"] # Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`). jsonrpsee = [ "dep:jsonrpsee", + "subxt-rpcs/jsonrpsee", "runtime" ] @@ -109,6 +112,7 @@ subxt-macro = { workspace = true } subxt-core = { workspace = true, features = ["std"] } subxt-metadata = { workspace = true, features = ["std"] } subxt-lightclient = { workspace = true, optional = true, default-features = false } +subxt-rpcs = { workspace = true, default-features = false, features = ["subxt-core"] } # For parsing urls to disallow insecure schemes url = { workspace = true } @@ -141,9 +145,9 @@ subxt-signer = { path = "../signer", features = ["unstable-eth"] } tracing-subscriber = { workspace = true } # These deps are needed to test the reconnecting rpc client jsonrpsee = { workspace = true, features = ["server"] } -tower = "0.4" -hyper = "1" -http-body = "1" +tower = { workspace = true } +hyper = { workspace = true } +http-body = { workspace = true } [[example]] name = "light_client_basic" diff --git a/subxt/src/backend/chain_head/follow_stream.rs b/subxt/src/backend/chain_head/follow_stream.rs index f68d3ba0be2..5d2d557d7f4 100644 --- a/subxt/src/backend/chain_head/follow_stream.rs +++ b/subxt/src/backend/chain_head/follow_stream.rs @@ -2,10 +2,10 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use super::rpc_methods::{ChainHeadRpcMethods, FollowEvent}; use crate::config::Config; use crate::error::Error; -use futures::{FutureExt, Stream, StreamExt}; +use subxt_rpcs::methods::chain_head::{ChainHeadRpcMethods, FollowEvent}; +use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; @@ -113,8 +113,10 @@ impl FollowStream { .to_owned(), )); }; - // Return both: + // Map stream errors into the higher level subxt one: + let stream = stream.map_err(|e| e.into()); let stream: FollowEventStream = Box::pin(stream); + // Return both: Ok((stream, sub_id)) }) }), @@ -215,7 +217,7 @@ impl Stream for FollowStream { #[cfg(test)] pub(super) mod test_utils { use super::*; - use crate::backend::chain_head::rpc_methods::{ + use subxt_rpcs::methods::chain_head::{ BestBlockChanged, Finalized, Initialized, NewBlock, }; use crate::config::substrate::H256; diff --git a/subxt/src/backend/chain_head/follow_stream_driver.rs b/subxt/src/backend/chain_head/follow_stream_driver.rs index 833d77fc655..15a6c8c52e9 100644 --- a/subxt/src/backend/chain_head/follow_stream_driver.rs +++ b/subxt/src/backend/chain_head/follow_stream_driver.rs @@ -3,9 +3,9 @@ // see LICENSE for license details. use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin}; -use crate::backend::chain_head::rpc_methods::{FollowEvent, Initialized, RuntimeEvent}; use crate::config::BlockHash; use crate::error::{Error, RpcError}; +use subxt_rpcs::methods::chain_head::{FollowEvent, Initialized, RuntimeEvent}; use futures::stream::{Stream, StreamExt}; use std::collections::{HashMap, HashSet, VecDeque}; use std::ops::DerefMut; @@ -454,9 +454,9 @@ where .iter() .position(|b| b.hash() == p.hash()) else { - return Poll::Ready(Some(Err(RpcError::DisconnectedWillReconnect( + return Poll::Ready(Some(Err(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect( "Missed at least one block when the connection was lost".to_owned(), - ) + )) .into()))); }; @@ -739,7 +739,7 @@ mod test { ) ); assert!( - matches!(&evs[1], Err(Error::Rpc(RpcError::DisconnectedWillReconnect(e))) if e.contains("Missed at least one block when the connection was lost")) + matches!(&evs[1], Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(e)))) if e.contains("Missed at least one block when the connection was lost")) ); assert_eq!( evs[2].as_ref().unwrap(), diff --git a/subxt/src/backend/chain_head/follow_stream_unpin.rs b/subxt/src/backend/chain_head/follow_stream_unpin.rs index 0ae0ef0a891..f2dcad788a7 100644 --- a/subxt/src/backend/chain_head/follow_stream_unpin.rs +++ b/subxt/src/backend/chain_head/follow_stream_unpin.rs @@ -4,11 +4,11 @@ use super::follow_stream::FollowStream; use super::ChainHeadRpcMethods; -use crate::backend::chain_head::rpc_methods::{ - BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, -}; use crate::config::{BlockHash, Config}; use crate::error::Error; +use subxt_rpcs::methods::chain_head::{ + BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, +}; use futures::stream::{FuturesUnordered, Stream, StreamExt}; use std::collections::{HashMap, HashSet}; diff --git a/subxt/src/backend/chain_head/mod.rs b/subxt/src/backend/chain_head/mod.rs index 9a75543ee9b..66b60f42ccf 100644 --- a/subxt/src/backend/chain_head/mod.rs +++ b/subxt/src/backend/chain_head/mod.rs @@ -16,14 +16,13 @@ mod follow_stream_driver; mod follow_stream_unpin; mod storage_items; -pub mod rpc_methods; - -use self::follow_stream_driver::FollowStreamFinalizedHeads; -use self::rpc_methods::{ +use subxt_rpcs::RpcClient; +use subxt_rpcs::methods::chain_head::{ FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType, StorageResultType, }; +use self::follow_stream_driver::FollowStreamFinalizedHeads; use crate::backend::{ - rpc::RpcClient, utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, + utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults, TransactionStatus, }; use crate::config::BlockHash; @@ -38,7 +37,7 @@ use std::task::Poll; use storage_items::StorageItems; // Expose the RPC methods. -pub use rpc_methods::ChainHeadRpcMethods; +pub use subxt_rpcs::methods::chain_head::ChainHeadRpcMethods; /// Configure and build an [`ChainHeadBackend`]. pub struct ChainHeadBackendBuilder { @@ -213,7 +212,7 @@ impl ChainHeadBackend { let header = match res { Ok(header) => header, - Err(e) => return Some(Err(e)), + Err(e) => return Some(Err(e.into())), }; Some(Ok((header, block_ref.into()))) @@ -338,13 +337,17 @@ impl Backend for ChainHeadBackend { } async fn genesis_hash(&self) -> Result { - retry(|| self.methods.chainspec_v1_genesis_hash()).await + retry(|| async { + let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?; + Ok(genesis_hash) + }).await } async fn block_header(&self, at: T::Hash) -> Result, Error> { retry(|| async { let sub_id = get_subscription_id(&self.follow_handle).await?; - self.methods.chainhead_v1_header(&sub_id, at).await + let header = self.methods.chainhead_v1_header(&sub_id, at).await?; + Ok(header) }) .await } @@ -653,20 +656,21 @@ impl Backend for ChainHeadBackend { let tx_progress_ev = match tx_progress.poll_next_unpin(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(None) => return Poll::Ready(err_other("No more transaction progress events, but we haven't seen a Finalized one yet")), - Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))), + Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e.into()))), Poll::Ready(Some(Ok(ev))) => ev, }; // When we get one, map it to the correct format (or for finalized ev, wait for the pinned block): + use subxt_rpcs::methods::chain_head::TransactionStatus as RpcTransactionStatus; let tx_progress_ev = match tx_progress_ev { - rpc_methods::TransactionStatus::Finalized { block } => { + RpcTransactionStatus::Finalized { block } => { // We'll wait until we have seen this hash, to try to guarantee // that when we return this event, the corresponding block is // pinned and accessible. finalized_hash = Some(block.hash); continue; } - rpc_methods::TransactionStatus::BestChainBlockIncluded { + RpcTransactionStatus::BestChainBlockIncluded { block: Some(block), } => { // Look up a pinned block ref if we can, else return a non-pinned @@ -679,20 +683,20 @@ impl Backend for ChainHeadBackend { }; TransactionStatus::InBestBlock { hash: block_ref } } - rpc_methods::TransactionStatus::BestChainBlockIncluded { block: None } => { + RpcTransactionStatus::BestChainBlockIncluded { block: None } => { TransactionStatus::NoLongerInBestBlock } - rpc_methods::TransactionStatus::Broadcasted => TransactionStatus::Broadcasted, - rpc_methods::TransactionStatus::Dropped { error, .. } => { + RpcTransactionStatus::Broadcasted => TransactionStatus::Broadcasted, + RpcTransactionStatus::Dropped { error, .. } => { TransactionStatus::Dropped { message: error } } - rpc_methods::TransactionStatus::Error { error } => { + RpcTransactionStatus::Error { error } => { TransactionStatus::Error { message: error } } - rpc_methods::TransactionStatus::Invalid { error } => { + RpcTransactionStatus::Invalid { error } => { TransactionStatus::Invalid { message: error } } - rpc_methods::TransactionStatus::Validated => TransactionStatus::Validated, + RpcTransactionStatus::Validated => TransactionStatus::Validated, }; return Poll::Ready(Some(Ok(tx_progress_ev))); } diff --git a/subxt/src/backend/chain_head/storage_items.rs b/subxt/src/backend/chain_head/storage_items.rs index 403cc45117e..22ea4bd930a 100644 --- a/subxt/src/backend/chain_head/storage_items.rs +++ b/subxt/src/backend/chain_head/storage_items.rs @@ -4,11 +4,11 @@ use super::follow_stream_driver::FollowStreamDriverHandle; use super::follow_stream_unpin::BlockRef; -use super::rpc_methods::{ - ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult, -}; use crate::config::Config; use crate::error::{Error, RpcError}; +use subxt_rpcs::methods::chain_head::{ + ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult, +}; use futures::{FutureExt, Stream, StreamExt}; use std::collections::VecDeque; use std::future::Future; @@ -59,7 +59,10 @@ impl StorageItems { let operation_id = operation_id.clone(); let methods = methods.clone(); - Box::pin(async move { methods.chainhead_v1_continue(&sub_id, &operation_id).await }) + Box::pin(async move { + let cont = methods.chainhead_v1_continue(&sub_id, &operation_id).await?; + Ok(cont) + }) }) }; diff --git a/subxt/src/backend/legacy/mod.rs b/subxt/src/backend/legacy.rs similarity index 90% rename from subxt/src/backend/legacy/mod.rs rename to subxt/src/backend/legacy.rs index 77cdd0b339a..6e253216efd 100644 --- a/subxt/src/backend/legacy/mod.rs +++ b/subxt/src/backend/legacy.rs @@ -5,21 +5,25 @@ //! This module exposes a legacy backend implementation, which relies //! on the legacy RPC API methods. -pub mod rpc_methods; - use self::rpc_methods::TransactionStatus as RpcTransactionStatus; use crate::backend::utils::{retry, retry_stream}; use crate::backend::{ - rpc::RpcClient, Backend, BlockRef, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults, + Backend, BlockRef, RuntimeVersion, StorageResponse, StreamOf, StreamOfResults, TransactionStatus, }; -use crate::error::RpcError; use crate::{config::Header, Config, Error}; use async_trait::async_trait; +use futures::TryStreamExt; use futures::{future, future::Either, stream, Future, FutureExt, Stream, StreamExt}; use std::collections::VecDeque; use std::pin::Pin; use std::task::{Context, Poll}; +use subxt_rpcs::RpcClient; + +/// Re-export legacy RPC types from [`subxt_rpcs::methods::legacy`]. +pub mod rpc_methods { + pub use subxt_rpcs::methods::legacy::*; +} // Expose the RPC methods. pub use rpc_methods::LegacyRpcMethods; @@ -181,11 +185,17 @@ impl Backend for LegacyBackend { } async fn genesis_hash(&self) -> Result { - retry(|| self.methods.genesis_hash()).await + retry(|| async { + let hash = self.methods.genesis_hash().await?; + Ok(hash) + }).await } async fn block_header(&self, at: T::Hash) -> Result, Error> { - retry(|| self.methods.chain_get_header(Some(at))).await + retry(|| async { + let header = self.methods.chain_get_header(Some(at)).await?; + Ok(header) + }).await } async fn block_body(&self, at: T::Hash) -> Result>>, Error> { @@ -227,12 +237,14 @@ impl Backend for LegacyBackend { Box::pin(async move { let sub = methods.state_subscribe_runtime_version().await?; - let sub = sub.map(|r| { - r.map(|v| RuntimeVersion { - spec_version: v.spec_version, - transaction_version: v.transaction_version, - }) - }); + let sub = sub + .map_err(|e| e.into()) + .map(|r| { + r.map(|v| RuntimeVersion { + spec_version: v.spec_version, + transaction_version: v.transaction_version, + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -244,8 +256,13 @@ impl Backend for LegacyBackend { // Thus, it's technically possible that a runtime version can be missed if // two runtime upgrades happen in quick succession, but this is very unlikely. let stream = retry_sub.filter(|r| { - let forward = !matches!(r, Err(Error::Rpc(RpcError::DisconnectedWillReconnect(_)))); - async move { forward } + let mut keep = true; + if let Err(e) = r { + if e.is_disconnected_will_reconnect() { + keep = false; + } + } + async move { keep } }); Ok(StreamOf(Box::pin(stream))) @@ -260,12 +277,14 @@ impl Backend for LegacyBackend { let methods = methods.clone(); Box::pin(async move { let sub = methods.chain_subscribe_all_heads().await?; - let sub = sub.map(|r| { - r.map(|h| { - let hash = h.hash(); - (h, BlockRef::from_hash(hash)) - }) - }); + let sub = sub + .map_err(|e| e.into()) + .map(|r| { + r.map(|h| { + let hash = h.hash(); + (h, BlockRef::from_hash(hash)) + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -283,12 +302,14 @@ impl Backend for LegacyBackend { let methods = methods.clone(); Box::pin(async move { let sub = methods.chain_subscribe_new_heads().await?; - let sub = sub.map(|r| { - r.map(|h| { - let hash = h.hash(); - (h, BlockRef::from_hash(hash)) - }) - }); + let sub = sub + .map_err(|e| e.into()) + .map(|r| { + r.map(|h| { + let hash = h.hash(); + (h, BlockRef::from_hash(hash)) + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -347,6 +368,7 @@ impl Backend for LegacyBackend { let sub = sub.filter_map(|r| { let mapped = r + .map_err(|e| e.into()) .map(|tx| { match tx { // We ignore these because they don't map nicely to the new API. They don't signal "end states" so this should be fine. @@ -401,7 +423,10 @@ impl Backend for LegacyBackend { call_parameters: Option<&[u8]>, at: T::Hash, ) -> Result, Error> { - retry(|| self.methods.state_call(method, call_parameters, Some(at))).await + retry(|| async { + let res = self.methods.state_call(method, call_parameters, Some(at)).await?; + Ok(res) + }).await } } @@ -530,14 +555,15 @@ impl Stream for StorageFetchDescendantKeysStream { let storage_page_size = this.storage_page_size; let pagination_start_key = this.pagination_start_key.clone(); let keys_fut = async move { - methods + let keys = methods .state_get_keys_paged( &key, storage_page_size, pagination_start_key.as_deref(), Some(at), ) - .await + .await?; + Ok(keys) }; this.keys_fut = Some(Box::pin(keys_fut)); } @@ -600,8 +626,10 @@ impl Stream for StorageFetchDescendantValuesStream { let results_fut = async move { let keys = keys.iter().map(|k| &**k); let values = - retry(|| methods.state_query_storage_at(keys.clone(), Some(at))) - .await?; + retry(|| async { + let res = methods.state_query_storage_at(keys.clone(), Some(at)).await?; + Ok(res) + }).await?; let values: VecDeque<_> = values .into_iter() .flat_map(|v| { diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index a8b1d1c426d..b167ad7e2b5 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -8,7 +8,6 @@ pub mod chain_head; pub mod legacy; -pub mod rpc; pub mod utils; use subxt_core::client::RuntimeVersion; @@ -22,6 +21,12 @@ use futures::{Stream, StreamExt}; use std::pin::Pin; use std::sync::Arc; +/// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`]. +pub mod rpc { + pub use subxt_rpcs::{ RpcClient, RpcClientT }; + pub use subxt_rpcs::client::{ RawRpcFuture, RawRpcSubscription, RawValue, reconnecting_rpc_client }; +} + /// Prevent the backend trait being implemented externally. #[doc(hidden)] pub(crate) mod sealed { @@ -334,7 +339,7 @@ pub struct StorageResponse { mod test { use super::*; pub use crate::backend::rpc::{RawRpcFuture, RawRpcSubscription}; - pub use crate::{backend::StorageResponse, error::RpcError}; + pub use crate::backend::StorageResponse; pub use futures::StreamExt; pub use polkadot_sdk::sp_core; pub use primitive_types::H256; @@ -345,7 +350,7 @@ mod test { pub use subxt_core::{config::DefaultExtrinsicParams, Config}; pub use tokio::sync::{mpsc, Mutex}; - pub type RpcResult = Result; + pub type RpcResult = Result; pub type Item = RpcResult; fn random_hash() -> H256 { @@ -361,6 +366,7 @@ mod test { value: value.into(), } } + pub mod rpc_client { use super::*; use std::time::Duration; @@ -617,9 +623,7 @@ mod test { mod legacy { use super::*; - use crate::backend::{ - legacy::rpc_methods::Bytes, legacy::rpc_methods::RuntimeVersion, legacy::LegacyBackend, - }; + use crate::{backend::legacy::{rpc_methods::{Bytes, RuntimeVersion}, LegacyBackend}, error::RpcError}; use rpc_client::*; pub fn setup_mock_rpc() -> MockRpcBuilder { @@ -668,14 +672,14 @@ mod test { ("ID1", Message::Single(bytes("Data1"))), ( "ID2", - Message::Single(Err(RpcError::DisconnectedWillReconnect( + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), ))), ), ("ID2", Message::Single(bytes("Data2"))), ( "ID3", - Message::Single(Err(RpcError::DisconnectedWillReconnect( + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), ))), ), @@ -713,7 +717,7 @@ mod test { let mock_data = [ ( "ID1", - Message::Single(Err(RpcError::DisconnectedWillReconnect( + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), ))), ), @@ -752,7 +756,7 @@ mod test { let mock_data = vec![ ( "chain_getBlockHash", - Message::Single(Err(RpcError::DisconnectedWillReconnect( + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), ))), ), @@ -796,7 +800,7 @@ mod test { "state_subscribeRuntimeVersion", Message::Many(Ok(vec![ Ok(runtime_version(0)), - Err(RpcError::DisconnectedWillReconnect( + Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), )), Ok(runtime_version(1)), @@ -805,7 +809,7 @@ mod test { ( "state_subscribeRuntimeVersion", Message::Many(Ok(vec![ - Err(RpcError::DisconnectedWillReconnect( + Err(subxt_rpcs::Error::DisconnectedWillReconnect( "Reconnecting".to_string(), )), Ok(runtime_version(2)), @@ -817,7 +821,7 @@ mod test { Message::Many(Ok(vec![ Ok(runtime_version(4)), Ok(runtime_version(5)), - Err(RpcError::RequestRejected("Reconnecting".to_string())), + Err(subxt_rpcs::Error::Client("Reconnecting".into())), ])), ), ]; @@ -859,7 +863,7 @@ mod test { } else { assert!(matches!( res, - Err(crate::Error::Rpc(RpcError::RequestRejected(_))) + Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::Client(_)))) )) } } @@ -874,7 +878,8 @@ mod test { use futures::task::Poll; use rpc_client::{Message, MockRpcBuilder, Subscription}; - use rpc_methods::{ + use subxt_rpcs::methods::chain_head::{ + self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent, }; @@ -921,7 +926,7 @@ mod test { serde_json::from_value(spec).unwrap() } - type FollowEvent = chain_head::rpc_methods::FollowEvent<::Hash>; + type FollowEvent = chain_head::FollowEvent<::Hash>; fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder { let hash = random_hash(); @@ -936,7 +941,7 @@ mod test { let follow_event = FollowEvent::Initialized(Initialized::<::Hash> { finalized_block_hashes: vec![hash], - finalized_block_runtime: Some(rpc_methods::RuntimeEvent::Valid( + finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( RuntimeVersionEvent { spec: runtime_spec(), }, @@ -983,15 +988,15 @@ mod test { operation_id: id.to_owned(), }) } - fn storage_result(key: &str, value: &str) -> chain_head::rpc_methods::StorageResult { - chain_head::rpc_methods::StorageResult { + fn storage_result(key: &str, value: &str) -> chain_head::StorageResult { + chain_head::StorageResult { key: Bytes(key.to_owned().into()), - result: rpc_methods::StorageResultType::Value(Bytes(value.to_owned().into())), + result: chain_head::StorageResultType::Value(Bytes(value.to_owned().into())), } } fn storage_items( id: &str, - items: &[chain_head::rpc_methods::StorageResult], + items: &[chain_head::StorageResult], ) -> FollowEvent { FollowEvent::OperationStorageItems(OperationStorageItems { operation_id: id.to_owned(), @@ -1062,7 +1067,7 @@ mod test { let response_data = vec![ ( "method_response", - Message::Single(Err(RpcError::DisconnectedWillReconnect("Error".into()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()))), ), ( "method_response", @@ -1137,8 +1142,8 @@ mod test { let response_data = vec![ ( "method_response", - Message::Single(Err::( - RpcError::DisconnectedWillReconnect("Error".into()), + Message::Single(Err::( + subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()), )), ), ( @@ -1155,7 +1160,7 @@ mod test { ("continue_response", Message::Single(Ok(()))), ( "continue_response", - Message::Single(Err(RpcError::DisconnectedWillReconnect("Error".into()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()))), ), ("continue_response", Message::Single(Ok(()))), ("continue_response", Message::Single(Ok(()))), @@ -1289,13 +1294,11 @@ mod test { let mock_data = vec![ ( "chainSpec_v1_genesisHash", - Message::Single(Err::(RpcError::RequestRejected( - "Error".to_owned(), - ))), + Message::Single(Err::(subxt_rpcs::Error::Client("Error".into()))), ), ( "chainSpec_v1_genesisHash", - Message::Single(Err(RpcError::DisconnectedWillReconnect("Error".to_owned()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".to_owned()))), ), ("chainSpec_v1_genesisHash", Message::Single(Ok(hash))), ]; @@ -1329,7 +1332,7 @@ mod test { ), ( "method_response", - Message::Single(Err(RpcError::RequestRejected("stale id".into()))), + Message::Single(Err(subxt_rpcs::Error::Client("stale id".into()))), ), ( "method_response", @@ -1369,7 +1372,7 @@ mod test { let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); let key: String = rpc_params.sequence().next().unwrap(); if key == *"ID1" { - return Err(RpcError::RequestRejected("stale id".into())); + return Err(subxt_rpcs::Error::Client("stale id".into())); } else { subscription_expired .swap(false, std::sync::atomic::Ordering::SeqCst); @@ -1429,7 +1432,7 @@ mod test { assert!(matches!( response, - Err(Error::Rpc(RpcError::RequestRejected(reason))) if reason == "stale id" + Err(e) if e.to_string() == "stale id" )) } } diff --git a/subxt/src/backend/utils.rs b/subxt/src/backend/utils.rs index e8587734ba0..28a89bdf37e 100644 --- a/subxt/src/backend/utils.rs +++ b/subxt/src/backend/utils.rs @@ -182,9 +182,9 @@ mod tests { use crate::backend::StreamOf; fn disconnect_err() -> Error { - Error::Rpc(crate::error::RpcError::DisconnectedWillReconnect( + Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect( String::new(), - )) + ).into()) } fn custom_err() -> Error { diff --git a/subxt/src/client/online_client.rs b/subxt/src/client/online_client.rs index fccd4874234..d0323195655 100644 --- a/subxt/src/client/online_client.rs +++ b/subxt/src/client/online_client.rs @@ -64,7 +64,7 @@ impl OnlineClient { /// Construct a new [`OnlineClient`], providing a URL to connect to. pub async fn from_url(url: impl AsRef) -> Result, Error> { - crate::utils::validate_url_is_secure(url.as_ref())?; + subxt_rpcs::utils::validate_url_is_secure(url.as_ref())?; OnlineClient::from_insecure_url(url).await } diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 21e20735c1f..90f421dd0dc 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -120,10 +120,16 @@ impl From for Error { } } +impl From for Error { + fn from(value: subxt_rpcs::Error) -> Self { + Error::Rpc(value.into()) + } +} + impl Error { /// Checks whether the error was caused by a RPC re-connection. pub fn is_disconnected_will_reconnect(&self) -> bool { - matches!(self, Error::Rpc(RpcError::DisconnectedWillReconnect(_))) + matches!(self, Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(_)))) } /// Checks whether the error was caused by a RPC request being rejected. @@ -141,7 +147,7 @@ pub enum RpcError { // for `subscribe_to_block_headers_filling_in_gaps` and friends. /// Error related to the RPC client. #[error("RPC error: {0}")] - ClientError(Box), + ClientError(#[from] subxt_rpcs::Error), /// This error signals that the request was rejected for some reason. /// The specific reason is provided. #[error("RPC error: request rejected: {0}")] @@ -149,12 +155,6 @@ pub enum RpcError { /// The RPC subscription dropped. #[error("RPC error: subscription dropped.")] SubscriptionDropped, - /// The requested URL is insecure. - #[error("RPC error: insecure URL: {0}")] - InsecureUrl(String), - /// The connection was lost and automatically reconnected. - #[error("RPC error: the connection was lost `{0}`; reconnect automatically initiated")] - DisconnectedWillReconnect(String), } impl RpcError { diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index c88920a8006..dba19f4221f 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -108,6 +108,7 @@ pub mod ext { pub use scale_encode; pub use scale_value; pub use subxt_core; + pub use subxt_rpcs; cfg_jsonrpsee! { pub use jsonrpsee; diff --git a/subxt/src/macros.rs b/subxt/src/macros.rs index b3365ef4e63..2119361935b 100644 --- a/subxt/src/macros.rs +++ b/subxt/src/macros.rs @@ -46,19 +46,8 @@ macro_rules! cfg_jsonrpsee_web { } } -#[allow(unused)] -macro_rules! cfg_reconnecting_rpc_client { - ($($item:item)*) => { - $( - #[cfg(all(feature = "reconnecting-rpc-client", any(feature = "native", feature = "web")))] - #[cfg_attr(docsrs, doc(cfg(feature = "reconnecting-rpc-client")))] - $item - )* - } -} - pub(crate) use { - cfg_feature, cfg_jsonrpsee, cfg_reconnecting_rpc_client, cfg_unstable_light_client, + cfg_feature, cfg_jsonrpsee, cfg_unstable_light_client, }; // Only used by light-client. diff --git a/subxt/src/utils/mod.rs b/subxt/src/utils/mod.rs index 08ae07ca33c..a5f8d8ba885 100644 --- a/subxt/src/utils/mod.rs +++ b/subxt/src/utils/mod.rs @@ -5,8 +5,6 @@ //! Miscellaneous utility helpers. use crate::macros::cfg_jsonrpsee; -use crate::{error::RpcError, Error}; -use url::Url; pub use subxt_core::utils::{ bits, strip_compact_prefix, to_hex, AccountId32, Encoded, Era, KeyedVec, MultiAddress, @@ -14,32 +12,9 @@ pub use subxt_core::utils::{ H256, H512, }; +pub use subxt_rpcs::utils::url_is_secure; + cfg_jsonrpsee! { mod fetch_chain_spec; pub use fetch_chain_spec::{fetch_chainspec_from_rpc_node, FetchChainspecError}; } - -/// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. -/// -/// Returns an error if the string could not be parsed into a URL. -pub fn url_is_secure(url: &str) -> Result { - let url = Url::parse(url).map_err(|e| Error::Rpc(RpcError::ClientError(Box::new(e))))?; - - let secure_scheme = url.scheme() == "https" || url.scheme() == "wss"; - let is_localhost = url.host().is_some_and(|e| match e { - url::Host::Domain(e) => e == "localhost", - url::Host::Ipv4(e) => e.is_loopback(), - url::Host::Ipv6(e) => e.is_loopback(), - }); - - Ok(secure_scheme || is_localhost) -} - -/// Validates, that the given Url is secure ("https" or "wss" scheme) or is referring to localhost. -pub fn validate_url_is_secure(url: &str) -> Result<(), Error> { - if !url_is_secure(url)? { - Err(Error::Rpc(crate::error::RpcError::InsecureUrl(url.into()))) - } else { - Ok(()) - } -} diff --git a/testing/integration-tests/Cargo.toml b/testing/integration-tests/Cargo.toml index 9a1016ef4df..49a7a5fb550 100644 --- a/testing/integration-tests/Cargo.toml +++ b/testing/integration-tests/Cargo.toml @@ -40,6 +40,7 @@ subxt = { workspace = true, features = ["unstable-metadata", "native", "jsonrpse subxt-signer = { workspace = true, features = ["default"] } subxt-codegen = { workspace = true } subxt-metadata = { workspace = true } +subxt-rpcs = { workspace = true } test-runtime = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 458d1ff6aeb..2d3d33ff9ca 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -168,10 +168,10 @@ async fn runtime_api_call() -> Result<(), subxt::Error> { .await?; // get metadata via `state_getMetadata`. - let meta2 = rpc.state_get_metadata(Some(block.hash())).await?; + let meta2_bytes = rpc.state_get_metadata(Some(block.hash())).await?.into_raw(); // They should be the same. - assert_eq!(meta1.encode(), meta2.encode()); + assert_eq!(meta1.encode(), meta2_bytes); Ok(()) } diff --git a/testing/integration-tests/src/full_client/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs index 4eeb5fe0567..736abb44e27 100644 --- a/testing/integration-tests/src/full_client/client/mod.rs +++ b/testing/integration-tests/src/full_client/client/mod.rs @@ -428,7 +428,7 @@ async fn chainhead_block_subscription_reconnect() { let disconnected = match item { Ok(_) => false, Err(e) => { - if matches!(e, Error::Rpc(subxt::error::RpcError::DisconnectedWillReconnect(e)) if e.contains("Missed at least one block when the connection was lost")) { + if e.is_disconnected_will_reconnect() && e.to_string().contains("Missed at least one block when the connection was lost") { missed_blocks = true; } e.is_disconnected_will_reconnect() diff --git a/testing/integration-tests/src/full_client/client/unstable_rpcs.rs b/testing/integration-tests/src/full_client/client/unstable_rpcs.rs index dbc2defd9eb..c6d28bd1e8c 100644 --- a/testing/integration-tests/src/full_client/client/unstable_rpcs.rs +++ b/testing/integration-tests/src/full_client/client/unstable_rpcs.rs @@ -13,14 +13,14 @@ use assert_matches::assert_matches; use codec::Encode; use futures::Stream; use subxt::{ - backend::chain_head::rpc_methods::{ - FollowEvent, Initialized, MethodResponse, RuntimeEvent, RuntimeVersionEvent, StorageQuery, - StorageQueryType, - }, config::Hasher, utils::{AccountId32, MultiAddress}, SubstrateConfig, }; +use subxt_rpcs::methods::chain_head::{ + FollowEvent, Initialized, MethodResponse, RuntimeEvent, RuntimeVersionEvent, StorageQuery, + StorageQueryType, +}; use subxt_signer::sr25519::dev; @@ -293,7 +293,8 @@ async fn transactionwatch_v1_submit_and_watch() { /// Ignore block related events and obtain the next event related to an operation. async fn next_operation_event< T: serde::de::DeserializeOwned, - S: Unpin + Stream, subxt::Error>>, + S: Unpin + Stream, E>>, + E: core::fmt::Debug, >( sub: &mut S, ) -> FollowEvent { From 467a5ecaf9be44f89235b3850b2122cb0432e86d Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 27 Jan 2025 15:44:56 +0000 Subject: [PATCH 02/32] fmt --- rpcs/src/client/mod.rs | 4 +- rpcs/src/client/rpc_client.rs | 8 +-- rpcs/src/client/rpc_client_t.rs | 2 +- rpcs/src/lib.rs | 20 +++--- rpcs/src/methods/chain_head.rs | 2 +- rpcs/src/methods/legacy.rs | 15 ++-- rpcs/src/methods/mod.rs | 8 +-- rpcs/src/utils.rs | 4 +- subxt/src/backend/chain_head/follow_stream.rs | 6 +- .../chain_head/follow_stream_driver.rs | 11 +-- .../backend/chain_head/follow_stream_unpin.rs | 2 +- subxt/src/backend/chain_head/mod.rs | 19 +++--- subxt/src/backend/chain_head/storage_items.rs | 12 ++-- subxt/src/backend/legacy.rs | 68 ++++++++++--------- subxt/src/backend/mod.rs | 42 ++++++++---- subxt/src/backend/utils.rs | 4 +- subxt/src/error/mod.rs | 7 +- subxt/src/macros.rs | 4 +- 18 files changed, 131 insertions(+), 107 deletions(-) diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index 056ed946813..c3804999131 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -58,12 +58,12 @@ crate::macros::cfg_jsonrpsee! { mod jsonrpsee_impl; - pub use jsonrpsee::core::client::Client as JsonrpseeRpcClient; + pub use jsonrpsee::core::client::Client as JsonrpseeRpcClient; } crate::macros::cfg_unstable_light_client! { mod lightclient_impl; - pub use lightclient_impl::LightClientRpc as LightClientRpcClient; + pub use lightclient_impl::LightClientRpc as LightClientRpcClient; } crate::macros::cfg_reconnecting_rpc_client! { diff --git a/rpcs/src/client/rpc_client.rs b/rpcs/src/client/rpc_client.rs index 94bcc2ba209..bfea732309b 100644 --- a/rpcs/src/client/rpc_client.rs +++ b/rpcs/src/client/rpc_client.rs @@ -165,8 +165,7 @@ impl RpcParams { } else { self.0.push(b',') } - serde_json::to_writer(&mut self.0, ¶m) - .map_err(Error::Deserialization)?; + serde_json::to_writer(&mut self.0, ¶m).map_err(Error::Deserialization)?; Ok(()) } /// Build a [`RawValue`] from our params, returning `None` if no parameters @@ -236,8 +235,9 @@ impl Stream for RpcSubscription { // Decode the inner RawValue to the type we're expecting and map // any errors to the right shape: let res = res.map(|r| { - r.map_err(|e| e.into()) - .and_then(|raw_val| serde_json::from_str(raw_val.get()).map_err(Error::Deserialization)) + r.map_err(|e| e.into()).and_then(|raw_val| { + serde_json::from_str(raw_val.get()).map_err(Error::Deserialization) + }) }); Poll::Ready(res) diff --git a/rpcs/src/client/rpc_client_t.rs b/rpcs/src/client/rpc_client_t.rs index 68fce963128..66075fd688a 100644 --- a/rpcs/src/client/rpc_client_t.rs +++ b/rpcs/src/client/rpc_client_t.rs @@ -2,9 +2,9 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. +use crate::Error; use futures::Stream; use std::{future::Future, pin::Pin}; -use crate::Error; // Re-exporting for simplicity since it's used a bunch in the trait definition. pub use serde_json::value::RawValue; diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 65acf3cd0dc..3aa287a3cd8 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -10,19 +10,18 @@ ))] compile_error!("subxt-rpcs: exactly one of the 'web' and 'native' features should be used."); - mod macros; -pub mod utils; pub mod client; pub mod methods; +pub mod utils; // Expose the most common things at the top level: -pub use client::{ RpcClient, RpcClientT }; -pub use methods::{ ChainHeadRpcMethods, LegacyRpcMethods }; +pub use client::{RpcClient, RpcClientT}; +pub use methods::{ChainHeadRpcMethods, LegacyRpcMethods}; /// Configuration used by some of the RPC methods to determine the shape of -/// some of the inputs or responses. +/// some of the inputs or responses. pub trait RpcConfig { /// The block header type. type Header: Header; @@ -34,20 +33,23 @@ pub trait RpcConfig { /// A trait which is applied to any type that is a valid block header. pub trait Header: std::fmt::Debug + codec::Decode + serde::de::DeserializeOwned {} -impl Header for T where T: std::fmt::Debug + codec::Decode + serde::de::DeserializeOwned {} +impl Header for T where T: std::fmt::Debug + codec::Decode + serde::de::DeserializeOwned {} /// A trait which is applied to any type that is a valid block hash. pub trait BlockHash: serde::de::DeserializeOwned + serde::Serialize {} -impl BlockHash for T where T: serde::de::DeserializeOwned + serde::Serialize {} +impl BlockHash for T where T: serde::de::DeserializeOwned + serde::Serialize {} /// A trait which is applied to any type that is a valid Account ID. pub trait AccountId: serde::Serialize {} -impl AccountId for T where T: serde::Serialize {} +impl AccountId for T where T: serde::Serialize {} #[cfg(feature = "subxt-core")] mod impl_config { use super::*; - impl RpcConfig for T where T: subxt_core::Config { + impl RpcConfig for T + where + T: subxt_core::Config, + { type Header = T::Header; type Hash = T::Hash; type AccountId = T::AccountId; diff --git a/rpcs/src/methods/chain_head.rs b/rpcs/src/methods/chain_head.rs index 6d1faeacdea..6b7c958172e 100644 --- a/rpcs/src/methods/chain_head.rs +++ b/rpcs/src/methods/chain_head.rs @@ -8,7 +8,7 @@ use crate::client::{rpc_params, RpcClient, RpcSubscription}; use crate::BlockHash; -use crate::{RpcConfig, Error}; +use crate::{Error, RpcConfig}; use derive_where::derive_where; use futures::{Stream, StreamExt}; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/rpcs/src/methods/legacy.rs b/rpcs/src/methods/legacy.rs index 0125ee1ec04..a2b7690f911 100644 --- a/rpcs/src/methods/legacy.rs +++ b/rpcs/src/methods/legacy.rs @@ -5,10 +5,10 @@ //! An interface to call the raw legacy RPC methods. use crate::client::{rpc_params, RpcClient, RpcSubscription}; -use crate::{RpcConfig, Error}; +use crate::{Error, RpcConfig}; use codec::Decode; -use frame_metadata::RuntimeMetadataPrefixed; use derive_where::derive_where; +use frame_metadata::RuntimeMetadataPrefixed; use primitive_types::U256; use serde::{Deserialize, Serialize}; @@ -101,7 +101,10 @@ impl LegacyRpcMethods { } /// Fetch the metadata via the legacy `state_getMetadata` RPC method. - pub async fn state_get_metadata(&self, at: Option) -> Result { + pub async fn state_get_metadata( + &self, + at: Option, + ) -> Result { let bytes: Bytes = self .client .request("state_getMetadata", rpc_params![at]) @@ -398,12 +401,14 @@ impl LegacyRpcMethods { pub struct StateGetMetadataResponse(Vec); impl StateGetMetadataResponse { - /// Return the raw SCALE encoded metadata bytes + /// Return the raw SCALE encoded metadata bytes pub fn into_raw(self) -> Vec { self.0 } /// Decode and return [`frame_metadata::RuntimeMetadataPrefixed`]. - pub fn to_frame_metadata(&self) -> Result { + pub fn to_frame_metadata( + &self, + ) -> Result { RuntimeMetadataPrefixed::decode(&mut &*self.0) } } diff --git a/rpcs/src/methods/mod.rs b/rpcs/src/methods/mod.rs index 98b4252ea75..c61736bade0 100644 --- a/rpcs/src/methods/mod.rs +++ b/rpcs/src/methods/mod.rs @@ -3,12 +3,12 @@ // see LICENSE for license details. //! RPC methods are defined in this module. At the moment we have: -//! +//! //! - [`ChainHeadRpcMethods`] (and the types in [`chain_head`]): these methods //! implement the RPC spec at -//! +//! //! We also have (although their use is not advised): -//! +//! //! - [`LegacyRpcMethods`] (and the types in [`legacy`]): a collection of legacy RPCs. //! These are not well specified and may change in implementations without warning, //! but for those methods we expose, we make a best effort to work against latest Substrate versions. @@ -17,4 +17,4 @@ pub mod chain_head; pub mod legacy; pub use chain_head::ChainHeadRpcMethods; -pub use legacy::LegacyRpcMethods; \ No newline at end of file +pub use legacy::LegacyRpcMethods; diff --git a/rpcs/src/utils.rs b/rpcs/src/utils.rs index 1d61c7854f3..b77462f32e0 100644 --- a/rpcs/src/utils.rs +++ b/rpcs/src/utils.rs @@ -4,8 +4,8 @@ //! A couple of utility methods that we make use of. -use url::Url; use crate::Error; +use url::Url; /// A URL is considered secure if it uses a secure scheme ("https" or "wss") or is referring to localhost. /// @@ -30,4 +30,4 @@ pub fn validate_url_is_secure(url: &str) -> Result<(), Error> { } else { Ok(()) } -} \ No newline at end of file +} diff --git a/subxt/src/backend/chain_head/follow_stream.rs b/subxt/src/backend/chain_head/follow_stream.rs index 5d2d557d7f4..0fecd3bc89f 100644 --- a/subxt/src/backend/chain_head/follow_stream.rs +++ b/subxt/src/backend/chain_head/follow_stream.rs @@ -4,11 +4,11 @@ use crate::config::Config; use crate::error::Error; -use subxt_rpcs::methods::chain_head::{ChainHeadRpcMethods, FollowEvent}; use futures::{FutureExt, Stream, StreamExt, TryStreamExt}; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; +use subxt_rpcs::methods::chain_head::{ChainHeadRpcMethods, FollowEvent}; /// A `Stream` whose goal is to remain subscribed to `chainHead_follow`. It will re-subscribe if the subscription /// is ended for any reason, and it will return the current `subscription_id` as an event, along with the other @@ -217,12 +217,10 @@ impl Stream for FollowStream { #[cfg(test)] pub(super) mod test_utils { use super::*; - use subxt_rpcs::methods::chain_head::{ - BestBlockChanged, Finalized, Initialized, NewBlock, - }; use crate::config::substrate::H256; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; + use subxt_rpcs::methods::chain_head::{BestBlockChanged, Finalized, Initialized, NewBlock}; /// Given some events, returns a follow stream getter that we can use in /// place of the usual RPC method. diff --git a/subxt/src/backend/chain_head/follow_stream_driver.rs b/subxt/src/backend/chain_head/follow_stream_driver.rs index 15a6c8c52e9..70c49dbf4cc 100644 --- a/subxt/src/backend/chain_head/follow_stream_driver.rs +++ b/subxt/src/backend/chain_head/follow_stream_driver.rs @@ -5,13 +5,13 @@ use super::follow_stream_unpin::{BlockRef, FollowStreamMsg, FollowStreamUnpin}; use crate::config::BlockHash; use crate::error::{Error, RpcError}; -use subxt_rpcs::methods::chain_head::{FollowEvent, Initialized, RuntimeEvent}; use futures::stream::{Stream, StreamExt}; use std::collections::{HashMap, HashSet, VecDeque}; use std::ops::DerefMut; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Waker}; +use subxt_rpcs::methods::chain_head::{FollowEvent, Initialized, RuntimeEvent}; /// A `Stream` which builds on `FollowStreamDriver`, and allows multiple subscribers to obtain events /// from the single underlying subscription (each being provided an `Initialized` message and all new @@ -454,9 +454,12 @@ where .iter() .position(|b| b.hash() == p.hash()) else { - return Poll::Ready(Some(Err(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect( - "Missed at least one block when the connection was lost".to_owned(), - )) + return Poll::Ready(Some(Err(RpcError::ClientError( + subxt_rpcs::Error::DisconnectedWillReconnect( + "Missed at least one block when the connection was lost" + .to_owned(), + ), + ) .into()))); }; diff --git a/subxt/src/backend/chain_head/follow_stream_unpin.rs b/subxt/src/backend/chain_head/follow_stream_unpin.rs index f2dcad788a7..a3aca94a349 100644 --- a/subxt/src/backend/chain_head/follow_stream_unpin.rs +++ b/subxt/src/backend/chain_head/follow_stream_unpin.rs @@ -6,10 +6,10 @@ use super::follow_stream::FollowStream; use super::ChainHeadRpcMethods; use crate::config::{BlockHash, Config}; use crate::error::Error; +use futures::stream::{FuturesUnordered, Stream, StreamExt}; use subxt_rpcs::methods::chain_head::{ BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, }; -use futures::stream::{FuturesUnordered, Stream, StreamExt}; use std::collections::{HashMap, HashSet}; use std::future::Future; diff --git a/subxt/src/backend/chain_head/mod.rs b/subxt/src/backend/chain_head/mod.rs index 66b60f42ccf..4f78d2882eb 100644 --- a/subxt/src/backend/chain_head/mod.rs +++ b/subxt/src/backend/chain_head/mod.rs @@ -16,14 +16,10 @@ mod follow_stream_driver; mod follow_stream_unpin; mod storage_items; -use subxt_rpcs::RpcClient; -use subxt_rpcs::methods::chain_head::{ - FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType, StorageResultType, -}; use self::follow_stream_driver::FollowStreamFinalizedHeads; use crate::backend::{ - utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, - StreamOf, StreamOfResults, TransactionStatus, + utils::retry, Backend, BlockRef, BlockRefT, RuntimeVersion, StorageResponse, StreamOf, + StreamOfResults, TransactionStatus, }; use crate::config::BlockHash; use crate::error::{Error, RpcError}; @@ -35,6 +31,10 @@ use futures::{Stream, StreamExt}; use std::collections::HashMap; use std::task::Poll; use storage_items::StorageItems; +use subxt_rpcs::methods::chain_head::{ + FollowEvent, MethodResponse, RuntimeEvent, StorageQuery, StorageQueryType, StorageResultType, +}; +use subxt_rpcs::RpcClient; // Expose the RPC methods. pub use subxt_rpcs::methods::chain_head::ChainHeadRpcMethods; @@ -340,7 +340,8 @@ impl Backend for ChainHeadBackend { retry(|| async { let genesis_hash = self.methods.chainspec_v1_genesis_hash().await?; Ok(genesis_hash) - }).await + }) + .await } async fn block_header(&self, at: T::Hash) -> Result, Error> { @@ -670,9 +671,7 @@ impl Backend for ChainHeadBackend { finalized_hash = Some(block.hash); continue; } - RpcTransactionStatus::BestChainBlockIncluded { - block: Some(block), - } => { + RpcTransactionStatus::BestChainBlockIncluded { block: Some(block) } => { // Look up a pinned block ref if we can, else return a non-pinned // block that likely isn't accessible. We have no guarantee that a best // block on the node a tx was sent to will ever be known about on the diff --git a/subxt/src/backend/chain_head/storage_items.rs b/subxt/src/backend/chain_head/storage_items.rs index 22ea4bd930a..4194ceaf6a6 100644 --- a/subxt/src/backend/chain_head/storage_items.rs +++ b/subxt/src/backend/chain_head/storage_items.rs @@ -6,15 +6,15 @@ use super::follow_stream_driver::FollowStreamDriverHandle; use super::follow_stream_unpin::BlockRef; use crate::config::Config; use crate::error::{Error, RpcError}; -use subxt_rpcs::methods::chain_head::{ - ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult, -}; use futures::{FutureExt, Stream, StreamExt}; use std::collections::VecDeque; use std::future::Future; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +use subxt_rpcs::methods::chain_head::{ + ChainHeadRpcMethods, FollowEvent, MethodResponse, StorageQuery, StorageResult, +}; /// Obtain a stream of storage items given some query. this handles continuing /// and stopping under the hood, and returns a stream of `StorageResult`s. @@ -59,8 +59,10 @@ impl StorageItems { let operation_id = operation_id.clone(); let methods = methods.clone(); - Box::pin(async move { - let cont = methods.chainhead_v1_continue(&sub_id, &operation_id).await?; + Box::pin(async move { + let cont = methods + .chainhead_v1_continue(&sub_id, &operation_id) + .await?; Ok(cont) }) }) diff --git a/subxt/src/backend/legacy.rs b/subxt/src/backend/legacy.rs index 6e253216efd..4e6ae3ee7da 100644 --- a/subxt/src/backend/legacy.rs +++ b/subxt/src/backend/legacy.rs @@ -188,14 +188,16 @@ impl Backend for LegacyBackend { retry(|| async { let hash = self.methods.genesis_hash().await?; Ok(hash) - }).await + }) + .await } async fn block_header(&self, at: T::Hash) -> Result, Error> { retry(|| async { let header = self.methods.chain_get_header(Some(at)).await?; Ok(header) - }).await + }) + .await } async fn block_body(&self, at: T::Hash) -> Result>>, Error> { @@ -237,14 +239,12 @@ impl Backend for LegacyBackend { Box::pin(async move { let sub = methods.state_subscribe_runtime_version().await?; - let sub = sub - .map_err(|e| e.into()) - .map(|r| { - r.map(|v| RuntimeVersion { - spec_version: v.spec_version, - transaction_version: v.transaction_version, - }) - }); + let sub = sub.map_err(|e| e.into()).map(|r| { + r.map(|v| RuntimeVersion { + spec_version: v.spec_version, + transaction_version: v.transaction_version, + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -277,14 +277,12 @@ impl Backend for LegacyBackend { let methods = methods.clone(); Box::pin(async move { let sub = methods.chain_subscribe_all_heads().await?; - let sub = sub - .map_err(|e| e.into()) - .map(|r| { - r.map(|h| { - let hash = h.hash(); - (h, BlockRef::from_hash(hash)) - }) - }); + let sub = sub.map_err(|e| e.into()).map(|r| { + r.map(|h| { + let hash = h.hash(); + (h, BlockRef::from_hash(hash)) + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -302,14 +300,12 @@ impl Backend for LegacyBackend { let methods = methods.clone(); Box::pin(async move { let sub = methods.chain_subscribe_new_heads().await?; - let sub = sub - .map_err(|e| e.into()) - .map(|r| { - r.map(|h| { - let hash = h.hash(); - (h, BlockRef::from_hash(hash)) - }) - }); + let sub = sub.map_err(|e| e.into()).map(|r| { + r.map(|h| { + let hash = h.hash(); + (h, BlockRef::from_hash(hash)) + }) + }); Ok(StreamOf(Box::pin(sub))) }) }) @@ -424,9 +420,13 @@ impl Backend for LegacyBackend { at: T::Hash, ) -> Result, Error> { retry(|| async { - let res = self.methods.state_call(method, call_parameters, Some(at)).await?; + let res = self + .methods + .state_call(method, call_parameters, Some(at)) + .await?; Ok(res) - }).await + }) + .await } } @@ -625,11 +625,13 @@ impl Stream for StorageFetchDescendantValuesStream { let at = this.keys.at; let results_fut = async move { let keys = keys.iter().map(|k| &**k); - let values = - retry(|| async { - let res = methods.state_query_storage_at(keys.clone(), Some(at)).await?; - Ok(res) - }).await?; + let values = retry(|| async { + let res = methods + .state_query_storage_at(keys.clone(), Some(at)) + .await?; + Ok(res) + }) + .await?; let values: VecDeque<_> = values .into_iter() .flat_map(|v| { diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index b167ad7e2b5..ea0292f6835 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -23,8 +23,10 @@ use std::sync::Arc; /// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`]. pub mod rpc { - pub use subxt_rpcs::{ RpcClient, RpcClientT }; - pub use subxt_rpcs::client::{ RawRpcFuture, RawRpcSubscription, RawValue, reconnecting_rpc_client }; + pub use subxt_rpcs::client::{ + reconnecting_rpc_client, RawRpcFuture, RawRpcSubscription, RawValue, + }; + pub use subxt_rpcs::{RpcClient, RpcClientT}; } /// Prevent the backend trait being implemented externally. @@ -623,7 +625,13 @@ mod test { mod legacy { use super::*; - use crate::{backend::legacy::{rpc_methods::{Bytes, RuntimeVersion}, LegacyBackend}, error::RpcError}; + use crate::{ + backend::legacy::{ + rpc_methods::{Bytes, RuntimeVersion}, + LegacyBackend, + }, + error::RpcError, + }; use rpc_client::*; pub fn setup_mock_rpc() -> MockRpcBuilder { @@ -863,7 +871,9 @@ mod test { } else { assert!(matches!( res, - Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::Client(_)))) + Err(Error::Rpc(RpcError::ClientError( + subxt_rpcs::Error::Client(_) + ))) )) } } @@ -879,9 +889,8 @@ mod test { use futures::task::Poll; use rpc_client::{Message, MockRpcBuilder, Subscription}; use subxt_rpcs::methods::chain_head::{ - self, - Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, - OperationStorageItems, RuntimeSpec, RuntimeVersionEvent, + self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, + OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent, }; use super::chain_head::*; @@ -994,10 +1003,7 @@ mod test { result: chain_head::StorageResultType::Value(Bytes(value.to_owned().into())), } } - fn storage_items( - id: &str, - items: &[chain_head::StorageResult], - ) -> FollowEvent { + fn storage_items(id: &str, items: &[chain_head::StorageResult]) -> FollowEvent { FollowEvent::OperationStorageItems(OperationStorageItems { operation_id: id.to_owned(), items: VecDeque::from(items.to_owned()), @@ -1067,7 +1073,9 @@ mod test { let response_data = vec![ ( "method_response", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "Error".into(), + ))), ), ( "method_response", @@ -1160,7 +1168,9 @@ mod test { ("continue_response", Message::Single(Ok(()))), ( "continue_response", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "Error".into(), + ))), ), ("continue_response", Message::Single(Ok(()))), ("continue_response", Message::Single(Ok(()))), @@ -1298,7 +1308,9 @@ mod test { ), ( "chainSpec_v1_genesisHash", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect("Error".to_owned()))), + Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "Error".to_owned(), + ))), ), ("chainSpec_v1_genesisHash", Message::Single(Ok(hash))), ]; @@ -1332,7 +1344,7 @@ mod test { ), ( "method_response", - Message::Single(Err(subxt_rpcs::Error::Client("stale id".into()))), + Message::Single(Err(subxt_rpcs::Error::Client("stale id".into()))), ), ( "method_response", diff --git a/subxt/src/backend/utils.rs b/subxt/src/backend/utils.rs index 28a89bdf37e..b7b003c48ef 100644 --- a/subxt/src/backend/utils.rs +++ b/subxt/src/backend/utils.rs @@ -182,9 +182,7 @@ mod tests { use crate::backend::StreamOf; fn disconnect_err() -> Error { - Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect( - String::new(), - ).into()) + Error::Rpc(subxt_rpcs::Error::DisconnectedWillReconnect(String::new()).into()) } fn custom_err() -> Error { diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index 90f421dd0dc..f2e10d46a6d 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -129,7 +129,12 @@ impl From for Error { impl Error { /// Checks whether the error was caused by a RPC re-connection. pub fn is_disconnected_will_reconnect(&self) -> bool { - matches!(self, Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::DisconnectedWillReconnect(_)))) + matches!( + self, + Error::Rpc(RpcError::ClientError( + subxt_rpcs::Error::DisconnectedWillReconnect(_) + )) + ) } /// Checks whether the error was caused by a RPC request being rejected. diff --git a/subxt/src/macros.rs b/subxt/src/macros.rs index 2119361935b..2a3b893b70c 100644 --- a/subxt/src/macros.rs +++ b/subxt/src/macros.rs @@ -46,9 +46,7 @@ macro_rules! cfg_jsonrpsee_web { } } -pub(crate) use { - cfg_feature, cfg_jsonrpsee, cfg_unstable_light_client, -}; +pub(crate) use {cfg_feature, cfg_jsonrpsee, cfg_unstable_light_client}; // Only used by light-client. #[allow(unused)] From d0943e606c05ca7bbf7ca2897018e0ed9562e72c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 27 Jan 2025 17:10:42 +0000 Subject: [PATCH 03/32] Fix test --- rpcs/src/client/reconnecting_rpc_client/mod.rs | 4 ++-- testing/integration-tests/src/full_client/blocks/mod.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rpcs/src/client/reconnecting_rpc_client/mod.rs b/rpcs/src/client/reconnecting_rpc_client/mod.rs index 69df9d8f131..8986561f5c8 100644 --- a/rpcs/src/client/reconnecting_rpc_client/mod.rs +++ b/rpcs/src/client/reconnecting_rpc_client/mod.rs @@ -431,8 +431,8 @@ impl RpcClientT for RpcClient { Error::DisconnectedWillReconnect(e) => { SubxtRpcError::DisconnectedWillReconnect(e.to_string()) } - Error::Dropped => SubxtRpcError::ClientError(Box::new(e)), - Error::RpcError(e) => SubxtRpcError::ClientError(Box::new(e)), + Error::Dropped => SubxtRpcError::Client(Box::new(e)), + Error::RpcError(e) => SubxtRpcError::Client(Box::new(e)), }) } .boxed() diff --git a/testing/integration-tests/src/full_client/blocks/mod.rs b/testing/integration-tests/src/full_client/blocks/mod.rs index 2d3d33ff9ca..a8c1184633d 100644 --- a/testing/integration-tests/src/full_client/blocks/mod.rs +++ b/testing/integration-tests/src/full_client/blocks/mod.rs @@ -21,8 +21,6 @@ use subxt::{ #[cfg(fullclient)] use subxt_signer::sr25519::dev; -use subxt_metadata::Metadata; - #[cfg(fullclient)] #[subxt_test] async fn block_subscriptions_are_consistent_with_eachother() -> Result<(), subxt::Error> { @@ -164,7 +162,10 @@ async fn runtime_api_call() -> Result<(), subxt::Error> { // get metadata via state_call. let (_, meta1) = rt - .call_raw::<(Compact, Metadata)>("Metadata_metadata", None) + .call_raw::<(Compact, frame_metadata::RuntimeMetadataPrefixed)>( + "Metadata_metadata", + None, + ) .await?; // get metadata via `state_getMetadata`. From 7b676182ec540f78734e7fb1fedbd33779164652 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 27 Jan 2025 17:12:21 +0000 Subject: [PATCH 04/32] Remove unused deps --- Cargo.lock | 2 -- subxt/Cargo.toml | 3 --- 2 files changed, 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0341b865a1..c9469412fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10552,14 +10552,12 @@ dependencies = [ "bitvec", "derive-where", "either", - "finito", "frame-metadata 18.0.0", "futures", "getrandom", "hex", "http-body", "hyper", - "impl-serde 0.5.0", "jsonrpsee", "parity-scale-codec", "polkadot-sdk", diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index c3ef650dcfb..c57bdd5e3e1 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -46,7 +46,6 @@ web = [ "subxt-macro/web", "subxt-rpcs/web", "tokio?/sync", - "finito?/wasm-bindgen", ] # Feature flag to enable the default future executor. @@ -101,7 +100,6 @@ either = { workspace = true } web-time = { workspace = true } # Provides some deserialization, types like U256/H256 and hashing impls like twox/blake256: -impl-serde = { workspace = true } primitive-types = { workspace = true, features = ["codec", "scale-info", "serde"] } # Included if the "jsonrpsee" feature is enabled. @@ -127,7 +125,6 @@ tokio-util = { workspace = true, features = ["compat"], optional = true } # Only the `tokio/sync` is used in the reconnecting rpc client # and that compiles both for native and web. tokio = { workspace = true, optional = true } -finito = { workspace = true, optional = true } wasm-bindgen-futures = { workspace = true, optional = true } [dev-dependencies] From 4576c4799ffd7a76fbae47b53ae0af153ac4a0eb Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 28 Jan 2025 15:23:05 +0000 Subject: [PATCH 05/32] fix import --- subxt/src/backend/mod.rs | 10 +++++----- subxt/src/lib.rs | 8 ++++---- subxt/src/macros.rs | 8 +++++++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index ea0292f6835..ebe3a5c0ced 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -10,8 +10,6 @@ pub mod chain_head; pub mod legacy; pub mod utils; -use subxt_core::client::RuntimeVersion; - use crate::error::Error; use crate::metadata::Metadata; use crate::Config; @@ -20,12 +18,14 @@ use codec::{Decode, Encode}; use futures::{Stream, StreamExt}; use std::pin::Pin; use std::sync::Arc; +use subxt_core::client::RuntimeVersion; /// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`]. pub mod rpc { - pub use subxt_rpcs::client::{ - reconnecting_rpc_client, RawRpcFuture, RawRpcSubscription, RawValue, - }; + pub use subxt_rpcs::client::{RawRpcFuture, RawRpcSubscription, RawValue}; + crate::macros::cfg_reconnecting_rpc_client! { + pub use subxt_rpcs::client::reconnecting_rpc_client; + } pub use subxt_rpcs::{RpcClient, RpcClientT}; } diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index dba19f4221f..c62f4702f07 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -18,6 +18,10 @@ ))] compile_error!("subxt: exactly one of the 'web' and 'native' features should be used."); +// Internal helper macros +#[macro_use] +mod macros; + // The guide is here. pub mod book; @@ -80,10 +84,6 @@ pub mod dynamic { }; } -// Internal helper macros -#[macro_use] -mod macros; - // Expose light client bits cfg_unstable_light_client! { pub use subxt_lightclient as lightclient; diff --git a/subxt/src/macros.rs b/subxt/src/macros.rs index 2a3b893b70c..31058223d08 100644 --- a/subxt/src/macros.rs +++ b/subxt/src/macros.rs @@ -18,6 +18,12 @@ macro_rules! cfg_unstable_light_client { }; } +macro_rules! cfg_reconnecting_rpc_client { + ($($item:item)*) => { + crate::macros::cfg_feature!("reconnecting-rpc-client", $($item)*); + }; +} + macro_rules! cfg_jsonrpsee { ($($item:item)*) => { crate::macros::cfg_feature!("jsonrpsee", $($item)*); @@ -50,4 +56,4 @@ pub(crate) use {cfg_feature, cfg_jsonrpsee, cfg_unstable_light_client}; // Only used by light-client. #[allow(unused)] -pub(crate) use {cfg_jsonrpsee_native, cfg_jsonrpsee_web}; +pub(crate) use {cfg_jsonrpsee_native, cfg_jsonrpsee_web, cfg_reconnecting_rpc_client}; From b0f9c9d845edbb5b53006de5d738e6f15c1558f8 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 29 Jan 2025 16:19:07 +0000 Subject: [PATCH 06/32] WIP: Fix up errors and most tests. Start extracintg some tests/code to rpc crate --- lightclient/src/lib.rs | 7 + rpcs/Cargo.toml | 2 +- rpcs/src/client/jsonrpsee_impl.rs | 24 ++- rpcs/src/client/lightclient_impl.rs | 23 ++- rpcs/src/client/mock_rpc_client.rs | 148 ++++++++++++++++++ rpcs/src/client/mod.rs | 7 +- .../src/client/reconnecting_rpc_client/mod.rs | 31 +++- rpcs/src/client/rpc_client_t.rs | 2 +- rpcs/src/lib.rs | 40 +++++ subxt/src/backend/chain_head/mod.rs | 4 +- subxt/src/backend/chain_head/storage_items.rs | 2 +- subxt/src/backend/mod.rs | 12 +- subxt/src/backend/utils.rs | 18 ++- subxt/src/error/mod.rs | 19 +-- 14 files changed, 290 insertions(+), 49 deletions(-) create mode 100644 rpcs/src/client/mock_rpc_client.rs diff --git a/lightclient/src/lib.rs b/lightclient/src/lib.rs index 24fd8c960fb..8d2a4c58a28 100644 --- a/lightclient/src/lib.rs +++ b/lightclient/src/lib.rs @@ -59,6 +59,13 @@ pub enum LightClientRpcError { #[error("RPC Error: {0}.")] pub struct JsonRpcError(Box); +impl JsonRpcError { + /// Attempt to deserialize this error into some type. + pub fn try_deserialize<'a, T: serde::de::Deserialize<'a>>(&'a self) -> Result { + serde_json::from_str(self.0.get()) + } +} + /// This represents a single light client connection to the network. Instantiate /// it with [`LightClient::relay_chain()`] to communicate with a relay chain, and /// then call [`LightClient::parachain()`] to establish connections to parachains. diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 876b374106b..047338a6e5f 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -15,7 +15,7 @@ description = "Make RPC calls to Substrate based nodes" keywords = ["parity", "subxt", "rpcs"] [features] -default = ["jsonrpsee", "native"] +default = ["jsonrpsee", "native", "unstable-light-client"] subxt-core = ["dep:subxt-core"] jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] diff --git a/rpcs/src/client/jsonrpsee_impl.rs b/rpcs/src/client/jsonrpsee_impl.rs index dd5e864d675..dc7d40dd334 100644 --- a/rpcs/src/client/jsonrpsee_impl.rs +++ b/rpcs/src/client/jsonrpsee_impl.rs @@ -7,7 +7,7 @@ use crate::Error; use futures::stream::{StreamExt, TryStreamExt}; use jsonrpsee::{ core::{ - client::{Client, ClientT, SubscriptionClientT, SubscriptionKind}, + client::{Error as JsonrpseeError, Client, ClientT, SubscriptionClientT, SubscriptionKind}, traits::ToRpcParams, }, types::SubscriptionId, @@ -31,7 +31,7 @@ impl RpcClientT for Client { Box::pin(async move { let res = ClientT::request(self, method, Params(params)) .await - .map_err(|e| Error::Client(Box::new(e)))?; + .map_err(error_to_rpc_error)?; Ok(res) }) } @@ -50,7 +50,7 @@ impl RpcClientT for Client { unsub, ) .await - .map_err(|e| Error::Client(Box::new(e)))?; + .map_err(error_to_rpc_error)?; let id = match stream.kind() { SubscriptionKind::Subscription(SubscriptionId::Str(id)) => { @@ -66,3 +66,21 @@ impl RpcClientT for Client { }) } } + +/// Convert a JsonrpseeError into the RPC error in this crate. +/// The main reason for this is to capture user errors so that +/// they can be represented/handled without casting. +fn error_to_rpc_error(error: JsonrpseeError) -> Error { + match error { + JsonrpseeError::Call(e) => { + Error::User(crate::UserError { + code: e.code(), + message: e.message().to_owned(), + data: e.data().map(|d| d.to_owned()) + }) + }, + e => { + Error::Client(Box::new(e)) + } + } +} \ No newline at end of file diff --git a/rpcs/src/client/lightclient_impl.rs b/rpcs/src/client/lightclient_impl.rs index b6a736450f5..21ed7437aff 100644 --- a/rpcs/src/client/lightclient_impl.rs +++ b/rpcs/src/client/lightclient_impl.rs @@ -3,7 +3,7 @@ // see LICENSE for license details. use super::{RawRpcFuture, RawRpcSubscription, RpcClientT}; -use crate::error::RpcError; +use crate::Error; use futures::stream::{StreamExt, TryStreamExt}; use serde_json::value::RawValue; use subxt_lightclient::{LightClientRpc, LightClientRpcError}; @@ -36,7 +36,7 @@ impl RpcClientT for LightClientRpc { let id = Some(sub.id().to_owned()); let stream = sub - .map_err(|e| RpcError::ClientError(Box::new(e))) + .map_err(|e| Error::Client(Box::new(e))) .boxed(); Ok(RawRpcSubscription { id, stream }) @@ -44,10 +44,19 @@ impl RpcClientT for LightClientRpc { } } -fn lc_err_to_rpc_err(err: LightClientRpcError) -> RpcError { +fn lc_err_to_rpc_err(err: LightClientRpcError) -> Error { match err { - LightClientRpcError::JsonRpcError(e) => RpcError::ClientError(Box::new(e)), - LightClientRpcError::SmoldotError(e) => RpcError::ClientError(Box::new(e)), - LightClientRpcError::BackgroundTaskDropped => RpcError::ClientError(Box::new("Smoldot background task was dropped")), + LightClientRpcError::JsonRpcError(e) => { + // If the error is a typical user error, report it as such, else + // just wrap the error into a ClientError. + let Ok(user_error) = e.try_deserialize() else { + return Error::Client(Box::::from(e)) + }; + Error::User(user_error) + }, + LightClientRpcError::SmoldotError(e) => Error::Client(Box::::from(e)), + LightClientRpcError::BackgroundTaskDropped => Error::Client(Box::::from("Smoldot background task was dropped")), } -} \ No newline at end of file +} + +type CoreError = dyn core::error::Error + Send + Sync + 'static; \ No newline at end of file diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs new file mode 100644 index 00000000000..104cf37924b --- /dev/null +++ b/rpcs/src/client/mock_rpc_client.rs @@ -0,0 +1,148 @@ +// Copyright 2019-2025 Parity Technologies (UK) Ltd. +// This file is dual-licensed as Apache-2.0 or GPL-3.0. +// see LICENSE for license details. + +//! This module exposes a [`MockRpcClient`], which is useful for testing. + +use super::{RpcClientT, RawRpcFuture, RawRpcSubscription}; +use crate::Error; +use core::future::Future; +use serde_json::value::RawValue; + +type MethodHandlerFn = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; +type SubscriptionHandlerFn = Box>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; + +/// A mock RPC client that responds programmatically to requests. +/// Useful for testing. +pub struct MockRpcClient { + method_handler: MethodHandlerFn, + subscription_handler: SubscriptionHandlerFn +} + +impl MockRpcClient { + /// Create a [`MockRpcClient`] by providing a function to handle method calls + /// and a function to handle subscription calls. + pub fn from_handlers(method_handler: M, subscription_handler: S) -> MockRpcClient + where + M: IntoMethodHandler, + S: IntoSubscriptionHandler, + { + MockRpcClient { + method_handler: method_handler.into_method_handler(), + subscription_handler: subscription_handler.into_subscription_handler() + } + } +} + +impl RpcClientT for MockRpcClient { + fn request_raw<'a>( + &'a self, + method: &'a str, + params: Option>, + ) -> RawRpcFuture<'a, Box> { + (self.method_handler)(method, params) + } + + fn subscribe_raw<'a>( + &'a self, + sub: &'a str, + params: Option>, + unsub: &'a str, + ) -> RawRpcFuture<'a, RawRpcSubscription> { + (self.subscription_handler)(sub, params, unsub) + } +} + +/// Return responses wrapped in this to have them serialized to JSON. +pub struct Json(T); + +/// Anything that can be converted into a valid handler response implements this. +pub trait IntoHandlerResponse { + /// Convert self into a handler response. + fn into_handler_response(self) -> Result, Error>; +} + +impl IntoHandlerResponse for Result { + fn into_handler_response(self) -> Result, Error> { + self.and_then(|val| serialize_to_raw_value(&val)) + } +} + +impl IntoHandlerResponse for Box { + fn into_handler_response(self) -> Result, Error> { + Ok(self) + } +} + +impl IntoHandlerResponse for serde_json::Value { + fn into_handler_response(self) -> Result, Error> { + serialize_to_raw_value(&self) + } +} + +impl IntoHandlerResponse for Json { + fn into_handler_response(self) -> Result, Error> { + serialize_to_raw_value(&self.0) + } +} + +fn serialize_to_raw_value(val: &T) -> Result, Error> { + let res = serde_json::to_string(val).map_err(Error::Deserialization)?; + let raw_value = RawValue::from_string(res).map_err(Error::Deserialization)?; + Ok(raw_value) +} + +/// Anything that is a valid method handler implements this trait. +pub trait IntoMethodHandler { + /// Convert self into a method handler function. + fn into_method_handler(self) -> MethodHandlerFn; +} + +enum SyncMethodHandler {} +impl IntoMethodHandler for F +where + F: Fn(&str, Option>) -> R + Send + Sync + 'static, + R: IntoHandlerResponse + Send + 'static, +{ + fn into_method_handler(self) -> MethodHandlerFn { + Box::new(move |method: &str, params: Option>| { + let res = self(method, params); + Box::pin(async move { res.into_handler_response() }) + }) + } +} + +enum AsyncMethodHandler {} +impl IntoMethodHandler for F +where + F: Fn(&str, Option>) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + R: IntoHandlerResponse + Send + 'static, +{ + fn into_method_handler(self) -> MethodHandlerFn { + Box::new(move |method: &str, params: Option>| { + let fut = self(method, params); + Box::pin(async move { fut.await.into_handler_response() }) + }) + } +} + +/// Anything that is a valid subscription handler implements this trait. +pub trait IntoSubscriptionHandler { + /// Convert self into a subscription handler function. + fn into_subscription_handler(self) -> SubscriptionHandlerFn; +} + +enum SyncSubscriptionHandler {} +impl IntoMethodHandler for F +where + F: Fn(&str, Option>) -> R + Send + Sync + 'static, + R: IntoHandlerResponse + Send + 'static, +{ + fn into_method_handler(self) -> MethodHandlerFn { + Box::new(move |method: &str, params: Option>| { + let res = self(method, params); + Box::pin(async move { res.into_handler_response() }) + }) + } +} \ No newline at end of file diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index c3804999131..4f9ffe6a346 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -63,7 +63,7 @@ crate::macros::cfg_jsonrpsee! { crate::macros::cfg_unstable_light_client! { mod lightclient_impl; - pub use lightclient_impl::LightClientRpc as LightClientRpcClient; + pub use subxt_lightclient::LightClientRpc as LightClientRpcClient; } crate::macros::cfg_reconnecting_rpc_client! { @@ -71,6 +71,11 @@ crate::macros::cfg_reconnecting_rpc_client! { pub use reconnecting_rpc_client::RpcClient as ReconnectingRpcClient; } +#[cfg(test)] +pub mod mock_rpc_client; +#[cfg(test)] +pub use mock_rpc_client::MockRpcClient; + mod rpc_client; mod rpc_client_t; diff --git a/rpcs/src/client/reconnecting_rpc_client/mod.rs b/rpcs/src/client/reconnecting_rpc_client/mod.rs index 8986561f5c8..c2c34b1aaec 100644 --- a/rpcs/src/client/reconnecting_rpc_client/mod.rs +++ b/rpcs/src/client/reconnecting_rpc_client/mod.rs @@ -427,13 +427,7 @@ impl RpcClientT for RpcClient { async { self.request(method.to_string(), params) .await - .map_err(|e| match e { - Error::DisconnectedWillReconnect(e) => { - SubxtRpcError::DisconnectedWillReconnect(e.to_string()) - } - Error::Dropped => SubxtRpcError::Client(Box::new(e)), - Error::RpcError(e) => SubxtRpcError::Client(Box::new(e)), - }) + .map_err(error_to_rpc_error) } .boxed() } @@ -448,7 +442,7 @@ impl RpcClientT for RpcClient { let sub = self .subscribe(sub.to_string(), params, unsub.to_string()) .await - .map_err(|e| SubxtRpcError::Client(Box::new(e)))?; + .map_err(error_to_rpc_error)?; let id = match sub.id() { SubscriptionId::Num(n) => n.to_string(), @@ -471,6 +465,27 @@ impl RpcClientT for RpcClient { } } +/// Convert a reconnecting client Error into the RPC error in this crate. +/// The main reason for this is to capture user errors so that +/// they can be represented/handled without casting. +fn error_to_rpc_error(error: Error) -> SubxtRpcError { + match error { + Error::DisconnectedWillReconnect(reason) => { + SubxtRpcError::DisconnectedWillReconnect(reason.to_string()) + }, + Error::RpcError(RpcError::Call(e)) => { + SubxtRpcError::User(crate::UserError { + code: e.code(), + message: e.message().to_owned(), + data: e.data().map(|d| d.to_owned()) + }) + }, + e => { + SubxtRpcError::Client(Box::new(e)) + } + } +} + async fn background_task

( mut client: Arc, mut rx: UnboundedReceiver, diff --git a/rpcs/src/client/rpc_client_t.rs b/rpcs/src/client/rpc_client_t.rs index 66075fd688a..697331ca439 100644 --- a/rpcs/src/client/rpc_client_t.rs +++ b/rpcs/src/client/rpc_client_t.rs @@ -54,7 +54,7 @@ pub trait RpcClientT: Send + Sync + 'static { } /// A boxed future that is returned from the [`RpcClientT`] methods. -pub type RawRpcFuture<'a, T, E = Error> = Pin> + Send + 'a>>; +pub type RawRpcFuture<'a, T> = Pin> + Send + 'a>>; /// The RPC subscription returned from [`RpcClientT`]'s `subscription` method. pub struct RawRpcSubscription { diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 3aa287a3cd8..a71cc6b0acc 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -60,6 +60,9 @@ mod impl_config { #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum Error { + /// An error which indicates a user fault. + #[error("User error: {0}")] + User(UserError), // Dev note: We need the error to be safely sent between threads // for `subscribe_to_block_headers_filling_in_gaps` and friends. /// An error coming from the underlying RPC Client. @@ -81,3 +84,40 @@ pub enum Error { #[error("RPC error: insecure URL: {0}")] InsecureUrl(String), } + +impl Error { + /// Is the error the `DisconnectedWillReconnect` variant? This should be true + /// only if the underlying `RpcClient` implementation was disconnected and is + /// automatically reconnecting behind the scenes. + pub fn is_disconnected_will_reconnect(&self) -> bool { + matches!(self, Error::DisconnectedWillReconnect(_)) + } +} + +/// This error tends to be returned when the user made an RPC call with +/// invalid parameters. Implementations of [`RpcClientT`] should turn any such +/// errors into this, so that they can be handled appropriately. By contrast, +/// [`Error::Client`] is emitted when the underlying RPC Client implementation +/// has some problem that isn't user specific (eg network issue or similar). +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct UserError { + /// Code + pub code: i32, + /// Message + pub message: String, + /// Optional data + pub data: Option>, +} + +impl core::fmt::Display for UserError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{} ({})", &self.message, &self.code) + } +} + +#[cfg(test)] +mod test { + + +} \ No newline at end of file diff --git a/subxt/src/backend/chain_head/mod.rs b/subxt/src/backend/chain_head/mod.rs index 4f78d2882eb..7349c0af423 100644 --- a/subxt/src/backend/chain_head/mod.rs +++ b/subxt/src/backend/chain_head/mod.rs @@ -362,7 +362,7 @@ impl Backend for ChainHeadBackend { let status = self.methods.chainhead_v1_body(&sub_id, at).await?; let operation_id = match status { MethodResponse::LimitReached => { - return Err(RpcError::request_rejected("limit reached").into()) + return Err(RpcError::LimitReached.into()) } MethodResponse::Started(s) => s.operation_id, }; @@ -722,7 +722,7 @@ impl Backend for ChainHeadBackend { .await?; let operation_id = match status { MethodResponse::LimitReached => { - return Err(RpcError::request_rejected("limit reached").into()) + return Err(RpcError::LimitReached.into()) } MethodResponse::Started(s) => s.operation_id, }; diff --git a/subxt/src/backend/chain_head/storage_items.rs b/subxt/src/backend/chain_head/storage_items.rs index 4194ceaf6a6..f140cefffb1 100644 --- a/subxt/src/backend/chain_head/storage_items.rs +++ b/subxt/src/backend/chain_head/storage_items.rs @@ -46,7 +46,7 @@ impl StorageItems { .await?; let operation_id: Arc = match status { MethodResponse::LimitReached => { - return Err(RpcError::request_rejected("limit reached").into()) + return Err(RpcError::LimitReached.into()) } MethodResponse::Started(s) => s.operation_id.into(), }; diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index ebe3a5c0ced..6af85962c6f 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -992,6 +992,10 @@ mod test { }) } + fn limit_reached() -> MethodResponse { + MethodResponse::LimitReached + } + fn storage_done(id: &str) -> FollowEvent { FollowEvent::OperationStorageDone(OperationId { operation_id: id.to_owned(), @@ -1302,10 +1306,6 @@ mod test { let hash = random_hash(); let mock_data = vec![ - ( - "chainSpec_v1_genesisHash", - Message::Single(Err::(subxt_rpcs::Error::Client("Error".into()))), - ), ( "chainSpec_v1_genesisHash", Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( @@ -1344,7 +1344,7 @@ mod test { ), ( "method_response", - Message::Single(Err(subxt_rpcs::Error::Client("stale id".into()))), + Message::Single(Ok(limit_reached())), ), ( "method_response", @@ -1384,7 +1384,7 @@ mod test { let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); let key: String = rpc_params.sequence().next().unwrap(); if key == *"ID1" { - return Err(subxt_rpcs::Error::Client("stale id".into())); + return Message::Single(Ok(limit_reached())); } else { subscription_expired .swap(false, std::sync::atomic::Ordering::SeqCst); diff --git a/subxt/src/backend/utils.rs b/subxt/src/backend/utils.rs index b7b003c48ef..47356f672eb 100644 --- a/subxt/src/backend/utils.rs +++ b/subxt/src/backend/utils.rs @@ -118,14 +118,20 @@ where } // TODO: https://github.com/paritytech/subxt/issues/1567 - // This is a hack because if a reconnection occurs - // the order of pending calls is not guaranteed. + // This is a hack because, in the event of a disconnection, + // we may not get the correct subscription ID back on reconnecting. // - // Such that it's possible the a pending future completes - // before `chainHead_follow` is established with fresh - // subscription id. + // This is because we have a race between this future and the + // separate chainHead subscription, which runs in a different task. + // if this future is too quick, it'll be given back an old + // subscription ID from the chainHead subscription which has yet + // to reconnect and establish a new subscription ID. // - if e.is_rejected() && rejected_retries < REJECTED_MAX_RETRIES { + // In the event of a wrong subscription Id being used, we happen to + // hand back an `RpcError::LimitReached`, and so can retry when we + // specifically hit that error to see if we get a new subscription ID + // eventually. + if e.is_rpc_limit_reached() && rejected_retries < REJECTED_MAX_RETRIES { rejected_retries += 1; continue; } diff --git a/subxt/src/error/mod.rs b/subxt/src/error/mod.rs index f2e10d46a6d..1b1b4188073 100644 --- a/subxt/src/error/mod.rs +++ b/subxt/src/error/mod.rs @@ -138,8 +138,8 @@ impl Error { } /// Checks whether the error was caused by a RPC request being rejected. - pub fn is_rejected(&self) -> bool { - matches!(self, Error::Rpc(RpcError::RequestRejected(_))) + pub fn is_rpc_limit_reached(&self) -> bool { + matches!(self, Error::Rpc(RpcError::LimitReached)) } } @@ -153,22 +153,15 @@ pub enum RpcError { /// Error related to the RPC client. #[error("RPC error: {0}")] ClientError(#[from] subxt_rpcs::Error), - /// This error signals that the request was rejected for some reason. - /// The specific reason is provided. - #[error("RPC error: request rejected: {0}")] - RequestRejected(String), + /// This error signals that we got back a [`subxt_rpcs::methods::chain_head::MethodResponse::LimitReached`], + /// which is not technically an RPC error but is treated as an error in our own APIs. + #[error("RPC error: limit reached")] + LimitReached, /// The RPC subscription dropped. #[error("RPC error: subscription dropped.")] SubscriptionDropped, } -impl RpcError { - /// Create a `RequestRejected` error from anything that can be turned into a string. - pub fn request_rejected>(s: S) -> RpcError { - RpcError::RequestRejected(s.into()) - } -} - /// Block error #[derive(Clone, Debug, thiserror::Error)] #[non_exhaustive] From bb0096e93f8245762f68a156ef00bd9b3dccf8d3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 29 Jan 2025 18:00:25 +0000 Subject: [PATCH 07/32] MockRpcClient sync or async --- rpcs/Cargo.toml | 2 +- rpcs/src/client/mock_rpc_client.rs | 170 ++++++++++++++++++++++------- rpcs/src/client/mod.rs | 51 +-------- rpcs/src/client/rpc_client.rs | 4 +- 4 files changed, 138 insertions(+), 89 deletions(-) diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 047338a6e5f..876b374106b 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -15,7 +15,7 @@ description = "Make RPC calls to Substrate based nodes" keywords = ["parity", "subxt", "rpcs"] [features] -default = ["jsonrpsee", "native", "unstable-light-client"] +default = ["jsonrpsee", "native"] subxt-core = ["dep:subxt-core"] jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 104cf37924b..0ccf2ba87af 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -3,58 +3,100 @@ // see LICENSE for license details. //! This module exposes a [`MockRpcClient`], which is useful for testing. +//! +//! # Example +//! +//! ```rust +//! use subxt_rpcs::client::{ RpcClient, MockRpcClient }; +//! use subxt_rpcs::client::mock_rpc_client::Json; +//! +//! let state = vec![ +//! Json(1u8), +//! Json(2u8), +//! Json(3u8), +//! ]; +//! +//! // Define a mock client by providing some state (can be optional) +//! // and functions which intercept method and subscription calls and +//! // return something back. +//! let mock_client = MockRpcClient::new( +//! state, +//! |state: &mut Vec>, method, params| { +//! state.pop().unwrap() +//! }, +//! |state: &mut _, sub, params, unsub| { +//! vec![Json(1), Json(2), Json(3)] +//! } +//! ); +//! +//! // Build an RPC Client that can be used in Subxt or in conjunction with +//! // the RPC methods provided in this crate. +//! let rpc_client = RpcClient::new(mock_client); +//! ``` use super::{RpcClientT, RawRpcFuture, RawRpcSubscription}; use crate::Error; use core::future::Future; use serde_json::value::RawValue; +use std::sync::{Arc,Mutex}; -type MethodHandlerFn = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; -type SubscriptionHandlerFn = Box>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; +type MethodHandlerFn = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; +type SubscriptionHandlerFn = Box>, String) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; /// A mock RPC client that responds programmatically to requests. /// Useful for testing. -pub struct MockRpcClient { - method_handler: MethodHandlerFn, - subscription_handler: SubscriptionHandlerFn +pub struct MockRpcClient { + state: Arc>, + method_handler: MethodHandlerFn, + subscription_handler: SubscriptionHandlerFn } -impl MockRpcClient { +impl MockRpcClient { /// Create a [`MockRpcClient`] by providing a function to handle method calls /// and a function to handle subscription calls. - pub fn from_handlers(method_handler: M, subscription_handler: S) -> MockRpcClient - where - M: IntoMethodHandler, - S: IntoSubscriptionHandler, + pub fn new( + state: State, + method_handler: MethodHandler, + subscription_handler: SubscriptionHandler + ) -> MockRpcClient + where + MethodHandler: IntoMethodHandler, + SubscriptionHandler: IntoSubscriptionHandler, { MockRpcClient { + state: Arc::new(Mutex::new(state)), method_handler: method_handler.into_method_handler(), subscription_handler: subscription_handler.into_subscription_handler() } } } -impl RpcClientT for MockRpcClient { +impl RpcClientT for MockRpcClient { fn request_raw<'a>( &'a self, method: &'a str, params: Option>, ) -> RawRpcFuture<'a, Box> { - (self.method_handler)(method, params) + let mut s = self.state.lock().unwrap(); + (self.method_handler)(&mut *s, method.to_owned(), params) } - fn subscribe_raw<'a>( &'a self, sub: &'a str, params: Option>, unsub: &'a str, ) -> RawRpcFuture<'a, RawRpcSubscription> { - (self.subscription_handler)(sub, params, unsub) + let mut s = self.state.lock().unwrap(); + (self.subscription_handler)(&mut *s, sub.to_owned(), params, unsub.to_owned()) } } +// The below is all boilerplate to allow various types of functions, sync and async, +// and returning different types of arguments, are all able to be used as method and +// subscription handler functions. + /// Return responses wrapped in this to have them serialized to JSON. -pub struct Json(T); +pub struct Json(pub T); /// Anything that can be converted into a valid handler response implements this. pub trait IntoHandlerResponse { @@ -92,57 +134,105 @@ fn serialize_to_raw_value(val: &T) -> Result, Ok(raw_value) } +/// Anything that can be a response to a subscription handler implements this. +pub trait IntoSubscriptionResponse { + /// Convert self into a handler response. + fn into_subscription_response(self) -> Result; +} + +impl IntoSubscriptionResponse for Result { + fn into_subscription_response(self) -> Result { + self + } +} + +impl IntoSubscriptionResponse for Vec { + fn into_subscription_response(self) -> Result { + let iter = self.into_iter().map(|item| item.into_handler_response()); + Ok(RawRpcSubscription { + stream: Box::pin(futures::stream::iter(iter)), + id: None, + }) + } +} + +impl IntoSubscriptionResponse for [T; N] { + fn into_subscription_response(self) -> Result { + let iter = self.into_iter().map(|item| item.into_handler_response()); + Ok(RawRpcSubscription { + stream: Box::pin(futures::stream::iter(iter)), + id: None, + }) + } +} + /// Anything that is a valid method handler implements this trait. -pub trait IntoMethodHandler { +pub trait IntoMethodHandler { /// Convert self into a method handler function. - fn into_method_handler(self) -> MethodHandlerFn; + fn into_method_handler(self) -> MethodHandlerFn; } -enum SyncMethodHandler {} -impl IntoMethodHandler for F +impl IntoMethodHandler for F where - F: Fn(&str, Option>) -> R + Send + Sync + 'static, + F: Fn(&mut State, String, Option>) -> R + Send + Sync + 'static, R: IntoHandlerResponse + Send + 'static, { - fn into_method_handler(self) -> MethodHandlerFn { - Box::new(move |method: &str, params: Option>| { - let res = self(method, params); + fn into_method_handler(self) -> MethodHandlerFn { + Box::new(move |state: &mut State, method: String, params: Option>| { + let res = self(state, method, params); Box::pin(async move { res.into_handler_response() }) }) } } -enum AsyncMethodHandler {} -impl IntoMethodHandler for F +impl IntoMethodHandler for F where - F: Fn(&str, Option>) -> Fut + Send + Sync + 'static, + F: Fn(&mut State, String, Option>) -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, R: IntoHandlerResponse + Send + 'static, { - fn into_method_handler(self) -> MethodHandlerFn { - Box::new(move |method: &str, params: Option>| { - let fut = self(method, params); + fn into_method_handler(self) -> MethodHandlerFn { + Box::new(move |state: &mut State, method: String, params: Option>| { + let fut = self(state, method, params); Box::pin(async move { fut.await.into_handler_response() }) }) } } /// Anything that is a valid subscription handler implements this trait. -pub trait IntoSubscriptionHandler { +pub trait IntoSubscriptionHandler { /// Convert self into a subscription handler function. - fn into_subscription_handler(self) -> SubscriptionHandlerFn; + fn into_subscription_handler(self) -> SubscriptionHandlerFn; } -enum SyncSubscriptionHandler {} -impl IntoMethodHandler for F +impl IntoSubscriptionHandler for F where - F: Fn(&str, Option>) -> R + Send + Sync + 'static, - R: IntoHandlerResponse + Send + 'static, + F: Fn(&mut State, String, Option>, String) -> R + Send + Sync + 'static, + R: IntoSubscriptionResponse + Send + 'static, { - fn into_method_handler(self) -> MethodHandlerFn { - Box::new(move |method: &str, params: Option>| { - let res = self(method, params); - Box::pin(async move { res.into_handler_response() }) + fn into_subscription_handler(self) -> SubscriptionHandlerFn { + Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { + let res = self(state, sub, params, unsub); + Box::pin(async move { res.into_subscription_response() }) }) } -} \ No newline at end of file +} + +impl IntoSubscriptionHandler for F +where + F: Fn(&mut State, String, Option>, String) -> Fut + Send + Sync + 'static, + Fut: Future + Send + 'static, + R: IntoSubscriptionResponse + Send + 'static, +{ + fn into_subscription_handler(self) -> SubscriptionHandlerFn { + Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { + let fut = self(state, sub, params, unsub); + Box::pin(async move { fut.await.into_subscription_response() }) + }) + } +} + +#[doc(hidden)] +pub enum SyncMarker {} +#[doc(hidden)] +pub enum AsyncMarker {} \ No newline at end of file diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index 4f9ffe6a346..0d6f4000c66 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -4,10 +4,9 @@ //! RPC types and client for interacting with a substrate node. //! -//! These are used behind the scenes by Subxt backend implementations, for -//! example [`crate::backend::legacy::LegacyBackend`]. If you need an RPC client, -//! then you can manually instantiate one, and then hand it to Subxt if you'd like -//! to re-use it for the Subxt connection. +//! An RPC client is instantiated and then used to create some methods, for instance +//! [`crate::methods::ChainHeadRpcMethods`], which defines the calls that can be made with it. +//! The core RPC client bits are: //! //! - [`RpcClientT`] is the underlying dynamic RPC implementation. This provides //! the low level [`RpcClientT::request_raw`] and [`RpcClientT::subscribe_raw`] @@ -15,46 +14,8 @@ //! - [`RpcClient`] is the higher level wrapper around this, offering //! the [`RpcClient::request`] and [`RpcClient::subscribe`] methods. //! -//! # Example -//! -//! Fetching the genesis hash. -//! -//! ```no_run -//! # #[tokio::main] -//! # async fn main() { -//! use subxt::{ -//! client::OnlineClient, -//! config::SubstrateConfig, -//! backend::rpc::RpcClient, -//! backend::legacy::LegacyRpcMethods, -//! }; -//! -//! // Instantiate a default RPC client pointing at some URL. -//! let rpc_client = RpcClient::from_url("ws://localhost:9944") -//! .await -//! .unwrap(); -//! -//! // Instantiate the legacy RPC interface, providing an appropriate -//! // config so that it uses the correct types for your chain. -//! let rpc_methods = LegacyRpcMethods::::new(rpc_client.clone()); -//! -//! // Use it to make RPC calls, here using the legacy genesis_hash method. -//! let genesis_hash = rpc_methods -//! .genesis_hash() -//! .await -//! .unwrap(); -//! -//! println!("{genesis_hash}"); -//! -//! // Instantiate the Subxt interface using the same client and config if you -//! // want to reuse the same connection: -//! let client = OnlineClient::::from_rpc_client(rpc_client); -//! # } -//! ``` - -// Allow an `rpc.rs` file in the `rpc` folder to align better -// with other file names for their types. -#![allow(clippy::module_inception)] +//! We then expose implementations here (depending on which features are enabled) +//! which implement [`RpcClientT`] and can therefore be used to construct [`RpcClient`]s. crate::macros::cfg_jsonrpsee! { mod jsonrpsee_impl; @@ -71,9 +32,7 @@ crate::macros::cfg_reconnecting_rpc_client! { pub use reconnecting_rpc_client::RpcClient as ReconnectingRpcClient; } -#[cfg(test)] pub mod mock_rpc_client; -#[cfg(test)] pub use mock_rpc_client::MockRpcClient; mod rpc_client; diff --git a/rpcs/src/client/rpc_client.rs b/rpcs/src/client/rpc_client.rs index bfea732309b..e2f060c1193 100644 --- a/rpcs/src/client/rpc_client.rs +++ b/rpcs/src/client/rpc_client.rs @@ -108,7 +108,7 @@ impl std::ops::Deref for RpcClient { /// # Example /// /// ```rust -/// use subxt::backend::rpc::{ rpc_params, RpcParams }; +/// use subxt_rpcs::client::{ rpc_params, RpcParams }; /// /// // If you provide no params you get `None` back /// let params: RpcParams = rpc_params![]; @@ -140,7 +140,7 @@ pub use rpc_params; /// # Example /// /// ```rust -/// use subxt::backend::rpc::RpcParams; +/// use subxt_rpcs::client::RpcParams; /// /// let mut params = RpcParams::new(); /// params.push(1).unwrap(); From a371df4cf7c1a77c8502be38bf0d10d943db7510 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Wed, 29 Jan 2025 18:52:19 +0000 Subject: [PATCH 08/32] MockRpcClient only async but better type inference --- rpcs/src/client/mock_rpc_client.rs | 115 +++++++---------------------- 1 file changed, 28 insertions(+), 87 deletions(-) diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 0ccf2ba87af..d6262df733a 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -18,14 +18,19 @@ //! //! // Define a mock client by providing some state (can be optional) //! // and functions which intercept method and subscription calls and -//! // return something back. +//! // returns something back. //! let mock_client = MockRpcClient::new( //! state, -//! |state: &mut Vec>, method, params| { -//! state.pop().unwrap() +//! |state, method, params| { +//! // We'll panic if an RPC method is called more than 3 times: +//! let val = state.pop().unwrap(); +//! async move { val } //! }, -//! |state: &mut _, sub, params, unsub| { -//! vec![Json(1), Json(2), Json(3)] +//! |state, sub, params, unsub| { +//! // Arrays, vecs or an RpcSubscription can be returned here to +//! // signal the set of values to be handed back on a subscription. +//! let vals = vec![Json(1), Json(2), Json(3)]; +//! async move { vals } //! } //! ); //! @@ -52,21 +57,32 @@ pub struct MockRpcClient { } impl MockRpcClient { - /// Create a [`MockRpcClient`] by providing a function to handle method calls - /// and a function to handle subscription calls. - pub fn new( + /// Create a [`MockRpcClient`] by providing some state (which will be mutably available + /// to each function), a function to handle method calls, and a function to handle + /// subscription calls. + pub fn new( state: State, method_handler: MethodHandler, subscription_handler: SubscriptionHandler ) -> MockRpcClient where - MethodHandler: IntoMethodHandler, - SubscriptionHandler: IntoSubscriptionHandler, + MethodHandler: Fn(&mut State, String, Option>) -> MFut + Send + Sync + 'static, + MFut: Future + Send + 'static, + MRes: IntoHandlerResponse, + SubscriptionHandler: Fn(&mut State, String, Option>, String) -> SFut + Send + Sync + 'static, + SFut: Future + Send + 'static, + SRes: IntoSubscriptionResponse, { MockRpcClient { state: Arc::new(Mutex::new(state)), - method_handler: method_handler.into_method_handler(), - subscription_handler: subscription_handler.into_subscription_handler() + method_handler: Box::new(move |state: &mut State, method: String, params: Option>| { + let fut = method_handler(state, method, params); + Box::pin(async move { fut.await.into_handler_response() }) + }), + subscription_handler: Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { + let fut = subscription_handler(state, sub, params, unsub); + Box::pin(async move { fut.await.into_subscription_response() }) + }) } } } @@ -91,10 +107,6 @@ impl RpcClientT for MockRpcClient { } } -// The below is all boilerplate to allow various types of functions, sync and async, -// and returning different types of arguments, are all able to be used as method and -// subscription handler functions. - /// Return responses wrapped in this to have them serialized to JSON. pub struct Json(pub T); @@ -165,74 +177,3 @@ impl IntoSubscriptionR }) } } - -/// Anything that is a valid method handler implements this trait. -pub trait IntoMethodHandler { - /// Convert self into a method handler function. - fn into_method_handler(self) -> MethodHandlerFn; -} - -impl IntoMethodHandler for F -where - F: Fn(&mut State, String, Option>) -> R + Send + Sync + 'static, - R: IntoHandlerResponse + Send + 'static, -{ - fn into_method_handler(self) -> MethodHandlerFn { - Box::new(move |state: &mut State, method: String, params: Option>| { - let res = self(state, method, params); - Box::pin(async move { res.into_handler_response() }) - }) - } -} - -impl IntoMethodHandler for F -where - F: Fn(&mut State, String, Option>) -> Fut + Send + Sync + 'static, - Fut: Future + Send + 'static, - R: IntoHandlerResponse + Send + 'static, -{ - fn into_method_handler(self) -> MethodHandlerFn { - Box::new(move |state: &mut State, method: String, params: Option>| { - let fut = self(state, method, params); - Box::pin(async move { fut.await.into_handler_response() }) - }) - } -} - -/// Anything that is a valid subscription handler implements this trait. -pub trait IntoSubscriptionHandler { - /// Convert self into a subscription handler function. - fn into_subscription_handler(self) -> SubscriptionHandlerFn; -} - -impl IntoSubscriptionHandler for F -where - F: Fn(&mut State, String, Option>, String) -> R + Send + Sync + 'static, - R: IntoSubscriptionResponse + Send + 'static, -{ - fn into_subscription_handler(self) -> SubscriptionHandlerFn { - Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { - let res = self(state, sub, params, unsub); - Box::pin(async move { res.into_subscription_response() }) - }) - } -} - -impl IntoSubscriptionHandler for F -where - F: Fn(&mut State, String, Option>, String) -> Fut + Send + Sync + 'static, - Fut: Future + Send + 'static, - R: IntoSubscriptionResponse + Send + 'static, -{ - fn into_subscription_handler(self) -> SubscriptionHandlerFn { - Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { - let fut = self(state, sub, params, unsub); - Box::pin(async move { fut.await.into_subscription_response() }) - }) - } -} - -#[doc(hidden)] -pub enum SyncMarker {} -#[doc(hidden)] -pub enum AsyncMarker {} \ No newline at end of file From a92ebcda5b34dce3daa2039f51f1db66100fd92a Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 3 Feb 2025 11:50:26 +0000 Subject: [PATCH 09/32] WIP MockRpcClient FnMuts and some test updates to use it --- rpcs/Cargo.toml | 13 +- rpcs/src/client/mock_rpc_client.rs | 327 ++++++++++++++++++++++++----- rpcs/src/client/mod.rs | 6 +- rpcs/src/lib.rs | 15 +- rpcs/src/macros.rs | 34 +-- subxt/Cargo.toml | 3 +- subxt/src/backend/mod.rs | 313 ++++++++++++++------------- 7 files changed, 469 insertions(+), 242 deletions(-) diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 876b374106b..9204f3f0eba 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -15,11 +15,15 @@ description = "Make RPC calls to Substrate based nodes" keywords = ["parity", "subxt", "rpcs"] [features] -default = ["jsonrpsee", "native"] +default = ["jsonrpsee", "native", "mock-rpc-client"] subxt-core = ["dep:subxt-core"] jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] -unstable-light-client = ["dep:subxt-lightclient"] + +unstable-light-client = [ + "dep:subxt-lightclient" +] + reconnecting-rpc-client = [ "jsonrpsee", "dep:finito", @@ -27,6 +31,11 @@ reconnecting-rpc-client = [ "tokio/sync", ] +mock-rpc-client = [ + "dep:tokio", + "tokio/sync", +] + # Enable this for native (ie non web/wasm builds). # Exactly 1 of "web" and "native" is expected. native = [ diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index d6262df733a..53729329ccd 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -19,20 +19,17 @@ //! // Define a mock client by providing some state (can be optional) //! // and functions which intercept method and subscription calls and //! // returns something back. -//! let mock_client = MockRpcClient::new( -//! state, -//! |state, method, params| { +//! let mock_client = MockRpcClient::>::builder() +//! .method_handler("foo", |state, params| async move { //! // We'll panic if an RPC method is called more than 3 times: -//! let val = state.pop().unwrap(); -//! async move { val } -//! }, -//! |state, sub, params, unsub| { +//! state.get().await.pop().unwrap() +//! }) +//! .subscription_handler("bar", |state, params, unsub| async move { //! // Arrays, vecs or an RpcSubscription can be returned here to //! // signal the set of values to be handed back on a subscription. -//! let vals = vec![Json(1), Json(2), Json(3)]; -//! async move { vals } -//! } -//! ); +//! vec![Json(1), Json(2), Json(3)] +//! }) +//! .build(state); //! //! // Build an RPC Client that can be used in Subxt or in conjunction with //! // the RPC methods provided in this crate. @@ -40,61 +37,145 @@ //! ``` use super::{RpcClientT, RawRpcFuture, RawRpcSubscription}; -use crate::Error; +use crate::{Error, UserError}; use core::future::Future; +use futures::StreamExt; use serde_json::value::RawValue; -use std::sync::{Arc,Mutex}; +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; -type MethodHandlerFn = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; -type SubscriptionHandlerFn = Box>, String) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; +type MethodHandlerFn = Box, &str, Option>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; +type SubscriptionHandlerFn = Box, &str, Option>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; -/// A mock RPC client that responds programmatically to requests. -/// Useful for testing. -pub struct MockRpcClient { - state: Arc>, - method_handler: MethodHandlerFn, - subscription_handler: SubscriptionHandlerFn +/// A builder to configure and build a new [`MockRpcClient`]. +pub struct MockRpcClientBuilder { + method_handlers: HashMap>, + subscription_handlers: HashMap>, + method_fallback: Option>, + subscription_fallback: Option> } -impl MockRpcClient { - /// Create a [`MockRpcClient`] by providing some state (which will be mutably available - /// to each function), a function to handle method calls, and a function to handle - /// subscription calls. - pub fn new( - state: State, - method_handler: MethodHandler, - subscription_handler: SubscriptionHandler - ) -> MockRpcClient +impl MockRpcClientBuilder { + fn new() -> Self { + MockRpcClientBuilder { + method_handlers: HashMap::new(), + subscription_handlers: HashMap::new(), + method_fallback: None, + subscription_fallback: None + } + } + + /// Add a handler for a specific RPC method. + pub fn method_handler(mut self, name: impl Into, mut f: MethodHandler) -> Self + where + MethodHandler: FnMut(StateHolder, Option>) -> MFut + Send + Sync + 'static, + MFut: Future + Send + 'static, + MRes: IntoHandlerResponse, + { + let handler: MethodHandlerFn = Box::new(move |state: StateHolder, _method: &str, params: Option>| { + let fut = f(state, params); + Box::pin(async move { fut.await.into_handler_response() }) + }); + self.method_handlers.insert(name.into(), handler); + self + } + + /// Add a fallback handler to handle any methods not handled by a specific handler. + pub fn method_fallback(mut self, mut f: MethodHandler) -> Self where - MethodHandler: Fn(&mut State, String, Option>) -> MFut + Send + Sync + 'static, + MethodHandler: FnMut(StateHolder, String, Option>) -> MFut + Send + Sync + 'static, MFut: Future + Send + 'static, MRes: IntoHandlerResponse, - SubscriptionHandler: Fn(&mut State, String, Option>, String) -> SFut + Send + Sync + 'static, + { + let handler: MethodHandlerFn = Box::new(move |state: StateHolder, method: &str, params: Option>| { + let fut = f(state, method.to_owned(), params); + Box::pin(async move { fut.await.into_handler_response() }) + }); + self.method_fallback = Some(handler); + self + } + + /// Add a handler for a specific RPC subscription. + pub fn subscription_handler(mut self, name: impl Into, mut f: SubscriptionHandler) -> Self + where + SubscriptionHandler: FnMut(StateHolder, Option>, String) -> SFut + Send + Sync + 'static, SFut: Future + Send + 'static, SRes: IntoSubscriptionResponse, { - MockRpcClient { - state: Arc::new(Mutex::new(state)), - method_handler: Box::new(move |state: &mut State, method: String, params: Option>| { - let fut = method_handler(state, method, params); - Box::pin(async move { fut.await.into_handler_response() }) - }), - subscription_handler: Box::new(move |state: &mut State, sub: String, params: Option>, unsub: String| { - let fut = subscription_handler(state, sub, params, unsub); - Box::pin(async move { fut.await.into_subscription_response() }) - }) + let handler: SubscriptionHandlerFn = Box::new(move |state: StateHolder, _sub: &str, params: Option>, unsub: &str| { + let fut = f(state, params, unsub.to_owned()); + Box::pin(async move { fut.await.into_subscription_response() }) + }); + self.subscription_handlers.insert(name.into(), handler); + self + } + + /// Add a fallback handler to handle any subscriptions not handled by a specific handler. + pub fn subscription_fallback(mut self, mut f: SubscriptionHandler) -> Self + where + SubscriptionHandler: FnMut(StateHolder, String, Option>, String) -> SFut + Send + Sync + 'static, + SFut: Future + Send + 'static, + SRes: IntoSubscriptionResponse, + { + let handler: SubscriptionHandlerFn = Box::new(move |state: StateHolder, sub: &str, params: Option>, unsub: &str| { + let fut = f(state, sub.to_owned(), params, unsub.to_owned()); + Box::pin(async move { fut.await.into_subscription_response() }) + }); + self.subscription_fallback = Some(handler); + self + } + + /// Construct a [`MockRpcClient`] given some state which will be mutably available to each of the handlers. + pub fn build(self, state: State) -> MockRpcClient { + MockRpcClient { + state: Arc::new(tokio::sync::Mutex::new(state)), + method_handlers: Arc::new(Mutex::new(self.method_handlers)), + subscription_handlers: Arc::new(Mutex::new(self.subscription_handlers)), + method_fallback: self.method_fallback.map(|f| Arc::new(Mutex::new(f))), + subscription_fallback: self.subscription_fallback.map(|f| Arc::new(Mutex::new(f))), } } } +/// A mock RPC client that responds programmatically to requests. +/// Useful for testing. +pub struct MockRpcClient { + // State is accessed inside async functions and may be held across await points, + // so we use a tokio Mutex for it. + state: Arc>, + // These are all accessed for just long enough to call the method. The method + // returns a future, but the method call itself isn't held for long. + method_handlers: Arc>>>, + subscription_handlers: Arc>>>, + method_fallback: Option>>>, + subscription_fallback: Option>>>, +} + +impl MockRpcClient { + /// Construct a new [`MockRpcClient`] + pub fn builder() -> MockRpcClientBuilder { + MockRpcClientBuilder::new() + } +} + impl RpcClientT for MockRpcClient { fn request_raw<'a>( &'a self, method: &'a str, params: Option>, ) -> RawRpcFuture<'a, Box> { - let mut s = self.state.lock().unwrap(); - (self.method_handler)(&mut *s, method.to_owned(), params) + let mut handlers = self.method_handlers.lock().unwrap(); + if let Some(handler) = handlers.get_mut(method) { + // Call a specific handler for the method if one is found. + handler(StateHolder(self.state.clone()), method, params) + } else if let Some(handler) = &self.method_fallback { + // Else, call the fallback handler if that exists. + let mut handler = handler.lock().unwrap(); + handler(StateHolder(self.state.clone()), method, params) + } else { + // Else, method not found. + Box::pin(async move { Err(UserError::method_not_found().into()) }) + } } fn subscribe_raw<'a>( &'a self, @@ -102,23 +183,86 @@ impl RpcClientT for MockRpcClient { params: Option>, unsub: &'a str, ) -> RawRpcFuture<'a, RawRpcSubscription> { - let mut s = self.state.lock().unwrap(); - (self.subscription_handler)(&mut *s, sub.to_owned(), params, unsub.to_owned()) + let mut handlers = self.subscription_handlers.lock().unwrap(); + if let Some(handler) = handlers.get_mut(sub) { + // Call a specific handler for the method if one is found. + handler(StateHolder(self.state.clone()), sub, params, unsub) + } else if let Some(handler) = &self.subscription_fallback { + // Else, call the fallback handler if that exists. + let mut handler = handler.lock().unwrap(); + handler(StateHolder(self.state.clone()), sub, params, unsub) + } else { + // Else, method not found. + Box::pin(async move { Err(UserError::method_not_found().into()) }) + } + } +} + +/// State passed to each handler. +pub struct StateHolder(Arc>); + +/// A guard for state that is being accessed. +pub struct StateHolderGuard<'a, T>(tokio::sync::MutexGuard<'a, T>); + +impl StateHolder { + /// Get the inner state + pub async fn get(&self) -> StateHolderGuard { + StateHolderGuard(self.0.lock().await) + } + + /// Set the inner state to a new value, returning the old state. + pub async fn set(&self, new: T) -> T { + let mut guard = self.0.lock().await; + std::mem::replace(&mut *guard, new) + } + + /// Update the inner state, returning the old state. + pub async fn update T>(&self, f: F) -> T { + let mut guard = self.0.lock().await; + let new = f(&guard); + std::mem::replace(&mut *guard, new) + } +} + +impl <'a, T> core::ops::Deref for StateHolderGuard<'a, T> { + type Target = T; + fn deref(&self) -> &Self::Target { + &*self.0 + } +} +impl <'a, T> core::ops::DerefMut for StateHolderGuard<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 } } /// Return responses wrapped in this to have them serialized to JSON. pub struct Json(pub T); +impl Json { + /// Create a [`Json`] from some serializable value. + /// Useful when value types are heterogenous. + pub fn value_of(item: T) -> Self { + Json(serde_json::to_value(item).expect("item cannot be converted to a serde_json::Value")) + } +} + /// Anything that can be converted into a valid handler response implements this. pub trait IntoHandlerResponse { /// Convert self into a handler response. fn into_handler_response(self) -> Result, Error>; } -impl IntoHandlerResponse for Result { +impl IntoHandlerResponse for Result { + fn into_handler_response(self) -> Result, Error> { + self.and_then(|val| val.into_handler_response()) + } +} + +impl IntoHandlerResponse for Option { fn into_handler_response(self) -> Result, Error> { - self.and_then(|val| serialize_to_raw_value(&val)) + self.ok_or_else(|| UserError::method_not_found().into()) + .and_then(|val| val.into_handler_response()) } } @@ -152,6 +296,51 @@ pub trait IntoSubscriptionResponse { fn into_subscription_response(self) -> Result; } +// A tuple of a subscription plus some string is treated as a subscription with that string ID. +impl > IntoSubscriptionResponse for (T, S) { + fn into_subscription_response(self) -> Result { + self.0 + .into_subscription_response() + .map(|mut r| { + r.id = Some(self.1.into()); + r + }) + } +} + +impl IntoSubscriptionResponse for tokio::sync::mpsc::Receiver { + fn into_subscription_response(self) -> Result { + struct IntoStream(tokio::sync::mpsc::Receiver); + impl futures::Stream for IntoStream { + type Item = T; + fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { + self.0.poll_recv(cx) + } + } + + Ok(RawRpcSubscription { + stream: Box::pin(IntoStream(self).map(|item| item.into_handler_response())), + id: None, + }) + } +} +impl IntoSubscriptionResponse for tokio::sync::mpsc::UnboundedReceiver { + fn into_subscription_response(self) -> Result { + struct IntoStream(tokio::sync::mpsc::UnboundedReceiver); + impl futures::Stream for IntoStream { + type Item = T; + fn poll_next(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll> { + self.0.poll_recv(cx) + } + } + + Ok(RawRpcSubscription { + stream: Box::pin(IntoStream(self).map(|item| item.into_handler_response())), + id: None, + }) + } +} + impl IntoSubscriptionResponse for Result { fn into_subscription_response(self) -> Result { self @@ -168,6 +357,22 @@ impl IntoSubscriptionResponse for Vec< } } +impl IntoSubscriptionResponse for Option { + fn into_subscription_response(self) -> Result { + match self { + Some(sub) => { + sub.into_subscription_response() + }, + None => { + Ok(RawRpcSubscription { + stream: Box::pin(futures::stream::empty()), + id: None, + }) + } + } + } +} + impl IntoSubscriptionResponse for [T; N] { fn into_subscription_response(self) -> Result { let iter = self.into_iter().map(|item| item.into_handler_response()); @@ -177,3 +382,29 @@ impl IntoSubscriptionR }) } } + +/// Send the first items and then the second items back on a subscription; +/// If any one of the responses is an error, we'll return the error. +/// If one response has an ID and the other doesn't, we'll use that ID. +pub struct AndThen(pub A, pub B); + +impl IntoSubscriptionResponse for AndThen { + fn into_subscription_response(self) -> Result { + let a_responses = self.0.into_subscription_response(); + let b_responses = self.1.into_subscription_response(); + + match (a_responses, b_responses) { + (Err(a), _) => { + Err(a) + }, + (_, Err(b)) => { + Err(b) + }, + (Ok(mut a), Ok(b)) => { + a.stream = Box::pin(a.stream.chain(b.stream)); + a.id = a.id.or(b.id); + Ok(a) + } + } + } +} \ No newline at end of file diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index 0d6f4000c66..85553b290f4 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -32,8 +32,10 @@ crate::macros::cfg_reconnecting_rpc_client! { pub use reconnecting_rpc_client::RpcClient as ReconnectingRpcClient; } -pub mod mock_rpc_client; -pub use mock_rpc_client::MockRpcClient; +crate::macros::cfg_mock_rpc_client! { + pub mod mock_rpc_client; + pub use mock_rpc_client::MockRpcClient; +} mod rpc_client; mod rpc_client_t; diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index a71cc6b0acc..1e73cfe910e 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -62,7 +62,7 @@ mod impl_config { pub enum Error { /// An error which indicates a user fault. #[error("User error: {0}")] - User(UserError), + User(#[from] UserError), // Dev note: We need the error to be safely sent between threads // for `subscribe_to_block_headers_filling_in_gaps` and friends. /// An error coming from the underlying RPC Client. @@ -99,7 +99,7 @@ impl Error { /// errors into this, so that they can be handled appropriately. By contrast, /// [`Error::Client`] is emitted when the underlying RPC Client implementation /// has some problem that isn't user specific (eg network issue or similar). -#[derive(Debug, Clone, serde::Deserialize)] +#[derive(Debug, Clone, serde::Deserialize, thiserror::Error)] #[serde(deny_unknown_fields)] pub struct UserError { /// Code @@ -110,6 +110,17 @@ pub struct UserError { pub data: Option>, } +impl UserError { + /// Returns a standard JSON-RPC "method not found" error. + pub fn method_not_found() -> UserError { + UserError { + code: -32601, + message: "Method not found".to_owned(), + data: None + } + } +} + impl core::fmt::Display for UserError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{} ({})", &self.message, &self.code) diff --git a/rpcs/src/macros.rs b/rpcs/src/macros.rs index c8b1eb7ed0a..71f8eb5bb80 100644 --- a/rpcs/src/macros.rs +++ b/rpcs/src/macros.rs @@ -24,29 +24,6 @@ macro_rules! cfg_jsonrpsee { }; } -#[allow(unused)] -macro_rules! cfg_jsonrpsee_native { - ($($item:item)*) => { - $( - #[cfg(all(feature = "jsonrpsee", feature = "native"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "jsonrpsee", feature = "native"))))] - $item - )* - } -} - -#[allow(unused)] -macro_rules! cfg_jsonrpsee_web { - ($($item:item)*) => { - $( - #[cfg(all(feature = "jsonrpsee", feature = "web"))] - #[cfg_attr(docsrs, doc(cfg(all(feature = "jsonrpsee", feature = "web"))))] - $item - )* - } -} - -#[allow(unused)] macro_rules! cfg_reconnecting_rpc_client { ($($item:item)*) => { $( @@ -57,10 +34,13 @@ macro_rules! cfg_reconnecting_rpc_client { } } +macro_rules! cfg_mock_rpc_client { + ($($item:item)*) => { + crate::macros::cfg_feature!("mock-rpc-client", $($item)*); + }; +} + pub(crate) use { - cfg_feature, cfg_jsonrpsee, cfg_reconnecting_rpc_client, cfg_unstable_light_client, + cfg_feature, cfg_jsonrpsee, cfg_mock_rpc_client, cfg_reconnecting_rpc_client, cfg_unstable_light_client, }; -// Only used by light-client. -#[allow(unused)] -pub(crate) use {cfg_jsonrpsee_native, cfg_jsonrpsee_web}; diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index c57bdd5e3e1..fb8476b4fcf 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -110,7 +110,7 @@ subxt-macro = { workspace = true } subxt-core = { workspace = true, features = ["std"] } subxt-metadata = { workspace = true, features = ["std"] } subxt-lightclient = { workspace = true, optional = true, default-features = false } -subxt-rpcs = { workspace = true, default-features = false, features = ["subxt-core"] } +subxt-rpcs = { workspace = true, features = ["subxt-core"] } # For parsing urls to disallow insecure schemes url = { workspace = true } @@ -135,6 +135,7 @@ tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread", "sy polkadot-sdk = { workspace = true, features = ["sp-core", "sp-keyring", "sp-runtime", "std"] } assert_matches = { workspace = true } subxt-signer = { path = "../signer", features = ["unstable-eth"] } +subxt-rpcs = { workspace = true, features = ["subxt-core", "mock-rpc-client"] } # Tracing subscriber is useful for light-client examples to ensure that # the `bootNodes` and chain spec are configured correctly. If all is fine, then # the light-client wlll emit INFO logs with diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 6af85962c6f..06d74fe0a22 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -337,6 +337,7 @@ pub struct StorageResponse { pub value: Vec, } +#[allow(warnings)]// TODO: Remove before merging #[cfg(test)] mod test { use super::*; @@ -634,25 +635,6 @@ mod test { }; use rpc_client::*; - pub fn setup_mock_rpc() -> MockRpcBuilder { - MockRpcBuilder::default() - .add_method("state_getStorage", |data, _sub, params| { - Box::pin(async move { - let params = params.map(|p| p.get().to_string()); - let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); - let key: sp_core::Bytes = rpc_params.sequence().next().unwrap(); - let value = data.pop(key.0).unwrap_single(); - value.map(|v| serde_json::value::RawValue::from_string(v).unwrap()) - }) - }) - .add_method("chain_getBlockHash", |data, _, _| { - Box::pin(async move { - let value = data.pop("chain_getBlockHash".into()).unwrap_single(); - value.map(|v| serde_json::value::RawValue::from_string(v).unwrap()) - }) - }) - } - use crate::backend::Backend; fn client_runtime_version(num: u32) -> crate::client::RuntimeVersion { @@ -676,27 +658,49 @@ mod test { #[tokio::test] async fn storage_fetch_values() { - let mock_data = vec![ - ("ID1", Message::Single(bytes("Data1"))), + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, StateHolder} }; + + // Map from storage key to responses, given out in order, when that key is requested. + let data = HashMap::from_iter([ + ( + "ID1", + VecDeque::from_iter([ + Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Ok(Json(hex::encode("Data1"))), + ]) + ), ( "ID2", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - ))), + VecDeque::from_iter([ + Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Ok(Json(hex::encode("Data2"))), + ]) ), - ("ID2", Message::Single(bytes("Data2"))), ( "ID3", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - ))), + VecDeque::from_iter([ + Ok(Json(hex::encode("Data3"))), + ]) ), - ("ID3", Message::Single(bytes("Data3"))), - ]; - let rpc_client = setup_mock_rpc().add_mock_data(mock_data).build(); - let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); + ]); + + let rpc_client = MockRpcClient::builder() + .method_handler("state_getStorage", move |data: StateHolder>>>, params| async move { + // Decode the storage key as first item from sequence of params: + let params = params.map(|p| p.get().to_string()); + let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); + let key: sp_core::Bytes = rpc_params.sequence().next().unwrap(); + let key = std::str::from_utf8(&key.0).unwrap(); + // Fetch the response to use from our map, popping it from the front. + let mut values = data.get().await; + let mut values = values.get_mut(key).unwrap(); + values.pop_front().unwrap() + }) + .build(data); // Test + let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); + let response = backend .storage_fetch_values( ["ID1".into(), "ID2".into(), "ID3".into()].into(), @@ -721,18 +725,21 @@ mod test { #[tokio::test] async fn storage_fetch_value() { - // Setup - let mock_data = [ - ( - "ID1", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - ))), - ), - ("ID1", Message::Single(bytes("Data1"))), - ]; - let rpc_client = setup_mock_rpc().add_mock_data(mock_data).build(); + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; + let rpc_client = MockRpcClient::builder() + .method_handler("state_getStorage", move |is_fst, _params| async move { + if is_fst.set(false).await { + // Return "disconnected" error on first call + return Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "Reconnecting".to_string(), + )) + } + // Return some hex encoded storage value on the next one + Ok(Json(hex::encode("Data1"))) + }) + .build(true); + // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); let response = backend @@ -760,17 +767,21 @@ mod test { /// } /// ``` async fn simple_fetch() { + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; + let hash = random_hash(); - let mock_data = vec![ - ( - "chain_getBlockHash", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - ))), - ), - ("chain_getBlockHash", Message::Single(Ok(Some(hash)))), - ]; - let rpc_client = setup_mock_rpc().add_mock_data(mock_data).build(); + let rpc_client = MockRpcClient::builder() + .method_handler("chain_getBlockHash", move |is_fst, _params| async move { + if is_fst.set(false).await { + // Return "disconnected" error on first call + return Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "Reconnecting".to_string(), + )) + } + // Return the blockhash on subsequent calls + Ok(Json(hash)) + }) + .build(true); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); @@ -803,82 +814,43 @@ mod test { /// } /// ``` async fn stream_simple() { - let mock_subscription_data = vec![ - ( - "state_subscribeRuntimeVersion", - Message::Many(Ok(vec![ - Ok(runtime_version(0)), - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - )), - Ok(runtime_version(1)), - ])), - ), - ( - "state_subscribeRuntimeVersion", - Message::Many(Ok(vec![ - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - )), - Ok(runtime_version(2)), - Ok(runtime_version(3)), - ])), - ), - ( - "state_subscribeRuntimeVersion", - Message::Many(Ok(vec![ - Ok(runtime_version(4)), - Ok(runtime_version(5)), - Err(subxt_rpcs::Error::Client("Reconnecting".into())), - ])), - ), - ]; - let rpc_client = setup_mock_rpc() - .add_subscription("state_subscribeRuntimeVersion", |data, _, _| { - Box::pin(async move { - let values = data - .pop("state_subscribeRuntimeVersion".into()) - .unwrap_many(); - let values: RpcResult>>> = values.map(|v| { - v.into_iter() - .map(|v| { - v.map(|v| serde_json::value::RawValue::from_string(v).unwrap()) - }) - .collect::>>>() - }); - values.map(|v| RawRpcSubscription { - stream: futures::stream::iter(v).boxed(), - id: Some("ID".to_string()), - }) - }) + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; + + // Each time the subscription is called, it will pop the first set + // of values from this and return them one after the other. + let data = VecDeque::from_iter([ + vec![ + Ok(Json(runtime_version(0))), + Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Ok(Json(runtime_version(1))), + ], + vec![ + Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Ok(Json(runtime_version(2))), + Ok(Json(runtime_version(3))), + ], + vec![ + Ok(Json(runtime_version(4))), + Ok(Json(runtime_version(5))), + Err(subxt_rpcs::Error::Client("..".into())), + ] + ]); + + let rpc_client = MockRpcClient::>::builder() + .subscription_handler("state_subscribeRuntimeVersion", move |data, _params, _unsub| async move { + data.get().await.pop_front().unwrap() }) - .add_mock_data(mock_subscription_data) - .build(); + .build(data); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); - let mut results = backend.stream_runtime_version().await.unwrap(); - let mut expected = VecDeque::from(vec![ - Ok::(client_runtime_version(0)), - Ok(client_runtime_version(4)), - Ok(client_runtime_version(5)), - ]); - while let Some(res) = results.next().await { - if res.is_ok() { - assert_eq!(expected.pop_front().unwrap().unwrap(), res.unwrap()) - } else { - assert!(matches!( - res, - Err(Error::Rpc(RpcError::ClientError( - subxt_rpcs::Error::Client(_) - ))) - )) - } - } - assert!(expected.is_empty()); - assert!(results.next().await.is_none()) + assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(0)); + assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(4)); + assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(5)); + assert!(matches!(results.next().await.unwrap(), Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::Client(_)))))); + assert!(results.next().await.is_none()); } } @@ -937,6 +909,41 @@ mod test { type FollowEvent = chain_head::FollowEvent<::Hash>; + /// Build a mock client which can handle `chainHead_v1_follow` calls, and accepts + /// a receiver whose messages will be sent onto the first such subscription. + fn mock_client_builder(recv: tokio::sync::mpsc::Receiver>) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen} }; + + let hash = random_hash(); + let mut id = 0; + let mut recv = Some(recv); + + MockRpcClient::builder() + .subscription_handler("chainHead_v1_follow", move |_state, _params, _unsub| { + let recv = recv.take(); + async move { + id += 1; + + let follow_event = + FollowEvent::Initialized(Initialized::<::Hash> { + finalized_block_hashes: vec![hash], + finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( + RuntimeVersionEvent { + spec: runtime_spec(), + }, + )), + }); + + AndThen( + // First send an initialized event + (vec![Json(follow_event)], format!("ID{id}")), + // Next, send any events provided via the recv channel + recv + ) + } + }) + } + fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder { let hash = random_hash(); let mut id = 0; @@ -1022,37 +1029,30 @@ mod test { #[tokio::test] async fn storage_fetch_values_returns_stream_with_single_error() { - let response_data = vec![( - "method_response", - Message::Single(Ok(response_started("Id1"))), - )]; - let mock_subscription_data = vec![( - "chainHead_v1_storage", - Message::Many(Ok(vec![Ok(operation_error("Id1")), Ok(FollowEvent::Stop)])), - )]; - let rpc_client = setup_mock_rpc_client(false) - .add_method("chainHead_v1_storage", |data, sub, _| { - Box::pin(async move { - let response = data.pop("method_response".into()).unwrap_single(); - if response.is_ok() { - let item = data.pop("chainHead_v1_storage".into()); - if let Some(sub) = sub { - let item = item; - sub.write_delayed(item).await - } - } - response.map(|x| RawValue::from_string(x).unwrap()) - }) + use subxt_rpcs::client::mock_rpc_client::Json; + + let (tx, rx) = tokio::sync::mpsc::channel(10); + + let rpc_client = mock_client_builder(rx) + .method_handler("chainHead_v1_storage", move |data, params| { + let tx = tx.clone(); + async move { + tokio::spawn(async move { + // Wait a little and then send an error response on the + // chainHead_follow subscription: + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + tx.send(Json(operation_error("Id1"))).await.unwrap(); + }); + Json(response_started("Id1")) + } }) - .add_mock_data(mock_subscription_data) - .add_mock_data(response_data) - .build(); + .build(()); let backend = build_backend_spawn_background(rpc_client); // Test - // This request should encounter an error on `request` and do a retry. - let response = backend + // This request should encounter an error. + let mut response = backend .storage_fetch_values( ["ID1".into(), "ID2".into(), "ID3".into()].into(), random_hash(), @@ -1060,15 +1060,8 @@ mod test { .await .unwrap(); - // operation returned FollowEvent::OperationError - let response = response - .collect::>>() - .await; - - assert!(matches!( - response.as_slice(), - [Err(Error::Other(s) )] if s == "error" - )); + assert!(response.next().await.unwrap().is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))); + assert!(response.next().await.is_none()); } #[tokio::test] @@ -1333,7 +1326,7 @@ mod test { assert_eq!(hash, response_hash) } - #[tokio::test] + /* #[tokio::test] // Failure as we do not wait for subscription id to be updated. // see https://github.com/paritytech/subxt/issues/1567 async fn stale_subscription_id_failure() { @@ -1446,6 +1439,6 @@ mod test { response, Err(e) if e.to_string() == "stale id" )) - } + } */ } } From 6e641ddb1e3453f8353101d8c6e9174b7534de2a Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 3 Feb 2025 17:50:03 +0000 Subject: [PATCH 10/32] Get all but one test working with new MockRpcClient --- rpcs/src/client/mock_rpc_client.rs | 172 ++++++++---- subxt/src/backend/mod.rs | 436 +++++++++++------------------ 2 files changed, 281 insertions(+), 327 deletions(-) diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 53729329ccd..9731d82b0a5 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -42,38 +42,61 @@ use core::future::Future; use futures::StreamExt; use serde_json::value::RawValue; use std::sync::{Arc, Mutex}; -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; -type MethodHandlerFn = Box, &str, Option>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; -type SubscriptionHandlerFn = Box, &str, Option>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; +type MethodHandlerFnOnce = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; +type SubscriptionHandlerFnOnce = Box>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; + +type MethodHandlerFn = Box>) -> RawRpcFuture<'static, Box> + Send + Sync + 'static>; +type SubscriptionHandlerFn = Box>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; /// A builder to configure and build a new [`MockRpcClient`]. -pub struct MockRpcClientBuilder { - method_handlers: HashMap>, - subscription_handlers: HashMap>, - method_fallback: Option>, - subscription_fallback: Option> +pub struct MockRpcClientBuilder { + method_handlers_once: HashMap>, + method_handlers: HashMap, + method_fallback: Option, + subscription_handlers_once: HashMap>, + subscription_handlers: HashMap, + subscription_fallback: Option } -impl MockRpcClientBuilder { +impl MockRpcClientBuilder { fn new() -> Self { MockRpcClientBuilder { + method_handlers_once: HashMap::new(), method_handlers: HashMap::new(), - subscription_handlers: HashMap::new(), method_fallback: None, + subscription_handlers_once: HashMap::new(), + subscription_handlers: HashMap::new(), subscription_fallback: None } } + /// Add a handler for a specific RPC method. This is called exactly once, and multiple such calls for the same method can be + /// added. Only when any calls registered with this have been used up is the method set by [`Self::method_handler`] called. + pub fn method_handler_once(mut self, name: impl Into, f: MethodHandler) -> Self + where + MethodHandler: FnOnce(Option>) -> MFut + Send + Sync + 'static, + MFut: Future + Send + 'static, + MRes: IntoHandlerResponse, + { + let handler: MethodHandlerFnOnce = Box::new(move |_method: &str, params: Option>| { + let fut = f(params); + Box::pin(async move { fut.await.into_handler_response() }) + }); + self.method_handlers_once.entry(name.into()).or_default().push_back(handler); + self + } + /// Add a handler for a specific RPC method. pub fn method_handler(mut self, name: impl Into, mut f: MethodHandler) -> Self where - MethodHandler: FnMut(StateHolder, Option>) -> MFut + Send + Sync + 'static, + MethodHandler: FnMut(Option>) -> MFut + Send + Sync + 'static, MFut: Future + Send + 'static, MRes: IntoHandlerResponse, { - let handler: MethodHandlerFn = Box::new(move |state: StateHolder, _method: &str, params: Option>| { - let fut = f(state, params); + let handler: MethodHandlerFn = Box::new(move |_method: &str, params: Option>| { + let fut = f(params); Box::pin(async move { fut.await.into_handler_response() }) }); self.method_handlers.insert(name.into(), handler); @@ -83,27 +106,42 @@ impl MockRpcClientBuilder { /// Add a fallback handler to handle any methods not handled by a specific handler. pub fn method_fallback(mut self, mut f: MethodHandler) -> Self where - MethodHandler: FnMut(StateHolder, String, Option>) -> MFut + Send + Sync + 'static, + MethodHandler: FnMut(String, Option>) -> MFut + Send + Sync + 'static, MFut: Future + Send + 'static, MRes: IntoHandlerResponse, { - let handler: MethodHandlerFn = Box::new(move |state: StateHolder, method: &str, params: Option>| { - let fut = f(state, method.to_owned(), params); + let handler: MethodHandlerFn = Box::new(move |method: &str, params: Option>| { + let fut = f(method.to_owned(), params); Box::pin(async move { fut.await.into_handler_response() }) }); self.method_fallback = Some(handler); self } + /// Add a handler for a specific RPC subscription. + pub fn subscription_handler_once(mut self, name: impl Into, f: SubscriptionHandler) -> Self + where + SubscriptionHandler: FnOnce(Option>, String) -> SFut + Send + Sync + 'static, + SFut: Future + Send + 'static, + SRes: IntoSubscriptionResponse, + { + let handler: SubscriptionHandlerFnOnce = Box::new(move |_sub: &str, params: Option>, unsub: &str| { + let fut = f(params, unsub.to_owned()); + Box::pin(async move { fut.await.into_subscription_response() }) + }); + self.subscription_handlers_once.entry(name.into()).or_default().push_back(handler); + self + } + /// Add a handler for a specific RPC subscription. pub fn subscription_handler(mut self, name: impl Into, mut f: SubscriptionHandler) -> Self where - SubscriptionHandler: FnMut(StateHolder, Option>, String) -> SFut + Send + Sync + 'static, + SubscriptionHandler: FnMut(Option>, String) -> SFut + Send + Sync + 'static, SFut: Future + Send + 'static, SRes: IntoSubscriptionResponse, { - let handler: SubscriptionHandlerFn = Box::new(move |state: StateHolder, _sub: &str, params: Option>, unsub: &str| { - let fut = f(state, params, unsub.to_owned()); + let handler: SubscriptionHandlerFn = Box::new(move |_sub: &str, params: Option>, unsub: &str| { + let fut = f(params, unsub.to_owned()); Box::pin(async move { fut.await.into_subscription_response() }) }); self.subscription_handlers.insert(name.into(), handler); @@ -113,12 +151,12 @@ impl MockRpcClientBuilder { /// Add a fallback handler to handle any subscriptions not handled by a specific handler. pub fn subscription_fallback(mut self, mut f: SubscriptionHandler) -> Self where - SubscriptionHandler: FnMut(StateHolder, String, Option>, String) -> SFut + Send + Sync + 'static, + SubscriptionHandler: FnMut(String, Option>, String) -> SFut + Send + Sync + 'static, SFut: Future + Send + 'static, SRes: IntoSubscriptionResponse, { - let handler: SubscriptionHandlerFn = Box::new(move |state: StateHolder, sub: &str, params: Option>, unsub: &str| { - let fut = f(state, sub.to_owned(), params, unsub.to_owned()); + let handler: SubscriptionHandlerFn = Box::new(move |sub: &str, params: Option>, unsub: &str| { + let fut = f(sub.to_owned(), params, unsub.to_owned()); Box::pin(async move { fut.await.into_subscription_response() }) }); self.subscription_fallback = Some(handler); @@ -126,12 +164,13 @@ impl MockRpcClientBuilder { } /// Construct a [`MockRpcClient`] given some state which will be mutably available to each of the handlers. - pub fn build(self, state: State) -> MockRpcClient { + pub fn build(self) -> MockRpcClient { MockRpcClient { - state: Arc::new(tokio::sync::Mutex::new(state)), + method_handlers_once: Arc::new(Mutex::new(self.method_handlers_once)), method_handlers: Arc::new(Mutex::new(self.method_handlers)), - subscription_handlers: Arc::new(Mutex::new(self.subscription_handlers)), method_fallback: self.method_fallback.map(|f| Arc::new(Mutex::new(f))), + subscription_handlers_once: Arc::new(Mutex::new(self.subscription_handlers_once)), + subscription_handlers: Arc::new(Mutex::new(self.subscription_handlers)), subscription_fallback: self.subscription_fallback.map(|f| Arc::new(Mutex::new(f))), } } @@ -139,43 +178,54 @@ impl MockRpcClientBuilder { /// A mock RPC client that responds programmatically to requests. /// Useful for testing. -pub struct MockRpcClient { - // State is accessed inside async functions and may be held across await points, - // so we use a tokio Mutex for it. - state: Arc>, +pub struct MockRpcClient { // These are all accessed for just long enough to call the method. The method // returns a future, but the method call itself isn't held for long. - method_handlers: Arc>>>, - subscription_handlers: Arc>>>, - method_fallback: Option>>>, - subscription_fallback: Option>>>, + method_handlers_once: Arc>>>, + method_handlers: Arc>>, + method_fallback: Option>>, + subscription_handlers_once: Arc>>>, + subscription_handlers: Arc>>, + subscription_fallback: Option>>, } -impl MockRpcClient { +impl MockRpcClient { /// Construct a new [`MockRpcClient`] - pub fn builder() -> MockRpcClientBuilder { + pub fn builder() -> MockRpcClientBuilder { MockRpcClientBuilder::new() } } -impl RpcClientT for MockRpcClient { +impl RpcClientT for MockRpcClient { fn request_raw<'a>( &'a self, method: &'a str, params: Option>, ) -> RawRpcFuture<'a, Box> { + // Remove and call a one-time handler if any exist. + let mut handlers_once = self.method_handlers_once.lock().unwrap(); + if let Some(handlers) = handlers_once.get_mut(method) { + if let Some(handler) = handlers.pop_front() { + return handler(method, params) + } + } + drop(handlers_once); + + // Call a specific handler for the method if one is found. let mut handlers = self.method_handlers.lock().unwrap(); if let Some(handler) = handlers.get_mut(method) { - // Call a specific handler for the method if one is found. - handler(StateHolder(self.state.clone()), method, params) - } else if let Some(handler) = &self.method_fallback { - // Else, call the fallback handler if that exists. + return handler(method, params) + } + drop(handlers); + + // Call a fallback handler if one exists + if let Some(handler) = &self.method_fallback { let mut handler = handler.lock().unwrap(); - handler(StateHolder(self.state.clone()), method, params) - } else { - // Else, method not found. - Box::pin(async move { Err(UserError::method_not_found().into()) }) + return handler(method, params) } + + // Else, method not found. + Box::pin(async move { Err(UserError::method_not_found().into()) }) } fn subscribe_raw<'a>( &'a self, @@ -183,18 +233,30 @@ impl RpcClientT for MockRpcClient { params: Option>, unsub: &'a str, ) -> RawRpcFuture<'a, RawRpcSubscription> { + // Remove and call a one-time handler if any exist. + let mut handlers_once = self.subscription_handlers_once.lock().unwrap(); + if let Some(handlers) = handlers_once.get_mut(sub) { + if let Some(handler) = handlers.pop_front() { + return handler(sub, params, unsub) + } + } + drop(handlers_once); + + // Call a specific handler for the subscrpition if one is found. let mut handlers = self.subscription_handlers.lock().unwrap(); if let Some(handler) = handlers.get_mut(sub) { - // Call a specific handler for the method if one is found. - handler(StateHolder(self.state.clone()), sub, params, unsub) - } else if let Some(handler) = &self.subscription_fallback { - // Else, call the fallback handler if that exists. + return handler(sub, params, unsub) + } + drop(handlers); + + // Call a fallback handler if one exists + if let Some(handler) = &self.subscription_fallback { let mut handler = handler.lock().unwrap(); - handler(StateHolder(self.state.clone()), sub, params, unsub) - } else { - // Else, method not found. - Box::pin(async move { Err(UserError::method_not_found().into()) }) + return handler(sub, params, unsub) } + + // Else, method not found. + Box::pin(async move { Err(UserError::method_not_found().into()) }) } } @@ -253,6 +315,12 @@ pub trait IntoHandlerResponse { fn into_handler_response(self) -> Result, Error>; } +impl IntoHandlerResponse for () { + fn into_handler_response(self) -> Result, Error> { + serialize_to_raw_value(&()) + } +} + impl IntoHandlerResponse for Result { fn into_handler_response(self) -> Result, Error> { self.and_then(|val| val.into_handler_response()) diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 06d74fe0a22..2a72d922302 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -661,7 +661,7 @@ mod test { use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, StateHolder} }; // Map from storage key to responses, given out in order, when that key is requested. - let data = HashMap::from_iter([ + let mut values: HashMap<&str, VecDeque<_>> = HashMap::from_iter([ ( "ID1", VecDeque::from_iter([ @@ -685,18 +685,18 @@ mod test { ]); let rpc_client = MockRpcClient::builder() - .method_handler("state_getStorage", move |data: StateHolder>>>, params| async move { + .method_handler("state_getStorage", move |params| { // Decode the storage key as first item from sequence of params: let params = params.map(|p| p.get().to_string()); let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); let key: sp_core::Bytes = rpc_params.sequence().next().unwrap(); let key = std::str::from_utf8(&key.0).unwrap(); // Fetch the response to use from our map, popping it from the front. - let mut values = data.get().await; let mut values = values.get_mut(key).unwrap(); - values.pop_front().unwrap() + let value = values.pop_front().unwrap(); + async move { value } }) - .build(data); + .build(); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); @@ -725,20 +725,18 @@ mod test { #[tokio::test] async fn storage_fetch_value() { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json, RawRpcFuture }; let rpc_client = MockRpcClient::builder() - .method_handler("state_getStorage", move |is_fst, _params| async move { - if is_fst.set(false).await { - // Return "disconnected" error on first call - return Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - )) - } + .method_handler_once("state_getStorage", move |_params| async move { + // Return "disconnected" error on first call + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + }) + .method_handler_once("state_getStorage", move |_param| async move { // Return some hex encoded storage value on the next one - Ok(Json(hex::encode("Data1"))) + Json(hex::encode("Data1")) }) - .build(true); + .build(); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); @@ -769,19 +767,18 @@ mod test { async fn simple_fetch() { use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; + let mut is_fst = true; let hash = random_hash(); let rpc_client = MockRpcClient::builder() - .method_handler("chain_getBlockHash", move |is_fst, _params| async move { - if is_fst.set(false).await { - // Return "disconnected" error on first call - return Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Reconnecting".to_string(), - )) - } - // Return the blockhash on subsequent calls - Ok(Json(hash)) + .method_handler_once("chain_getBlockHash", move |_params| async move { + // Return "disconnected" error on first call + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + }) + .method_handler_once("chain_getBlockHash", move |_params| async move { + // Return the blockhash on next call + Json(hash) }) - .build(true); + .build(); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); @@ -818,7 +815,7 @@ mod test { // Each time the subscription is called, it will pop the first set // of values from this and return them one after the other. - let data = VecDeque::from_iter([ + let mut data = VecDeque::from_iter([ vec![ Ok(Json(runtime_version(0))), Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), @@ -836,11 +833,12 @@ mod test { ] ]); - let rpc_client = MockRpcClient::>::builder() - .subscription_handler("state_subscribeRuntimeVersion", move |data, _params, _unsub| async move { - data.get().await.pop_front().unwrap() + let rpc_client = MockRpcClient::builder() + .subscription_handler("state_subscribeRuntimeVersion", move |_params, _unsub| { + let res = data.pop_front().unwrap(); + async move { res } }) - .build(data); + .build(); // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); @@ -855,16 +853,12 @@ mod test { } mod unstable_backend { - use std::sync::atomic::AtomicBool; - use futures::task::Poll; use rpc_client::{Message, MockRpcBuilder, Subscription}; use subxt_rpcs::methods::chain_head::{ - self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, - OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent, + self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent }; - use super::chain_head::*; use super::*; @@ -911,7 +905,7 @@ mod test { /// Build a mock client which can handle `chainHead_v1_follow` calls, and accepts /// a receiver whose messages will be sent onto the first such subscription. - fn mock_client_builder(recv: tokio::sync::mpsc::Receiver>) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { + fn mock_client_builder(recv: tokio::sync::mpsc::Receiver>) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen} }; let hash = random_hash(); @@ -919,7 +913,7 @@ mod test { let mut recv = Some(recv); MockRpcClient::builder() - .subscription_handler("chainHead_v1_follow", move |_state, _params, _unsub| { + .subscription_handler("chainHead_v1_follow", move |_params, _unsub| { let recv = recv.take(); async move { id += 1; @@ -944,46 +938,46 @@ mod test { }) } - fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder { - let hash = random_hash(); - let mut id = 0; - rpc_client::MockRpcBuilder::default().add_subscription( - "chainHead_v1_follow", - move |_, sub, _| { - Box::pin(async move { - if cycle_ids { - id += 1; - } - let follow_event = - FollowEvent::Initialized(Initialized::<::Hash> { - finalized_block_hashes: vec![hash], - finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( - RuntimeVersionEvent { - spec: runtime_spec(), - }, - )), - }); - let (subscription, mut receiver) = Subscription::new(); - subscription - .write(Message::Single(Ok( - serde_json::to_string(&follow_event).unwrap() - ))) - .await; - sub.replace(subscription); - let read_stream = - futures::stream::poll_fn(move |cx| -> Poll> { - receiver.poll_recv(cx) - }) - .map(|item| item.map(|x| RawValue::from_string(x).unwrap())); - let stream = RawRpcSubscription { - stream: read_stream.boxed(), - id: Some(format!("ID{}", id)), - }; - Ok(stream) - }) - }, - ) - } + // fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder { + // let hash = random_hash(); + // let mut id = 0; + // rpc_client::MockRpcBuilder::default().add_subscription( + // "chainHead_v1_follow", + // move |_, sub, _| { + // Box::pin(async move { + // if cycle_ids { + // id += 1; + // } + // let follow_event = + // FollowEvent::Initialized(Initialized::<::Hash> { + // finalized_block_hashes: vec![hash], + // finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( + // RuntimeVersionEvent { + // spec: runtime_spec(), + // }, + // )), + // }); + // let (subscription, mut receiver) = Subscription::new(); + // subscription + // .write(Message::Single(Ok( + // serde_json::to_string(&follow_event).unwrap() + // ))) + // .await; + // sub.replace(subscription); + // let read_stream = + // futures::stream::poll_fn(move |cx| -> Poll> { + // receiver.poll_recv(cx) + // }) + // .map(|item| item.map(|x| RawValue::from_string(x).unwrap())); + // let stream = RawRpcSubscription { + // stream: read_stream.boxed(), + // id: Some(format!("ID{}", id)), + // }; + // Ok(stream) + // }) + // }, + // ) + // } fn response_started(id: &str) -> MethodResponse { MethodResponse::Started(MethodResponseStarted { @@ -1034,19 +1028,19 @@ mod test { let (tx, rx) = tokio::sync::mpsc::channel(10); let rpc_client = mock_client_builder(rx) - .method_handler("chainHead_v1_storage", move |data, params| { - let tx = tx.clone(); + .method_handler_once("chainHead_v1_storage", move |params| { + tokio::spawn(async move { + // Wait a little and then send an error response on the + // chainHead_follow subscription: + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + tx.send(Json(operation_error("Id1"))).await.unwrap(); + }); + async move { - tokio::spawn(async move { - // Wait a little and then send an error response on the - // chainHead_follow subscription: - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - tx.send(Json(operation_error("Id1"))).await.unwrap(); - }); Json(response_started("Id1")) } }) - .build(()); + .build(); let backend = build_backend_spawn_background(rpc_client); @@ -1067,52 +1061,40 @@ mod test { #[tokio::test] /// Tests that the method will retry on failed query async fn storage_fetch_values_retry_query() { - let response_data = vec![ - ( - "method_response", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Error".into(), - ))), - ), - ( - "method_response", - Message::Single(Ok(response_started("Id1"))), - ), - ]; - let mock_data = vec![( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items( - "Id1", - &[ - storage_result("ID1", "Data1"), - storage_result("ID2", "Data2"), - storage_result("ID3", "Data3"), - ], - )), - Ok(storage_done("Id1")), - ])), - )]; - let rpc_client = setup_mock_rpc_client(false) - .add_method("chainHead_v1_storage", |data, sub, _| { - Box::pin(async move { - let response = data.pop("method_response".into()).unwrap_single(); - if response.is_ok() { - let item = data.pop("chainHead_v1_storage".into()); - if let Some(sub) = sub { - let item = item; - sub.write_delayed(item).await - } - } - response.map(|x| RawValue::from_string(x).unwrap()) - }) + use subxt_rpcs::client::mock_rpc_client::Json; + + let (tx, rx) = tokio::sync::mpsc::channel(10); + + let rpc_client = mock_client_builder(rx) + .method_handler_once("chainHead_v1_storage", move |_params| async move { + // First call; return DisconnectedWillReconnect + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())); + }) + .method_handler_once("chainHead_v1_storage", move |_params| async move { + // Otherwise, return that we'll start sending a response, and spawn + // task to send the relevant response via chainHead_follow. + tokio::spawn(async move { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + + tx.send(Json(storage_items( + "Id1", + &[ + storage_result("ID1", "Data1"), + storage_result("ID2", "Data2"), + storage_result("ID3", "Data3"), + ], + ))).await.unwrap(); + + tx.send(Json(storage_done("Id1"))).await.unwrap(); + }); + + Ok(Json(response_started("Id1"))) }) - .add_mock_data(mock_data) - .add_mock_data(response_data) .build(); - let backend = build_backend_spawn_background(rpc_client); - // We try again and should succeed + // Despite DisconnectedWillReconnect we try again transparently + // and get the data we asked for. + let backend = build_backend_spawn_background(rpc_client); let response = backend .storage_fetch_values( ["ID1".into(), "ID2".into(), "ID3".into()].into(), @@ -1137,140 +1119,50 @@ mod test { } #[tokio::test] async fn storage_fetch_values_retry_chainhead_continue() { - fn compare_storage_responses( - expected_response: &StorageResponse, - received_response: &StorageResponse, - ) -> bool { - expected_response == received_response - } + use subxt_rpcs::client::mock_rpc_client::Json; - let response_data = vec![ - ( - "method_response", - Message::Single(Err::( - subxt_rpcs::Error::DisconnectedWillReconnect("Error".into()), - )), - ), - ( - "method_response", - Message::Single(Ok(response_started("Id1"))), - ), - ( - "method_response", - Message::Single(Ok(response_started("Id1"))), - ), - ]; - let continue_data = vec![ - ("continue_response", Message::Single(Ok(()))), - ("continue_response", Message::Single(Ok(()))), - ( - "continue_response", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Error".into(), - ))), - ), - ("continue_response", Message::Single(Ok(()))), - ("continue_response", Message::Single(Ok(()))), - ]; - let mock_data = vec![ - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items("Id1", &[storage_result("ID1", "Data1")])), - Ok(operation_continue("Id1")), - ])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items("Id1", &[storage_result("ID2", "Data2")])), - Ok(operation_continue("Id1")), - ])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![Ok(operation_error("Id1")), Ok(FollowEvent::Stop)])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items("Id1", &[storage_result("ID1", "Data1")])), - Ok(operation_continue("Id1")), - ])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items("Id1", &[storage_result("ID2", "Data2")])), - Ok(operation_continue("Id1")), - ])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items("Id1", &[storage_result("ID3", "Data3")])), - Ok(storage_done("Id1")), - ])), - ), - ]; - let rpc_client = setup_mock_rpc_client(false) - .add_method("chainHead_v1_storage", |data, sub, _| { - Box::pin(async move { - let response = data.pop("method_response".into()).unwrap_single(); - if response.is_ok() { - let item = data.pop("chainHead_v1_storage".into()); - if let Some(sub) = sub { - let item = item; - sub.write_delayed(item).await - } - } - response.map(|x| RawValue::from_string(x).unwrap()) - }) + let mut storage_call_num = 0; + let mut continue_call_num = 0; + let (tx, rx) = tokio::sync::mpsc::channel(10); + let tx2 = tx.clone(); + + let rpc_client = mock_client_builder(rx) + .method_handler_once("chainHead_v1_storage", move |_params| async move { + // First call; return DisconnectedWillReconnect + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) - .add_method("chainHead_v1_continue", |data, sub, _| { - Box::pin(async move { - let response = data.pop("continue_response".into()).unwrap_single(); - if response.is_ok() { - let item = data.pop("chainHead_v1_storage".into()); - if let Some(sub) = sub { - let item = item; - sub.write_delayed(item).await - } - } - response.map(|x| RawValue::from_string(x).unwrap()) - }) + .method_handler_once("chainHead_v1_storage", move |_params| async move { + // Next call, return a storage item and then a "waiting for continue". + tokio::spawn(async move { + tx.send( + Json(storage_items("Id1", &[storage_result("ID1", "Data1")])) + ).await.unwrap(); + tx.send(Json(operation_continue("Id1"))).await.unwrap(); + }); + Ok(Json(response_started("Id1"))) + }) + .method_handler_once("chainHead_v1_continue", move |_params| async move { + // First call; return DisconnectedWillReconnect + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + }) + .method_handler_once("chainHead_v1_continue", move |_params| async move { + // Next call; acknowledge the "continue" and return reamining storage items. + tokio::spawn(async move { + tx2.send( + Json(storage_items("Id1", &[storage_result("ID2", "Data2")])) + ).await.unwrap(); + tx2.send( + Json(storage_items("Id1", &[storage_result("ID3", "Data3")])) + ).await.unwrap(); + tx2.send(Json(storage_done("Id1"))).await.unwrap(); + }); + Ok(Json(())) }) - .add_mock_data(mock_data) - .add_mock_data(response_data) - .add_mock_data(continue_data) .build(); - let backend = build_backend_spawn_background(rpc_client); - // We try again and should fail mid way - let response = backend - .storage_fetch_values( - ["ID1".into(), "ID2".into(), "ID3".into()].into(), - random_hash(), - ) - .await - .unwrap(); - // operation returned FollowEvent::OperationError - let response = response - .collect::>>() - .await; - - assert!(matches!( - response.as_slice(), - [ - Ok(resp1 @ StorageResponse { .. }), - Ok(resp2 @ StorageResponse { .. }), - Err(Error::Other(s)) - ] if s == "error" - && compare_storage_responses(&storage_response("ID1", "Data1"), resp1) - && compare_storage_responses(&storage_response("ID2", "Data2"), resp2) - )); + let backend = build_backend_spawn_background(rpc_client); - // We try again and should succeed + // We should succees, transparently handling `continue`s and `DisconnectWillReconnects`. let response = backend .storage_fetch_values( ["ID1".into(), "ID2".into(), "ID3".into()].into(), @@ -1296,31 +1188,25 @@ mod test { #[tokio::test] async fn simple_fetch() { + use subxt_rpcs::client::mock_rpc_client::Json; + let hash = random_hash(); + let (tx, rx) = tokio::sync::mpsc::channel(10); - let mock_data = vec![ - ( - "chainSpec_v1_genesisHash", - Message::Single(Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "Error".to_owned(), - ))), - ), - ("chainSpec_v1_genesisHash", Message::Single(Ok(hash))), - ]; - let rpc_client = setup_mock_rpc_client(false) - .add_method("chainSpec_v1_genesisHash", |data, _, _| { - Box::pin(async move { - let response = data.pop("chainSpec_v1_genesisHash".into()).unwrap_single(); - response.map(|x| RawValue::from_string(x).unwrap()) - }) + let rpc_client = mock_client_builder(rx) + .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { + // First call, return disconnected error. + Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_owned())) + }) + .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { + // Next call, return the hash. + Ok(Json(hash)) }) - .add_mock_data(mock_data) .build(); - let backend = build_backend_spawn_background(rpc_client); - // Test // This request should encounter an error on `request` and do a retry. + let backend = build_backend_spawn_background(rpc_client); let response_hash = backend.genesis_hash().await.unwrap(); assert_eq!(hash, response_hash) From d1c243d4ebddd3687cbaf5472c78b0d15a7db64c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 4 Feb 2025 17:20:24 +0000 Subject: [PATCH 11/32] WIP trying to debug failure --- rpcs/src/client/mock_rpc_client.rs | 190 +++++++++++++++++++++++++++-- subxt/src/backend/mod.rs | 160 ++++++++++-------------- 2 files changed, 249 insertions(+), 101 deletions(-) diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 9731d82b0a5..9dd2bdf4354 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -10,7 +10,7 @@ //! use subxt_rpcs::client::{ RpcClient, MockRpcClient }; //! use subxt_rpcs::client::mock_rpc_client::Json; //! -//! let state = vec![ +//! let mut state = vec![ //! Json(1u8), //! Json(2u8), //! Json(3u8), @@ -19,17 +19,18 @@ //! // Define a mock client by providing some state (can be optional) //! // and functions which intercept method and subscription calls and //! // returns something back. -//! let mock_client = MockRpcClient::>::builder() -//! .method_handler("foo", |state, params| async move { -//! // We'll panic if an RPC method is called more than 3 times: -//! state.get().await.pop().unwrap() +//! let mock_client = MockRpcClient::builder() +//! .method_handler_once("foo", move |params| { +//! // Return each item from our state, and then null afterwards. +//! let val = state.pop() +//! async move { Json(val) } //! }) -//! .subscription_handler("bar", |state, params, unsub| async move { +//! .subscription_handler("bar", |params, unsub| async move { //! // Arrays, vecs or an RpcSubscription can be returned here to //! // signal the set of values to be handed back on a subscription. //! vec![Json(1), Json(2), Json(3)] //! }) -//! .build(state); +//! .build(); //! //! // Build an RPC Client that can be used in Subxt or in conjunction with //! // the RPC methods provided in this crate. @@ -352,6 +353,12 @@ impl IntoHandlerResponse for Json { } } +impl IntoHandlerResponse for core::convert::Infallible { + fn into_handler_response(self) -> Result, Error> { + match self {} + } +} + fn serialize_to_raw_value(val: &T) -> Result, Error> { let res = serde_json::to_string(val).map_err(Error::Deserialization)?; let raw_value = RawValue::from_string(res).map_err(Error::Deserialization)?; @@ -451,6 +458,12 @@ impl IntoSubscriptionR } } +impl IntoSubscriptionResponse for core::convert::Infallible { + fn into_subscription_response(self) -> Result { + match self {} + } +} + /// Send the first items and then the second items back on a subscription; /// If any one of the responses is an error, we'll return the error. /// If one response has an ID and the other doesn't, we'll use that ID. @@ -475,4 +488,167 @@ impl IntoSubscription } } } +} + +#[cfg(test)] +mod test { + use crate::{RpcClient, rpc_params}; + use super::*; + + #[tokio::test] + async fn test_method_params() { + let rpc_client = MockRpcClient::builder() + .method_handler("foo", |params| async { + Json(params) + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // We get back whatever params we give + let res: (i32,i32,i32) = rpc_client.request("foo", rpc_params![1, 2, 3]).await.unwrap(); + assert_eq!(res, (1,2,3)); + + let res: (String,) = rpc_client.request("foo", rpc_params!["hello"]).await.unwrap(); + assert_eq!(res, ("hello".to_owned(),)); + } + + #[tokio::test] + async fn test_method_handler_then_fallback() { + let rpc_client = MockRpcClient::builder() + .method_handler("foo", |_params| async { + Json(1) + }) + .method_fallback(|name, _params| async { + Json(name) + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // Whenever we call "foo", we get 1 back. + for i in [1,1,1,1] { + let res: i32 = rpc_client.request("foo", rpc_params![]).await.unwrap(); + assert_eq!(res, i); + } + + // Whenever we call anything else, we get the name of the method back + for name in ["bar", "wibble", "steve"] { + let res: String = rpc_client.request(name, rpc_params![]).await.unwrap(); + assert_eq!(res, name); + } + } + + #[tokio::test] + async fn test_method_once_then_handler() { + let rpc_client = MockRpcClient::builder() + .method_handler_once("foo", |_params| async { + Json(1) + }) + .method_handler("foo", |_params| async { + Json(2) + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // Check that we call the "once" one time and then the second after that. + for i in [1,2,2,2,2] { + let res: i32 = rpc_client.request("foo", rpc_params![]).await.unwrap(); + assert_eq!(res, i); + } + } + + #[tokio::test] + async fn test_method_once() { + let rpc_client = MockRpcClient::builder() + .method_handler_once("foo", |_params| async { + Json(1) + }) + .method_handler_once("foo", |_params| async { + Json(2) + }) + .method_handler_once("foo", |_params| async { + Json(3) + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // Check that each method is only called once, in the right order. + for i in [1,2,3] { + let res: i32 = rpc_client.request("foo", rpc_params![]).await.unwrap(); + assert_eq!(res, i); + } + + // Check that we get a "method not found" error afterwards. + let err = rpc_client.request::("foo", rpc_params![]).await.unwrap_err(); + let not_found_code = UserError::method_not_found().code; + assert!(matches!(err, Error::User(u) if u.code == not_found_code)); + } + + #[tokio::test] + async fn test_subscription_once_then_handler_then_fallback() { + let rpc_client = MockRpcClient::builder() + .subscription_handler_once("foo", |_params, _unsub| async { + vec![Json(0), Json(0)] + }) + .subscription_handler("foo", |_params, _unsub| async { + vec![Json(1), Json(2), Json(3)] + }) + .subscription_fallback(|_name, _params, _unsub| async { + vec![Json(4)] + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // "foo" returns 0,0 the first time it's subscribed to + let sub = rpc_client.subscribe::("foo", rpc_params![], "unsub").await.unwrap(); + let res: Vec = sub.map(|i| i.unwrap()).collect().await; + assert_eq!(res, vec![0,0]); + + // then, "foo" returns 1,2,3 in subscription every other time + for _ in 1..5 { + let sub = rpc_client.subscribe::("foo", rpc_params![], "unsub").await.unwrap(); + let res: Vec = sub.map(|i| i.unwrap()).collect().await; + assert_eq!(res, vec![1,2,3]); + } + + // anything else returns 4 + let sub = rpc_client.subscribe::("bar", rpc_params![], "unsub").await.unwrap(); + let res: Vec = sub.map(|i| i.unwrap()).collect().await; + assert_eq!(res, vec![4]); + } + + #[tokio::test] + async fn test_subscription_and_then_with_channel() { + let (tx, rx) = tokio::sync::mpsc::channel(10); + + let rpc_client = MockRpcClient::builder() + .subscription_handler_once("foo", move |_params, _unsub| async move { + AndThen( + // These should be sent first.. + vec![Json(1), Json(2), Json(3)], + // .. and then anything the channel is handing back. + rx + ) + }) + .build(); + + let rpc_client = RpcClient::new(rpc_client); + + // Send a few values down the channel to be handed back in "foo" subscription: + tokio::spawn(async move { + for i in 4..=6 { + tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + tx.send(Json(i)).await.unwrap(); + } + }); + + // Expect all values back: + let sub = rpc_client.subscribe::("foo", rpc_params![], "unsub").await.unwrap(); + let res: Vec = sub.map(|i| i.unwrap()).collect().await; + assert_eq!(res, vec![1,2,3,4,5,6]); + } } \ No newline at end of file diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 2a72d922302..276b393de16 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -859,6 +859,7 @@ mod test { use subxt_rpcs::methods::chain_head::{ self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent }; + use tokio::select; use super::chain_head::*; use super::*; @@ -876,51 +877,65 @@ mod test { fn runtime_spec() -> RuntimeSpec { let spec = serde_json::json!({ - "specName": "westend", - "implName": "parity-westend", - "specVersion": 9122, - "implVersion": 0, - "transactionVersion": 7, - "apis": { - "0xdf6acb689907609b": 3, - "0x37e397fc7c91f5e4": 1, - "0x40fe3ad401f8959a": 5, - "0xd2bc9897eed08f15": 3, - "0xf78b278be53f454c": 2, - "0xaf2c0297a23e6d3d": 1, - "0x49eaaf1b548a0cb0": 1, - "0x91d5df18b0d2cf58": 1, - "0xed99c5acb25eedf5": 3, - "0xcbca25e39f142387": 2, - "0x687ad44ad37f03c2": 1, - "0xab3c0572291feb8b": 1, - "0xbc9d89904f5b923f": 1, - "0x37c8bb1350a9a2a8": 1 - } + "specName": "westend", + "implName": "parity-westend", + "specVersion": 9122, + "implVersion": 0, + "transactionVersion": 7, + "apis": { + "0xdf6acb689907609b": 3, + "0x37e397fc7c91f5e4": 1, + "0x40fe3ad401f8959a": 5, + "0xd2bc9897eed08f15": 3, + "0xf78b278be53f454c": 2, + "0xaf2c0297a23e6d3d": 1, + "0x49eaaf1b548a0cb0": 1, + "0x91d5df18b0d2cf58": 1, + "0xed99c5acb25eedf5": 3, + "0xcbca25e39f142387": 2, + "0x687ad44ad37f03c2": 1, + "0xab3c0572291feb8b": 1, + "0xbc9d89904f5b923f": 1, + "0x37c8bb1350a9a2a8": 1 + } }); - serde_json::from_value(spec).unwrap() + serde_json::from_value(spec).expect("Mock runtime spec should be the right shape") } type FollowEvent = chain_head::FollowEvent<::Hash>; - /// Build a mock client which can handle `chainHead_v1_follow` calls, and accepts - /// a receiver whose messages will be sent onto the first such subscription. - fn mock_client_builder(recv: tokio::sync::mpsc::Receiver>) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { + /// Build a mock client which can handle `chainHead_v1_follow` subscriptions. + /// Messages from the provided receiver are sent to the latest active subscription. + fn mock_client_builder(recv: tokio::sync::mpsc::UnboundedReceiver) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen} }; - let hash = random_hash(); - let mut id = 0; - let mut recv = Some(recv); + let recv = Arc::new(tokio::sync::Mutex::new(recv)); MockRpcClient::builder() .subscription_handler("chainHead_v1_follow", move |_params, _unsub| { - let recv = recv.take(); + let recv = recv.clone(); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + tokio::spawn(async move { + let mut recv_guard = recv.lock().await; + loop { + select! { + _ = tx.closed() => { + println!("CLOSED!"); + break + }, + Some(msg) = recv_guard.recv() => { + if tx.send(Json(msg)).is_err() { + break + } + } + } + } + }); + async move { - id += 1; - let follow_event = FollowEvent::Initialized(Initialized::<::Hash> { - finalized_block_hashes: vec![hash], + finalized_block_hashes: vec![random_hash()], finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( RuntimeVersionEvent { spec: runtime_spec(), @@ -930,55 +945,14 @@ mod test { AndThen( // First send an initialized event - (vec![Json(follow_event)], format!("ID{id}")), + (vec![Json(follow_event)], "chainHeadFollowSubscriptionId"), // Next, send any events provided via the recv channel - recv + rx ) } }) } - // fn setup_mock_rpc_client(cycle_ids: bool) -> MockRpcBuilder { - // let hash = random_hash(); - // let mut id = 0; - // rpc_client::MockRpcBuilder::default().add_subscription( - // "chainHead_v1_follow", - // move |_, sub, _| { - // Box::pin(async move { - // if cycle_ids { - // id += 1; - // } - // let follow_event = - // FollowEvent::Initialized(Initialized::<::Hash> { - // finalized_block_hashes: vec![hash], - // finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( - // RuntimeVersionEvent { - // spec: runtime_spec(), - // }, - // )), - // }); - // let (subscription, mut receiver) = Subscription::new(); - // subscription - // .write(Message::Single(Ok( - // serde_json::to_string(&follow_event).unwrap() - // ))) - // .await; - // sub.replace(subscription); - // let read_stream = - // futures::stream::poll_fn(move |cx| -> Poll> { - // receiver.poll_recv(cx) - // }) - // .map(|item| item.map(|x| RawValue::from_string(x).unwrap())); - // let stream = RawRpcSubscription { - // stream: read_stream.boxed(), - // id: Some(format!("ID{}", id)), - // }; - // Ok(stream) - // }) - // }, - // ) - // } - fn response_started(id: &str) -> MethodResponse { MethodResponse::Started(MethodResponseStarted { operation_id: id.to_owned(), @@ -1025,7 +999,7 @@ mod test { async fn storage_fetch_values_returns_stream_with_single_error() { use subxt_rpcs::client::mock_rpc_client::Json; - let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |params| { @@ -1033,7 +1007,7 @@ mod test { // Wait a little and then send an error response on the // chainHead_follow subscription: tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - tx.send(Json(operation_error("Id1"))).await.unwrap(); + tx.send(operation_error("Id1")); }); async move { @@ -1063,7 +1037,7 @@ mod test { async fn storage_fetch_values_retry_query() { use subxt_rpcs::client::mock_rpc_client::Json; - let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { @@ -1074,18 +1048,16 @@ mod test { // Otherwise, return that we'll start sending a response, and spawn // task to send the relevant response via chainHead_follow. tokio::spawn(async move { - tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - - tx.send(Json(storage_items( + tx.send(storage_items( "Id1", &[ storage_result("ID1", "Data1"), storage_result("ID2", "Data2"), storage_result("ID3", "Data3"), ], - ))).await.unwrap(); + )).unwrap(); - tx.send(Json(storage_done("Id1"))).await.unwrap(); + tx.send(storage_done("Id1")).unwrap(); }); Ok(Json(response_started("Id1"))) @@ -1102,7 +1074,7 @@ mod test { ) .await .unwrap(); - + let response = response .map(|x| x.unwrap()) .collect::>() @@ -1123,7 +1095,7 @@ mod test { let mut storage_call_num = 0; let mut continue_call_num = 0; - let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let tx2 = tx.clone(); let rpc_client = mock_client_builder(rx) @@ -1135,9 +1107,9 @@ mod test { // Next call, return a storage item and then a "waiting for continue". tokio::spawn(async move { tx.send( - Json(storage_items("Id1", &[storage_result("ID1", "Data1")])) - ).await.unwrap(); - tx.send(Json(operation_continue("Id1"))).await.unwrap(); + storage_items("Id1", &[storage_result("ID1", "Data1")]) + ).unwrap(); + tx.send(operation_continue("Id1")).unwrap(); }); Ok(Json(response_started("Id1"))) }) @@ -1149,12 +1121,12 @@ mod test { // Next call; acknowledge the "continue" and return reamining storage items. tokio::spawn(async move { tx2.send( - Json(storage_items("Id1", &[storage_result("ID2", "Data2")])) - ).await.unwrap(); + storage_items("Id1", &[storage_result("ID2", "Data2")]) + ).unwrap(); tx2.send( - Json(storage_items("Id1", &[storage_result("ID3", "Data3")])) - ).await.unwrap(); - tx2.send(Json(storage_done("Id1"))).await.unwrap(); + storage_items("Id1", &[storage_result("ID3", "Data3")]) + ).unwrap(); + tx2.send(storage_done("Id1")).unwrap(); }); Ok(Json(())) }) @@ -1191,7 +1163,7 @@ mod test { use subxt_rpcs::client::mock_rpc_client::Json; let hash = random_hash(); - let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { From 141608f0f57153694f2f73dab1bb4df53ed0bc41 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Thu, 6 Feb 2025 18:02:15 +0000 Subject: [PATCH 12/32] WIP, Tests mostly fixed, need to add back oen more --- Cargo.lock | 2 +- rpcs/src/client/mock_rpc_client.rs | 43 +++++++++++--- subxt/src/backend/mod.rs | 92 ++++++++++++++++++++++++------ 3 files changed, 112 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 533bb8488b1..d536f9e4bb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10722,7 +10722,7 @@ dependencies = [ [[package]] name = "subxt-rpcs" -version = "0.38.0" +version = "0.39.0" dependencies = [ "derive-where", "finito", diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 9dd2bdf4354..32c661bd80e 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -316,12 +316,6 @@ pub trait IntoHandlerResponse { fn into_handler_response(self) -> Result, Error>; } -impl IntoHandlerResponse for () { - fn into_handler_response(self) -> Result, Error> { - serialize_to_raw_value(&()) - } -} - impl IntoHandlerResponse for Result { fn into_handler_response(self) -> Result, Error> { self.and_then(|val| val.into_handler_response()) @@ -416,9 +410,15 @@ impl IntoSubscriptionResponse for toki } } -impl IntoSubscriptionResponse for Result { +impl IntoSubscriptionResponse for RawRpcSubscription { fn into_subscription_response(self) -> Result { - self + Ok(self) + } +} + +impl IntoSubscriptionResponse for Result { + fn into_subscription_response(self) -> Result { + self.and_then(|res| res.into_subscription_response()) } } @@ -490,6 +490,33 @@ impl IntoSubscription } } +/// Send back either one response or the other. +pub enum Either { + /// The first possibility. + A(A), + /// The second possibility. + B(B) +} + +impl IntoHandlerResponse for Either { + fn into_handler_response(self) -> Result, Error> { + match self { + Either::A(a) => a.into_handler_response(), + Either::B(b) => b.into_handler_response(), + } + } +} + +impl IntoSubscriptionResponse for Either { + fn into_subscription_response(self) -> Result { + match self { + Either::A(a) => a.into_subscription_response(), + Either::B(b) => b.into_subscription_response(), + } + } +} + + #[cfg(test)] mod test { use crate::{RpcClient, rpc_params}; diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 276b393de16..c2b88ff58bb 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -352,6 +352,7 @@ mod test { pub use std::collections::{HashMap, VecDeque}; pub use subxt_core::{config::DefaultExtrinsicParams, Config}; pub use tokio::sync::{mpsc, Mutex}; + pub use core::convert::Infallible; pub type RpcResult = Result; pub type Item = RpcResult; @@ -730,7 +731,7 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("state_getStorage", move |_params| async move { // Return "disconnected" error on first call - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) }) .method_handler_once("state_getStorage", move |_param| async move { // Return some hex encoded storage value on the next one @@ -772,7 +773,7 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("chain_getBlockHash", move |_params| async move { // Return "disconnected" error on first call - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) }) .method_handler_once("chain_getBlockHash", move |_params| async move { // Return the blockhash on next call @@ -859,6 +860,7 @@ mod test { use subxt_rpcs::methods::chain_head::{ self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent }; + use subxt_rpcs::UserError; use tokio::select; use super::chain_head::*; use super::*; @@ -907,22 +909,38 @@ mod test { /// Build a mock client which can handle `chainHead_v1_follow` subscriptions. /// Messages from the provided receiver are sent to the latest active subscription. fn mock_client_builder(recv: tokio::sync::mpsc::UnboundedReceiver) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen} }; + mock_client_builder_with_ids(recv, 0..) + } + + fn mock_client_builder_with_ids(recv: tokio::sync::mpsc::UnboundedReceiver, ids: I) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder + where + I: IntoIterator + Send, + I::IntoIter: Send + Sync + 'static, + { + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen, Either} }; + use subxt_rpcs::{Error, UserError}; let recv = Arc::new(tokio::sync::Mutex::new(recv)); + let mut ids = ids.into_iter(); MockRpcClient::builder() .subscription_handler("chainHead_v1_follow", move |_params, _unsub| { let recv = recv.clone(); + let id = ids.next(); + + // For each new follow subscription, we take messages from `recv` and pipe them to the output + // for the subscription (after an Initialized event). if the output is dropped/closed, we stop pulling + // messages from `recv`. let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); tokio::spawn(async move { let mut recv_guard = recv.lock().await; loop { select! { + // Channel closed, so stop pulling from `recv`. _ = tx.closed() => { - println!("CLOSED!"); break }, + // Relay messages from `recv` unless some error sending. Some(msg) = recv_guard.recv() => { if tx.send(Json(msg)).is_err() { break @@ -942,13 +960,33 @@ mod test { }, )), }); - - AndThen( - // First send an initialized event - (vec![Json(follow_event)], "chainHeadFollowSubscriptionId"), - // Next, send any events provided via the recv channel - rx - ) + + match id { + Some(id) => { + let follow_event = + FollowEvent::Initialized(Initialized::<::Hash> { + finalized_block_hashes: vec![random_hash()], + finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( + RuntimeVersionEvent { + spec: runtime_spec(), + }, + )), + }); + + let res = AndThen( + // First send an initialized event with new ID + (vec![Json(follow_event)], format!("chainHeadFollowSubscriptionId{id}")), + // Next, send any events provided via the recv channel + rx + ); + + Ok(res) + }, + None => { + // Ran out of subscription IDs; return an error. + Err(Error::User(UserError::method_not_found())) + } + } } }) } @@ -1042,7 +1080,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())); + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Otherwise, return that we'll start sending a response, and spawn @@ -1101,7 +1139,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Next call, return a storage item and then a "waiting for continue". @@ -1115,7 +1153,7 @@ mod test { }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // Next call; acknowledge the "continue" and return reamining storage items. @@ -1168,7 +1206,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // First call, return disconnected error. - Err::<(),_>(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_owned())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_owned())) }) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // Next call, return the hash. @@ -1184,7 +1222,29 @@ mod test { assert_eq!(hash, response_hash) } - /* #[tokio::test] + // TODO: WIP + // Failure as we do not wait for subscription id to be updated. + // see https://github.com/paritytech/subxt/issues/1567 + #[tokio::test] + async fn stale_subscription_id_failure() { + use subxt_rpcs::client::mock_rpc_client::Json; + + //let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + + // let rpc_client = mock_client_builder_with_ids(rx, [1,2]) + // .method_handler("chainHead_v1_storage", |_params| { + + // }) + // .build(); + + // 1. chainHead_v1_follow called. returns initialized event. then DisconnectedWillReconnect + // 2. chainHead_v1_storage called. If ID is wrong, we see limitReached error . We retry internally + // 10 times until rejecting the call. + // 3. calling next on driver should lead to chainHead_v1_follow being called again. Returns new subscription ID. + } + + /* + #[tokio::test] // Failure as we do not wait for subscription id to be updated. // see https://github.com/paritytech/subxt/issues/1567 async fn stale_subscription_id_failure() { From afc7048198ca483f87bc26c69d1e6432b1effe97 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 15:40:32 +0000 Subject: [PATCH 13/32] Get mock RPC tests working --- rpcs/src/client/mock_rpc_client.rs | 1 + subxt/src/backend/mod.rs | 541 +++++------------------------ 2 files changed, 92 insertions(+), 450 deletions(-) diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 32c661bd80e..aab7735404a 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -179,6 +179,7 @@ impl MockRpcClientBuilder { /// A mock RPC client that responds programmatically to requests. /// Useful for testing. +#[derive(Clone)] pub struct MockRpcClient { // These are all accessed for just long enough to call the method. The method // returns a future, but the method call itself isn't held for long. diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index c2b88ff58bb..dc1b003473f 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -337,25 +337,18 @@ pub struct StorageResponse { pub value: Vec, } -#[allow(warnings)]// TODO: Remove before merging #[cfg(test)] mod test { use super::*; - pub use crate::backend::rpc::{RawRpcFuture, RawRpcSubscription}; - pub use crate::backend::StorageResponse; - pub use futures::StreamExt; - pub use polkadot_sdk::sp_core; - pub use primitive_types::H256; - pub use rpc::RpcClientT; - pub use serde::Serialize; - pub use serde_json::value::RawValue; - pub use std::collections::{HashMap, VecDeque}; - pub use subxt_core::{config::DefaultExtrinsicParams, Config}; - pub use tokio::sync::{mpsc, Mutex}; - pub use core::convert::Infallible; - - pub type RpcResult = Result; - pub type Item = RpcResult; + use crate::backend::StorageResponse; + use futures::StreamExt; + use polkadot_sdk::sp_core; + use primitive_types::H256; + use rpc::RpcClientT; + use std::collections::{HashMap, VecDeque}; + use subxt_core::{config::DefaultExtrinsicParams, Config}; + use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, MockRpcClientBuilder} }; + use core::convert::Infallible; fn random_hash() -> H256 { H256::random() @@ -371,247 +364,6 @@ mod test { } } - pub mod rpc_client { - use super::*; - use std::time::Duration; - - pub type SubscriptionHandler = Box< - dyn for<'a> Fn( - &'a mut MockDataTable, - &'a mut Option, - Option>, - ) -> RawRpcFuture<'a, RawRpcSubscription> - + Send, - >; - - pub type MethodHandler = Box< - dyn for<'a> Fn( - &'a mut MockDataTable, - &'a mut Option, - Option>, - ) -> RawRpcFuture<'a, Box> - + Send, - >; - - pub enum Message { - Many(RpcResult>), - Single(T), - } - - impl Message { - pub fn unwrap_single(self) -> T { - match self { - Self::Single(s) => s, - _ => panic!("cannot unwrap_single on Message::Many"), - } - } - pub fn unwrap_many(self) -> RpcResult> { - match self { - Self::Many(s) => s, - _ => panic!("cannot unwrap_many on Message::Single"), - } - } - } - - #[derive(Default)] - pub struct MockDataTable { - items: HashMap, VecDeque>>, - } - - impl MockDataTable { - pub fn push(&mut self, key: Vec, item: Message>) { - let item = match item { - Message::Many(items) => Message::Many(items.map(|items| { - items - .into_iter() - .map(|item| item.map(|x| serde_json::to_string(&x).unwrap())) - .collect() - })), - Message::Single(item) => { - Message::Single(item.map(|x| serde_json::to_string(&x).unwrap())) - } - }; - self.items.entry(key).or_default().push_back(item); - } - - pub fn pop(&mut self, key: Vec) -> Message { - self.items.get_mut(&key).unwrap().pop_front().unwrap() - } - } - - pub struct Subscription { - sender: mpsc::Sender, - } - - impl Subscription { - pub fn new() -> (Self, mpsc::Receiver) { - let (sender, receiver) = mpsc::channel(32); - (Self { sender }, receiver) - } - - pub async fn write(&self, items: Message) { - match items { - Message::Many(items) => { - for i in items.unwrap() { - self.sender.send(i).await.unwrap() - } - } - Message::Single(item) => self.sender.send(item).await.unwrap(), - }; - } - - pub async fn write_delayed(&self, items: Message) { - let sender = self.sender.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(500)).await; - - match items { - Message::Many(items) => { - for i in items.unwrap() { - let _ = sender.send(i).await; - } - } - Message::Single(item) => sender.send(item).await.unwrap(), - }; - }); - } - } - - #[derive(Default)] - struct InnerMockedRpcClient { - data_table: MockDataTable, - subscription_channel: Option, - subscription_handlers: HashMap, - method_handlers: HashMap, - } - - impl InnerMockedRpcClient { - fn call<'a>( - &'a mut self, - method_handler: &str, - params: Option>, - ) -> RawRpcFuture<'a, Box> { - let method = self.method_handlers.get(method_handler).unwrap_or_else(|| { - panic!( - "no method named {} registered. Params: {:?}", - method_handler, params - ) - }); - - (*method)(&mut self.data_table, &mut self.subscription_channel, params) - } - - fn subscribe<'a>( - &'a mut self, - sub: &str, - params: Option>, - ) -> RawRpcFuture<'a, RawRpcSubscription> { - let sub = self.subscription_handlers.get(sub).unwrap_or_else(|| { - panic!( - "no subscription named {} registered. Params: {:?}", - sub, params - ) - }); - - (*sub)(&mut self.data_table, &mut self.subscription_channel, params) - } - } - - #[derive(Default)] - pub struct MockRpcBuilder { - data: InnerMockedRpcClient, - } - - impl MockRpcBuilder { - pub fn add_method(mut self, method_name: &str, method_handler: F) -> Self - where - F: Send - + for<'a> Fn( - &'a mut MockDataTable, - &'a mut Option, - Option>, - ) - -> RawRpcFuture<'a, Box> - + 'static, - { - self.data - .method_handlers - .insert(method_name.into(), Box::new(method_handler)); - self - } - - pub fn add_subscription( - mut self, - subscription_name: &str, - subscription_handler: F, - ) -> Self - where - F: Send - + for<'a> Fn( - &'a mut MockDataTable, - &'a mut Option, - Option>, - ) -> RawRpcFuture<'a, RawRpcSubscription> - + 'static, - { - self.data - .subscription_handlers - .insert(subscription_name.into(), Box::new(subscription_handler)); - self - } - - pub fn add_mock_data< - 'a, - T: Serialize, - I: IntoIterator>)>, - >( - mut self, - item: I, - ) -> Self { - let data = &mut self.data.data_table; - for (key, item) in item.into_iter() { - data.push(key.into(), item); - } - self - } - - pub fn build(self) -> MockRpcClient { - MockRpcClient { - data: Arc::new(Mutex::new(self.data)), - } - } - } - - pub struct MockRpcClient { - data: Arc>, - } - - impl RpcClientT for MockRpcClient { - fn request_raw<'a>( - &'a self, - method: &'a str, - params: Option>, - ) -> RawRpcFuture<'a, Box> { - Box::pin(async { - let mut data = self.data.lock().await; - data.call(method, params).await - }) - } - - fn subscribe_raw<'a>( - &'a self, - sub: &'a str, - params: Option>, - _unsub: &'a str, - ) -> RawRpcFuture<'a, RawRpcSubscription> { - Box::pin(async { - let mut data = self.data.lock().await; - data.subscribe(sub, params).await - }) - } - } - } - // Define dummy config enum Conf {} impl Config for Conf { @@ -629,12 +381,11 @@ mod test { use super::*; use crate::{ backend::legacy::{ - rpc_methods::{Bytes, RuntimeVersion}, + rpc_methods::RuntimeVersion, LegacyBackend, }, error::RpcError, }; - use rpc_client::*; use crate::backend::Backend; @@ -653,14 +404,8 @@ mod test { } } - fn bytes(str: &str) -> RpcResult> { - Ok(Some(Bytes(str.into()))) - } - #[tokio::test] async fn storage_fetch_values() { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, StateHolder} }; - // Map from storage key to responses, given out in order, when that key is requested. let mut values: HashMap<&str, VecDeque<_>> = HashMap::from_iter([ ( @@ -693,7 +438,7 @@ mod test { let key: sp_core::Bytes = rpc_params.sequence().next().unwrap(); let key = std::str::from_utf8(&key.0).unwrap(); // Fetch the response to use from our map, popping it from the front. - let mut values = values.get_mut(key).unwrap(); + let values = values.get_mut(key).unwrap(); let value = values.pop_front().unwrap(); async move { value } }) @@ -726,8 +471,6 @@ mod test { #[tokio::test] async fn storage_fetch_value() { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json, RawRpcFuture }; - let rpc_client = MockRpcClient::builder() .method_handler_once("state_getStorage", move |_params| async move { // Return "disconnected" error on first call @@ -750,7 +493,6 @@ mod test { assert_eq!("Data1".to_owned(), String::from_utf8(response).unwrap()) } - #[tokio::test] /// This test should cover the logic of the following methods: /// - `genesis_hash` /// - `block_header` @@ -765,10 +507,8 @@ mod test { /// retry(|| ).await /// } /// ``` + #[tokio::test] async fn simple_fetch() { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; - - let mut is_fst = true; let hash = random_hash(); let rpc_client = MockRpcClient::builder() .method_handler_once("chain_getBlockHash", move |_params| async move { @@ -788,7 +528,6 @@ mod test { assert_eq!(hash, response) } - #[tokio::test] /// This test should cover the logic of the following methods: /// - `stream_runtime_version` /// - `stream_all_block_headers` @@ -811,9 +550,8 @@ mod test { /// Ok(retry_sub) /// } /// ``` + #[tokio::test] async fn stream_simple() { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::Json }; - // Each time the subscription is called, it will pop the first set // of values from this and return them one after the other. let mut data = VecDeque::from_iter([ @@ -854,14 +592,12 @@ mod test { } mod unstable_backend { - use std::sync::atomic::AtomicBool; - use futures::task::Poll; - use rpc_client::{Message, MockRpcBuilder, Subscription}; use subxt_rpcs::methods::chain_head::{ self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent }; - use subxt_rpcs::UserError; use tokio::select; + use crate::error::RpcError; + use super::chain_head::*; use super::*; @@ -908,16 +644,16 @@ mod test { /// Build a mock client which can handle `chainHead_v1_follow` subscriptions. /// Messages from the provided receiver are sent to the latest active subscription. - fn mock_client_builder(recv: tokio::sync::mpsc::UnboundedReceiver) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder { + fn mock_client_builder(recv: tokio::sync::mpsc::UnboundedReceiver) -> MockRpcClientBuilder { mock_client_builder_with_ids(recv, 0..) } - fn mock_client_builder_with_ids(recv: tokio::sync::mpsc::UnboundedReceiver, ids: I) -> subxt_rpcs::client::mock_rpc_client::MockRpcClientBuilder + fn mock_client_builder_with_ids(recv: tokio::sync::mpsc::UnboundedReceiver, ids: I) -> MockRpcClientBuilder where I: IntoIterator + Send, I::IntoIter: Send + Sync + 'static, { - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, AndThen, Either} }; + use subxt_rpcs::client::mock_rpc_client::AndThen; use subxt_rpcs::{Error, UserError}; let recv = Arc::new(tokio::sync::Mutex::new(recv)); @@ -930,7 +666,7 @@ mod test { // For each new follow subscription, we take messages from `recv` and pipe them to the output // for the subscription (after an Initialized event). if the output is dropped/closed, we stop pulling - // messages from `recv`. + // messages from `recv`, waiting for a new chainHEad_v1_follow subscription. let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); tokio::spawn(async move { let mut recv_guard = recv.lock().await; @@ -951,46 +687,37 @@ mod test { }); async move { - let follow_event = - FollowEvent::Initialized(Initialized::<::Hash> { - finalized_block_hashes: vec![random_hash()], - finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( - RuntimeVersionEvent { - spec: runtime_spec(), - }, - )), - }); - - match id { - Some(id) => { - let follow_event = - FollowEvent::Initialized(Initialized::<::Hash> { - finalized_block_hashes: vec![random_hash()], - finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( - RuntimeVersionEvent { - spec: runtime_spec(), - }, - )), - }); - - let res = AndThen( - // First send an initialized event with new ID - (vec![Json(follow_event)], format!("chainHeadFollowSubscriptionId{id}")), - // Next, send any events provided via the recv channel - rx - ); - - Ok(res) - }, - None => { - // Ran out of subscription IDs; return an error. - Err(Error::User(UserError::method_not_found())) - } + if let Some(id) = id { + let follow_event = + FollowEvent::Initialized(Initialized::<::Hash> { + finalized_block_hashes: vec![random_hash()], + finalized_block_runtime: Some(chain_head::RuntimeEvent::Valid( + RuntimeVersionEvent { + spec: runtime_spec(), + }, + )), + }); + + let res = AndThen( + // First send an initialized event with new ID + (vec![Json(follow_event)], subscription_id(id)), + // Next, send any events provided via the recv channel + rx + ); + + Ok(res) + } else { + // Ran out of subscription IDs; return an error. + Err(Error::User(UserError::method_not_found())) } } }) } + fn subscription_id(id: usize) -> String { + format!("chainHeadFollowSubscriptionId{id}") + } + fn response_started(id: &str) -> MethodResponse { MethodResponse::Started(MethodResponseStarted { operation_id: id.to_owned(), @@ -1033,19 +760,21 @@ mod test { }) } + fn follow_event_stop() -> FollowEvent { + FollowEvent::Stop + } + #[tokio::test] async fn storage_fetch_values_returns_stream_with_single_error() { - use subxt_rpcs::client::mock_rpc_client::Json; - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) - .method_handler_once("chainHead_v1_storage", move |params| { + .method_handler_once("chainHead_v1_storage", move |_params| { tokio::spawn(async move { // Wait a little and then send an error response on the // chainHead_follow subscription: tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; - tx.send(operation_error("Id1")); + tx.send(operation_error("Id1")).unwrap(); }); async move { @@ -1070,11 +799,9 @@ mod test { assert!(response.next().await.is_none()); } - #[tokio::test] /// Tests that the method will retry on failed query + #[tokio::test] async fn storage_fetch_values_retry_query() { - use subxt_rpcs::client::mock_rpc_client::Json; - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) @@ -1127,12 +854,9 @@ mod test { response ) } + #[tokio::test] async fn storage_fetch_values_retry_chainhead_continue() { - use subxt_rpcs::client::mock_rpc_client::Json; - - let mut storage_call_num = 0; - let mut continue_call_num = 0; let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let tx2 = tx.clone(); @@ -1198,11 +922,8 @@ mod test { #[tokio::test] async fn simple_fetch() { - use subxt_rpcs::client::mock_rpc_client::Json; - let hash = random_hash(); - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - + let (_tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rpc_client = mock_client_builder(rx) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // First call, return disconnected error. @@ -1222,141 +943,61 @@ mod test { assert_eq!(hash, response_hash) } - // TODO: WIP - // Failure as we do not wait for subscription id to be updated. + // Check that the backend will resubscribe on Stop, and handle a change in subscription ID. // see https://github.com/paritytech/subxt/issues/1567 #[tokio::test] async fn stale_subscription_id_failure() { - use subxt_rpcs::client::mock_rpc_client::Json; - - //let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - - // let rpc_client = mock_client_builder_with_ids(rx, [1,2]) - // .method_handler("chainHead_v1_storage", |_params| { - - // }) - // .build(); - - // 1. chainHead_v1_follow called. returns initialized event. then DisconnectedWillReconnect - // 2. chainHead_v1_storage called. If ID is wrong, we see limitReached error . We retry internally - // 10 times until rejecting the call. - // 3. calling next on driver should lead to chainHead_v1_follow being called again. Returns new subscription ID. - } + let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); + let rpc_client = mock_client_builder_with_ids(rx, [1,2]) + .method_handler("chainHead_v1_storage", move |params| { + // Decode the follow subscription ID which is the first param. + let this_sub_id = { + let params = params.as_ref().map(|p| p.get()); + let rpc_params = jsonrpsee::types::Params::new(params); + rpc_params.sequence().next::().unwrap() + }; - /* - #[tokio::test] - // Failure as we do not wait for subscription id to be updated. - // see https://github.com/paritytech/subxt/issues/1567 - async fn stale_subscription_id_failure() { - let response_data = vec![ - ( - "method_response", - Message::Single(Ok(response_started("Id1"))), - ), - ( - "method_response", - Message::Single(Ok(limit_reached())), - ), - ( - "method_response", - Message::Single(Ok(response_started("Id1"))), - ), - ]; + // While it's equal to `subscription_id(1)`, it means we are seeing the first + // chainHead_follow subscription ID. error until we see an updated ID. + let is_wrong_sub_id = this_sub_id == subscription_id(1); - let mock_data = vec![ - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![Ok(operation_error("Id1")), Ok(FollowEvent::Stop)])), - ), - ( - "chainHead_v1_storage", - Message::Many(Ok(vec![ - Ok(storage_items( - "Id1", - &[ - storage_result("ID1", "Data1"), - storage_result("ID2", "Data2"), - storage_result("ID3", "Data3"), - ], - )), - Ok(storage_done("Id1")), - ])), - ), - ]; - let rpc_client = setup_mock_rpc_client(true) - .add_method("chainHead_v1_storage", { - let subscription_expired = Arc::new(AtomicBool::new(false)); - move |data, sub, params| { - let subscription_expired = subscription_expired.clone(); - Box::pin(async move { - let subscription_expired = subscription_expired.clone(); - if subscription_expired.load(std::sync::atomic::Ordering::SeqCst) { - let params = params.map(|p| p.get().to_string()); - let rpc_params = jsonrpsee::types::Params::new(params.as_deref()); - let key: String = rpc_params.sequence().next().unwrap(); - if key == *"ID1" { - return Message::Single(Ok(limit_reached())); - } else { - subscription_expired - .swap(false, std::sync::atomic::Ordering::SeqCst); - } - } - let response = data.pop("method_response".into()).unwrap_single(); - if response.is_ok() { - let item = data.pop("chainHead_v1_storage".into()); - if let Some(sub) = sub { - let item = item; - sub.write_delayed(item).await - } - } else { - subscription_expired - .swap(true, std::sync::atomic::Ordering::SeqCst); - } - response.map(|x| RawValue::from_string(x).unwrap()) - }) + async move { + if is_wrong_sub_id { + Json(limit_reached()) + } else { + Json(response_started("some_id")) + } } }) - .add_mock_data(mock_data) - .add_mock_data(response_data) .build(); + let (backend, mut driver): (ChainHeadBackend, _) = build_backend(rpc_client); - let _ = driver.next().await.unwrap(); + // Send a "FollowEvent::Stop" via chainhead_follow, and advance the driver just enough + // that this message has been processed. + tx.send(follow_event_stop()).unwrap(); let _ = driver.next().await.unwrap(); - // not getting new subscription id and hitting request rejected > 10 times + // If we make a storage call at this point, we'll still be passing the "old" subscription + // ID, because the driver hasn't advanced enough to start a new chainhead_follow subscription, + // and will therefore fail with a "limit reached" response (to emulate what would happen if + // the chainHead_v1_storage call was made with the wrong subscription ID). let response = backend - .storage_fetch_values( - ["ID1".into(), "ID2".into(), "ID3".into()].into(), - random_hash(), - ) + .storage_fetch_values(["ID1".into()].into(), random_hash()) .await; + assert!(matches!(response, Err(Error::Rpc(RpcError::LimitReached)))); + // Advance the driver until a new chainHead_follow subscription has been started up. + let _ = driver.next().await.unwrap(); + let _ = driver.next().await.unwrap(); let _ = driver.next().await.unwrap(); - let binding = response - .unwrap() - .collect::>>() - .await; - let response = binding.last().unwrap(); - - assert!(matches!( - response, - Err(Error::Other(reason)) if reason == "error" - )); - - // not getting new subscription id and hitting request rejected > 10 times + // Now, the ChainHeadBackend will use a new subscription ID and work. (If the driver + // advanced in the background automatically, this would happen automatically for us). let response = backend - .storage_fetch_values( - ["ID1".into(), "ID2".into(), "ID3".into()].into(), - random_hash(), - ) + .storage_fetch_values(["ID1".into()].into(), random_hash()) .await; - - assert!(matches!( - response, - Err(e) if e.to_string() == "stale id" - )) - } */ + assert!(response.is_ok()); + } } } From ed1a0545be8484ab99a20113ce7a2a590b477731 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 15:41:21 +0000 Subject: [PATCH 14/32] fmt --- subxt/src/backend/chain_head/mod.rs | 8 +- subxt/src/backend/chain_head/storage_items.rs | 4 +- subxt/src/backend/mod.rs | 149 +++++++++++------- 3 files changed, 93 insertions(+), 68 deletions(-) diff --git a/subxt/src/backend/chain_head/mod.rs b/subxt/src/backend/chain_head/mod.rs index 7349c0af423..a53a488fa25 100644 --- a/subxt/src/backend/chain_head/mod.rs +++ b/subxt/src/backend/chain_head/mod.rs @@ -361,9 +361,7 @@ impl Backend for ChainHeadBackend { let follow_events = self.follow_handle.subscribe().events(); let status = self.methods.chainhead_v1_body(&sub_id, at).await?; let operation_id = match status { - MethodResponse::LimitReached => { - return Err(RpcError::LimitReached.into()) - } + MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()), MethodResponse::Started(s) => s.operation_id, }; @@ -721,9 +719,7 @@ impl Backend for ChainHeadBackend { .chainhead_v1_call(&sub_id, at, method, call_parameters) .await?; let operation_id = match status { - MethodResponse::LimitReached => { - return Err(RpcError::LimitReached.into()) - } + MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()), MethodResponse::Started(s) => s.operation_id, }; diff --git a/subxt/src/backend/chain_head/storage_items.rs b/subxt/src/backend/chain_head/storage_items.rs index f140cefffb1..f4a3ef66a9c 100644 --- a/subxt/src/backend/chain_head/storage_items.rs +++ b/subxt/src/backend/chain_head/storage_items.rs @@ -45,9 +45,7 @@ impl StorageItems { .chainhead_v1_storage(&sub_id, at, queries, None) .await?; let operation_id: Arc = match status { - MethodResponse::LimitReached => { - return Err(RpcError::LimitReached.into()) - } + MethodResponse::LimitReached => return Err(RpcError::LimitReached.into()), MethodResponse::Started(s) => s.operation_id.into(), }; diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index dc1b003473f..25122131033 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -341,14 +341,17 @@ pub struct StorageResponse { mod test { use super::*; use crate::backend::StorageResponse; + use core::convert::Infallible; use futures::StreamExt; use polkadot_sdk::sp_core; use primitive_types::H256; use rpc::RpcClientT; use std::collections::{HashMap, VecDeque}; use subxt_core::{config::DefaultExtrinsicParams, Config}; - use subxt_rpcs::client::{ MockRpcClient, mock_rpc_client::{Json, MockRpcClientBuilder} }; - use core::convert::Infallible; + use subxt_rpcs::client::{ + mock_rpc_client::{Json, MockRpcClientBuilder}, + MockRpcClient, + }; fn random_hash() -> H256 { H256::random() @@ -380,10 +383,7 @@ mod test { mod legacy { use super::*; use crate::{ - backend::legacy::{ - rpc_methods::RuntimeVersion, - LegacyBackend, - }, + backend::legacy::{rpc_methods::RuntimeVersion, LegacyBackend}, error::RpcError, }; @@ -411,23 +411,22 @@ mod test { ( "ID1", VecDeque::from_iter([ - Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )), Ok(Json(hex::encode("Data1"))), - ]) + ]), ), ( "ID2", VecDeque::from_iter([ - Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )), Ok(Json(hex::encode("Data2"))), - ]) - ), - ( - "ID3", - VecDeque::from_iter([ - Ok(Json(hex::encode("Data3"))), - ]) + ]), ), + ("ID3", VecDeque::from_iter([Ok(Json(hex::encode("Data3")))])), ]); let rpc_client = MockRpcClient::builder() @@ -474,14 +473,16 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("state_getStorage", move |_params| async move { // Return "disconnected" error on first call - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )) }) .method_handler_once("state_getStorage", move |_param| async move { // Return some hex encoded storage value on the next one Json(hex::encode("Data1")) }) .build(); - + // Test let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); let response = backend @@ -513,7 +514,9 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("chain_getBlockHash", move |_params| async move { // Return "disconnected" error on first call - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )) }) .method_handler_once("chain_getBlockHash", move |_params| async move { // Return the blockhash on next call @@ -557,11 +560,15 @@ mod test { let mut data = VecDeque::from_iter([ vec![ Ok(Json(runtime_version(0))), - Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )), Ok(Json(runtime_version(1))), ], vec![ - Err(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_string())), + Err(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_string(), + )), Ok(Json(runtime_version(2))), Ok(Json(runtime_version(3))), ], @@ -569,7 +576,7 @@ mod test { Ok(Json(runtime_version(4))), Ok(Json(runtime_version(5))), Err(subxt_rpcs::Error::Client("..".into())), - ] + ], ]); let rpc_client = MockRpcClient::builder() @@ -583,20 +590,35 @@ mod test { let backend: LegacyBackend = LegacyBackend::builder().build(rpc_client); let mut results = backend.stream_runtime_version().await.unwrap(); - assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(0)); - assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(4)); - assert_eq!(results.next().await.unwrap().unwrap(), client_runtime_version(5)); - assert!(matches!(results.next().await.unwrap(), Err(Error::Rpc(RpcError::ClientError(subxt_rpcs::Error::Client(_)))))); + assert_eq!( + results.next().await.unwrap().unwrap(), + client_runtime_version(0) + ); + assert_eq!( + results.next().await.unwrap().unwrap(), + client_runtime_version(4) + ); + assert_eq!( + results.next().await.unwrap().unwrap(), + client_runtime_version(5) + ); + assert!(matches!( + results.next().await.unwrap(), + Err(Error::Rpc(RpcError::ClientError( + subxt_rpcs::Error::Client(_) + ))) + )); assert!(results.next().await.is_none()); } } mod unstable_backend { + use crate::error::RpcError; use subxt_rpcs::methods::chain_head::{ - self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent + self, Bytes, Initialized, MethodResponse, MethodResponseStarted, OperationError, + OperationId, OperationStorageItems, RuntimeSpec, RuntimeVersionEvent, }; use tokio::select; - use crate::error::RpcError; use super::chain_head::*; use super::*; @@ -644,13 +666,18 @@ mod test { /// Build a mock client which can handle `chainHead_v1_follow` subscriptions. /// Messages from the provided receiver are sent to the latest active subscription. - fn mock_client_builder(recv: tokio::sync::mpsc::UnboundedReceiver) -> MockRpcClientBuilder { + fn mock_client_builder( + recv: tokio::sync::mpsc::UnboundedReceiver, + ) -> MockRpcClientBuilder { mock_client_builder_with_ids(recv, 0..) } - - fn mock_client_builder_with_ids(recv: tokio::sync::mpsc::UnboundedReceiver, ids: I) -> MockRpcClientBuilder + + fn mock_client_builder_with_ids( + recv: tokio::sync::mpsc::UnboundedReceiver, + ids: I, + ) -> MockRpcClientBuilder where - I: IntoIterator + Send, + I: IntoIterator + Send, I::IntoIter: Send + Sync + 'static, { use subxt_rpcs::client::mock_rpc_client::AndThen; @@ -659,8 +686,9 @@ mod test { let recv = Arc::new(tokio::sync::Mutex::new(recv)); let mut ids = ids.into_iter(); - MockRpcClient::builder() - .subscription_handler("chainHead_v1_follow", move |_params, _unsub| { + MockRpcClient::builder().subscription_handler( + "chainHead_v1_follow", + move |_params, _unsub| { let recv = recv.clone(); let id = ids.next(); @@ -681,7 +709,7 @@ mod test { if tx.send(Json(msg)).is_err() { break } - } + } } } }); @@ -702,7 +730,7 @@ mod test { // First send an initialized event with new ID (vec![Json(follow_event)], subscription_id(id)), // Next, send any events provided via the recv channel - rx + rx, ); Ok(res) @@ -711,7 +739,8 @@ mod test { Err(Error::User(UserError::method_not_found())) } } - }) + }, + ) } fn subscription_id(id: usize) -> String { @@ -777,9 +806,7 @@ mod test { tx.send(operation_error("Id1")).unwrap(); }); - async move { - Json(response_started("Id1")) - } + async move { Json(response_started("Id1")) } }) .build(); @@ -795,7 +822,11 @@ mod test { .await .unwrap(); - assert!(response.next().await.unwrap().is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))); + assert!(response + .next() + .await + .unwrap() + .is_err_and(|e| matches!(e, Error::Other(e) if e == "error"))); assert!(response.next().await.is_none()); } @@ -807,7 +838,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Otherwise, return that we'll start sending a response, and spawn @@ -820,7 +851,8 @@ mod test { storage_result("ID2", "Data2"), storage_result("ID3", "Data3"), ], - )).unwrap(); + )) + .unwrap(); tx.send(storage_done("Id1")).unwrap(); }); @@ -829,7 +861,7 @@ mod test { }) .build(); - // Despite DisconnectedWillReconnect we try again transparently + // Despite DisconnectedWillReconnect we try again transparently // and get the data we asked for. let backend = build_backend_spawn_background(rpc_client); let response = backend @@ -839,7 +871,7 @@ mod test { ) .await .unwrap(); - + let response = response .map(|x| x.unwrap()) .collect::>() @@ -863,31 +895,28 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Next call, return a storage item and then a "waiting for continue". tokio::spawn(async move { - tx.send( - storage_items("Id1", &[storage_result("ID1", "Data1")]) - ).unwrap(); + tx.send(storage_items("Id1", &[storage_result("ID1", "Data1")])) + .unwrap(); tx.send(operation_continue("Id1")).unwrap(); }); Ok(Json(response_started("Id1"))) }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // Next call; acknowledge the "continue" and return reamining storage items. tokio::spawn(async move { - tx2.send( - storage_items("Id1", &[storage_result("ID2", "Data2")]) - ).unwrap(); - tx2.send( - storage_items("Id1", &[storage_result("ID3", "Data3")]) - ).unwrap(); + tx2.send(storage_items("Id1", &[storage_result("ID2", "Data2")])) + .unwrap(); + tx2.send(storage_items("Id1", &[storage_result("ID3", "Data3")])) + .unwrap(); tx2.send(storage_done("Id1")).unwrap(); }); Ok(Json(())) @@ -927,7 +956,9 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // First call, return disconnected error. - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".to_owned())) + Err::(subxt_rpcs::Error::DisconnectedWillReconnect( + "..".to_owned(), + )) }) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // Next call, return the hash. @@ -948,7 +979,7 @@ mod test { #[tokio::test] async fn stale_subscription_id_failure() { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - let rpc_client = mock_client_builder_with_ids(rx, [1,2]) + let rpc_client = mock_client_builder_with_ids(rx, [1, 2]) .method_handler("chainHead_v1_storage", move |params| { // Decode the follow subscription ID which is the first param. let this_sub_id = { @@ -963,7 +994,7 @@ mod test { async move { if is_wrong_sub_id { - Json(limit_reached()) + Json(limit_reached()) } else { Json(response_started("some_id")) } From 6c335c939897ac4517f438710cbe27c6a2088058 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 15:44:19 +0000 Subject: [PATCH 15/32] fmt --- lightclient/src/lib.rs | 4 +++- rpcs/src/client/mod.rs | 2 +- rpcs/src/lib.rs | 20 +++++++------------- rpcs/src/macros.rs | 4 ++-- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/lightclient/src/lib.rs b/lightclient/src/lib.rs index 8d2a4c58a28..0517b8d4449 100644 --- a/lightclient/src/lib.rs +++ b/lightclient/src/lib.rs @@ -61,7 +61,9 @@ pub struct JsonRpcError(Box); impl JsonRpcError { /// Attempt to deserialize this error into some type. - pub fn try_deserialize<'a, T: serde::de::Deserialize<'a>>(&'a self) -> Result { + pub fn try_deserialize<'a, T: serde::de::Deserialize<'a>>( + &'a self, + ) -> Result { serde_json::from_str(self.0.get()) } } diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index 85553b290f4..d442f1a7580 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -4,7 +4,7 @@ //! RPC types and client for interacting with a substrate node. //! -//! An RPC client is instantiated and then used to create some methods, for instance +//! An RPC client is instantiated and then used to create some methods, for instance //! [`crate::methods::ChainHeadRpcMethods`], which defines the calls that can be made with it. //! The core RPC client bits are: //! diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 1e73cfe910e..2edc7a8dc76 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -102,12 +102,12 @@ impl Error { #[derive(Debug, Clone, serde::Deserialize, thiserror::Error)] #[serde(deny_unknown_fields)] pub struct UserError { - /// Code - pub code: i32, - /// Message - pub message: String, - /// Optional data - pub data: Option>, + /// Code + pub code: i32, + /// Message + pub message: String, + /// Optional data + pub data: Option>, } impl UserError { @@ -116,7 +116,7 @@ impl UserError { UserError { code: -32601, message: "Method not found".to_owned(), - data: None + data: None, } } } @@ -126,9 +126,3 @@ impl core::fmt::Display for UserError { write!(f, "{} ({})", &self.message, &self.code) } } - -#[cfg(test)] -mod test { - - -} \ No newline at end of file diff --git a/rpcs/src/macros.rs b/rpcs/src/macros.rs index 71f8eb5bb80..dc6b8fa195b 100644 --- a/rpcs/src/macros.rs +++ b/rpcs/src/macros.rs @@ -41,6 +41,6 @@ macro_rules! cfg_mock_rpc_client { } pub(crate) use { - cfg_feature, cfg_jsonrpsee, cfg_mock_rpc_client, cfg_reconnecting_rpc_client, cfg_unstable_light_client, + cfg_feature, cfg_jsonrpsee, cfg_mock_rpc_client, cfg_reconnecting_rpc_client, + cfg_unstable_light_client, }; - From 35276da049309999b3edc8776e8210e315edf171 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 16:25:09 +0000 Subject: [PATCH 16/32] Clippy and comment tweak --- rpcs/Cargo.toml | 2 +- rpcs/src/client/mock_rpc_client.rs | 5 ++--- rpcs/src/client/rpc_client.rs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 9204f3f0eba..3ed0ab9d5b6 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -15,7 +15,7 @@ description = "Make RPC calls to Substrate based nodes" keywords = ["parity", "subxt", "rpcs"] [features] -default = ["jsonrpsee", "native", "mock-rpc-client"] +default = ["jsonrpsee", "native"] subxt-core = ["dep:subxt-core"] jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index aab7735404a..68d09aa7601 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -16,9 +16,8 @@ //! Json(3u8), //! ]; //! -//! // Define a mock client by providing some state (can be optional) -//! // and functions which intercept method and subscription calls and -//! // returns something back. +//! // Define a mock client by providing some functions which intercept +//! // method and subscription calls and return some response. //! let mock_client = MockRpcClient::builder() //! .method_handler_once("foo", move |params| { //! // Return each item from our state, and then null afterwards. diff --git a/rpcs/src/client/rpc_client.rs b/rpcs/src/client/rpc_client.rs index e2f060c1193..f3a19dfabe4 100644 --- a/rpcs/src/client/rpc_client.rs +++ b/rpcs/src/client/rpc_client.rs @@ -235,7 +235,7 @@ impl Stream for RpcSubscription { // Decode the inner RawValue to the type we're expecting and map // any errors to the right shape: let res = res.map(|r| { - r.map_err(|e| e.into()).and_then(|raw_val| { + r.and_then(|raw_val| { serde_json::from_str(raw_val.get()).map_err(Error::Deserialization) }) }); From 7d792978aef72404fe26aa7fa673d6632e6380f2 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 16:29:16 +0000 Subject: [PATCH 17/32] update CI to explicitly check subxt-rpc features --- .github/workflows/rust.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 355f6e26b95..b21cfacd046 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -191,13 +191,24 @@ jobs: cargo check -p subxt-signer --no-default-features --features ecdsa cargo check -p subxt-signer --no-default-features --features unstable-eth + # Subxt-rpcs has a bunch of clients that can be exposed. Check that they all stand on their own. + - name: Cargo check subxt-rpcs + run: | + cargo check -p subxt-rpcs + cargo check -p subxt-rpcs --no-default-features --features web + cargo check -p subxt-rpcs --no-default-features --features native + cargo check -p subxt-rpcs --no-default-features --features native,jsonrpsee + cargo check -p subxt-rpcs --no-default-features --features native,reconnecting-rpc-client + cargo check -p subxt-rpcs --no-default-features --features native,mock-rpc-client + cargo check -p subxt-rpcs --no-default-features --features native,unstable-light-client + # We can't enable web features here, so no cargo hack. - name: Cargo check subxt-lightclient run: cargo check -p subxt-lightclient # Next, check each other package in isolation. - name: Cargo hack; check each feature/crate on its own - run: cargo hack --exclude subxt --exclude subxt-signer --exclude subxt-lightclient --exclude-all-features --each-feature check --workspace + run: cargo hack --exclude subxt --exclude subxt-signer --exclude subxt-lightclient --exclude subxt-rpcs --exclude-all-features --each-feature check --workspace # Check the parachain-example code, which isn't a part of the workspace so is otherwise ignored. - name: Cargo check parachain-example From 5d79b0d9745f513344d2415a5ce1c8002fdf4277 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 16:36:23 +0000 Subject: [PATCH 18/32] clippy --- rpcs/src/client/mock_rpc_client.rs | 8 ++++---- subxt/src/backend/chain_head/storage_items.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 68d09aa7601..5ca478e3ae9 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -287,15 +287,15 @@ impl StateHolder { } } -impl <'a, T> core::ops::Deref for StateHolderGuard<'a, T> { +impl core::ops::Deref for StateHolderGuard<'_, T> { type Target = T; fn deref(&self) -> &Self::Target { - &*self.0 + &self.0 } } -impl <'a, T> core::ops::DerefMut for StateHolderGuard<'a, T> { +impl core::ops::DerefMut for StateHolderGuard<'_, T> { fn deref_mut(&mut self) -> &mut Self::Target { - &mut *self.0 + &mut self.0 } } diff --git a/subxt/src/backend/chain_head/storage_items.rs b/subxt/src/backend/chain_head/storage_items.rs index f4a3ef66a9c..ec4f2635bec 100644 --- a/subxt/src/backend/chain_head/storage_items.rs +++ b/subxt/src/backend/chain_head/storage_items.rs @@ -58,10 +58,10 @@ impl StorageItems { let methods = methods.clone(); Box::pin(async move { - let cont = methods + methods .chainhead_v1_continue(&sub_id, &operation_id) .await?; - Ok(cont) + Ok(()) }) }) }; From a13931230ebb4cdbfca7b1a3b685c282ff7af8a1 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 17:07:13 +0000 Subject: [PATCH 19/32] small tweaks after pass over --- Cargo.toml | 2 +- rpcs/Cargo.toml | 2 +- rpcs/src/client/mock_rpc_client.rs | 38 ------------------ rpcs/src/client/mod.rs | 8 ++++ rpcs/src/lib.rs | 18 +++++---- rpcs/src/methods/legacy.rs | 64 +++++++++++++++++++++++++++++- signer/tests/no-std/.gitignore | 1 - signer/tests/wasm/.gitignore | 1 - subxt/Cargo.toml | 4 +- 9 files changed, 84 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c7a2c07f9df..09b9e57dda5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ members = [ "signer", "subxt", "scripts/artifacts", - "utils/fetch-metadata", "rpcs", + "utils/fetch-metadata", ] # We exclude any crates that would depend on non mutually diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 3ed0ab9d5b6..80f17fdcc9c 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -17,7 +17,7 @@ keywords = ["parity", "subxt", "rpcs"] [features] default = ["jsonrpsee", "native"] -subxt-core = ["dep:subxt-core"] +subxt = ["dep:subxt-core"] jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"] unstable-light-client = [ diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 5ca478e3ae9..7f40acb3ecb 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -261,44 +261,6 @@ impl RpcClientT for MockRpcClient { } } -/// State passed to each handler. -pub struct StateHolder(Arc>); - -/// A guard for state that is being accessed. -pub struct StateHolderGuard<'a, T>(tokio::sync::MutexGuard<'a, T>); - -impl StateHolder { - /// Get the inner state - pub async fn get(&self) -> StateHolderGuard { - StateHolderGuard(self.0.lock().await) - } - - /// Set the inner state to a new value, returning the old state. - pub async fn set(&self, new: T) -> T { - let mut guard = self.0.lock().await; - std::mem::replace(&mut *guard, new) - } - - /// Update the inner state, returning the old state. - pub async fn update T>(&self, f: F) -> T { - let mut guard = self.0.lock().await; - let new = f(&guard); - std::mem::replace(&mut *guard, new) - } -} - -impl core::ops::Deref for StateHolderGuard<'_, T> { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} -impl core::ops::DerefMut for StateHolderGuard<'_, T> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - /// Return responses wrapped in this to have them serialized to JSON. pub struct Json(pub T); diff --git a/rpcs/src/client/mod.rs b/rpcs/src/client/mod.rs index d442f1a7580..0e4e29c0c0b 100644 --- a/rpcs/src/client/mod.rs +++ b/rpcs/src/client/mod.rs @@ -16,6 +16,14 @@ //! //! We then expose implementations here (depending on which features are enabled) //! which implement [`RpcClientT`] and can therefore be used to construct [`RpcClient`]s. +//! +//! - **jsonrpsee**: Enable an RPC client based on `jsonrpsee`. +//! - **unstable-light-client**: Enable an RPC client which uses the Smoldot light client under +//! the hood to communicate with the network of choice. +//! - **reconnecting-rpc-client**: Enable an RPC client based on `jsonrpsee` which handles +//! reconnecting automatically in the event of network issues. +//! - **mock-rpc-client**: Enable a mock RPC client that can be used in tests. +//! crate::macros::cfg_jsonrpsee! { mod jsonrpsee_impl; diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 2edc7a8dc76..37954433d89 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -43,7 +43,9 @@ impl BlockHash for T where T: serde::de::DeserializeOwned + serde::Serialize pub trait AccountId: serde::Serialize {} impl AccountId for T where T: serde::Serialize {} -#[cfg(feature = "subxt-core")] +// When the subxt feature is enabled, ensure that any valid `subxt::Config` +// is also a valid `RpcConfig`. +#[cfg(feature = "subxt")] mod impl_config { use super::*; impl RpcConfig for T @@ -68,10 +70,9 @@ pub enum Error { /// An error coming from the underlying RPC Client. #[error("RPC error: client error: {0}")] Client(Box), - /// The connection was lost and automatically reconnected. Reconnecting clients - /// should only emit this when they plan to try reconnecting internally, in which - /// case they should buffer anycalls made to them until they have reconnected and - /// then send them off in the order given. + /// The connection was lost and the client will automatically reconnect. Clients + /// should only emit this if they are internally reconnecting, and will buffer any + /// calls made to them in the meantime until the connection is re-established. #[error("RPC error: the connection was lost ({0}); reconnect automatically initiated")] DisconnectedWillReconnect(String), /// Cannot deserialize the response. @@ -94,11 +95,12 @@ impl Error { } } -/// This error tends to be returned when the user made an RPC call with -/// invalid parameters. Implementations of [`RpcClientT`] should turn any such +/// This error should be returned when the user is at fault making a call, +/// for instance because the method name was wrong, parameters invalid or some +/// invariant not upheld. Implementations of [`RpcClientT`] should turn any such /// errors into this, so that they can be handled appropriately. By contrast, /// [`Error::Client`] is emitted when the underlying RPC Client implementation -/// has some problem that isn't user specific (eg network issue or similar). +/// has some problem that isn't user specific (eg network issues or similar). #[derive(Debug, Clone, serde::Deserialize, thiserror::Error)] #[serde(deny_unknown_fields)] pub struct UserError { diff --git a/rpcs/src/methods/legacy.rs b/rpcs/src/methods/legacy.rs index a2b7690f911..0043c8801f1 100644 --- a/rpcs/src/methods/legacy.rs +++ b/rpcs/src/methods/legacy.rs @@ -390,10 +390,10 @@ impl LegacyRpcMethods { &self, encoded_signed: &[u8], at: Option, - ) -> Result, Error> { + ) -> Result { let params = rpc_params![to_hex(encoded_signed), at]; let result_bytes: Bytes = self.client.request("system_dryRun", params).await?; - Ok(result_bytes.0) + Ok(DryRunResultBytes(result_bytes.0)) } } @@ -524,6 +524,66 @@ pub enum TransactionStatus { Invalid, } +/// The decoded result returned from calling `system_dryRun` on some extrinsic. +#[derive(Debug, PartialEq, Eq)] +pub enum DryRunResult<'a> { + /// The transaction could be included in the block and executed. + Success, + /// The transaction could be included in the block, but the call failed to dispatch. + /// If Subxt is available, the bytes here can be further decoded by calling: + /// + /// ```rust,ignore + /// subxt::error::DispatchError::decode_from(bytes, metadata)?; + /// ``` + /// + /// Where metadata is an instance of `subxt::Metadata` that is valid for the runtime + /// version which returned this error. + DispatchError(&'a [u8]), + /// The transaction could not be included in the block. + TransactionValidityError, +} + +/// The bytes representing an error dry running an extrinsic. call [`DryRunResultBytes::into_dry_run_result`] +/// to attempt to decode this into something more meaningful. +pub struct DryRunResultBytes(pub Vec); + +impl DryRunResultBytes { + /// Attempt to decode the error bytes into a [`DryRunResult`]. + pub fn into_dry_run_result(&self) -> Result, DryRunDecodeError> { + // dryRun returns an ApplyExtrinsicResult, which is basically a + // `Result, TransactionValidityError>`. + let bytes = &*self.0; + + // We expect at least 2 bytes. In case we got a naff response back (or + // manually constructed this struct), just error to avoid a panic: + if bytes.len() < 2 { + return Err(DryRunDecodeError::WrongNumberOfBytes); + } + + if bytes[0] == 0 && bytes[1] == 0 { + // Ok(Ok(())); transaction is valid and executed ok + Ok(DryRunResult::Success) + } else if bytes[0] == 0 && bytes[1] == 1 { + // Ok(Err(dispatch_error)); transaction is valid but execution failed + Ok(DryRunResult::DispatchError(&bytes[2..])) + } else if bytes[0] == 1 { + // Err(transaction_error); some transaction validity error (we ignore the details at the moment) + Ok(DryRunResult::TransactionValidityError) + } else { + // unable to decode the bytes; they aren't what we expect. + Err(DryRunDecodeError::InvalidBytes) + } + } +} + +/// An error which can be emitted when calling [`DryRunResultBytes::into_dry_run_result`]. +pub enum DryRunDecodeError { + /// The dry run result was less than 2 bytes, which is invalid. + WrongNumberOfBytes, + /// The dry run bytes are not valid. + InvalidBytes, +} + /// Storage change set #[derive(Clone, Serialize, Deserialize, PartialEq, Eq, Debug)] #[serde(rename_all = "camelCase")] diff --git a/signer/tests/no-std/.gitignore b/signer/tests/no-std/.gitignore index 96ef6c0b944..ea8c4bf7f35 100644 --- a/signer/tests/no-std/.gitignore +++ b/signer/tests/no-std/.gitignore @@ -1,2 +1 @@ /target -Cargo.lock diff --git a/signer/tests/wasm/.gitignore b/signer/tests/wasm/.gitignore index 96ef6c0b944..ea8c4bf7f35 100644 --- a/signer/tests/wasm/.gitignore +++ b/signer/tests/wasm/.gitignore @@ -1,2 +1 @@ /target -Cargo.lock diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index fb8476b4fcf..2935cf1ff17 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -74,7 +74,7 @@ unstable-metadata = [] # Activate this to expose the Light Client functionality. # Note that this feature is experimental and things may break or not work as expected. -unstable-light-client = ["subxt-lightclient"] +unstable-light-client = ["subxt-lightclient", "subxt-rpcs/unstable-light-client"] # Activate this to expose the ability to generate metadata from Wasm runtime files. runtime-metadata-path = ["subxt-macro/runtime-metadata-path"] @@ -110,7 +110,7 @@ subxt-macro = { workspace = true } subxt-core = { workspace = true, features = ["std"] } subxt-metadata = { workspace = true, features = ["std"] } subxt-lightclient = { workspace = true, optional = true, default-features = false } -subxt-rpcs = { workspace = true, features = ["subxt-core"] } +subxt-rpcs = { workspace = true, features = ["subxt"] } # For parsing urls to disallow insecure schemes url = { workspace = true } From 4813af3121734451d2cc2ed5609b83aed04975d0 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 17:13:30 +0000 Subject: [PATCH 20/32] feature flag rename --- subxt/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 2935cf1ff17..8ec1c1e61c7 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -135,7 +135,7 @@ tokio = { workspace = true, features = ["macros", "time", "rt-multi-thread", "sy polkadot-sdk = { workspace = true, features = ["sp-core", "sp-keyring", "sp-runtime", "std"] } assert_matches = { workspace = true } subxt-signer = { path = "../signer", features = ["unstable-eth"] } -subxt-rpcs = { workspace = true, features = ["subxt-core", "mock-rpc-client"] } +subxt-rpcs = { workspace = true, features = ["subxt", "mock-rpc-client"] } # Tracing subscriber is useful for light-client examples to ensure that # the `bootNodes` and chain spec are configured correctly. If all is fine, then # the light-client wlll emit INFO logs with From 52f6bf6dc51e1f795d1512cb77d77d38e0444b59 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 17:30:45 +0000 Subject: [PATCH 21/32] update some docs --- RELEASING.md | 1 + rpcs/src/client/rpc_client.rs | 5 ++--- rpcs/src/client/rpc_client_t.rs | 4 ++-- subxt/src/backend/chain_head/mod.rs | 5 +++++ subxt/src/backend/legacy.rs | 2 +- subxt/src/book/usage/rpc.rs | 12 +++++++----- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 6fa609649e9..5dc244a9b0f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -86,6 +86,7 @@ We also assume that ongoing work done is being merged directly to the `master` b ``` (cd core && cargo publish) && \ + (cd rpcs && cargo publish) && \ (cd subxt && cargo publish) && \ (cd signer && cargo publish) && \ (cd cli && cargo publish); diff --git a/rpcs/src/client/rpc_client.rs b/rpcs/src/client/rpc_client.rs index f3a19dfabe4..f7a6ccf8b87 100644 --- a/rpcs/src/client/rpc_client.rs +++ b/rpcs/src/client/rpc_client.rs @@ -9,9 +9,8 @@ use serde::{de::DeserializeOwned, Serialize}; use serde_json::value::RawValue; use std::{pin::Pin, sync::Arc, task::Poll}; -/// A concrete wrapper around an [`RpcClientT`] which provides some higher level helper methods, -/// is cheaply cloneable, and can be handed to things like [`crate::client::OnlineClient`] to -/// instantiate it. +/// A concrete wrapper around an [`RpcClientT`] which provides some higher level helper methods +/// and is cheaply cloneable. #[derive(Clone)] pub struct RpcClient { client: Arc, diff --git a/rpcs/src/client/rpc_client_t.rs b/rpcs/src/client/rpc_client_t.rs index 697331ca439..3ef0634fc9f 100644 --- a/rpcs/src/client/rpc_client_t.rs +++ b/rpcs/src/client/rpc_client_t.rs @@ -10,8 +10,8 @@ use std::{future::Future, pin::Pin}; pub use serde_json::value::RawValue; /// A trait describing low level JSON-RPC interactions. Implementations of this can be -/// used to instantiate a [`super::RpcClient`], which can be passed to [`crate::OnlineClient`] -/// or used for lower level RPC calls via eg [`crate::backend::legacy::LegacyRpcMethods`]. +/// used to instantiate a [`super::RpcClient`], used for lower level RPC calls via eg +/// [`crate::methods::LegacyRpcMethods`] and [`crate::methods::ChainHeadRpcMethods`]. /// /// This is a low level interface whose methods expect an already-serialized set of params, /// and return an owned but still-serialized [`RawValue`], deferring deserialization to diff --git a/subxt/src/backend/chain_head/mod.rs b/subxt/src/backend/chain_head/mod.rs index a53a488fa25..f09148d6cd5 100644 --- a/subxt/src/backend/chain_head/mod.rs +++ b/subxt/src/backend/chain_head/mod.rs @@ -36,6 +36,11 @@ use subxt_rpcs::methods::chain_head::{ }; use subxt_rpcs::RpcClient; +/// Re-export RPC types and methods from [`subxt_rpcs::methods::chain_head`]. +pub mod rpc_methods { + pub use subxt_rpcs::methods::legacy::*; +} + // Expose the RPC methods. pub use subxt_rpcs::methods::chain_head::ChainHeadRpcMethods; diff --git a/subxt/src/backend/legacy.rs b/subxt/src/backend/legacy.rs index 4e6ae3ee7da..ef42acbbdb7 100644 --- a/subxt/src/backend/legacy.rs +++ b/subxt/src/backend/legacy.rs @@ -20,7 +20,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use subxt_rpcs::RpcClient; -/// Re-export legacy RPC types from [`subxt_rpcs::methods::legacy`]. +/// Re-export legacy RPC types and methods from [`subxt_rpcs::methods::legacy`]. pub mod rpc_methods { pub use subxt_rpcs::methods::legacy::*; } diff --git a/subxt/src/book/usage/rpc.rs b/subxt/src/book/usage/rpc.rs index 3b4fae4b149..6c58257d33e 100644 --- a/subxt/src/book/usage/rpc.rs +++ b/subxt/src/book/usage/rpc.rs @@ -4,12 +4,14 @@ //! # RPC calls //! -//! Subxt exposes low level interfaces that can be used to make RPC requests; [`crate::backend::legacy::rpc_methods`] -//! and [`crate::backend::chain_head::rpc_methods`]. +//! The RPC interface is provided by the [`subxt_rpcs`] crate but re-exposed here. We have: //! -//! These interfaces cannot be accessed directly through an [`crate::OnlineClient`]; this is so that the high level -//! Subxt APIs can target either the "legacy" or the more modern "unstable" sets of RPC methods by selecting an appropriate -//! [`crate::backend::Backend`]. It also means that there could exist a backend in the future that doesn't use JSON-RPC at all. +//! - [`crate::backend::rpc::RpcClient`] and [`crate::backend::rpc::RpcClientT`]: the underlying type and trait +//! which provides a basic RPC client. +//! - [`crate::backend::legacy::rpc_methods`] and [`crate::backend::chain_head::rpc_methods`]: RPc methods that +//! can be instantiated with an RPC client. +//! +//! See [`subxt_rpcs`] or [`crate::ext::subxt_rpcs`] for more. //! //! # Example //! From 353c997a29124f40ee33b4d8cb9a9dd5fa305aa9 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 17:53:33 +0000 Subject: [PATCH 22/32] Fix some examples --- .github/workflows/rust.yml | 1 + rpcs/src/client/mock_rpc_client.rs | 4 +- .../src/client/reconnecting_rpc_client/mod.rs | 37 +----------------- subxt/src/backend/mod.rs | 39 +++++++++++++++++++ 4 files changed, 43 insertions(+), 38 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b21cfacd046..8dc2070cf63 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -197,6 +197,7 @@ jobs: cargo check -p subxt-rpcs cargo check -p subxt-rpcs --no-default-features --features web cargo check -p subxt-rpcs --no-default-features --features native + cargo check -p subxt-rpcs --no-default-features --features native,subxt cargo check -p subxt-rpcs --no-default-features --features native,jsonrpsee cargo check -p subxt-rpcs --no-default-features --features native,reconnecting-rpc-client cargo check -p subxt-rpcs --no-default-features --features native,mock-rpc-client diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 7f40acb3ecb..0140b03cd08 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -21,8 +21,8 @@ //! let mock_client = MockRpcClient::builder() //! .method_handler_once("foo", move |params| { //! // Return each item from our state, and then null afterwards. -//! let val = state.pop() -//! async move { Json(val) } +//! let val = state.pop(); +//! async move { val } //! }) //! .subscription_handler("bar", |params, unsub| async move { //! // Arrays, vecs or an RpcSubscription can be returned here to diff --git a/rpcs/src/client/reconnecting_rpc_client/mod.rs b/rpcs/src/client/reconnecting_rpc_client/mod.rs index c2c34b1aaec..9d039272360 100644 --- a/rpcs/src/client/reconnecting_rpc_client/mod.rs +++ b/rpcs/src/client/reconnecting_rpc_client/mod.rs @@ -10,42 +10,7 @@ //! //! The logic which action to take for individual calls and subscriptions are //! handled by the subxt backend implementations. -//! -//! # Example -//! -//! ```no_run -//! use std::time::Duration; -//! use futures::StreamExt; -//! use subxt::backend::rpc::reconnecting_rpc_client::{RpcClient, ExponentialBackoff}; -//! use subxt::{OnlineClient, PolkadotConfig}; -//! -//! #[tokio::main] -//! async fn main() { -//! let rpc = RpcClient::builder() -//! .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) -//! .build("ws://localhost:9944".to_string()) -//! .await -//! .unwrap(); -//! -//! let subxt_client: OnlineClient = OnlineClient::from_rpc_client(rpc.clone()).await.unwrap(); -//! let mut blocks_sub = subxt_client.blocks().subscribe_finalized().await.unwrap(); -//! -//! while let Some(block) = blocks_sub.next().await { -//! let block = match block { -//! Ok(b) => b, -//! Err(e) => { -//! if e.is_disconnected_will_reconnect() { -//! println!("The RPC connection was lost and we may have missed a few blocks"); -//! continue; -//! } else { -//! panic!("Error: {}", e); -//! } -//! } -//! }; -//! println!("Block #{} ({})", block.number(), block.hash()); -//! } -//! } -//! ``` +//! mod platform; #[cfg(test)] diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 25122131033..c21ab0cd69b 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -23,9 +23,48 @@ use subxt_core::client::RuntimeVersion; /// Some re-exports from the [`subxt_rpcs`] crate, also accessible in full via [`crate::ext::subxt_rpcs`]. pub mod rpc { pub use subxt_rpcs::client::{RawRpcFuture, RawRpcSubscription, RawValue}; + crate::macros::cfg_reconnecting_rpc_client! { + /// An RPC client that automatically reconnects. + /// + /// # Example + /// + /// ```rust,no_run + /// use std::time::Duration; + /// use futures::StreamExt; + /// use subxt::backend::rpc::reconnecting_rpc_client::{RpcClient, ExponentialBackoff}; + /// use subxt::{OnlineClient, PolkadotConfig}; + /// + /// #[tokio::main] + /// async fn main() { + /// let rpc = RpcClient::builder() + /// .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + /// .build("ws://localhost:9944".to_string()) + /// .await + /// .unwrap(); + /// + /// let subxt_client: OnlineClient = OnlineClient::from_rpc_client(rpc.clone()).await.unwrap(); + /// let mut blocks_sub = subxt_client.blocks().subscribe_finalized().await.unwrap(); + /// + /// while let Some(block) = blocks_sub.next().await { + /// let block = match block { + /// Ok(b) => b, + /// Err(e) => { + /// if e.is_disconnected_will_reconnect() { + /// println!("The RPC connection was lost and we may have missed a few blocks"); + /// continue; + /// } else { + /// panic!("Error: {}", e); + /// } + /// } + /// }; + /// println!("Block #{} ({})", block.number(), block.hash()); + /// } + /// } + /// ``` pub use subxt_rpcs::client::reconnecting_rpc_client; } + pub use subxt_rpcs::{RpcClient, RpcClientT}; } From b09fe2a2ca86440825b878c9e0ca6939cd0e589d Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 7 Feb 2025 17:57:17 +0000 Subject: [PATCH 23/32] fmt --- subxt/src/backend/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index c21ab0cd69b..5d0deee6605 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -26,9 +26,9 @@ pub mod rpc { crate::macros::cfg_reconnecting_rpc_client! { /// An RPC client that automatically reconnects. - /// + /// /// # Example - /// + /// /// ```rust,no_run /// use std::time::Duration; /// use futures::StreamExt; @@ -45,7 +45,7 @@ pub mod rpc { /// /// let subxt_client: OnlineClient = OnlineClient::from_rpc_client(rpc.clone()).await.unwrap(); /// let mut blocks_sub = subxt_client.blocks().subscribe_finalized().await.unwrap(); - /// + /// /// while let Some(block) = blocks_sub.next().await { /// let block = match block { /// Ok(b) => b, From 3da4138d14767277ae13ac347e013d78cd4576a3 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 10 Feb 2025 11:44:14 +0000 Subject: [PATCH 24/32] Fix features flags to work with web/wasm32 --- .github/workflows/rust.yml | 6 +++++- Cargo.lock | 3 ++- rpcs/Cargo.toml | 6 ++++++ subxt/Cargo.toml | 13 ++----------- subxt/src/lib.rs | 5 ----- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8dc2070cf63..76007f7be0b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -195,7 +195,6 @@ jobs: - name: Cargo check subxt-rpcs run: | cargo check -p subxt-rpcs - cargo check -p subxt-rpcs --no-default-features --features web cargo check -p subxt-rpcs --no-default-features --features native cargo check -p subxt-rpcs --no-default-features --features native,subxt cargo check -p subxt-rpcs --no-default-features --features native,jsonrpsee @@ -237,6 +236,11 @@ jobs: - name: Rust Cache uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7 + - name: Cargo check web features which require wasm32 target. + run: | + cargo check -p subxt-rpcs --no-default-features --features web + cargo check -p subxt-rpcs --no-default-features --features web,reconnecting-rpc-client + # Check WASM examples, which aren't a part of the workspace and so are otherwise missed: - name: Cargo check WASM examples run: | diff --git a/Cargo.lock b/Cargo.lock index d536f9e4bb5..0f4206eb1a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10554,7 +10554,6 @@ dependencies = [ "either", "frame-metadata 18.0.0", "futures", - "getrandom", "hex", "http-body", "hyper", @@ -10728,6 +10727,7 @@ dependencies = [ "finito", "frame-metadata 18.0.0", "futures", + "getrandom", "hex", "http-body", "hyper", @@ -10745,6 +10745,7 @@ dependencies = [ "tower", "tracing", "url", + "wasm-bindgen-futures", ] [[package]] diff --git a/rpcs/Cargo.toml b/rpcs/Cargo.toml index 80f17fdcc9c..f3a62d63635 100644 --- a/rpcs/Cargo.toml +++ b/rpcs/Cargo.toml @@ -53,6 +53,8 @@ web = [ "jsonrpsee?/wasm-client", "subxt-lightclient?/web", "finito?/wasm-bindgen", + "dep:wasm-bindgen-futures", + "getrandom/js", ] [dependencies] @@ -68,6 +70,7 @@ thiserror = { workspace = true } frame-metadata = { workspace = true, features = ["decode"] } url = { workspace = true } tracing = { workspace = true } +getrandom = { workspace = true, optional = true } # Included with the jsonrpsee feature jsonrpsee = { workspace = true, optional = true } @@ -83,6 +86,9 @@ subxt-lightclient = { workspace = true, optional = true, default-features = fals # Included with the subxt-core feature to impl Config for RpcConfig subxt-core = { workspace = true, optional = true } +# Included with WASM feature +wasm-bindgen-futures = { workspace = true, optional = true } + [dev-dependencies] tower = { workspace = true } hyper = { workspace = true } diff --git a/subxt/Cargo.toml b/subxt/Cargo.toml index 8ec1c1e61c7..0be6f8a1c12 100644 --- a/subxt/Cargo.toml +++ b/subxt/Cargo.toml @@ -25,9 +25,6 @@ default = ["jsonrpsee", "native"] # Enable this for native (ie non web/wasm builds). # Exactly 1 of "web" and "native" is expected. native = [ - "jsonrpsee?/async-client", - "jsonrpsee?/client-ws-transport-tls", - "jsonrpsee?/ws-client", "subxt-lightclient?/native", "subxt-rpcs/native", "tokio-util", @@ -38,10 +35,6 @@ native = [ # Enable this for web/wasm builds. # Exactly 1 of "web" and "native" is expected. web = [ - "jsonrpsee?/async-wasm-client", - "jsonrpsee?/client-web-transport", - "jsonrpsee?/wasm-client", - "getrandom/js", "subxt-lightclient?/web", "subxt-macro/web", "subxt-rpcs/web", @@ -59,7 +52,8 @@ runtime = ["tokio/rt", "wasm-bindgen-futures"] # Enable this to use the reconnecting rpc client reconnecting-rpc-client = ["subxt-rpcs/reconnecting-rpc-client"] -# Enable this to use jsonrpsee (allowing for example `OnlineClient::from_url`). +# Enable this to use jsonrpsee, which enables the jsonrpsee RPC client, and +# a couple of util functions which rely on jsonrpsee. jsonrpsee = [ "dep:jsonrpsee", "subxt-rpcs/jsonrpsee", @@ -115,9 +109,6 @@ subxt-rpcs = { workspace = true, features = ["subxt"] } # For parsing urls to disallow insecure schemes url = { workspace = true } -# Included if "web" feature is enabled, to enable its js feature. -getrandom = { workspace = true, optional = true } - # Included if "native" feature is enabled tokio-util = { workspace = true, features = ["compat"], optional = true } diff --git a/subxt/src/lib.rs b/subxt/src/lib.rs index c62f4702f07..d5d495a274b 100644 --- a/subxt/src/lib.rs +++ b/subxt/src/lib.rs @@ -38,11 +38,6 @@ mod only_used_in_docs_or_tests { #[cfg(test)] use tracing_subscriber as _; -// Used to enable the js feature for wasm. -#[cfg(feature = "web")] -#[allow(unused_imports)] -pub use getrandom as _; - pub mod backend; pub mod blocks; pub mod client; From ff74d2548395bcddda592471b9fb3ed4ccc74ed2 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 10 Feb 2025 12:02:12 +0000 Subject: [PATCH 25/32] Fix unused dep warning --- rpcs/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 37954433d89..c6125beae64 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -16,6 +16,11 @@ pub mod client; pub mod methods; pub mod utils; +// Used to enable the js feature for wasm. +#[cfg(feature = "web")] +#[allow(unused_imports)] +pub use getrandom as _; + // Expose the most common things at the top level: pub use client::{RpcClient, RpcClientT}; pub use methods::{ChainHeadRpcMethods, LegacyRpcMethods}; From ad5549aba493a92385a60f76dff6bc667db32385 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 10 Feb 2025 12:11:39 +0000 Subject: [PATCH 26/32] explicit targets in wasm CI --- .github/workflows/rust.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 76007f7be0b..f84dd7dc01f 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -238,8 +238,8 @@ jobs: - name: Cargo check web features which require wasm32 target. run: | - cargo check -p subxt-rpcs --no-default-features --features web - cargo check -p subxt-rpcs --no-default-features --features web,reconnecting-rpc-client + cargo check -p subxt-rpcs --target wasm32-unknown-unknown --no-default-features --features web + cargo check -p subxt-rpcs --target wasm32-unknown-unknown --no-default-features --features web,reconnecting-rpc-client # Check WASM examples, which aren't a part of the workspace and so are otherwise missed: - name: Cargo check WASM examples From 5afe631aa2bbedc7c111bb045b216a4054bab32a Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 10 Feb 2025 15:42:42 +0000 Subject: [PATCH 27/32] Add better crate level docs --- rpcs/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index c6125beae64..7838ac66ce8 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -3,6 +3,18 @@ // see LICENSE for license details. //! This crate provides a low level RPC interface to Substrate based nodes. +//! +//! See the [`client`] module for a [`client::RpcClient`] which is driven by implementations +//! of [`client::RpcClientT`] (several of which are provided behind feature flags). +//! +//! See the [`methods`] module for structs which implement sets of concrete RPC calls for +//! communicating with Substrate based nodes. These structs are all driven by a [`client::RpcClient`]. +//! +//! The RPC clients/methods here are made use of in `subxt`. Enabling the `subxt` feature flag ensures +//! that all Subxt configurations are also valid RPC configurations. +//! +//! The provided RPC client implementations can be used natively (with the default `native` feature +//! flag) or in WASM based web apps (with the `web` feature flag). #[cfg(any( all(feature = "web", feature = "native"), From 74e2d187f679b20676bab5824305954e4cfb677c Mon Sep 17 00:00:00 2001 From: James Wilson Date: Mon, 10 Feb 2025 15:48:06 +0000 Subject: [PATCH 28/32] fmt --- rpcs/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs/src/lib.rs b/rpcs/src/lib.rs index 7838ac66ce8..e6db55fe409 100644 --- a/rpcs/src/lib.rs +++ b/rpcs/src/lib.rs @@ -3,18 +3,18 @@ // see LICENSE for license details. //! This crate provides a low level RPC interface to Substrate based nodes. -//! +//! //! See the [`client`] module for a [`client::RpcClient`] which is driven by implementations //! of [`client::RpcClientT`] (several of which are provided behind feature flags). -//! +//! //! See the [`methods`] module for structs which implement sets of concrete RPC calls for //! communicating with Substrate based nodes. These structs are all driven by a [`client::RpcClient`]. -//! +//! //! The RPC clients/methods here are made use of in `subxt`. Enabling the `subxt` feature flag ensures //! that all Subxt configurations are also valid RPC configurations. -//! -//! The provided RPC client implementations can be used natively (with the default `native` feature -//! flag) or in WASM based web apps (with the `web` feature flag). +//! +//! The provided RPC client implementations can be used natively (with the default `native` feature +//! flag) or in WASM based web apps (with the `web` feature flag). #[cfg(any( all(feature = "web", feature = "native"), From 541720ce44cb56be0bde72dd3e429d8466044ba8 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Fri, 14 Feb 2025 16:18:00 +0000 Subject: [PATCH 29/32] Address review comments --- rpcs/src/client/jsonrpsee_impl.rs | 40 ++++++++++++++--------------- rpcs/src/client/lightclient_impl.rs | 32 +++++++++++------------ rpcs/src/client/mock_rpc_client.rs | 16 +++--------- subxt/src/backend/mod.rs | 38 ++++++++++----------------- 4 files changed, 52 insertions(+), 74 deletions(-) diff --git a/rpcs/src/client/jsonrpsee_impl.rs b/rpcs/src/client/jsonrpsee_impl.rs index dc7d40dd334..173396fb25d 100644 --- a/rpcs/src/client/jsonrpsee_impl.rs +++ b/rpcs/src/client/jsonrpsee_impl.rs @@ -29,9 +29,7 @@ impl RpcClientT for Client { params: Option>, ) -> RawRpcFuture<'a, Box> { Box::pin(async move { - let res = ClientT::request(self, method, Params(params)) - .await - .map_err(error_to_rpc_error)?; + let res = ClientT::request(self, method, Params(params)).await?; Ok(res) }) } @@ -48,9 +46,7 @@ impl RpcClientT for Client { sub, Params(params), unsub, - ) - .await - .map_err(error_to_rpc_error)?; + ).await?; let id = match stream.kind() { SubscriptionKind::Subscription(SubscriptionId::Str(id)) => { @@ -67,20 +63,22 @@ impl RpcClientT for Client { } } -/// Convert a JsonrpseeError into the RPC error in this crate. -/// The main reason for this is to capture user errors so that -/// they can be represented/handled without casting. -fn error_to_rpc_error(error: JsonrpseeError) -> Error { - match error { - JsonrpseeError::Call(e) => { - Error::User(crate::UserError { - code: e.code(), - message: e.message().to_owned(), - data: e.data().map(|d| d.to_owned()) - }) - }, - e => { - Error::Client(Box::new(e)) +// Convert a JsonrpseeError into the RPC error in this crate. +// The main reason for this is to capture user errors so that +// they can be represented/handled without casting. +impl From for Error { + fn from(error: JsonrpseeError) -> Self { + match error { + JsonrpseeError::Call(e) => { + Error::User(crate::UserError { + code: e.code(), + message: e.message().to_owned(), + data: e.data().map(|d| d.to_owned()) + }) + }, + e => { + Error::Client(Box::new(e)) + } } } -} \ No newline at end of file +} diff --git a/rpcs/src/client/lightclient_impl.rs b/rpcs/src/client/lightclient_impl.rs index 21ed7437aff..5384f997cac 100644 --- a/rpcs/src/client/lightclient_impl.rs +++ b/rpcs/src/client/lightclient_impl.rs @@ -16,8 +16,7 @@ impl RpcClientT for LightClientRpc { ) -> RawRpcFuture<'a, Box> { Box::pin(async move { let res = self.request(method.to_owned(), params) - .await - .map_err(lc_err_to_rpc_err)?; + .await?; Ok(res) }) @@ -31,8 +30,7 @@ impl RpcClientT for LightClientRpc { ) -> RawRpcFuture<'a, RawRpcSubscription> { Box::pin(async move { let sub = self.subscribe(sub.to_owned(), params, unsub.to_owned()) - .await - .map_err(lc_err_to_rpc_err)?; + .await?; let id = Some(sub.id().to_owned()); let stream = sub @@ -44,18 +42,20 @@ impl RpcClientT for LightClientRpc { } } -fn lc_err_to_rpc_err(err: LightClientRpcError) -> Error { - match err { - LightClientRpcError::JsonRpcError(e) => { - // If the error is a typical user error, report it as such, else - // just wrap the error into a ClientError. - let Ok(user_error) = e.try_deserialize() else { - return Error::Client(Box::::from(e)) - }; - Error::User(user_error) - }, - LightClientRpcError::SmoldotError(e) => Error::Client(Box::::from(e)), - LightClientRpcError::BackgroundTaskDropped => Error::Client(Box::::from("Smoldot background task was dropped")), +impl From for Error { + fn from(err: LightClientRpcError) -> Error { + match err { + LightClientRpcError::JsonRpcError(e) => { + // If the error is a typical user error, report it as such, else + // just wrap the error into a ClientError. + let Ok(user_error) = e.try_deserialize() else { + return Error::Client(Box::::from(e)) + }; + Error::User(user_error) + }, + LightClientRpcError::SmoldotError(e) => Error::Client(Box::::from(e)), + LightClientRpcError::BackgroundTaskDropped => Error::Client(Box::::from("Smoldot background task was dropped")), + } } } diff --git a/rpcs/src/client/mock_rpc_client.rs b/rpcs/src/client/mock_rpc_client.rs index 0140b03cd08..97ca23b6dfa 100644 --- a/rpcs/src/client/mock_rpc_client.rs +++ b/rpcs/src/client/mock_rpc_client.rs @@ -51,6 +51,7 @@ type MethodHandlerFn = Box>, &str) -> RawRpcFuture<'static, RawRpcSubscription> + Send + Sync + 'static>; /// A builder to configure and build a new [`MockRpcClient`]. +#[derive(Default)] pub struct MockRpcClientBuilder { method_handlers_once: HashMap>, method_handlers: HashMap, @@ -60,18 +61,7 @@ pub struct MockRpcClientBuilder { subscription_fallback: Option } -impl MockRpcClientBuilder { - fn new() -> Self { - MockRpcClientBuilder { - method_handlers_once: HashMap::new(), - method_handlers: HashMap::new(), - method_fallback: None, - subscription_handlers_once: HashMap::new(), - subscription_handlers: HashMap::new(), - subscription_fallback: None - } - } - +impl MockRpcClientBuilder { /// Add a handler for a specific RPC method. This is called exactly once, and multiple such calls for the same method can be /// added. Only when any calls registered with this have been used up is the method set by [`Self::method_handler`] called. pub fn method_handler_once(mut self, name: impl Into, f: MethodHandler) -> Self @@ -193,7 +183,7 @@ pub struct MockRpcClient { impl MockRpcClient { /// Construct a new [`MockRpcClient`] pub fn builder() -> MockRpcClientBuilder { - MockRpcClientBuilder::new() + MockRpcClientBuilder::default() } } diff --git a/subxt/src/backend/mod.rs b/subxt/src/backend/mod.rs index 5d0deee6605..08bba3303bd 100644 --- a/subxt/src/backend/mod.rs +++ b/subxt/src/backend/mod.rs @@ -396,6 +396,10 @@ mod test { H256::random() } + fn disconnected_will_reconnect() -> subxt_rpcs::Error { + subxt_rpcs::Error::DisconnectedWillReconnect("..".into()) + } + fn storage_response>, V: Into>>(key: K, value: V) -> StorageResponse where Vec: From, @@ -450,18 +454,14 @@ mod test { ( "ID1", VecDeque::from_iter([ - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )), + Err(disconnected_will_reconnect()), Ok(Json(hex::encode("Data1"))), ]), ), ( "ID2", VecDeque::from_iter([ - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )), + Err(disconnected_will_reconnect()), Ok(Json(hex::encode("Data2"))), ]), ), @@ -512,9 +512,7 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("state_getStorage", move |_params| async move { // Return "disconnected" error on first call - Err::(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )) + Err::(disconnected_will_reconnect()) }) .method_handler_once("state_getStorage", move |_param| async move { // Return some hex encoded storage value on the next one @@ -553,9 +551,7 @@ mod test { let rpc_client = MockRpcClient::builder() .method_handler_once("chain_getBlockHash", move |_params| async move { // Return "disconnected" error on first call - Err::(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )) + Err::(disconnected_will_reconnect()) }) .method_handler_once("chain_getBlockHash", move |_params| async move { // Return the blockhash on next call @@ -599,15 +595,11 @@ mod test { let mut data = VecDeque::from_iter([ vec![ Ok(Json(runtime_version(0))), - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )), + Err(disconnected_will_reconnect()), Ok(Json(runtime_version(1))), ], vec![ - Err(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_string(), - )), + Err(disconnected_will_reconnect()), Ok(Json(runtime_version(2))), Ok(Json(runtime_version(3))), ], @@ -877,7 +869,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(disconnected_will_reconnect()) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Otherwise, return that we'll start sending a response, and spawn @@ -934,7 +926,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainHead_v1_storage", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(disconnected_will_reconnect()) }) .method_handler_once("chainHead_v1_storage", move |_params| async move { // Next call, return a storage item and then a "waiting for continue". @@ -947,7 +939,7 @@ mod test { }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // First call; return DisconnectedWillReconnect - Err::(subxt_rpcs::Error::DisconnectedWillReconnect("..".into())) + Err::(disconnected_will_reconnect()) }) .method_handler_once("chainHead_v1_continue", move |_params| async move { // Next call; acknowledge the "continue" and return reamining storage items. @@ -995,9 +987,7 @@ mod test { let rpc_client = mock_client_builder(rx) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // First call, return disconnected error. - Err::(subxt_rpcs::Error::DisconnectedWillReconnect( - "..".to_owned(), - )) + Err::(disconnected_will_reconnect()) }) .method_handler_once("chainSpec_v1_genesisHash", move |_params| async move { // Next call, return the hash. From 5add5c116c560f2d6125b1cf601b7045cedc2c7f Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 18 Feb 2025 10:52:07 +0000 Subject: [PATCH 30/32] Comment out flaky test for now and make more obvious how similar POlkadot and Substrate configs are --- core/src/config/polkadot.rs | 10 ++++++++-- .../integration-tests/src/full_client/client/mod.rs | 10 +++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/src/config/polkadot.rs b/core/src/config/polkadot.rs index 7f4e3a88f94..9875c233f73 100644 --- a/core/src/config/polkadot.rs +++ b/core/src/config/polkadot.rs @@ -19,12 +19,18 @@ pub enum PolkadotConfig {} impl Config for PolkadotConfig { type Hash = ::Hash; type AccountId = ::AccountId; - type Address = MultiAddress; type Signature = ::Signature; type Hasher = ::Hasher; type Header = ::Header; + type AssetId = ::AssetId; + + // Address on Polkadot has no account index, whereas it's u32 on + // the default substrate dev node. + type Address = MultiAddress; + + // These are the same as the default substrate node, but redefined + // because we need to pass the PolkadotConfig trait as a param. type ExtrinsicParams = PolkadotExtrinsicParams; - type AssetId = u32; } /// A struct representing the signed extra and additional parameters required diff --git a/testing/integration-tests/src/full_client/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs index 736abb44e27..6952a35d05a 100644 --- a/testing/integration-tests/src/full_client/client/mod.rs +++ b/testing/integration-tests/src/full_client/client/mod.rs @@ -412,10 +412,17 @@ async fn partial_fee_estimate_correct() { assert_eq!(partial_fee_1, partial_fee_2); } +/// This test runs OK locally but fails sporadically in CI eg: +/// +/// https://github.com/paritytech/subxt/actions/runs/13374953009/job/37353887719?pr=1910#step:7:178 +/// https://github.com/paritytech/subxt/actions/runs/13385878645/job/37382498200#step:6:163 +/// +/// While those errors were timeouts, I also saw errors like "intersections size is 1". +/* #[subxt_test(timeout = 300)] async fn chainhead_block_subscription_reconnect() { let ctx = test_context_reconnecting_rpc_client().await; - let api = ctx.chainhead_backend().await; + let api = ctx.chainhead_backend().await;ccc let chainhead_client_blocks = move |num: usize| { let api = api.clone(); async move { @@ -463,3 +470,4 @@ async fn chainhead_block_subscription_reconnect() { assert!(intersection >= 3, "intersections size is {}", intersection); } } +*/ \ No newline at end of file From fb7ceb78f4fc71cbf3ce58998b3dab8c78825456 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 18 Feb 2025 10:54:40 +0000 Subject: [PATCH 31/32] Not a doc comment --- .../src/full_client/client/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/testing/integration-tests/src/full_client/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs index 6952a35d05a..b282124d735 100644 --- a/testing/integration-tests/src/full_client/client/mod.rs +++ b/testing/integration-tests/src/full_client/client/mod.rs @@ -412,12 +412,12 @@ async fn partial_fee_estimate_correct() { assert_eq!(partial_fee_1, partial_fee_2); } -/// This test runs OK locally but fails sporadically in CI eg: -/// -/// https://github.com/paritytech/subxt/actions/runs/13374953009/job/37353887719?pr=1910#step:7:178 -/// https://github.com/paritytech/subxt/actions/runs/13385878645/job/37382498200#step:6:163 -/// -/// While those errors were timeouts, I also saw errors like "intersections size is 1". +// This test runs OK locally but fails sporadically in CI eg: +// +// https://github.com/paritytech/subxt/actions/runs/13374953009/job/37353887719?pr=1910#step:7:178 +// https://github.com/paritytech/subxt/actions/runs/13385878645/job/37382498200#step:6:163 +// +// While those errors were timeouts, I also saw errors like "intersections size is 1". /* #[subxt_test(timeout = 300)] async fn chainhead_block_subscription_reconnect() { @@ -470,4 +470,4 @@ async fn chainhead_block_subscription_reconnect() { assert!(intersection >= 3, "intersections size is {}", intersection); } } -*/ \ No newline at end of file +*/ From 8e0c2ebb0b3edb01ac46489ae249ccdbbcdcb286 Mon Sep 17 00:00:00 2001 From: James Wilson Date: Tue, 18 Feb 2025 11:03:57 +0000 Subject: [PATCH 32/32] Remove unused imports --- testing/integration-tests/src/full_client/client/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/testing/integration-tests/src/full_client/client/mod.rs b/testing/integration-tests/src/full_client/client/mod.rs index b282124d735..5896f34ab01 100644 --- a/testing/integration-tests/src/full_client/client/mod.rs +++ b/testing/integration-tests/src/full_client/client/mod.rs @@ -2,10 +2,8 @@ // This file is dual-licensed as Apache-2.0 or GPL-3.0. // see LICENSE for license details. -use std::collections::HashSet; - use crate::{ - subxt_test, test_context, test_context_reconnecting_rpc_client, + subxt_test, test_context, utils::{node_runtime, wait_for_blocks}, }; use codec::{Decode, Encode}; @@ -421,6 +419,9 @@ async fn partial_fee_estimate_correct() { /* #[subxt_test(timeout = 300)] async fn chainhead_block_subscription_reconnect() { + use std::collections::HashSet; + use crate::test_context_reconnecting_rpc_client; + let ctx = test_context_reconnecting_rpc_client().await; let api = ctx.chainhead_backend().await;ccc let chainhead_client_blocks = move |num: usize| {