Repository: nervosnetwork/ckb (language: rust)

Issue:
currently does not support client body read timeout
## Bug Report

the client body reading timeout is not implemented. As a result, the connection may be fully occupied by slow attacks from the client and memory may be exhausted.

### Current Behavior
if the access node does not go through the nginx proxy, it will cause the following request to be stuck and the connection will be occupied indefinitely.
```
 curl -vv 'http://18.162.180.86:8114' -X POST -H 'content-type: application/json' -H 'accept: */*' -H 'host: testnet.ckbapp.dev' -H 'content-length: 254' -d '{"id":0,"jsonrpc":"2.0","method":"get_cells","params":[{"filter":null,"script":{"args":"0x6962632d636b622d3935","code_hash":"0x529dd7087986a7591e0fb860a49111915b8ba68b6c97656bbaafa94b223dcc88","hash_type":"type"},"script_type":"type"},"asc","0x1",null]}' 
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 18.162.180.86:8114...
* TCP_NODELAY set
* Connected to 18.162.180.86 (18.162.180.86) port 8114 (#0)
> POST / HTTP/1.1
> Host: testnet.ckbapp.dev
> User-Agent: curl/7.68.0
> content-type: application/json
> accept: */*
> content-length: 254
> 
* upload completely sent off: 253 out of 253 bytes
```

### Expected Behavior

The request will not be stuck indefinitely.

### Environment

- **CKB version**: 108
- **Chain**: testnet
- **Operating system**: Ubuntu 20.04
- **Arch**: x64 
- **Installation**: GitHub Release

### Additional context/Screenshots

This project relies on a library called jsonrpc (https://github.com/paritytech/jsonrpc), but it has been abandoned and does not implement the `client body read timeout` feature. Therefore, it is recommended to replace this library with jsonrpsee (https://github.com/paritytech/jsonrpsee), which implements the `client body read timeout` feature.

similar problem: 
- https://github.com/hyperium/hyper/issues/2864


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 587c3196c2..7a0b95e3c8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,7 +24,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
 dependencies = [
  "crypto-common",
- "generic-array 0.14.7",
+ "generic-array",
 ]
 
 [[package]]
@@ -50,7 +50,7 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "const-random",
  "getrandom 0.2.10",
  "once_cell",
@@ -133,6 +133,28 @@ version = "1.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6"
 
+[[package]]
+name = "async-stream"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.38",
+]
+
 [[package]]
 name = "async-trait"
 version = "0.1.74"
@@ -162,6 +184,58 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
 
+[[package]]
+name = "axum"
+version = "0.6.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
+dependencies = [
+ "async-trait",
+ "axum-core",
+ "base64",
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "itoa",
+ "matchit",
+ "memchr",
+ "mime",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustversion",
+ "serde",
+ "serde_json",
+ "serde_path_to_error",
+ "serde_urlencoded",
+ "sha1",
+ "sync_wrapper",
+ "tokio",
+ "tokio-tungstenite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "axum-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
+dependencies = [
+ "async-trait",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "mime",
+ "rustversion",
+ "tower-layer",
+ "tower-service",
+]
+
 [[package]]
 name = "backtrace"
 version = "0.3.69"
@@ -170,7 +244,7 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
 dependencies = [
  "addr2line",
  "cc",
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "miniz_oxide",
  "object",
@@ -249,34 +323,13 @@ dependencies = [
  "cty",
 ]
 
-[[package]]
-name = "block-buffer"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
-dependencies = [
- "block-padding",
- "byte-tools",
- "byteorder",
- "generic-array 0.12.4",
-]
-
 [[package]]
 name = "block-buffer"
 version = "0.10.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
 dependencies = [
- "generic-array 0.14.7",
-]
-
-[[package]]
-name = "block-padding"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
-dependencies = [
- "byte-tools",
+ "generic-array",
 ]
 
 [[package]]
@@ -294,44 +347,18 @@ version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
 
-[[package]]
-name = "bstr"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019"
-dependencies = [
- "memchr",
- "serde",
-]
-
 [[package]]
 name = "bumpalo"
 version = "3.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
 
-[[package]]
-name = "byte-tools"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
-
 [[package]]
 name = "byteorder"
 version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
-[[package]]
-name = "bytes"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
-dependencies = [
- "byteorder",
- "iovec",
-]
-
 [[package]]
 name = "bytes"
 version = "1.5.0"
@@ -393,12 +420,6 @@ dependencies = [
  "nom",
 ]
 
-[[package]]
-name = "cfg-if"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-
 [[package]]
 name = "cfg-if"
 version = "1.0.0"
@@ -411,7 +432,7 @@ version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cipher",
  "cpufeatures",
 ]
@@ -798,7 +819,7 @@ dependencies = [
 name = "ckb-gen-types"
 version = "0.113.0-pre"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "ckb-error",
  "ckb-fixed-hash",
  "ckb-hash",
@@ -979,7 +1000,7 @@ version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56ccb671c5921be8a84686e6212ca184cb1d7c51cadcdbfcbd1cc3f042f5dfb8"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1112,7 +1133,7 @@ dependencies = [
  "tempfile",
  "tentacle",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
  "trust-dns-resolver",
 ]
 
@@ -1250,7 +1271,11 @@ dependencies = [
 name = "ckb-rpc"
 version = "0.113.0-pre"
 dependencies = [
+ "async-stream",
+ "async-trait",
+ "axum",
  "ckb-app-config",
+ "ckb-async-runtime",
  "ckb-chain",
  "ckb-chain-spec",
  "ckb-constant",
@@ -1268,6 +1293,7 @@ dependencies = [
  "ckb-pow",
  "ckb-reward-calculator",
  "ckb-shared",
+ "ckb-stop-handler",
  "ckb-store",
  "ckb-sync",
  "ckb-systemtime",
@@ -1278,20 +1304,18 @@ dependencies = [
  "ckb-util",
  "ckb-verification",
  "ckb-verification-traits",
+ "futures-util",
  "itertools 0.11.0",
  "jsonrpc-core",
- "jsonrpc-derive",
- "jsonrpc-http-server",
- "jsonrpc-pubsub",
- "jsonrpc-server-utils",
- "jsonrpc-tcp-server",
- "jsonrpc-ws-server",
+ "jsonrpc-utils",
  "pretty_assertions",
  "reqwest",
  "serde",
  "serde_json",
  "tempfile",
  "tokio",
+ "tokio-util",
+ "tower-http",
 ]
 
 [[package]]
@@ -1387,7 +1411,7 @@ dependencies = [
  "once_cell",
  "rand 0.8.5",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
 ]
 
 [[package]]
@@ -1529,7 +1553,7 @@ dependencies = [
  "slab",
  "tempfile",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
 ]
 
 [[package]]
@@ -1537,7 +1561,7 @@ name = "ckb-types"
 version = "0.113.0-pre"
 dependencies = [
  "bit-vec",
- "bytes 1.5.0",
+ "bytes",
  "ckb-channel",
  "ckb-constant",
  "ckb-error",
@@ -1629,7 +1653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0cc004a826b9bc9319ffae0b8415690e1b5f1482266d55fbd43843aa40ddcd63"
 dependencies = [
  "byteorder",
- "bytes 1.5.0",
+ "bytes",
  "cc",
  "ckb-vm-definitions",
  "derive_more",
@@ -1773,7 +1797,7 @@ version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1818,7 +1842,7 @@ version = "0.5.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "crossbeam-utils",
 ]
 
@@ -1828,7 +1852,7 @@ version = "0.8.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "crossbeam-epoch",
  "crossbeam-utils",
 ]
@@ -1840,7 +1864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
 dependencies = [
  "autocfg",
- "cfg-if 1.0.0",
+ "cfg-if",
  "crossbeam-utils",
  "memoffset",
  "scopeguard",
@@ -1852,7 +1876,7 @@ version = "0.8.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -1867,7 +1891,7 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
 dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
  "rand_core 0.6.4",
  "typenum",
 ]
@@ -1942,7 +1966,7 @@ version = "4.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "num_cpus",
 ]
 
@@ -1990,22 +2014,13 @@ version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
 
-[[package]]
-name = "digest"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
-dependencies = [
- "generic-array 0.12.4",
-]
-
 [[package]]
 name = "digest"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
 dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
 ]
 
 [[package]]
@@ -2014,7 +2029,7 @@ version = "0.10.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
 dependencies = [
- "block-buffer 0.10.4",
+ "block-buffer",
  "crypto-common",
  "subtle",
 ]
@@ -2052,7 +2067,7 @@ version = "0.8.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -2107,12 +2122,6 @@ dependencies = [
  "rand 0.7.3",
 ]
 
-[[package]]
-name = "fake-simd"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
-
 [[package]]
 name = "faster-hex"
 version = "0.6.1"
@@ -2210,25 +2219,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
 dependencies = [
  "libc",
- "winapi 0.3.9",
-]
-
-[[package]]
-name = "fuchsia-zircon"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-dependencies = [
- "bitflags 1.3.2",
- "fuchsia-zircon-sys",
+ "winapi",
 ]
 
-[[package]]
-name = "fuchsia-zircon-sys"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-
 [[package]]
 name = "futures"
 version = "0.3.28"
@@ -2269,7 +2262,6 @@ dependencies = [
  "futures-core",
  "futures-task",
  "futures-util",
- "num_cpus",
 ]
 
 [[package]]
@@ -2334,15 +2326,6 @@ dependencies = [
  "byteorder",
 ]
 
-[[package]]
-name = "generic-array"
-version = "0.12.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
-dependencies = [
- "typenum",
-]
-
 [[package]]
 name = "generic-array"
 version = "0.14.7"
@@ -2359,7 +2342,7 @@ version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "js-sys",
  "libc",
  "wasi 0.9.0+wasi-snapshot-preview1",
@@ -2372,7 +2355,7 @@ version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
 ]
@@ -2389,19 +2372,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 = "goblin"
 version = "0.2.3"
@@ -2456,7 +2426,7 @@ version = "0.3.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "fnv",
  "futures-core",
  "futures-sink",
@@ -2465,7 +2435,7 @@ dependencies = [
  "indexmap 1.9.3",
  "slab",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
  "tracing",
 ]
 
@@ -2512,7 +2482,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2559,7 +2529,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
 dependencies = [
  "libc",
  "match_cfg",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -2568,7 +2538,7 @@ version = "0.2.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "fnv",
  "itoa",
 ]
@@ -2579,11 +2549,17 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "http",
  "pin-project-lite",
 ]
 
+[[package]]
+name = "http-range-header"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
+
 [[package]]
 name = "httparse"
 version = "1.8.0"
@@ -2608,7 +2584,7 @@ version = "0.14.27"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "futures-channel",
  "futures-core",
  "futures-util",
@@ -2632,7 +2608,7 @@ version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "hyper",
  "native-tls",
  "tokio",
@@ -2738,7 +2714,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
 dependencies = [
- "generic-array 0.14.7",
+ "generic-array",
 ]
 
 [[package]]
@@ -2747,16 +2723,7 @@ version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
 dependencies = [
- "cfg-if 1.0.0",
-]
-
-[[package]]
-name = "iovec"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
-dependencies = [
- "libc",
+ "cfg-if",
 ]
 
 [[package]]
@@ -2767,7 +2734,7 @@ checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
 dependencies = [
  "socket2 0.3.19",
  "widestring",
- "winapi 0.3.9",
+ "winapi",
  "winreg 0.6.2",
 ]
 
@@ -2861,102 +2828,33 @@ dependencies = [
 ]
 
 [[package]]
-name = "jsonrpc-derive"
-version = "18.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "jsonrpc-http-server"
-version = "18.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff"
-dependencies = [
- "futures",
- "hyper",
- "jsonrpc-core",
- "jsonrpc-server-utils",
- "log",
- "net2",
- "parking_lot 0.11.2",
- "unicase",
-]
-
-[[package]]
-name = "jsonrpc-pubsub"
-version = "18.0.0"
+name = "jsonrpc-utils"
+version = "0.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011"
+checksum = "dfe6601e4eedf0730aebf2ad532cd22c05c67d3cb20105577dd12a5f004abd3b"
 dependencies = [
- "futures",
- "jsonrpc-core",
- "lazy_static",
- "log",
- "parking_lot 0.11.2",
- "rand 0.7.3",
- "serde",
-]
-
-[[package]]
-name = "jsonrpc-server-utils"
-version = "18.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4"
-dependencies = [
- "bytes 1.5.0",
- "futures",
- "globset",
+ "anyhow",
+ "axum",
+ "futures-core",
+ "futures-util",
+ "hex",
  "jsonrpc-core",
- "lazy_static",
- "log",
+ "jsonrpc-utils-macros",
+ "pin-project-lite",
+ "rand 0.8.5",
+ "serde_json",
  "tokio",
- "tokio-stream",
- "tokio-util 0.6.10",
- "unicase",
 ]
 
 [[package]]
-name = "jsonrpc-tcp-server"
-version = "18.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60a0a0d35558123e93743d467285196905da1368500378cb5352b71856377874"
-dependencies = [
- "jsonrpc-core",
- "jsonrpc-server-utils",
- "log",
- "parking_lot 0.11.2",
- "tower-service",
-]
-
-[[package]]
-name = "jsonrpc-ws-server"
-version = "18.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f892c7d766369475ab7b0669f417906302d7c0fb521285c0a0c92e52e7c8e946"
-dependencies = [
- "futures",
- "jsonrpc-core",
- "jsonrpc-server-utils",
- "log",
- "parity-ws",
- "parking_lot 0.11.2",
- "slab",
-]
-
-[[package]]
-name = "kernel32-sys"
-version = "0.2.2"
+name = "jsonrpc-utils-macros"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+checksum = "34fe3ce66f7e4909575f6be478862325559bf5b5d0a681d106aae2c9849e1857"
 dependencies = [
- "winapi 0.2.8",
- "winapi-build",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.38",
 ]
 
 [[package]]
@@ -2992,8 +2890,8 @@ version = "0.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
 dependencies = [
- "cfg-if 1.0.0",
- "winapi 0.3.9",
+ "cfg-if",
+ "winapi",
 ]
 
 [[package]]
@@ -3063,6 +2961,12 @@ version = "0.1.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
 
+[[package]]
+name = "matchit"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40"
+
 [[package]]
 name = "memchr"
 version = "2.6.4"
@@ -3093,7 +2997,7 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "171d2f700835121c3b04ccf0880882987a050fd5c7ae88148abf537d33dd3a56"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
 ]
 
 [[package]]
@@ -3140,25 +3044,6 @@ dependencies = [
  "adler",
 ]
 
-[[package]]
-name = "mio"
-version = "0.6.23"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4"
-dependencies = [
- "cfg-if 0.1.10",
- "fuchsia-zircon",
- "fuchsia-zircon-sys",
- "iovec",
- "kernel32-sys",
- "libc",
- "log",
- "miow",
- "net2",
- "slab",
- "winapi 0.2.8",
-]
-
 [[package]]
 name = "mio"
 version = "0.8.9"
@@ -3170,38 +3055,14 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "mio-extras"
-version = "2.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19"
-dependencies = [
- "lazycell",
- "log",
- "mio 0.6.23",
- "slab",
-]
-
-[[package]]
-name = "miow"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d"
-dependencies = [
- "kernel32-sys",
- "net2",
- "winapi 0.2.8",
- "ws2_32-sys",
-]
-
 [[package]]
 name = "molecule"
 version = "0.7.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d4fd9767ab5e5f2ea40f71ff4c8bdb633c50509052e093c2fdd0e390a749dfa3"
 dependencies = [
- "bytes 1.5.0",
- "cfg-if 1.0.0",
+ "bytes",
+ "cfg-if",
  "faster-hex",
 ]
 
