From e16ef0861f576dd260487d78b57949b18795ed77 Mon Sep 17 00:00:00 2001
From: Niklas Adolfsson <niklasadolfsson1@gmail.com>
Date: Tue, 23 Jan 2024 09:55:13 +0100
Subject: [PATCH] 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
---
 Cargo.lock                                    | 163 ++++++++-----
 .../client/relay-chain-interface/Cargo.toml   |   2 +-
 .../relay-chain-rpc-interface/Cargo.toml      |   2 +-
 cumulus/parachain-template/node/Cargo.toml    |   2 +-
 .../assets/asset-hub-rococo/src/xcm_config.rs |  14 +-
 .../asset-hub-westend/src/xcm_config.rs       |  14 +-
 cumulus/polkadot-parachain/Cargo.toml         |   2 +-
 cumulus/test/service/Cargo.toml               |   2 +-
 cumulus/test/service/src/lib.rs               |   1 +
 docs/sdk/src/guides/your_first_pallet/mod.rs  |   2 +-
 polkadot/node/test/service/src/lib.rs         |   1 +
 polkadot/rpc/Cargo.toml                       |   2 +-
 polkadot/xcm/procedural/src/v2.rs             |   2 +-
 .../xcm/xcm-builder/src/matches_location.rs   |  13 +-
 prdoc/pr_1313.prdoc                           |  18 ++
 substrate/bin/minimal/node/Cargo.toml         |   2 +-
 substrate/bin/node-template/node/Cargo.toml   |   2 +-
 substrate/bin/node/cli/Cargo.toml             |   2 +-
 .../bin/node/cli/benches/block_production.rs  |   1 +
 .../bin/node/cli/benches/transaction_pool.rs  |   1 +
 substrate/bin/node/rpc/Cargo.toml             |   2 +-
 substrate/client/cli/src/commands/run_cmd.rs  |  17 +-
 substrate/client/cli/src/config.rs            |  11 +-
 substrate/client/cli/src/runner.rs            |   1 +
 .../client/consensus/babe/rpc/Cargo.toml      |   2 +-
 .../client/consensus/babe/rpc/src/lib.rs      |  44 ++--
 .../client/consensus/beefy/rpc/Cargo.toml     |   2 +-
 .../client/consensus/beefy/rpc/src/lib.rs     |  49 ++--
 .../consensus/common/src/block_import.rs      |   2 +-
 .../client/consensus/grandpa/rpc/Cargo.toml   |   2 +-
 .../client/consensus/grandpa/rpc/src/error.rs |  13 +-
 .../consensus/grandpa/rpc/src/finality.rs     |   2 +-
 .../client/consensus/grandpa/rpc/src/lib.rs   |  63 ++---
 .../consensus/grandpa/rpc/src/report.rs       |   8 +-
 .../client/consensus/manual-seal/Cargo.toml   |   2 +-
 .../client/consensus/manual-seal/src/error.rs |   9 +-
 .../client/consensus/manual-seal/src/rpc.rs   |  19 +-
 .../merkle-mountain-range/rpc/Cargo.toml      |   3 +-
 .../merkle-mountain-range/rpc/src/lib.rs      |  34 +--
 substrate/client/network/sync/src/strategy.rs |   2 +-
 substrate/client/rpc-api/Cargo.toml           |   2 +-
 substrate/client/rpc-api/src/author/error.rs  |  75 +++---
 substrate/client/rpc-api/src/author/mod.rs    |  23 +-
 substrate/client/rpc-api/src/chain/error.rs   |  14 +-
 substrate/client/rpc-api/src/chain/mod.rs     |  15 +-
 .../client/rpc-api/src/child_state/mod.rs     |  18 +-
 substrate/client/rpc-api/src/dev/error.rs     |  23 +-
 substrate/client/rpc-api/src/dev/mod.rs       |   5 +-
 substrate/client/rpc-api/src/lib.rs           |   2 +-
 substrate/client/rpc-api/src/mixnet/error.rs  |   6 +-
 substrate/client/rpc-api/src/mixnet/mod.rs    |   5 +-
 .../client/rpc-api/src/offchain/error.rs      |  12 +-
 substrate/client/rpc-api/src/offchain/mod.rs  |  11 +-
 substrate/client/rpc-api/src/policy.rs        |  24 +-
 substrate/client/rpc-api/src/state/error.rs   |  18 +-
 substrate/client/rpc-api/src/state/helpers.rs |   2 +-
 substrate/client/rpc-api/src/state/mod.rs     |  38 +--
 .../client/rpc-api/src/statement/error.rs     |  12 +-
 substrate/client/rpc-api/src/system/error.rs  |  29 ++-
 .../client/rpc-api/src/system/helpers.rs      |   8 +-
 substrate/client/rpc-api/src/system/mod.rs    |  46 ++--
 substrate/client/rpc-servers/Cargo.toml       |   4 +-
 substrate/client/rpc-servers/src/lib.rs       |  63 ++---
 .../client/rpc-servers/src/middleware.rs      |   8 +-
 substrate/client/rpc-spec-v2/Cargo.toml       |   3 +-
 .../client/rpc-spec-v2/src/archive/error.rs   |  11 +-
 .../client/rpc-spec-v2/src/archive/tests.rs   |  10 +-
 .../client/rpc-spec-v2/src/chain_head/api.rs  |  21 +-
 .../rpc-spec-v2/src/chain_head/chain_head.rs  |  99 +++-----
 .../src/chain_head/chain_head_follow.rs       |  39 ++-
 .../rpc-spec-v2/src/chain_head/error.rs       |  11 +-
 .../rpc-spec-v2/src/chain_head/tests.rs       |  67 +++--
 .../rpc-spec-v2/src/chain_spec/tests.rs       |   2 +-
 substrate/client/rpc-spec-v2/src/lib.rs       |   2 +-
 .../src/transaction/transaction.rs            |  73 +++---
 substrate/client/rpc/Cargo.toml               |   4 +-
 substrate/client/rpc/src/author/mod.rs        |  40 ++-
 substrate/client/rpc/src/author/tests.rs      |  13 +-
 substrate/client/rpc/src/chain/chain_full.rs  |  30 +--
 substrate/client/rpc/src/chain/mod.rs         |  41 ++--
 substrate/client/rpc/src/chain/tests.rs       |   4 +-
 substrate/client/rpc/src/dev/mod.rs           |   3 +-
 substrate/client/rpc/src/dev/tests.rs         |   2 +-
 substrate/client/rpc/src/lib.rs               |   1 +
 substrate/client/rpc/src/mixnet/mod.rs        |   4 +-
 substrate/client/rpc/src/offchain/mod.rs      |  10 +-
 substrate/client/rpc/src/offchain/tests.rs    |   9 +-
 substrate/client/rpc/src/state/mod.rs         | 101 ++++----
 substrate/client/rpc/src/state/state_full.rs  |  53 ++--
 substrate/client/rpc/src/state/tests.rs       | 162 +++++--------
 substrate/client/rpc/src/system/mod.rs        |  85 +++----
 substrate/client/rpc/src/system/tests.rs      |   7 +-
 substrate/client/rpc/src/testing.rs           |  45 +++-
 substrate/client/rpc/src/utils.rs             | 228 ++++++++++++++++++
 substrate/client/service/Cargo.toml           |   2 +-
 substrate/client/service/src/config.rs        |   2 +
 substrate/client/service/src/lib.rs           |  13 +-
 substrate/client/service/test/src/lib.rs      |   1 +
 substrate/client/sync-state-rpc/Cargo.toml    |   2 +-
 substrate/client/sync-state-rpc/src/lib.rs    |  21 +-
 .../src/traits/tokens/fungible/regular.rs     |   2 +-
 .../frame/transaction-payment/rpc/Cargo.toml  |   2 +-
 .../frame/transaction-payment/rpc/src/lib.rs  |  31 ++-
 .../frame/transaction-payment/src/types.rs    |   2 +-
 substrate/primitives/rpc/src/list.rs          |   2 +-
 substrate/primitives/storage/src/lib.rs       |   2 +-
 substrate/test-utils/client/Cargo.toml        |   1 +
 substrate/test-utils/client/src/lib.rs        |  10 +-
 .../frame/remote-externalities/Cargo.toml     |   2 +-
 .../frame/remote-externalities/src/lib.rs     |   3 +-
 substrate/utils/frame/rpc/client/Cargo.toml   |   2 +-
 substrate/utils/frame/rpc/client/src/lib.rs   |   5 +-
 .../rpc/state-trie-migration-rpc/Cargo.toml   |   3 +-
 .../rpc/state-trie-migration-rpc/src/lib.rs   |  12 +-
 substrate/utils/frame/rpc/support/Cargo.toml  |   4 +-
 substrate/utils/frame/rpc/system/Cargo.toml   |   2 +-
 substrate/utils/frame/rpc/system/src/lib.rs   |  29 ++-
 117 files changed, 1244 insertions(+), 1089 deletions(-)
 create mode 100644 prdoc/pr_1313.prdoc
 create mode 100644 substrate/client/rpc/src/utils.rs

