mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 05:51:02 +00:00
rpc: backpressured RPC server (bump jsonrpsee 0.20) (#1313)
This is a rather big change in jsonrpsee, the major things in this bump are: - Server backpressure (the subscription impls are modified to deal with that) - Allow custom error types / return types (remove jsonrpsee::core::Error and jsonrpee::core::CallError) - Bug fixes (graceful shutdown in particular not used by substrate anyway) - Less dependencies for the clients in particular - Return type requires Clone in method call responses - Moved to tokio channels - Async subscription API (not used in this PR) Major changes in this PR: - The subscriptions are now bounded and if subscription can't keep up with the server it is dropped - CLI: add parameter to configure the jsonrpc server bounded message buffer (default is 64) - Add our own subscription helper to deal with the unbounded streams in substrate The most important things in this PR to review is the added helpers functions in `substrate/client/rpc/src/utils.rs` and the rest is pretty much chore. Regarding the "bounded buffer limit" it may cause the server to handle the JSON-RPC calls slower than before. The message size limit is bounded by "--rpc-response-size" thus "by default 10MB * 64 = 640MB" but the subscription message size is not covered by this limit and could be capped as well. Hopefully the last release prior to 1.0, sorry in advance for a big PR Previous attempt: https://github.com/paritytech/substrate/pull/13992 Resolves https://github.com/paritytech/polkadot-sdk/issues/748, resolves https://github.com/paritytech/polkadot-sdk/issues/627
This commit is contained in:
Generated
+98
-65
@@ -6158,19 +6158,6 @@ version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutton-westend-runtime"
|
||||
version = "1.0.0"
|
||||
@@ -6508,7 +6495,6 @@ dependencies = [
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki-roots 0.23.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6847,9 +6833,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "367a292944c07385839818bb71c8d76611138e2dedb0677d035b8da21d29c78b"
|
||||
checksum = "affdc52f7596ccb2d7645231fc6163bb314630c989b64998f3699a28b4d5d4dc"
|
||||
dependencies = [
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-http-client",
|
||||
@@ -6857,19 +6843,19 @@ dependencies = [
|
||||
"jsonrpsee-server",
|
||||
"jsonrpsee-types",
|
||||
"jsonrpsee-ws-client",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-client-transport"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8b3815d9f5d5de348e5f162b316dc9cdf4548305ebb15b4eb9328e66cf27d7a"
|
||||
checksum = "b5b005c793122d03217da09af68ba9383363caa950b90d3436106df8cabce935"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"pin-project",
|
||||
"rustls-native-certs",
|
||||
"soketto",
|
||||
@@ -6878,24 +6864,21 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"webpki-roots 0.25.2",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-core"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b5dde66c53d6dcdc8caea1874a45632ec0fcf5b437789f1e45766a1512ce803"
|
||||
checksum = "da2327ba8df2fdbd5e897e2b5ed25ce7f299d345b9736b6828814c3dbd1fd47b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec 0.7.4",
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"beef",
|
||||
"futures-channel",
|
||||
"futures-timer",
|
||||
"futures-util",
|
||||
"globset",
|
||||
"hyper",
|
||||
"jsonrpsee-types",
|
||||
"parking_lot 0.12.1",
|
||||
@@ -6911,28 +6894,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-http-client"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e5f9fabdd5d79344728521bb65e3106b49ec405a78b66fbff073b72b389fa43"
|
||||
checksum = "5f80c17f62c7653ce767e3d7288b793dfec920f97067ceb189ebdd3570f2bc20"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-proc-macros"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44e8ab85614a08792b9bff6c8feee23be78c98d0182d4c622c05256ab553892a"
|
||||
checksum = "29110019693a4fa2dbda04876499d098fa16d70eba06b1e6e2b3f1b251419515"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-crate 1.3.1",
|
||||
@@ -6943,19 +6927,20 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-server"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf4d945a6008c9b03db3354fb3c83ee02d2faa9f2e755ec1dfb69c3551b8f4ba"
|
||||
checksum = "82c39a00449c9ef3f50b84fc00fc4acba20ef8f559f07902244abf4c15c5ab9c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"hyper",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"route-recognizer",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"soketto",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
@@ -6965,9 +6950,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-types"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245ba8e5aa633dd1c1e4fae72bce06e71f42d34c14a2767c6b4d173b57bee5e5"
|
||||
checksum = "5be0be325642e850ed0bdff426674d2e66b2b7117c9be23a7caef68a2902b7d9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"beef",
|
||||
@@ -6979,14 +6964,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jsonrpsee-ws-client"
|
||||
version = "0.16.3"
|
||||
version = "0.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e1b3975ed5d73f456478681a417128597acd6a2487855fdb7b4a3d4d195bf5e"
|
||||
checksum = "bca9cb3933ccae417eb6b08c3448eb1cb46e39834e5b503e395e5e5bd08546c0"
|
||||
dependencies = [
|
||||
"http",
|
||||
"jsonrpsee-client-transport",
|
||||
"jsonrpsee-core",
|
||||
"jsonrpsee-types",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7972,6 +7958,15 @@ dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
@@ -8242,7 +8237,6 @@ dependencies = [
|
||||
name = "mmr-rpc"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"jsonrpsee",
|
||||
"parity-scale-codec",
|
||||
"serde",
|
||||
@@ -8831,6 +8825,16 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.1"
|
||||
@@ -9048,6 +9052,12 @@ version = "6.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
@@ -15046,6 +15056,12 @@ dependencies = [
|
||||
"westend-emulated-chain",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "route-recognizer"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "7.2.0"
|
||||
@@ -15227,7 +15243,7 @@ checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.16.20",
|
||||
"rustls-webpki 0.101.4",
|
||||
"rustls-webpki",
|
||||
"sct",
|
||||
]
|
||||
|
||||
@@ -15252,16 +15268,6 @@ dependencies = [
|
||||
"base64 0.21.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab"
|
||||
dependencies = [
|
||||
"ring 0.16.20",
|
||||
"untrusted 0.7.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.4"
|
||||
@@ -15973,7 +15979,7 @@ dependencies = [
|
||||
"substrate-test-runtime",
|
||||
"tempfile",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-subscriber 0.2.25",
|
||||
"wat",
|
||||
]
|
||||
|
||||
@@ -16403,6 +16409,7 @@ dependencies = [
|
||||
"sp-version",
|
||||
"substrate-test-runtime-client",
|
||||
"tokio",
|
||||
"tracing-subscriber 0.3.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -16455,6 +16462,7 @@ dependencies = [
|
||||
"sc-block-builder",
|
||||
"sc-chain-spec",
|
||||
"sc-client-api",
|
||||
"sc-rpc",
|
||||
"sc-service",
|
||||
"sc-transaction-pool-api",
|
||||
"sc-utils",
|
||||
@@ -16713,8 +16721,8 @@ dependencies = [
|
||||
"sp-tracing 10.0.0",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
"tracing-log 0.1.3",
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -18899,7 +18907,7 @@ dependencies = [
|
||||
"sp-std 8.0.0",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -18911,7 +18919,7 @@ dependencies = [
|
||||
"sp-std 8.0.0 (git+https://github.com/paritytech/polkadot-sdk)",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
"tracing-subscriber 0.2.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -19614,6 +19622,7 @@ dependencies = [
|
||||
"sp-keystore",
|
||||
"sp-runtime",
|
||||
"sp-state-machine",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -20428,6 +20437,10 @@ version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite 0.2.12",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -20540,6 +20553,17 @@ dependencies = [
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.3"
|
||||
@@ -20559,7 +20583,7 @@ dependencies = [
|
||||
"ansi_term",
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"matchers",
|
||||
"matchers 0.0.1",
|
||||
"parking_lot 0.11.2",
|
||||
"regex",
|
||||
"serde",
|
||||
@@ -20569,10 +20593,28 @@ dependencies = [
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-log 0.1.3",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers 0.1.0",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trie-bench"
|
||||
version = "0.38.0"
|
||||
@@ -21499,15 +21541,6 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
|
||||
dependencies = [
|
||||
"rustls-webpki 0.100.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.25.2"
|
||||
|
||||
@@ -22,5 +22,5 @@ sc-client-api = { path = "../../../substrate/client/api" }
|
||||
futures = "0.3.28"
|
||||
async-trait = "0.1.74"
|
||||
thiserror = "1.0.48"
|
||||
jsonrpsee-core = "0.16.2"
|
||||
jsonrpsee-core = "0.20.3"
|
||||
parity-scale-codec = "3.6.4"
|
||||
|
||||
@@ -33,7 +33,7 @@ tokio-util = { version = "0.7.8", features = ["compat"] }
|
||||
futures = "0.3.28"
|
||||
futures-timer = "3.0.2"
|
||||
parity-scale-codec = "3.6.4"
|
||||
jsonrpsee = { version = "0.16.2", features = ["ws-client"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["ws-client"] }
|
||||
tracing = "0.1.37"
|
||||
async-trait = "0.1.74"
|
||||
url = "2.4.0"
|
||||
|
||||
@@ -18,7 +18,7 @@ clap = { version = "4.4.18", features = ["derive"] }
|
||||
log = "0.4.20"
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
futures = "0.3.28"
|
||||
serde_json = "1.0.111"
|
||||
|
||||
|
||||
@@ -236,20 +236,14 @@ impl MatchesLocalAndForeignAssetsLocation<xcm::v3::Location>
|
||||
{
|
||||
fn is_local(location: &xcm::v3::Location) -> bool {
|
||||
use assets_common::fungible_conversion::MatchesLocation;
|
||||
let latest_location: Location = if let Ok(location) = (*location).try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_location: Location =
|
||||
if let Ok(location) = (*location).try_into() { location } else { return false };
|
||||
TrustBackedAssetsConvertedConcreteId::contains(&latest_location)
|
||||
}
|
||||
fn is_foreign(location: &xcm::v3::Location) -> bool {
|
||||
use assets_common::fungible_conversion::MatchesLocation;
|
||||
let latest_location: Location = if let Ok(location) = (*location).try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_location: Location =
|
||||
if let Ok(location) = (*location).try_into() { location } else { return false };
|
||||
ForeignAssetsConvertedConcreteId::contains(&latest_location)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,21 +231,15 @@ impl MatchesLocalAndForeignAssetsLocation<xcm::v3::Location>
|
||||
{
|
||||
fn is_local(location: &xcm::v3::Location) -> bool {
|
||||
use assets_common::fungible_conversion::MatchesLocation;
|
||||
let latest_location: Location = if let Ok(location) = (*location).try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_location: Location =
|
||||
if let Ok(location) = (*location).try_into() { location } else { return false };
|
||||
TrustBackedAssetsConvertedConcreteId::contains(&latest_location)
|
||||
}
|
||||
|
||||
fn is_foreign(location: &xcm::v3::Location) -> bool {
|
||||
use assets_common::fungible_conversion::MatchesLocation;
|
||||
let latest_location: Location = if let Ok(location) = (*location).try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_location: Location =
|
||||
if let Ok(location) = (*location).try_into() { location } else { return false };
|
||||
ForeignAssetsConvertedConcreteId::contains(&latest_location)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ coretime-rococo-runtime = { path = "../parachains/runtimes/coretime/coretime-roc
|
||||
coretime-westend-runtime = { path = "../parachains/runtimes/coretime/coretime-westend" }
|
||||
bridge-hub-westend-runtime = { path = "../parachains/runtimes/bridge-hubs/bridge-hub-westend" }
|
||||
penpal-runtime = { path = "../parachains/runtimes/testing/penpal" }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
people-rococo-runtime = { path = "../parachains/runtimes/people/people-rococo" }
|
||||
people-westend-runtime = { path = "../parachains/runtimes/people/people-westend" }
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
parachains-common = { path = "../parachains/common" }
|
||||
|
||||
# Substrate
|
||||
|
||||
@@ -17,7 +17,7 @@ async-trait = "0.1.74"
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
codec = { package = "parity-scale-codec", version = "3.0.0" }
|
||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
|
||||
@@ -799,6 +799,7 @@ pub fn node_config(
|
||||
rpc_id_provider: None,
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_port: 9945,
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
|
||||
@@ -365,7 +365,7 @@ pub mod pallet {
|
||||
// ensure sender has enough balance, and if so, calculate what is left after `amount`.
|
||||
let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
|
||||
if sender_balance < amount {
|
||||
return Err("InsufficientBalance".into());
|
||||
return Err("InsufficientBalance".into())
|
||||
}
|
||||
let reminder = sender_balance - amount;
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ pub fn node_config(
|
||||
rpc_id_provider: None,
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_port: 9944,
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
|
||||
@@ -10,7 +10,7 @@ description = "Polkadot specific RPC functionality."
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
polkadot-primitives = { path = "../primitives" }
|
||||
sc-client-api = { path = "../../substrate/client/api" }
|
||||
sp-blockchain = { path = "../../substrate/primitives/blockchain" }
|
||||
|
||||
@@ -189,7 +189,7 @@ pub mod junctions {
|
||||
|
||||
pub fn generate_conversion_functions(input: proc_macro::TokenStream) -> Result<TokenStream> {
|
||||
if !input.is_empty() {
|
||||
return Err(syn::Error::new(Span::call_site(), "No arguments expected"));
|
||||
return Err(syn::Error::new(Span::call_site(), "No arguments expected"))
|
||||
}
|
||||
|
||||
let from_slice_syntax = generate_conversion_from_slice_syntax();
|
||||
|
||||
@@ -25,16 +25,9 @@ use xcm::latest::{InteriorLocation, Location, NetworkId};
|
||||
pub struct StartsWith<T, L = Location>(sp_std::marker::PhantomData<(T, L)>);
|
||||
impl<T: Get<L>, L: TryInto<Location> + Clone> Contains<L> for StartsWith<T, L> {
|
||||
fn contains(location: &L) -> bool {
|
||||
let latest_location: Location = if let Ok(location) = (*location).clone().try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_t = if let Ok(location) = T::get().try_into() {
|
||||
location
|
||||
} else {
|
||||
return false;
|
||||
};
|
||||
let latest_location: Location =
|
||||
if let Ok(location) = (*location).clone().try_into() { location } else { return false };
|
||||
let latest_t = if let Ok(location) = T::get().try_into() { location } else { return false };
|
||||
latest_location.starts_with(&latest_t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
|
||||
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
|
||||
|
||||
title: backpressured JSON-RPC server (upgrade jsonrpsee)
|
||||
|
||||
doc:
|
||||
- audience: Node Operator
|
||||
description: |
|
||||
Modifies the jsonrpc server to be "backpressured" and it's possible to configure
|
||||
how many messages can be "buffered" via the CLI `--rpc_message_buffer_capacity`.
|
||||
|
||||
Major changes in this PR:
|
||||
- The subscriptions are now bounded and if subscription can't keep up with the server it is dropped
|
||||
- CLI: add parameter to configure the jsonrpc server bounded message buffer (default is 64)
|
||||
- Add our own subscription helper to deal with the unbounded streams in substrate
|
||||
|
||||
crates:
|
||||
- name: sc-rpc-server
|
||||
@@ -23,7 +23,7 @@ name = "minimal-node"
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
futures = { version = "0.3.21", features = ["thread-pool"] }
|
||||
futures-timer = "3.0.1"
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
serde_json = "1.0.111"
|
||||
|
||||
sc-cli = { path = "../../../client/cli" }
|
||||
|
||||
@@ -48,7 +48,7 @@ frame-system = { path = "../../../frame/system" }
|
||||
pallet-transaction-payment = { path = "../../../frame/transaction-payment", default-features = false }
|
||||
|
||||
# These dependencies are used for the node template's RPCs
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
sp-api = { path = "../../../primitives/api" }
|
||||
sc-rpc-api = { path = "../../../client/rpc-api" }
|
||||
sp-blockchain = { path = "../../../primitives/blockchain" }
|
||||
|
||||
@@ -44,7 +44,7 @@ array-bytes = "6.1"
|
||||
clap = { version = "4.4.18", features = ["derive"], optional = true }
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
futures = "0.3.21"
|
||||
log = "0.4.17"
|
||||
rand = "0.8"
|
||||
|
||||
@@ -83,6 +83,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
|
||||
rpc_id_provider: Default::default(),
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_port: 9944,
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
|
||||
@@ -79,6 +79,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
|
||||
rpc_id_provider: Default::default(),
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_port: 9944,
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
node-primitives = { path = "../primitives" }
|
||||
pallet-transaction-payment-rpc = { path = "../../../frame/transaction-payment/rpc" }
|
||||
mmr-rpc = { path = "../../../client/merkle-mountain-range/rpc" }
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
},
|
||||
CliConfiguration, PrometheusParams, RuntimeParams, TelemetryParams,
|
||||
RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
|
||||
RPC_DEFAULT_MAX_SUBS_PER_CONN,
|
||||
RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
|
||||
};
|
||||
use clap::Parser;
|
||||
use regex::Regex;
|
||||
@@ -102,9 +102,20 @@ pub struct RunCmd {
|
||||
#[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)]
|
||||
pub rpc_max_connections: u32,
|
||||
|
||||
/// Specify browser *origins* allowed to access the HTTP and WS RPC servers.
|
||||
/// The number of messages the RPC server is allowed to keep in memory.
|
||||
///
|
||||
/// A comma-separated list of origins (`protocol://domain` or special `null`
|
||||
/// If the buffer becomes full then the server will not process
|
||||
/// new messages until the connected client start reading the
|
||||
/// underlying messages.
|
||||
///
|
||||
/// This applies per connection which includes both
|
||||
/// JSON-RPC methods calls and subscriptions.
|
||||
#[arg(long, default_value_t = RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)]
|
||||
pub rpc_message_buffer_capacity_per_connection: u32,
|
||||
|
||||
/// Specify browser *origins* allowed to access the HTTP & WS RPC servers.
|
||||
///
|
||||
/// A comma-separated list of origins (protocol://domain or special `null`
|
||||
/// value). Value of `all` will disable origin validation. Default is to
|
||||
/// allow localhost and <https://polkadot.js.org> origins. When running in
|
||||
/// `--dev` mode the default is to allow all origins.
|
||||
|
||||
@@ -52,8 +52,11 @@ pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
|
||||
pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
|
||||
/// The default max response size in MB.
|
||||
pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15;
|
||||
/// The default number of connection..
|
||||
/// The default concurrent connection limit.
|
||||
pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100;
|
||||
/// The default number of messages the RPC server
|
||||
/// is allowed to keep in memory per connection.
|
||||
pub const RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN: u32 = 64;
|
||||
|
||||
/// Default configuration values used by Substrate
|
||||
///
|
||||
@@ -330,6 +333,11 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
|
||||
Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN)
|
||||
}
|
||||
|
||||
/// The number of messages the RPC server is allowed to keep in memory per connection.
|
||||
fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
|
||||
Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)
|
||||
}
|
||||
|
||||
/// Get the prometheus configuration (`None` if disabled)
|
||||
///
|
||||
/// By default this is `None`.
|
||||
@@ -501,6 +509,7 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
|
||||
rpc_id_provider: None,
|
||||
rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
|
||||
rpc_port: DCV::rpc_listen_port(),
|
||||
rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?,
|
||||
prometheus_config: self
|
||||
.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
|
||||
telemetry_endpoints,
|
||||
|
||||
@@ -269,6 +269,7 @@ mod tests {
|
||||
rpc_max_response_size: Default::default(),
|
||||
rpc_id_provider: Default::default(),
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
rpc_port: 9944,
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
futures = "0.3.21"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
|
||||
@@ -22,15 +22,15 @@ use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use futures::TryFutureExt;
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError, RpcResult},
|
||||
core::async_trait,
|
||||
proc_macros::rpc,
|
||||
types::{error::CallError, ErrorObject},
|
||||
types::{ErrorObject, ErrorObjectOwned},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use sc_consensus_babe::{authorship, BabeWorkerHandle};
|
||||
use sc_consensus_epochs::Epoch as EpochT;
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sc_rpc_api::{DenyUnsafe, UnsafeRpcError};
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_application_crypto::AppCrypto;
|
||||
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
|
||||
@@ -48,7 +48,7 @@ pub trait BabeApi {
|
||||
/// Returns data about which slots (primary or secondary) can be claimed in the current epoch
|
||||
/// with the keys in the keystore.
|
||||
#[method(name = "babe_epochAuthorship")]
|
||||
async fn epoch_authorship(&self) -> RpcResult<HashMap<AuthorityId, EpochAuthorship>>;
|
||||
async fn epoch_authorship(&self) -> Result<HashMap<AuthorityId, EpochAuthorship>, Error>;
|
||||
}
|
||||
|
||||
/// Provides RPC methods for interacting with Babe.
|
||||
@@ -89,7 +89,7 @@ where
|
||||
C::Api: BabeRuntimeApi<B>,
|
||||
SC: SelectChain<B> + Clone + 'static,
|
||||
{
|
||||
async fn epoch_authorship(&self) -> RpcResult<HashMap<AuthorityId, EpochAuthorship>> {
|
||||
async fn epoch_authorship(&self) -> Result<HashMap<AuthorityId, EpochAuthorship>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?;
|
||||
@@ -147,7 +147,7 @@ where
|
||||
}
|
||||
|
||||
/// Holds information about the `slot`'s that can be claimed by a given key.
|
||||
#[derive(Default, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Default, Debug, Deserialize, Serialize)]
|
||||
pub struct EpochAuthorship {
|
||||
/// the array of primary slots that can be claimed
|
||||
primary: Vec<u64>,
|
||||
@@ -166,20 +166,26 @@ pub enum Error {
|
||||
/// Failed to fetch epoch data.
|
||||
#[error("Failed to fetch epoch data")]
|
||||
FetchEpoch,
|
||||
/// Consensus error
|
||||
#[error(transparent)]
|
||||
Consensus(#[from] ConsensusError),
|
||||
/// Errors that can be formatted as a String
|
||||
#[error("{0}")]
|
||||
StringError(String),
|
||||
/// Call to an unsafe RPC was denied.
|
||||
#[error(transparent)]
|
||||
UnsafeRpcCalled(#[from] UnsafeRpcError),
|
||||
}
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(error: Error) -> Self {
|
||||
let error_code = match error {
|
||||
Error::SelectChain(_) => 1,
|
||||
Error::FetchEpoch => 2,
|
||||
};
|
||||
|
||||
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
|
||||
BABE_ERROR + error_code,
|
||||
error.to_string(),
|
||||
Some(format!("{:?}", error)),
|
||||
)))
|
||||
match error {
|
||||
Error::SelectChain(e) => ErrorObject::owned(BABE_ERROR + 1, e.to_string(), None::<()>),
|
||||
Error::FetchEpoch => ErrorObject::owned(BABE_ERROR + 2, error.to_string(), None::<()>),
|
||||
Error::Consensus(e) => ErrorObject::owned(BABE_ERROR + 3, e.to_string(), None::<()>),
|
||||
Error::StringError(e) => ErrorObject::owned(BABE_ERROR + 4, e, None::<()>),
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +257,7 @@ mod tests {
|
||||
let api = babe_rpc.into_rpc();
|
||||
|
||||
let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#;
|
||||
let (response, _) = api.raw_json_request(request).await.unwrap();
|
||||
let (response, _) = api.raw_json_request(request, 1).await.unwrap();
|
||||
let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#;
|
||||
|
||||
assert_eq!(&response.result, expected);
|
||||
@@ -263,7 +269,7 @@ mod tests {
|
||||
let api = babe_rpc.into_rpc();
|
||||
|
||||
let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#;
|
||||
let (response, _) = api.raw_json_request(request).await.unwrap();
|
||||
let (response, _) = api.raw_json_request(request, 1).await.unwrap();
|
||||
let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#;
|
||||
|
||||
assert_eq!(&response.result, expected);
|
||||
|
||||
@@ -14,7 +14,7 @@ workspace = true
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] }
|
||||
futures = "0.3.21"
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
log = "0.4"
|
||||
parking_lot = "0.12.1"
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
|
||||
@@ -23,15 +23,15 @@
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
use sc_rpc::SubscriptionTaskExecutor;
|
||||
use sc_rpc::{utils::pipe_from_stream, SubscriptionTaskExecutor};
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
|
||||
use futures::{task::SpawnError, FutureExt, StreamExt};
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError, RpcResult},
|
||||
core::async_trait,
|
||||
proc_macros::rpc,
|
||||
types::{error::CallError, ErrorObject, SubscriptionResult},
|
||||
SubscriptionSink,
|
||||
types::{ErrorObject, ErrorObjectOwned},
|
||||
PendingSubscriptionSink,
|
||||
};
|
||||
use log::warn;
|
||||
|
||||
@@ -69,15 +69,11 @@ impl From<Error> for ErrorCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(error: Error) -> Self {
|
||||
let message = error.to_string();
|
||||
let code = ErrorCode::from(error);
|
||||
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
|
||||
code as i32,
|
||||
message,
|
||||
None::<()>,
|
||||
)))
|
||||
ErrorObject::owned(code as i32, message, None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +94,7 @@ pub trait BeefyApi<Notification, Hash> {
|
||||
/// in the network or if the client is still initializing or syncing with the network.
|
||||
/// In such case an error would be returned.
|
||||
#[method(name = "beefy_getFinalizedHead")]
|
||||
async fn latest_finalized(&self) -> RpcResult<Hash>;
|
||||
async fn latest_finalized(&self) -> Result<Hash, Error>;
|
||||
}
|
||||
|
||||
/// Implements the BeefyApi RPC trait for interacting with BEEFY.
|
||||
@@ -138,27 +134,17 @@ impl<Block> BeefyApiServer<notification::EncodedVersionedFinalityProof, Block::H
|
||||
where
|
||||
Block: BlockT,
|
||||
{
|
||||
fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult {
|
||||
fn subscribe_justifications(&self, pending: PendingSubscriptionSink) {
|
||||
let stream = self
|
||||
.finality_proof_stream
|
||||
.subscribe(100_000)
|
||||
.map(|vfp| notification::EncodedVersionedFinalityProof::new::<Block>(vfp));
|
||||
|
||||
let fut = async move {
|
||||
sink.pipe_from_stream(stream).await;
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
sc_rpc::utils::spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream));
|
||||
}
|
||||
|
||||
async fn latest_finalized(&self) -> RpcResult<Block::Hash> {
|
||||
self.beefy_best_block
|
||||
.read()
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.ok_or(Error::EndpointNotReady)
|
||||
.map_err(Into::into)
|
||||
async fn latest_finalized(&self) -> Result<Block::Hash, Error> {
|
||||
self.beefy_best_block.read().as_ref().cloned().ok_or(Error::EndpointNotReady)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +153,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule};
|
||||
use jsonrpsee::{core::EmptyServerParams as EmptyParams, RpcModule};
|
||||
use sc_consensus_beefy::{
|
||||
communication::notification::BeefyVersionedFinalityProofSender,
|
||||
justification::BeefyVersionedFinalityProof,
|
||||
@@ -199,7 +185,7 @@ mod tests {
|
||||
let (rpc, _) = setup_io_handler();
|
||||
let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#;
|
||||
let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string();
|
||||
let (response, _) = rpc.raw_json_request(&request).await.unwrap();
|
||||
let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
|
||||
|
||||
assert_eq!(expected_response, response.result);
|
||||
}
|
||||
@@ -230,13 +216,13 @@ mod tests {
|
||||
|
||||
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
|
||||
while std::time::Instant::now() < deadline {
|
||||
let (response, _) = io.raw_json_request(request).await.expect("RPC requests work");
|
||||
let (response, _) = io.raw_json_request(request, 1).await.expect("RPC requests work");
|
||||
if response.result != not_ready {
|
||||
assert_eq!(response.result, expected);
|
||||
// Success
|
||||
return
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(50))
|
||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
panic!(
|
||||
@@ -249,7 +235,7 @@ mod tests {
|
||||
let (rpc, _) = setup_io_handler();
|
||||
// Subscribe call.
|
||||
let _sub = rpc
|
||||
.subscribe("beefy_subscribeJustifications", EmptyParams::new())
|
||||
.subscribe_unbounded("beefy_subscribeJustifications", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -257,6 +243,7 @@ mod tests {
|
||||
let (response, _) = rpc
|
||||
.raw_json_request(
|
||||
r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#,
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -284,7 +271,7 @@ mod tests {
|
||||
|
||||
// Subscribe
|
||||
let mut sub = rpc
|
||||
.subscribe("beefy_subscribeJustifications", EmptyParams::new())
|
||||
.subscribe_unbounded("beefy_subscribeJustifications", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ pub enum ImportResult {
|
||||
}
|
||||
|
||||
/// Auxiliary data associated with an imported block result.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ImportedAux {
|
||||
/// Only the header has been imported. Block body verification was skipped.
|
||||
pub header_only: bool,
|
||||
|
||||
@@ -15,7 +15,7 @@ workspace = true
|
||||
[dependencies]
|
||||
finality-grandpa = { version = "0.16.2", features = ["derive-codec"] }
|
||||
futures = "0.3.16"
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
log = "0.4.8"
|
||||
parity-scale-codec = { version = "3.6.1", features = ["derive"] }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
/// Top-level error type for the RPC handler
|
||||
@@ -61,15 +58,11 @@ impl From<Error> for ErrorCode {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(error: Error) -> Self {
|
||||
let message = error.to_string();
|
||||
let code = ErrorCode::from(error);
|
||||
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
|
||||
code as i32,
|
||||
message,
|
||||
None::<()>,
|
||||
)))
|
||||
ErrorObject::owned(code as i32, message, None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sc_consensus_grandpa::FinalityProofProvider;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct EncodedFinalityProof(pub sp_core::Bytes);
|
||||
|
||||
/// Local trait mainly to allow mocking in tests.
|
||||
|
||||
@@ -19,15 +19,13 @@
|
||||
//! RPC API for GRANDPA.
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use futures::{FutureExt, StreamExt};
|
||||
use futures::StreamExt;
|
||||
use log::warn;
|
||||
use std::sync::Arc;
|
||||
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, RpcResult},
|
||||
core::{async_trait, server::PendingSubscriptionSink},
|
||||
proc_macros::rpc,
|
||||
types::SubscriptionResult,
|
||||
SubscriptionSink,
|
||||
};
|
||||
|
||||
mod error;
|
||||
@@ -35,13 +33,13 @@ mod finality;
|
||||
mod notification;
|
||||
mod report;
|
||||
|
||||
use sc_consensus_grandpa::GrandpaJustificationStream;
|
||||
use sc_rpc::SubscriptionTaskExecutor;
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
use error::Error;
|
||||
use finality::{EncodedFinalityProof, RpcFinalityProofProvider};
|
||||
use notification::JustificationNotification;
|
||||
use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates};
|
||||
use sc_consensus_grandpa::GrandpaJustificationStream;
|
||||
use sc_rpc::{utils::pipe_from_stream, SubscriptionTaskExecutor};
|
||||
use sp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
/// Provides RPC methods for interacting with GRANDPA.
|
||||
#[rpc(client, server)]
|
||||
@@ -49,7 +47,7 @@ pub trait GrandpaApi<Notification, Hash, Number> {
|
||||
/// Returns the state of the current best round state as well as the
|
||||
/// ongoing background rounds.
|
||||
#[method(name = "grandpa_roundState")]
|
||||
async fn round_state(&self) -> RpcResult<ReportedRoundStates>;
|
||||
async fn round_state(&self) -> Result<ReportedRoundStates, Error>;
|
||||
|
||||
/// Returns the block most recently finalized by Grandpa, alongside
|
||||
/// side its justification.
|
||||
@@ -63,7 +61,7 @@ pub trait GrandpaApi<Notification, Hash, Number> {
|
||||
/// Prove finality for the given block number by returning the Justification for the last block
|
||||
/// in the set and all the intermediary headers to link them together.
|
||||
#[method(name = "grandpa_proveFinality")]
|
||||
async fn prove_finality(&self, block: Number) -> RpcResult<Option<EncodedFinalityProof>>;
|
||||
async fn prove_finality(&self, block: Number) -> Result<Option<EncodedFinalityProof>, Error>;
|
||||
}
|
||||
|
||||
/// Provides RPC methods for interacting with GRANDPA.
|
||||
@@ -99,36 +97,28 @@ where
|
||||
Block: BlockT,
|
||||
ProofProvider: RpcFinalityProofProvider<Block> + Send + Sync + 'static,
|
||||
{
|
||||
async fn round_state(&self) -> RpcResult<ReportedRoundStates> {
|
||||
ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into)
|
||||
async fn round_state(&self) -> Result<ReportedRoundStates, Error> {
|
||||
ReportedRoundStates::from(&self.authority_set, &self.voter_state)
|
||||
}
|
||||
|
||||
fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult {
|
||||
fn subscribe_justifications(&self, pending: PendingSubscriptionSink) {
|
||||
let stream = self.justification_stream.subscribe(100_000).map(
|
||||
|x: sc_consensus_grandpa::GrandpaJustification<Block>| {
|
||||
JustificationNotification::from(x)
|
||||
},
|
||||
);
|
||||
|
||||
let fut = async move {
|
||||
sink.pipe_from_stream(stream).await;
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
sc_rpc::utils::spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream));
|
||||
}
|
||||
|
||||
async fn prove_finality(
|
||||
&self,
|
||||
block: NumberFor<Block>,
|
||||
) -> RpcResult<Option<EncodedFinalityProof>> {
|
||||
self.finality_proof_provider
|
||||
.rpc_prove_finality(block)
|
||||
.map_err(|e| {
|
||||
warn!("Error proving finality: {}", e);
|
||||
error::Error::ProveFinalityFailed(e)
|
||||
})
|
||||
.map_err(Into::into)
|
||||
) -> Result<Option<EncodedFinalityProof>, Error> {
|
||||
self.finality_proof_provider.rpc_prove_finality(block).map_err(|e| {
|
||||
warn!("Error proving finality: {}", e);
|
||||
error::Error::ProveFinalityFailed(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,17 +127,15 @@ mod tests {
|
||||
use super::*;
|
||||
use std::{collections::HashSet, convert::TryInto, sync::Arc};
|
||||
|
||||
use jsonrpsee::{
|
||||
types::{EmptyServerParams as EmptyParams, SubscriptionId},
|
||||
RpcModule,
|
||||
};
|
||||
use jsonrpsee::{core::EmptyServerParams as EmptyParams, types::SubscriptionId, RpcModule};
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sc_consensus_grandpa::{
|
||||
report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender,
|
||||
};
|
||||
use sc_rpc::testing::test_executor;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_core::{crypto::ByteArray, testing::TaskExecutor};
|
||||
use sp_core::crypto::ByteArray;
|
||||
use sp_keyring::Ed25519Keyring;
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
|
||||
use substrate_test_runtime_client::{
|
||||
@@ -264,7 +252,7 @@ mod tests {
|
||||
{
|
||||
let (justification_sender, justification_stream) = GrandpaJustificationStream::channel();
|
||||
let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof });
|
||||
let executor = Arc::new(TaskExecutor::default());
|
||||
let executor = test_executor();
|
||||
|
||||
let rpc = Grandpa::new(
|
||||
executor,
|
||||
@@ -283,7 +271,7 @@ mod tests {
|
||||
let (rpc, _) = setup_io_handler(EmptyVoterState);
|
||||
let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string();
|
||||
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
|
||||
let (response, _) = rpc.raw_json_request(&request).await.unwrap();
|
||||
let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
|
||||
|
||||
assert_eq!(expected_response, response.result);
|
||||
}
|
||||
@@ -306,7 +294,7 @@ mod tests {
|
||||
},\"id\":0}".to_string();
|
||||
|
||||
let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#;
|
||||
let (response, _) = rpc.raw_json_request(&request).await.unwrap();
|
||||
let (response, _) = rpc.raw_json_request(&request, 1).await.unwrap();
|
||||
assert_eq!(expected_response, response.result);
|
||||
}
|
||||
|
||||
@@ -315,7 +303,7 @@ mod tests {
|
||||
let (rpc, _) = setup_io_handler(TestVoterState);
|
||||
// Subscribe call.
|
||||
let _sub = rpc
|
||||
.subscribe("grandpa_subscribeJustifications", EmptyParams::new())
|
||||
.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -323,6 +311,7 @@ mod tests {
|
||||
let (response, _) = rpc
|
||||
.raw_json_request(
|
||||
r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#,
|
||||
1,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -385,7 +374,7 @@ mod tests {
|
||||
let (rpc, justification_sender) = setup_io_handler(TestVoterState);
|
||||
|
||||
let mut sub = rpc
|
||||
.subscribe("grandpa_subscribeJustifications", EmptyParams::new())
|
||||
.subscribe_unbounded("grandpa_subscribeJustifications", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -57,21 +57,21 @@ impl ReportVoterState for SharedVoterState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Prevotes {
|
||||
current_weight: u32,
|
||||
missing: BTreeSet<AuthorityId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Precommits {
|
||||
current_weight: u32,
|
||||
missing: BTreeSet<AuthorityId>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct RoundState {
|
||||
round: u32,
|
||||
@@ -111,7 +111,7 @@ impl RoundState {
|
||||
|
||||
/// The state of the current best round, as well as the background rounds in a
|
||||
/// form suitable for serialization.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReportedRoundStates {
|
||||
set_id: u32,
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
assert_matches = "1.3.0"
|
||||
async-trait = "0.1.74"
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
|
||||
@@ -20,10 +20,7 @@
|
||||
//! This is suitable for a testing environment.
|
||||
|
||||
use futures::channel::{mpsc::SendError, oneshot};
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
use sc_consensus::ImportResult;
|
||||
use sp_blockchain::Error as BlockchainError;
|
||||
use sp_consensus::Error as ConsensusError;
|
||||
@@ -106,8 +103,8 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(err: Error) -> Self {
|
||||
CallError::Custom(ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)).into()
|
||||
ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,10 +23,7 @@ use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt,
|
||||
};
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError, RpcResult},
|
||||
proc_macros::rpc,
|
||||
};
|
||||
use jsonrpsee::{core::async_trait, proc_macros::rpc};
|
||||
use sc_consensus::ImportedAux;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_runtime::EncodedJustification;
|
||||
@@ -74,7 +71,7 @@ pub trait ManualSealApi<Hash> {
|
||||
create_empty: bool,
|
||||
finalize: bool,
|
||||
parent_hash: Option<Hash>,
|
||||
) -> RpcResult<CreatedBlock<Hash>>;
|
||||
) -> Result<CreatedBlock<Hash>, Error>;
|
||||
|
||||
/// Instructs the manual-seal authorship task to finalize a block
|
||||
#[method(name = "engine_finalizeBlock")]
|
||||
@@ -82,7 +79,7 @@ pub trait ManualSealApi<Hash> {
|
||||
&self,
|
||||
hash: Hash,
|
||||
justification: Option<EncodedJustification>,
|
||||
) -> RpcResult<bool>;
|
||||
) -> Result<bool, Error>;
|
||||
}
|
||||
|
||||
/// A struct that implements the [`ManualSealApiServer`].
|
||||
@@ -91,7 +88,7 @@ pub struct ManualSeal<Hash> {
|
||||
}
|
||||
|
||||
/// return type of `engine_createBlock`
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct CreatedBlock<Hash> {
|
||||
/// hash of the created block.
|
||||
pub hash: Hash,
|
||||
@@ -115,7 +112,7 @@ impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
|
||||
create_empty: bool,
|
||||
finalize: bool,
|
||||
parent_hash: Option<Hash>,
|
||||
) -> RpcResult<CreatedBlock<Hash>> {
|
||||
) -> Result<CreatedBlock<Hash>, Error> {
|
||||
let mut sink = self.import_block_channel.clone();
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
// NOTE: this sends a Result over the channel.
|
||||
@@ -131,7 +128,7 @@ impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
|
||||
match receiver.await {
|
||||
Ok(Ok(rx)) => Ok(rx),
|
||||
Ok(Err(e)) => Err(e.into()),
|
||||
Err(e) => Err(JsonRpseeError::to_call_error(e)),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,12 +136,12 @@ impl<Hash: Send + 'static> ManualSealApiServer<Hash> for ManualSeal<Hash> {
|
||||
&self,
|
||||
hash: Hash,
|
||||
justification: Option<EncodedJustification>,
|
||||
) -> RpcResult<bool> {
|
||||
) -> Result<bool, Error> {
|
||||
let mut sink = self.import_block_channel.clone();
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification };
|
||||
sink.send(command).await?;
|
||||
receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
receiver.await.map(|_| true).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
sp-api = { path = "../../../primitives/api" }
|
||||
sp-blockchain = { path = "../../../primitives/blockchain" }
|
||||
sp-core = { path = "../../../primitives/core" }
|
||||
sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" }
|
||||
sp-runtime = { path = "../../../primitives/runtime" }
|
||||
anyhow = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1.0.111"
|
||||
|
||||
@@ -26,7 +26,7 @@ use codec::{Codec, Decode, Encode};
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, RpcResult},
|
||||
proc_macros::rpc,
|
||||
types::error::{CallError, ErrorObject},
|
||||
types::{error::ErrorObject, ErrorObjectOwned},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -189,11 +189,9 @@ where
|
||||
fn verify_proof(&self, proof: LeavesProof<<Block as BlockT>::Hash>) -> RpcResult<bool> {
|
||||
let mut api = self.client.runtime_api();
|
||||
|
||||
let leaves = Decode::decode(&mut &proof.leaves.0[..])
|
||||
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
|
||||
let leaves = Decode::decode(&mut &proof.leaves.0[..]).map_err(invalid_params)?;
|
||||
|
||||
let decoded_proof = Decode::decode(&mut &proof.proof.0[..])
|
||||
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
|
||||
let decoded_proof = Decode::decode(&mut &proof.proof.0[..]).map_err(invalid_params)?;
|
||||
|
||||
api.register_extension(OffchainDbExt::new(self.offchain_db.clone()));
|
||||
|
||||
@@ -211,11 +209,9 @@ where
|
||||
) -> RpcResult<bool> {
|
||||
let api = self.client.runtime_api();
|
||||
|
||||
let leaves = Decode::decode(&mut &proof.leaves.0[..])
|
||||
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
|
||||
let leaves = Decode::decode(&mut &proof.leaves.0[..]).map_err(invalid_params)?;
|
||||
|
||||
let decoded_proof = Decode::decode(&mut &proof.proof.0[..])
|
||||
.map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?;
|
||||
let decoded_proof = Decode::decode(&mut &proof.proof.0[..]).map_err(invalid_params)?;
|
||||
|
||||
api.verify_proof_stateless(proof.block_hash, mmr_root, leaves, decoded_proof)
|
||||
.map_err(runtime_error_into_rpc_error)?
|
||||
@@ -226,7 +222,7 @@ where
|
||||
}
|
||||
|
||||
/// Converts an mmr-specific error into a [`CallError`].
|
||||
fn mmr_error_into_rpc_error(err: MmrError) -> CallError {
|
||||
fn mmr_error_into_rpc_error(err: MmrError) -> ErrorObjectOwned {
|
||||
let error_code = MMR_ERROR +
|
||||
match err {
|
||||
MmrError::LeafNotFound => 1,
|
||||
@@ -237,16 +233,20 @@ fn mmr_error_into_rpc_error(err: MmrError) -> CallError {
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
CallError::Custom(ErrorObject::owned(error_code, err.to_string(), Some(format!("{:?}", err))))
|
||||
ErrorObject::owned(error_code, err.to_string(), Some(format!("{:?}", err)))
|
||||
}
|
||||
|
||||
/// Converts a runtime trap into a [`CallError`].
|
||||
fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> CallError {
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
RUNTIME_ERROR,
|
||||
"Runtime trapped",
|
||||
Some(format!("{:?}", err)),
|
||||
))
|
||||
fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> ErrorObjectOwned {
|
||||
ErrorObject::owned(RUNTIME_ERROR, "Runtime trapped", Some(format!("{:?}", err)))
|
||||
}
|
||||
|
||||
fn invalid_params(e: impl std::error::Error) -> ErrorObjectOwned {
|
||||
ErrorObject::owned(
|
||||
jsonrpsee::types::error::ErrorCode::InvalidParams.code(),
|
||||
e.to_string(),
|
||||
None::<()>,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -469,7 +469,7 @@ where
|
||||
Ok(chain_sync) => chain_sync,
|
||||
Err(e) => {
|
||||
error!(target: LOG_TARGET, "Failed to start `ChainSync`.");
|
||||
return Err(e);
|
||||
return Err(e)
|
||||
},
|
||||
};
|
||||
// Let `ChainSync` know about connected peers.
|
||||
|
||||
@@ -28,4 +28,4 @@ sp-core = { path = "../../primitives/core" }
|
||||
sp-rpc = { path = "../../primitives/rpc" }
|
||||
sp-runtime = { path = "../../primitives/runtime" }
|
||||
sp-version = { path = "../../primitives/version" }
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Authoring RPC module errors.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
use sp_runtime::transaction_validity::InvalidTransaction;
|
||||
|
||||
/// Author RPC Result type.
|
||||
@@ -86,98 +83,104 @@ const POOL_NO_TAGS: i32 = POOL_INVALID_TX + 9;
|
||||
const POOL_INVALID_BLOCK_ID: i32 = POOL_INVALID_TX + 10;
|
||||
/// The pool is not accepting future transactions.
|
||||
const POOL_FUTURE_TX: i32 = POOL_INVALID_TX + 11;
|
||||
/// Other error.
|
||||
const OTHER_ERR: i32 = BASE_ERROR + 40;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
fn from(e: Error) -> Self {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> ErrorObjectOwned {
|
||||
use sc_transaction_pool_api::error::Error as PoolError;
|
||||
|
||||
match e {
|
||||
Error::BadFormat(e) => CallError::Custom(ErrorObject::owned(
|
||||
Error::BadFormat(e) => ErrorObject::owned(
|
||||
BAD_FORMAT,
|
||||
format!("Extrinsic has invalid format: {}", e),
|
||||
None::<()>,
|
||||
)),
|
||||
Error::Verification(e) => CallError::Custom(ErrorObject::owned(
|
||||
),
|
||||
Error::Verification(e) => ErrorObject::owned(
|
||||
VERIFICATION_ERROR,
|
||||
format!("Verification Error: {}", e),
|
||||
Some(format!("{:?}", e)),
|
||||
)),
|
||||
),
|
||||
Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => {
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_INVALID_TX,
|
||||
"Invalid Transaction",
|
||||
Some(format!("Custom error: {}", e)),
|
||||
))
|
||||
)
|
||||
},
|
||||
Error::Pool(PoolError::InvalidTransaction(e)) => {
|
||||
let msg: &str = e.into();
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_INVALID_TX,
|
||||
"Invalid Transaction",
|
||||
Some(msg),
|
||||
))
|
||||
)
|
||||
},
|
||||
Error::Pool(PoolError::UnknownTransaction(e)) => {
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_UNKNOWN_VALIDITY,
|
||||
"Unknown Transaction Validity",
|
||||
Some(format!("{:?}", e)),
|
||||
))
|
||||
)
|
||||
},
|
||||
Error::Pool(PoolError::TemporarilyBanned) =>
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_TEMPORARILY_BANNED,
|
||||
"Transaction is temporarily banned",
|
||||
None::<()>,
|
||||
)),
|
||||
),
|
||||
Error::Pool(PoolError::AlreadyImported(hash)) =>
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_ALREADY_IMPORTED,
|
||||
"Transaction Already Imported",
|
||||
Some(format!("{:?}", hash)),
|
||||
)),
|
||||
Error::Pool(PoolError::TooLowPriority { old, new }) => CallError::Custom(ErrorObject::owned(
|
||||
),
|
||||
Error::Pool(PoolError::TooLowPriority { old, new }) => ErrorObject::owned(
|
||||
POOL_TOO_LOW_PRIORITY,
|
||||
format!("Priority is too low: ({} vs {})", old, new),
|
||||
Some("The transaction has too low priority to replace another transaction already in the pool.")
|
||||
)),
|
||||
),
|
||||
Error::Pool(PoolError::CycleDetected) =>
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_CYCLE_DETECTED,
|
||||
"Cycle Detected",
|
||||
None::<()>
|
||||
)),
|
||||
Error::Pool(PoolError::ImmediatelyDropped) => CallError::Custom(ErrorObject::owned(
|
||||
),
|
||||
Error::Pool(PoolError::ImmediatelyDropped) => ErrorObject::owned(
|
||||
POOL_IMMEDIATELY_DROPPED,
|
||||
"Immediately Dropped",
|
||||
Some("The transaction couldn't enter the pool because of the limit"),
|
||||
)),
|
||||
Error::Pool(PoolError::Unactionable) => CallError::Custom(ErrorObject::owned(
|
||||
),
|
||||
Error::Pool(PoolError::Unactionable) => ErrorObject::owned(
|
||||
POOL_UNACTIONABLE,
|
||||
"Unactionable",
|
||||
Some("The transaction is unactionable since it is not propagable and \
|
||||
the local node does not author blocks")
|
||||
)),
|
||||
Error::Pool(PoolError::NoTagsProvided) => CallError::Custom(ErrorObject::owned(
|
||||
),
|
||||
Error::Pool(PoolError::NoTagsProvided) => ErrorObject::owned(
|
||||
POOL_NO_TAGS,
|
||||
"No tags provided",
|
||||
Some("Transaction does not provide any tags, so the pool can't identify it")
|
||||
)),
|
||||
),
|
||||
Error::Pool(PoolError::InvalidBlockId(_)) =>
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_INVALID_BLOCK_ID,
|
||||
"The provided block ID is not valid",
|
||||
None::<()>
|
||||
)),
|
||||
),
|
||||
Error::Pool(PoolError::RejectedFutureTransaction) => {
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorObject::owned(
|
||||
POOL_FUTURE_TX,
|
||||
"The pool is not accepting future transactions",
|
||||
None::<()>,
|
||||
))
|
||||
)
|
||||
},
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
e => CallError::Failed(e.into()),
|
||||
}.into()
|
||||
other => ErrorObject::owned(
|
||||
OTHER_ERR,
|
||||
other.to_string(),
|
||||
None::<()>,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,27 +18,28 @@
|
||||
|
||||
//! Substrate block-author/full-node API.
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use sp_core::Bytes;
|
||||
|
||||
pub mod error;
|
||||
pub mod hash;
|
||||
|
||||
use error::Error;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sc_transaction_pool_api::TransactionStatus;
|
||||
use sp_core::Bytes;
|
||||
|
||||
/// Substrate authoring RPC API
|
||||
#[rpc(client, server)]
|
||||
pub trait AuthorApi<Hash, BlockHash> {
|
||||
/// Submit hex-encoded extrinsic for inclusion in block.
|
||||
#[method(name = "author_submitExtrinsic")]
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<Hash>;
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> Result<Hash, Error>;
|
||||
|
||||
/// Insert a key into the keystore.
|
||||
#[method(name = "author_insertKey")]
|
||||
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()>;
|
||||
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<(), Error>;
|
||||
|
||||
/// Generate new session keys and returns the corresponding public keys.
|
||||
#[method(name = "author_rotateKeys")]
|
||||
fn rotate_keys(&self) -> RpcResult<Bytes>;
|
||||
fn rotate_keys(&self) -> Result<Bytes, Error>;
|
||||
|
||||
/// Checks if the keystore has private keys for the given session public keys.
|
||||
///
|
||||
@@ -46,24 +47,24 @@ pub trait AuthorApi<Hash, BlockHash> {
|
||||
///
|
||||
/// Returns `true` iff all private keys could be found.
|
||||
#[method(name = "author_hasSessionKeys")]
|
||||
fn has_session_keys(&self, session_keys: Bytes) -> RpcResult<bool>;
|
||||
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool, Error>;
|
||||
|
||||
/// Checks if the keystore has private keys for the given public key and key type.
|
||||
///
|
||||
/// Returns `true` if a private key could be found.
|
||||
#[method(name = "author_hasKey")]
|
||||
fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult<bool>;
|
||||
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool, Error>;
|
||||
|
||||
/// Returns all pending extrinsics, potentially grouped by sender.
|
||||
#[method(name = "author_pendingExtrinsics")]
|
||||
fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>>;
|
||||
fn pending_extrinsics(&self) -> Result<Vec<Bytes>, Error>;
|
||||
|
||||
/// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting.
|
||||
#[method(name = "author_removeExtrinsic")]
|
||||
fn remove_extrinsic(
|
||||
&self,
|
||||
bytes_or_hash: Vec<hash::ExtrinsicOrHash<Hash>>,
|
||||
) -> RpcResult<Vec<Hash>>;
|
||||
) -> Result<Vec<Hash>, Error>;
|
||||
|
||||
/// Submit an extrinsic to watch.
|
||||
///
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Error helpers for Chain RPC module.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::{error::ErrorObject, ErrorObjectOwned};
|
||||
/// Chain RPC Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -39,12 +36,11 @@ pub enum Error {
|
||||
/// Base error code for all chain errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::CHAIN;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
fn from(e: Error) -> Self {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> ErrorObjectOwned {
|
||||
match e {
|
||||
Error::Other(message) =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, message, None::<()>)).into(),
|
||||
e => e.into(),
|
||||
Error::Other(message) => ErrorObject::owned(BASE_ERROR + 1, message, None::<()>),
|
||||
e => ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,21 @@
|
||||
|
||||
//! Substrate blockchain API.
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
|
||||
|
||||
pub mod error;
|
||||
|
||||
use error::Error;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
|
||||
|
||||
#[rpc(client, server)]
|
||||
pub trait ChainApi<Number, Hash, Header, SignedBlock> {
|
||||
/// Get header.
|
||||
#[method(name = "chain_getHeader", blocking)]
|
||||
fn header(&self, hash: Option<Hash>) -> RpcResult<Option<Header>>;
|
||||
fn header(&self, hash: Option<Hash>) -> Result<Option<Header>, Error>;
|
||||
|
||||
/// Get header and body of a block.
|
||||
#[method(name = "chain_getBlock", blocking)]
|
||||
fn block(&self, hash: Option<Hash>) -> RpcResult<Option<SignedBlock>>;
|
||||
fn block(&self, hash: Option<Hash>) -> Result<Option<SignedBlock>, Error>;
|
||||
|
||||
/// Get hash of the n-th block in the canon chain.
|
||||
///
|
||||
@@ -40,11 +41,11 @@ pub trait ChainApi<Number, Hash, Header, SignedBlock> {
|
||||
fn block_hash(
|
||||
&self,
|
||||
hash: Option<ListOrValue<NumberOrHex>>,
|
||||
) -> RpcResult<ListOrValue<Option<Hash>>>;
|
||||
) -> Result<ListOrValue<Option<Hash>>, Error>;
|
||||
|
||||
/// Get hash of the last finalized block in the canon chain.
|
||||
#[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"], blocking)]
|
||||
fn finalized_head(&self) -> RpcResult<Hash>;
|
||||
fn finalized_head(&self) -> Result<Hash, Error>;
|
||||
|
||||
/// All head subscription.
|
||||
#[subscription(
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! Substrate child state API
|
||||
use crate::state::ReadProof;
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use crate::state::{Error, ReadProof};
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey};
|
||||
|
||||
/// Substrate child state API
|
||||
@@ -35,7 +35,7 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
prefix: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>>;
|
||||
) -> Result<Vec<StorageKey>, Error>;
|
||||
|
||||
/// Returns the keys with prefix from a child storage with pagination support.
|
||||
/// Up to `count` keys will be returned.
|
||||
@@ -48,7 +48,7 @@ pub trait ChildStateApi<Hash> {
|
||||
count: u32,
|
||||
start_key: Option<StorageKey>,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>>;
|
||||
) -> Result<Vec<StorageKey>, Error>;
|
||||
|
||||
/// Returns a child storage entry at a specific block's state.
|
||||
#[method(name = "childstate_getStorage", blocking)]
|
||||
@@ -57,7 +57,7 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Option<StorageData>>;
|
||||
) -> Result<Option<StorageData>, Error>;
|
||||
|
||||
/// Returns child storage entries for multiple keys at a specific block's state.
|
||||
#[method(name = "childstate_getStorageEntries", blocking)]
|
||||
@@ -66,7 +66,7 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
keys: Vec<StorageKey>,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<Option<StorageData>>>;
|
||||
) -> Result<Vec<Option<StorageData>>, Error>;
|
||||
|
||||
/// Returns the hash of a child storage entry at a block's state.
|
||||
#[method(name = "childstate_getStorageHash", blocking)]
|
||||
@@ -75,7 +75,7 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Option<Hash>>;
|
||||
) -> Result<Option<Hash>, Error>;
|
||||
|
||||
/// Returns the size of a child storage entry at a block's state.
|
||||
#[method(name = "childstate_getStorageSize", blocking)]
|
||||
@@ -84,7 +84,7 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Option<u64>>;
|
||||
) -> Result<Option<u64>, Error>;
|
||||
|
||||
/// Returns proof of storage for child key entries at a specific block's state.
|
||||
#[method(name = "state_getChildReadProof", blocking)]
|
||||
@@ -93,5 +93,5 @@ pub trait ChildStateApi<Hash> {
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
keys: Vec<StorageKey>,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<ReadProof<Hash>>;
|
||||
) -> Result<ReadProof<Hash>, Error>;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
|
||||
//! Error helpers for Dev RPC module.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
|
||||
/// Dev RPC Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Dev RPC errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -46,21 +46,16 @@ pub enum Error {
|
||||
/// Base error code for all dev errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::DEV;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> Self {
|
||||
let msg = e.to_string();
|
||||
|
||||
match e {
|
||||
Error::BlockQueryError(_) =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, msg, None::<()>)),
|
||||
Error::BlockExecutionFailed =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 3, msg, None::<()>)),
|
||||
Error::WitnessCompactionFailed =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 4, msg, None::<()>)),
|
||||
Error::ProofExtractionFailed =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 5, msg, None::<()>)),
|
||||
Error::BlockQueryError(_) => ErrorObject::owned(BASE_ERROR + 1, msg, None::<()>),
|
||||
Error::BlockExecutionFailed => ErrorObject::owned(BASE_ERROR + 3, msg, None::<()>),
|
||||
Error::WitnessCompactionFailed => ErrorObject::owned(BASE_ERROR + 4, msg, None::<()>),
|
||||
Error::ProofExtractionFailed => ErrorObject::owned(BASE_ERROR + 5, msg, None::<()>),
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
pub mod error;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use error::Error;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -59,5 +60,5 @@ pub trait DevApi<Hash> {
|
||||
/// at the queried node. If either the specified block or the parent is pruned,
|
||||
/// this function will return `None`.
|
||||
#[method(name = "dev_getBlockStats")]
|
||||
fn block_stats(&self, block_hash: Hash) -> RpcResult<Option<BlockStats>>;
|
||||
fn block_stats(&self, block_hash: Hash) -> Result<Option<BlockStats>, Error>;
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
mod error;
|
||||
mod policy;
|
||||
|
||||
pub use policy::DenyUnsafe;
|
||||
pub use policy::{DenyUnsafe, UnsafeRpcError};
|
||||
|
||||
pub mod author;
|
||||
pub mod chain;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
//! Mixnet RPC module errors.
|
||||
|
||||
use jsonrpsee::types::error::{CallError, ErrorObject};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
use sc_mixnet::{PostErr, RemoteErr, TopologyErr};
|
||||
|
||||
/// Mixnet RPC error type.
|
||||
@@ -27,7 +27,7 @@ pub struct Error(pub sc_mixnet::Error);
|
||||
/// Base code for all mixnet errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::MIXNET;
|
||||
|
||||
impl From<Error> for jsonrpsee::core::Error {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(err: Error) -> Self {
|
||||
let code = match err.0 {
|
||||
sc_mixnet::Error::ServiceUnavailable => BASE_ERROR + 1,
|
||||
@@ -43,6 +43,6 @@ impl From<Error> for jsonrpsee::core::Error {
|
||||
sc_mixnet::Error::Remote(RemoteErr::Other(_)) => BASE_ERROR + 200,
|
||||
sc_mixnet::Error::Remote(RemoteErr::Decode(_)) => BASE_ERROR + 201,
|
||||
};
|
||||
CallError::Custom(ErrorObject::owned(code, err.0.to_string(), None::<()>)).into()
|
||||
ErrorObject::owned(code, err.0.to_string(), None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
|
||||
pub mod error;
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use error::Error;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_core::Bytes;
|
||||
|
||||
#[rpc(client, server)]
|
||||
pub trait MixnetApi {
|
||||
/// Submit encoded extrinsic over the mixnet for inclusion in block.
|
||||
#[method(name = "mixnet_submitExtrinsic")]
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<()>;
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Offchain RPC errors.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
|
||||
/// Offchain RPC Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -40,15 +37,14 @@ pub enum Error {
|
||||
/// Base error code for all offchain errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::OFFCHAIN;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error::UnavailableStorageKind => CallError::Custom(ErrorObject::owned(
|
||||
Error::UnavailableStorageKind => ErrorObject::owned(
|
||||
BASE_ERROR + 1,
|
||||
"This storage kind is not available yet",
|
||||
None::<()>,
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,19 +18,20 @@
|
||||
|
||||
//! Substrate offchain API.
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use sp_core::{offchain::StorageKind, Bytes};
|
||||
|
||||
pub mod error;
|
||||
|
||||
use error::Error;
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_core::{offchain::StorageKind, Bytes};
|
||||
|
||||
/// Substrate offchain RPC API
|
||||
#[rpc(client, server)]
|
||||
pub trait OffchainApi {
|
||||
/// Set offchain local storage under given key and prefix.
|
||||
#[method(name = "offchain_localStorageSet")]
|
||||
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()>;
|
||||
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error>;
|
||||
|
||||
/// Get offchain local storage under given key and prefix.
|
||||
#[method(name = "offchain_localStorageGet")]
|
||||
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult<Option<Bytes>>;
|
||||
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>, Error>;
|
||||
}
|
||||
|
||||
@@ -21,13 +21,7 @@
|
||||
//! Contains a `DenyUnsafe` type that can be used to deny potentially unsafe
|
||||
//! RPC when accessed externally.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::{
|
||||
error::{CallError, ErrorCode},
|
||||
ErrorObject,
|
||||
},
|
||||
};
|
||||
use jsonrpsee::types::{error::ErrorCode, ErrorObject, ErrorObjectOwned};
|
||||
|
||||
/// Signifies whether a potentially unsafe RPC should be denied.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -61,18 +55,8 @@ impl std::fmt::Display for UnsafeRpcError {
|
||||
|
||||
impl std::error::Error for UnsafeRpcError {}
|
||||
|
||||
impl From<UnsafeRpcError> for CallError {
|
||||
fn from(e: UnsafeRpcError) -> CallError {
|
||||
CallError::Custom(ErrorObject::owned(
|
||||
ErrorCode::MethodNotFound.code(),
|
||||
e.to_string(),
|
||||
None::<()>,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnsafeRpcError> for JsonRpseeError {
|
||||
fn from(e: UnsafeRpcError) -> JsonRpseeError {
|
||||
JsonRpseeError::Call(e.into())
|
||||
impl From<UnsafeRpcError> for ErrorObjectOwned {
|
||||
fn from(e: UnsafeRpcError) -> ErrorObjectOwned {
|
||||
ErrorObject::owned(ErrorCode::MethodNotFound.code(), e.to_string(), None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,8 @@
|
||||
|
||||
//! State RPC errors.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
|
||||
/// State RPC Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -57,16 +55,14 @@ pub enum Error {
|
||||
/// Base code for all state errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::STATE;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
fn from(e: Error) -> Self {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> ErrorObjectOwned {
|
||||
match e {
|
||||
Error::InvalidBlockRange { .. } =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, e.to_string(), None::<()>))
|
||||
.into(),
|
||||
ErrorObject::owned(BASE_ERROR + 1, e.to_string(), None::<()>),
|
||||
Error::InvalidCount { .. } =>
|
||||
CallError::Custom(ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>))
|
||||
.into(),
|
||||
e => Self::to_call_error(e),
|
||||
ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>),
|
||||
e => ErrorObject::owned(BASE_ERROR + 3, e.to_string(), None::<()>),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize};
|
||||
use sp_core::Bytes;
|
||||
|
||||
/// ReadProof struct returned by the RPC
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ReadProof<Hash> {
|
||||
/// Block hash used to generate the proof
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
//! Substrate state API.
|
||||
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_core::{
|
||||
storage::{StorageChangeSet, StorageData, StorageKey},
|
||||
Bytes,
|
||||
@@ -29,18 +29,23 @@ pub mod error;
|
||||
pub mod helpers;
|
||||
|
||||
pub use self::helpers::ReadProof;
|
||||
pub use error::Error;
|
||||
|
||||
/// Substrate state API
|
||||
#[rpc(client, server)]
|
||||
pub trait StateApi<Hash> {
|
||||
/// Call a method from the runtime API at a block's state.
|
||||
#[method(name = "state_call", aliases = ["state_callAt"], blocking)]
|
||||
fn call(&self, name: String, bytes: Bytes, hash: Option<Hash>) -> RpcResult<Bytes>;
|
||||
fn call(&self, name: String, bytes: Bytes, hash: Option<Hash>) -> Result<Bytes, Error>;
|
||||
|
||||
/// Returns the keys with prefix, leave empty to get all the keys.
|
||||
#[method(name = "state_getKeys", blocking)]
|
||||
#[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")]
|
||||
fn storage_keys(&self, prefix: StorageKey, hash: Option<Hash>) -> RpcResult<Vec<StorageKey>>;
|
||||
fn storage_keys(
|
||||
&self,
|
||||
prefix: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> Result<Vec<StorageKey>, Error>;
|
||||
|
||||
/// Returns the keys with prefix, leave empty to get all the keys
|
||||
#[method(name = "state_getPairs", blocking)]
|
||||
@@ -48,7 +53,7 @@ pub trait StateApi<Hash> {
|
||||
&self,
|
||||
prefix: StorageKey,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<(StorageKey, StorageData)>>;
|
||||
) -> Result<Vec<(StorageKey, StorageData)>, Error>;
|
||||
|
||||
/// Returns the keys with prefix with pagination support.
|
||||
/// Up to `count` keys will be returned.
|
||||
@@ -60,27 +65,28 @@ pub trait StateApi<Hash> {
|
||||
count: u32,
|
||||
start_key: Option<StorageKey>,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>>;
|
||||
) -> Result<Vec<StorageKey>, Error>;
|
||||
|
||||
/// Returns a storage entry at a specific block's state.
|
||||
#[method(name = "state_getStorage", aliases = ["state_getStorageAt"], blocking)]
|
||||
fn storage(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<StorageData>>;
|
||||
fn storage(&self, key: StorageKey, hash: Option<Hash>) -> Result<Option<StorageData>, Error>;
|
||||
|
||||
/// Returns the hash of a storage entry at a block's state.
|
||||
#[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"], blocking)]
|
||||
fn storage_hash(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<Hash>>;
|
||||
fn storage_hash(&self, key: StorageKey, hash: Option<Hash>) -> Result<Option<Hash>, Error>;
|
||||
|
||||
/// Returns the size of a storage entry at a block's state.
|
||||
#[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])]
|
||||
async fn storage_size(&self, key: StorageKey, hash: Option<Hash>) -> RpcResult<Option<u64>>;
|
||||
async fn storage_size(&self, key: StorageKey, hash: Option<Hash>)
|
||||
-> Result<Option<u64>, Error>;
|
||||
|
||||
/// Returns the runtime metadata as an opaque blob.
|
||||
#[method(name = "state_getMetadata", blocking)]
|
||||
fn metadata(&self, hash: Option<Hash>) -> RpcResult<Bytes>;
|
||||
fn metadata(&self, hash: Option<Hash>) -> Result<Bytes, Error>;
|
||||
|
||||
/// Get the runtime version.
|
||||
#[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"], blocking)]
|
||||
fn runtime_version(&self, hash: Option<Hash>) -> RpcResult<RuntimeVersion>;
|
||||
fn runtime_version(&self, hash: Option<Hash>) -> Result<RuntimeVersion, Error>;
|
||||
|
||||
/// Query historical storage entries (by key) starting from a block given as the second
|
||||
/// parameter.
|
||||
@@ -95,7 +101,7 @@ pub trait StateApi<Hash> {
|
||||
keys: Vec<StorageKey>,
|
||||
block: Hash,
|
||||
hash: Option<Hash>,
|
||||
) -> RpcResult<Vec<StorageChangeSet<Hash>>>;
|
||||
) -> Result<Vec<StorageChangeSet<Hash>>, Error>;
|
||||
|
||||
/// Query storage entries (by key) at a block hash given as the second parameter.
|
||||
/// NOTE: Each StorageChangeSet in the result corresponds to exactly one element --
|
||||
@@ -105,11 +111,15 @@ pub trait StateApi<Hash> {
|
||||
&self,
|
||||
keys: Vec<StorageKey>,
|
||||
at: Option<Hash>,
|
||||
) -> RpcResult<Vec<StorageChangeSet<Hash>>>;
|
||||
) -> Result<Vec<StorageChangeSet<Hash>>, Error>;
|
||||
|
||||
/// Returns proof of storage entries at a specific block's state.
|
||||
#[method(name = "state_getReadProof", blocking)]
|
||||
fn read_proof(&self, keys: Vec<StorageKey>, hash: Option<Hash>) -> RpcResult<ReadProof<Hash>>;
|
||||
fn read_proof(
|
||||
&self,
|
||||
keys: Vec<StorageKey>,
|
||||
hash: Option<Hash>,
|
||||
) -> Result<ReadProof<Hash>, Error>;
|
||||
|
||||
/// New runtime version subscription
|
||||
#[subscription(
|
||||
@@ -288,5 +298,5 @@ pub trait StateApi<Hash> {
|
||||
targets: Option<String>,
|
||||
storage_keys: Option<String>,
|
||||
methods: Option<String>,
|
||||
) -> RpcResult<sp_rpc::tracing::TraceBlockResponse>;
|
||||
) -> Result<sp_rpc::tracing::TraceBlockResponse, Error>;
|
||||
}
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Statement RPC errors.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::{ErrorObject, ErrorObjectOwned};
|
||||
|
||||
/// Statement RPC Result type.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@@ -40,15 +37,14 @@ pub enum Error {
|
||||
/// Base error code for all statement errors.
|
||||
const BASE_ERROR: i32 = crate::error::base::STATEMENT;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> Self {
|
||||
match e {
|
||||
Error::StatementStore(message) => CallError::Custom(ErrorObject::owned(
|
||||
Error::StatementStore(message) => ErrorObject::owned(
|
||||
BASE_ERROR + 1,
|
||||
format!("Statement store error: {message}"),
|
||||
None::<()>,
|
||||
))
|
||||
.into(),
|
||||
),
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
//! System RPC module errors.
|
||||
|
||||
use crate::system::helpers::Health;
|
||||
use jsonrpsee::{
|
||||
core::Error as JsonRpseeError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
use jsonrpsee::types::{
|
||||
error::{ErrorCode, ErrorObject},
|
||||
ErrorObjectOwned,
|
||||
};
|
||||
|
||||
/// System RPC Result type.
|
||||
@@ -36,6 +36,12 @@ pub enum Error {
|
||||
/// Peer argument is malformatted.
|
||||
#[error("{0}")]
|
||||
MalformattedPeerArg(String),
|
||||
/// Call to an unsafe RPC was denied.
|
||||
#[error(transparent)]
|
||||
UnsafeRpcCalled(#[from] crate::policy::UnsafeRpcError),
|
||||
/// Internal error.
|
||||
#[error("{0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
// Base code for all system errors.
|
||||
@@ -45,17 +51,16 @@ const NOT_HEALTHY_ERROR: i32 = BASE_ERROR + 1;
|
||||
// Peer argument is malformatted.
|
||||
const MALFORMATTED_PEER_ARG_ERROR: i32 = BASE_ERROR + 2;
|
||||
|
||||
impl From<Error> for JsonRpseeError {
|
||||
fn from(e: Error) -> Self {
|
||||
impl From<Error> for ErrorObjectOwned {
|
||||
fn from(e: Error) -> ErrorObjectOwned {
|
||||
match e {
|
||||
Error::NotHealthy(ref h) =>
|
||||
CallError::Custom(ErrorObject::owned(NOT_HEALTHY_ERROR, e.to_string(), Some(h))),
|
||||
Error::MalformattedPeerArg(e) => CallError::Custom(ErrorObject::owned(
|
||||
MALFORMATTED_PEER_ARG_ERROR + 2,
|
||||
e,
|
||||
None::<()>,
|
||||
)),
|
||||
ErrorObject::owned(NOT_HEALTHY_ERROR, e.to_string(), Some(h)),
|
||||
Error::MalformattedPeerArg(e) =>
|
||||
ErrorObject::owned(MALFORMATTED_PEER_ARG_ERROR, e, None::<()>),
|
||||
Error::UnsafeRpcCalled(e) => e.into(),
|
||||
Error::Internal(e) =>
|
||||
ErrorObjectOwned::owned(ErrorCode::InternalError.code(), e, None::<()>),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct SystemInfo {
|
||||
}
|
||||
|
||||
/// Health struct returned by the RPC
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Health {
|
||||
/// Number of connected peers
|
||||
@@ -58,7 +58,7 @@ impl fmt::Display for Health {
|
||||
}
|
||||
|
||||
/// Network Peer information
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PeerInfo<Hash, Number> {
|
||||
/// Peer ID
|
||||
@@ -72,7 +72,7 @@ pub struct PeerInfo<Hash, Number> {
|
||||
}
|
||||
|
||||
/// The role the node is running as
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub enum NodeRole {
|
||||
/// The node is a full node
|
||||
Full,
|
||||
@@ -81,7 +81,7 @@ pub enum NodeRole {
|
||||
}
|
||||
|
||||
/// The state of the syncing of the node.
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct SyncState<Number> {
|
||||
/// Height of the block at which syncing started.
|
||||
|
||||
@@ -18,38 +18,36 @@
|
||||
|
||||
//! Substrate system API.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::{JsonValue, RpcResult},
|
||||
proc_macros::rpc,
|
||||
};
|
||||
|
||||
pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo};
|
||||
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
|
||||
use jsonrpsee::{core::JsonValue, proc_macros::rpc};
|
||||
|
||||
pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo};
|
||||
pub use error::Error;
|
||||
|
||||
/// Substrate system RPC API
|
||||
#[rpc(client, server)]
|
||||
pub trait SystemApi<Hash, Number> {
|
||||
/// Get the node's implementation name. Plain old string.
|
||||
#[method(name = "system_name")]
|
||||
fn system_name(&self) -> RpcResult<String>;
|
||||
fn system_name(&self) -> Result<String, Error>;
|
||||
|
||||
/// Get the node implementation's version. Should be a semver string.
|
||||
#[method(name = "system_version")]
|
||||
fn system_version(&self) -> RpcResult<String>;
|
||||
fn system_version(&self) -> Result<String, Error>;
|
||||
|
||||
/// Get the chain's name. Given as a string identifier.
|
||||
#[method(name = "system_chain")]
|
||||
fn system_chain(&self) -> RpcResult<String>;
|
||||
fn system_chain(&self) -> Result<String, Error>;
|
||||
|
||||
/// Get the chain's type.
|
||||
#[method(name = "system_chainType")]
|
||||
fn system_type(&self) -> RpcResult<sc_chain_spec::ChainType>;
|
||||
fn system_type(&self) -> Result<sc_chain_spec::ChainType, Error>;
|
||||
|
||||
/// Get a custom set of properties as a JSON object, defined in the chain spec.
|
||||
#[method(name = "system_properties")]
|
||||
fn system_properties(&self) -> RpcResult<sc_chain_spec::Properties>;
|
||||
fn system_properties(&self) -> Result<sc_chain_spec::Properties, Error>;
|
||||
|
||||
/// Return health status of the node.
|
||||
///
|
||||
@@ -57,22 +55,22 @@ pub trait SystemApi<Hash, Number> {
|
||||
/// - connected to some peers (unless running in dev mode)
|
||||
/// - not performing a major sync
|
||||
#[method(name = "system_health")]
|
||||
async fn system_health(&self) -> RpcResult<Health>;
|
||||
async fn system_health(&self) -> Result<Health, Error>;
|
||||
|
||||
/// Returns the base58-encoded PeerId of the node.
|
||||
#[method(name = "system_localPeerId")]
|
||||
async fn system_local_peer_id(&self) -> RpcResult<String>;
|
||||
async fn system_local_peer_id(&self) -> Result<String, Error>;
|
||||
|
||||
/// Returns the multi-addresses that the local node is listening on
|
||||
///
|
||||
/// The addresses include a trailing `/p2p/` with the local PeerId, and are thus suitable to
|
||||
/// be passed to `addReservedPeer` or as a bootnode address for example.
|
||||
#[method(name = "system_localListenAddresses")]
|
||||
async fn system_local_listen_addresses(&self) -> RpcResult<Vec<String>>;
|
||||
async fn system_local_listen_addresses(&self) -> Result<Vec<String>, Error>;
|
||||
|
||||
/// Returns currently connected peers
|
||||
#[method(name = "system_peers")]
|
||||
async fn system_peers(&self) -> RpcResult<Vec<PeerInfo<Hash, Number>>>;
|
||||
async fn system_peers(&self) -> Result<Vec<PeerInfo<Hash, Number>>, Error>;
|
||||
|
||||
/// Returns current state of the network.
|
||||
///
|
||||
@@ -81,7 +79,7 @@ pub trait SystemApi<Hash, Number> {
|
||||
// TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890
|
||||
// https://github.com/paritytech/substrate/issues/5541
|
||||
#[method(name = "system_unstable_networkState")]
|
||||
async fn system_network_state(&self) -> RpcResult<JsonValue>;
|
||||
async fn system_network_state(&self) -> Result<JsonValue, Error>;
|
||||
|
||||
/// Adds a reserved peer. Returns the empty string or an error. The string
|
||||
/// parameter should encode a `p2p` multiaddr.
|
||||
@@ -89,25 +87,25 @@ pub trait SystemApi<Hash, Number> {
|
||||
/// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`
|
||||
/// is an example of a valid, passing multiaddr with PeerId attached.
|
||||
#[method(name = "system_addReservedPeer")]
|
||||
async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()>;
|
||||
async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error>;
|
||||
|
||||
/// Remove a reserved peer. Returns the empty string or an error. The string
|
||||
/// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`.
|
||||
#[method(name = "system_removeReservedPeer")]
|
||||
async fn system_remove_reserved_peer(&self, peer_id: String) -> RpcResult<()>;
|
||||
async fn system_remove_reserved_peer(&self, peer_id: String) -> Result<(), Error>;
|
||||
|
||||
/// Returns the list of reserved peers
|
||||
#[method(name = "system_reservedPeers")]
|
||||
async fn system_reserved_peers(&self) -> RpcResult<Vec<String>>;
|
||||
async fn system_reserved_peers(&self) -> Result<Vec<String>, Error>;
|
||||
|
||||
/// Returns the roles the node is running as.
|
||||
#[method(name = "system_nodeRoles")]
|
||||
async fn system_node_roles(&self) -> RpcResult<Vec<NodeRole>>;
|
||||
async fn system_node_roles(&self) -> Result<Vec<NodeRole>, Error>;
|
||||
|
||||
/// Returns the state of the syncing of the node: starting block, current best block, highest
|
||||
/// known block.
|
||||
#[method(name = "system_syncState")]
|
||||
async fn system_sync_state(&self) -> RpcResult<SyncState<Number>>;
|
||||
async fn system_sync_state(&self) -> Result<SyncState<Number>, Error>;
|
||||
|
||||
/// Adds the supplied directives to the current log filter
|
||||
///
|
||||
@@ -115,9 +113,9 @@ pub trait SystemApi<Hash, Number> {
|
||||
///
|
||||
/// `sync=debug,state=trace`
|
||||
#[method(name = "system_addLogFilter")]
|
||||
fn system_add_log_filter(&self, directives: String) -> RpcResult<()>;
|
||||
fn system_add_log_filter(&self, directives: String) -> Result<(), Error>;
|
||||
|
||||
/// Resets the log filter to Substrate defaults
|
||||
#[method(name = "system_resetLogFilter")]
|
||||
fn system_reset_log_filter(&self) -> RpcResult<()>;
|
||||
fn system_reset_log_filter(&self) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@ workspace = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
log = "0.4.17"
|
||||
serde_json = "1.0.111"
|
||||
tokio = { version = "1.22.0", features = ["parking_lot"] }
|
||||
prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" }
|
||||
tower-http = { version = "0.4.0", features = ["cors"] }
|
||||
tower = "0.4.13"
|
||||
tower = { version = "0.4.13", features = ["util"] }
|
||||
http = "0.2.8"
|
||||
|
||||
@@ -22,15 +22,14 @@
|
||||
|
||||
pub mod middleware;
|
||||
|
||||
use std::{error::Error as StdError, net::SocketAddr, time::Duration};
|
||||
|
||||
use http::header::HeaderValue;
|
||||
use jsonrpsee::{
|
||||
server::{
|
||||
middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder,
|
||||
ServerHandle,
|
||||
},
|
||||
server::middleware::{HostFilterLayer, ProxyGetRequestLayer},
|
||||
RpcModule,
|
||||
};
|
||||
use std::{error::Error as StdError, net::SocketAddr};
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::cors::{AllowOrigin, CorsLayer};
|
||||
|
||||
pub use crate::middleware::RpcMetrics;
|
||||
@@ -42,7 +41,7 @@ pub use jsonrpsee::core::{
|
||||
const MEGABYTE: u32 = 1024 * 1024;
|
||||
|
||||
/// Type alias for the JSON-RPC server.
|
||||
pub type Server = ServerHandle;
|
||||
pub type Server = jsonrpsee::server::ServerHandle;
|
||||
|
||||
/// RPC server configuration.
|
||||
#[derive(Debug)]
|
||||
@@ -61,6 +60,8 @@ pub struct Config<'a, M: Send + Sync + 'static> {
|
||||
pub max_payload_out_mb: u32,
|
||||
/// Metrics.
|
||||
pub metrics: Option<RpcMetrics>,
|
||||
/// Message buffer size
|
||||
pub message_buffer_capacity: u32,
|
||||
/// RPC API.
|
||||
pub rpc_api: RpcModule<M>,
|
||||
/// Subscription ID provider.
|
||||
@@ -72,7 +73,7 @@ pub struct Config<'a, M: Send + Sync + 'static> {
|
||||
/// Start RPC server listening on given address.
|
||||
pub async fn start_server<M: Send + Sync + 'static>(
|
||||
config: Config<'_, M>,
|
||||
) -> Result<ServerHandle, Box<dyn StdError + Send + Sync>> {
|
||||
) -> Result<Server, Box<dyn StdError + Send + Sync>> {
|
||||
let Config {
|
||||
addrs,
|
||||
cors,
|
||||
@@ -81,26 +82,30 @@ pub async fn start_server<M: Send + Sync + 'static>(
|
||||
max_connections,
|
||||
max_subs_per_conn,
|
||||
metrics,
|
||||
message_buffer_capacity,
|
||||
id_provider,
|
||||
tokio_handle,
|
||||
rpc_api,
|
||||
} = config;
|
||||
|
||||
let host_filter = hosts_filtering(cors.is_some(), &addrs);
|
||||
let std_listener = TcpListener::bind(addrs.as_slice()).await?.into_std()?;
|
||||
let local_addr = std_listener.local_addr().ok();
|
||||
let host_filter = hosts_filtering(cors.is_some(), local_addr);
|
||||
|
||||
let middleware = tower::ServiceBuilder::new()
|
||||
.option_layer(host_filter)
|
||||
// Proxy `GET /health` requests to internal `system_health` method.
|
||||
.layer(ProxyGetRequestLayer::new("/health", "system_health")?)
|
||||
.layer(try_into_cors(cors)?);
|
||||
|
||||
let mut builder = ServerBuilder::new()
|
||||
let mut builder = jsonrpsee::server::Server::builder()
|
||||
.max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE))
|
||||
.max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE))
|
||||
.max_connections(max_connections)
|
||||
.max_subscriptions_per_connection(max_subs_per_conn)
|
||||
.ping_interval(std::time::Duration::from_secs(30))
|
||||
.set_host_filtering(host_filter)
|
||||
.ping_interval(Duration::from_secs(30))
|
||||
.set_middleware(middleware)
|
||||
.set_message_buffer_capacity(message_buffer_capacity)
|
||||
.custom_tokio_runtime(tokio_handle);
|
||||
|
||||
if let Some(provider) = id_provider {
|
||||
@@ -110,36 +115,34 @@ pub async fn start_server<M: Send + Sync + 'static>(
|
||||
};
|
||||
|
||||
let rpc_api = build_rpc_api(rpc_api);
|
||||
let (handle, addr) = if let Some(metrics) = metrics {
|
||||
let server = builder.set_logger(metrics).build(&addrs[..]).await?;
|
||||
let addr = server.local_addr();
|
||||
(server.start(rpc_api)?, addr)
|
||||
let handle = if let Some(metrics) = metrics {
|
||||
let server = builder.set_logger(metrics).build_from_tcp(std_listener)?;
|
||||
server.start(rpc_api)
|
||||
} else {
|
||||
let server = builder.build(&addrs[..]).await?;
|
||||
let addr = server.local_addr();
|
||||
(server.start(rpc_api)?, addr)
|
||||
let server = builder.build_from_tcp(std_listener)?;
|
||||
server.start(rpc_api)
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"Running JSON-RPC server: addr={}, allowed origins={}",
|
||||
addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
|
||||
local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()),
|
||||
format_cors(cors)
|
||||
);
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn hosts_filtering(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts {
|
||||
fn hosts_filtering(enabled: bool, addr: Option<SocketAddr>) -> Option<HostFilterLayer> {
|
||||
// If the local_addr failed, fallback to wildcard.
|
||||
let port = addr.map_or("*".to_string(), |p| p.port().to_string());
|
||||
|
||||
if enabled {
|
||||
// NOTE The listening addresses are whitelisted by default.
|
||||
let mut hosts = Vec::with_capacity(addrs.len() * 2);
|
||||
for addr in addrs {
|
||||
hosts.push(format!("localhost:{}", addr.port()).into());
|
||||
hosts.push(format!("127.0.0.1:{}", addr.port()).into());
|
||||
}
|
||||
AllowHosts::Only(hosts)
|
||||
// NOTE: The listening addresses are whitelisted by default.
|
||||
let hosts =
|
||||
[format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")];
|
||||
Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed"))
|
||||
} else {
|
||||
AllowHosts::Any
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,9 +154,9 @@ fn build_rpc_api<M: Send + Sync + 'static>(mut rpc_api: RpcModule<M>) -> RpcModu
|
||||
|
||||
rpc_api
|
||||
.register_method("rpc_methods", move |_, _| {
|
||||
Ok(serde_json::json!({
|
||||
serde_json::json!({
|
||||
"methods": available_methods,
|
||||
}))
|
||||
})
|
||||
})
|
||||
.expect("infallible all other methods have their own address space; qed");
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
//! RPC middleware to collect prometheus metrics on RPC calls.
|
||||
|
||||
use jsonrpsee::server::logger::{HttpRequest, Logger, MethodKind, Params, TransportProtocol};
|
||||
use jsonrpsee::server::logger::{
|
||||
HttpRequest, Logger, MethodKind, Params, SuccessOrError, TransportProtocol,
|
||||
};
|
||||
use prometheus_endpoint::{
|
||||
register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry,
|
||||
U64,
|
||||
@@ -176,7 +178,7 @@ impl Logger for RpcMetrics {
|
||||
fn on_result(
|
||||
&self,
|
||||
name: &str,
|
||||
success: bool,
|
||||
success_or_error: SuccessOrError,
|
||||
started_at: Self::Instant,
|
||||
transport: TransportProtocol,
|
||||
) {
|
||||
@@ -197,7 +199,7 @@ impl Logger for RpcMetrics {
|
||||
name,
|
||||
// the label "is_error", so `success` should be regarded as false
|
||||
// and vice-versa to be registrered correctly.
|
||||
if success { "false" } else { "true" },
|
||||
if success_or_error.is_success() { "false" } else { "true" },
|
||||
])
|
||||
.inc();
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ workspace = true
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
# Internal chain structures for "chain_spec".
|
||||
sc-chain-spec = { path = "../chain-spec" }
|
||||
# Pool for submitting extrinsics required by "transaction"
|
||||
@@ -29,6 +29,7 @@ sp-blockchain = { path = "../../primitives/blockchain" }
|
||||
sp-version = { path = "../../primitives/version" }
|
||||
sc-client-api = { path = "../api" }
|
||||
sc-utils = { path = "../utils" }
|
||||
sc-rpc = { path = "../rpc" }
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
thiserror = "1.0"
|
||||
serde = "1.0"
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Error helpers for `archive` RPC module.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::ErrorObject;
|
||||
|
||||
/// ChainHead RPC errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -58,9 +55,3 @@ impl From<Error> for ErrorObject<'static> {
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
CallError::Custom(e.into()).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,8 @@ use super::{archive::Archive, *};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::{Decode, Encode};
|
||||
use jsonrpsee::{
|
||||
core::error::Error,
|
||||
rpc_params,
|
||||
types::{error::CallError, EmptyServerParams as EmptyParams},
|
||||
RpcModule,
|
||||
core::{EmptyServerParams as EmptyParams, Error},
|
||||
rpc_params, RpcModule,
|
||||
};
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sc_client_api::ChildInfo;
|
||||
@@ -289,7 +287,7 @@ async fn archive_call() {
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err, Error::Call(CallError::Custom(ref err)) if err.code() == 3001 && err.message().contains("Invalid parameter"));
|
||||
assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter"));
|
||||
|
||||
// Pass an invalid parameters that cannot be decode.
|
||||
let err = api
|
||||
@@ -300,7 +298,7 @@ async fn archive_call() {
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err, Error::Call(CallError::Custom(ref err)) if err.code() == 3001 && err.message().contains("Invalid parameter"));
|
||||
assert_matches!(err, Error::Call(err) if err.code() == 3001 && err.message().contains("Invalid parameter"));
|
||||
|
||||
// Invalid hash.
|
||||
let result: MethodResult = api
|
||||
|
||||
@@ -20,10 +20,13 @@
|
||||
|
||||
//! API trait of the chain head.
|
||||
use crate::{
|
||||
chain_head::event::{FollowEvent, MethodResponse},
|
||||
chain_head::{
|
||||
error::Error,
|
||||
event::{FollowEvent, MethodResponse},
|
||||
},
|
||||
common::events::StorageQuery,
|
||||
};
|
||||
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
|
||||
use jsonrpsee::proc_macros::rpc;
|
||||
use sp_rpc::list::ListOrValue;
|
||||
|
||||
#[rpc(client, server)]
|
||||
@@ -56,7 +59,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash: Hash,
|
||||
) -> RpcResult<MethodResponse>;
|
||||
) -> Result<MethodResponse, Error>;
|
||||
|
||||
/// Retrieves the header of a pinned block.
|
||||
///
|
||||
@@ -75,7 +78,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash: Hash,
|
||||
) -> RpcResult<Option<String>>;
|
||||
) -> Result<Option<String>, Error>;
|
||||
|
||||
/// Returns storage entries at a specific block's state.
|
||||
///
|
||||
@@ -89,7 +92,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
hash: Hash,
|
||||
items: Vec<StorageQuery<String>>,
|
||||
child_trie: Option<String>,
|
||||
) -> RpcResult<MethodResponse>;
|
||||
) -> Result<MethodResponse, Error>;
|
||||
|
||||
/// Call into the Runtime API at a specified block's state.
|
||||
///
|
||||
@@ -103,7 +106,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
hash: Hash,
|
||||
function: String,
|
||||
call_parameters: String,
|
||||
) -> RpcResult<MethodResponse>;
|
||||
) -> Result<MethodResponse, Error>;
|
||||
|
||||
/// Unpin a block or multiple blocks reported by the `follow` method.
|
||||
///
|
||||
@@ -120,7 +123,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash_or_hashes: ListOrValue<Hash>,
|
||||
) -> RpcResult<()>;
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Resumes a storage fetch started with `chainHead_storage` after it has generated an
|
||||
/// `operationWaitingForContinue` event.
|
||||
@@ -133,7 +136,7 @@ pub trait ChainHeadApi<Hash> {
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
operation_id: String,
|
||||
) -> RpcResult<()>;
|
||||
) -> Result<(), Error>;
|
||||
|
||||
/// Stops an operation started with chainHead_unstable_body, chainHead_unstable_call, or
|
||||
/// chainHead_unstable_storage. If the operation was still in progress, this interrupts it. If
|
||||
@@ -147,5 +150,5 @@ pub trait ChainHeadApi<Hash> {
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
operation_id: String,
|
||||
) -> RpcResult<()>;
|
||||
) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
@@ -36,15 +36,14 @@ use crate::{
|
||||
use codec::Encode;
|
||||
use futures::future::FutureExt;
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, RpcResult},
|
||||
types::{SubscriptionEmptyError, SubscriptionId, SubscriptionResult},
|
||||
SubscriptionSink,
|
||||
core::async_trait, types::SubscriptionId, PendingSubscriptionSink, SubscriptionSink,
|
||||
};
|
||||
use log::debug;
|
||||
use sc_client_api::{
|
||||
Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey,
|
||||
StorageProvider,
|
||||
};
|
||||
use sc_rpc::utils::to_sub_message;
|
||||
use sp_api::CallApiAt;
|
||||
use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata};
|
||||
use sp_core::{traits::CallContext, Bytes};
|
||||
@@ -136,27 +135,13 @@ impl<BE: Backend<Block>, Block: BlockT, Client> ChainHead<BE, Block, Client> {
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Accept the subscription and return the subscription ID on success.
|
||||
fn accept_subscription(
|
||||
&self,
|
||||
sink: &mut SubscriptionSink,
|
||||
) -> Result<String, SubscriptionEmptyError> {
|
||||
// The subscription must be accepted before it can provide a valid subscription ID.
|
||||
sink.accept()?;
|
||||
|
||||
let Some(sub_id) = sink.subscription_id() else {
|
||||
// This can only happen if the subscription was not accepted.
|
||||
return Err(SubscriptionEmptyError)
|
||||
};
|
||||
|
||||
// Get the string representation for the subscription.
|
||||
let sub_id = match sub_id {
|
||||
SubscriptionId::Num(num) => num.to_string(),
|
||||
SubscriptionId::Str(id) => id.into_owned().into(),
|
||||
};
|
||||
|
||||
Ok(sub_id)
|
||||
/// Helper to convert the `subscription ID` to a string.
|
||||
pub fn read_subscription_id_as_string(sink: &SubscriptionSink) -> String {
|
||||
match sink.subscription_id() {
|
||||
SubscriptionId::Num(n) => n.to_string(),
|
||||
SubscriptionId::Str(s) => s.into_owned().into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,35 +175,28 @@ where
|
||||
+ StorageProvider<Block, BE>
|
||||
+ 'static,
|
||||
{
|
||||
fn chain_head_unstable_follow(
|
||||
&self,
|
||||
mut sink: SubscriptionSink,
|
||||
with_runtime: bool,
|
||||
) -> SubscriptionResult {
|
||||
let sub_id = match self.accept_subscription(&mut sink) {
|
||||
Ok(sub_id) => sub_id,
|
||||
Err(err) => {
|
||||
sink.close(ChainHeadRpcError::InternalError(
|
||||
"Cannot generate subscription ID".into(),
|
||||
));
|
||||
return Err(err)
|
||||
},
|
||||
};
|
||||
// Keep track of the subscription.
|
||||
let Some(sub_data) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime)
|
||||
else {
|
||||
// Inserting the subscription can only fail if the JsonRPSee
|
||||
// generated a duplicate subscription ID.
|
||||
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription already accepted", sub_id);
|
||||
let _ = sink.send(&FollowEvent::<Block::Hash>::Stop);
|
||||
return Ok(())
|
||||
};
|
||||
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription accepted", sub_id);
|
||||
|
||||
fn chain_head_unstable_follow(&self, pending: PendingSubscriptionSink, with_runtime: bool) {
|
||||
let subscriptions = self.subscriptions.clone();
|
||||
let backend = self.backend.clone();
|
||||
let client = self.client.clone();
|
||||
|
||||
let fut = async move {
|
||||
let Ok(sink) = pending.accept().await else { return };
|
||||
|
||||
let sub_id = read_subscription_id_as_string(&sink);
|
||||
|
||||
// Keep track of the subscription.
|
||||
let Some(sub_data) = subscriptions.insert_subscription(sub_id.clone(), with_runtime)
|
||||
else {
|
||||
// Inserting the subscription can only fail if the JsonRPSee
|
||||
// generated a duplicate subscription ID.
|
||||
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription already accepted", sub_id);
|
||||
let msg = to_sub_message(&sink, &FollowEvent::<String>::Stop);
|
||||
let _ = sink.send(msg).await;
|
||||
return
|
||||
};
|
||||
debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription accepted", sub_id);
|
||||
|
||||
let mut chain_head_follow = ChainHeadFollower::new(
|
||||
client,
|
||||
backend,
|
||||
@@ -234,14 +212,13 @@ where
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn chain_head_unstable_body(
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash: Block::Hash,
|
||||
) -> RpcResult<MethodResponse> {
|
||||
) -> Result<MethodResponse, ChainHeadRpcError> {
|
||||
let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) {
|
||||
Ok(block) => block,
|
||||
Err(SubscriptionManagementError::SubscriptionAbsent) |
|
||||
@@ -293,7 +270,7 @@ where
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash: Block::Hash,
|
||||
) -> RpcResult<Option<String>> {
|
||||
) -> Result<Option<String>, ChainHeadRpcError> {
|
||||
let _block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) {
|
||||
Ok(block) => block,
|
||||
Err(SubscriptionManagementError::SubscriptionAbsent) |
|
||||
@@ -309,7 +286,6 @@ where
|
||||
.header(hash)
|
||||
.map(|opt_header| opt_header.map(|h| hex_string(&h.encode())))
|
||||
.map_err(|err| ChainHeadRpcError::InternalError(err.to_string()))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn chain_head_unstable_storage(
|
||||
@@ -318,7 +294,7 @@ where
|
||||
hash: Block::Hash,
|
||||
items: Vec<StorageQuery<String>>,
|
||||
child_trie: Option<String>,
|
||||
) -> RpcResult<MethodResponse> {
|
||||
) -> Result<MethodResponse, ChainHeadRpcError> {
|
||||
// Gain control over parameter parsing and returned error.
|
||||
let items = items
|
||||
.into_iter()
|
||||
@@ -376,7 +352,7 @@ where
|
||||
hash: Block::Hash,
|
||||
function: String,
|
||||
call_parameters: String,
|
||||
) -> RpcResult<MethodResponse> {
|
||||
) -> Result<MethodResponse, ChainHeadRpcError> {
|
||||
let call_parameters = Bytes::from(parse_hex_param(call_parameters)?);
|
||||
|
||||
let mut block_guard = match self.subscriptions.lock_block(&follow_subscription, hash, 1) {
|
||||
@@ -420,14 +396,17 @@ where
|
||||
});
|
||||
|
||||
let _ = block_guard.response_sender().unbounded_send(event);
|
||||
Ok(MethodResponse::Started(MethodResponseStarted { operation_id, discarded_items: None }))
|
||||
Ok(MethodResponse::Started(MethodResponseStarted {
|
||||
operation_id: operation_id.clone(),
|
||||
discarded_items: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn chain_head_unstable_unpin(
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
hash_or_hashes: ListOrValue<Block::Hash>,
|
||||
) -> RpcResult<()> {
|
||||
) -> Result<(), ChainHeadRpcError> {
|
||||
let result = match hash_or_hashes {
|
||||
ListOrValue::Value(hash) =>
|
||||
self.subscriptions.unpin_blocks(&follow_subscription, [hash]),
|
||||
@@ -443,9 +422,9 @@ where
|
||||
},
|
||||
Err(SubscriptionManagementError::BlockHashAbsent) => {
|
||||
// Block is not part of the subscription.
|
||||
Err(ChainHeadRpcError::InvalidBlock.into())
|
||||
Err(ChainHeadRpcError::InvalidBlock)
|
||||
},
|
||||
Err(_) => Err(ChainHeadRpcError::InvalidBlock.into()),
|
||||
Err(_) => Err(ChainHeadRpcError::InvalidBlock),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -453,7 +432,7 @@ where
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
operation_id: String,
|
||||
) -> RpcResult<()> {
|
||||
) -> Result<(), ChainHeadRpcError> {
|
||||
let Some(operation) = self.subscriptions.get_operation(&follow_subscription, &operation_id)
|
||||
else {
|
||||
return Ok(())
|
||||
@@ -471,7 +450,7 @@ where
|
||||
&self,
|
||||
follow_subscription: String,
|
||||
operation_id: String,
|
||||
) -> RpcResult<()> {
|
||||
) -> Result<(), ChainHeadRpcError> {
|
||||
let Some(operation) = self.subscriptions.get_operation(&follow_subscription, &operation_id)
|
||||
else {
|
||||
return Ok(())
|
||||
|
||||
@@ -24,7 +24,7 @@ use crate::chain_head::{
|
||||
BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, RuntimeEvent,
|
||||
RuntimeVersionEvent,
|
||||
},
|
||||
subscription::{InsertedSubscriptionData, SubscriptionManagement, SubscriptionManagementError},
|
||||
subscription::{SubscriptionManagement, SubscriptionManagementError},
|
||||
};
|
||||
use futures::{
|
||||
channel::oneshot,
|
||||
@@ -36,6 +36,7 @@ use log::{debug, error};
|
||||
use sc_client_api::{
|
||||
Backend, BlockBackend, BlockImportNotification, BlockchainEvents, FinalityNotification,
|
||||
};
|
||||
use sc_rpc::utils::to_sub_message;
|
||||
use sp_api::CallApiAt;
|
||||
use sp_blockchain::{
|
||||
Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, Info,
|
||||
@@ -43,6 +44,8 @@ use sp_blockchain::{
|
||||
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use super::subscription::InsertedSubscriptionData;
|
||||
|
||||
/// Generates the events of the `chainHead_follow` method.
|
||||
pub struct ChainHeadFollower<BE: Backend<Block>, Block: BlockT, Client> {
|
||||
/// Substrate client.
|
||||
@@ -500,7 +503,7 @@ where
|
||||
startup_point: &StartupPoint<Block>,
|
||||
mut stream: EventStream,
|
||||
mut to_ignore: HashSet<Block::Hash>,
|
||||
mut sink: SubscriptionSink,
|
||||
sink: SubscriptionSink,
|
||||
rx_stop: oneshot::Receiver<()>,
|
||||
) where
|
||||
EventStream: Stream<Item = NotificationType<Block>> + Unpin,
|
||||
@@ -529,35 +532,23 @@ where
|
||||
self.sub_id,
|
||||
err
|
||||
);
|
||||
let _ = sink.send(&FollowEvent::<String>::Stop);
|
||||
let msg = to_sub_message(&sink, &FollowEvent::<String>::Stop);
|
||||
let _ = sink.send(msg).await;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
for event in events {
|
||||
let result = sink.send(&event);
|
||||
|
||||
// Migration note: the new version of jsonrpsee returns Result<(), DisconnectError>
|
||||
// The logic from `Err(err)` should be moved when building the new
|
||||
// `SubscriptionMessage`.
|
||||
|
||||
// For now, jsonrpsee returns:
|
||||
// Ok(true): message sent
|
||||
// Ok(false): client disconnected or subscription closed
|
||||
// Err(err): serder serialization error of the event
|
||||
if let Err(err) = result {
|
||||
let msg = to_sub_message(&sink, &event);
|
||||
if let Err(err) = sink.send(msg).await {
|
||||
// Failed to submit event.
|
||||
debug!(
|
||||
target: LOG_TARGET,
|
||||
"[follow][id={:?}] Failed to send event {:?}", self.sub_id, err
|
||||
);
|
||||
|
||||
let _ = sink.send(&FollowEvent::<String>::Stop);
|
||||
return
|
||||
}
|
||||
|
||||
if let Ok(false) = result {
|
||||
// Client disconnected or subscription was closed.
|
||||
let msg = to_sub_message(&sink, &FollowEvent::<String>::Stop);
|
||||
let _ = sink.send(msg).await;
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -568,13 +559,14 @@ where
|
||||
|
||||
// If we got here either the substrate streams have closed
|
||||
// or the `Stop` receiver was triggered.
|
||||
let _ = sink.send(&FollowEvent::<String>::Stop);
|
||||
let msg = to_sub_message(&sink, &FollowEvent::<String>::Stop);
|
||||
let _ = sink.send(msg).await;
|
||||
}
|
||||
|
||||
/// Generate the block events for the `chainHead_follow` method.
|
||||
pub async fn generate_events(
|
||||
&mut self,
|
||||
mut sink: SubscriptionSink,
|
||||
sink: SubscriptionSink,
|
||||
sub_data: InsertedSubscriptionData<Block>,
|
||||
) {
|
||||
// Register for the new block and finalized notifications.
|
||||
@@ -602,7 +594,8 @@ where
|
||||
self.sub_id,
|
||||
err
|
||||
);
|
||||
let _ = sink.send(&FollowEvent::<Block::Hash>::Stop);
|
||||
let msg = to_sub_message(&sink, &FollowEvent::<String>::Stop);
|
||||
let _ = sink.send(msg).await;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
@@ -18,10 +18,7 @@
|
||||
|
||||
//! Error helpers for `chainHead` RPC module.
|
||||
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::error::{CallError, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::types::error::ErrorObject;
|
||||
|
||||
/// ChainHead RPC errors.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
@@ -81,9 +78,3 @@ impl From<Error> for ErrorObject<'static> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
CallError::Custom(e.into()).into()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,8 @@ use assert_matches::assert_matches;
|
||||
use codec::{Decode, Encode};
|
||||
use futures::Future;
|
||||
use jsonrpsee::{
|
||||
core::{error::Error, server::rpc_module::Subscription as RpcSubscription},
|
||||
rpc_params,
|
||||
types::error::CallError,
|
||||
RpcModule,
|
||||
core::{error::Error, server::Subscription as RpcSubscription},
|
||||
rpc_params, RpcModule,
|
||||
};
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sc_client_api::ChildInfo;
|
||||
@@ -120,7 +118,7 @@ async fn setup_api() -> (
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -171,7 +169,7 @@ async fn follow_subscription_produces_blocks() {
|
||||
.into_rpc();
|
||||
|
||||
let finalized_hash = client.info().finalized_hash;
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -239,7 +237,7 @@ async fn follow_with_runtime() {
|
||||
.into_rpc();
|
||||
|
||||
let finalized_hash = client.info().finalized_hash;
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -361,7 +359,7 @@ async fn get_header() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Obtain the valid header.
|
||||
@@ -390,7 +388,7 @@ async fn get_body() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Valid call.
|
||||
@@ -475,7 +473,7 @@ async fn call_runtime() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Pass an invalid parameters that cannot be decode.
|
||||
@@ -488,7 +486,7 @@ async fn call_runtime() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter")
|
||||
Error::Call(err) if err.code() == super::error::json_rpc_spec::INVALID_PARAM_ERROR && err.message().contains("Invalid parameter")
|
||||
);
|
||||
|
||||
// Valid call.
|
||||
@@ -550,7 +548,7 @@ async fn call_runtime_without_flag() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -591,7 +589,7 @@ async fn call_runtime_without_flag() {
|
||||
.unwrap_err();
|
||||
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`")
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_RUNTIME_CALL && err.message().contains("subscription was started with `withRuntime` set to `false`")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -629,7 +627,7 @@ async fn get_storage_hash() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Valid call without storage at the key.
|
||||
@@ -897,7 +895,7 @@ async fn get_storage_value() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Valid call without storage at the key.
|
||||
@@ -1209,11 +1207,12 @@ async fn separate_operation_ids_for_subscriptions() {
|
||||
.into_rpc();
|
||||
|
||||
// Create two separate subscriptions.
|
||||
let mut sub_first = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub_first = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id_first = sub_first.subscription_id();
|
||||
let sub_id_first = serde_json::to_string(&sub_id_first).unwrap();
|
||||
|
||||
let mut sub_second = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub_second =
|
||||
api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id_second = sub_second.subscription_id();
|
||||
let sub_id_second = serde_json::to_string(&sub_id_second).unwrap();
|
||||
|
||||
@@ -1341,7 +1340,7 @@ async fn follow_generates_initial_blocks() {
|
||||
let block_2_f_hash = block_2_f.header.hash();
|
||||
client.import(BlockOrigin::Own, block_2_f.clone()).await.unwrap();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -1450,7 +1449,7 @@ async fn follow_exceeding_pinned_blocks() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
let block = BlockBuilderBuilder::new(&*client)
|
||||
.on_parent_block(client.chain_info().genesis_hash)
|
||||
@@ -1526,7 +1525,7 @@ async fn follow_with_unpin() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -1572,7 +1571,7 @@ async fn follow_with_unpin() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// To not exceed the number of pinned blocks, we need to unpin before the next import.
|
||||
@@ -1637,7 +1636,7 @@ async fn follow_with_multiple_unpin_hashes() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -1721,7 +1720,7 @@ async fn follow_with_multiple_unpin_hashes() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
let _res: () = api
|
||||
@@ -1738,7 +1737,7 @@ async fn follow_with_multiple_unpin_hashes() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
// Unpin multiple blocks.
|
||||
@@ -1756,7 +1755,7 @@ async fn follow_with_multiple_unpin_hashes() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
|
||||
let err = api
|
||||
@@ -1767,7 +1766,7 @@ async fn follow_with_multiple_unpin_hashes() {
|
||||
.await
|
||||
.unwrap_err();
|
||||
assert_matches!(err,
|
||||
Error::Call(CallError::Custom(ref err)) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
Error::Call(err) if err.code() == super::error::rpc_spec_v2::INVALID_BLOCK_ERROR && err.message() == "Invalid block hash"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1791,7 +1790,7 @@ async fn follow_prune_best_block() {
|
||||
.into_rpc();
|
||||
|
||||
let finalized_hash = client.info().finalized_hash;
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -2051,7 +2050,7 @@ async fn follow_forks_pruned_block() {
|
||||
// Block 2_f and 3_f are not pruned, pruning happens at height (N - 1).
|
||||
client.finalize_block(block_3_hash, None).unwrap();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -2205,7 +2204,7 @@ async fn follow_report_multiple_pruned_block() {
|
||||
let block_3_f = block_builder.build().unwrap().block;
|
||||
let block_3_f_hash = block_3_f.hash();
|
||||
client.import(BlockOrigin::Own, block_3_f.clone()).await.unwrap();
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Initialized must always be reported first.
|
||||
let event: FollowEvent<String> = get_next_event(&mut sub).await;
|
||||
@@ -2388,7 +2387,7 @@ async fn pin_block_references() {
|
||||
}
|
||||
}
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -2520,7 +2519,7 @@ async fn follow_finalized_before_new_block() {
|
||||
let block_1_hash = block_1.header.hash();
|
||||
client.import(BlockOrigin::Own, block_1.clone()).await.unwrap();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [false]).await.unwrap();
|
||||
|
||||
// Trigger the `FinalizedNotification` for block 1 before the `BlockImportNotification`, and
|
||||
// expect for the `chainHead` to generate `NewBlock`, `BestBlock` and `Finalized` events.
|
||||
@@ -2622,7 +2621,7 @@ async fn ensure_operation_limits_works() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -2726,7 +2725,7 @@ async fn check_continue_operation() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
@@ -2908,7 +2907,7 @@ async fn stop_storage_operation() {
|
||||
)
|
||||
.into_rpc();
|
||||
|
||||
let mut sub = api.subscribe("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("chainHead_unstable_follow", [true]).await.unwrap();
|
||||
let sub_id = sub.subscription_id();
|
||||
let sub_id = serde_json::to_string(&sub_id).unwrap();
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use super::*;
|
||||
use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule};
|
||||
use jsonrpsee::{core::EmptyServerParams as EmptyParams, RpcModule};
|
||||
use sc_chain_spec::Properties;
|
||||
|
||||
const CHAIN_NAME: &'static str = "TEST_CHAIN_NAME";
|
||||
|
||||
@@ -37,7 +37,7 @@ pub mod transaction;
|
||||
pub type SubscriptionTaskExecutor = std::sync::Arc<dyn sp_core::traits::SpawnNamed>;
|
||||
|
||||
/// The result of an RPC method.
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum MethodResult {
|
||||
/// Method generated a result.
|
||||
|
||||
@@ -29,27 +29,21 @@ use crate::{
|
||||
},
|
||||
SubscriptionTaskExecutor,
|
||||
};
|
||||
use jsonrpsee::{
|
||||
core::async_trait,
|
||||
types::{
|
||||
error::{CallError, ErrorObject},
|
||||
SubscriptionResult,
|
||||
},
|
||||
SubscriptionSink,
|
||||
};
|
||||
use jsonrpsee::{core::async_trait, types::error::ErrorObject, PendingSubscriptionSink};
|
||||
use sc_transaction_pool_api::{
|
||||
error::IntoPoolError, BlockHash, TransactionFor, TransactionPool, TransactionSource,
|
||||
TransactionStatus,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use sc_rpc::utils::pipe_from_stream;
|
||||
use sp_api::ProvideRuntimeApi;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_core::Bytes;
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
|
||||
use codec::Decode;
|
||||
use futures::{FutureExt, StreamExt, TryFutureExt};
|
||||
use futures::{StreamExt, TryFutureExt};
|
||||
|
||||
/// An API for transaction RPC calls.
|
||||
pub struct Transaction<Pool, Client> {
|
||||
@@ -90,52 +84,53 @@ where
|
||||
<Pool::Block as BlockT>::Hash: Unpin,
|
||||
Client: HeaderBackend<Pool::Block> + ProvideRuntimeApi<Pool::Block> + Send + Sync + 'static,
|
||||
{
|
||||
fn submit_and_watch(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult {
|
||||
// This is the only place where the RPC server can return an error for this
|
||||
// subscription. Other defects must be signaled as events to the sink.
|
||||
let decoded_extrinsic = match TransactionFor::<Pool>::decode(&mut &xt[..]) {
|
||||
Ok(decoded_extrinsic) => decoded_extrinsic,
|
||||
Err(e) => {
|
||||
let err = CallError::Custom(ErrorObject::owned(
|
||||
BAD_FORMAT,
|
||||
format!("Extrinsic has invalid format: {}", e),
|
||||
None::<()>,
|
||||
));
|
||||
let _ = sink.reject(err);
|
||||
return Ok(())
|
||||
},
|
||||
};
|
||||
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
|
||||
let submit = self
|
||||
.pool
|
||||
.submit_and_watch(best_block_hash, TX_SOURCE, decoded_extrinsic)
|
||||
.map_err(|e| {
|
||||
e.into_pool_error()
|
||||
.map(Error::from)
|
||||
.unwrap_or_else(|e| Error::Verification(Box::new(e)))
|
||||
});
|
||||
fn submit_and_watch(&self, pending: PendingSubscriptionSink, xt: Bytes) {
|
||||
let client = self.client.clone();
|
||||
let pool = self.pool.clone();
|
||||
|
||||
let fut = async move {
|
||||
// This is the only place where the RPC server can return an error for this
|
||||
// subscription. Other defects must be signaled as events to the sink.
|
||||
let decoded_extrinsic = match TransactionFor::<Pool>::decode(&mut &xt[..]) {
|
||||
Ok(decoded_extrinsic) => decoded_extrinsic,
|
||||
Err(e) => {
|
||||
let err = ErrorObject::owned(
|
||||
BAD_FORMAT,
|
||||
format!("Extrinsic has invalid format: {}", e),
|
||||
None::<()>,
|
||||
);
|
||||
let _ = pending.reject(err).await;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let best_block_hash = client.info().best_hash;
|
||||
|
||||
let submit = pool
|
||||
.submit_and_watch(best_block_hash, TX_SOURCE, decoded_extrinsic)
|
||||
.map_err(|e| {
|
||||
e.into_pool_error()
|
||||
.map(Error::from)
|
||||
.unwrap_or_else(|e| Error::Verification(Box::new(e)))
|
||||
});
|
||||
|
||||
match submit.await {
|
||||
Ok(stream) => {
|
||||
let mut state = TransactionState::new();
|
||||
let stream =
|
||||
stream.filter_map(|event| async move { state.handle_event(event) });
|
||||
sink.pipe_from_stream(stream.boxed()).await;
|
||||
stream.filter_map(move |event| async move { state.handle_event(event) });
|
||||
pipe_from_stream(pending, stream.boxed()).await;
|
||||
},
|
||||
Err(err) => {
|
||||
// We have not created an `Watcher` for the tx. Make sure the
|
||||
// error is still propagated as an event.
|
||||
let event: TransactionEvent<<Pool::Block as BlockT>::Hash> = err.into();
|
||||
sink.pipe_from_stream(futures::stream::once(async { event }).boxed()).await;
|
||||
pipe_from_stream(pending, futures::stream::once(async { event }).boxed()).await;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
sc_rpc::utils::spawn_subscription_task(&self.executor, fut);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
futures = "0.3.21"
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
log = "0.4.17"
|
||||
parking_lot = "0.12.1"
|
||||
serde_json = "1.0.111"
|
||||
@@ -40,7 +40,6 @@ sp-runtime = { path = "../../primitives/runtime" }
|
||||
sp-session = { path = "../../primitives/session" }
|
||||
sp-version = { path = "../../primitives/version" }
|
||||
sp-statement-store = { path = "../../primitives/statement-store" }
|
||||
|
||||
tokio = "1.22.0"
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -56,6 +55,7 @@ tokio = "1.22.0"
|
||||
sp-io = { path = "../../primitives/io" }
|
||||
substrate-test-runtime-client = { path = "../../test-utils/runtime/client" }
|
||||
pretty_assertions = "1.2.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
|
||||
[features]
|
||||
test-helpers = []
|
||||
|
||||
@@ -23,15 +23,14 @@ mod tests;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::SubscriptionTaskExecutor;
|
||||
use crate::{
|
||||
utils::{pipe_from_stream, spawn_subscription_task},
|
||||
SubscriptionTaskExecutor,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{FutureExt, TryFutureExt};
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError, RpcResult},
|
||||
types::SubscriptionResult,
|
||||
SubscriptionSink,
|
||||
};
|
||||
use futures::TryFutureExt;
|
||||
use jsonrpsee::{core::async_trait, types::ErrorObject, PendingSubscriptionSink};
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sc_transaction_pool_api::{
|
||||
error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool,
|
||||
@@ -91,7 +90,7 @@ where
|
||||
P::Hash: Unpin,
|
||||
<P::Block as BlockT>::Hash: Unpin,
|
||||
{
|
||||
async fn submit_extrinsic(&self, ext: Bytes) -> RpcResult<TxHash<P>> {
|
||||
async fn submit_extrinsic(&self, ext: Bytes) -> Result<TxHash<P>> {
|
||||
let xt = match Decode::decode(&mut &ext[..]) {
|
||||
Ok(xt) => xt,
|
||||
Err(err) => return Err(Error::Client(Box::new(err)).into()),
|
||||
@@ -105,7 +104,7 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()> {
|
||||
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
|
||||
@@ -115,7 +114,7 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn rotate_keys(&self) -> RpcResult<Bytes> {
|
||||
fn rotate_keys(&self) -> Result<Bytes> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
@@ -129,7 +128,7 @@ where
|
||||
.map_err(|api_err| Error::Client(Box::new(api_err)).into())
|
||||
}
|
||||
|
||||
fn has_session_keys(&self, session_keys: Bytes) -> RpcResult<bool> {
|
||||
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
@@ -143,21 +142,21 @@ where
|
||||
Ok(self.keystore.has_keys(&keys))
|
||||
}
|
||||
|
||||
fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult<bool> {
|
||||
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
|
||||
Ok(self.keystore.has_keys(&[(public_key.to_vec(), key_type)]))
|
||||
}
|
||||
|
||||
fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>> {
|
||||
fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
|
||||
Ok(self.pool.ready().map(|tx| tx.data().encode().into()).collect())
|
||||
}
|
||||
|
||||
fn remove_extrinsic(
|
||||
&self,
|
||||
bytes_or_hash: Vec<hash::ExtrinsicOrHash<TxHash<P>>>,
|
||||
) -> RpcResult<Vec<TxHash<P>>> {
|
||||
) -> Result<Vec<TxHash<P>>> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
let hashes = bytes_or_hash
|
||||
.into_iter()
|
||||
@@ -178,13 +177,13 @@ where
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn watch_extrinsic(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult {
|
||||
fn watch_extrinsic(&self, pending: PendingSubscriptionSink, xt: Bytes) {
|
||||
let best_block_hash = self.client.info().best_hash;
|
||||
let dxt = match TransactionFor::<P>::decode(&mut &xt[..]).map_err(|e| Error::from(e)) {
|
||||
Ok(dxt) => dxt,
|
||||
Err(e) => {
|
||||
let _ = sink.reject(JsonRpseeError::from(e));
|
||||
return Ok(())
|
||||
spawn_subscription_task(&self.executor, pending.reject(e));
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
@@ -198,15 +197,14 @@ where
|
||||
let stream = match submit.await {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => {
|
||||
let _ = sink.reject(JsonRpseeError::from(err));
|
||||
let _ = pending.reject(ErrorObject::from(err)).await;
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
sink.pipe_from_stream(stream).await;
|
||||
pipe_from_stream(pending, stream).await;
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
Ok(())
|
||||
spawn_subscription_task(&self.executor, fut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ use crate::testing::{test_executor, timeout_secs};
|
||||
use assert_matches::assert_matches;
|
||||
use codec::Encode;
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::{error::CallError, EmptyServerParams as EmptyParams},
|
||||
core::{EmptyServerParams as EmptyParams, Error as RpcError},
|
||||
RpcModule,
|
||||
};
|
||||
use sc_transaction_pool::{BasicPool, FullChainApi};
|
||||
@@ -104,7 +103,7 @@ async fn author_submit_transaction_should_not_cause_error() {
|
||||
|
||||
assert_matches!(
|
||||
api.call::<_, H256>("author_submitExtrinsic", [xt]).await,
|
||||
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Already Imported") && err.code() == 1013
|
||||
Err(RpcError::Call(err)) if err.message().contains("Already Imported") && err.code() == 1013
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,7 +118,7 @@ async fn author_should_watch_extrinsic() {
|
||||
true,
|
||||
);
|
||||
|
||||
let mut sub = api.subscribe("author_submitAndWatchExtrinsic", [xt]).await.unwrap();
|
||||
let mut sub = api.subscribe_unbounded("author_submitAndWatchExtrinsic", [xt]).await.unwrap();
|
||||
let (tx, sub_id) = timeout_secs(10, sub.next::<TransactionStatus<H256, Block>>())
|
||||
.await
|
||||
.unwrap()
|
||||
@@ -157,11 +156,11 @@ async fn author_should_return_watch_validation_error() {
|
||||
let invalid_xt = ExtrinsicBuilder::new_fill_block(Perbill::from_percent(100)).build();
|
||||
|
||||
let api = TestSetup::into_rpc();
|
||||
let failed_sub = api.subscribe(METHOD, [to_hex(&invalid_xt.encode(), true)]).await;
|
||||
let failed_sub = api.subscribe_unbounded(METHOD, [to_hex(&invalid_xt.encode(), true)]).await;
|
||||
|
||||
assert_matches!(
|
||||
failed_sub,
|
||||
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Invalid Transaction") && err.code() == 1010
|
||||
Err(RpcError::Call(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010
|
||||
);
|
||||
}
|
||||
|
||||
@@ -277,7 +276,7 @@ async fn author_has_session_keys() {
|
||||
|
||||
assert_matches!(
|
||||
api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await,
|
||||
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Session keys are not encoded correctly")
|
||||
Err(RpcError::Call(err)) if err.message().contains("Session keys are not encoded correctly")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,14 +19,17 @@
|
||||
//! Blockchain API backend for full nodes.
|
||||
|
||||
use super::{client_err, ChainBackend, Error};
|
||||
use crate::SubscriptionTaskExecutor;
|
||||
use crate::{
|
||||
utils::{pipe_from_stream, spawn_subscription_task},
|
||||
SubscriptionTaskExecutor,
|
||||
};
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
use futures::{
|
||||
future::{self, FutureExt},
|
||||
future::{self},
|
||||
stream::{self, Stream, StreamExt},
|
||||
};
|
||||
use jsonrpsee::SubscriptionSink;
|
||||
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
|
||||
use sc_client_api::{BlockBackend, BlockchainEvents};
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_runtime::{generic::SignedBlock, traits::Block as BlockT};
|
||||
@@ -48,6 +51,7 @@ impl<Block: BlockT, Client> FullChain<Block, Client> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Block, Client> ChainBackend<Client, Block> for FullChain<Block, Client>
|
||||
where
|
||||
Block: BlockT + 'static,
|
||||
@@ -66,11 +70,11 @@ where
|
||||
self.client.block(self.unwrap_or_best(hash)).map_err(client_err)
|
||||
}
|
||||
|
||||
fn subscribe_all_heads(&self, sink: SubscriptionSink) {
|
||||
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink) {
|
||||
subscribe_headers(
|
||||
&self.client,
|
||||
&self.executor,
|
||||
sink,
|
||||
pending,
|
||||
|| self.client().info().best_hash,
|
||||
|| {
|
||||
self.client()
|
||||
@@ -80,11 +84,11 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
fn subscribe_new_heads(&self, sink: SubscriptionSink) {
|
||||
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink) {
|
||||
subscribe_headers(
|
||||
&self.client,
|
||||
&self.executor,
|
||||
sink,
|
||||
pending,
|
||||
|| self.client().info().best_hash,
|
||||
|| {
|
||||
self.client()
|
||||
@@ -95,11 +99,11 @@ where
|
||||
)
|
||||
}
|
||||
|
||||
fn subscribe_finalized_heads(&self, sink: SubscriptionSink) {
|
||||
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink) {
|
||||
subscribe_headers(
|
||||
&self.client,
|
||||
&self.executor,
|
||||
sink,
|
||||
pending,
|
||||
|| self.client().info().finalized_hash,
|
||||
|| {
|
||||
self.client()
|
||||
@@ -114,7 +118,7 @@ where
|
||||
fn subscribe_headers<Block, Client, F, G, S>(
|
||||
client: &Arc<Client>,
|
||||
executor: &SubscriptionTaskExecutor,
|
||||
mut sink: SubscriptionSink,
|
||||
pending: PendingSubscriptionSink,
|
||||
best_block_hash: G,
|
||||
stream: F,
|
||||
) where
|
||||
@@ -139,9 +143,5 @@ fn subscribe_headers<Block, Client, F, G, S>(
|
||||
// duplicates at the beginning of the stream though.
|
||||
let stream = stream::iter(maybe_header).chain(stream());
|
||||
|
||||
let fut = async move {
|
||||
sink.pipe_from_stream(stream).await;
|
||||
};
|
||||
|
||||
executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
spawn_subscription_task(executor, pipe_from_stream(pending, stream));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::SubscriptionTaskExecutor;
|
||||
|
||||
use jsonrpsee::{core::RpcResult, types::SubscriptionResult, SubscriptionSink};
|
||||
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
|
||||
use sc_client_api::BlockchainEvents;
|
||||
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
|
||||
use sp_runtime::{
|
||||
@@ -42,6 +42,7 @@ pub use sc_rpc_api::chain::*;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
|
||||
/// Blockchain backend API
|
||||
#[async_trait]
|
||||
trait ChainBackend<Client, Block: BlockT>: Send + Sync + 'static
|
||||
where
|
||||
Block: BlockT + 'static,
|
||||
@@ -91,13 +92,13 @@ where
|
||||
}
|
||||
|
||||
/// All new head subscription
|
||||
fn subscribe_all_heads(&self, sink: SubscriptionSink);
|
||||
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink);
|
||||
|
||||
/// New best head subscription
|
||||
fn subscribe_new_heads(&self, sink: SubscriptionSink);
|
||||
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink);
|
||||
|
||||
/// Finalized head subscription
|
||||
fn subscribe_finalized_heads(&self, sink: SubscriptionSink);
|
||||
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink);
|
||||
}
|
||||
|
||||
/// Create new state API that works on full node.
|
||||
@@ -118,6 +119,7 @@ pub struct Chain<Block: BlockT, Client> {
|
||||
backend: Box<dyn ChainBackend<Client, Block>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Block, Client> ChainApiServer<NumberFor<Block>, Block::Hash, Block::Header, SignedBlock<Block>>
|
||||
for Chain<Block, Client>
|
||||
where
|
||||
@@ -125,20 +127,20 @@ where
|
||||
Block::Header: Unpin,
|
||||
Client: HeaderBackend<Block> + BlockchainEvents<Block> + 'static,
|
||||
{
|
||||
fn header(&self, hash: Option<Block::Hash>) -> RpcResult<Option<Block::Header>> {
|
||||
self.backend.header(hash).map_err(Into::into)
|
||||
fn header(&self, hash: Option<Block::Hash>) -> Result<Option<Block::Header>, Error> {
|
||||
self.backend.header(hash)
|
||||
}
|
||||
|
||||
fn block(&self, hash: Option<Block::Hash>) -> RpcResult<Option<SignedBlock<Block>>> {
|
||||
self.backend.block(hash).map_err(Into::into)
|
||||
fn block(&self, hash: Option<Block::Hash>) -> Result<Option<SignedBlock<Block>>, Error> {
|
||||
self.backend.block(hash)
|
||||
}
|
||||
|
||||
fn block_hash(
|
||||
&self,
|
||||
number: Option<ListOrValue<NumberOrHex>>,
|
||||
) -> RpcResult<ListOrValue<Option<Block::Hash>>> {
|
||||
) -> Result<ListOrValue<Option<Block::Hash>>, Error> {
|
||||
match number {
|
||||
None => self.backend.block_hash(None).map(ListOrValue::Value).map_err(Into::into),
|
||||
None => self.backend.block_hash(None).map(ListOrValue::Value),
|
||||
Some(ListOrValue::Value(number)) => self
|
||||
.backend
|
||||
.block_hash(Some(number))
|
||||
@@ -152,23 +154,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn finalized_head(&self) -> RpcResult<Block::Hash> {
|
||||
self.backend.finalized_head().map_err(Into::into)
|
||||
fn finalized_head(&self) -> Result<Block::Hash, Error> {
|
||||
self.backend.finalized_head()
|
||||
}
|
||||
|
||||
fn subscribe_all_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
|
||||
self.backend.subscribe_all_heads(sink);
|
||||
Ok(())
|
||||
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink) {
|
||||
self.backend.subscribe_all_heads(pending);
|
||||
}
|
||||
|
||||
fn subscribe_new_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
|
||||
self.backend.subscribe_new_heads(sink);
|
||||
Ok(())
|
||||
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink) {
|
||||
self.backend.subscribe_new_heads(pending)
|
||||
}
|
||||
|
||||
fn subscribe_finalized_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
|
||||
self.backend.subscribe_finalized_heads(sink);
|
||||
Ok(())
|
||||
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink) {
|
||||
self.backend.subscribe_finalized_heads(pending)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use super::*;
|
||||
use crate::testing::{test_executor, timeout_secs};
|
||||
use assert_matches::assert_matches;
|
||||
use jsonrpsee::types::EmptyServerParams as EmptyParams;
|
||||
use jsonrpsee::core::EmptyServerParams as EmptyParams;
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sp_consensus::BlockOrigin;
|
||||
use sp_rpc::list::ListOrValue;
|
||||
@@ -252,7 +252,7 @@ async fn test_head_subscription(method: &str) {
|
||||
|
||||
let mut sub = {
|
||||
let api = new_full(client.clone(), test_executor()).into_rpc();
|
||||
let sub = api.subscribe(method, EmptyParams::new()).await.unwrap();
|
||||
let sub = api.subscribe_unbounded(method, EmptyParams::new()).await.unwrap();
|
||||
let block = BlockBuilderBuilder::new(&*client)
|
||||
.on_parent_block(client.chain_info().best_hash)
|
||||
.with_parent_block_number(client.chain_info().best_number)
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use jsonrpsee::core::RpcResult;
|
||||
use sc_client_api::{BlockBackend, HeaderBackend};
|
||||
use sc_rpc_api::{dev::error::Error, DenyUnsafe};
|
||||
use sp_api::{ApiExt, Core, ProvideRuntimeApi};
|
||||
@@ -65,7 +64,7 @@ where
|
||||
+ 'static,
|
||||
Client::Api: Core<Block>,
|
||||
{
|
||||
fn block_stats(&self, hash: Block::Hash) -> RpcResult<Option<BlockStats>> {
|
||||
fn block_stats(&self, hash: Block::Hash) -> Result<Option<BlockStats>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let block = {
|
||||
|
||||
@@ -97,7 +97,7 @@ async fn deny_unsafe_works() {
|
||||
"{{\"jsonrpc\":\"2.0\",\"method\":\"dev_getBlockStats\",\"params\":[{}],\"id\":1}}",
|
||||
best_hash_param
|
||||
);
|
||||
let (resp, _) = api.raw_json_request(&request).await.expect("Raw calls should succeed");
|
||||
let (resp, _) = api.raw_json_request(&request, 1).await.expect("Raw calls should succeed");
|
||||
|
||||
assert_eq!(
|
||||
resp.result,
|
||||
|
||||
@@ -39,6 +39,7 @@ pub mod offchain;
|
||||
pub mod state;
|
||||
pub mod statement;
|
||||
pub mod system;
|
||||
pub mod utils;
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
pub mod testing;
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
//! Substrate mixnet API.
|
||||
|
||||
use jsonrpsee::core::{async_trait, RpcResult};
|
||||
use jsonrpsee::core::async_trait;
|
||||
use sc_mixnet::Api;
|
||||
use sc_rpc_api::mixnet::error::Error;
|
||||
pub use sc_rpc_api::mixnet::MixnetApiServer;
|
||||
@@ -36,7 +36,7 @@ impl Mixnet {
|
||||
|
||||
#[async_trait]
|
||||
impl MixnetApiServer for Mixnet {
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult<()> {
|
||||
async fn submit_extrinsic(&self, extrinsic: Bytes) -> Result<(), Error> {
|
||||
// We only hold the lock while pushing the request into the requests channel
|
||||
let fut = {
|
||||
let mut api = self.0.lock().await;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
mod tests;
|
||||
|
||||
use self::error::Error;
|
||||
use jsonrpsee::core::{async_trait, Error as JsonRpseeError, RpcResult};
|
||||
use jsonrpsee::core::async_trait;
|
||||
use parking_lot::RwLock;
|
||||
/// Re-export the API for backward compatibility.
|
||||
pub use sc_rpc_api::offchain::*;
|
||||
@@ -50,23 +50,23 @@ impl<T: OffchainStorage> Offchain<T> {
|
||||
|
||||
#[async_trait]
|
||||
impl<T: OffchainStorage + 'static> OffchainApiServer for Offchain<T> {
|
||||
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()> {
|
||||
fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<(), Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let prefix = match kind {
|
||||
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
|
||||
StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)),
|
||||
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
|
||||
};
|
||||
self.storage.write().set(prefix, &key, &value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult<Option<Bytes>> {
|
||||
fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result<Option<Bytes>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
let prefix = match kind {
|
||||
StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX,
|
||||
StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)),
|
||||
StorageKind::LOCAL => return Err(Error::UnavailableStorageKind),
|
||||
};
|
||||
|
||||
Ok(self.storage.read().get(prefix, &key).map(Into::into))
|
||||
|
||||
@@ -39,7 +39,6 @@ fn local_storage_should_work() {
|
||||
|
||||
#[test]
|
||||
fn offchain_calls_considered_unsafe() {
|
||||
use jsonrpsee::types::error::CallError;
|
||||
let storage = InMemOffchainStorage::default();
|
||||
let offchain = Offchain::new(storage, DenyUnsafe::Yes);
|
||||
let key = Bytes(b"offchain_storage".to_vec());
|
||||
@@ -47,14 +46,14 @@ fn offchain_calls_considered_unsafe() {
|
||||
|
||||
assert_matches!(
|
||||
offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()),
|
||||
Err(JsonRpseeError::Call(CallError::Custom(err))) => {
|
||||
assert_eq!(err.message(), "RPC call is unsafe to be called externally")
|
||||
Err(Error::UnsafeRpcCalled(e)) => {
|
||||
assert_eq!(e.to_string(), "RPC call is unsafe to be called externally")
|
||||
}
|
||||
);
|
||||
assert_matches!(
|
||||
offchain.get_local_storage(StorageKind::PERSISTENT, key),
|
||||
Err(JsonRpseeError::Call(CallError::Custom(err))) => {
|
||||
assert_eq!(err.message(), "RPC call is unsafe to be called externally")
|
||||
Err(Error::UnsafeRpcCalled(e)) => {
|
||||
assert_eq!(e.to_string(), "RPC call is unsafe to be called externally")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,32 +24,23 @@ mod utils;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::SubscriptionTaskExecutor;
|
||||
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, server::rpc_module::SubscriptionSink, Error as JsonRpseeError, RpcResult},
|
||||
types::SubscriptionResult,
|
||||
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
|
||||
use sc_client_api::{
|
||||
Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider,
|
||||
};
|
||||
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
|
||||
use sp_blockchain::{HeaderBackend, HeaderMetadata};
|
||||
use sp_core::{
|
||||
storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey},
|
||||
Bytes,
|
||||
};
|
||||
use sp_runtime::traits::Block as BlockT;
|
||||
use sp_version::RuntimeVersion;
|
||||
use std::sync::Arc;
|
||||
|
||||
use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi};
|
||||
|
||||
use self::error::Error;
|
||||
|
||||
use sc_client_api::{
|
||||
Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider,
|
||||
};
|
||||
pub use sc_rpc_api::{child_state::*, state::*};
|
||||
use sp_blockchain::{HeaderBackend, HeaderMetadata};
|
||||
|
||||
const STORAGE_KEYS_PAGED_MAX_COUNT: u32 = 1000;
|
||||
|
||||
@@ -158,10 +149,15 @@ where
|
||||
) -> Result<sp_rpc::tracing::TraceBlockResponse, Error>;
|
||||
|
||||
/// New runtime version subscription
|
||||
fn subscribe_runtime_version(&self, sink: SubscriptionSink);
|
||||
fn subscribe_runtime_version(&self, pending: PendingSubscriptionSink);
|
||||
|
||||
/// New storage subscription
|
||||
fn subscribe_storage(&self, sink: SubscriptionSink, keys: Option<Vec<StorageKey>>);
|
||||
fn subscribe_storage(
|
||||
&self,
|
||||
pending: PendingSubscriptionSink,
|
||||
keys: Option<Vec<StorageKey>>,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
);
|
||||
}
|
||||
|
||||
/// Create new state API that works on full node.
|
||||
@@ -207,7 +203,12 @@ where
|
||||
Block: BlockT + 'static,
|
||||
Client: Send + Sync + 'static,
|
||||
{
|
||||
fn call(&self, method: String, data: Bytes, block: Option<Block::Hash>) -> RpcResult<Bytes> {
|
||||
fn call(
|
||||
&self,
|
||||
method: String,
|
||||
data: Bytes,
|
||||
block: Option<Block::Hash>,
|
||||
) -> Result<Bytes, Error> {
|
||||
self.backend.call(block, method, data).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ where
|
||||
&self,
|
||||
key_prefix: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>> {
|
||||
) -> Result<Vec<StorageKey>, Error> {
|
||||
self.backend.storage_keys(block, key_prefix).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -223,7 +224,7 @@ where
|
||||
&self,
|
||||
key_prefix: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<(StorageKey, StorageData)>> {
|
||||
) -> Result<Vec<(StorageKey, StorageData)>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
self.backend.storage_pairs(block, key_prefix).map_err(Into::into)
|
||||
}
|
||||
@@ -234,12 +235,9 @@ where
|
||||
count: u32,
|
||||
start_key: Option<StorageKey>,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>> {
|
||||
) -> Result<Vec<StorageKey>, Error> {
|
||||
if count > STORAGE_KEYS_PAGED_MAX_COUNT {
|
||||
return Err(JsonRpseeError::from(Error::InvalidCount {
|
||||
value: count,
|
||||
max: STORAGE_KEYS_PAGED_MAX_COUNT,
|
||||
}))
|
||||
return Err(Error::InvalidCount { value: count, max: STORAGE_KEYS_PAGED_MAX_COUNT })
|
||||
}
|
||||
self.backend
|
||||
.storage_keys_paged(block, prefix, count, start_key)
|
||||
@@ -250,7 +248,7 @@ where
|
||||
&self,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<StorageData>> {
|
||||
) -> Result<Option<StorageData>, Error> {
|
||||
self.backend.storage(block, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -258,7 +256,7 @@ where
|
||||
&self,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<Block::Hash>> {
|
||||
) -> Result<Option<Block::Hash>, Error> {
|
||||
self.backend.storage_hash(block, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -266,18 +264,18 @@ where
|
||||
&self,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<u64>> {
|
||||
) -> Result<Option<u64>, Error> {
|
||||
self.backend
|
||||
.storage_size(block, key, self.deny_unsafe)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn metadata(&self, block: Option<Block::Hash>) -> RpcResult<Bytes> {
|
||||
fn metadata(&self, block: Option<Block::Hash>) -> Result<Bytes, Error> {
|
||||
self.backend.metadata(block).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn runtime_version(&self, at: Option<Block::Hash>) -> RpcResult<RuntimeVersion> {
|
||||
fn runtime_version(&self, at: Option<Block::Hash>) -> Result<RuntimeVersion, Error> {
|
||||
self.backend.runtime_version(at).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -286,7 +284,7 @@ where
|
||||
keys: Vec<StorageKey>,
|
||||
from: Block::Hash,
|
||||
to: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageChangeSet<Block::Hash>>> {
|
||||
) -> Result<Vec<StorageChangeSet<Block::Hash>>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
self.backend.query_storage(from, to, keys).map_err(Into::into)
|
||||
}
|
||||
@@ -295,7 +293,7 @@ where
|
||||
&self,
|
||||
keys: Vec<StorageKey>,
|
||||
at: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageChangeSet<Block::Hash>>> {
|
||||
) -> Result<Vec<StorageChangeSet<Block::Hash>>, Error> {
|
||||
self.backend.query_storage_at(keys, at).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -303,7 +301,7 @@ where
|
||||
&self,
|
||||
keys: Vec<StorageKey>,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<ReadProof<Block::Hash>> {
|
||||
) -> Result<ReadProof<Block::Hash>, Error> {
|
||||
self.backend.read_proof(block, keys).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -318,32 +316,19 @@ where
|
||||
targets: Option<String>,
|
||||
storage_keys: Option<String>,
|
||||
methods: Option<String>,
|
||||
) -> RpcResult<sp_rpc::tracing::TraceBlockResponse> {
|
||||
) -> Result<sp_rpc::tracing::TraceBlockResponse, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
self.backend
|
||||
.trace_block(block, targets, storage_keys, methods)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
fn subscribe_runtime_version(&self, sink: SubscriptionSink) -> SubscriptionResult {
|
||||
self.backend.subscribe_runtime_version(sink);
|
||||
Ok(())
|
||||
fn subscribe_runtime_version(&self, pending: PendingSubscriptionSink) {
|
||||
self.backend.subscribe_runtime_version(pending)
|
||||
}
|
||||
|
||||
fn subscribe_storage(
|
||||
&self,
|
||||
mut sink: SubscriptionSink,
|
||||
keys: Option<Vec<StorageKey>>,
|
||||
) -> SubscriptionResult {
|
||||
if keys.is_none() {
|
||||
if let Err(err) = self.deny_unsafe.check_if_safe() {
|
||||
let _ = sink.reject(JsonRpseeError::from(err));
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
self.backend.subscribe_storage(sink, keys);
|
||||
Ok(())
|
||||
fn subscribe_storage(&self, pending: PendingSubscriptionSink, keys: Option<Vec<StorageKey>>) {
|
||||
self.backend.subscribe_storage(pending, keys, self.deny_unsafe)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +415,7 @@ where
|
||||
storage_key: PrefixedStorageKey,
|
||||
key_prefix: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>> {
|
||||
) -> Result<Vec<StorageKey>, Error> {
|
||||
self.backend.storage_keys(block, storage_key, key_prefix).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -441,7 +426,7 @@ where
|
||||
count: u32,
|
||||
start_key: Option<StorageKey>,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<StorageKey>> {
|
||||
) -> Result<Vec<StorageKey>, Error> {
|
||||
self.backend
|
||||
.storage_keys_paged(block, storage_key, prefix, count, start_key)
|
||||
.map_err(Into::into)
|
||||
@@ -452,7 +437,7 @@ where
|
||||
storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<StorageData>> {
|
||||
) -> Result<Option<StorageData>, Error> {
|
||||
self.backend.storage(block, storage_key, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -461,7 +446,7 @@ where
|
||||
storage_key: PrefixedStorageKey,
|
||||
keys: Vec<StorageKey>,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Vec<Option<StorageData>>> {
|
||||
) -> Result<Vec<Option<StorageData>>, Error> {
|
||||
self.backend.storage_entries(block, storage_key, keys).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -470,7 +455,7 @@ where
|
||||
storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<Block::Hash>> {
|
||||
) -> Result<Option<Block::Hash>, Error> {
|
||||
self.backend.storage_hash(block, storage_key, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -479,7 +464,7 @@ where
|
||||
storage_key: PrefixedStorageKey,
|
||||
key: StorageKey,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<Option<u64>> {
|
||||
) -> Result<Option<u64>, Error> {
|
||||
self.backend.storage_size(block, storage_key, key).map_err(Into::into)
|
||||
}
|
||||
|
||||
@@ -488,7 +473,7 @@ where
|
||||
child_storage_key: PrefixedStorageKey,
|
||||
keys: Vec<StorageKey>,
|
||||
block: Option<Block::Hash>,
|
||||
) -> RpcResult<ReadProof<Block::Hash>> {
|
||||
) -> Result<ReadProof<Block::Hash>, Error> {
|
||||
self.backend
|
||||
.read_child_proof(block, child_storage_key, keys)
|
||||
.map_err(Into::into)
|
||||
|
||||
@@ -25,13 +25,13 @@ use super::{
|
||||
error::{Error, Result},
|
||||
ChildStateBackend, StateBackend,
|
||||
};
|
||||
use crate::{DenyUnsafe, SubscriptionTaskExecutor};
|
||||
|
||||
use futures::{future, stream, FutureExt, StreamExt};
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError},
|
||||
SubscriptionSink,
|
||||
use crate::{
|
||||
utils::{pipe_from_stream, spawn_subscription_task},
|
||||
DenyUnsafe, SubscriptionTaskExecutor,
|
||||
};
|
||||
|
||||
use futures::{future, stream, StreamExt};
|
||||
use jsonrpsee::{core::async_trait, types::ErrorObject, PendingSubscriptionSink};
|
||||
use sc_client_api::{
|
||||
Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider,
|
||||
StorageProvider,
|
||||
@@ -371,9 +371,7 @@ where
|
||||
.map_err(client_err)
|
||||
}
|
||||
|
||||
fn subscribe_runtime_version(&self, mut sink: SubscriptionSink) {
|
||||
let client = self.client.clone();
|
||||
|
||||
fn subscribe_runtime_version(&self, pending: PendingSubscriptionSink) {
|
||||
let initial = match self
|
||||
.block_or_best(None)
|
||||
.and_then(|block| self.client.runtime_version_at(block).map_err(Into::into))
|
||||
@@ -381,12 +379,13 @@ where
|
||||
{
|
||||
Ok(initial) => initial,
|
||||
Err(e) => {
|
||||
let _ = sink.reject(JsonRpseeError::from(e));
|
||||
spawn_subscription_task(&self.executor, pending.reject(e));
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
let mut previous_version = initial.clone();
|
||||
let client = self.client.clone();
|
||||
|
||||
// A stream of new versions
|
||||
let version_stream = client
|
||||
@@ -406,24 +405,33 @@ where
|
||||
});
|
||||
|
||||
let stream = futures::stream::once(future::ready(initial)).chain(version_stream);
|
||||
|
||||
let fut = async move {
|
||||
sink.pipe_from_stream(stream).await;
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream));
|
||||
}
|
||||
|
||||
fn subscribe_storage(&self, mut sink: SubscriptionSink, keys: Option<Vec<StorageKey>>) {
|
||||
fn subscribe_storage(
|
||||
&self,
|
||||
pending: PendingSubscriptionSink,
|
||||
keys: Option<Vec<StorageKey>>,
|
||||
deny_unsafe: DenyUnsafe,
|
||||
) {
|
||||
if keys.is_none() {
|
||||
if let Err(err) = deny_unsafe.check_if_safe() {
|
||||
spawn_subscription_task(&self.executor, pending.reject(ErrorObject::from(err)));
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let stream = match self.client.storage_changes_notification_stream(keys.as_deref(), None) {
|
||||
Ok(stream) => stream,
|
||||
Err(blockchain_err) => {
|
||||
let _ = sink.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err))));
|
||||
spawn_subscription_task(
|
||||
&self.executor,
|
||||
pending.reject(Error::Client(Box::new(blockchain_err))),
|
||||
);
|
||||
return
|
||||
},
|
||||
};
|
||||
|
||||
// initial values
|
||||
let initial = stream::iter(keys.map(|keys| {
|
||||
let block = self.client.info().best_hash;
|
||||
let changes = keys
|
||||
@@ -436,7 +444,6 @@ where
|
||||
StorageChangeSet { block, changes }
|
||||
}));
|
||||
|
||||
// let storage_stream = stream.map(|(block, changes)| StorageChangeSet {
|
||||
let storage_stream = stream.map(|storage_notif| StorageChangeSet {
|
||||
block: storage_notif.block,
|
||||
changes: storage_notif
|
||||
@@ -450,11 +457,7 @@ where
|
||||
.chain(storage_stream)
|
||||
.filter(|storage| future::ready(!storage.changes.is_empty()));
|
||||
|
||||
let fut = async move {
|
||||
sink.pipe_from_stream(stream).await;
|
||||
};
|
||||
|
||||
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
spawn_subscription_task(&self.executor, pipe_from_stream(pending, stream));
|
||||
}
|
||||
|
||||
fn trace_block(
|
||||
|
||||
@@ -21,10 +21,7 @@ use super::*;
|
||||
use crate::testing::{test_executor, timeout_secs};
|
||||
use assert_matches::assert_matches;
|
||||
use futures::executor;
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::{error::CallError as RpcCallError, EmptyServerParams as EmptyParams, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::core::{EmptyServerParams as EmptyParams, Error as RpcError};
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sp_consensus::BlockOrigin;
|
||||
@@ -42,6 +39,14 @@ fn prefixed_storage_key() -> PrefixedStorageKey {
|
||||
child_info.prefixed_storage_key()
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
let _ = FmtSubscriber::builder()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_storage() {
|
||||
const KEY: &[u8] = b":mock";
|
||||
@@ -200,22 +205,25 @@ async fn should_call_contract() {
|
||||
let genesis_hash = client.genesis_hash();
|
||||
let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No);
|
||||
|
||||
use jsonrpsee::{core::Error, types::error::CallError};
|
||||
|
||||
assert_matches!(
|
||||
client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()),
|
||||
Err(Error::Call(CallError::Failed(_)))
|
||||
Err(Error::Client(_))
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_notify_about_storage_changes() {
|
||||
init_logger();
|
||||
|
||||
let mut sub = {
|
||||
let mut client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No);
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap();
|
||||
let sub = api_rpc
|
||||
.subscribe_unbounded("state_subscribeStorage", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Cause a change:
|
||||
let mut builder = BlockBuilderBuilder::new(&*client)
|
||||
@@ -241,11 +249,12 @@ async fn should_notify_about_storage_changes() {
|
||||
// NOTE: previous versions of the subscription code used to return an empty value for the
|
||||
// "initial" storage change here
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_send_initial_storage_changes_and_notifications() {
|
||||
init_logger();
|
||||
|
||||
let mut sub = {
|
||||
let mut client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No);
|
||||
@@ -263,7 +272,10 @@ async fn should_send_initial_storage_changes_and_notifications() {
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc
|
||||
.subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key)]])
|
||||
.subscribe_unbounded(
|
||||
"state_subscribeStorage",
|
||||
[[StorageKey(alice_balance_key.to_vec())]],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -288,9 +300,6 @@ async fn should_send_initial_storage_changes_and_notifications() {
|
||||
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
|
||||
// No more messages to follow
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -393,108 +402,48 @@ async fn should_query_storage() {
|
||||
assert_eq!(result.unwrap(), expected);
|
||||
|
||||
// Inverted range.
|
||||
let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("1 ({:?})", block1_hash),
|
||||
to: format!("0 ({:?})", genesis_hash),
|
||||
details: "from number > to number".to_owned(),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string())
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("1 ({:?})", block1_hash) && to == format!("0 ({:?})", genesis_hash) && details == "from number > to number".to_owned()
|
||||
);
|
||||
|
||||
let random_hash1 = H256::random();
|
||||
let random_hash2 = H256::random();
|
||||
|
||||
// Invalid second hash.
|
||||
let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", genesis_hash),
|
||||
to: format!("{:?}", Some(random_hash1)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string())
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", genesis_hash) && to == format!("{:?}", Some(random_hash1)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Invalid first hash with Some other hash.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1),
|
||||
to: format!("{:?}", Some(genesis_hash)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(genesis_hash)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Invalid first hash with None.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, None);
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1),
|
||||
to: format!("{:?}", Some(block2_hash)), // Best block hash.
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, None),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(block2_hash)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Both hashes invalid.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1), // First hash not found.
|
||||
to: format!("{:?}", Some(random_hash2)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, Some(random_hash2)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(random_hash2)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// single block range
|
||||
@@ -548,7 +497,7 @@ async fn should_notify_on_runtime_version_initially() {
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc
|
||||
.subscribe("state_subscribeRuntimeVersion", EmptyParams::new())
|
||||
.subscribe_unbounded("state_subscribeRuntimeVersion", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -557,9 +506,6 @@ async fn should_notify_on_runtime_version_initially() {
|
||||
|
||||
// assert initial version sent.
|
||||
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(Some(_)));
|
||||
|
||||
sub.close();
|
||||
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -572,12 +518,14 @@ fn should_deserialize_storage_key() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn wildcard_storage_subscriptions_are_rpc_unsafe() {
|
||||
init_logger();
|
||||
|
||||
let client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes);
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await;
|
||||
assert_matches!(err, Err(RpcError::Call(RpcCallError::Custom(e))) if e.message() == "RPC call is unsafe to be called externally");
|
||||
let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await;
|
||||
assert_matches!(err, Err(RpcError::Call(e)) if e.message() == "RPC call is unsafe to be called externally");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -587,7 +535,7 @@ async fn concrete_storage_subscriptions_are_rpc_safe() {
|
||||
let api_rpc = api.into_rpc();
|
||||
|
||||
let key = StorageKey(STORAGE_KEY.to_vec());
|
||||
let sub = api_rpc.subscribe("state_subscribeStorage", [[key]]).await;
|
||||
let sub = api_rpc.subscribe_unbounded("state_subscribeStorage", [[key]]).await;
|
||||
|
||||
assert!(sub.is_ok());
|
||||
}
|
||||
|
||||
@@ -22,17 +22,12 @@
|
||||
mod tests;
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, error::Error as JsonRpseeError, JsonValue, RpcResult},
|
||||
types::error::{CallError, ErrorCode, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::core::{async_trait, JsonValue};
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sc_tracing::logging;
|
||||
use sc_utils::mpsc::TracingUnboundedSender;
|
||||
use sp_runtime::traits::{self, Header as HeaderT};
|
||||
|
||||
use self::error::Result;
|
||||
|
||||
pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo};
|
||||
pub use sc_rpc_api::system::*;
|
||||
|
||||
@@ -57,9 +52,9 @@ pub enum Request<B: traits::Block> {
|
||||
/// Must return the state of the network.
|
||||
NetworkState(oneshot::Sender<serde_json::Value>),
|
||||
/// Must return any potential parse error.
|
||||
NetworkAddReservedPeer(String, oneshot::Sender<Result<()>>),
|
||||
NetworkAddReservedPeer(String, oneshot::Sender<error::Result<()>>),
|
||||
/// Must return any potential parse error.
|
||||
NetworkRemoveReservedPeer(String, oneshot::Sender<Result<()>>),
|
||||
NetworkRemoveReservedPeer(String, oneshot::Sender<error::Result<()>>),
|
||||
/// Must return the list of reserved peers
|
||||
NetworkReservedPeers(oneshot::Sender<Vec<String>>),
|
||||
/// Must return the node role.
|
||||
@@ -84,121 +79,109 @@ impl<B: traits::Block> System<B> {
|
||||
|
||||
#[async_trait]
|
||||
impl<B: traits::Block> SystemApiServer<B::Hash, <B::Header as HeaderT>::Number> for System<B> {
|
||||
fn system_name(&self) -> RpcResult<String> {
|
||||
fn system_name(&self) -> Result<String, Error> {
|
||||
Ok(self.info.impl_name.clone())
|
||||
}
|
||||
|
||||
fn system_version(&self) -> RpcResult<String> {
|
||||
fn system_version(&self) -> Result<String, Error> {
|
||||
Ok(self.info.impl_version.clone())
|
||||
}
|
||||
|
||||
fn system_chain(&self) -> RpcResult<String> {
|
||||
fn system_chain(&self) -> Result<String, Error> {
|
||||
Ok(self.info.chain_name.clone())
|
||||
}
|
||||
|
||||
fn system_type(&self) -> RpcResult<sc_chain_spec::ChainType> {
|
||||
fn system_type(&self) -> Result<sc_chain_spec::ChainType, Error> {
|
||||
Ok(self.info.chain_type.clone())
|
||||
}
|
||||
|
||||
fn system_properties(&self) -> RpcResult<sc_chain_spec::Properties> {
|
||||
fn system_properties(&self) -> Result<sc_chain_spec::Properties, Error> {
|
||||
Ok(self.info.properties.clone())
|
||||
}
|
||||
|
||||
async fn system_health(&self) -> RpcResult<Health> {
|
||||
async fn system_health(&self) -> Result<Health, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::Health(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_local_peer_id(&self) -> RpcResult<String> {
|
||||
async fn system_local_peer_id(&self) -> Result<String, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::LocalPeerId(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_local_listen_addresses(&self) -> RpcResult<Vec<String>> {
|
||||
async fn system_local_listen_addresses(&self) -> Result<Vec<String>, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::LocalListenAddresses(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_peers(
|
||||
&self,
|
||||
) -> RpcResult<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>> {
|
||||
) -> Result<Vec<PeerInfo<B::Hash, <B::Header as HeaderT>::Number>>, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::Peers(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_network_state(&self) -> RpcResult<JsonValue> {
|
||||
async fn system_network_state(&self) -> Result<JsonValue, Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkState(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()> {
|
||||
async fn system_add_reserved_peer(&self, peer: String) -> Result<(), Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx));
|
||||
match rx.await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(e)) => Err(JsonRpseeError::from(e)),
|
||||
Err(e) => Err(JsonRpseeError::to_call_error(e)),
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(e) => Err(Error::Internal(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn system_remove_reserved_peer(&self, peer: String) -> RpcResult<()> {
|
||||
async fn system_remove_reserved_peer(&self, peer: String) -> Result<(), Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx));
|
||||
match rx.await {
|
||||
Ok(Ok(())) => Ok(()),
|
||||
Ok(Err(e)) => Err(JsonRpseeError::from(e)),
|
||||
Err(e) => Err(JsonRpseeError::to_call_error(e)),
|
||||
Ok(Err(e)) => Err(e),
|
||||
Err(e) => Err(Error::Internal(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
async fn system_reserved_peers(&self) -> RpcResult<Vec<String>> {
|
||||
async fn system_reserved_peers(&self) -> Result<Vec<String>, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NetworkReservedPeers(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_node_roles(&self) -> RpcResult<Vec<NodeRole>> {
|
||||
async fn system_node_roles(&self) -> Result<Vec<NodeRole>, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::NodeRoles(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
async fn system_sync_state(&self) -> RpcResult<SyncState<<B::Header as HeaderT>::Number>> {
|
||||
async fn system_sync_state(&self) -> Result<SyncState<<B::Header as HeaderT>::Number>, Error> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let _ = self.send_back.unbounded_send(Request::SyncState(tx));
|
||||
rx.await.map_err(|e| JsonRpseeError::to_call_error(e))
|
||||
rx.await.map_err(|e| Error::Internal(e.to_string()))
|
||||
}
|
||||
|
||||
fn system_add_log_filter(&self, directives: String) -> RpcResult<()> {
|
||||
fn system_add_log_filter(&self, directives: String) -> Result<(), Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
|
||||
logging::add_directives(&directives);
|
||||
logging::reload_filter().map_err(|e| {
|
||||
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
|
||||
ErrorCode::InternalError.code(),
|
||||
e,
|
||||
None::<()>,
|
||||
)))
|
||||
})
|
||||
logging::reload_filter().map_err(|e| Error::Internal(e))
|
||||
}
|
||||
|
||||
fn system_reset_log_filter(&self) -> RpcResult<()> {
|
||||
fn system_reset_log_filter(&self) -> Result<(), Error> {
|
||||
self.deny_unsafe.check_if_safe()?;
|
||||
logging::reset_log_filter().map_err(|e| {
|
||||
JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
|
||||
ErrorCode::InternalError.code(),
|
||||
e,
|
||||
None::<()>,
|
||||
)))
|
||||
})
|
||||
logging::reset_log_filter().map_err(|e| Error::Internal(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,8 +20,7 @@ use super::{helpers::SyncState, *};
|
||||
use assert_matches::assert_matches;
|
||||
use futures::prelude::*;
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::{error::CallError, EmptyServerParams as EmptyParams},
|
||||
core::{EmptyServerParams as EmptyParams, Error as RpcError},
|
||||
RpcModule,
|
||||
};
|
||||
use sc_network::{self, config::Role, PeerId};
|
||||
@@ -312,7 +311,7 @@ async fn system_network_add_reserved() {
|
||||
let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"];
|
||||
assert_matches!(
|
||||
api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await,
|
||||
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Peer id is missing from the address")
|
||||
Err(RpcError::Call(err)) if err.message().contains("Peer id is missing from the address")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -328,7 +327,7 @@ async fn system_network_remove_reserved() {
|
||||
|
||||
assert_matches!(
|
||||
api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await,
|
||||
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0")
|
||||
Err(RpcError::Call(err)) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0")
|
||||
);
|
||||
}
|
||||
#[tokio::test]
|
||||
|
||||
@@ -20,11 +20,50 @@
|
||||
|
||||
use std::{future::Future, sync::Arc};
|
||||
|
||||
use sp_core::testing::TaskExecutor;
|
||||
/// A task executor that can be used for running RPC tests.
|
||||
///
|
||||
/// Warning: the tokio runtime must be initialized before calling this.
|
||||
#[derive(Clone)]
|
||||
pub struct TokioTestExecutor(tokio::runtime::Handle);
|
||||
|
||||
impl TokioTestExecutor {
|
||||
/// Create a new instance of `Self`.
|
||||
pub fn new() -> Self {
|
||||
Self(tokio::runtime::Handle::current())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TokioTestExecutor {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl sp_core::traits::SpawnNamed for TokioTestExecutor {
|
||||
fn spawn_blocking(
|
||||
&self,
|
||||
_name: &'static str,
|
||||
_group: Option<&'static str>,
|
||||
future: futures::future::BoxFuture<'static, ()>,
|
||||
) {
|
||||
let handle = self.0.clone();
|
||||
self.0.spawn_blocking(move || {
|
||||
handle.block_on(future);
|
||||
});
|
||||
}
|
||||
fn spawn(
|
||||
&self,
|
||||
_name: &'static str,
|
||||
_group: Option<&'static str>,
|
||||
future: futures::future::BoxFuture<'static, ()>,
|
||||
) {
|
||||
self.0.spawn(future);
|
||||
}
|
||||
}
|
||||
|
||||
/// Executor for testing.
|
||||
pub fn test_executor() -> Arc<sp_core::testing::TaskExecutor> {
|
||||
Arc::new(TaskExecutor::default())
|
||||
pub fn test_executor() -> Arc<TokioTestExecutor> {
|
||||
Arc::new(TokioTestExecutor::default())
|
||||
}
|
||||
|
||||
/// Wrap a future in a timeout a little more concisely
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
//! JSON-RPC helpers.
|
||||
|
||||
use crate::SubscriptionTaskExecutor;
|
||||
use futures::{
|
||||
future::{self, Either, Fuse, FusedFuture},
|
||||
Future, FutureExt, Stream, StreamExt,
|
||||
};
|
||||
use jsonrpsee::{PendingSubscriptionSink, SubscriptionMessage, SubscriptionSink};
|
||||
use sp_runtime::Serialize;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
const DEFAULT_BUF_SIZE: usize = 16;
|
||||
|
||||
/// A simple bounded VecDeque.
|
||||
struct BoundedVecDeque<T> {
|
||||
inner: VecDeque<T>,
|
||||
max_cap: usize,
|
||||
}
|
||||
|
||||
impl<T> BoundedVecDeque<T> {
|
||||
/// Create a new bounded VecDeque.
|
||||
fn new() -> Self {
|
||||
Self { inner: VecDeque::with_capacity(DEFAULT_BUF_SIZE), max_cap: DEFAULT_BUF_SIZE }
|
||||
}
|
||||
|
||||
fn push_back(&mut self, item: T) -> Result<(), ()> {
|
||||
if self.inner.len() >= self.max_cap {
|
||||
Err(())
|
||||
} else {
|
||||
self.inner.push_back(item);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<T> {
|
||||
self.inner.pop_front()
|
||||
}
|
||||
}
|
||||
|
||||
/// Feed items to the subscription from the underlying stream.
|
||||
///
|
||||
/// This is bounded because the underlying streams in substrate are
|
||||
/// unbounded and if the subscription can't keep with stream it can
|
||||
/// cause the buffer to become very large and consume lots of memory.
|
||||
///
|
||||
/// In such cases the subscription is dropped.
|
||||
pub async fn pipe_from_stream<S, T>(pending: PendingSubscriptionSink, mut stream: S)
|
||||
where
|
||||
S: Stream<Item = T> + Unpin + Send + 'static,
|
||||
T: Serialize + Send + 'static,
|
||||
{
|
||||
let mut buf = BoundedVecDeque::new();
|
||||
let accept_fut = pending.accept();
|
||||
|
||||
futures::pin_mut!(accept_fut);
|
||||
|
||||
// Poll the stream while waiting for the subscription to be accepted
|
||||
//
|
||||
// If the `max_cap` is exceeded then the subscription is dropped.
|
||||
let sink = loop {
|
||||
match future::select(accept_fut, stream.next()).await {
|
||||
Either::Left((Ok(sink), _)) => break sink,
|
||||
Either::Right((Some(msg), f)) => {
|
||||
if buf.push_back(msg).is_err() {
|
||||
log::warn!(target: "rpc", "Subscription::accept failed buffer limit={} exceed; dropping subscription", buf.max_cap);
|
||||
return
|
||||
}
|
||||
accept_fut = f;
|
||||
},
|
||||
// The connection was closed or the stream was closed.
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
|
||||
inner_pipe_from_stream(sink, stream, buf).await
|
||||
}
|
||||
|
||||
async fn inner_pipe_from_stream<S, T>(
|
||||
sink: SubscriptionSink,
|
||||
mut stream: S,
|
||||
mut buf: BoundedVecDeque<T>,
|
||||
) where
|
||||
S: Stream<Item = T> + Unpin + Send + 'static,
|
||||
T: Serialize + Send + 'static,
|
||||
{
|
||||
let mut next_fut = Box::pin(Fuse::terminated());
|
||||
let mut next_item = stream.next();
|
||||
let closed = sink.closed();
|
||||
|
||||
futures::pin_mut!(closed);
|
||||
|
||||
loop {
|
||||
if next_fut.is_terminated() {
|
||||
if let Some(v) = buf.pop_front() {
|
||||
let val = to_sub_message(&sink, &v);
|
||||
next_fut.set(async { sink.send(val).await }.fuse());
|
||||
}
|
||||
}
|
||||
|
||||
match future::select(closed, future::select(next_fut, next_item)).await {
|
||||
// Send operation finished.
|
||||
Either::Right((Either::Left((_, n)), c)) => {
|
||||
next_item = n;
|
||||
closed = c;
|
||||
next_fut = Box::pin(Fuse::terminated());
|
||||
},
|
||||
// New item from the stream
|
||||
Either::Right((Either::Right((Some(v), n)), c)) => {
|
||||
if buf.push_back(v).is_err() {
|
||||
log::warn!(target: "rpc", "Subscription buffer limit={} exceed; dropping subscription", buf.max_cap);
|
||||
return
|
||||
}
|
||||
|
||||
next_fut = n;
|
||||
closed = c;
|
||||
next_item = stream.next();
|
||||
},
|
||||
// Stream "finished".
|
||||
//
|
||||
// Process remaining items and terminate.
|
||||
Either::Right((Either::Right((None, pending_fut)), _)) => {
|
||||
if pending_fut.await.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
while let Some(v) = buf.pop_front() {
|
||||
let val = to_sub_message(&sink, &v);
|
||||
if sink.send(val).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
},
|
||||
// Subscription was closed.
|
||||
Either::Left(_) => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a subscription message.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics `Serialize` fails and it is treated a bug.
|
||||
pub fn to_sub_message(sink: &SubscriptionSink, result: &impl Serialize) -> SubscriptionMessage {
|
||||
SubscriptionMessage::new(sink.method_name(), sink.subscription_id(), result)
|
||||
.expect("Serialize infallible; qed")
|
||||
}
|
||||
|
||||
/// Helper for spawning non-blocking rpc subscription task.
|
||||
pub fn spawn_subscription_task(
|
||||
executor: &SubscriptionTaskExecutor,
|
||||
fut: impl Future<Output = ()> + Send + 'static,
|
||||
) {
|
||||
executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::pipe_from_stream;
|
||||
use futures::StreamExt;
|
||||
use jsonrpsee::{core::EmptyServerParams, RpcModule, Subscription};
|
||||
|
||||
async fn subscribe() -> Subscription {
|
||||
let mut module = RpcModule::new(());
|
||||
module
|
||||
.register_subscription("sub", "my_sub", "unsub", |_, pending, _| async move {
|
||||
let stream = futures::stream::iter([0; 16]);
|
||||
pipe_from_stream(pending, stream).await;
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
module.subscribe("sub", EmptyServerParams::new(), 1).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pipe_from_stream_works() {
|
||||
let mut sub = subscribe().await;
|
||||
let mut rx = 0;
|
||||
|
||||
while let Some(Ok(_)) = sub.next::<usize>().await {
|
||||
rx += 1;
|
||||
}
|
||||
|
||||
assert_eq!(rx, 16);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pipe_from_stream_is_bounded() {
|
||||
let (tx, mut rx) = futures::channel::mpsc::unbounded::<()>();
|
||||
|
||||
let mut module = RpcModule::new(tx);
|
||||
module
|
||||
.register_subscription("sub", "my_sub", "unsub", |_, pending, ctx| async move {
|
||||
let stream = futures::stream::iter([0; 32]);
|
||||
pipe_from_stream(pending, stream).await;
|
||||
_ = ctx.unbounded_send(());
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut sub = module.subscribe("sub", EmptyServerParams::new(), 1).await.unwrap();
|
||||
|
||||
// When the 17th item arrives the subscription is dropped
|
||||
_ = rx.next().await.unwrap();
|
||||
assert!(sub.next::<usize>().await.is_none());
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ runtime-benchmarks = [
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
jsonrpsee = { version = "0.16.2", features = ["server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["server"] }
|
||||
thiserror = "1.0.48"
|
||||
futures = "0.3.21"
|
||||
rand = "0.8.5"
|
||||
|
||||
@@ -100,6 +100,8 @@ pub struct Configuration {
|
||||
pub rpc_max_subs_per_conn: u32,
|
||||
/// JSON-RPC server default port.
|
||||
pub rpc_port: u16,
|
||||
/// The number of messages the JSON-RPC server is allowed to keep in memory.
|
||||
pub rpc_message_buffer_capacity: u32,
|
||||
/// Prometheus endpoint configuration. `None` if disabled.
|
||||
pub prometheus_config: Option<PrometheusConfig>,
|
||||
/// Telemetry service URL. `None` if disabled.
|
||||
|
||||
@@ -37,7 +37,7 @@ mod task_manager;
|
||||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{channel::mpsc, pin_mut, FutureExt, StreamExt};
|
||||
use futures::{pin_mut, FutureExt, StreamExt};
|
||||
use jsonrpsee::{core::Error as JsonRpseeError, RpcModule};
|
||||
use log::{debug, error, warn};
|
||||
use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider};
|
||||
@@ -109,9 +109,15 @@ impl RpcHandlers {
|
||||
pub async fn rpc_query(
|
||||
&self,
|
||||
json_query: &str,
|
||||
) -> Result<(String, mpsc::UnboundedReceiver<String>), JsonRpseeError> {
|
||||
) -> Result<(String, tokio::sync::mpsc::Receiver<String>), JsonRpseeError> {
|
||||
// Because `tokio::sync::mpsc::channel` is used under the hood
|
||||
// it will panic if it's set to usize::MAX.
|
||||
//
|
||||
// This limit is used to prevent panics and is large enough.
|
||||
const TOKIO_MPSC_MAX_SIZE: usize = tokio::sync::Semaphore::MAX_PERMITS;
|
||||
|
||||
self.0
|
||||
.raw_json_request(json_query)
|
||||
.raw_json_request(json_query, TOKIO_MPSC_MAX_SIZE)
|
||||
.await
|
||||
.map(|(method_res, recv)| (method_res.result, recv))
|
||||
}
|
||||
@@ -394,6 +400,7 @@ where
|
||||
max_payload_in_mb: config.rpc_max_request_size,
|
||||
max_payload_out_mb: config.rpc_max_response_size,
|
||||
max_subs_per_conn: config.rpc_max_subs_per_conn,
|
||||
message_buffer_capacity: config.rpc_message_buffer_capacity,
|
||||
rpc_api: gen_rpc_module(deny_unsafe(addr, &config.rpc_methods))?,
|
||||
metrics,
|
||||
id_provider: rpc_id_provider,
|
||||
|
||||
@@ -253,6 +253,7 @@ fn node_config<
|
||||
rpc_id_provider: Default::default(),
|
||||
rpc_max_subs_per_conn: Default::default(),
|
||||
rpc_port: 9944,
|
||||
rpc_message_buffer_capacity: Default::default(),
|
||||
prometheus_config: None,
|
||||
telemetry_endpoints: None,
|
||||
default_heap_pages: None,
|
||||
|
||||
@@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1" }
|
||||
jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
|
||||
jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
|
||||
serde = { version = "1.0.195", features = ["derive"] }
|
||||
serde_json = "1.0.111"
|
||||
thiserror = "1.0.48"
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use jsonrpsee::{
|
||||
core::{async_trait, Error as JsonRpseeError, RpcResult},
|
||||
core::async_trait,
|
||||
proc_macros::rpc,
|
||||
types::{error::CallError, ErrorObject},
|
||||
types::{ErrorObject, ErrorObjectOwned},
|
||||
};
|
||||
|
||||
use sc_client_api::StorageData;
|
||||
@@ -80,13 +80,13 @@ pub enum Error<Block: BlockT> {
|
||||
LightSyncStateExtensionNotFound,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> From<Error<Block>> for JsonRpseeError {
|
||||
impl<Block: BlockT> From<Error<Block>> for ErrorObjectOwned {
|
||||
fn from(error: Error<Block>) -> Self {
|
||||
let message = match error {
|
||||
Error::JsonRpc(s) => s,
|
||||
_ => error.to_string(),
|
||||
};
|
||||
CallError::Custom(ErrorObject::owned(1, message, None::<()>)).into()
|
||||
ErrorObject::owned(1, message, None::<()>)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,10 +126,10 @@ pub struct LightSyncState<Block: BlockT> {
|
||||
|
||||
/// An api for sync state RPC calls.
|
||||
#[rpc(client, server)]
|
||||
pub trait SyncStateApi {
|
||||
pub trait SyncStateApi<B: BlockT> {
|
||||
/// Returns the JSON serialized chainspec running the node, with a sync state.
|
||||
#[method(name = "sync_state_genSyncSpec")]
|
||||
async fn system_gen_sync_spec(&self, raw: bool) -> RpcResult<serde_json::Value>;
|
||||
async fn system_gen_sync_spec(&self, raw: bool) -> Result<serde_json::Value, Error<B>>;
|
||||
}
|
||||
|
||||
/// An api for sync state RPC calls.
|
||||
@@ -188,12 +188,12 @@ where
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<Block, Backend> SyncStateApiServer for SyncState<Block, Backend>
|
||||
impl<Block, Backend> SyncStateApiServer<Block> for SyncState<Block, Backend>
|
||||
where
|
||||
Block: BlockT,
|
||||
Backend: HeaderBackend<Block> + sc_client_api::AuxStore + 'static,
|
||||
{
|
||||
async fn system_gen_sync_spec(&self, raw: bool) -> RpcResult<serde_json::Value> {
|
||||
async fn system_gen_sync_spec(&self, raw: bool) -> Result<serde_json::Value, Error<Block>> {
|
||||
let current_sync_state = self.build_sync_state().await?;
|
||||
let mut chain_spec = self.chain_spec.cloned_box();
|
||||
|
||||
@@ -202,10 +202,11 @@ where
|
||||
)
|
||||
.ok_or(Error::<Block>::LightSyncStateExtensionNotFound)?;
|
||||
|
||||
let val = serde_json::to_value(¤t_sync_state)?;
|
||||
let val = serde_json::to_value(¤t_sync_state)
|
||||
.map_err(|e| Error::<Block>::JsonRpc(e.to_string()))?;
|
||||
*extension = Some(val);
|
||||
|
||||
let json_str = chain_spec.as_json(raw).map_err(|e| Error::<Block>::JsonRpc(e))?;
|
||||
serde_json::from_str(&json_str).map_err(Into::into)
|
||||
serde_json::from_str(&json_str).map_err(|e| Error::<Block>::JsonRpc(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user