@@ -3247,17 +3108,6 @@ dependencies = [
  "tempfile",
 ]
 
-[[package]]
-name = "net2"
-version = "0.2.39"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac"
-dependencies = [
- "cfg-if 0.1.10",
- "libc",
- "winapi 0.3.9",
-]
-
 [[package]]
 name = "nix"
 version = "0.27.1"
@@ -3265,7 +3115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
 dependencies = [
  "bitflags 2.4.1",
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
 ]
 
@@ -3393,12 +3243,6 @@ version = "11.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
 
-[[package]]
-name = "opaque-debug"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
-
 [[package]]
 name = "opaque-debug"
 version = "0.3.0"
@@ -3412,7 +3256,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45"
 dependencies = [
  "bitflags 2.4.1",
- "cfg-if 1.0.0",
+ "cfg-if",
  "foreign-types",
  "libc",
  "once_cell",
@@ -3459,24 +3303,6 @@ dependencies = [
  "hashbrown 0.13.2",
 ]
 
-[[package]]
-name = "parity-ws"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5983d3929ad50f12c3eb9a6743f19d691866ecd44da74c0a3308c3f8a56df0c6"
-dependencies = [
- "byteorder",
- "bytes 0.4.12",
- "httparse",
- "log",
- "mio 0.6.23",
- "mio-extras",
- "rand 0.7.3",
- "sha-1 0.8.2",
- "slab",
- "url",
-]
-
 [[package]]
 name = "parking_lot"
 version = "0.11.2"
@@ -3504,12 +3330,12 @@ version = "0.8.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "instant",
  "libc",
  "redox_syscall 0.2.16",
  "smallvec",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3519,7 +3345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
 dependencies = [
  "backtrace",
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
  "petgraph",
  "redox_syscall 0.4.1",
@@ -3600,6 +3426,26 @@ dependencies = [
  "siphasher",
 ]
 
+[[package]]
+name = "pin-project"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.38",
+]
+
 [[package]]
 name = "pin-project-lite"
 version = "0.2.13"
@@ -3659,7 +3505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
 dependencies = [
  "cpufeatures",
- "opaque-debug 0.3.0",
+ "opaque-debug",
  "universal-hash",
 ]
 
@@ -3695,15 +3541,6 @@ dependencies = [
  "syn 2.0.38",
 ]
 
-[[package]]
-name = "proc-macro-crate"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
-dependencies = [
- "toml",
-]
-
 [[package]]
 name = "proc-macro-error"
 version = "1.0.4"
@@ -3743,7 +3580,7 @@ version = "0.13.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "fnv",
  "lazy_static",
  "memchr",
@@ -3797,7 +3634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d98dc777a7a39b76b1a26ae9d3f691f4c1bc0455090aa0b64dfa8cb7fc34c135"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -3968,7 +3805,7 @@ version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f97f7665e51f23760e9e4949d454a4782c76ef954acaeec9d1b0f48a58e4529e"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "rustix",
  "windows",
 ]
@@ -4015,7 +3852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
 dependencies = [
  "base64",
- "bytes 1.5.0",
+ "bytes",
  "encoding_rs",
  "futures-core",
  "futures-util",
@@ -4094,7 +3931,7 @@ dependencies = [
  "spin",
  "untrusted",
  "web-sys",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4103,7 +3940,7 @@ version = "0.19.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "ordered-multimap",
 ]
 
@@ -4141,6 +3978,12 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
+
 [[package]]
 name = "rusty-fork"
 version = "0.3.0"
@@ -4372,6 +4215,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_path_to_error"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde_plain"
 version = "0.3.0"
@@ -4393,25 +4245,13 @@ dependencies = [
  "serde",
 ]
 
-[[package]]
-name = "sha-1"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
-dependencies = [
- "block-buffer 0.7.3",
- "digest 0.8.1",
- "fake-simd",
- "opaque-debug 0.2.3",
-]
-
 [[package]]
 name = "sha-1"
 version = "0.10.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cpufeatures",
  "digest 0.10.7",
 ]
@@ -4422,7 +4262,7 @@ version = "0.10.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cpufeatures",
  "digest 0.10.7",
 ]
@@ -4433,7 +4273,7 @@ version = "0.10.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "cpufeatures",
  "digest 0.10.7",
 ]