diff --git a/Cargo.lock b/Cargo.lock
index 9b620d684c5..dd9fe26c813 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/cumulus/client/relay-chain-interface/Cargo.toml b/cumulus/client/relay-chain-interface/Cargo.toml
index 5100119a2e4..c003f52a918 100644
--- a/cumulus/client/relay-chain-interface/Cargo.toml
+++ b/cumulus/client/relay-chain-interface/Cargo.toml
@@ -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"
diff --git a/cumulus/client/relay-chain-rpc-interface/Cargo.toml b/cumulus/client/relay-chain-rpc-interface/Cargo.toml
index 515c8ead32a..e27dd4376ec 100644
--- a/cumulus/client/relay-chain-rpc-interface/Cargo.toml
+++ b/cumulus/client/relay-chain-rpc-interface/Cargo.toml
@@ -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"
diff --git a/cumulus/parachain-template/node/Cargo.toml b/cumulus/parachain-template/node/Cargo.toml
index c12ee72f5cb..c66c96056b9 100644
--- a/cumulus/parachain-template/node/Cargo.toml
+++ b/cumulus/parachain-template/node/Cargo.toml
@@ -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"
 
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
index 9e1affb533a..91a3337804c 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/xcm_config.rs
@@ -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)
 	}
 }
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
index 0f4ce360ee4..df9baa11554 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
@@ -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)
 	}
 }
diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml
index 0a40816fc0b..5d5aa5ec812 100644
--- a/cumulus/polkadot-parachain/Cargo.toml
+++ b/cumulus/polkadot-parachain/Cargo.toml
@@ -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
diff --git a/cumulus/test/service/Cargo.toml b/cumulus/test/service/Cargo.toml
index dcd70ca97e8..c17cdf35419 100644
--- a/cumulus/test/service/Cargo.toml
+++ b/cumulus/test/service/Cargo.toml
@@ -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"
diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs
index 37ea984ac87..2bd6fa43f76 100644
--- a/cumulus/test/service/src/lib.rs
+++ b/cumulus/test/service/src/lib.rs
@@ -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,
diff --git a/docs/sdk/src/guides/your_first_pallet/mod.rs b/docs/sdk/src/guides/your_first_pallet/mod.rs
index ea23af51ec9..29cdda36ed1 100644
--- a/docs/sdk/src/guides/your_first_pallet/mod.rs
+++ b/docs/sdk/src/guides/your_first_pallet/mod.rs
@@ -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;
 
diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs
index e9423d513bf..3ef969566b2 100644
--- a/polkadot/node/test/service/src/lib.rs
+++ b/polkadot/node/test/service/src/lib.rs
@@ -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,
diff --git a/polkadot/rpc/Cargo.toml b/polkadot/rpc/Cargo.toml
index dcb7a13f6f3..84594b14b64 100644
--- a/polkadot/rpc/Cargo.toml
+++ b/polkadot/rpc/Cargo.toml
@@ -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" }
diff --git a/polkadot/xcm/procedural/src/v2.rs b/polkadot/xcm/procedural/src/v2.rs
index 1a2f281a498..6878f7755cc 100644
--- a/polkadot/xcm/procedural/src/v2.rs
+++ b/polkadot/xcm/procedural/src/v2.rs
@@ -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();
diff --git a/polkadot/xcm/xcm-builder/src/matches_location.rs b/polkadot/xcm/xcm-builder/src/matches_location.rs
index a96df9e92de..1664c247729 100644
--- a/polkadot/xcm/xcm-builder/src/matches_location.rs
+++ b/polkadot/xcm/xcm-builder/src/matches_location.rs
@@ -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)
 	}
 }
diff --git a/prdoc/pr_1313.prdoc b/prdoc/pr_1313.prdoc
new file mode 100644
index 00000000000..0ee91da41a9
--- /dev/null
+++ b/prdoc/pr_1313.prdoc
@@ -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
diff --git a/substrate/bin/minimal/node/Cargo.toml b/substrate/bin/minimal/node/Cargo.toml
index cc00988dcfe..6be5b6d80ca 100644
--- a/substrate/bin/minimal/node/Cargo.toml
+++ b/substrate/bin/minimal/node/Cargo.toml
@@ -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" }
diff --git a/substrate/bin/node-template/node/Cargo.toml b/substrate/bin/node-template/node/Cargo.toml
index 36c2f9f8b70..da601a665e9 100644
--- a/substrate/bin/node-template/node/Cargo.toml
+++ b/substrate/bin/node-template/node/Cargo.toml
@@ -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" }
diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml
index 6803d2def43..5dfe915b789 100644
--- a/substrate/bin/node/cli/Cargo.toml
+++ b/substrate/bin/node/cli/Cargo.toml
@@ -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"
diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs
index f59a125e1c0..c17c12dfef1 100644
--- a/substrate/bin/node/cli/benches/block_production.rs
+++ b/substrate/bin/node/cli/benches/block_production.rs
@@ -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,
diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs
index 1cf71f8872f..0d0d3a072d8 100644
--- a/substrate/bin/node/cli/benches/transaction_pool.rs
+++ b/substrate/bin/node/cli/benches/transaction_pool.rs
@@ -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,
diff --git a/substrate/bin/node/rpc/Cargo.toml b/substrate/bin/node/rpc/Cargo.toml
index 66bd6e9a0a5..63a30965a16 100644
--- a/substrate/bin/node/rpc/Cargo.toml
+++ b/substrate/bin/node/rpc/Cargo.toml
@@ -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" }
diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs
index 4ac10a03cd4..f7b0fc51049 100644
--- a/substrate/client/cli/src/commands/run_cmd.rs
+++ b/substrate/client/cli/src/commands/run_cmd.rs
@@ -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.
diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs
index 170173c8c28..defcc4a8a69 100644
--- a/substrate/client/cli/src/config.rs
+++ b/substrate/client/cli/src/config.rs
@@ -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,
diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs
index 1707a76cbe7..e37c8ab0e55 100644
--- a/substrate/client/cli/src/runner.rs
+++ b/substrate/client/cli/src/runner.rs
@@ -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,
diff --git a/substrate/client/consensus/babe/rpc/Cargo.toml b/substrate/client/consensus/babe/rpc/Cargo.toml
index 753f8fbc821..87b39f1f9ca 100644
--- a/substrate/client/consensus/babe/rpc/Cargo.toml
+++ b/substrate/client/consensus/babe/rpc/Cargo.toml
@@ -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"
diff --git a/substrate/client/consensus/babe/rpc/src/lib.rs b/substrate/client/consensus/babe/rpc/src/lib.rs
index bffe026ea6e..307b1f955ba 100644
--- a/substrate/client/consensus/babe/rpc/src/lib.rs
+++ b/substrate/client/consensus/babe/rpc/src/lib.rs
@@ -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);
diff --git a/substrate/client/consensus/beefy/rpc/Cargo.toml b/substrate/client/consensus/beefy/rpc/Cargo.toml
index 198d3d81642..a13da724eae 100644
--- a/substrate/client/consensus/beefy/rpc/Cargo.toml
+++ b/substrate/client/consensus/beefy/rpc/Cargo.toml
@@ -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"] }
diff --git a/substrate/client/consensus/beefy/rpc/src/lib.rs b/substrate/client/consensus/beefy/rpc/src/lib.rs
index f5c0ff32627..03c83e92716 100644
--- a/substrate/client/consensus/beefy/rpc/src/lib.rs
+++ b/substrate/client/consensus/beefy/rpc/src/lib.rs
@@ -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();
 
