Skip to content

Commit 816a864

Browse files
authored
Split RPCs into a separate crate (#1910)
* WIP extract RPCs into separate crate * fmt * Fix test * Remove unused deps * fix import * WIP: Fix up errors and most tests. Start extracintg some tests/code to rpc crate * MockRpcClient sync or async * MockRpcClient only async but better type inference * WIP MockRpcClient FnMuts and some test updates to use it * Get all but one test working with new MockRpcClient * WIP trying to debug failure * WIP, Tests mostly fixed, need to add back oen more * Get mock RPC tests working * fmt * fmt * Clippy and comment tweak * update CI to explicitly check subxt-rpc features * clippy * small tweaks after pass over * feature flag rename * update some docs * Fix some examples * fmt * Fix features flags to work with web/wasm32 * Fix unused dep warning * explicit targets in wasm CI * Add better crate level docs * fmt * Address review comments * Comment out flaky test for now and make more obvious how similar POlkadot and Substrate configs are * Not a doc comment * Remove unused imports
1 parent 333de95 commit 816a864

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+4572
-1183
lines changed

.github/workflows/rust.yml

+17-1
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,24 @@ jobs:
191191
cargo check -p subxt-signer --no-default-features --features ecdsa
192192
cargo check -p subxt-signer --no-default-features --features unstable-eth
193193
194+
# Subxt-rpcs has a bunch of clients that can be exposed. Check that they all stand on their own.
195+
- name: Cargo check subxt-rpcs
196+
run: |
197+
cargo check -p subxt-rpcs
198+
cargo check -p subxt-rpcs --no-default-features --features native
199+
cargo check -p subxt-rpcs --no-default-features --features native,subxt
200+
cargo check -p subxt-rpcs --no-default-features --features native,jsonrpsee
201+
cargo check -p subxt-rpcs --no-default-features --features native,reconnecting-rpc-client
202+
cargo check -p subxt-rpcs --no-default-features --features native,mock-rpc-client
203+
cargo check -p subxt-rpcs --no-default-features --features native,unstable-light-client
204+
194205
# We can't enable web features here, so no cargo hack.
195206
- name: Cargo check subxt-lightclient
196207
run: cargo check -p subxt-lightclient
197208

198209
# Next, check each other package in isolation.
199210
- name: Cargo hack; check each feature/crate on its own
200-
run: cargo hack --exclude subxt --exclude subxt-signer --exclude subxt-lightclient --exclude-all-features --each-feature check --workspace
211+
run: cargo hack --exclude subxt --exclude subxt-signer --exclude subxt-lightclient --exclude subxt-rpcs --exclude-all-features --each-feature check --workspace
201212

202213
# Check the parachain-example code, which isn't a part of the workspace so is otherwise ignored.
203214
- name: Cargo check parachain-example
@@ -225,6 +236,11 @@ jobs:
225236
- name: Rust Cache
226237
uses: Swatinem/rust-cache@f0deed1e0edfc6a9be95417288c0e1099b1eeec3 # v2.7.7
227238

239+
- name: Cargo check web features which require wasm32 target.
240+
run: |
241+
cargo check -p subxt-rpcs --target wasm32-unknown-unknown --no-default-features --features web
242+
cargo check -p subxt-rpcs --target wasm32-unknown-unknown --no-default-features --features web,reconnecting-rpc-client
243+
228244
# Check WASM examples, which aren't a part of the workspace and so are otherwise missed:
229245
- name: Cargo check WASM examples
230246
run: |

Cargo.lock

+31-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ members = [
1212
"testing/generate-custom-metadata",
1313
"macro",
1414
"metadata",
15+
"rpcs",
1516
"signer",
1617
"subxt",
1718
"scripts/artifacts",
@@ -82,7 +83,7 @@ frame-metadata = { version = "18.0.0", default-features = false }
8283
futures = { version = "0.3.31", default-features = false, features = ["std"] }
8384
getrandom = { version = "0.2", default-features = false }
8485
hashbrown = "0.14.5"
85-
hex = { version = "0.4.3", default-features = false }
86+
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
8687
heck = "0.5.0"
8788
impl-serde = { version = "0.5.0", default-features = false }
8889
indoc = "2"
@@ -116,6 +117,9 @@ which = "6.0.3"
116117
strip-ansi-escapes = "0.2.0"
117118
proptest = "1.5.0"
118119
hex-literal = "0.4.1"
120+
tower = "0.4"
121+
hyper = "1"
122+
http-body = "1"
119123

120124
# Light client support:
121125
smoldot = { version = "0.18.0", default-features = false }
@@ -145,6 +149,7 @@ subxt-macro = { version = "0.39.0", path = "macro" }
145149
subxt-metadata = { version = "0.39.0", path = "metadata", default-features = false }
146150
subxt-codegen = { version = "0.39.0", path = "codegen" }
147151
subxt-signer = { version = "0.39.0", path = "signer", default-features = false }
152+
subxt-rpcs = { version = "0.39.0", path = "rpcs", default-features = false }
148153
subxt-lightclient = { version = "0.39.0", path = "lightclient", default-features = false }
149154
subxt-utils-fetchmetadata = { version = "0.39.0", path = "utils/fetch-metadata", default-features = false }
150155
test-runtime = { path = "testing/test-runtime" }

RELEASING.md

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ We also assume that ongoing work done is being merged directly to the `master` b
8686
8787
```
8888
(cd core && cargo publish) && \
89+
(cd rpcs && cargo publish) && \
8990
(cd subxt && cargo publish) && \
9091
(cd signer && cargo publish) && \
9192
(cd cli && cargo publish);

core/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ scale-encode = { workspace = true, default-features = false, features = ["derive
4040
frame-metadata = { workspace = true, default-features = false }
4141
subxt-metadata = { workspace = true, default-features = false }
4242
derive-where = { workspace = true }
43-
hex = { workspace = true, default-features = false, features = ["alloc"] }
43+
hex = { workspace = true }
4444
serde = { workspace = true, default-features = false, features = ["derive"] }
4545
serde_json = { workspace = true, default-features = false, features = ["raw_value", "alloc"] }
4646
tracing = { workspace = true, default-features = false }

core/src/config/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub trait Config: Sized + Send + Sync + 'static {
3939
type Hash: BlockHash;
4040

4141
/// The account ID type.
42-
type AccountId: Debug + Clone + Encode;
42+
type AccountId: Debug + Clone + Encode + Serialize;
4343

4444
/// The address type.
4545
type Address: Debug + Encode + From<Self::AccountId>;

core/src/config/polkadot.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,18 @@ pub enum PolkadotConfig {}
1919
impl Config for PolkadotConfig {
2020
type Hash = <SubstrateConfig as Config>::Hash;
2121
type AccountId = <SubstrateConfig as Config>::AccountId;
22-
type Address = MultiAddress<Self::AccountId, ()>;
2322
type Signature = <SubstrateConfig as Config>::Signature;
2423
type Hasher = <SubstrateConfig as Config>::Hasher;
2524
type Header = <SubstrateConfig as Config>::Header;
25+
type AssetId = <SubstrateConfig as Config>::AssetId;
26+
27+
// Address on Polkadot has no account index, whereas it's u32 on
28+
// the default substrate dev node.
29+
type Address = MultiAddress<Self::AccountId, ()>;
30+
31+
// These are the same as the default substrate node, but redefined
32+
// because we need to pass the PolkadotConfig trait as a param.
2633
type ExtrinsicParams = PolkadotExtrinsicParams<Self>;
27-
type AssetId = u32;
2834
}
2935

3036
/// A struct representing the signed extra and additional parameters required

lightclient/src/lib.rs

+9
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ pub enum LightClientRpcError {
5959
#[error("RPC Error: {0}.")]
6060
pub struct JsonRpcError(Box<RawValue>);
6161

62+
impl JsonRpcError {
63+
/// Attempt to deserialize this error into some type.
64+
pub fn try_deserialize<'a, T: serde::de::Deserialize<'a>>(
65+
&'a self,
66+
) -> Result<T, serde_json::Error> {
67+
serde_json::from_str(self.0.get())
68+
}
69+
}
70+
6271
/// This represents a single light client connection to the network. Instantiate
6372
/// it with [`LightClient::relay_chain()`] to communicate with a relay chain, and
6473
/// then call [`LightClient::parachain()`] to establish connections to parachains.

rpcs/Cargo.toml

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
[package]
2+
name = "subxt-rpcs"
3+
version.workspace = true
4+
authors.workspace = true
5+
edition.workspace = true
6+
rust-version.workspace = true
7+
publish = true
8+
9+
license.workspace = true
10+
readme = "README.md"
11+
repository.workspace = true
12+
documentation.workspace = true
13+
homepage.workspace = true
14+
description = "Make RPC calls to Substrate based nodes"
15+
keywords = ["parity", "subxt", "rpcs"]
16+
17+
[features]
18+
default = ["jsonrpsee", "native"]
19+
20+
subxt = ["dep:subxt-core"]
21+
jsonrpsee = ["dep:jsonrpsee", "dep:tokio-util"]
22+
23+
unstable-light-client = [
24+
"dep:subxt-lightclient"
25+
]
26+
27+
reconnecting-rpc-client = [
28+
"jsonrpsee",
29+
"dep:finito",
30+
"dep:tokio",
31+
"tokio/sync",
32+
]
33+
34+
mock-rpc-client = [
35+
"dep:tokio",
36+
"tokio/sync",
37+
]
38+
39+
# Enable this for native (ie non web/wasm builds).
40+
# Exactly 1 of "web" and "native" is expected.
41+
native = [
42+
"jsonrpsee?/async-client",
43+
"jsonrpsee?/client-ws-transport-tls",
44+
"jsonrpsee?/ws-client",
45+
"subxt-lightclient?/native",
46+
]
47+
48+
# Enable this for web/wasm builds.
49+
# Exactly 1 of "web" and "native" is expected.
50+
web = [
51+
"jsonrpsee?/async-wasm-client",
52+
"jsonrpsee?/client-web-transport",
53+
"jsonrpsee?/wasm-client",
54+
"subxt-lightclient?/web",
55+
"finito?/wasm-bindgen",
56+
"dep:wasm-bindgen-futures",
57+
"getrandom/js",
58+
]
59+
60+
[dependencies]
61+
codec = { workspace = true }
62+
derive-where = { workspace = true }
63+
futures = { workspace = true }
64+
hex = { workspace = true }
65+
impl-serde = { workspace = true }
66+
primitive-types = { workspace = true, features = ["serde"] }
67+
serde = { workspace = true }
68+
serde_json = { workspace = true, features = ["default", "raw_value"] }
69+
thiserror = { workspace = true }
70+
frame-metadata = { workspace = true, features = ["decode"] }
71+
url = { workspace = true }
72+
tracing = { workspace = true }
73+
getrandom = { workspace = true, optional = true }
74+
75+
# Included with the jsonrpsee feature
76+
jsonrpsee = { workspace = true, optional = true }
77+
tokio-util = { workspace = true, features = ["compat"], optional = true }
78+
79+
# Included with the reconnecting-rpc-client feature
80+
finito = { workspace = true, optional = true }
81+
tokio = { workspace = true, optional = true }
82+
83+
# Included with the lightclient feature
84+
subxt-lightclient = { workspace = true, optional = true, default-features = false }
85+
86+
# Included with the subxt-core feature to impl Config for RpcConfig
87+
subxt-core = { workspace = true, optional = true }
88+
89+
# Included with WASM feature
90+
wasm-bindgen-futures = { workspace = true, optional = true }
91+
92+
[dev-dependencies]
93+
tower = { workspace = true }
94+
hyper = { workspace = true }
95+
http-body = { workspace = true }
96+
97+
[lints]
98+
workspace = true

subxt/src/backend/rpc/jsonrpsee_impl.rs rpcs/src/client/jsonrpsee_impl.rs

+26-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
// Copyright 2019-2023 Parity Technologies (UK) Ltd.
1+
// Copyright 2019-2025 Parity Technologies (UK) Ltd.
22
// This file is dual-licensed as Apache-2.0 or GPL-3.0.
33
// see LICENSE for license details.
44

55
use super::{RawRpcFuture, RawRpcSubscription, RpcClientT};
6-
use crate::error::RpcError;
6+
use crate::Error;
77
use futures::stream::{StreamExt, TryStreamExt};
88
use jsonrpsee::{
99
core::{
10-
client::{Client, ClientT, SubscriptionClientT, SubscriptionKind},
10+
client::{Error as JsonrpseeError, Client, ClientT, SubscriptionClientT, SubscriptionKind},
1111
traits::ToRpcParams,
1212
},
1313
types::SubscriptionId,
@@ -29,9 +29,7 @@ impl RpcClientT for Client {
2929
params: Option<Box<RawValue>>,
3030
) -> RawRpcFuture<'a, Box<RawValue>> {
3131
Box::pin(async move {
32-
let res = ClientT::request(self, method, Params(params))
33-
.await
34-
.map_err(|e| RpcError::ClientError(Box::new(e)))?;
32+
let res = ClientT::request(self, method, Params(params)).await?;
3533
Ok(res)
3634
})
3735
}
@@ -48,9 +46,7 @@ impl RpcClientT for Client {
4846
sub,
4947
Params(params),
5048
unsub,
51-
)
52-
.await
53-
.map_err(|e| RpcError::ClientError(Box::new(e)))?;
49+
).await?;
5450

5551
let id = match stream.kind() {
5652
SubscriptionKind::Subscription(SubscriptionId::Str(id)) => {
@@ -60,9 +56,29 @@ impl RpcClientT for Client {
6056
};
6157

6258
let stream = stream
63-
.map_err(|e| RpcError::ClientError(Box::new(e)))
59+
.map_err(|e| Error::Client(Box::new(e)))
6460
.boxed();
6561
Ok(RawRpcSubscription { stream, id })
6662
})
6763
}
6864
}
65+
66+
// Convert a JsonrpseeError into the RPC error in this crate.
67+
// The main reason for this is to capture user errors so that
68+
// they can be represented/handled without casting.
69+
impl From<JsonrpseeError> for Error {
70+
fn from(error: JsonrpseeError) -> Self {
71+
match error {
72+
JsonrpseeError::Call(e) => {
73+
Error::User(crate::UserError {
74+
code: e.code(),
75+
message: e.message().to_owned(),
76+
data: e.data().map(|d| d.to_owned())
77+
})
78+
},
79+
e => {
80+
Error::Client(Box::new(e))
81+
}
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)