@@ -4513,9 +4353,9 @@ version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4525,7 +4365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4555,7 +4395,7 @@ dependencies = [
  "hex",
  "miette",
  "serde",
- "sha-1 0.10.1",
+ "sha-1",
  "sha2",
  "thiserror",
  "xxhash-rust",
@@ -4601,13 +4441,19 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "sync_wrapper"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
+
 [[package]]
 name = "tempfile"
 version = "3.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "fastrand",
  "redox_syscall 0.3.5",
  "rustix",
@@ -4621,7 +4467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6ffbbdb4f54a9605ceda168884efcf5fb24ef655103362af211e6cfe61eff832"
 dependencies = [
  "async-trait",
- "bytes 1.5.0",
+ "bytes",
  "futures",
  "igd",
  "js-sys",
@@ -4637,11 +4483,11 @@ dependencies = [
  "tentacle-secio",
  "thiserror",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
  "tokio-yamux",
  "wasm-bindgen",
  "wasm-bindgen-futures",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4651,7 +4497,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "895b25a7de42f7bba27b011495a4a48ea7f0591c079f59c8fdb07936ca8aa3d1"
 dependencies = [
  "bs58",
- "bytes 1.5.0",
+ "bytes",
  "serde",
  "sha2",
  "unsigned-varint",
@@ -4664,7 +4510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "78a41abd9ae459d1f744f59670d8f4e9a25c4597b12b9060740fbd17bf9687fc"
 dependencies = [
  "bs58",
- "bytes 1.5.0",
+ "bytes",
  "chacha20poly1305",
  "futures",
  "hmac",
@@ -4679,7 +4525,7 @@ dependencies = [
  "secp256k1",
  "sha2",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
  "unsigned-varint",
  "x25519-dalek",
 ]
@@ -4730,7 +4576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0ec81c46e9eb50deaa257be2f148adf052d1fb7701cfd55ccfab2525280b70b"
 dependencies = [
  "libc",
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -4834,9 +4680,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
 dependencies = [
  "backtrace",
- "bytes 1.5.0",
+ "bytes",
  "libc",
- "mio 0.8.9",
+ "mio",
  "num_cpus",
  "parking_lot 0.12.1",
  "pin-project-lite",
@@ -4879,17 +4725,15 @@ dependencies = [
 ]
 
 [[package]]
-name = "tokio-util"
-version = "0.6.10"
+name = "tokio-tungstenite"
+version = "0.20.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
+checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
 dependencies = [
- "bytes 1.5.0",
- "futures-core",
- "futures-sink",
+ "futures-util",
  "log",
- "pin-project-lite",
  "tokio",
+ "tungstenite",
 ]
 
 [[package]]
@@ -4898,7 +4742,7 @@ version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "futures-core",
  "futures-sink",
  "pin-project-lite",
@@ -4912,12 +4756,12 @@ version = "0.3.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f2ed88a04bfbf9e70343a5748a423200ee0591c55e7e487d784a55ee8af17db9"
 dependencies = [
- "bytes 1.5.0",
+ "bytes",
  "futures",
  "log",
  "nohash-hasher",
  "tokio",
- "tokio-util 0.7.10",
+ "tokio-util",
 ]
 
 [[package]]
@@ -4929,6 +4773,47 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "tower"
+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",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
+dependencies = [
+ "bitflags 1.3.2",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-range-header",
+ "pin-project-lite",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
+
 [[package]]
 name = "tower-service"
 version = "0.3.2"
@@ -4941,6 +4826,7 @@ version = "0.1.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
 dependencies = [
+ "log",
  "pin-project-lite",
  "tracing-core",
 ]
@@ -4961,7 +4847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31"
 dependencies = [
  "async-trait",
- "cfg-if 1.0.0",
+ "cfg-if",
  "data-encoding",
  "enum-as-inner",
  "futures-channel",
@@ -4985,7 +4871,7 @@ version = "0.20.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "futures-util",
  "ipconfig",
  "lazy_static",
@@ -5005,6 +4891,25 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
+[[package]]
+name = "tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand 0.8.5",
+ "sha1",
+ "thiserror",
+ "url",
+ "utf-8",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -5035,15 +4940,6 @@ version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
 
-[[package]]
-name = "unicase"
-version = "2.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
-dependencies = [
- "version_check",
-]
-
 [[package]]
 name = "unicode-bidi"
 version = "0.3.13"
@@ -5111,6 +5007,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
 [[package]]
 name = "utf8parse"
 version = "0.2.1"
@@ -5185,7 +5087,7 @@ version = "0.2.87"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "wasm-bindgen-macro",
 ]
 
@@ -5210,7 +5112,7 @@ version = "0.4.37"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "js-sys",
  "wasm-bindgen",
  "web-sys",
@@ -5279,12 +5181,6 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a"
 
-[[package]]
-name = "winapi"
-version = "0.2.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -5295,12 +5191,6 @@ dependencies = [
  "winapi-x86_64-pc-windows-gnu",
 ]
 
-[[package]]
-name = "winapi-build"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
-
 [[package]]
 name = "winapi-i686-pc-windows-gnu"
 version = "0.4.0"
@@ -5313,7 +5203,7 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -5479,7 +5369,7 @@ version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
 dependencies = [
- "winapi 0.3.9",
+ "winapi",
 ]
 
 [[package]]
@@ -5488,20 +5378,10 @@ version = "0.50.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
 dependencies = [
- "cfg-if 1.0.0",
+ "cfg-if",
  "windows-sys 0.48.0",
 ]
 
-[[package]]
-name = "ws2_32-sys"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
-dependencies = [
- "winapi 0.2.8",
- "winapi-build",
-]
-
 [[package]]
 name = "x25519-dalek"
 version = "1.1.1"
diff --git a/ckb-bin/src/subcommand/run.rs b/ckb-bin/src/subcommand/run.rs
index fd6355af33..6418540192 100644
--- a/ckb-bin/src/subcommand/run.rs
+++ b/ckb-bin/src/subcommand/run.rs
@@ -12,7 +12,6 @@ pub fn run(args: RunArgs, version: Version, async_handle: Handle) -> Result<(),
     deadlock_detection();
 
     info!("ckb version: {}", version);
-
     let mut launcher = Launcher::new(args, version, async_handle);
 
     let block_assembler_config = launcher.sanitize_block_assembler_config()?;
@@ -45,7 +44,7 @@ pub fn run(args: RunArgs, version: Version, async_handle: Handle) -> Result<(),
 
     launcher.start_block_filter(&shared);
 
-    let (network_controller, _rpc_server) = launcher.start_network_and_rpc(
+    let network_controller = launcher.start_network_and_rpc(
         &shared,
         chain_controller.clone(),
         miner_enable,
diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml
index eaf36a7eed..3afd21651a 100644
--- a/rpc/Cargo.toml
+++ b/rpc/Cargo.toml
@@ -17,19 +17,14 @@ ckb-shared = { path = "../shared", version = "= 0.113.0-pre" }
 ckb-store = { path = "../store", version = "= 0.113.0-pre" }
 ckb-sync = { path = "../sync", version = "= 0.113.0-pre" }
 ckb-chain = { path = "../chain", version = "= 0.113.0-pre" }
-ckb-logger = { path = "../util/logger", version = "= 0.113.0-pre"}
-ckb-logger-service = { path = "../util/logger-service", version = "= 0.113.0-pre"}
+ckb-logger = { path = "../util/logger", version = "= 0.113.0-pre" }
+ckb-logger-service = { path = "../util/logger-service", version = "= 0.113.0-pre" }
 ckb-network-alert = { path = "../util/network-alert", version = "= 0.113.0-pre" }
 ckb-app-config = { path = "../util/app-config", version = "= 0.113.0-pre" }
 ckb-constant = { path = "../util/constant", version = "= 0.113.0-pre" }
 jsonrpc-core = "18.0"
-jsonrpc-derive = "18.0"
-jsonrpc-http-server = "18.0"
-jsonrpc-tcp-server = "18.0"
-jsonrpc-ws-server = "18.0"
-jsonrpc-server-utils = "18.0"
-jsonrpc-pubsub = "18.0"
 serde_json = "1.0"
+jsonrpc-utils = { version = "0.2.6", features = ["server", "macros", "axum"] }
 ckb-jsonrpc-types = { path = "../util/jsonrpc-types", version = "= 0.113.0-pre" }
 ckb-verification = { path = "../verification", version = "= 0.113.0-pre" }
 ckb-verification-traits = { path = "../verification/traits", version = "= 0.113.0-pre" }
@@ -43,8 +38,16 @@ ckb-tx-pool = { path = "../tx-pool", version = "= 0.113.0-pre" }
 ckb-memory-tracker = { path = "../util/memory-tracker", version = "= 0.113.0-pre" }
 ckb-pow = { path = "../pow", version = "= 0.113.0-pre" }
 ckb-indexer = { path = "../util/indexer", version = "= 0.113.0-pre" }
+ckb-stop-handler = { path = "../util/stop-handler", version = "= 0.113.0-pre" }
 itertools.workspace = true
 tokio = "1"
+async-trait = "0.1"
+axum = "0.6.20"
+tokio-util = { version = "0.7.3", features = ["codec"] }
+futures-util = { version = "0.3.21" }
+tower-http = { version = "0.3.5", features = ["timeout"] }
+async-stream = "0.3.3"
+ckb-async-runtime = { path = "../util/runtime", version = "= 0.113.0-pre" }
 
 [dev-dependencies]
 reqwest = { version = "=0.11.20", features = ["blocking", "json"] }
diff --git a/rpc/README.md b/rpc/README.md
index 3dec06f930..a243ae33ba 100644
--- a/rpc/README.md
+++ b/rpc/README.md
@@ -99,7 +99,6 @@ The crate `ckb-rpc`'s minimum supported rustc version is 1.71.1.
         * [Method `get_deployments_info`](#method-get_deployments_info)
     * [Module Subscription](#module-subscription)
         * [Method `subscribe`](#method-subscribe)
-        * [Method `unsubscribe`](#method-unsubscribe)
 * [RPC Errors](#rpc-errors)
 * [RPC Types](#rpc-types)
     * [Type `Alert`](#type-alert)
@@ -4956,7 +4955,7 @@ The type of the `params.result` in the push message is a two-elements array, whe
 
 ###### Examples
 
-Request
+Subscribe Request
 
 
 ```
@@ -4971,32 +4970,19 @@ Request
 ```
 
 
-Response
+Subscribe Response
 
 
 ```
 {
   "id": 42,
   "jsonrpc": "2.0",
-  "result": "0x2a"
+  "result": "0xf3"
 }
 ```
 
 
-#### Method `unsubscribe`
-* `unsubscribe(id)`
-    * `id`: `string`
-* result: `boolean`
-
-Unsubscribes from a subscribed topic.
-
-###### Params
-
-*   `id` - Subscription ID
-
-###### Examples
-
-Request
+Unsubscribe Request
 
 
 ```
@@ -5005,13 +4991,13 @@ Request
   "jsonrpc": "2.0",
   "method": "unsubscribe",
   "params": [
-    "0x2a"
+    "0xf3"
   ]
 }
 ```
 
 
-Response
+Unsubscribe Response
 
 
 ```
diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs
index ebbd2227e4..3156c6003f 100644
--- a/rpc/src/lib.rs
+++ b/rpc/src/lib.rs
@@ -10,9 +10,12 @@ pub mod module;
 #[cfg(test)]
 mod tests;
 
+use jsonrpc_core::MetaIoHandler;
+use jsonrpc_utils::pub_sub::Session;
+
 pub use crate::error::RPCError;
 pub use crate::server::RpcServer;
 pub use crate::service_builder::ServiceBuilder;
 
 #[doc(hidden)]
-pub type IoHandler = jsonrpc_pubsub::PubSubHandler<Option<crate::module::SubscriptionSession>>;
+pub type IoHandler = MetaIoHandler<Option<Session>>;
diff --git a/rpc/src/module/alert.rs b/rpc/src/module/alert.rs
index d1c6ce7762..f75bc6014c 100644
--- a/rpc/src/module/alert.rs
+++ b/rpc/src/module/alert.rs
@@ -1,12 +1,14 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_jsonrpc_types::Alert;
 use ckb_logger::error;
 use ckb_network::{NetworkController, SupportProtocols};
 use ckb_network_alert::{notifier::Notifier as AlertNotifier, verifier::Verifier as AlertVerifier};
 use ckb_types::{packed, prelude::*};
 use ckb_util::Mutex;
+
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::sync::Arc;
 
 /// RPC Module Alert for network alerts.
@@ -15,7 +17,8 @@ use std::sync::Arc;
 ///
 /// The alerts must be signed by 2-of-4 signatures, where the public keys are hard-coded in the source code
 /// and belong to early CKB developers.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait AlertRpc {
     /// Sends an alert.
     ///
@@ -70,6 +73,7 @@ pub trait AlertRpc {
     fn send_alert(&self, alert: Alert) -> Result<()>;
 }
 
+#[derive(Clone)]
 pub(crate) struct AlertRpcImpl {
     network_controller: NetworkController,
     verifier: Arc<AlertVerifier>,
@@ -90,6 +94,7 @@ impl AlertRpcImpl {
     }
 }
 
+#[async_trait]
 impl AlertRpc for AlertRpcImpl {
     fn send_alert(&self, alert: Alert) -> Result<()> {
         let alert: packed::Alert = alert.into();
diff --git a/rpc/src/module/chain.rs b/rpc/src/module/chain.rs
index fdbab35d67..f19a5b71e0 100644
--- a/rpc/src/module/chain.rs
+++ b/rpc/src/module/chain.rs
@@ -1,5 +1,6 @@
 use crate::error::RPCError;
 use crate::util::FeeRateCollector;
+use async_trait::async_trait;
 use ckb_jsonrpc_types::{
     BlockEconomicState, BlockFilter, BlockNumber, BlockResponse, BlockView, CellWithStatus,
     Consensus, EpochNumber, EpochView, EstimateCycles, FeeRateStatistics, HeaderView, OutPoint,
@@ -26,7 +27,7 @@ use ckb_types::{
 use ckb_verification::ScriptVerifier;
 use ckb_verification::TxVerifyEnv;
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::collections::HashSet;
 use std::sync::Arc;
 
@@ -52,7 +53,8 @@ use std::sync::Arc;
 /// * it is found as an output in any transaction in the [canonical chain](#canonical-chain),
 /// and
 /// * it is not found as an input in any transaction in the canonical chain.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait ChainRpc {
     /// Returns the information about a block by hash.
     ///
@@ -1606,6 +1608,7 @@ pub trait ChainRpc {
     fn get_fee_rate_statistics(&self, target: Option<Uint64>) -> Result<Option<FeeRateStatistics>>;
 }
 
+#[derive(Clone)]
 pub(crate) struct ChainRpcImpl {
     pub shared: Shared,
 }
@@ -1614,6 +1617,7 @@ const DEFAULT_BLOCK_VERBOSITY_LEVEL: u32 = 2;
 const DEFAULT_HEADER_VERBOSITY_LEVEL: u32 = 1;
 const DEFAULT_GET_TRANSACTION_VERBOSITY_LEVEL: u32 = 2;
 
+#[async_trait]
 impl ChainRpc for ChainRpcImpl {
     fn get_block(
         &self,
diff --git a/rpc/src/module/debug.rs b/rpc/src/module/debug.rs
index eabf49d87d..350cbdd1c5 100644
--- a/rpc/src/module/debug.rs
+++ b/rpc/src/module/debug.rs
@@ -1,15 +1,16 @@
+use async_trait::async_trait;
 use ckb_jsonrpc_types::{ExtraLoggerConfig, MainLoggerConfig};
 use ckb_logger_service::Logger;
 use jsonrpc_core::{Error, ErrorCode::InternalError, Result};
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::time;
-
 /// RPC Module Debug for internal RPC methods.
 ///
 /// **This module is for CKB developers and will not guarantee compatibility.** The methods here
 /// will be changed or removed without advanced notification.
-#[rpc(server)]
 #[doc(hidden)]
+#[rpc]
+#[async_trait]
 pub trait DebugRpc {
     /// Dumps jemalloc memory profiling information into a file.
     ///
@@ -35,8 +36,10 @@ pub trait DebugRpc {
     fn set_extra_logger(&self, name: String, config_opt: Option<ExtraLoggerConfig>) -> Result<()>;
 }
 
+#[derive(Clone)]
 pub(crate) struct DebugRpcImpl {}
 
+#[async_trait]
 impl DebugRpc for DebugRpcImpl {
     fn jemalloc_profiling_dump(&self) -> Result<String> {
         let timestamp = time::SystemTime::now()
diff --git a/rpc/src/module/experiment.rs b/rpc/src/module/experiment.rs
index 0486c9009a..55edc5dd5a 100644
--- a/rpc/src/module/experiment.rs
+++ b/rpc/src/module/experiment.rs
@@ -1,5 +1,6 @@
 use crate::error::RPCError;
 use crate::module::chain::CyclesEstimator;
+use async_trait::async_trait;
 use ckb_dao::DaoCalculator;
 use ckb_jsonrpc_types::{
     Capacity, DaoWithdrawingCalculationKind, EstimateCycles, OutPoint, Transaction,
@@ -8,14 +9,15 @@ use ckb_shared::{shared::Shared, Snapshot};
 use ckb_store::ChainStore;
 use ckb_types::{core, packed, prelude::*};
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 
 /// RPC Module Experiment for experimenting methods.
 ///
 /// **EXPERIMENTAL warning**
 ///
 /// The methods here may be removed or changed in future releases without prior notifications.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait ExperimentRpc {
     /// Dry run a transaction and return the execution cycles.
     ///
@@ -162,10 +164,12 @@ pub trait ExperimentRpc {
     ) -> Result<Capacity>;
 }
 
+#[derive(Clone)]
 pub(crate) struct ExperimentRpcImpl {
     pub shared: Shared,
 }
 
+#[async_trait]
 impl ExperimentRpc for ExperimentRpcImpl {
     fn dry_run_transaction(&self, tx: Transaction) -> Result<EstimateCycles> {
         let tx: packed::Transaction = tx.into();
diff --git a/rpc/src/module/indexer.rs b/rpc/src/module/indexer.rs
index ea7578b9e3..aa8cb29c8c 100644
--- a/rpc/src/module/indexer.rs
+++ b/rpc/src/module/indexer.rs
@@ -1,14 +1,16 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_indexer::IndexerHandle;
 use ckb_jsonrpc_types::{
     IndexerCell, IndexerCellsCapacity, IndexerOrder, IndexerPagination, IndexerSearchKey,
     IndexerTip, IndexerTx, JsonBytes, Uint32,
 };
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 
 /// RPC Module Indexer.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait IndexerRpc {
     /// Returns the indexed tip
     ///
@@ -873,6 +875,7 @@ pub trait IndexerRpc {
     ) -> Result<Option<IndexerCellsCapacity>>;
 }
 
+#[derive(Clone)]
 pub(crate) struct IndexerRpcImpl {
     pub(crate) handle: IndexerHandle,
 }
@@ -883,6 +886,7 @@ impl IndexerRpcImpl {
     }
 }
 
+#[async_trait]
 impl IndexerRpc for IndexerRpcImpl {
     fn get_indexer_tip(&self) -> Result<Option<IndexerTip>> {
         self.handle
diff --git a/rpc/src/module/miner.rs b/rpc/src/module/miner.rs
index a334e88180..9a043804dd 100644
--- a/rpc/src/module/miner.rs
+++ b/rpc/src/module/miner.rs
@@ -1,4 +1,5 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_chain::chain::ChainController;
 use ckb_jsonrpc_types::{Block, BlockTemplate, Uint64, Version};
 use ckb_logger::{debug, error, info, warn};
@@ -9,7 +10,7 @@ use ckb_types::{core, packed, prelude::*, H256};
 use ckb_verification::HeaderVerifier;
 use ckb_verification_traits::Verifier;
 use jsonrpc_core::{Error, Result};
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::collections::HashSet;
 use std::fmt::Debug;
 use std::sync::Arc;
@@ -18,7 +19,8 @@ use std::sync::Arc;
 ///
 /// A miner gets a template from CKB, optionally selects transactions, resolves the PoW puzzle, and
 /// submits the found new block.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait MinerRpc {
     /// Returns block template for miners.
     ///
@@ -223,12 +225,14 @@ pub trait MinerRpc {
     fn submit_block(&self, work_id: String, block: Block) -> Result<H256>;
 }
 
+#[derive(Clone)]
 pub(crate) struct MinerRpcImpl {
     pub network_controller: NetworkController,
     pub shared: Shared,
     pub chain: ChainController,
 }
 
+#[async_trait]
 impl MinerRpc for MinerRpcImpl {
     fn get_block_template(
         &self,
diff --git a/rpc/src/module/mod.rs b/rpc/src/module/mod.rs
index e55ac1bde0..ad554dc63e 100644
--- a/rpc/src/module/mod.rs
+++ b/rpc/src/module/mod.rs
@@ -131,17 +131,17 @@ pub(crate) use self::miner::MinerRpcImpl;
 pub(crate) use self::net::NetRpcImpl;
 pub(crate) use self::pool::PoolRpcImpl;
 pub(crate) use self::stats::StatsRpcImpl;
-pub(crate) use self::subscription::{SubscriptionRpcImpl, SubscriptionSession};
+pub(crate) use self::subscription::SubscriptionRpcImpl;
 pub(crate) use self::test::IntegrationTestRpcImpl;
 
-pub use self::alert::AlertRpc;
-pub use self::chain::ChainRpc;
-pub use self::debug::DebugRpc;
-pub use self::experiment::ExperimentRpc;
-pub use self::indexer::IndexerRpc;
-pub use self::miner::MinerRpc;
-pub use self::net::NetRpc;
-pub use self::pool::PoolRpc;
-pub use self::stats::StatsRpc;
-pub use self::subscription::SubscriptionRpc;
-pub use self::test::IntegrationTestRpc;
+pub use self::alert::{add_alert_rpc_methods, AlertRpc};
+pub use self::chain::{add_chain_rpc_methods, ChainRpc};
+pub use self::debug::{add_debug_rpc_methods, DebugRpc};
+pub use self::experiment::{add_experiment_rpc_methods, ExperimentRpc};
+pub use self::indexer::{add_indexer_rpc_methods, IndexerRpc};
+pub use self::miner::{add_miner_rpc_methods, MinerRpc};
+pub use self::net::{add_net_rpc_methods, NetRpc};
+pub use self::pool::{add_pool_rpc_methods, PoolRpc};
+pub use self::stats::{add_stats_rpc_methods, StatsRpc};
+pub use self::subscription::{add_subscription_rpc_methods, SubscriptionRpc};
+pub use self::test::{add_integration_test_rpc_methods, IntegrationTestRpc};
diff --git a/rpc/src/module/net.rs b/rpc/src/module/net.rs
index bef8e6d91c..289ffb1710 100644
--- a/rpc/src/module/net.rs
+++ b/rpc/src/module/net.rs
@@ -1,4 +1,5 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_jsonrpc_types::{
     BannedAddr, LocalNode, LocalNodeProtocol, NodeAddress, PeerSyncState, RemoteNode,
     RemoteNodeProtocol, SyncState, Timestamp,
@@ -7,14 +8,15 @@ use ckb_network::{extract_peer_id, multiaddr::Multiaddr, NetworkController};
 use ckb_sync::SyncShared;
 use ckb_systemtime::unix_time_as_millis;
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::sync::Arc;
 
 const MAX_ADDRS: usize = 50;
 const DEFAULT_BAN_DURATION: u64 = 24 * 60 * 60 * 1000; // 1 day
 
 /// RPC Module Net for P2P network.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait NetRpc {
     /// Returns the local node information.
     ///
@@ -531,11 +533,13 @@ pub trait NetRpc {
     fn ping_peers(&self) -> Result<()>;
 }
 
+#[derive(Clone)]
 pub(crate) struct NetRpcImpl {
     pub network_controller: NetworkController,
     pub sync_shared: Arc<SyncShared>,
 }
 
+#[async_trait]
 impl NetRpc for NetRpcImpl {
     fn local_node_info(&self) -> Result<LocalNode> {
         Ok(LocalNode {
diff --git a/rpc/src/module/pool.rs b/rpc/src/module/pool.rs
index cdad05f947..854f8988ad 100644
--- a/rpc/src/module/pool.rs
+++ b/rpc/src/module/pool.rs
@@ -1,4 +1,5 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_chain_spec::consensus::Consensus;
 use ckb_constant::hardfork::{mainnet, testnet};
 use ckb_jsonrpc_types::{
@@ -9,11 +10,12 @@ use ckb_shared::shared::Shared;
 use ckb_types::{core, packed, prelude::*, H256};
 use ckb_verification::{Since, SinceMetric};
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::sync::Arc;
 
 /// RPC Module Pool for transaction memory pool.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait PoolRpc {
     /// Submits a new transaction into the transaction pool. If the transaction is already in the
     /// pool, rebroadcast it to peers.
@@ -324,6 +326,7 @@ pub trait PoolRpc {
     fn tx_pool_ready(&self) -> Result<bool>;
 }
 
+#[derive(Clone)]
 pub(crate) struct PoolRpcImpl {
     shared: Shared,
     well_known_lock_scripts: Vec<packed::Script>,
@@ -428,6 +431,7 @@ fn build_well_known_type_scripts(chain_spec_name: &str) -> Vec<packed::Script> {
     }).expect("checked json str").into_iter().map(Into::into).collect()
 }
 
+#[async_trait]
 impl PoolRpc for PoolRpcImpl {
     fn tx_pool_ready(&self) -> Result<bool> {
         let tx_pool = self.shared.tx_pool_controller();
diff --git a/rpc/src/module/stats.rs b/rpc/src/module/stats.rs
index b4162b0e40..b9adec9f4b 100644
--- a/rpc/src/module/stats.rs
+++ b/rpc/src/module/stats.rs
@@ -1,3 +1,4 @@
+use async_trait::async_trait;
 use ckb_jsonrpc_types::{AlertMessage, ChainInfo, DeploymentInfo, DeploymentPos, DeploymentsInfo};
 use ckb_network_alert::notifier::Notifier as AlertNotifier;
 use ckb_shared::shared::Shared;
@@ -5,12 +6,13 @@ use ckb_traits::HeaderFieldsProvider;
 use ckb_types::prelude::Unpack;
 use ckb_util::Mutex;
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::collections::BTreeMap;
 use std::sync::Arc;
 
 /// RPC Module Stats for getting various statistic data.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait StatsRpc {
     /// Returns statistics about the chain.
     ///
@@ -99,11 +101,13 @@ pub trait StatsRpc {
     fn get_deployments_info(&self) -> Result<DeploymentsInfo>;
 }
 
+#[derive(Clone)]
 pub(crate) struct StatsRpcImpl {
     pub shared: Shared,
     pub alert_notifier: Arc<Mutex<AlertNotifier>>,
 }
 
+#[async_trait]
 impl StatsRpc for StatsRpcImpl {
     fn get_blockchain_info(&self) -> Result<ChainInfo> {
         let chain = self.shared.consensus().id.clone();
diff --git a/rpc/src/module/subscription.rs b/rpc/src/module/subscription.rs
index c5ca5e5160..25bfe5845f 100644
--- a/rpc/src/module/subscription.rs
+++ b/rpc/src/module/subscription.rs
@@ -1,45 +1,12 @@
+use async_trait::async_trait;
+use ckb_async_runtime::Handle;
 use ckb_jsonrpc_types::Topic;
 use ckb_notify::NotifyController;
-
-use jsonrpc_core::{Metadata, Result};
-use jsonrpc_derive::rpc;
-use jsonrpc_pubsub::{
-    typed::{Sink, Subscriber},
-    PubSubMetadata, Session, SubscriptionId,
-};
-use std::collections::HashMap;
-use std::collections::HashSet;
-use std::sync::Arc;
-use std::sync::{
-    atomic::{AtomicUsize, Ordering},
-    RwLock,
-};
-use tokio::runtime::Handle;
-
-const SUBSCRIBER_NAME: &str = "TcpSubscription";
-
-#[derive(Clone, Debug)]
-pub struct SubscriptionSession {
-    pub(crate) subscription_ids: Arc<RwLock<HashSet<SubscriptionId>>>,
-    pub(crate) session: Arc<Session>,
-}
-
-impl SubscriptionSession {
-    pub fn new(session: Session) -> Self {
-        Self {
-            subscription_ids: Arc::new(RwLock::new(HashSet::new())),
-            session: Arc::new(session),
-        }
-    }
-}
-
-impl Metadata for SubscriptionSession {}
-
-impl PubSubMetadata for SubscriptionSession {
-    fn session(&self) -> Option<Arc<Session>> {
-        Some(Arc::clone(&self.session))
-    }
-}
+use ckb_stop_handler::new_tokio_exit_rx;
+use futures_util::{stream::BoxStream, Stream};
+use jsonrpc_core::Result;
+use jsonrpc_utils::{pub_sub::PublishMsg, rpc};
+use tokio::sync::broadcast;
 
 /// RPC Module Subscription that CKB node will push new messages to subscribers.
 ///
@@ -78,11 +45,13 @@ impl PubSubMetadata for SubscriptionSession {
 /// socket.send(`{"id": 2, "jsonrpc": "2.0", "method": "unsubscribe", "params": ["0x0"]}`)
 /// ```
 #[allow(clippy::needless_return)]
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait SubscriptionRpc {
     /// Context to implement the subscription RPC.
-    type Metadata;
 
+    /// The stream of subscription messages.
+    type S: Stream<Item = PublishMsg<String>> + Send + 'static;
     /// Subscribes to a topic.
     ///
     /// ## Params
@@ -149,7 +118,7 @@ pub trait SubscriptionRpc {
     ///
     /// ## Examples
     ///
-    /// Request
+    /// Subscribe Request
     ///
     /// ```json
     /// {
@@ -162,27 +131,17 @@ pub trait SubscriptionRpc {
     /// }
     /// ```
     ///
-    /// Response
+    /// Subscribe Response
     ///
     /// ```json
     /// {
     ///   "id": 42,
     ///   "jsonrpc": "2.0",
-    ///   "result": "0x2a"
+    ///   "result": "0xf3"
     /// }
     /// ```
-    #[pubsub(subscription = "subscribe", subscribe, name = "subscribe")]
-    fn subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<String>, topic: Topic);
-
-    /// Unsubscribes from a subscribed topic.
-    ///
-    /// ## Params
     ///
-    /// * `id` - Subscription ID
-    ///
-    /// ## Examples
-    ///
-    /// Request
+    /// Unsubscribe Request
     ///
     /// ```json
     /// {
@@ -190,12 +149,12 @@ pub trait SubscriptionRpc {
     ///   "jsonrpc": "2.0",
     ///   "method": "unsubscribe",
     ///   "params": [
-    ///     "0x2a"
+    ///     "0xf3"
     ///   ]
     /// }
     /// ```
     ///
-    /// Response
+    /// Unsubscribe Response
     ///
     /// ```json
     /// {
@@ -204,78 +163,52 @@ pub trait SubscriptionRpc {
     ///   "result": true
     /// }
     /// ```
-    #[pubsub(subscription = "subscribe", unsubscribe, name = "unsubscribe")]
-    fn unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool>;
+    ///
+    #[rpc(pub_sub(notify = "subscribe", unsubscribe = "unsubscribe"))]
+    fn subscribe(&self, topic: Topic) -> Result<Self::S>;
 }
 
-type Subscribers = HashMap<SubscriptionId, Sink<String>>;
-
-#[derive(Default)]
+#[derive(Clone)]
 pub struct SubscriptionRpcImpl {
-    pub(crate) id_generator: AtomicUsize,
-    pub(crate) subscribers: Arc<RwLock<HashMap<Topic, Subscribers>>>,
+    pub new_tip_header_sender: broadcast::Sender<PublishMsg<String>>,
+    pub new_tip_block_sender: broadcast::Sender<PublishMsg<String>>,
+    pub new_transaction_sender: broadcast::Sender<PublishMsg<String>>,
+    pub proposed_transaction_sender: broadcast::Sender<PublishMsg<String>>,
+    pub new_reject_transaction_sender: broadcast::Sender<PublishMsg<String>>,
 }
 
-impl SubscriptionRpc for SubscriptionRpcImpl {
-    type Metadata = Option<SubscriptionSession>;
-
-    fn subscribe(&self, meta: Self::Metadata, subscriber: Subscriber<String>, topic: Topic) {
-        if let Some(session) = meta {
-            let id = SubscriptionId::String(format!(
-                "{:#x}",
-                self.id_generator.fetch_add(1, Ordering::SeqCst)
-            ));
-            if let Ok(sink) = subscriber.assign_id(id.clone()) {
-                let mut subscribers = self
-                    .subscribers
-                    .write()
-                    .expect("acquiring subscribers write lock");
-                subscribers
-                    .entry(topic)
-                    .or_default()
-                    .insert(id.clone(), sink);
-
-                session
-                    .subscription_ids
-                    .write()
-                    .expect("acquiring subscription_ids write lock")
-                    .insert(id);
-            }
-        }
-    }
+macro_rules! publiser_send {
+    ($ty:ty, $info:expr, $sender:ident) => {{
+        let msg: $ty = $info.into();
+        let json_string = serde_json::to_string(&msg).expect("serialization should be ok");
+        drop($sender.send(PublishMsg::result(&json_string)));
+    }};
+}
 
-    fn unsubscribe(&self, meta: Option<Self::Metadata>, id: SubscriptionId) -> Result<bool> {
-        let mut subscribers = self
-            .subscribers
-            .write()
-            .expect("acquiring subscribers write lock");
-        match meta {
-            // unsubscribe handler method is explicitly called.
-            Some(Some(session)) => {
-                if session
-                    .subscription_ids
-                    .write()
-                    .expect("acquiring subscription_ids write lock")
-                    .remove(&id)
-                {
-                    Ok(subscribers.values_mut().any(|s| s.remove(&id).is_some()))
-                } else {
-                    Ok(false)
-                }
-            }
-            // closed or dropped connection
-            _ => {
-                subscribers.values_mut().for_each(|s| {
-                    s.remove(&id);
-                });
-                Ok(true)
-            }
-        }
+#[async_trait]
+impl SubscriptionRpc for SubscriptionRpcImpl {
+    type S = BoxStream<'static, PublishMsg<String>>;
+    fn subscribe(&self, topic: Topic) -> Result<Self::S> {
+        let tx = match topic {
+            Topic::NewTipHeader => self.new_tip_header_sender.clone(),
+            Topic::NewTipBlock => self.new_tip_block_sender.clone(),
+            Topic::NewTransaction => self.new_transaction_sender.clone(),
+            Topic::ProposedTransaction => self.proposed_transaction_sender.clone(),
+            Topic::RejectedTransaction => self.new_reject_transaction_sender.clone(),
+        };
+        let mut rx = tx.subscribe();
+        Ok(Box::pin(async_stream::stream! {
+               while let Ok(msg) = rx.recv().await {
+                    yield msg;
+               }
+        }))
     }
 }
 
 impl SubscriptionRpcImpl {
     pub fn new(notify_controller: NotifyController, handle: Handle) -> Self {
+        const SUBSCRIBER_NAME: &str = "TcpSubscription";
+
         let mut new_block_receiver =
             handle.block_on(notify_controller.subscribe_new_block(SUBSCRIBER_NAME.to_string()));
         let mut new_transaction_receiver = handle
@@ -286,64 +219,52 @@ impl SubscriptionRpcImpl {
         let mut reject_transaction_receiver = handle
             .block_on(notify_controller.subscribe_reject_transaction(SUBSCRIBER_NAME.to_string()));
 
-        let subscription_rpc_impl = SubscriptionRpcImpl::default();
-        let subscribers = Arc::clone(&subscription_rpc_impl.subscribers);
-        handle.spawn(async move {
-            loop {
-                tokio::select! {
-                    Some(block) = new_block_receiver.recv() => {
-                        let subscribers = subscribers.read().expect("acquiring subscribers read lock");
-                        if let Some(new_tip_header_subscribers) = subscribers.get(&Topic::NewTipHeader) {
-                            let header: ckb_jsonrpc_types::HeaderView  = block.header().into();
-                            let json_string = Ok(serde_json::to_string(&header).expect("serialization should be ok"));
-                            for sink in new_tip_header_subscribers.values() {
-                                let _ = sink.notify(json_string.clone());
-                            }
-                        }
-                        if let Some(new_tip_block_subscribers) = subscribers.get(&Topic::NewTipBlock) {
-                            let block: ckb_jsonrpc_types::BlockView  = block.into();
-                            let json_string = Ok(serde_json::to_string(&block).expect("serialization should be ok"));
-                            for sink in new_tip_block_subscribers.values() {
-                                let _ = sink.notify(json_string.clone());
-                            }
-                        }
-                    },
-                    Some(tx_entry) = new_transaction_receiver.recv() => {
-                        let subscribers = subscribers.read().expect("acquiring subscribers read lock");
-                        if let Some(new_transaction_subscribers) = subscribers.get(&Topic::NewTransaction) {
-                            let entry: ckb_jsonrpc_types::PoolTransactionEntry = tx_entry.into();
-                            let json_string = Ok(serde_json::to_string(&entry).expect("serialization should be ok"));
-                            for sink in new_transaction_subscribers.values() {
-                                let _ = sink.notify(json_string.clone());
-                            }
-                        }
-                    },
-                    Some(tx_entry) = proposed_transaction_receiver.recv() => {
-                        let subscribers = subscribers.read().expect("acquiring subscribers read lock");
-                        if let Some(new_transaction_subscribers) = subscribers.get(&Topic::ProposedTransaction) {
-                            let entry: ckb_jsonrpc_types::PoolTransactionEntry = tx_entry.into();
-                            let json_string = Ok(serde_json::to_string(&entry).expect("serialization should be ok"));
-                            for sink in new_transaction_subscribers.values() {
-                                let _ = sink.notify(json_string.clone());
-                            }
-                        }
-                    },
-                    Some((tx_entry, reject)) = reject_transaction_receiver.recv() => {
-                        let subscribers = subscribers.read().expect("acquiring subscribers read lock");
-                        if let Some(new_transaction_subscribers) = subscribers.get(&Topic::RejectedTransaction) {
-                            let entry: ckb_jsonrpc_types::PoolTransactionEntry = tx_entry.into();
-                            let reject: ckb_jsonrpc_types::PoolTransactionReject = reject.into();
-                            let json_string = Ok(serde_json::to_string(&(entry, reject)).expect("serialization should be ok"));
-                            for sink in new_transaction_subscribers.values() {
-                                let _ = sink.notify(json_string.clone());
-                            }
+        let (new_tip_header_sender, _) = broadcast::channel(10);
+        let (new_tip_block_sender, _) = broadcast::channel(10);
+        let (proposed_transaction_sender, _) = broadcast::channel(10);
+        let (new_transaction_sender, _) = broadcast::channel(10);
+        let (new_reject_transaction_sender, _) = broadcast::channel(10);
+
+        let stop_rx = new_tokio_exit_rx();
+        handle.spawn({
+            let new_tip_header_sender = new_tip_header_sender.clone();
+            let new_tip_block_sender = new_tip_block_sender.clone();
+            let new_transaction_sender = new_transaction_sender.clone();
+            let proposed_transaction_sender = proposed_transaction_sender.clone();
+            let new_reject_transaction_sender = new_reject_transaction_sender.clone();
+            async move {
+                loop {
+                    tokio::select! {
+                        Some(block) = new_block_receiver.recv() => {
+                            publiser_send!(ckb_jsonrpc_types::HeaderView, block.header(), new_tip_header_sender);
+                            publiser_send!(ckb_jsonrpc_types::BlockView, block, new_tip_block_sender);
+                        },
+                        Some(tx_entry) = new_transaction_receiver.recv() => {
+                            publiser_send!(ckb_jsonrpc_types::PoolTransactionEntry, tx_entry, new_transaction_sender);
+                        },
+                        Some(tx_entry) = proposed_transaction_receiver.recv() => {
+                            publiser_send!(ckb_jsonrpc_types::PoolTransactionEntry, tx_entry, proposed_transaction_sender);
+                        },
+                        Some((tx_entry, reject)) = reject_transaction_receiver.recv() => {
+                            publiser_send!((ckb_jsonrpc_types::PoolTransactionEntry, ckb_jsonrpc_types::PoolTransactionReject),
+                                            (tx_entry.into(), reject.into()),
+                                            new_reject_transaction_sender);
                         }
+                        _ = stop_rx.cancelled() => {
+                            break;
+                        },
+                        else => break,
                     }
-                    else => break,
                 }
             }
         });
 
-        subscription_rpc_impl
+        Self {
+            new_tip_header_sender,
+            new_tip_block_sender,
+            new_transaction_sender,
+            proposed_transaction_sender,
+            new_reject_transaction_sender,
+        }
     }
 }
diff --git a/rpc/src/server.rs b/rpc/src/server.rs
index c778f961c3..08550d9730 100644
--- a/rpc/src/server.rs
+++ b/rpc/src/server.rs
@@ -1,19 +1,30 @@
-use crate::module::{SubscriptionRpc, SubscriptionRpcImpl, SubscriptionSession};
 use crate::IoHandler;
+use axum::routing::post;
+use axum::{Extension, Router};
 use ckb_app_config::RpcConfig;
+use ckb_async_runtime::Handle;
+use ckb_error::AnyError;
 use ckb_logger::info;
-use ckb_notify::NotifyController;
-use jsonrpc_pubsub::Session;
-use jsonrpc_server_utils::cors::AccessControlAllowOrigin;
-use jsonrpc_server_utils::hosts::DomainsValidation;
+
+use ckb_stop_handler::{new_tokio_exit_rx, CancellationToken};
+use futures_util::{SinkExt, TryStreamExt};
+use jsonrpc_core::MetaIoHandler;
+use jsonrpc_utils::axum_utils::{handle_jsonrpc, handle_jsonrpc_ws};
+use jsonrpc_utils::pub_sub::Session;
+use jsonrpc_utils::stream::{serve_stream_sink, StreamMsg, StreamServerConfig};
 use std::net::{SocketAddr, ToSocketAddrs};
-use tokio::runtime::Handle;
+use std::sync::Arc;
+use std::time::Duration;
+use tokio::net::TcpListener;
+use tokio_util::codec::{FramedRead, FramedWrite, LinesCodec, LinesCodecError};
+use tower_http::timeout::TimeoutLayer;
 
 #[doc(hidden)]
+#[derive(Debug)]
 pub struct RpcServer {
-    pub(crate) http: jsonrpc_http_server::Server,
-    pub(crate) _tcp: Option<jsonrpc_tcp_server::Server>,
-    pub(crate) _ws: Option<jsonrpc_ws_server::Server>,
+    pub http_address: SocketAddr,
+    pub tcp_address: Option<SocketAddr>,
+    pub ws_address: Option<SocketAddr>,
 }
 
 impl RpcServer {
@@ -23,96 +34,145 @@ impl RpcServer {
     ///
     /// * `config` - RPC config options.
     /// * `io_handler` - RPC methods handler. See [ServiceBuilder](../service_builder/struct.ServiceBuilder.html).
-    /// * `notify_controller` - Controller emitting notifications.
-    pub fn new(
-        config: RpcConfig,
-        io_handler: IoHandler,
-        notify_controller: &NotifyController,
-        handle: Handle,
-    ) -> RpcServer {
-        let http = jsonrpc_http_server::ServerBuilder::new(io_handler.clone())
-            .cors(DomainsValidation::AllowOnly(vec![
-                AccessControlAllowOrigin::Null,
-                AccessControlAllowOrigin::Any,
-            ]))
-            .event_loop_executor(handle.clone())
-            .max_request_body_size(config.max_request_body_size)
-            .health_api(("/ping", "ping"))
-            .start_http(
-                &config
-                    .listen_address
-                    .to_socket_addrs()
-                    .expect("config listen_address parsed")
-                    .next()
-                    .expect("config listen_address parsed"),
-            )
-            .expect("Start Jsonrpc HTTP service");
-        info!(
-            "Listen HTTP RPC server on address {}",
-            config.listen_address
-        );
-
-        let _tcp = config
-            .tcp_listen_address
-            .as_ref()
-            .map(|tcp_listen_address| {
-                let subscription_rpc_impl =
-                    SubscriptionRpcImpl::new(notify_controller.clone(), handle.clone());
-                let mut handler = io_handler.clone();
-                if config.subscription_enable() {
-                    handler.extend_with(subscription_rpc_impl.to_delegate());
-                }
-                let tcp_server = jsonrpc_tcp_server::ServerBuilder::with_meta_extractor(
-                    handler,
-                    |context: &jsonrpc_tcp_server::RequestContext| {
-                        Some(SubscriptionSession::new(Session::new(
-                            context.sender.clone(),
-                        )))
-                    },
-                )
-                .start(
-                    &tcp_listen_address
-                        .to_socket_addrs()
-                        .expect("config tcp_listen_address parsed")
-                        .next()
-                        .expect("config tcp_listen_address parsed"),
-                )
-                .expect("Start Jsonrpc TCP service");
-                info!("Listen TCP RPC server on address {}", tcp_listen_address);
-
-                tcp_server
+    /// * `handler` - Tokio runtime handle.
+    pub fn new(config: RpcConfig, io_handler: IoHandler, handler: Handle) -> Self {
+        let rpc = Arc::new(io_handler);
+
+        let http_address = Self::start_server(
+            &rpc,
+            config.listen_address.to_owned(),
+            handler.clone(),
+            false,
+        )
+        .map(|local_addr| {
+            info!("Listen HTTP RPCServer on address: {}", local_addr);
+            local_addr
+        })
+        .unwrap();
+
+        let ws_address = if let Some(addr) = config.ws_listen_address {
+            let local_addr = Self::start_server(&rpc, addr, handler.clone(), true).map(|addr| {
+                info!("Listen WebSocket RPCServer on address: {}", addr);
+                addr
             });
+            local_addr.ok()
+        } else {
+            None
+        };
 
-        let _ws = config.ws_listen_address.as_ref().map(|ws_listen_address| {
-            let subscription_rpc_impl = SubscriptionRpcImpl::new(notify_controller.clone(), handle);
-            let mut handler = io_handler.clone();
-            if config.subscription_enable() {
-                handler.extend_with(subscription_rpc_impl.to_delegate());
-            }
-            let ws_server = jsonrpc_ws_server::ServerBuilder::with_meta_extractor(
-                handler,
-                |context: &jsonrpc_ws_server::RequestContext| {
-                    Some(SubscriptionSession::new(Session::new(context.sender())))
-                },
-            )
-            .start(
-                &ws_listen_address
+        let tcp_address = if let Some(addr) = config.tcp_listen_address {
+            let local_addr = handler.block_on(Self::start_tcp_server(rpc, addr));
+            if let Ok(addr) = &local_addr {
+                info!("Listen TCP RPCServer on address: {}", addr);
+            };
+            local_addr.ok()
+        } else {
+            None
+        };
+
+        Self {
+            http_address,
+            tcp_address,
+            ws_address,
+        }
+    }
+
+    fn start_server(
+        rpc: &Arc<MetaIoHandler<Option<Session>>>,
+        address: String,
+        handler: Handle,
+        enable_websocket: bool,
+    ) -> Result<SocketAddr, AnyError> {
+        let stream_config = StreamServerConfig::default()
+            .with_channel_size(4)
+            .with_pipeline_size(4);
+
+        // HTTP and WS server.
+        let method_router =
+            post(handle_jsonrpc::<Option<Session>>).get(handle_jsonrpc_ws::<Option<Session>>);
+        let mut app = Router::new()
+            .route("/", method_router.clone())
+            .route("/*path", method_router)
+            .layer(Extension(Arc::clone(rpc)))
+            .layer(TimeoutLayer::new(Duration::from_secs(30)));
+
+        if enable_websocket {
+            let ws_config: StreamServerConfig =
+                stream_config
+                    .with_keep_alive(true)
+                    .with_shutdown(async move {
+                        new_tokio_exit_rx().cancelled().await;
+                    });
+            app = app.layer(Extension(ws_config));
+        }
+
+        let (tx_addr, rx_addr) = tokio::sync::oneshot::channel::<SocketAddr>();
+
+        handler.spawn(async move {
+            let server = axum::Server::bind(
+                &address
                     .to_socket_addrs()
-                    .expect("config ws_listen_address parsed")
+                    .expect("config listen_address parsed")
                     .next()
-                    .expect("config ws_listen_address parsed"),
+                    .expect("config listen_address parsed"),
             )
-            .expect("Start Jsonrpc WebSocket service");
-            info!("Listen WS RPC server on address {}", ws_listen_address);
+            .serve(app.clone().into_make_service());
 
-            ws_server
+            let _ = tx_addr.send(server.local_addr());
+            let graceful = server.with_graceful_shutdown(async move {
+                new_tokio_exit_rx().cancelled().await;
+            });
+            drop(graceful.await);
         });
 
-        RpcServer { http, _tcp, _ws }
+        let rx_addr = handler.block_on(rx_addr)?;
+        Ok(rx_addr)
     }
 
-    /// Gets the HTTP RPC endpoint.
-    pub fn http_address(&self) -> &SocketAddr {
-        self.http.address()
+    async fn start_tcp_server(
+        rpc: Arc<MetaIoHandler<Option<Session>>>,
+        tcp_listen_address: String,
+    ) -> Result<SocketAddr, AnyError> {
+        // TCP server with line delimited json codec.
+        let listener = TcpListener::bind(tcp_listen_address).await?;
+        let tcp_address = listener.local_addr()?;
+        tokio::spawn(async move {
+            let codec = LinesCodec::new_with_max_length(2 * 1024 * 1024);
+            let stream_config = StreamServerConfig::default()
+                .with_channel_size(4)
+                .with_pipeline_size(4)
+                .with_shutdown(async move {
+                    new_tokio_exit_rx().cancelled().await;
+                });
+
+            let exit_signal: CancellationToken = new_tokio_exit_rx();
+            tokio::select! {
+                _ = async {
+                        while let Ok((stream, _)) = listener.accept().await {
+                            let rpc = Arc::clone(&rpc);
+                            let stream_config = stream_config.clone();
+                            let codec = codec.clone();
+                            tokio::spawn(async move {
+                                let (r, w) = stream.into_split();
+                                let r = FramedRead::new(r, codec.clone()).map_ok(StreamMsg::Str);
+                                let w = FramedWrite::new(w, codec).with(|msg| async move {
+                                    Ok::<_, LinesCodecError>(match msg {
+                                        StreamMsg::Str(msg) => msg,
+                                        _ => "".into(),
+                                    })
+                                });
+                                tokio::pin!(w);
+                                if let Err(err) = serve_stream_sink(&rpc, w, r, stream_config).await {
+                                    info!("TCP RPCServer error: {:?}", err);
+                                }
+                            });
+                        }
+                    } => {},
+                _ = exit_signal.cancelled() => {
+                    info!("TCP RPCServer stopped");
+                }
+            }
+        });
+        Ok(tcp_address)
     }
 }
diff --git a/rpc/src/service_builder.rs b/rpc/src/service_builder.rs
index 19372ce8a0..ceecf2316c 100644
--- a/rpc/src/service_builder.rs
+++ b/rpc/src/service_builder.rs
@@ -1,12 +1,13 @@
 #![allow(deprecated)]
-use crate::error::RPCError;
-use crate::module::SubscriptionSession;
 use crate::module::{
-    AlertRpc, AlertRpcImpl, ChainRpc, ChainRpcImpl, DebugRpc, DebugRpcImpl, ExperimentRpc,
-    ExperimentRpcImpl, IndexerRpc, IndexerRpcImpl, IntegrationTestRpc, IntegrationTestRpcImpl,
-    MinerRpc, MinerRpcImpl, NetRpc, NetRpcImpl, PoolRpc, PoolRpcImpl, StatsRpc, StatsRpcImpl,
+    add_alert_rpc_methods, add_chain_rpc_methods, add_debug_rpc_methods,
+    add_experiment_rpc_methods, add_indexer_rpc_methods, add_integration_test_rpc_methods,
+    add_miner_rpc_methods, add_net_rpc_methods, add_pool_rpc_methods, add_stats_rpc_methods,
+    add_subscription_rpc_methods, AlertRpcImpl, ChainRpcImpl, DebugRpcImpl, ExperimentRpcImpl,
+    IndexerRpcImpl, IntegrationTestRpcImpl, MinerRpcImpl, NetRpcImpl, PoolRpcImpl, StatsRpcImpl,
+    SubscriptionRpcImpl,
 };
-use crate::IoHandler;
+use crate::{IoHandler, RPCError};
 use ckb_app_config::{DBConfig, IndexerConfig, RpcConfig};
 use ckb_chain::chain::ChainController;
 use ckb_indexer::IndexerService;
@@ -17,7 +18,9 @@ use ckb_shared::shared::Shared;
 use ckb_sync::SyncShared;
 use ckb_types::packed::Script;
 use ckb_util::Mutex;
+use jsonrpc_core::MetaIoHandler;
 use jsonrpc_core::RemoteProcedure;
+use jsonrpc_utils::pub_sub::Session;
 use std::sync::Arc;
 
 const DEPRECATED_RPC_PREFIX: &str = "deprecated.";
@@ -28,6 +31,19 @@ pub struct ServiceBuilder<'a> {
     io_handler: IoHandler,
 }
 
+macro_rules! set_rpc_module_methods {
+    ($self:ident, $name:expr, $check:ident, $add_methods:ident, $methods:expr) => {{
+        let mut meta_io = MetaIoHandler::default();
+        $add_methods(&mut meta_io, $methods);
+        if $self.config.$check() {
+            $self.add_methods(meta_io);
+        } else {
+            $self.update_disabled_methods($name, meta_io);
+        }
+        $self
+    }};
+}
+
 impl<'a> ServiceBuilder<'a> {
     /// Creates the RPC service builder from config.
     pub fn new(config: &'a RpcConfig) -> Self {
@@ -39,13 +55,8 @@ impl<'a> ServiceBuilder<'a> {
 
     /// Mounts methods from module Chain if it is enabled in the config.
     pub fn enable_chain(mut self, shared: Shared) -> Self {
-        let rpc_methods = ChainRpcImpl { shared }.to_delegate();
-        if self.config.chain_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Chain", rpc_methods);
-        }
-        self
+        let methods = ChainRpcImpl { shared };
+        set_rpc_module_methods!(self, "Chain", chain_enable, add_chain_rpc_methods, methods)
     }
 
     /// Mounts methods from module Pool if it is enabled in the config.
@@ -55,18 +66,12 @@ impl<'a> ServiceBuilder<'a> {
         extra_well_known_lock_scripts: Vec<Script>,
         extra_well_known_type_scripts: Vec<Script>,
     ) -> Self {
-        let rpc_methods = PoolRpcImpl::new(
+        let methods = PoolRpcImpl::new(
             shared,
             extra_well_known_lock_scripts,
             extra_well_known_type_scripts,
-        )
-        .to_delegate();
-        if self.config.pool_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Pool", rpc_methods);
-        }
-        self
+        );
+        set_rpc_module_methods!(self, "Pool", pool_enable, add_pool_rpc_methods, methods)
     }
 
     /// Mounts methods from module Miner if `enable` is `true` and it is enabled in the config.
@@ -77,16 +82,17 @@ impl<'a> ServiceBuilder<'a> {
         chain: ChainController,
         enable: bool,
     ) -> Self {
-        let rpc_methods = MinerRpcImpl {
+        let mut meta_io = MetaIoHandler::default();
+        let methods = MinerRpcImpl {
             shared,
             chain,
             network_controller,
-        }
-        .to_delegate();
+        };
+        add_miner_rpc_methods(&mut meta_io, methods);
         if enable && self.config.miner_enable() {
-            self.add_methods(rpc_methods);
+            self.add_methods(meta_io);
         } else {
-            self.update_disabled_methods("Miner", rpc_methods);
+            self.update_disabled_methods("Miner", meta_io);
         }
         self
     }
@@ -97,17 +103,11 @@ impl<'a> ServiceBuilder<'a> {
         network_controller: NetworkController,
         sync_shared: Arc<SyncShared>,
     ) -> Self {
-        let rpc_methods = NetRpcImpl {
+        let methods = NetRpcImpl {
             network_controller,
             sync_shared,
-        }
-        .to_delegate();
-        if self.config.net_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Net", rpc_methods);
-        }
-        self
+        };
+        set_rpc_module_methods!(self, "Net", net_enable, add_net_rpc_methods, methods)
     }
 
     /// Mounts methods from module Stats if it is enabled in the config.
@@ -116,28 +116,23 @@ impl<'a> ServiceBuilder<'a> {
         shared: Shared,
         alert_notifier: Arc<Mutex<AlertNotifier>>,
     ) -> Self {
-        let rpc_methods = StatsRpcImpl {
+        let methods = StatsRpcImpl {
             shared,
             alert_notifier,
-        }
-        .to_delegate();
-        if self.config.stats_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Stats", rpc_methods);
-        }
-        self
+        };
+        set_rpc_module_methods!(self, "Stats", stats_enable, add_stats_rpc_methods, methods)
     }
 
     /// Mounts methods from module Experiment if it is enabled in the config.
     pub fn enable_experiment(mut self, shared: Shared) -> Self {
-        let rpc_methods = ExperimentRpcImpl { shared }.to_delegate();
-        if self.config.experiment_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Experiment", rpc_methods);
-        }
-        self
+        let methods = ExperimentRpcImpl { shared };
+        set_rpc_module_methods!(
+            self,
+            "Experiment",
+            experiment_enable,
+            add_experiment_rpc_methods,
+            methods
+        )
     }
 
     /// Mounts methods from module Integration if it is enabled in the config.
@@ -147,13 +142,6 @@ impl<'a> ServiceBuilder<'a> {
         network_controller: NetworkController,
         chain: ChainController,
     ) -> Self {
-        let rpc_methods = IntegrationTestRpcImpl {
-            shared: shared.clone(),
-            network_controller,
-            chain,
-        }
-        .to_delegate();
-
         if self.config.integration_test_enable() {
             // IntegrationTest only on Dummy PoW chain
             assert_eq!(
@@ -161,12 +149,19 @@ impl<'a> ServiceBuilder<'a> {
                 Pow::Dummy,
                 "Only run integration test on Dummy PoW chain"
             );
-
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("IntegrationTest", rpc_methods);
         }
-        self
+        let methods = IntegrationTestRpcImpl {
+            shared: shared.clone(),
+            network_controller,
+            chain,
+        };
+        set_rpc_module_methods!(
+            self,
+            "IntegrationTest",
+            integration_test_enable,
+            add_integration_test_rpc_methods,
+            methods
+        )
     }
 
     /// Mounts methods from module Alert if it is enabled in the config.
@@ -176,22 +171,14 @@ impl<'a> ServiceBuilder<'a> {
         alert_notifier: Arc<Mutex<AlertNotifier>>,
         network_controller: NetworkController,
     ) -> Self {
-        let rpc_methods =
-            AlertRpcImpl::new(alert_verifier, alert_notifier, network_controller).to_delegate();
-        if self.config.alert_enable() {
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Alert", rpc_methods);
-        }
-        self
+        let methods = AlertRpcImpl::new(alert_verifier, alert_notifier, network_controller);
+        set_rpc_module_methods!(self, "Alert", alert_enable, add_alert_rpc_methods, methods)
     }
 
     /// Mounts methods from module Debug if it is enabled in the config.
     pub fn enable_debug(mut self) -> Self {
-        if self.config.debug_enable() {
-            self.io_handler.extend_with(DebugRpcImpl {}.to_delegate());
-        }
-        self
+        let methods = DebugRpcImpl {};
+        set_rpc_module_methods!(self, "Debug", debug_enable, add_debug_rpc_methods, methods)
     }
 
     /// Mounts methods from module Indexer if it is enabled in the config.
@@ -203,42 +190,41 @@ impl<'a> ServiceBuilder<'a> {
     ) -> Self {
         let indexer = IndexerService::new(db_config, indexer_config, shared.async_handle().clone());
         let indexer_handle = indexer.handle();
-        let rpc_methods = IndexerRpcImpl::new(indexer_handle).to_delegate();
+        let methods = IndexerRpcImpl::new(indexer_handle);
         if self.config.indexer_enable() {
             start_indexer(&shared, indexer, indexer_config.index_tx_pool);
-            self.add_methods(rpc_methods);
-        } else {
-            self.update_disabled_methods("Indexer", rpc_methods);
         }
-        self
+        set_rpc_module_methods!(
+            self,
+            "Indexer",
+            indexer_enable,
+            add_indexer_rpc_methods,
+            methods
+        )
     }
 
-    fn update_disabled_methods<I, M>(&mut self, module: &str, rpc_methods: I)
-    where
-        I: IntoIterator<Item = (String, M)>,
-    {
-        rpc_methods.into_iter().for_each(|(name, _method)| {
-            let error = Err(RPCError::rpc_module_is_disabled(module));
-            self.io_handler.add_sync_method(
-                name.split("deprecated.")
-                    .collect::<Vec<&str>>()
-                    .last()
-                    .unwrap(),
-                move |_param| error.clone(),
-            )
-        });
+    pub fn enable_subscription(&mut self, shared: Shared) {
+        if self.config.subscription_enable() {
+            let methods = SubscriptionRpcImpl::new(
+                shared.notify_controller().clone(),
+                shared.async_handle().clone(),
+            );
+            let mut meta_io = MetaIoHandler::default();
+            add_subscription_rpc_methods(&mut meta_io, methods);
+            self.add_methods(meta_io);
+        }
     }
 
     fn add_methods<I>(&mut self, rpc_methods: I)
     where
-        I: IntoIterator<Item = (String, RemoteProcedure<Option<SubscriptionSession>>)>,
+        I: IntoIterator<Item = (String, RemoteProcedure<Option<Session>>)>,
     {
         let enable_deprecated_rpc = self.config.enable_deprecated_rpc;
         self.io_handler
             .extend_with(rpc_methods.into_iter().map(|(name, method)| {
-                if let Some(deprecated_method_name) = name.strip_prefix(DEPRECATED_RPC_PREFIX) {
+                if let Some(striped_method_name) = name.strip_prefix(DEPRECATED_RPC_PREFIX) {
                     (
-                        deprecated_method_name.to_owned(),
+                        striped_method_name.to_owned(),
                         if enable_deprecated_rpc {
                             method
                         } else {
@@ -253,11 +239,26 @@ impl<'a> ServiceBuilder<'a> {
             }));
     }
 
+    fn update_disabled_methods<I, M>(&mut self, module: &str, rpc_methods: I)
+    where
+        I: IntoIterator<Item = (String, M)>,
+    {
+        rpc_methods.into_iter().for_each(|(name, _method)| {
+            let error = Err(RPCError::rpc_module_is_disabled(module));
+            self.io_handler.add_sync_method(
+                name.split(DEPRECATED_RPC_PREFIX)
+                    .collect::<Vec<&str>>()
+                    .last()
+                    .unwrap(),
+                move |_param| error.clone(),
+            )
+        });
+    }
+
     /// Builds the RPC methods handler used in the RPC server.
     pub fn build(self) -> IoHandler {
         let mut io_handler = self.io_handler;
-        io_handler.add_sync_method("ping", |_| Ok("pong".into()));
-
+        io_handler.add_method("ping", |_| async { Ok("pong".into()) });
         io_handler
     }
 }
diff --git a/shared/src/shared_builder.rs b/shared/src/shared_builder.rs
index 0e6937eb02..7f5a0673bb 100644
--- a/shared/src/shared_builder.rs
+++ b/shared/src/shared_builder.rs
@@ -175,7 +175,7 @@ impl SharedBuilder {
             notify_config: None,
             store_config: None,
             block_assembler_config: None,
-            async_handle: runtime.borrow().get_or_init(new_background_runtime).clone(),
+            async_handle: runtime.get_or_init(new_background_runtime).clone(),
         })
     }
 }
diff --git a/util/indexer/src/service.rs b/util/indexer/src/service.rs
index ad99825bb9..2dacb88475 100644
--- a/util/indexer/src/service.rs
+++ b/util/indexer/src/service.rs
@@ -253,6 +253,7 @@ impl IndexerService {
 ///
 /// The handle is internally reference-counted and can be freely cloned.
 /// A handle can be obtained using the IndexerService::handle method.
+#[derive(Clone)]
 pub struct IndexerHandle {
     pub(crate) store: RocksdbStore,
     pub(crate) pool: Option<Arc<RwLock<Pool>>>,
diff --git a/util/launcher/Cargo.toml b/util/launcher/Cargo.toml
index 2c4d2b7dba..adaecabc4c 100644
--- a/util/launcher/Cargo.toml
+++ b/util/launcher/Cargo.toml
@@ -33,6 +33,7 @@ ckb-tx-pool = { path = "../../tx-pool", version = "= 0.113.0-pre" }
 ckb-light-client-protocol-server = { path = "../light-client-protocol-server", version = "= 0.113.0-pre" }
 ckb-block-filter = { path = "../../block-filter", version = "= 0.113.0-pre" }
 
+
 [features]
 with_sentry = [ "ckb-sync/with_sentry", "ckb-network/with_sentry", "ckb-app-config/with_sentry" ]
 portable = ["ckb-shared/portable"]
diff --git a/util/launcher/src/lib.rs b/util/launcher/src/lib.rs
index c6ff62f5d7..f00dd801aa 100644
--- a/util/launcher/src/lib.rs
+++ b/util/launcher/src/lib.rs
@@ -20,7 +20,8 @@ use ckb_network::{
 use ckb_network_alert::alert_relayer::AlertRelayer;
 use ckb_proposal_table::ProposalTable;
 use ckb_resource::Resource;
-use ckb_rpc::{RpcServer, ServiceBuilder};
+use ckb_rpc::RpcServer;
+use ckb_rpc::ServiceBuilder;
 use ckb_shared::Shared;
 
 use ckb_shared::shared_builder::{SharedBuilder, SharedPackage};
@@ -259,7 +260,7 @@ impl Launcher {
         chain_controller: ChainController,
         miner_enable: bool,
         relay_tx_receiver: Receiver<TxVerificationResult>,
-    ) -> (NetworkController, RpcServer) {
+    ) -> NetworkController {
         let sync_shared = Arc::new(SyncShared::with_tmpdir(
             shared.clone(),
             self.args.config.network.sync.clone(),
@@ -375,7 +376,7 @@ impl Launcher {
         .expect("Start network service failed");
 
         let rpc_config = self.adjust_rpc_config();
-        let builder = ServiceBuilder::new(&rpc_config)
+        let mut builder = ServiceBuilder::new(&rpc_config)
             .enable_chain(shared.clone())
             .enable_pool(
                 shared.clone(),
@@ -407,15 +408,12 @@ impl Launcher {
                 &self.args.config.indexer,
             )
             .enable_debug();
+        builder.enable_subscription(shared.clone());
         let io_handler = builder.build();
 
-        let rpc_server = RpcServer::new(
-            rpc_config.clone(),
-            io_handler,
-            shared.notify_controller(),
-            self.async_handle.clone().into_inner(),
-        );
+        let async_handle = shared.async_handle();
+        let _rpc = RpcServer::new(rpc_config, io_handler, async_handle.clone());
 
-        (network_controller, rpc_server)
+        network_controller
     }
 }
diff --git a/util/runtime/src/lib.rs b/util/runtime/src/lib.rs
index 0e50cbc68a..a5f6965645 100644
--- a/util/runtime/src/lib.rs
+++ b/util/runtime/src/lib.rs
@@ -61,7 +61,6 @@ impl Handle {
             // move tokio_task_guard into the spawned future
             // so that it will be dropped when the future is finished
             let _guard = tokio_task_guard;
-
             future.await
         })
     }


Test patch:
diff --git a/rpc/src/module/test.rs b/rpc/src/module/test.rs
index d56c85239f..1452a1af31 100644
--- a/rpc/src/module/test.rs
+++ b/rpc/src/module/test.rs
@@ -1,4 +1,5 @@
 use crate::error::RPCError;
+use async_trait::async_trait;
 use ckb_chain::chain::ChainController;
 use ckb_dao::DaoCalculator;
 use ckb_jsonrpc_types::{Block, BlockTemplate, Byte32, EpochNumberWithFraction, Transaction};
@@ -20,12 +21,13 @@ use ckb_types::{
 };
 use ckb_verification_traits::Switch;
 use jsonrpc_core::Result;
-use jsonrpc_derive::rpc;
+use jsonrpc_utils::rpc;
 use std::collections::HashSet;
 use std::sync::Arc;
 
 /// RPC for Integration Test.
-#[rpc(server)]
+#[rpc]
+#[async_trait]
 pub trait IntegrationTestRpc {
     /// process block without any block verification.
     ///
@@ -498,12 +500,14 @@ pub trait IntegrationTestRpc {
     fn calculate_dao_field(&self, block_template: BlockTemplate) -> Result<Byte32>;
 }
 
+#[derive(Clone)]
 pub(crate) struct IntegrationTestRpcImpl {
     pub network_controller: NetworkController,
     pub shared: Shared,
     pub chain: ChainController,
 }
 
+#[async_trait]
 impl IntegrationTestRpc for IntegrationTestRpcImpl {
     fn process_block_without_verify(&self, data: Block, broadcast: bool) -> Result<Option<H256>> {
         let block: packed::Block = data.into();
diff --git a/rpc/src/tests/examples.rs b/rpc/src/tests/examples.rs
index 574b0634e1..fe9071b0da 100644
--- a/rpc/src/tests/examples.rs
+++ b/rpc/src/tests/examples.rs
@@ -1,325 +1,34 @@
-use crate::{
-    tests::{always_success_transaction, next_block},
-    RpcServer, ServiceBuilder,
+use super::{
+    setup::{always_success_consensus, setup_rpc_test_suite},
+    RpcTestRequest, RpcTestResponse, RpcTestSuite,
 };
-use ckb_app_config::{
-    BlockAssemblerConfig, NetworkAlertConfig, NetworkConfig, RpcConfig, RpcModule,
-};
-use ckb_chain::chain::ChainService;
-use ckb_chain_spec::consensus::{Consensus, ConsensusBuilder};
-use ckb_chain_spec::versionbits::{ActiveMode, Deployment, DeploymentPos};
-use ckb_dao_utils::genesis_dao_data;
-use ckb_network::{Flags, NetworkService, NetworkState};
-use ckb_network_alert::alert_relayer::AlertRelayer;
-use ckb_notify::NotifyService;
-use ckb_shared::SharedBuilder;
-use ckb_sync::SyncShared;
+use crate::tests::always_success_transaction;
 use ckb_test_chain_utils::always_success_cell;
-use ckb_types::{
-    core::{
-        capacity_bytes, BlockBuilder, Capacity, EpochNumberWithFraction, Ratio, TransactionBuilder,
-        TransactionView,
-    },
-    global::DATA_DIR,
-    h256,
-    packed::{AlertBuilder, CellDep, CellInput, CellOutputBuilder, OutPoint, RawAlertBuilder},
-    prelude::*,
-    H256,
-};
 use pretty_assertions::assert_eq as pretty_assert_eq;
 use serde::de::DeserializeOwned;
 use serde_json::{json, Value};
 use std::cmp;
 use std::collections::BTreeSet;
-use std::collections::HashMap;
+
 use std::fs::{read_dir, File};
 use std::hash;
 use std::io::{self, BufRead};
 use std::path::PathBuf;
-use std::sync::Arc;
-use std::{thread::sleep, time::Duration};
 
-use super::{RpcTestRequest, RpcTestResponse, RpcTestSuite};
+use ckb_types::{
+    core::{capacity_bytes, Capacity, TransactionBuilder, TransactionView},
+    h256,
+    packed::{CellDep, CellInput, CellOutputBuilder, OutPoint},
+    prelude::*,
+    H256,
+};
 
-const GENESIS_TIMESTAMP: u64 = 1_557_310_743;
-const GENESIS_TARGET: u32 = 0x2001_0000;
-const EPOCH_REWARD: u64 = 125_000_000_000_000;
-const CELLBASE_MATURITY: u64 = 0;
-const ALERT_UNTIL_TIMESTAMP: u64 = 2_524_579_200;
 const TARGET_HEIGHT: u64 = 1024;
 const EXAMPLE_TX_PARENT: H256 =
     h256!("0x365698b50ca0da75dca2c87f9e7b563811d3b5813736b8cc62cc3b106faceb17");
 const EXAMPLE_TX_HASH: H256 =
     h256!("0xa0ef4eb5f4ceeb08a4c8524d84c5da95dce2f608e0ca2ec8091191b0f330c6e3");
 
-// Construct `Consensus` with an always-success cell
-//
-// It is similar to `util::test-chain-utils::always_success_consensus`, but with hard-code
-// genesis timestamp.
-fn always_success_consensus() -> Consensus {
-    let always_success_tx = always_success_transaction();
-    let dao = genesis_dao_data(vec![&always_success_tx]).unwrap();
-    let genesis = BlockBuilder::default()
-        .timestamp(GENESIS_TIMESTAMP.pack())
-        .compact_target(GENESIS_TARGET.pack())
-        .epoch(EpochNumberWithFraction::new_unchecked(0, 0, 0).pack())
-        .dao(dao)
-        .transaction(always_success_tx)
-        .build();
-    let mut deployments = HashMap::new();
-    let test_dummy = Deployment {
-        bit: 1,
-        start: 0,
-        timeout: 0,
-        min_activation_epoch: 0,
-        period: 10,
-        active_mode: ActiveMode::Never,
-        threshold: Ratio::new(3, 4),
-    };
-    deployments.insert(DeploymentPos::Testdummy, test_dummy);
-    ConsensusBuilder::default()
-        .genesis_block(genesis)
-        .initial_primary_epoch_reward(Capacity::shannons(EPOCH_REWARD))
-        .cellbase_maturity(EpochNumberWithFraction::from_full_value(CELLBASE_MATURITY))
-        .softfork_deployments(deployments)
-        .build()
-}
-
-fn construct_example_transaction() -> TransactionView {
-    let previous_output = OutPoint::new(EXAMPLE_TX_PARENT.clone().pack(), 0);
-    let input = CellInput::new(previous_output, 0);
-    let output = CellOutputBuilder::default()
-        .capacity(capacity_bytes!(100).pack())
-        .lock(always_success_cell().2.clone())
-        .build();
-    let cell_dep = CellDep::new_builder()
-        .out_point(OutPoint::new(always_success_transaction().hash(), 0))
-        .build();
-    TransactionBuilder::default()
-        .input(input)
-        .output(output)
-        .output_data(Default::default())
-        .cell_dep(cell_dep)
-        .header_dep(always_success_consensus().genesis_hash())
-        .build()
-}
-
-fn json_bytes(hex: &str) -> ckb_jsonrpc_types::JsonBytes {
-    serde_json::from_value(json!(hex)).expect("JsonBytes")
-}
-
-// Setup the running environment
-fn setup_rpc_test_suite(height: u64) -> RpcTestSuite {
-    let (shared, mut pack) = SharedBuilder::with_temp_db()
-        .consensus(always_success_consensus())
-        .block_assembler_config(Some(BlockAssemblerConfig {
-            code_hash: h256!("0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2"),
-            hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
-            args: json_bytes("0xb2e61ff569acf041b3c2c17724e2379c581eeac3"),
-            message: "message".pack().into(),
-            use_binary_version_as_message_prefix: true,
-            binary_version: "TEST".to_string(),
-            update_interval_millis: 800,
-            notify: vec![],
-            notify_scripts: vec![],
-            notify_timeout_millis: 800,
-        }))
-        .build()
-        .unwrap();
-    let chain_controller =
-        ChainService::new(shared.clone(), pack.take_proposal_table()).start::<&str>(None);
-
-    // Start network services
-    let temp_dir = tempfile::tempdir().expect("create tempdir failed");
-    DATA_DIR
-        .set(temp_dir.path().join("data"))
-        .expect("DATA_DIR set only once");
-
-    let network_controller = {
-        let network_config = NetworkConfig {
-            path: temp_dir.path().join("network").to_path_buf(),
-            ping_interval_secs: 1,
-            ping_timeout_secs: 1,
-            connect_outbound_interval_secs: 1,
-            ..Default::default()
-        };
-        let network_state =
-            Arc::new(NetworkState::from_config(network_config).expect("Init network state failed"));
-        NetworkService::new(
-            Arc::clone(&network_state),
-            Vec::new(),
-            Vec::new(),
-            (
-                shared.consensus().identify_name(),
-                "0.1.0".to_string(),
-                Flags::COMPATIBILITY,
-            ),
-        )
-        .start(shared.async_handle())
-        .expect("Start network service failed")
-    };
-
-    pack.take_tx_pool_builder()
-        .start(network_controller.clone());
-
-    let tx_pool = shared.tx_pool_controller();
-    while !tx_pool.service_started() {
-        sleep(Duration::from_millis(400));
-    }
-
-    // Build chain, insert [1, height) blocks
-    let mut parent = always_success_consensus().genesis_block;
-
-    for _ in 0..height {
-        let block = next_block(&shared, &parent.header());
-        chain_controller
-            .process_block(Arc::new(block.clone()))
-            .expect("processing new block should be ok");
-        parent = block;
-    }
-    assert_eq!(
-        EXAMPLE_TX_PARENT,
-        parent.tx_hashes()[0].unpack(),
-        "Expect the last cellbase tx hash matches the constant, which is used later in an example tx."
-    );
-
-    let sync_shared = Arc::new(SyncShared::new(
-        shared.clone(),
-        Default::default(),
-        pack.take_relay_tx_receiver(),
-    ));
-
-    let notify_controller =
-        NotifyService::new(Default::default(), shared.async_handle().clone()).start();
-    let (alert_notifier, alert_verifier) = {
-        let alert_relayer = AlertRelayer::new(
-            "0.1.0".to_string(),
-            notify_controller,
-            NetworkAlertConfig::default(),
-        );
-        let alert_notifier = alert_relayer.notifier();
-        let alert = AlertBuilder::default()
-            .raw(
-                RawAlertBuilder::default()
-                    .id(42u32.pack())
-                    .min_version(Some("0.0.1".to_string()).pack())
-                    .max_version(Some("1.0.0".to_string()).pack())
-                    .priority(1u32.pack())
-                    .notice_until((ALERT_UNTIL_TIMESTAMP * 1000).pack())
-                    .message("An example alert message!".pack())
-                    .build(),
-            )
-            .build();
-        alert_notifier.lock().add(&alert);
-        (
-            Arc::clone(alert_notifier),
-            Arc::clone(alert_relayer.verifier()),
-        )
-    };
-
-    // Start rpc services
-    let rpc_config = RpcConfig {
-        listen_address: "127.0.0.1:0".to_owned(),
-        tcp_listen_address: None,
-        ws_listen_address: None,
-        max_request_body_size: 20_000_000,
-        threads: None,
-        // enable all rpc modules in unit test
-        modules: vec![
-            RpcModule::Net,
-            RpcModule::Chain,
-            RpcModule::Miner,
-            RpcModule::Pool,
-            RpcModule::Experiment,
-            RpcModule::Stats,
-            RpcModule::IntegrationTest,
-            RpcModule::Alert,
-            RpcModule::Subscription,
-            RpcModule::Debug,
-        ],
-        reject_ill_transactions: true,
-        // enable deprecated rpc in unit test
-        enable_deprecated_rpc: true,
-        extra_well_known_lock_scripts: vec![],
-        extra_well_known_type_scripts: vec![],
-    };
-
-    let builder = ServiceBuilder::new(&rpc_config)
-        .enable_chain(shared.clone())
-        .enable_pool(shared.clone(), vec![], vec![])
-        .enable_miner(
-            shared.clone(),
-            network_controller.clone(),
-            chain_controller.clone(),
-            true,
-        )
-        .enable_net(network_controller.clone(), sync_shared)
-        .enable_stats(shared.clone(), Arc::clone(&alert_notifier))
-        .enable_experiment(shared.clone())
-        .enable_integration_test(
-            shared.clone(),
-            network_controller.clone(),
-            chain_controller.clone(),
-        )
-        .enable_debug()
-        .enable_alert(alert_verifier, alert_notifier, network_controller);
-    let io_handler = builder.build();
-
-    let rpc_server = RpcServer::new(
-        rpc_config,
-        io_handler,
-        shared.notify_controller(),
-        shared.async_handle().clone().into_inner(),
-    );
-    let rpc_uri = format!(
-        "http://{}:{}/",
-        rpc_server.http_address().ip(),
-        rpc_server.http_address().port()
-    );
-    let rpc_client = reqwest::blocking::Client::new();
-
-    let suite = RpcTestSuite {
-        shared,
-        chain_controller: chain_controller.clone(),
-        rpc_server,
-        rpc_uri,
-        rpc_client,
-        _tmp_dir: temp_dir,
-    };
-
-    suite.wait_block_template_number(height + 1);
-
-    // insert a fork block for rpc `get_fork_block` test
-    {
-        let fork_block = parent
-            .as_advanced_builder()
-            .header(
-                parent
-                    .header()
-                    .as_advanced_builder()
-                    .timestamp((parent.header().timestamp() + 1).pack())
-                    .build(),
-            )
-            .build();
-        chain_controller
-            .process_block(Arc::new(fork_block))
-            .expect("processing new block should be ok");
-    }
-
-    suite.send_example_transaction();
-
-    suite
-}
-
-fn find_comment(line: &str) -> Option<&str> {
-    let line = line.trim();
-    if line.starts_with("///") || line.starts_with("//!") {
-        Some(line[3..].trim())
-    } else {
-        None
-    }
-}
-
 fn find_rpc_method(line: &str) -> Option<&str> {
     let line = line.trim();
     if line.starts_with("#[")
@@ -392,6 +101,34 @@ fn collect_code_block(
     Ok(())
 }
 
+fn construct_example_transaction() -> TransactionView {
+    let previous_output = OutPoint::new(EXAMPLE_TX_PARENT.clone().pack(), 0);
+    let input = CellInput::new(previous_output, 0);
+    let output = CellOutputBuilder::default()
+        .capacity(capacity_bytes!(100).pack())
+        .lock(always_success_cell().2.clone())
+        .build();
+    let cell_dep = CellDep::new_builder()
+        .out_point(OutPoint::new(always_success_transaction().hash(), 0))
+        .build();
+    TransactionBuilder::default()
+        .input(input)
+        .output(output)
+        .output_data(Default::default())
+        .cell_dep(cell_dep)
+        .header_dep(always_success_consensus().genesis_hash())
+        .build()
+}
+
+fn find_comment(line: &str) -> Option<&str> {
+    let line = line.trim();
+    if line.starts_with("///") || line.starts_with("//!") {
+        Some(line[3..].trim())
+    } else {
+        None
+    }
+}
+
 fn collect_rpc_examples_in_file(
     collected: &mut BTreeSet<RpcTestExample>,
     path: PathBuf,
@@ -588,7 +325,8 @@ impl RpcTestSuite {
 /// Edit `around_rpc_example`.
 #[test]
 fn test_rpc_examples() {
-    let suite = setup_rpc_test_suite(TARGET_HEIGHT);
+    let suite = setup_rpc_test_suite(TARGET_HEIGHT, None);
+    suite.send_example_transaction();
     for example in collect_rpc_examples().expect("collect RPC examples") {
         println!("Test RPC Example {}", example.request);
         around_rpc_example(&suite, example);
diff --git a/rpc/src/tests/mod.rs b/rpc/src/tests/mod.rs
index 8551cd706a..b59897bd22 100644
--- a/rpc/src/tests/mod.rs
+++ b/rpc/src/tests/mod.rs
@@ -1,12 +1,8 @@
-use crate::{RpcServer, ServiceBuilder};
-use ckb_app_config::{BlockAssemblerConfig, NetworkConfig, RpcConfig, RpcModule};
-use ckb_chain::chain::{ChainController, ChainService};
+use ckb_chain::chain::ChainController;
 use ckb_chain_spec::consensus::Consensus;
 use ckb_dao::DaoCalculator;
-use ckb_jsonrpc_types::ScriptHashType;
-use ckb_network::{Flags, NetworkService, NetworkState};
 use ckb_reward_calculator::RewardCalculator;
-use ckb_shared::{Shared, SharedBuilder, Snapshot};
+use ckb_shared::{Shared, Snapshot};
 use ckb_store::ChainStore;
 use ckb_test_chain_utils::{always_success_cell, always_success_cellbase};
 use ckb_types::{
@@ -14,20 +10,24 @@ use ckb_types::{
         cell::resolve_transaction, BlockBuilder, BlockView, HeaderView, TransactionBuilder,
         TransactionView,
     },
-    global::DATA_DIR,
-    h256,
     packed::{CellInput, OutPoint},
     prelude::*,
 };
 use reqwest::blocking::Client;
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
-use std::{cmp, collections::HashSet, fmt, sync::Arc};
+use std::error::Error;
+use std::{cmp, collections::HashSet, fmt};
+use tokio::io::{AsyncReadExt, AsyncWriteExt};
+use tokio::net::TcpStream;
+
+use self::setup::setup_rpc_test_suite;
 
 mod error;
 mod examples;
 mod fee_rate;
 mod module;
+mod setup;
 
 #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Default)]
 struct RpcTestRequest {
@@ -78,12 +78,12 @@ impl RpcTestResponse {
 }
 
 #[allow(dead_code)]
-struct RpcTestSuite {
+pub(crate) struct RpcTestSuite {
     rpc_client: Client,
     rpc_uri: String,
+    tcp_uri: Option<String>,
     shared: Shared,
     chain_controller: ChainController,
-    rpc_server: RpcServer,
     _tmp_dir: tempfile::TempDir,
 }
 
@@ -104,6 +104,20 @@ impl RpcTestSuite {
             .expect("Deserialize RpcTestRequest")
     }
 
+    async fn tcp(&self, request: &RpcTestRequest) -> Result<RpcTestResponse, Box<dyn Error>> {
+        // Connect to the server.
+        assert!(self.tcp_uri.is_some());
+        let mut stream = TcpStream::connect(self.tcp_uri.as_ref().unwrap()).await?;
+        let json = serde_json::to_string(&request)? + "\n";
+        stream.write_all(json.as_bytes()).await?;
+        // Read the server's response.
+        let mut buffer = [0; 1024];
+        let n = stream.read(&mut buffer).await?;
+        let response = std::str::from_utf8(&buffer[..n])?;
+        let message: RpcTestResponse = serde_json::from_str(response)?;
+        Ok(message)
+    }
+
     fn wait_block_template_number(&self, target: u64) {
         use ckb_jsonrpc_types::Uint64;
         use std::{thread::sleep, time::Duration};
@@ -203,121 +217,7 @@ fn always_success_transaction() -> TransactionView {
         .build()
 }
 
-// setup a chain with 20 blocks and enable `Chain`, `Miner` and `Pool` rpc modules for unit test
-// there is a similar fn `setup_rpc_test_suite` which enables all rpc modules, may be refactored into one fn with different paramsters in other PRs
+// setup a chain with 20 blocks and enable `Chain`, `Miner` and `Pool` rpc modules for unit test.
 fn setup(consensus: Consensus) -> RpcTestSuite {
-    let (shared, mut pack) = SharedBuilder::with_temp_db()
-        .consensus(consensus)
-        .block_assembler_config(Some(BlockAssemblerConfig {
-            code_hash: h256!("0x0"),
-            args: Default::default(),
-            hash_type: ScriptHashType::Data,
-            message: Default::default(),
-            use_binary_version_as_message_prefix: false,
-            binary_version: "TEST".to_string(),
-            update_interval_millis: 800,
-            notify: vec![],
-            notify_scripts: vec![],
-            notify_timeout_millis: 800,
-        }))
-        .build()
-        .unwrap();
-    let chain_controller =
-        ChainService::new(shared.clone(), pack.take_proposal_table()).start::<&str>(None);
-
-    let tmp_dir = tempfile::tempdir().expect("create tmp_dir failed");
-    DATA_DIR
-        .set(tmp_dir.path().join("data"))
-        .expect("DATA_DIR set only once");
-
-    // Start network services
-    let network_controller = {
-        let network_config = NetworkConfig {
-            path: tmp_dir.path().join("network").to_path_buf(),
-            ping_interval_secs: 1,
-            ping_timeout_secs: 1,
-            connect_outbound_interval_secs: 1,
-            ..Default::default()
-        };
-        let network_state =
-            Arc::new(NetworkState::from_config(network_config).expect("Init network state failed"));
-        NetworkService::new(
-            Arc::clone(&network_state),
-            Vec::new(),
-            Vec::new(),
-            (
-                shared.consensus().identify_name(),
-                "0.1.0".to_string(),
-                Flags::COMPATIBILITY,
-            ),
-        )
-        .start(shared.async_handle())
-        .expect("Start network service failed")
-    };
-
-    pack.take_tx_pool_builder()
-        .start(network_controller.clone());
-
-    // Build chain, insert 20 blocks
-    let mut parent = shared.consensus().genesis_block().clone();
-
-    for _ in 0..20 {
-        let block = next_block(&shared, &parent.header());
-        chain_controller
-            .process_block(Arc::new(block.clone()))
-            .expect("processing new block should be ok");
-        parent = block;
-    }
-
-    // Start rpc services
-    let rpc_config = RpcConfig {
-        listen_address: "127.0.0.1:0".to_owned(),
-        tcp_listen_address: None,
-        ws_listen_address: None,
-        max_request_body_size: 20_000_000,
-        threads: None,
-        modules: vec![
-            RpcModule::Chain,
-            RpcModule::Miner,
-            RpcModule::Pool,
-            RpcModule::IntegrationTest,
-        ],
-        reject_ill_transactions: false,
-        enable_deprecated_rpc: false,
-        extra_well_known_lock_scripts: vec![],
-        extra_well_known_type_scripts: vec![],
-    };
-
-    let builder = ServiceBuilder::new(&rpc_config)
-        .enable_chain(shared.clone())
-        .enable_pool(shared.clone(), vec![], vec![])
-        .enable_miner(
-            shared.clone(),
-            network_controller.clone(),
-            chain_controller.clone(),
-            true,
-        )
-        .enable_integration_test(shared.clone(), network_controller, chain_controller.clone());
-    let io_handler = builder.build();
-
-    let rpc_server = RpcServer::new(
-        rpc_config,
-        io_handler,
-        shared.notify_controller(),
-        shared.async_handle().clone().into_inner(),
-    );
-    let rpc_uri = format!(
-        "http://{}:{}/",
-        rpc_server.http_address().ip(),
-        rpc_server.http_address().port()
-    );
-    let rpc_client = Client::new();
-    RpcTestSuite {
-        shared,
-        chain_controller,
-        rpc_server,
-        rpc_uri,
-        rpc_client,
-        _tmp_dir: tmp_dir,
-    }
+    setup_rpc_test_suite(20, Some(consensus))
 }
diff --git a/rpc/src/tests/module/test.rs b/rpc/src/tests/module/test.rs
index fca6408622..0c9ef54718 100644
--- a/rpc/src/tests/module/test.rs
+++ b/rpc/src/tests/module/test.rs
@@ -85,6 +85,26 @@ fn test_generate_epochs() {
     );
 }
 
+#[test]
+fn test_rpc_tcp() {
+    use tokio::runtime::Runtime;
+
+    let suite = setup_rpc();
+    let rt = Runtime::new().unwrap();
+    let res = rt.block_on(async move {
+        suite
+            .tcp(&RpcTestRequest {
+                id: 42,
+                jsonrpc: "2.0".to_string(),
+                method: "generate_epochs".to_string(),
+                params: vec!["0x20000000000".into()],
+            })
+            .await
+    });
+    assert!(res.is_ok());
+    assert_eq!(res.unwrap().result, "0x1e0014000000");
+}
+
 // setup a chain for integration test rpc
 fn setup_rpc() -> RpcTestSuite {
     const INITIAL_PRIMARY_EPOCH_REWARD: Capacity = Capacity::shannons(1_917_808_21917808);
diff --git a/rpc/src/tests/setup.rs b/rpc/src/tests/setup.rs
new file mode 100644
index 0000000000..76535ed091
--- /dev/null
+++ b/rpc/src/tests/setup.rs
@@ -0,0 +1,264 @@
+use crate::{
+    tests::{always_success_transaction, next_block},
+    RpcServer, ServiceBuilder,
+};
+use ckb_app_config::{
+    BlockAssemblerConfig, NetworkAlertConfig, NetworkConfig, RpcConfig, RpcModule,
+};
+use ckb_chain::chain::ChainService;
+use ckb_chain_spec::consensus::{Consensus, ConsensusBuilder};
+use ckb_chain_spec::versionbits::{ActiveMode, Deployment, DeploymentPos};
+use ckb_dao_utils::genesis_dao_data;
+use ckb_network::{Flags, NetworkService, NetworkState};
+use ckb_network_alert::alert_relayer::AlertRelayer;
+use ckb_notify::NotifyService;
+use ckb_shared::SharedBuilder;
+use ckb_sync::SyncShared;
+use serde_json::json;
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::{thread::sleep, time::Duration};
+
+use ckb_types::{
+    core::{BlockBuilder, Capacity, EpochNumberWithFraction, Ratio},
+    h256,
+    packed::{AlertBuilder, RawAlertBuilder},
+    prelude::*,
+};
+
+use super::RpcTestSuite;
+
+const GENESIS_TIMESTAMP: u64 = 1_557_310_743;
+const GENESIS_TARGET: u32 = 0x2001_0000;
+const EPOCH_REWARD: u64 = 125_000_000_000_000;
+const CELLBASE_MATURITY: u64 = 0;
+const ALERT_UNTIL_TIMESTAMP: u64 = 2_524_579_200;
+
+// Construct `Consensus` with an always-success cell
+pub(crate) fn always_success_consensus() -> Consensus {
+    let always_success_tx = always_success_transaction();
+    let dao = genesis_dao_data(vec![&always_success_tx]).unwrap();
+    let genesis = BlockBuilder::default()
+        .timestamp(GENESIS_TIMESTAMP.pack())
+        .compact_target(GENESIS_TARGET.pack())
+        .epoch(EpochNumberWithFraction::new_unchecked(0, 0, 0).pack())
+        .dao(dao)
+        .transaction(always_success_tx)
+        .build();
+    let mut deployments = HashMap::new();
+    let test_dummy = Deployment {
+        bit: 1,
+        start: 0,
+        timeout: 0,
+        min_activation_epoch: 0,
+        period: 10,
+        active_mode: ActiveMode::Never,
+        threshold: Ratio::new(3, 4),
+    };
+    deployments.insert(DeploymentPos::Testdummy, test_dummy);
+    ConsensusBuilder::default()
+        .genesis_block(genesis)
+        .initial_primary_epoch_reward(Capacity::shannons(EPOCH_REWARD))
+        .cellbase_maturity(EpochNumberWithFraction::from_full_value(CELLBASE_MATURITY))
+        .softfork_deployments(deployments)
+        .build()
+}
+
+fn json_bytes(hex: &str) -> ckb_jsonrpc_types::JsonBytes {
+    serde_json::from_value(json!(hex)).expect("JsonBytes")
+}
+
+// Setup the running environment
+pub(crate) fn setup_rpc_test_suite(height: u64, consensus: Option<Consensus>) -> RpcTestSuite {
+    let consensus = consensus.unwrap_or_else(always_success_consensus);
+    let (shared, mut pack) = SharedBuilder::with_temp_db()
+        .consensus(consensus)
+        .block_assembler_config(Some(BlockAssemblerConfig {
+            code_hash: h256!("0x1892ea40d82b53c678ff88312450bbb17e164d7a3e0a90941aa58839f56f8df2"),
+            hash_type: ckb_jsonrpc_types::ScriptHashType::Type,
+            args: json_bytes("0xb2e61ff569acf041b3c2c17724e2379c581eeac3"),
+            message: "message".pack().into(),
+            use_binary_version_as_message_prefix: true,
+            binary_version: "TEST".to_string(),
+            update_interval_millis: 800,
+            notify: vec![],
+            notify_scripts: vec![],
+            notify_timeout_millis: 800,
+        }))
+        .build()
+        .unwrap();
+    let chain_controller =
+        ChainService::new(shared.clone(), pack.take_proposal_table()).start::<&str>(None);
+
+    // Start network services
+    let temp_dir = tempfile::tempdir().expect("create tmp_dir failed");
+
+    let network_controller = {
+        let network_config = NetworkConfig {
+            path: temp_dir.path().join("network").to_path_buf(),
+            ping_interval_secs: 1,
+            ping_timeout_secs: 1,
+            connect_outbound_interval_secs: 1,
+            ..Default::default()
+        };
+        let network_state =
+            Arc::new(NetworkState::from_config(network_config).expect("Init network state failed"));
+        NetworkService::new(
+            Arc::clone(&network_state),
+            Vec::new(),
+            Vec::new(),
+            (
+                shared.consensus().identify_name(),
+                "0.1.0".to_string(),
+                Flags::COMPATIBILITY,
+            ),
+        )
+        .start(shared.async_handle())
+        .expect("Start network service failed")
+    };
+
+    pack.take_tx_pool_builder()
+        .start(network_controller.clone());
+
+    let tx_pool = shared.tx_pool_controller();
+    while !tx_pool.service_started() {
+        sleep(Duration::from_millis(400));
+    }
+
+    // Build chain, insert [1, height) blocks
+    let mut parent = shared.consensus().genesis_block().clone();
+
+    for _ in 0..height {
+        let block = next_block(&shared, &parent.header());
+        chain_controller
+            .process_block(Arc::new(block.clone()))
+            .expect("processing new block should be ok");
+        parent = block;
+    }
+
+    let sync_shared = Arc::new(SyncShared::new(
+        shared.clone(),
+        Default::default(),
+        pack.take_relay_tx_receiver(),
+    ));
+
+    let notify_controller =
+        NotifyService::new(Default::default(), shared.async_handle().clone()).start();
+    let (alert_notifier, alert_verifier) = {
+        let alert_relayer = AlertRelayer::new(
+            "0.1.0".to_string(),
+            notify_controller,
+            NetworkAlertConfig::default(),
+        );
+        let alert_notifier = alert_relayer.notifier();
+        let alert = AlertBuilder::default()
+            .raw(
+                RawAlertBuilder::default()
+                    .id(42u32.pack())
+                    .min_version(Some("0.0.1".to_string()).pack())
+                    .max_version(Some("1.0.0".to_string()).pack())
+                    .priority(1u32.pack())
+                    .notice_until((ALERT_UNTIL_TIMESTAMP * 1000).pack())
+                    .message("An example alert message!".pack())
+                    .build(),
+            )
+            .build();
+        alert_notifier.lock().add(&alert);
+        (
+            Arc::clone(alert_notifier),
+            Arc::clone(alert_relayer.verifier()),
+        )
+    };
+
+    // Start rpc services
+    let rpc_config = RpcConfig {
+        listen_address: "127.0.0.1:0".to_owned(),
+        tcp_listen_address: Some("127.0.0.1:0".to_owned()),
+        ws_listen_address: None,
+        max_request_body_size: 20_000_000,
+        threads: None,
+        // enable all rpc modules in unit test
+        modules: vec![
+            RpcModule::Net,
+            RpcModule::Chain,
+            RpcModule::Miner,
+            RpcModule::Pool,
+            RpcModule::Experiment,
+            RpcModule::Stats,
+            RpcModule::IntegrationTest,
+            RpcModule::Alert,
+            RpcModule::Subscription,
+            RpcModule::Debug,
+        ],
+        reject_ill_transactions: true,
+        // enable deprecated rpc in unit test
+        enable_deprecated_rpc: true,
+        extra_well_known_lock_scripts: vec![],
+        extra_well_known_type_scripts: vec![],
+    };
+
+    let builder = ServiceBuilder::new(&rpc_config)
+        .enable_chain(shared.clone())
+        .enable_pool(shared.clone(), vec![], vec![])
+        .enable_miner(
+            shared.clone(),
+            network_controller.clone(),
+            chain_controller.clone(),
+            true,
+        )
+        .enable_net(network_controller.clone(), sync_shared)
+        .enable_stats(shared.clone(), Arc::clone(&alert_notifier))
+        .enable_experiment(shared.clone())
+        .enable_integration_test(
+            shared.clone(),
+            network_controller.clone(),
+            chain_controller.clone(),
+        )
+        .enable_debug()
+        .enable_alert(alert_verifier, alert_notifier, network_controller);
+
+    let io_handler = builder.build();
+    let shared_clone = shared.clone();
+    let handler = shared_clone.async_handle().clone();
+    let rpc_server = RpcServer::new(rpc_config, io_handler, handler.clone());
+
+    let rpc_client = reqwest::blocking::Client::new();
+    let rpc_uri = format!(
+        "http://{}:{}/",
+        rpc_server.http_address.ip(),
+        rpc_server.http_address.port()
+    );
+    let tcp_uri = rpc_server
+        .tcp_address
+        .as_ref()
+        .map(|addr| format!("{}:{}", addr.ip(), addr.port()));
+
+    let suite = RpcTestSuite {
+        shared,
+        chain_controller: chain_controller.clone(),
+        rpc_uri,
+        tcp_uri,
+        rpc_client,
+        _tmp_dir: temp_dir,
+    };
+
+    suite.wait_block_template_number(height + 1);
+    // insert a fork block for rpc `get_fork_block` test
+    {
+        let fork_block = parent
+            .as_advanced_builder()
+            .header(
+                parent
+                    .header()
+                    .as_advanced_builder()
+                    .timestamp((parent.header().timestamp() + 1).pack())
+                    .build(),
+            )
+            .build();
+        chain_controller
+            .process_block(Arc::new(fork_block))
+            .expect("processing new block should be ok");
+    }
+
+    suite
+}
diff --git a/util/network-alert/src/tests/test_notifier.rs b/util/network-alert/src/tests/test_notifier.rs
index fc793dc5e8..86c77b0982 100644
--- a/util/network-alert/src/tests/test_notifier.rs
+++ b/util/network-alert/src/tests/test_notifier.rs
@@ -4,7 +4,6 @@ use ckb_notify::NotifyService;
 
 use ckb_types::{packed, prelude::*};
 use once_cell::unsync;
-use std::borrow::Borrow;
 
 fn build_alert(
     id: u32,
@@ -33,7 +32,7 @@ fn new_notifier(version: &str) -> Notifier {
     let notify_controller = RUNTIME_HANDLE.with(|runtime| {
         NotifyService::new(
             Default::default(),
-            runtime.borrow().get_or_init(new_background_runtime).clone(),
+            runtime.get_or_init(new_background_runtime).clone(),
         )
         .start()
     });