diff --git a/substrate/client/consensus/common/src/block_import.rs b/substrate/client/consensus/common/src/block_import.rs
index a451692ad47..d91851aea62 100644
--- a/substrate/client/consensus/common/src/block_import.rs
+++ b/substrate/client/consensus/common/src/block_import.rs
@@ -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,
diff --git a/substrate/client/consensus/grandpa/rpc/Cargo.toml b/substrate/client/consensus/grandpa/rpc/Cargo.toml
index 9cfc9616cbc..b4c53890815 100644
--- a/substrate/client/consensus/grandpa/rpc/Cargo.toml
+++ b/substrate/client/consensus/grandpa/rpc/Cargo.toml
@@ -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"] }
diff --git a/substrate/client/consensus/grandpa/rpc/src/error.rs b/substrate/client/consensus/grandpa/rpc/src/error.rs
index 4884380cd22..795077804a4 100644
--- a/substrate/client/consensus/grandpa/rpc/src/error.rs
+++ b/substrate/client/consensus/grandpa/rpc/src/error.rs
@@ -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::<()>)
 	}
 }
 
diff --git a/substrate/client/consensus/grandpa/rpc/src/finality.rs b/substrate/client/consensus/grandpa/rpc/src/finality.rs
index f8ec01781ac..93f6c46e411 100644
--- a/substrate/client/consensus/grandpa/rpc/src/finality.rs
+++ b/substrate/client/consensus/grandpa/rpc/src/finality.rs
@@ -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.
diff --git a/substrate/client/consensus/grandpa/rpc/src/lib.rs b/substrate/client/consensus/grandpa/rpc/src/lib.rs
index a7daefaab8e..878cefacc47 100644
--- a/substrate/client/consensus/grandpa/rpc/src/lib.rs
+++ b/substrate/client/consensus/grandpa/rpc/src/lib.rs
@@ -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();
 
diff --git a/substrate/client/consensus/grandpa/rpc/src/report.rs b/substrate/client/consensus/grandpa/rpc/src/report.rs
index ae4fd76d285..b41d090afac 100644
--- a/substrate/client/consensus/grandpa/rpc/src/report.rs
+++ b/substrate/client/consensus/grandpa/rpc/src/report.rs
@@ -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,
diff --git a/substrate/client/consensus/manual-seal/Cargo.toml b/substrate/client/consensus/manual-seal/Cargo.toml
index 77cd88dfc19..be6bca10257 100644
--- a/substrate/client/consensus/manual-seal/Cargo.toml
+++ b/substrate/client/consensus/manual-seal/Cargo.toml
@@ -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" }
diff --git a/substrate/client/consensus/manual-seal/src/error.rs b/substrate/client/consensus/manual-seal/src/error.rs
index eeae1d153e8..d7bb00eff6b 100644
--- a/substrate/client/consensus/manual-seal/src/error.rs
+++ b/substrate/client/consensus/manual-seal/src/error.rs
@@ -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::<()>)
 	}
 }
diff --git a/substrate/client/consensus/manual-seal/src/rpc.rs b/substrate/client/consensus/manual-seal/src/rpc.rs
index c0b3af69bed..6018c3ab092 100644
--- a/substrate/client/consensus/manual-seal/src/rpc.rs
+++ b/substrate/client/consensus/manual-seal/src/rpc.rs
@@ -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)
 	}
 }
 
diff --git a/substrate/client/merkle-mountain-range/rpc/Cargo.toml b/substrate/client/merkle-mountain-range/rpc/Cargo.toml
index 8eb48d65f81..dc95eebf4b7 100644
--- a/substrate/client/merkle-mountain-range/rpc/Cargo.toml
+++ b/substrate/client/merkle-mountain-range/rpc/Cargo.toml
@@ -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"
diff --git a/substrate/client/merkle-mountain-range/rpc/src/lib.rs b/substrate/client/merkle-mountain-range/rpc/src/lib.rs
index 1653749ffab..b4da9848de5 100644
--- a/substrate/client/merkle-mountain-range/rpc/src/lib.rs
+++ b/substrate/client/merkle-mountain-range/rpc/src/lib.rs
@@ -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)]
diff --git a/substrate/client/network/sync/src/strategy.rs b/substrate/client/network/sync/src/strategy.rs
index ee99252fc91..dbfb4188ec3 100644
--- a/substrate/client/network/sync/src/strategy.rs
+++ b/substrate/client/network/sync/src/strategy.rs
@@ -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.
diff --git a/substrate/client/rpc-api/Cargo.toml b/substrate/client/rpc-api/Cargo.toml
index 062d25408a1..bd0fd87bb9e 100644
--- a/substrate/client/rpc-api/Cargo.toml
+++ b/substrate/client/rpc-api/Cargo.toml
@@ -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"] }
diff --git a/substrate/client/rpc-api/src/author/error.rs b/substrate/client/rpc-api/src/author/error.rs
index 648dbb295d8..0ccd82fc0b6 100644
--- a/substrate/client/rpc-api/src/author/error.rs
+++ b/substrate/client/rpc-api/src/author/error.rs
@@ -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::<()>,
+			)
+		}
 	}
 }
diff --git a/substrate/client/rpc-api/src/author/mod.rs b/substrate/client/rpc-api/src/author/mod.rs
index 55881e62152..cfc56f4130a 100644
--- a/substrate/client/rpc-api/src/author/mod.rs
+++ b/substrate/client/rpc-api/src/author/mod.rs
@@ -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.
 	///
diff --git a/substrate/client/rpc-api/src/chain/error.rs b/substrate/client/rpc-api/src/chain/error.rs
index 65219294258..ff3593557bd 100644
--- a/substrate/client/rpc-api/src/chain/error.rs
+++ b/substrate/client/rpc-api/src/chain/error.rs
@@ -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::<()>),
 		}
 	}
 }
diff --git a/substrate/client/rpc-api/src/chain/mod.rs b/substrate/client/rpc-api/src/chain/mod.rs
index f215cd978f0..e53c2bc5510 100644
--- a/substrate/client/rpc-api/src/chain/mod.rs
+++ b/substrate/client/rpc-api/src/chain/mod.rs
@@ -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(
diff --git a/substrate/client/rpc-api/src/child_state/mod.rs b/substrate/client/rpc-api/src/child_state/mod.rs
index a184677a721..70f304ea8e0 100644
--- a/substrate/client/rpc-api/src/child_state/mod.rs
+++ b/substrate/client/rpc-api/src/child_state/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-api/src/dev/error.rs b/substrate/client/rpc-api/src/dev/error.rs
index 8e4ddb55e35..f70e368b873 100644
--- a/substrate/client/rpc-api/src/dev/error.rs
+++ b/substrate/client/rpc-api/src/dev/error.rs
@@ -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()
 	}
 }
diff --git a/substrate/client/rpc-api/src/dev/mod.rs b/substrate/client/rpc-api/src/dev/mod.rs
index bc7216199dd..5bee6df73ba 100644
--- a/substrate/client/rpc-api/src/dev/mod.rs
+++ b/substrate/client/rpc-api/src/dev/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-api/src/lib.rs b/substrate/client/rpc-api/src/lib.rs
index 32120d37902..451ebdf7fc0 100644
--- a/substrate/client/rpc-api/src/lib.rs
+++ b/substrate/client/rpc-api/src/lib.rs
@@ -25,7 +25,7 @@
 mod error;
 mod policy;
 
-pub use policy::DenyUnsafe;
+pub use policy::{DenyUnsafe, UnsafeRpcError};
 
 pub mod author;
 pub mod chain;
diff --git a/substrate/client/rpc-api/src/mixnet/error.rs b/substrate/client/rpc-api/src/mixnet/error.rs
index 0dde5f32e61..22352256f91 100644
--- a/substrate/client/rpc-api/src/mixnet/error.rs
+++ b/substrate/client/rpc-api/src/mixnet/error.rs
@@ -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::<()>)
 	}
 }
diff --git a/substrate/client/rpc-api/src/mixnet/mod.rs b/substrate/client/rpc-api/src/mixnet/mod.rs
index bc478cf3bf3..8bd3362ca61 100644
--- a/substrate/client/rpc-api/src/mixnet/mod.rs
+++ b/substrate/client/rpc-api/src/mixnet/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-api/src/offchain/error.rs b/substrate/client/rpc-api/src/offchain/error.rs
index 679e1000897..ae5771981ea 100644
--- a/substrate/client/rpc-api/src/offchain/error.rs
+++ b/substrate/client/rpc-api/src/offchain/error.rs
@@ -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(),
 		}
 	}
diff --git a/substrate/client/rpc-api/src/offchain/mod.rs b/substrate/client/rpc-api/src/offchain/mod.rs
index cd42d6db350..469e22d2b3f 100644
--- a/substrate/client/rpc-api/src/offchain/mod.rs
+++ b/substrate/client/rpc-api/src/offchain/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-api/src/policy.rs b/substrate/client/rpc-api/src/policy.rs
index 799898fb7cf..c0847de89d2 100644
--- a/substrate/client/rpc-api/src/policy.rs
+++ b/substrate/client/rpc-api/src/policy.rs
@@ -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::<()>)
 	}
 }
diff --git a/substrate/client/rpc-api/src/state/error.rs b/substrate/client/rpc-api/src/state/error.rs
index 9857784e354..f2396c63815 100644
--- a/substrate/client/rpc-api/src/state/error.rs
+++ b/substrate/client/rpc-api/src/state/error.rs
@@ -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::<()>),
 		}
 	}
 }
diff --git a/substrate/client/rpc-api/src/state/helpers.rs b/substrate/client/rpc-api/src/state/helpers.rs
index de20ee6f1bd..58b79ab64ea 100644
--- a/substrate/client/rpc-api/src/state/helpers.rs
+++ b/substrate/client/rpc-api/src/state/helpers.rs
@@ -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
diff --git a/substrate/client/rpc-api/src/state/mod.rs b/substrate/client/rpc-api/src/state/mod.rs
index 4acc64def2b..e38e383c4c1 100644
--- a/substrate/client/rpc-api/src/state/mod.rs
+++ b/substrate/client/rpc-api/src/state/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-api/src/statement/error.rs b/substrate/client/rpc-api/src/statement/error.rs
index 8438cc3ec9e..f8041864fac 100644
--- a/substrate/client/rpc-api/src/statement/error.rs
+++ b/substrate/client/rpc-api/src/statement/error.rs
@@ -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(),
 		}
 	}
diff --git a/substrate/client/rpc-api/src/system/error.rs b/substrate/client/rpc-api/src/system/error.rs
index 713ade9210d..1e826a75ae6 100644
--- a/substrate/client/rpc-api/src/system/error.rs
+++ b/substrate/client/rpc-api/src/system/error.rs
@@ -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()
 	}
 }
diff --git a/substrate/client/rpc-api/src/system/helpers.rs b/substrate/client/rpc-api/src/system/helpers.rs
index ad56dc38500..ed642e0679e 100644
--- a/substrate/client/rpc-api/src/system/helpers.rs
+++ b/substrate/client/rpc-api/src/system/helpers.rs
@@ -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.
diff --git a/substrate/client/rpc-api/src/system/mod.rs b/substrate/client/rpc-api/src/system/mod.rs
index bf2e92bc27a..c38fa8f3d81 100644
--- a/substrate/client/rpc-api/src/system/mod.rs
+++ b/substrate/client/rpc-api/src/system/mod.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml
index 60d999863ca..5b5610837ae 100644
--- a/substrate/client/rpc-servers/Cargo.toml
+++ b/substrate/client/rpc-servers/Cargo.toml
@@ -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"
diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs
index dc625c3d6c4..5d8da190f62 100644
--- a/substrate/client/rpc-servers/src/lib.rs
+++ b/substrate/client/rpc-servers/src/lib.rs
@@ -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");
 
diff --git a/substrate/client/rpc-servers/src/middleware.rs b/substrate/client/rpc-servers/src/middleware.rs
index c3e17c7852f..fabb64eafa7 100644
--- a/substrate/client/rpc-servers/src/middleware.rs
+++ b/substrate/client/rpc-servers/src/middleware.rs
@@ -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();
 	}
diff --git a/substrate/client/rpc-spec-v2/Cargo.toml b/substrate/client/rpc-spec-v2/Cargo.toml
index 17412f883ca..e665ddd4f6b 100644
--- a/substrate/client/rpc-spec-v2/Cargo.toml
+++ b/substrate/client/rpc-spec-v2/Cargo.toml
@@ -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"
diff --git a/substrate/client/rpc-spec-v2/src/archive/error.rs b/substrate/client/rpc-spec-v2/src/archive/error.rs
index b858212399c..d631c3fb8e8 100644
--- a/substrate/client/rpc-spec-v2/src/archive/error.rs
+++ b/substrate/client/rpc-spec-v2/src/archive/error.rs
@@ -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()
-	}
-}
diff --git a/substrate/client/rpc-spec-v2/src/archive/tests.rs b/substrate/client/rpc-spec-v2/src/archive/tests.rs
index 45da8e588e6..108395eb91a 100644
--- a/substrate/client/rpc-spec-v2/src/archive/tests.rs
+++ b/substrate/client/rpc-spec-v2/src/archive/tests.rs
@@ -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
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/api.rs b/substrate/client/rpc-spec-v2/src/chain_head/api.rs
index 3d6091b91bd..7c3b8d81c82 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/api.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/api.rs
@@ -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>;
 }
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs
index 6e4d6ade965..0e207addcae 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head.rs
@@ -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(())
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
index b981e69f2e4..e94374aebd9 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs
@@ -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
 			},
 		};
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/error.rs b/substrate/client/rpc-spec-v2/src/chain_head/error.rs
index a9b7d7f96e4..bf290edb29e 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/error.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/error.rs
@@ -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()
-	}
-}
diff --git a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
index 4859793a8e2..955a361e3ea 100644
--- a/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_head/tests.rs
@@ -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();
 
diff --git a/substrate/client/rpc-spec-v2/src/chain_spec/tests.rs b/substrate/client/rpc-spec-v2/src/chain_spec/tests.rs
index 9326fd5f3b7..3abd9a548a1 100644
--- a/substrate/client/rpc-spec-v2/src/chain_spec/tests.rs
+++ b/substrate/client/rpc-spec-v2/src/chain_spec/tests.rs
@@ -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";
diff --git a/substrate/client/rpc-spec-v2/src/lib.rs b/substrate/client/rpc-spec-v2/src/lib.rs
index 23ed422cff1..fa822fd446b 100644
--- a/substrate/client/rpc-spec-v2/src/lib.rs
+++ b/substrate/client/rpc-spec-v2/src/lib.rs
@@ -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.
diff --git a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs
index fe16310aeff..b2cfa36c9c9 100644
--- a/substrate/client/rpc-spec-v2/src/transaction/transaction.rs
+++ b/substrate/client/rpc-spec-v2/src/transaction/transaction.rs
@@ -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(())
-			},
-		};
+	fn submit_and_watch(&self, pending: PendingSubscriptionSink, xt: Bytes) {
+		let client = self.client.clone();
+		let pool = self.pool.clone();
 
-		let best_block_hash = self.client.info().best_hash;
+		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 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)))
-			});
+			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)))
+				});
 
-		let fut = async move {
 			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);
 	}
 }
 
diff --git a/substrate/client/rpc/Cargo.toml b/substrate/client/rpc/Cargo.toml
index 2b4366b7cfc..5fc4d9becc2 100644
--- a/substrate/client/rpc/Cargo.toml
+++ b/substrate/client/rpc/Cargo.toml
@@ -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 = []
diff --git a/substrate/client/rpc/src/author/mod.rs b/substrate/client/rpc/src/author/mod.rs
index 55d0a504aa6..975f66406a6 100644
--- a/substrate/client/rpc/src/author/mod.rs
+++ b/substrate/client/rpc/src/author/mod.rs
@@ -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);
 	}
 }
diff --git a/substrate/client/rpc/src/author/tests.rs b/substrate/client/rpc/src/author/tests.rs
index 3e3db4b03b3..471016a015d 100644
--- a/substrate/client/rpc/src/author/tests.rs
+++ b/substrate/client/rpc/src/author/tests.rs
@@ -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")
 	);
 }
 
diff --git a/substrate/client/rpc/src/chain/chain_full.rs b/substrate/client/rpc/src/chain/chain_full.rs
index a88291eb7bd..515c0f62c8a 100644
--- a/substrate/client/rpc/src/chain/chain_full.rs
+++ b/substrate/client/rpc/src/chain/chain_full.rs
@@ -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));
 }
diff --git a/substrate/client/rpc/src/chain/mod.rs b/substrate/client/rpc/src/chain/mod.rs
index 2b5994f217d..1c74db8642e 100644
--- a/substrate/client/rpc/src/chain/mod.rs
+++ b/substrate/client/rpc/src/chain/mod.rs
@@ -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)
 	}
 }
 
diff --git a/substrate/client/rpc/src/chain/tests.rs b/substrate/client/rpc/src/chain/tests.rs
index cff5bf39811..afb81f709f7 100644
--- a/substrate/client/rpc/src/chain/tests.rs
+++ b/substrate/client/rpc/src/chain/tests.rs
@@ -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)
diff --git a/substrate/client/rpc/src/dev/mod.rs b/substrate/client/rpc/src/dev/mod.rs
index 4d2e8618d55..424ef72b694 100644
--- a/substrate/client/rpc/src/dev/mod.rs
+++ b/substrate/client/rpc/src/dev/mod.rs
@@ -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 = {
diff --git a/substrate/client/rpc/src/dev/tests.rs b/substrate/client/rpc/src/dev/tests.rs
index 448d16274f2..5eb4897056c 100644
--- a/substrate/client/rpc/src/dev/tests.rs
+++ b/substrate/client/rpc/src/dev/tests.rs
@@ -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,
diff --git a/substrate/client/rpc/src/lib.rs b/substrate/client/rpc/src/lib.rs
index 94fdb2d734f..b40d0341e32 100644
--- a/substrate/client/rpc/src/lib.rs
+++ b/substrate/client/rpc/src/lib.rs
@@ -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;
diff --git a/substrate/client/rpc/src/mixnet/mod.rs b/substrate/client/rpc/src/mixnet/mod.rs
index 3f3d9c5aa45..64875c6fe76 100644
--- a/substrate/client/rpc/src/mixnet/mod.rs
+++ b/substrate/client/rpc/src/mixnet/mod.rs
@@ -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;
diff --git a/substrate/client/rpc/src/offchain/mod.rs b/substrate/client/rpc/src/offchain/mod.rs
index de711accdcc..66167386605 100644
--- a/substrate/client/rpc/src/offchain/mod.rs
+++ b/substrate/client/rpc/src/offchain/mod.rs
@@ -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))
diff --git a/substrate/client/rpc/src/offchain/tests.rs b/substrate/client/rpc/src/offchain/tests.rs
index 62a846d0887..7758fac7fab 100644
--- a/substrate/client/rpc/src/offchain/tests.rs
+++ b/substrate/client/rpc/src/offchain/tests.rs
@@ -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")
 		}
 	);
 }
diff --git a/substrate/client/rpc/src/state/mod.rs b/substrate/client/rpc/src/state/mod.rs
index 057661d6ec7..c9a41e25eda 100644
--- a/substrate/client/rpc/src/state/mod.rs
+++ b/substrate/client/rpc/src/state/mod.rs
@@ -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)
diff --git a/substrate/client/rpc/src/state/state_full.rs b/substrate/client/rpc/src/state/state_full.rs
index 9604d9165f9..bda678c1b45 100644
--- a/substrate/client/rpc/src/state/state_full.rs
+++ b/substrate/client/rpc/src/state/state_full.rs
@@ -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(
diff --git a/substrate/client/rpc/src/state/tests.rs b/substrate/client/rpc/src/state/tests.rs
index 663d511d43e..25a34faed9a 100644
--- a/substrate/client/rpc/src/state/tests.rs
+++ b/substrate/client/rpc/src/state/tests.rs
@@ -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());
 }
diff --git a/substrate/client/rpc/src/system/mod.rs b/substrate/client/rpc/src/system/mod.rs
index 0da4f8d0e21..8c7510c64cb 100644
--- a/substrate/client/rpc/src/system/mod.rs
+++ b/substrate/client/rpc/src/system/mod.rs
@@ -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))
 	}
 }
diff --git a/substrate/client/rpc/src/system/tests.rs b/substrate/client/rpc/src/system/tests.rs
index b6bfec7736b..21d13ccfafa 100644
--- a/substrate/client/rpc/src/system/tests.rs
+++ b/substrate/client/rpc/src/system/tests.rs
@@ -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]
diff --git a/substrate/client/rpc/src/testing.rs b/substrate/client/rpc/src/testing.rs
index 6b6e1906d44..e04f80a7b9e 100644
--- a/substrate/client/rpc/src/testing.rs
+++ b/substrate/client/rpc/src/testing.rs
@@ -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
diff --git a/substrate/client/rpc/src/utils.rs b/substrate/client/rpc/src/utils.rs
new file mode 100644
index 00000000000..b5ae4a2b6bc
--- /dev/null
+++ b/substrate/client/rpc/src/utils.rs
@@ -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());
+	}
+}
diff --git a/substrate/client/service/Cargo.toml b/substrate/client/service/Cargo.toml
index 576a8aac8e4..b011d3bdb94 100644
--- a/substrate/client/service/Cargo.toml
+++ b/substrate/client/service/Cargo.toml
@@ -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"
diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs
index c2ce67df7ea..3e68f5b58de 100644
--- a/substrate/client/service/src/config.rs
+++ b/substrate/client/service/src/config.rs
@@ -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.
diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs
index bec1044daab..875cb9ca79e 100644
--- a/substrate/client/service/src/lib.rs
+++ b/substrate/client/service/src/lib.rs
@@ -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,
diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs
index 456df73459a..9b88300bf53 100644
--- a/substrate/client/service/test/src/lib.rs
+++ b/substrate/client/service/test/src/lib.rs
@@ -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,
diff --git a/substrate/client/sync-state-rpc/Cargo.toml b/substrate/client/sync-state-rpc/Cargo.toml
index b3de9585ab4..6073c2ca0c0 100644
--- a/substrate/client/sync-state-rpc/Cargo.toml
+++ b/substrate/client/sync-state-rpc/Cargo.toml
@@ -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"
diff --git a/substrate/client/sync-state-rpc/src/lib.rs b/substrate/client/sync-state-rpc/src/lib.rs
index dda8a7edfa9..cb737cc6c52 100644
--- a/substrate/client/sync-state-rpc/src/lib.rs
+++ b/substrate/client/sync-state-rpc/src/lib.rs
@@ -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(&current_sync_state)?;
+		let val = serde_json::to_value(&current_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()))
 	}
 }
diff --git a/substrate/frame/support/src/traits/tokens/fungible/regular.rs b/substrate/frame/support/src/traits/tokens/fungible/regular.rs
index f15c3418d0a..0157b08bd13 100644
--- a/substrate/frame/support/src/traits/tokens/fungible/regular.rs
+++ b/substrate/frame/support/src/traits/tokens/fungible/regular.rs
@@ -321,7 +321,7 @@ where
 		let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?;
 		Self::can_deposit(dest, amount, Extant).into_result()?;
 		if source == dest {
-			return Ok(amount);
+			return Ok(amount)
 		}
 
 		Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?;
diff --git a/substrate/frame/transaction-payment/rpc/Cargo.toml b/substrate/frame/transaction-payment/rpc/Cargo.toml
index 5a574a944d8..162c4ce77f7 100644
--- a/substrate/frame/transaction-payment/rpc/Cargo.toml
+++ b/substrate/frame/transaction-payment/rpc/Cargo.toml
@@ -17,7 +17,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"] }
 pallet-transaction-payment-rpc-runtime-api = { path = "runtime-api" }
 sp-api = { path = "../../../primitives/api" }
 sp-blockchain = { path = "../../../primitives/blockchain" }
diff --git a/substrate/frame/transaction-payment/rpc/src/lib.rs b/substrate/frame/transaction-payment/rpc/src/lib.rs
index 7f8ed4b8026..f5323cf852e 100644
--- a/substrate/frame/transaction-payment/rpc/src/lib.rs
+++ b/substrate/frame/transaction-payment/rpc/src/lib.rs
@@ -21,9 +21,12 @@ use std::{convert::TryInto, sync::Arc};
 
 use codec::{Codec, Decode};
 use jsonrpsee::{
-	core::{Error as JsonRpseeError, RpcResult},
+	core::RpcResult,
 	proc_macros::rpc,
-	types::error::{CallError, ErrorCode, ErrorObject},
+	types::{
+		error::{ErrorCode, ErrorObject},
+		ErrorObjectOwned,
+	},
 };
 use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo};
 use sp_api::ProvideRuntimeApi;
@@ -100,19 +103,15 @@ where
 		let encoded_len = encoded_xt.len() as u32;
 
 		let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| {
-			CallError::Custom(ErrorObject::owned(
+			ErrorObject::owned(
 				Error::DecodeError.into(),
 				"Unable to query dispatch info.",
 				Some(format!("{:?}", e)),
-			))
+			)
 		})?;
 
-		fn map_err(error: impl ToString, desc: &'static str) -> CallError {
-			CallError::Custom(ErrorObject::owned(
-				Error::RuntimeError.into(),
-				desc,
-				Some(error.to_string()),
-			))
+		fn map_err(error: impl ToString, desc: &'static str) -> ErrorObjectOwned {
+			ErrorObject::owned(Error::RuntimeError.into(), desc, Some(error.to_string()))
 		}
 
 		let res = api
@@ -137,27 +136,27 @@ where
 		let encoded_len = encoded_xt.len() as u32;
 
 		let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| {
-			CallError::Custom(ErrorObject::owned(
+			ErrorObject::owned(
 				Error::DecodeError.into(),
 				"Unable to query fee details.",
 				Some(format!("{:?}", e)),
-			))
+			)
 		})?;
 		let fee_details = api.query_fee_details(at_hash, uxt, encoded_len).map_err(|e| {
-			CallError::Custom(ErrorObject::owned(
+			ErrorObject::owned(
 				Error::RuntimeError.into(),
 				"Unable to query fee details.",
 				Some(e.to_string()),
-			))
+			)
 		})?;
 
 		let try_into_rpc_balance = |value: Balance| {
 			value.try_into().map_err(|_| {
-				JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
+				ErrorObject::owned(
 					ErrorCode::InvalidParams.code(),
 					format!("{} doesn't fit in NumberOrHex representation", value),
 					None::<()>,
-				)))
+				)
 			})
 		};
 
diff --git a/substrate/frame/transaction-payment/src/types.rs b/substrate/frame/transaction-payment/src/types.rs
index cbe85309b85..25cecc58a63 100644
--- a/substrate/frame/transaction-payment/src/types.rs
+++ b/substrate/frame/transaction-payment/src/types.rs
@@ -94,7 +94,7 @@ impl<Balance: AtLeast32BitUnsigned + Copy> FeeDetails<Balance> {
 /// Information related to a dispatchable's class, weight, and fee that can be queried from the
 /// runtime.
 #[derive(Eq, PartialEq, Encode, Decode, Default, TypeInfo)]
-#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
+#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize, Clone))]
 #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
 #[cfg_attr(
 	feature = "std",
diff --git a/substrate/primitives/rpc/src/list.rs b/substrate/primitives/rpc/src/list.rs
index 860e5161b97..f1b06f3d223 100644
--- a/substrate/primitives/rpc/src/list.rs
+++ b/substrate/primitives/rpc/src/list.rs
@@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize};
 ///
 /// Also it's nice to be able to maintain backward compatibility for methods that
 /// were initially taking a value and now we want to expand them to take a list.
-#[derive(Serialize, Deserialize, Debug, PartialEq)]
+#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
 #[serde(untagged)]
 pub enum ListOrValue<T> {
 	/// A list of values of given type.
diff --git a/substrate/primitives/storage/src/lib.rs b/substrate/primitives/storage/src/lib.rs
index 3528d0558f5..79c090cabf8 100644
--- a/substrate/primitives/storage/src/lib.rs
+++ b/substrate/primitives/storage/src/lib.rs
@@ -178,7 +178,7 @@ pub struct Storage {
 
 /// Storage change set
 #[derive(RuntimeDebug)]
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, PartialEq, Eq))]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, PartialEq, Eq, Clone))]
 #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
 pub struct StorageChangeSet<Hash> {
 	/// Block hash
diff --git a/substrate/test-utils/client/Cargo.toml b/substrate/test-utils/client/Cargo.toml
index 79071e19e28..14f55f64546 100644
--- a/substrate/test-utils/client/Cargo.toml
+++ b/substrate/test-utils/client/Cargo.toml
@@ -39,3 +39,4 @@ sp-keyring = { path = "../../primitives/keyring" }
 sp-keystore = { path = "../../primitives/keystore" }
 sp-runtime = { path = "../../primitives/runtime" }
 sp-state-machine = { path = "../../primitives/state-machine" }
+tokio = { version = "1.22.0", features = ["sync"] }
diff --git a/substrate/test-utils/client/src/lib.rs b/substrate/test-utils/client/src/lib.rs
index f383f7c3dc3..e3f06e27563 100644
--- a/substrate/test-utils/client/src/lib.rs
+++ b/substrate/test-utils/client/src/lib.rs
@@ -284,7 +284,7 @@ pub struct RpcTransactionOutput {
 	/// The output string of the transaction if any.
 	pub result: String,
 	/// An async receiver if data will be returned via a callback.
-	pub receiver: futures::channel::mpsc::UnboundedReceiver<String>,
+	pub receiver: tokio::sync::mpsc::Receiver<String>,
 }
 
 impl std::fmt::Debug for RpcTransactionOutput {
@@ -344,7 +344,7 @@ impl RpcHandlersExt for RpcHandlers {
 
 pub(crate) fn parse_rpc_result(
 	result: String,
-	receiver: futures::channel::mpsc::UnboundedReceiver<String>,
+	receiver: tokio::sync::mpsc::Receiver<String>,
 ) -> Result<RpcTransactionOutput, RpcTransactionError> {
 	let json: serde_json::Value =
 		serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed");
@@ -398,7 +398,7 @@ where
 mod tests {
 	#[test]
 	fn parses_error_properly() {
-		let (_, rx) = futures::channel::mpsc::unbounded();
+		let (_, rx) = tokio::sync::mpsc::channel(1);
 		assert!(super::parse_rpc_result(
 			r#"{
 				"jsonrpc": "2.0",
@@ -410,7 +410,7 @@ mod tests {
 		)
 		.is_ok());
 
-		let (_, rx) = futures::channel::mpsc::unbounded();
+		let (_, rx) = tokio::sync::mpsc::channel(1);
 		let error = super::parse_rpc_result(
 			r#"{
 				"jsonrpc": "2.0",
@@ -428,7 +428,7 @@ mod tests {
 		assert_eq!(error.message, "Method not found");
 		assert!(error.data.is_none());
 
-		let (_, rx) = futures::channel::mpsc::unbounded();
+		let (_, rx) = tokio::sync::mpsc::channel(1);
 		let error = super::parse_rpc_result(
 			r#"{
 				"jsonrpc": "2.0",
diff --git a/substrate/utils/frame/remote-externalities/Cargo.toml b/substrate/utils/frame/remote-externalities/Cargo.toml
index ae5d0097d5e..f8e69511228 100644
--- a/substrate/utils/frame/remote-externalities/Cargo.toml
+++ b/substrate/utils/frame/remote-externalities/Cargo.toml
@@ -15,7 +15,7 @@ workspace = true
 targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
-jsonrpsee = { version = "0.16.2", features = ["http-client"] }
+jsonrpsee = { version = "0.20.3", features = ["http-client"] }
 codec = { package = "parity-scale-codec", version = "3.6.1" }
 log = "0.4.17"
 serde = "1.0.195"
diff --git a/substrate/utils/frame/remote-externalities/src/lib.rs b/substrate/utils/frame/remote-externalities/src/lib.rs
index ce6b25da80e..47c0508485c 100644
--- a/substrate/utils/frame/remote-externalities/src/lib.rs
+++ b/substrate/utils/frame/remote-externalities/src/lib.rs
@@ -189,7 +189,8 @@ impl Transport {
 				uri.clone()
 			};
 			let http_client = HttpClientBuilder::default()
-				.max_request_body_size(u32::MAX)
+				.max_request_size(u32::MAX)
+				.max_response_size(u32::MAX)
 				.request_timeout(std::time::Duration::from_secs(60 * 5))
 				.build(uri)
 				.map_err(|e| {
diff --git a/substrate/utils/frame/rpc/client/Cargo.toml b/substrate/utils/frame/rpc/client/Cargo.toml
index 1e8a298726e..6477339f348 100644
--- a/substrate/utils/frame/rpc/client/Cargo.toml
+++ b/substrate/utils/frame/rpc/client/Cargo.toml
@@ -15,7 +15,7 @@ workspace = true
 targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
-jsonrpsee = { version = "0.16.2", features = ["ws-client"] }
+jsonrpsee = { version = "0.20.3", features = ["ws-client"] }
 sc-rpc-api = { path = "../../../../client/rpc-api" }
 async-trait = "0.1.74"
 serde = "1"
diff --git a/substrate/utils/frame/rpc/client/src/lib.rs b/substrate/utils/frame/rpc/client/src/lib.rs
index a6a667bef56..9349ee2d357 100644
--- a/substrate/utils/frame/rpc/client/src/lib.rs
+++ b/substrate/utils/frame/rpc/client/src/lib.rs
@@ -61,10 +61,11 @@ pub use sc_rpc_api::{
 /// Create a new `WebSocket` connection with shared settings.
 pub async fn ws_client(uri: impl AsRef<str>) -> Result<WsClient, String> {
 	WsClientBuilder::default()
-		.max_request_body_size(u32::MAX)
+		.max_request_size(u32::MAX)
+		.max_response_size(u32::MAX)
 		.request_timeout(std::time::Duration::from_secs(60 * 10))
 		.connection_timeout(std::time::Duration::from_secs(60))
-		.max_notifs_per_subscription(1024)
+		.max_buffer_capacity_per_subscription(1024)
 		.build(uri)
 		.await
 		.map_err(|e| format!("`WsClientBuilder` failed to build: {:?}", e))
diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml
index 6cd99e5a6fe..86336336f0f 100644
--- a/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml
+++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml
@@ -23,8 +23,7 @@ sp-core = { path = "../../../../primitives/core" }
 sp-state-machine = { path = "../../../../primitives/state-machine" }
 sp-trie = { path = "../../../../primitives/trie" }
 trie-db = "0.28.0"
-
-jsonrpsee = { version = "0.16.2", features = ["client-core", "macros", "server"] }
+jsonrpsee = { version = "0.20.3", features = ["client-core", "macros", "server"] }
 
 # Substrate Dependencies
 sc-client-api = { path = "../../../../client/api" }
diff --git a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs
index d1175fdea90..f45258ea593 100644
--- a/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs
+++ b/substrate/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs
@@ -18,9 +18,9 @@
 //! Rpc for state migration.
 
 use jsonrpsee::{
-	core::{Error as JsonRpseeError, RpcResult},
+	core::RpcResult,
 	proc_macros::rpc,
-	types::error::{CallError, ErrorCode, ErrorObject},
+	types::error::{ErrorCode, ErrorObject, ErrorObjectOwned},
 };
 use sc_rpc_api::DenyUnsafe;
 use serde::{Deserialize, Serialize};
@@ -112,7 +112,7 @@ where
 }
 
 /// Current state migration status.
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Clone)]
 #[serde(rename_all = "camelCase")]
 #[serde(deny_unknown_fields)]
 pub struct MigrationStatusResult {
@@ -168,10 +168,10 @@ where
 	}
 }
 
-fn error_into_rpc_err(err: impl std::fmt::Display) -> JsonRpseeError {
-	JsonRpseeError::Call(CallError::Custom(ErrorObject::owned(
+fn error_into_rpc_err(err: impl std::fmt::Display) -> ErrorObjectOwned {
+	ErrorObject::owned(
 		ErrorCode::InternalError.code(),
 		"Error while checking migration state",
 		Some(err.to_string()),
-	)))
+	)
 }
diff --git a/substrate/utils/frame/rpc/support/Cargo.toml b/substrate/utils/frame/rpc/support/Cargo.toml
index 1cc6d8e98b3..b39b6df05e5 100644
--- a/substrate/utils/frame/rpc/support/Cargo.toml
+++ b/substrate/utils/frame/rpc/support/Cargo.toml
@@ -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 = ["jsonrpsee-types"] }
+jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types"] }
 serde = "1"
 frame-support = { path = "../../../../frame/support" }
 sc-rpc-api = { path = "../../../../client/rpc-api" }
@@ -24,7 +24,7 @@ sp-storage = { path = "../../../../primitives/storage" }
 
 [dev-dependencies]
 scale-info = "2.10.0"
-jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-types", "ws-client"] }
+jsonrpsee = { version = "0.20.3", features = ["jsonrpsee-types", "ws-client"] }
 tokio = "1.22.0"
 sp-core = { path = "../../../../primitives/core" }
 sp-runtime = { path = "../../../../primitives/runtime" }
diff --git a/substrate/utils/frame/rpc/system/Cargo.toml b/substrate/utils/frame/rpc/system/Cargo.toml
index 84c3265c93d..a7ca7455c55 100644
--- a/substrate/utils/frame/rpc/system/Cargo.toml
+++ b/substrate/utils/frame/rpc/system/Cargo.toml
@@ -17,7 +17,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"] }
 futures = "0.3.21"
 log = "0.4.17"
 frame-system-rpc-runtime-api = { path = "../../../../frame/system/rpc/runtime-api" }
diff --git a/substrate/utils/frame/rpc/system/src/lib.rs b/substrate/utils/frame/rpc/system/src/lib.rs
index f467a879890..bb0592599b2 100644
--- a/substrate/utils/frame/rpc/system/src/lib.rs
+++ b/substrate/utils/frame/rpc/system/src/lib.rs
@@ -23,7 +23,7 @@ use codec::{self, Codec, Decode, Encode};
 use jsonrpsee::{
 	core::{async_trait, RpcResult},
 	proc_macros::rpc,
-	types::error::{CallError, ErrorObject},
+	types::error::ErrorObject,
 };
 
 use sc_rpc_api::DenyUnsafe;
@@ -103,11 +103,11 @@ where
 		let best = self.client.info().best_hash;
 
 		let nonce = api.account_nonce(best, account.clone()).map_err(|e| {
-			CallError::Custom(ErrorObject::owned(
+			ErrorObject::owned(
 				Error::RuntimeError.into(),
 				"Unable to query nonce.",
 				Some(e.to_string()),
-			))
+			)
 		})?;
 		Ok(adjust_nonce(&*self.pool, account, nonce))
 	}
@@ -125,28 +125,28 @@ where
 
 		let uxt: <Block as traits::Block>::Extrinsic =
 			Decode::decode(&mut &*extrinsic).map_err(|e| {
-				CallError::Custom(ErrorObject::owned(
+				ErrorObject::owned(
 					Error::DecodeError.into(),
 					"Unable to dry run extrinsic",
 					Some(e.to_string()),
-				))
+				)
 			})?;
 
 		let api_version = api
 			.api_version::<dyn BlockBuilder<Block>>(best_hash)
 			.map_err(|e| {
-				CallError::Custom(ErrorObject::owned(
+				ErrorObject::owned(
 					Error::RuntimeError.into(),
 					"Unable to dry run extrinsic.",
 					Some(e.to_string()),
-				))
+				)
 			})?
 			.ok_or_else(|| {
-				CallError::Custom(ErrorObject::owned(
+				ErrorObject::owned(
 					Error::RuntimeError.into(),
 					"Unable to dry run extrinsic.",
 					Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", best_hash)),
-				))
+				)
 			})?;
 
 		let result = if api_version < 6 {
@@ -154,19 +154,19 @@ where
 			api.apply_extrinsic_before_version_6(best_hash, uxt)
 				.map(legacy::byte_sized_error::convert_to_latest)
 				.map_err(|e| {
-					CallError::Custom(ErrorObject::owned(
+					ErrorObject::owned(
 						Error::RuntimeError.into(),
 						"Unable to dry run extrinsic.",
 						Some(e.to_string()),
-					))
+					)
 				})?
 		} else {
 			api.apply_extrinsic(best_hash, uxt).map_err(|e| {
-				CallError::Custom(ErrorObject::owned(
+				ErrorObject::owned(
 					Error::RuntimeError.into(),
 					"Unable to dry run extrinsic.",
 					Some(e.to_string()),
-				))
+				)
 			})?
 		};
 
@@ -216,7 +216,6 @@ mod tests {
 
 	use assert_matches::assert_matches;
 	use futures::executor::block_on;
-	use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError};
 	use sc_transaction_pool::BasicPool;
 	use sp_runtime::{
 		transaction_validity::{InvalidTransaction, TransactionValidityError},
@@ -274,7 +273,7 @@ mod tests {
 
 		// when
 		let res = accounts.dry_run(vec![].into(), None).await;
-		assert_matches!(res, Err(JsonRpseeError::Call(CallError::Custom(e))) => {
+		assert_matches!(res, Err(e) => {
 			assert!(e.message().contains("RPC call is unsafe to be called externally"));
 		});
 	}
-- 
GitLab