feature(rpc): Migrate from deprecated `jsonrpc_*` crates to `jsonrpsee` (#9059)

* update methods

* update get block template rpc methods

* update other getblocktemplate files

* upgrade server and middlewares

* upgrade zebrad start command

* remove unused imports

* add a todo for unauthenticated rpc error

* upgrade tests, temporally comment out some of them

* fix the rpc tx queue

* update denies

* fix  links

* clippy

* fir more doc links

* fix queue tests

Co-authored-by: Arya <aryasolhi@gmail.com>

* add suggestions from code review

* fix snapshots

* try `block_on` instead of `now_or_never` in the http middleware

* move import

* Apply suggestions from code review

Co-authored-by: Arya <aryasolhi@gmail.com>

* fix bounds

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
This commit is contained in:
Alfredo Garcia 2024-12-20 23:29:16 -03:00 committed by GitHub
parent 1ecf6551bc
commit 0fe47bbbbb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 2225 additions and 2730 deletions

View File

@ -78,7 +78,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"cipher",
"cpufeatures",
]
@ -89,7 +89,7 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"getrandom 0.2.15",
"once_cell",
"version_check",
@ -293,8 +293,8 @@ dependencies = [
"axum-core",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"itoa",
"matchit",
@ -319,8 +319,8 @@ dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
@ -338,7 +338,7 @@ checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
"cc",
"cfg-if 1.0.0",
"cfg-if",
"libc",
"miniz_oxide 0.7.4",
"object",
@ -573,16 +573,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "bstr"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
@ -697,12 +687,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"
@ -721,7 +705,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",
]
@ -975,7 +959,7 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@ -1070,7 +1054,7 @@ version = "4.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"cpufeatures",
"curve25519-dalek-derive",
"digest",
@ -1538,7 +1522,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",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@ -1549,7 +1533,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
@ -1581,19 +1565,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.8",
"regex-syntax 0.8.5",
]
[[package]]
name = "group"
version = "0.13.0"
@ -1617,11 +1588,11 @@ dependencies = [
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"http",
"indexmap 2.7.0",
"slab",
"tokio",
"tokio-util 0.7.13",
"tokio-util",
"tracing",
]
@ -1631,7 +1602,7 @@ version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"crunchy",
]
@ -1788,7 +1759,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"windows",
]
@ -1801,18 +1772,7 @@ checksum = "f34059280f617a59ee59a0455e93460d67e5c76dec42dd262d38f0f390f437b2"
dependencies = [
"flume",
"indicatif",
"parking_lot 0.12.3",
]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
"parking_lot",
]
[[package]]
@ -1826,17 +1786,6 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.1"
@ -1844,7 +1793,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http 1.1.0",
"http",
]
[[package]]
@ -1855,8 +1804,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [
"bytes",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"pin-project-lite",
]
@ -1894,29 +1843,6 @@ dependencies = [
"serde",
]
[[package]]
name = "hyper"
version = "0.14.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.5.1"
@ -1927,8 +1853,8 @@ dependencies = [
"futures-channel",
"futures-util",
"h2",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
@ -1945,8 +1871,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.5.1",
"http",
"hyper",
"hyper-util",
"rustls",
"rustls-pki-types",
@ -1962,7 +1888,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
dependencies = [
"hyper 1.5.1",
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
@ -1978,9 +1904,9 @@ dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"hyper 1.5.1",
"http",
"http-body",
"hyper",
"pin-project-lite",
"socket2",
"tokio",
@ -2138,15 +2064,6 @@ dependencies = [
"similar",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "ipnet"
version = "2.10.1"
@ -2248,49 +2165,90 @@ dependencies = [
]
[[package]]
name = "jsonrpc-derive"
version = "18.0.0"
name = "jsonrpsee"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2"
checksum = "c5c71d8c1a731cc4227c2f698d377e7848ca12c8a48866fc5e6951c43a4db843"
dependencies = [
"proc-macro-crate 0.1.5",
"jsonrpsee-core",
"jsonrpsee-server",
"jsonrpsee-types",
"tokio",
]
[[package]]
name = "jsonrpsee-core"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2882f6f8acb9fdaec7cefc4fd607119a9bd709831df7d7672a1d3b644628280"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"jsonrpsee-types",
"parking_lot",
"rand 0.8.5",
"rustc-hash 2.0.0",
"serde",
"serde_json",
"thiserror 1.0.69",
"tokio",
"tracing",
]
[[package]]
name = "jsonrpsee-proc-macros"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06c01ae0007548e73412c08e2285ffe5d723195bf268bce67b1b77c3bb2a14d"
dependencies = [
"heck 0.5.0",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 1.0.109",
"syn 2.0.90",
]
[[package]]
name = "jsonrpc-http-server"
version = "18.0.0"
name = "jsonrpsee-server"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff"
checksum = "82ad8ddc14be1d4290cd68046e7d1d37acd408efed6d3ca08aefcc3ad6da069c"
dependencies = [
"futures",
"hyper 0.14.31",
"jsonrpc-core",
"jsonrpc-server-utils",
"log",
"net2",
"parking_lot 0.11.2",
"unicase",
]
[[package]]
name = "jsonrpc-server-utils"
version = "18.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4"
dependencies = [
"bytes",
"futures",
"globset",
"jsonrpc-core",
"lazy_static",
"log",
"futures-util",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-util",
"jsonrpsee-core",
"jsonrpsee-types",
"pin-project",
"route-recognizer",
"serde",
"serde_json",
"soketto",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
"tokio-util 0.6.10",
"unicase",
"tokio-util",
"tower 0.4.13",
"tracing",
]
[[package]]
name = "jsonrpsee-types"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a178c60086f24cc35bb82f57c651d0d25d99c4742b4d335de04e97fa1f08a8a1"
dependencies = [
"http",
"serde",
"serde_json",
"thiserror 1.0.69",
]
[[package]]
@ -2355,7 +2313,7 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"windows-targets 0.52.6",
]
@ -2477,7 +2435,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"rayon",
]
@ -2514,7 +2472,7 @@ checksum = "85b6f8152da6d7892ff1b7a1c0fa3f435e92b5918ad67035c3bb432111d9a29b"
dependencies = [
"base64 0.22.1",
"http-body-util",
"hyper 1.5.1",
"hyper",
"hyper-util",
"indexmap 2.7.0",
"ipnet",
@ -2603,17 +2561,6 @@ dependencies = [
"getrandom 0.2.15",
]
[[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",
]
[[package]]
name = "nix"
version = "0.29.0"
@ -2621,7 +2568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if 1.0.0",
"cfg-if",
"cfg_aliases",
"libc",
]
@ -2854,23 +2801,12 @@ version = "3.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c"
dependencies = [
"proc-macro-crate 3.2.0",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
@ -2878,21 +2814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core 0.9.10",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
"parking_lot_core",
]
[[package]]
@ -2901,9 +2823,9 @@ version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"redox_syscall 0.5.7",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
@ -3113,15 +3035,6 @@ dependencies = [
"uint 0.9.5",
]
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
dependencies = [
"toml 0.5.11",
]
[[package]]
name = "proc-macro-crate"
version = "3.2.0"
@ -3505,15 +3418,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.7"
@ -3590,10 +3494,10 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"hyper 1.5.1",
"hyper",
"hyper-rustls",
"hyper-util",
"ipnet",
@ -3613,7 +3517,7 @@ dependencies = [
"sync_wrapper 1.0.1",
"tokio",
"tokio-rustls",
"tokio-util 0.7.13",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
@ -3639,7 +3543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if 1.0.0",
"cfg-if",
"getrandom 0.2.15",
"libc",
"spin",
@ -3686,6 +3590,12 @@ dependencies = [
"serde",
]
[[package]]
name = "route-recognizer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746"
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -4076,13 +3986,24 @@ dependencies = [
"version_check",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
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",
]
@ -4169,6 +4090,22 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "soketto"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e859df029d160cb88608f5d7df7fb4753fd20fdfb4de5644f3d8b8440841721"
dependencies = [
"base64 0.22.1",
"bytes",
"futures",
"http",
"httparse",
"log",
"rand 0.8.5",
"sha1",
]
[[package]]
name = "spandoc"
version = "0.2.2"
@ -4325,7 +4262,7 @@ version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"fastrand",
"once_cell",
"rustix",
@ -4397,7 +4334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe075d7053dae61ac5413a34ea7d4913b6e6207844fd726bdd858b37ff72bf5"
dependencies = [
"bitflags 2.6.0",
"cfg-if 1.0.0",
"cfg-if",
"libc",
"log",
"rustversion",
@ -4410,7 +4347,7 @@ version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"once_cell",
]
@ -4482,7 +4419,7 @@ dependencies = [
"bytes",
"libc",
"mio",
"parking_lot 0.12.3",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -4522,7 +4459,7 @@ dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
"tokio-util 0.7.13",
"tokio-util",
]
[[package]]
@ -4538,20 +4475,6 @@ dependencies = [
"tokio-stream",
]
[[package]]
name = "tokio-util"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.13"
@ -4560,6 +4483,7 @@ checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
dependencies = [
"bytes",
"futures-core",
"futures-io",
"futures-sink",
"pin-project-lite",
"tokio",
@ -4620,10 +4544,10 @@ dependencies = [
"base64 0.22.1",
"bytes",
"h2",
"http 1.1.0",
"http-body 1.0.1",
"http",
"http-body",
"http-body-util",
"hyper 1.5.1",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
@ -4680,7 +4604,7 @@ dependencies = [
"rand 0.8.5",
"slab",
"tokio",
"tokio-util 0.7.13",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
@ -4714,7 +4638,7 @@ dependencies = [
"tinyvec",
"tokio",
"tokio-test",
"tokio-util 0.7.13",
"tokio-util",
"tower 0.4.13",
"tower-fallback",
"tower-test",
@ -4966,12 +4890,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-bidi"
version = "0.3.17"
@ -5101,7 +5019,7 @@ checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566"
dependencies = [
"anyhow",
"cargo_metadata",
"cfg-if 1.0.0",
"cfg-if",
"git2",
"regex",
"rustc_version",
@ -5228,7 +5146,7 @@ version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
@ -5254,7 +5172,7 @@ version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
@ -5962,7 +5880,7 @@ dependencies = [
"thiserror 2.0.6",
"tokio",
"tokio-stream",
"tokio-util 0.7.13",
"tokio-util",
"toml 0.8.19",
"tower 0.4.13",
"tracing",
@ -5994,11 +5912,13 @@ dependencies = [
"color-eyre",
"futures",
"hex",
"http-body-util",
"hyper",
"indexmap 2.7.0",
"insta",
"jsonrpc-core",
"jsonrpc-derive",
"jsonrpc-http-server",
"jsonrpsee",
"jsonrpsee-proc-macros",
"jsonrpsee-types",
"nix",
"proptest",
"prost",
@ -6204,13 +6124,13 @@ dependencies = [
"howudoin",
"http-body-util",
"humantime-serde",
"hyper 1.5.1",
"hyper",
"hyper-util",
"indexmap 2.7.0",
"indicatif",
"inferno",
"insta",
"jsonrpc-core",
"jsonrpsee-types",
"lazy_static",
"log",
"metrics",

View File

@ -78,19 +78,8 @@ skip-tree = [
{ name = "base64", version = "=0.21.7" },
{ name = "sync_wrapper", version = "0.1.2" },
# wait for jsonrpc-http-server to update hyper or for Zebra to replace jsonrpc (#8682)
{ name = "h2", version = "=0.3.26" },
{ name = "http", version = "=0.2.12" },
{ name = "http-body", version = "=0.4.6" },
{ name = "hyper", version = "=0.14.31" },
{ name = "hyper-rustls", version = "=0.24.2" },
{ name = "reqwest", version = "=0.11.27" },
{ name = "rustls", version = "=0.21.12" },
{ name = "rustls-pemfile", version = "=1.0.4" },
{ name = "rustls-webpki", version = "=0.101.7" },
{ name = "tokio-rustls", version = "=0.24.1" },
{ name = "webpki-roots", version = "=0.25.4" },
# wait for abscissa_core to update toml
{ name = "toml", version = "=0.5.11" },
# wait for structopt-derive to update heck
{ name = "heck", version = "=0.3.3" },

View File

@ -59,9 +59,11 @@ chrono = { version = "0.4.39", default-features = false, features = [
] }
futures = "0.3.31"
jsonrpc-core = "18.0.0"
jsonrpc-derive = "18.0.0"
jsonrpc-http-server = "18.0.0"
jsonrpsee = { version = "0.24.7", features = ["server"] }
jsonrpsee-types = "0.24.7"
jsonrpsee-proc-macros = "0.24.7"
hyper = "1.5.0"
http-body-util = "0.1.2"
# zebra-rpc needs the preserve_order feature in serde_json, which is a dependency of jsonrpc-core
serde_json = { version = "1.0.133", features = ["preserve_order"] }

View File

@ -50,24 +50,12 @@ pub struct Config {
/// The number of threads used to process RPC requests and responses.
///
/// Zebra's RPC server has a separate thread pool and a `tokio` executor for each thread.
/// State queries are run concurrently using the shared thread pool controlled by
/// the [`SyncSection.parallel_cpu_threads`](https://docs.rs/zebrad/latest/zebrad/components/sync/struct.Config.html#structfield.parallel_cpu_threads) config.
///
/// If the number of threads is not configured or zero, Zebra uses the number of logical cores.
/// If the number of logical cores can't be detected, Zebra uses one thread.
///
/// Set to `1` to run all RPC queries on a single thread, and detect RPC port conflicts from
/// multiple Zebra or `zcashd` instances.
///
/// For details, see [the `jsonrpc_http_server` documentation](https://docs.rs/jsonrpc-http-server/latest/jsonrpc_http_server/struct.ServerBuilder.html#method.threads).
///
/// ## Warning
///
/// The default config uses multiple threads, which disables RPC port conflict detection.
/// This can allow multiple Zebra instances to share the same RPC port.
///
/// If some of those instances are outdated or failed, RPC queries can be slow or inconsistent.
/// This field is deprecated and could be removed in a future release.
/// We keep it just for backward compatibility but it actually do nothing.
/// It was something configurable when the RPC server was based in the jsonrpc-core crate,
/// not anymore since we migrated to jsonrpsee.
// TODO: Prefix this field name with an underscore so it's clear that it's now unused, and
// use serde(rename) to continue successfully deserializing old configs.
pub parallel_cpu_threads: usize,
/// Test-only option that makes Zebra say it is at the chain tip,

View File

@ -9,12 +9,13 @@
use std::{collections::HashSet, fmt::Debug, sync::Arc};
use chrono::Utc;
use futures::{stream::FuturesOrdered, FutureExt, StreamExt, TryFutureExt};
use futures::{stream::FuturesOrdered, StreamExt, TryFutureExt};
use hex::{FromHex, ToHex};
use hex_data::HexData;
use indexmap::IndexMap;
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use jsonrpsee::core::{async_trait, RpcResult as Result};
use jsonrpsee_proc_macros::rpc;
use jsonrpsee_types::{ErrorCode, ErrorObject};
use tokio::{sync::broadcast, task::JoinHandle};
use tower::{Service, ServiceExt};
use tracing::Instrument;
@ -56,7 +57,7 @@ pub mod types;
pub mod get_block_template_rpcs;
#[cfg(feature = "getblocktemplate-rpcs")]
pub use get_block_template_rpcs::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl};
pub use get_block_template_rpcs::{GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer};
#[cfg(test)]
mod tests;
@ -64,7 +65,6 @@ mod tests;
#[rpc(server)]
/// RPC method signatures.
pub trait Rpc {
#[rpc(name = "getinfo")]
/// Returns software information from the RPC server, as a [`GetInfo`] JSON struct.
///
/// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html)
@ -79,6 +79,7 @@ pub trait Rpc {
///
/// Some fields from the zcashd reference are missing from Zebra's [`GetInfo`]. It only contains the fields
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L91-L95)
#[method(name = "getinfo")]
fn get_info(&self) -> Result<GetInfo>;
/// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct.
@ -91,8 +92,8 @@ pub trait Rpc {
///
/// Some fields from the zcashd reference are missing from Zebra's [`GetBlockChainInfo`]. It only contains the fields
/// [required for lightwalletd support.](https://github.com/zcash/lightwalletd/blob/v0.4.9/common/common.go#L72-L89)
#[rpc(name = "getblockchaininfo")]
fn get_blockchain_info(&self) -> BoxFuture<Result<GetBlockChainInfo>>;
#[method(name = "getblockchaininfo")]
async fn get_blockchain_info(&self) -> Result<GetBlockChainInfo>;
/// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance.
///
@ -116,11 +117,8 @@ pub trait Rpc {
/// The RPC documentation says that the returned object has a string `balance` field, but
/// zcashd actually [returns an
/// integer](https://github.com/zcash/lightwalletd/blob/bdaac63f3ee0dbef62bde04f6817a9f90d483b00/common/common.go#L128-L130).
#[rpc(name = "getaddressbalance")]
fn get_address_balance(
&self,
address_strings: AddressStrings,
) -> BoxFuture<Result<AddressBalance>>;
#[method(name = "getaddressbalance")]
async fn get_address_balance(&self, address_strings: AddressStrings) -> Result<AddressBalance>;
/// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid.
/// Returns the [`SentTransactionHash`] for the transaction, as a JSON string.
@ -137,11 +135,11 @@ pub trait Rpc {
///
/// zcashd accepts an optional `allowhighfees` parameter. Zebra doesn't support this parameter,
/// because lightwalletd doesn't use it.
#[rpc(name = "sendrawtransaction")]
fn send_raw_transaction(
#[method(name = "sendrawtransaction")]
async fn send_raw_transaction(
&self,
raw_transaction_hex: String,
) -> BoxFuture<Result<SentTransactionHash>>;
) -> Result<SentTransactionHash>;
/// Returns the requested block by hash or height, as a [`GetBlock`] JSON string.
/// If the block is not in Zebra's state, returns
@ -167,12 +165,8 @@ pub trait Rpc {
/// use verbosity=3.
///
/// The undocumented `chainwork` field is not returned.
#[rpc(name = "getblock")]
fn get_block(
&self,
hash_or_height: String,
verbosity: Option<u8>,
) -> BoxFuture<Result<GetBlock>>;
#[method(name = "getblock")]
async fn get_block(&self, hash_or_height: String, verbosity: Option<u8>) -> Result<GetBlock>;
/// Returns the requested block header by hash or height, as a [`GetBlockHeader`] JSON string.
/// If the block is not in Zebra's state,
@ -191,19 +185,19 @@ pub trait Rpc {
/// # Notes
///
/// The undocumented `chainwork` field is not returned.
#[rpc(name = "getblockheader")]
fn get_block_header(
#[method(name = "getblockheader")]
async fn get_block_header(
&self,
hash_or_height: String,
verbose: Option<bool>,
) -> BoxFuture<Result<GetBlockHeader>>;
) -> Result<GetBlockHeader>;
/// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string.
///
/// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html)
/// method: post
/// tags: blockchain
#[rpc(name = "getbestblockhash")]
#[method(name = "getbestblockhash")]
fn get_best_block_hash(&self) -> Result<GetBlockHash>;
/// Returns the height and hash of the current best blockchain tip block, as a [`GetBlockHeightAndHash`] JSON struct.
@ -211,7 +205,7 @@ pub trait Rpc {
/// zcashd reference: none
/// method: post
/// tags: blockchain
#[rpc(name = "getbestblockheightandhash")]
#[method(name = "getbestblockheightandhash")]
fn get_best_block_height_and_hash(&self) -> Result<GetBlockHeightAndHash>;
/// Returns all transaction ids in the memory pool, as a JSON array.
@ -219,8 +213,8 @@ pub trait Rpc {
/// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html)
/// method: post
/// tags: blockchain
#[rpc(name = "getrawmempool")]
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>>;
#[method(name = "getrawmempool")]
async fn get_raw_mempool(&self) -> Result<Vec<String>>;
/// Returns information about the given block's Sapling & Orchard tree state.
///
@ -238,8 +232,8 @@ pub trait Rpc {
/// negative where -1 is the last known valid block". On the other hand,
/// `lightwalletd` only uses positive heights, so Zebra does not support
/// negative heights.
#[rpc(name = "z_gettreestate")]
fn z_get_treestate(&self, hash_or_height: String) -> BoxFuture<Result<GetTreestate>>;
#[method(name = "z_gettreestate")]
async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestate>;
/// Returns information about a range of Sapling or Orchard subtrees.
///
@ -259,13 +253,13 @@ pub trait Rpc {
/// starting at the chain tip. This RPC will return an empty list if the `start_index` subtree
/// exists, but has not been rebuilt yet. This matches `zcashd`'s behaviour when subtrees aren't
/// available yet. (But `zcashd` does its rebuild before syncing any blocks.)
#[rpc(name = "z_getsubtreesbyindex")]
fn z_get_subtrees_by_index(
#[method(name = "z_getsubtreesbyindex")]
async fn z_get_subtrees_by_index(
&self,
pool: String,
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
) -> BoxFuture<Result<GetSubtrees>>;
) -> Result<GetSubtrees>;
/// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure.
///
@ -286,12 +280,12 @@ pub trait Rpc {
/// In verbose mode, we only expose the `hex` and `height` fields since
/// lightwalletd uses only those:
/// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L119>
#[rpc(name = "getrawtransaction")]
fn get_raw_transaction(
#[method(name = "getrawtransaction")]
async fn get_raw_transaction(
&self,
txid: String,
verbose: Option<u8>,
) -> BoxFuture<Result<GetRawTransaction>>;
) -> Result<GetRawTransaction>;
/// Returns the transaction ids made by the provided transparent addresses.
///
@ -310,9 +304,8 @@ pub trait Rpc {
///
/// Only the multi-argument format is used by lightwalletd and this is what we currently support:
/// <https://github.com/zcash/lightwalletd/blob/631bb16404e3d8b045e74a7c5489db626790b2f6/common/common.go#L97-L102>
#[rpc(name = "getaddresstxids")]
fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest)
-> BoxFuture<Result<Vec<String>>>;
#[method(name = "getaddresstxids")]
async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>>;
/// Returns all unspent outputs for a list of addresses.
///
@ -328,11 +321,11 @@ pub trait Rpc {
///
/// lightwalletd always uses the multi-address request, without chaininfo:
/// <https://github.com/zcash/lightwalletd/blob/master/frontend/service.go#L402>
#[rpc(name = "getaddressutxos")]
fn get_address_utxos(
#[method(name = "getaddressutxos")]
async fn get_address_utxos(
&self,
address_strings: AddressStrings,
) -> BoxFuture<Result<Vec<GetAddressUtxos>>>;
) -> Result<Vec<GetAddressUtxos>>;
/// Stop the running zebrad process.
///
@ -344,7 +337,7 @@ pub trait Rpc {
/// zcashd reference: [`stop`](https://zcash.github.io/rpc/stop.html)
/// method: post
/// tags: control
#[rpc(name = "stop")]
#[method(name = "stop")]
fn stop(&self) -> Result<String>;
}
@ -516,7 +509,8 @@ where
}
}
impl<Mempool, State, Tip> Rpc for RpcImpl<Mempool, State, Tip>
#[async_trait]
impl<Mempool, State, Tip> RpcServer for RpcImpl<Mempool, State, Tip>
where
Mempool: Service<
mempool::Request,
@ -548,12 +542,11 @@ where
}
#[allow(clippy::unwrap_in_result)]
fn get_blockchain_info(&self) -> BoxFuture<Result<GetBlockChainInfo>> {
async fn get_blockchain_info(&self) -> Result<GetBlockChainInfo> {
let network = self.network.clone();
let debug_force_finished_sync = self.debug_force_finished_sync;
let mut state = self.state.clone();
async move {
// `chain` field
let chain = network.bip70_network_name();
@ -657,15 +650,9 @@ where
Ok(response)
}
.boxed()
}
fn get_address_balance(
&self,
address_strings: AddressStrings,
) -> BoxFuture<Result<AddressBalance>> {
async fn get_address_balance(&self, address_strings: AddressStrings) -> Result<AddressBalance> {
let state = self.state.clone();
async move {
let valid_addresses = address_strings.valid_addresses()?;
let request = zebra_state::ReadRequest::AddressBalance(valid_addresses);
@ -678,18 +665,15 @@ where
_ => unreachable!("Unexpected response from state service: {response:?}"),
}
}
.boxed()
}
// TODO: use HexData or GetRawTransaction::Bytes to handle the transaction data argument
fn send_raw_transaction(
async fn send_raw_transaction(
&self,
raw_transaction_hex: String,
) -> BoxFuture<Result<SentTransactionHash>> {
) -> Result<SentTransactionHash> {
let mempool = self.mempool.clone();
let queue_sender = self.queue_sender.clone();
async move {
// Reference for the legacy error code:
// <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L1259-L1260>
let raw_transaction_bytes = Vec::from_hex(raw_transaction_hex)
@ -739,8 +723,6 @@ where
// error codes.
.map_error(server::error::LegacyCode::Verify)
}
.boxed()
}
// # Performance
//
@ -750,11 +732,7 @@ where
// TODO:
// - use `height_from_signed_int()` to handle negative heights
// (this might be better in the state request, because it needs the state height)
fn get_block(
&self,
hash_or_height: String,
verbosity: Option<u8>,
) -> BoxFuture<Result<GetBlock>> {
async fn get_block(&self, hash_or_height: String, verbosity: Option<u8>) -> Result<GetBlock> {
let mut state = self.state.clone();
let verbosity = verbosity.unwrap_or(1);
let network = self.network.clone();
@ -767,7 +745,6 @@ where
None
};
async move {
let hash_or_height: HashOrHeight = hash_or_height
.parse()
// Reference for the legacy error code:
@ -783,11 +760,10 @@ where
.map_misc_error()?;
match response {
zebra_state::ReadResponse::Block(Some(block)) => {
Ok(GetBlock::Raw(block.into()))
zebra_state::ReadResponse::Block(Some(block)) => Ok(GetBlock::Raw(block.into())),
zebra_state::ReadResponse::Block(None) => {
Err("Block not found").map_error(server::error::LegacyCode::InvalidParameter)
}
zebra_state::ReadResponse::Block(None) => Err("Block not found")
.map_error(server::error::LegacyCode::InvalidParameter),
_ => unreachable!("unmatched response to a block request"),
}
} else if let Some(get_block_header_future) = get_block_header_future {
@ -920,23 +896,19 @@ where
next_block_hash,
})
} else {
Err("invalid verbosity value")
.map_error(server::error::LegacyCode::InvalidParameter)
Err("invalid verbosity value").map_error(server::error::LegacyCode::InvalidParameter)
}
}
.boxed()
}
fn get_block_header(
async fn get_block_header(
&self,
hash_or_height: String,
verbose: Option<bool>,
) -> BoxFuture<Result<GetBlockHeader>> {
) -> Result<GetBlockHeader> {
let state = self.state.clone();
let verbose = verbose.unwrap_or(true);
let network = self.network.clone();
async move {
let hash_or_height: HashOrHeight = hash_or_height
.parse()
.map_error(server::error::LegacyCode::InvalidAddressOrKey)?;
@ -1037,8 +1009,6 @@ where
Ok(response)
}
.boxed()
}
fn get_best_block_hash(&self) -> Result<GetBlockHash> {
self.latest_chain_tip
@ -1054,7 +1024,7 @@ where
.ok_or_misc_error("No blocks in state")
}
fn get_raw_mempool(&self) -> BoxFuture<Result<Vec<String>>> {
async fn get_raw_mempool(&self) -> Result<Vec<String>> {
#[cfg(feature = "getblocktemplate-rpcs")]
use zebra_chain::block::MAX_BLOCK_BYTES;
@ -1064,7 +1034,6 @@ where
let mut mempool = self.mempool.clone();
async move {
#[cfg(feature = "getblocktemplate-rpcs")]
let request = if should_use_zcashd_order {
mempool::Request::FullTransactions
@ -1124,19 +1093,16 @@ where
_ => unreachable!("unmatched response to a transactionids request"),
}
}
.boxed()
}
fn get_raw_transaction(
async fn get_raw_transaction(
&self,
txid: String,
verbose: Option<u8>,
) -> BoxFuture<Result<GetRawTransaction>> {
) -> Result<GetRawTransaction> {
let mut state = self.state.clone();
let mut mempool = self.mempool.clone();
let verbose = verbose.unwrap_or(0) != 0;
async move {
// Reference for the legacy error code:
// <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/rawtransaction.cpp#L544>
let txid = transaction::Hash::from_hex(txid)
@ -1195,17 +1161,14 @@ where
_ => unreachable!("unmatched response to a `Transaction` read request"),
}
}
.boxed()
}
// TODO:
// - use `height_from_signed_int()` to handle negative heights
// (this might be better in the state request, because it needs the state height)
fn z_get_treestate(&self, hash_or_height: String) -> BoxFuture<Result<GetTreestate>> {
async fn z_get_treestate(&self, hash_or_height: String) -> Result<GetTreestate> {
let mut state = self.state.clone();
let network = self.network.clone();
async move {
// Reference for the legacy error code:
// <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/blockchain.cpp#L629>
let hash_or_height = hash_or_height
@ -1285,18 +1248,15 @@ where
hash, height, time, sapling, orchard,
))
}
.boxed()
}
fn z_get_subtrees_by_index(
async fn z_get_subtrees_by_index(
&self,
pool: String,
start_index: NoteCommitmentSubtreeIndex,
limit: Option<NoteCommitmentSubtreeIndex>,
) -> BoxFuture<Result<GetSubtrees>> {
) -> Result<GetSubtrees> {
let mut state = self.state.clone();
async move {
const POOL_LIST: &[&str] = &["sapling", "orchard"];
if pool == "sapling" {
@ -1352,27 +1312,21 @@ where
subtrees,
})
} else {
Err(Error {
code: server::error::LegacyCode::Misc.into(),
message: format!("invalid pool name, must be one of: {:?}", POOL_LIST),
data: None,
})
Err(ErrorObject::owned(
server::error::LegacyCode::Misc.into(),
format!("invalid pool name, must be one of: {:?}", POOL_LIST).as_str(),
None::<()>,
))
}
}
.boxed()
}
fn get_address_tx_ids(
&self,
request: GetAddressTxIdsRequest,
) -> BoxFuture<Result<Vec<String>>> {
async fn get_address_tx_ids(&self, request: GetAddressTxIdsRequest) -> Result<Vec<String>> {
let mut state = self.state.clone();
let latest_chain_tip = self.latest_chain_tip.clone();
let start = Height(request.start);
let end = Height(request.end);
async move {
let chain_height = best_chain_tip_height(&latest_chain_tip)?;
// height range checks
@ -1419,17 +1373,14 @@ where
Ok(hashes)
}
.boxed()
}
fn get_address_utxos(
async fn get_address_utxos(
&self,
address_strings: AddressStrings,
) -> BoxFuture<Result<Vec<GetAddressUtxos>>> {
) -> Result<Vec<GetAddressUtxos>> {
let mut state = self.state.clone();
let mut response_utxos = vec![];
async move {
let valid_addresses = address_strings.valid_addresses()?;
// get utxos data for addresses
@ -1478,33 +1429,31 @@ where
Ok(response_utxos)
}
.boxed()
}
fn stop(&self) -> Result<String> {
#[cfg(not(target_os = "windows"))]
if self.network.is_regtest() {
match nix::sys::signal::raise(nix::sys::signal::SIGINT) {
Ok(_) => Ok("Zebra server stopping".to_string()),
Err(error) => Err(Error {
code: ErrorCode::InternalError,
message: format!("Failed to shut down: {}", error),
data: None,
}),
Err(error) => Err(ErrorObject::owned(
ErrorCode::InternalError.code(),
format!("Failed to shut down: {}", error).as_str(),
None::<()>,
)),
}
} else {
Err(Error {
code: ErrorCode::MethodNotFound,
message: "stop is only available on regtest networks".to_string(),
data: None,
})
Err(ErrorObject::borrowed(
ErrorCode::MethodNotFound.code(),
"stop is only available on regtest networks",
None,
))
}
#[cfg(target_os = "windows")]
Err(Error {
code: ErrorCode::MethodNotFound,
message: "stop is not available in windows targets".to_string(),
data: None,
})
Err(ErrorObject::borrowed(
ErrorCode::MethodNotFound.code(),
"stop is not available in windows targets",
None,
))
}
}
@ -1591,8 +1540,8 @@ impl Default for GetBlockChainInfo {
/// A wrapper type with a list of transparent address strings.
///
/// This is used for the input parameter of [`Rpc::get_address_balance`],
/// [`Rpc::get_address_tx_ids`] and [`Rpc::get_address_utxos`].
/// This is used for the input parameter of [`RpcServer::get_address_balance`],
/// [`RpcServer::get_address_tx_ids`] and [`RpcServer::get_address_utxos`].
#[derive(Clone, Debug, Eq, PartialEq, Hash, serde::Deserialize)]
pub struct AddressStrings {
/// A list of transparent address strings.
@ -1749,7 +1698,7 @@ impl Default for SentTransactionHash {
/// Response to a `getblock` RPC request.
///
/// See the notes for the [`Rpc::get_block`] method.
/// See the notes for the [`RpcServer::get_block`] method.
#[derive(Clone, Debug, PartialEq, serde::Serialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)] //TODO: create a struct for the Object and Box it
@ -1881,7 +1830,7 @@ pub enum GetBlockTransaction {
/// Response to a `getblockheader` RPC request.
///
/// See the notes for the [`Rpc::get_block_header`] method.
/// See the notes for the [`RpcServer::get_block_header`] method.
#[derive(Clone, Debug, PartialEq, serde::Serialize)]
#[serde(untagged)]
pub enum GetBlockHeader {
@ -1895,7 +1844,7 @@ pub enum GetBlockHeader {
#[derive(Clone, Debug, PartialEq, serde::Serialize)]
/// Verbose response to a `getblockheader` RPC request.
///
/// See the notes for the [`Rpc::get_block_header`] method.
/// See the notes for the [`RpcServer::get_block_header`] method.
pub struct GetBlockHeaderObject {
/// The hash of the requested block.
pub hash: GetBlockHash,
@ -1984,7 +1933,7 @@ impl Default for GetBlockHeaderObject {
///
/// Contains the hex-encoded hash of the requested block.
///
/// Also see the notes for the [`Rpc::get_best_block_hash`] and `get_block_hash` methods.
/// Also see the notes for the [`RpcServer::get_best_block_hash`] and `get_block_hash` methods.
#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct GetBlockHash(#[serde(with = "hex")] pub block::Hash);
@ -2200,19 +2149,25 @@ impl OrchardTrees {
/// Check if provided height range is valid for address indexes.
fn check_height_range(start: Height, end: Height, chain_height: Height) -> Result<()> {
if start == Height(0) || end == Height(0) {
return Err(Error::invalid_params(format!(
"start {start:?} and end {end:?} must both be greater than zero"
)));
return Err(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!("start {start:?} and end {end:?} must both be greater than zero"),
None::<()>,
));
}
if start > end {
return Err(Error::invalid_params(format!(
"start {start:?} must be less than or equal to end {end:?}"
)));
return Err(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!("start {start:?} must be less than or equal to end {end:?}"),
None::<()>,
));
}
if start > chain_height || end > chain_height {
return Err(Error::invalid_params(format!(
"start {start:?} and end {end:?} must both be less than or equal to the chain tip {chain_height:?}"
)));
return Err(ErrorObject::owned(
ErrorCode::InvalidParams.code(),
format!("start {start:?} and end {end:?} must both be less than or equal to the chain tip {chain_height:?}"),
None::<()>,
));
}
Ok(())
@ -2230,8 +2185,10 @@ pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height>
if index >= 0 {
let height = index.try_into().expect("Positive i32 always fits in u32");
if height > tip_height.0 {
return Err(Error::invalid_params(
return Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"Provided index is greater than the current tip",
None,
));
}
Ok(Height(height))
@ -2242,17 +2199,27 @@ pub fn height_from_signed_int(index: i32, tip_height: Height) -> Result<Height>
.checked_add(index + 1);
let sanitized_height = match height {
None => return Err(Error::invalid_params("Provided index is not valid")),
None => {
return Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"Provided index is not valid",
None,
))
}
Some(h) => {
if h < 0 {
return Err(Error::invalid_params(
return Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"Provided negative index ends up with a negative height",
None,
));
}
let h: u32 = h.try_into().expect("Positive i32 always fits in u32");
if h > tip_height.0 {
return Err(Error::invalid_params(
return Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"Provided index is greater than the current tip",
None,
));
}

View File

@ -2,9 +2,10 @@
use std::{fmt::Debug, sync::Arc, time::Duration};
use futures::{future::OptionFuture, FutureExt, TryFutureExt};
use jsonrpc_core::{self, BoxFuture, Error, ErrorCode, Result};
use jsonrpc_derive::rpc;
use futures::{future::OptionFuture, TryFutureExt};
use jsonrpsee::core::{async_trait, RpcResult as Result};
use jsonrpsee_proc_macros::rpc;
use jsonrpsee_types::ErrorObject;
use tower::{Service, ServiceExt};
use zcash_address::{unified::Encoding, TryFromAddress};
@ -83,7 +84,7 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
#[rpc(name = "getblockcount")]
#[method(name = "getblockcount")]
fn get_block_count(&self) -> Result<u32>;
/// Returns the hash of the block of a given height iff the index argument correspond
@ -102,8 +103,8 @@ pub trait GetBlockTemplateRpc {
/// - If `index` is positive then index = block height.
/// - If `index` is negative then -1 is the last known valid block.
/// - This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
#[rpc(name = "getblockhash")]
fn get_block_hash(&self, index: i32) -> BoxFuture<Result<GetBlockHash>>;
#[method(name = "getblockhash")]
async fn get_block_hash(&self, index: i32) -> Result<GetBlockHash>;
/// Returns a block template for mining new Zcash blocks.
///
@ -128,11 +129,11 @@ pub trait GetBlockTemplateRpc {
/// so moving between chains and forking chains is very cheap.
///
/// This rpc method is available only if zebra is built with `--features getblocktemplate-rpcs`.
#[rpc(name = "getblocktemplate")]
fn get_block_template(
#[method(name = "getblocktemplate")]
async fn get_block_template(
&self,
parameters: Option<get_block_template::JsonParameters>,
) -> BoxFuture<Result<get_block_template::Response>>;
) -> Result<get_block_template::Response>;
/// Submits block to the node to be validated and committed.
/// Returns the [`submit_block::Response`] for the operation, as a JSON string.
@ -149,20 +150,20 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// - `jsonparametersobject` holds a single field, workid, that must be included in submissions if provided by the server.
#[rpc(name = "submitblock")]
fn submit_block(
#[method(name = "submitblock")]
async fn submit_block(
&self,
hex_data: HexData,
_parameters: Option<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>>;
) -> Result<submit_block::Response>;
/// Returns mining-related information.
///
/// zcashd reference: [`getmininginfo`](https://zcash.github.io/rpc/getmininginfo.html)
/// method: post
/// tags: mining
#[rpc(name = "getmininginfo")]
fn get_mining_info(&self) -> BoxFuture<Result<get_mining_info::Response>>;
#[method(name = "getmininginfo")]
async fn get_mining_info(&self) -> Result<get_mining_info::Response>;
/// Returns the estimated network solutions per second based on the last `num_blocks` before
/// `height`.
@ -174,12 +175,9 @@ pub trait GetBlockTemplateRpc {
/// zcashd reference: [`getnetworksolps`](https://zcash.github.io/rpc/getnetworksolps.html)
/// method: post
/// tags: mining
#[rpc(name = "getnetworksolps")]
fn get_network_sol_ps(
&self,
num_blocks: Option<i32>,
height: Option<i32>,
) -> BoxFuture<Result<u64>>;
#[method(name = "getnetworksolps")]
async fn get_network_sol_ps(&self, num_blocks: Option<i32>, height: Option<i32>)
-> Result<u64>;
/// Returns the estimated network solutions per second based on the last `num_blocks` before
/// `height`.
@ -190,13 +188,13 @@ pub trait GetBlockTemplateRpc {
/// zcashd reference: [`getnetworkhashps`](https://zcash.github.io/rpc/getnetworkhashps.html)
/// method: post
/// tags: mining
#[rpc(name = "getnetworkhashps")]
fn get_network_hash_ps(
#[method(name = "getnetworkhashps")]
async fn get_network_hash_ps(
&self,
num_blocks: Option<i32>,
height: Option<i32>,
) -> BoxFuture<Result<u64>> {
self.get_network_sol_ps(num_blocks, height)
) -> Result<u64> {
self.get_network_sol_ps(num_blocks, height).await
}
/// Returns data about each connected network node.
@ -204,8 +202,8 @@ pub trait GetBlockTemplateRpc {
/// zcashd reference: [`getpeerinfo`](https://zcash.github.io/rpc/getpeerinfo.html)
/// method: post
/// tags: network
#[rpc(name = "getpeerinfo")]
fn get_peer_info(&self) -> BoxFuture<Result<Vec<PeerInfo>>>;
#[method(name = "getpeerinfo")]
async fn get_peer_info(&self) -> Result<Vec<PeerInfo>>;
/// Checks if a zcash address is valid.
/// Returns information about the given address if valid.
@ -221,8 +219,8 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// - No notes
#[rpc(name = "validateaddress")]
fn validate_address(&self, address: String) -> BoxFuture<Result<validate_address::Response>>;
#[method(name = "validateaddress")]
async fn validate_address(&self, address: String) -> Result<validate_address::Response>;
/// Checks if a zcash address is valid.
/// Returns information about the given address if valid.
@ -238,11 +236,11 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// - No notes
#[rpc(name = "z_validateaddress")]
fn z_validate_address(
#[method(name = "z_validateaddress")]
async fn z_validate_address(
&self,
address: String,
) -> BoxFuture<Result<types::z_validate_address::Response>>;
) -> Result<types::z_validate_address::Response>;
/// Returns the block subsidy reward of the block at `height`, taking into account the mining slow start.
/// Returns an error if `height` is less than the height of the first halving for the current network.
@ -258,16 +256,16 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// If `height` is not supplied, uses the tip height.
#[rpc(name = "getblocksubsidy")]
fn get_block_subsidy(&self, height: Option<u32>) -> BoxFuture<Result<BlockSubsidy>>;
#[method(name = "getblocksubsidy")]
async fn get_block_subsidy(&self, height: Option<u32>) -> Result<BlockSubsidy>;
/// Returns the proof-of-work difficulty as a multiple of the minimum difficulty.
///
/// zcashd reference: [`getdifficulty`](https://zcash.github.io/rpc/getdifficulty.html)
/// method: post
/// tags: blockchain
#[rpc(name = "getdifficulty")]
fn get_difficulty(&self) -> BoxFuture<Result<f64>>;
#[method(name = "getdifficulty")]
async fn get_difficulty(&self) -> Result<f64>;
/// Returns the list of individual payment addresses given a unified address.
///
@ -282,13 +280,10 @@ pub trait GetBlockTemplateRpc {
/// # Notes
///
/// - No notes
#[rpc(name = "z_listunifiedreceivers")]
fn z_list_unified_receivers(
&self,
address: String,
) -> BoxFuture<Result<unified_address::Response>>;
#[method(name = "z_listunifiedreceivers")]
async fn z_list_unified_receivers(&self, address: String) -> Result<unified_address::Response>;
#[rpc(name = "generate")]
#[method(name = "generate")]
/// Mine blocks immediately. Returns the block hashes of the generated blocks.
///
/// # Parameters
@ -302,7 +297,7 @@ pub trait GetBlockTemplateRpc {
/// zcashd reference: [`generate`](https://zcash.github.io/rpc/generate.html)
/// method: post
/// tags: generating
fn generate(&self, num_blocks: u32) -> BoxFuture<Result<Vec<GetBlockHash>>>;
async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHash>>;
}
/// RPC method implementations.
@ -536,7 +531,8 @@ where
}
}
impl<Mempool, State, Tip, BlockVerifierRouter, SyncStatus, AddressBook> GetBlockTemplateRpc
#[async_trait]
impl<Mempool, State, Tip, BlockVerifierRouter, SyncStatus, AddressBook> GetBlockTemplateRpcServer
for GetBlockTemplateRpcImpl<Mempool, State, Tip, BlockVerifierRouter, SyncStatus, AddressBook>
where
Mempool: Service<
@ -571,11 +567,10 @@ where
best_chain_tip_height(&self.latest_chain_tip).map(|height| height.0)
}
fn get_block_hash(&self, index: i32) -> BoxFuture<Result<GetBlockHash>> {
async fn get_block_hash(&self, index: i32) -> Result<GetBlockHash> {
let mut state = self.state.clone();
let latest_chain_tip = self.latest_chain_tip.clone();
async move {
// TODO: look up this height as part of the state request?
let tip_height = best_chain_tip_height(&latest_chain_tip)?;
@ -590,21 +585,19 @@ where
match response {
zebra_state::ReadResponse::BlockHash(Some(hash)) => Ok(GetBlockHash(hash)),
zebra_state::ReadResponse::BlockHash(None) => Err(Error {
code: server::error::LegacyCode::InvalidParameter.into(),
message: "Block not found".to_string(),
data: None,
}),
zebra_state::ReadResponse::BlockHash(None) => Err(ErrorObject::borrowed(
server::error::LegacyCode::InvalidParameter.into(),
"Block not found",
None,
)),
_ => unreachable!("unmatched response to a block request"),
}
}
.boxed()
}
fn get_block_template(
async fn get_block_template(
&self,
parameters: Option<get_block_template::JsonParameters>,
) -> BoxFuture<Result<get_block_template::Response>> {
) -> Result<get_block_template::Response> {
// Clone Configs
let network = self.network.clone();
let miner_address = self.miner_address.clone();
@ -628,11 +621,10 @@ where
latest_chain_tip,
sync_status,
)
.boxed();
.await;
}
// To implement long polling correctly, we split this RPC into multiple phases.
async move {
get_block_template::check_parameters(&parameters)?;
let client_long_poll_id = parameters.as_ref().and_then(|params| params.long_poll_id);
@ -926,17 +918,14 @@ where
Ok(response.into())
}
.boxed()
}
fn submit_block(
async fn submit_block(
&self,
HexData(block_bytes): HexData,
_parameters: Option<submit_block::JsonParameters>,
) -> BoxFuture<Result<submit_block::Response>> {
) -> Result<submit_block::Response> {
let mut block_verifier_router = self.block_verifier_router.clone();
async move {
let block: Block = match block_bytes.zcash_deserialize_into() {
Ok(block_bytes) => block_bytes,
Err(error) => {
@ -955,11 +944,7 @@ where
let block_verifier_router_response = block_verifier_router
.ready()
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
.call(zebra_consensus::Request::Commit(Arc::new(block)))
.await;
@ -982,16 +967,19 @@ where
.downcast::<RouterError>()
.map(|boxed_chain_error| *boxed_chain_error);
tracing::info!(?error, ?block_hash, ?block_height, "submit block failed verification");
tracing::info!(
?error,
?block_hash,
?block_height,
"submit block failed verification"
);
error
}
};
let response = match chain_error {
Ok(source) if source.is_duplicate_request() => {
submit_block::ErrorResponse::Duplicate
}
Ok(source) if source.is_duplicate_request() => submit_block::ErrorResponse::Duplicate,
// Currently, these match arms return Reject for the older duplicate in a queue,
// but queued duplicates should be DuplicateInconclusive.
@ -1017,10 +1005,8 @@ where
Ok(response.into())
}
.boxed()
}
fn get_mining_info(&self) -> BoxFuture<Result<get_mining_info::Response>> {
async fn get_mining_info(&self) -> Result<get_mining_info::Response> {
let network = self.network.clone();
let mut state = self.state.clone();
@ -1035,7 +1021,6 @@ where
}
let solution_rate_fut = self.get_network_sol_ps(None, None);
async move {
// Get the current block size.
let mut current_block_size = None;
if tip_height > 0 {
@ -1059,14 +1044,12 @@ where
solution_rate_fut.await?,
))
}
.boxed()
}
fn get_network_sol_ps(
async fn get_network_sol_ps(
&self,
num_blocks: Option<i32>,
height: Option<i32>,
) -> BoxFuture<Result<u64>> {
) -> Result<u64> {
// Default number of blocks is 120 if not supplied.
let mut num_blocks = num_blocks.unwrap_or(DEFAULT_SOLUTION_RATE_WINDOW_SIZE);
// But if it is 0 or negative, it uses the proof of work averaging window.
@ -1082,18 +1065,13 @@ where
let mut state = self.state.clone();
async move {
let request = ReadRequest::SolutionRate { num_blocks, height };
let response = state
.ready()
.and_then(|service| service.call(request))
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
let solution_rate = match response {
// zcashd returns a 0 rate when the calculation is invalid
@ -1106,35 +1084,24 @@ where
.try_into()
.expect("per-second solution rate always fits in u64"))
}
.boxed()
}
fn get_peer_info(&self) -> BoxFuture<Result<Vec<PeerInfo>>> {
async fn get_peer_info(&self) -> Result<Vec<PeerInfo>> {
let address_book = self.address_book.clone();
async move {
Ok(address_book
.recently_live_peers(chrono::Utc::now())
.into_iter()
.map(PeerInfo::from)
.collect())
}
.boxed()
}
fn validate_address(
&self,
raw_address: String,
) -> BoxFuture<Result<validate_address::Response>> {
async fn validate_address(&self, raw_address: String) -> Result<validate_address::Response> {
let network = self.network.clone();
async move {
let Ok(address) = raw_address
.parse::<zcash_address::ZcashAddress>() else {
let Ok(address) = raw_address.parse::<zcash_address::ZcashAddress>() else {
return Ok(validate_address::Response::invalid());
};
let address = match address
.convert::<primitives::Address>() {
let address = match address.convert::<primitives::Address>() {
Ok(address) => address,
Err(err) => {
tracing::debug!(?err, "conversion error");
@ -1163,23 +1130,18 @@ where
Ok(validate_address::Response::invalid())
}
}
.boxed()
}
fn z_validate_address(
async fn z_validate_address(
&self,
raw_address: String,
) -> BoxFuture<Result<types::z_validate_address::Response>> {
) -> Result<types::z_validate_address::Response> {
let network = self.network.clone();
async move {
let Ok(address) = raw_address
.parse::<zcash_address::ZcashAddress>() else {
let Ok(address) = raw_address.parse::<zcash_address::ZcashAddress>() else {
return Ok(z_validate_address::Response::invalid());
};
let address = match address
.convert::<primitives::Address>() {
let address = match address.convert::<primitives::Address>() {
Ok(address) => address,
Err(err) => {
tracing::debug!(?err, "conversion error");
@ -1206,14 +1168,11 @@ where
Ok(z_validate_address::Response::invalid())
}
}
.boxed()
}
fn get_block_subsidy(&self, height: Option<u32>) -> BoxFuture<Result<BlockSubsidy>> {
async fn get_block_subsidy(&self, height: Option<u32>) -> Result<BlockSubsidy> {
let latest_chain_tip = self.latest_chain_tip.clone();
let network = self.network.clone();
async move {
let height = if let Some(height) = height {
Height(height)
} else {
@ -1221,13 +1180,12 @@ where
};
if height < network.height_for_first_halving() {
return Err(Error {
code: ErrorCode::ServerError(0),
message: "Zebra does not support founders' reward subsidies, \
use a block height that is after the first halving"
.into(),
data: None,
});
return Err(ErrorObject::borrowed(
0,
"Zebra does not support founders' reward subsidies, \
use a block height that is after the first halving",
None,
));
}
// Always zero for post-halving blocks
@ -1261,8 +1219,8 @@ where
});
// Format the funding streams and lockbox streams
let [funding_streams, lockbox_streams]: [Vec<_>; 2] =
[funding_streams, lockbox_streams].map(|streams| {
let [funding_streams, lockbox_streams]: [Vec<_>; 2] = [funding_streams, lockbox_streams]
.map(|streams| {
streams
.into_iter()
.map(|(receiver, value)| {
@ -1286,14 +1244,11 @@ where
total_block_subsidy: total_block_subsidy.into(),
})
}
.boxed()
}
fn get_difficulty(&self) -> BoxFuture<Result<f64>> {
async fn get_difficulty(&self) -> Result<f64> {
let network = self.network.clone();
let mut state = self.state.clone();
async move {
let request = ReadRequest::ChainInfo;
// # TODO
@ -1305,11 +1260,7 @@ where
.ready()
.and_then(|service| service.call(request))
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
let chain_info = match response {
ReadResponse::ChainInfo(info) => info,
@ -1357,26 +1308,13 @@ where
// Invert the division to give approximately: `work(difficulty) / work(pow_limit)`
Ok(pow_limit / difficulty)
}
.boxed()
}
fn z_list_unified_receivers(
&self,
address: String,
) -> BoxFuture<Result<unified_address::Response>> {
async fn z_list_unified_receivers(&self, address: String) -> Result<unified_address::Response> {
use zcash_address::unified::Container;
async move {
let (network, unified_address): (
zcash_address::Network,
zcash_address::unified::Address,
) = zcash_address::unified::Encoding::decode(address.clone().as_str()).map_err(
|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
},
)?;
let (network, unified_address): (zcash_address::Network, zcash_address::unified::Address) =
zcash_address::unified::Encoding::decode(address.clone().as_str())
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
let mut p2pkh = String::new();
let mut p2sh = String::new();
@ -1391,22 +1329,19 @@ where
orchard = addr.encode(&network);
}
zcash_address::unified::Receiver::Sapling(data) => {
let addr =
zebra_chain::primitives::Address::try_from_sapling(network, data)
let addr = zebra_chain::primitives::Address::try_from_sapling(network, data)
.expect("using data already decoded as valid");
sapling = addr.payment_address().unwrap_or_default();
}
zcash_address::unified::Receiver::P2pkh(data) => {
let addr = zebra_chain::primitives::Address::try_from_transparent_p2pkh(
network, data,
)
let addr =
zebra_chain::primitives::Address::try_from_transparent_p2pkh(network, data)
.expect("using data already decoded as valid");
p2pkh = addr.payment_address().unwrap_or_default();
}
zcash_address::unified::Receiver::P2sh(data) => {
let addr = zebra_chain::primitives::Address::try_from_transparent_p2sh(
network, data,
)
let addr =
zebra_chain::primitives::Address::try_from_transparent_p2sh(network, data)
.expect("using data already decoded as valid");
p2sh = addr.payment_address().unwrap_or_default();
}
@ -1418,10 +1353,8 @@ where
orchard, sapling, p2pkh, p2sh,
))
}
.boxed()
}
fn generate(&self, num_blocks: u32) -> BoxFuture<Result<Vec<GetBlockHash>>> {
async fn generate(&self, num_blocks: u32) -> Result<Vec<GetBlockHash>> {
let rpc: GetBlockTemplateRpcImpl<
Mempool,
State,
@ -1432,13 +1365,12 @@ where
> = self.clone();
let network = self.network.clone();
async move {
if !network.is_regtest() {
return Err(Error {
code: ErrorCode::ServerError(0),
message: "generate is only supported on regtest".to_string(),
data: None,
});
return Err(ErrorObject::borrowed(
0,
"generate is only supported on regtest",
None,
));
}
let mut block_hashes = Vec::new();
@ -1448,13 +1380,12 @@ where
.await
.map_error(server::error::LegacyCode::default())?;
let get_block_template::Response::TemplateMode(block_template) = block_template
else {
return Err(Error {
code: ErrorCode::ServerError(0),
message: "error generating block template".to_string(),
data: None,
});
let get_block_template::Response::TemplateMode(block_template) = block_template else {
return Err(ErrorObject::borrowed(
0,
"error generating block template",
None,
));
};
let proposal_block = proposal_block_from_template(
@ -1479,8 +1410,6 @@ where
Ok(block_hashes)
}
.boxed()
}
}
// Put support functions in a submodule, to keep this file small.

View File

@ -1,6 +1,6 @@
//! Constant values used in mining rpcs methods.
use jsonrpc_core::ErrorCode;
use jsonrpsee_types::ErrorCode;
use zebra_chain::{
block,

View File

@ -2,7 +2,8 @@
use std::{collections::HashMap, iter, sync::Arc};
use jsonrpc_core::{Error, ErrorCode, Result};
use jsonrpsee::core::RpcResult as Result;
use jsonrpsee_types::{ErrorCode, ErrorObject};
use tower::{Service, ServiceExt};
use zebra_chain::{
@ -61,25 +62,23 @@ pub fn check_parameters(parameters: &Option<JsonParameters>) -> Result<()> {
mode: GetBlockTemplateRequestMode::Proposal,
data: None,
..
} => Err(Error {
code: ErrorCode::InvalidParams,
message: "\"data\" parameter must be \
provided in \"proposal\" mode"
.to_string(),
data: None,
}),
} => Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"\"data\" parameter must be \
provided in \"proposal\" mode",
None,
)),
JsonParameters {
mode: GetBlockTemplateRequestMode::Template,
data: Some(_),
..
} => Err(Error {
code: ErrorCode::InvalidParams,
message: "\"data\" parameter must be \
omitted in \"template\" mode"
.to_string(),
data: None,
}),
} => Err(ErrorObject::borrowed(
ErrorCode::InvalidParams.code(),
"\"data\" parameter must be \
omitted in \"template\" mode",
None,
)),
}
}
@ -131,11 +130,7 @@ where
let block_verifier_router_response = block_verifier_router
.ready()
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?
.call(zebra_consensus::Request::CheckProposal(Arc::new(block)))
.await;
@ -189,16 +184,14 @@ where
Hint: check your network connection, clock, and time zone settings."
);
return Err(Error {
code: NOT_SYNCED_ERROR_CODE,
message: format!(
return Err(ErrorObject::borrowed(
NOT_SYNCED_ERROR_CODE.code(),
"Zebra has not synced to the chain tip, \
estimated distance: {estimated_distance_to_chain_tip:?}, \
local tip: {local_tip_height:?}. \
Hint: check your network connection, clock, and time zone settings."
),
data: None,
});
Hint: check your network connection, clock, and time zone settings.",
None,
));
}
Ok(())
@ -227,11 +220,7 @@ where
let response = state
.oneshot(request.clone())
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
let chain_info = match response {
zebra_state::ReadResponse::ChainInfo(chain_info) => chain_info,
@ -261,11 +250,7 @@ where
let response = mempool
.oneshot(mempool::Request::FullTransactions)
.await
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
.map_err(|error| ErrorObject::owned(0, error.to_string(), None::<()>))?;
// TODO: Order transactions in block templates based on their dependencies

View File

@ -3,7 +3,7 @@
use zebra_chain::parameters::Network;
/// Response to a `getmininginfo` RPC request.
#[derive(Debug, Default, PartialEq, Eq, serde::Serialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize)]
pub struct Response {
/// The current tip height.
#[serde(rename = "blocks")]

View File

@ -2,11 +2,11 @@
// Allow doc links to these imports.
#[allow(unused_imports)]
use crate::methods::get_block_template_rpcs::GetBlockTemplateRpc;
use crate::methods::get_block_template_rpcs::GetBlockTemplate;
/// Optional argument `jsonparametersobject` for `submitblock` RPC request
///
/// See notes for [`GetBlockTemplateRpc::submit_block`] method
/// See notes for [`crate::methods::GetBlockTemplateRpcServer::submit_block`] method
#[derive(Clone, Debug, PartialEq, Eq, serde::Deserialize)]
pub struct JsonParameters {
/// The workid for the block template. Currently unused.
@ -28,7 +28,7 @@ pub struct JsonParameters {
/// Response to a `submitblock` RPC request.
///
/// Zebra never returns "duplicate-invalid", because it does not store invalid blocks.
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ErrorResponse {
/// Block was already committed to the non-finalized or finalized state
@ -44,7 +44,7 @@ pub enum ErrorResponse {
/// Response to a `submitblock` RPC request.
///
/// Zebra never returns "duplicate-invalid", because it does not store invalid blocks.
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum Response {
/// Block was not successfully submitted, return error

View File

@ -4,7 +4,7 @@ use std::{collections::HashSet, fmt::Debug, sync::Arc};
use futures::{join, FutureExt, TryFutureExt};
use hex::{FromHex, ToHex};
use jsonrpc_core::{Error, ErrorCode};
use jsonrpsee_types::{ErrorCode, ErrorObject};
use proptest::{collection::vec, prelude::*};
use thiserror::Error;
use tokio::sync::oneshot;
@ -28,7 +28,7 @@ use zebra_test::mock_service::MockService;
use crate::methods;
use super::super::{
AddressBalance, AddressStrings, NetworkUpgradeStatus, Rpc, RpcImpl, SentTransactionHash,
AddressBalance, AddressStrings, NetworkUpgradeStatus, RpcImpl, RpcServer, SentTransactionHash,
};
proptest! {
@ -49,7 +49,7 @@ proptest! {
let transaction_hex = hex::encode(&transaction_bytes);
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex));
let send_task = tokio::spawn(async move { rpc.send_raw_transaction(transaction_hex).await });
let unmined_transaction = UnminedTx::from(transaction);
let expected_request = mempool::Request::Queue(vec![unmined_transaction.into()]);
@ -64,7 +64,7 @@ proptest! {
state.expect_no_requests().await?;
let result = send_task.await?;
let result = send_task.await.expect("send_raw_transaction should not panic");
prop_assert_eq!(result, Ok(hash));
@ -91,7 +91,9 @@ proptest! {
let transaction_bytes = transaction.zcash_serialize_to_vec()?;
let transaction_hex = hex::encode(&transaction_bytes);
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex.clone()));
let _rpc = rpc.clone();
let _transaction_hex = transaction_hex.clone();
let send_task = tokio::spawn(async move { _rpc.send_raw_transaction(_transaction_hex).await });
let unmined_transaction = UnminedTx::from(transaction);
let expected_request = mempool::Request::Queue(vec![unmined_transaction.clone().into()]);
@ -103,11 +105,11 @@ proptest! {
state.expect_no_requests().await?;
let result = send_task.await?;
let result = send_task.await.expect("send_raw_transaction should not panic");
check_err_code(result, ErrorCode::ServerError(-1))?;
let send_task = tokio::spawn(rpc.send_raw_transaction(transaction_hex));
let send_task = tokio::spawn(async move { rpc.send_raw_transaction(transaction_hex.clone()).await });
let expected_request = mempool::Request::Queue(vec![unmined_transaction.clone().into()]);
@ -118,7 +120,7 @@ proptest! {
.await?
.respond(Ok::<_, BoxError>(mempool::Response::Queued(vec![Ok(rsp_rx)])));
let result = send_task.await?;
let result = send_task.await.expect("send_raw_transaction should not panic");
check_err_code(result, ErrorCode::ServerError(-25))?;
@ -173,13 +175,13 @@ proptest! {
tokio::time::pause();
runtime.block_on(async move {
let send_task = tokio::spawn(rpc.send_raw_transaction(non_hex_string));
let send_task = rpc.send_raw_transaction(non_hex_string);
// Check that there are no further requests.
mempool.expect_no_requests().await?;
state.expect_no_requests().await?;
check_err_code(send_task.await?, ErrorCode::ServerError(-22))?;
check_err_code(send_task.await, ErrorCode::ServerError(-22))?;
// The queue task should continue without errors or panics
prop_assert!(mempool_tx_queue.now_or_never().is_none());
@ -204,12 +206,12 @@ proptest! {
prop_assume!(Transaction::zcash_deserialize(&*random_bytes).is_err());
runtime.block_on(async move {
let send_task = tokio::spawn(rpc.send_raw_transaction(hex::encode(random_bytes)));
let send_task = rpc.send_raw_transaction(hex::encode(random_bytes));
mempool.expect_no_requests().await?;
state.expect_no_requests().await?;
check_err_code(send_task.await?, ErrorCode::ServerError(-22))?;
check_err_code(send_task.await, ErrorCode::ServerError(-22))?;
// The queue task should continue without errors or panics
prop_assert!(mempool_tx_queue.now_or_never().is_none());
@ -374,8 +376,8 @@ proptest! {
let (response, _) = tokio::join!(response_fut, mock_state_handler);
prop_assert_eq!(
&response.err().unwrap().message,
"no chain tip available yet"
response.err().unwrap().message().to_string(),
"no chain tip available yet".to_string()
);
mempool.expect_no_requests().await?;
@ -603,8 +605,10 @@ proptest! {
let transaction_hash = tx.hash();
let tx_bytes = tx.zcash_serialize_to_vec()?;
let tx_hex = hex::encode(&tx_bytes);
let send_task = tokio::spawn(rpc.send_raw_transaction(tx_hex));
let send_task = {
let rpc = rpc.clone();
tokio::task::spawn(async move { rpc.send_raw_transaction(tx_hex).await })
};
let tx_unmined = UnminedTx::from(tx);
let expected_request = mempool::Request::Queue(vec![tx_unmined.clone().into()]);
@ -678,10 +682,11 @@ proptest! {
runtime.block_on(async move {
let mut transactions_hash_set = HashSet::new();
for tx in txs.clone() {
let rpc_clone = rpc.clone();
// send a transaction
let tx_bytes = tx.zcash_serialize_to_vec()?;
let tx_hex = hex::encode(&tx_bytes);
let send_task = tokio::spawn(rpc.send_raw_transaction(tx_hex));
let send_task = tokio::task::spawn(async move { rpc_clone.send_raw_transaction(tx_hex).await });
let tx_unmined = UnminedTx::from(tx.clone());
let expected_request = mempool::Request::Queue(vec![tx_unmined.clone().into()]);
@ -768,11 +773,22 @@ fn invalid_txid() -> BoxedStrategy<String> {
}
/// Checks that the given RPC response contains the given error code.
fn check_err_code<T>(rsp: Result<T, Error>, error_code: ErrorCode) -> Result<(), TestCaseError> {
fn check_err_code<T>(
rsp: Result<T, ErrorObject>,
error_code: ErrorCode,
) -> Result<(), TestCaseError> {
match rsp {
Err(e) => {
prop_assert!(
matches!(&rsp, Err(Error { code, .. }) if *code == error_code),
"the RPC response must match the error code: {error_code:?}"
e.code() == error_code.code(),
"the RPC response must match the error code: {:?}",
error_code.code()
);
}
Ok(_) => {
prop_assert!(false, "expected an error response, but got Ok");
}
}
Ok(())
}

View File

@ -7,7 +7,9 @@
use std::{collections::BTreeMap, sync::Arc};
use futures::FutureExt;
use insta::dynamic_redaction;
use jsonrpsee::core::RpcResult as Result;
use tower::buffer::Buffer;
use zebra_chain::{

View File

@ -12,7 +12,7 @@ use std::{
use hex::FromHex;
use insta::Settings;
use jsonrpc_core::Result;
use jsonrpsee::core::RpcResult as Result;
use tower::{buffer::Buffer, Service};
use zebra_chain::{
@ -47,7 +47,7 @@ use crate::methods::{
},
hex_data::HexData,
tests::{snapshot::EXCESSIVE_BLOCK_HEIGHT, utils::fake_history_tree},
GetBlockHash, GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
GetBlockHash, GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer,
};
pub async fn test_responses<State, ReadState>(
@ -488,19 +488,17 @@ pub async fn test_responses<State, ReadState>(
// `z_listunifiedreceivers`
let ua1 = String::from("u1l8xunezsvhq8fgzfl7404m450nwnd76zshscn6nfys7vyz2ywyh4cc5daaq0c7q2su5lqfh23sp7fkf3kt27ve5948mzpfdvckzaect2jtte308mkwlycj2u0eac077wu70vqcetkxf");
let z_list_unified_receivers =
tokio::spawn(get_block_template_rpc.z_list_unified_receivers(ua1))
let z_list_unified_receivers = get_block_template_rpc
.z_list_unified_receivers(ua1)
.await
.expect("unexpected panic in z_list_unified_receivers RPC task")
.expect("unexpected error in z_list_unified_receivers RPC call");
snapshot_rpc_z_listunifiedreceivers("ua1", z_list_unified_receivers, &settings);
let ua2 = String::from("u1uf4qsmh037x2jp6k042h9d2w22wfp39y9cqdf8kcg0gqnkma2gf4g80nucnfeyde8ev7a6kf0029gnwqsgadvaye9740gzzpmr67nfkjjvzef7rkwqunqga4u4jges4tgptcju5ysd0");
let z_list_unified_receivers =
tokio::spawn(get_block_template_rpc.z_list_unified_receivers(ua2))
let z_list_unified_receivers = get_block_template_rpc
.z_list_unified_receivers(ua2)
.await
.expect("unexpected panic in z_list_unified_receivers RPC task")
.expect("unexpected error in z_list_unified_receivers RPC call");
snapshot_rpc_z_listunifiedreceivers("ua2", z_list_unified_receivers, &settings);

View File

@ -3,6 +3,7 @@
use std::ops::RangeInclusive;
use std::sync::Arc;
use futures::FutureExt;
use tower::buffer::Buffer;
use zebra_chain::serialization::ZcashSerialize;
@ -495,7 +496,7 @@ async fn rpc_getblock_missing_error() {
// Make sure Zebra returns the correct error code `-8` for missing blocks
// https://github.com/zcash/lightwalletd/blob/v0.4.16/common/common.go#L287-L290
let block_future = tokio::spawn(rpc.get_block("0".to_string(), Some(0u8)));
let block_future = tokio::spawn(async move { rpc.get_block("0".to_string(), Some(0u8)).await });
// Make the mock service respond with no block
let response_handler = state
@ -503,11 +504,10 @@ async fn rpc_getblock_missing_error() {
.await;
response_handler.respond(zebra_state::ReadResponse::Block(None));
let block_response = block_future.await;
let block_response = block_response
.expect("unexpected panic in spawned request future")
.expect_err("unexpected success from missing block state response");
assert_eq!(block_response.code, ErrorCode::ServerError(-8),);
let block_response = block_future.await.expect("block future should not panic");
let block_response =
block_response.expect_err("unexpected success from missing block state response");
assert_eq!(block_response.code(), ErrorCode::ServerError(-8).code());
// Now check the error string the way `lightwalletd` checks it
assert_eq!(
@ -898,7 +898,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
.await
.unwrap_err();
assert_eq!(rpc_rsp.code, ErrorCode::ServerError(-5));
assert_eq!(rpc_rsp.code(), ErrorCode::ServerError(-5).code());
mempool.expect_no_requests().await;
@ -918,7 +918,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
.await
.unwrap_err();
assert_eq!(
error.message,
error.message(),
"start Height(2) must be less than or equal to end Height(1)".to_string()
);
@ -934,7 +934,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
.await
.unwrap_err();
assert_eq!(
error.message,
error.message(),
"start Height(0) and end Height(1) must both be greater than zero".to_string()
);
@ -950,7 +950,7 @@ async fn rpc_getaddresstxids_invalid_arguments() {
.await
.unwrap_err();
assert_eq!(
error.message,
error.message(),
"start Height(1) and end Height(11) must both be less than or equal to the chain tip Height(10)".to_string()
);
@ -1096,7 +1096,7 @@ async fn rpc_getaddressutxos_invalid_arguments() {
.await
.unwrap_err();
assert_eq!(error.code, ErrorCode::ServerError(-5));
assert_eq!(error.code(), ErrorCode::ServerError(-5).code());
mempool.expect_no_requests().await;
state.expect_no_requests().await;
@ -1253,7 +1253,10 @@ async fn rpc_getblockcount_empty_state() {
assert!(get_block_count.is_err());
// Check the error we got is the correct one
assert_eq!(get_block_count.err().unwrap().message, "No blocks in state");
assert_eq!(
get_block_count.err().unwrap().message(),
"No blocks in state"
);
mempool.expect_no_requests().await;
}
@ -1697,8 +1700,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.expect_err("needs an error when estimated distance to network chain tip is far");
assert_eq!(
get_block_template_sync_error.code,
ErrorCode::ServerError(-10)
get_block_template_sync_error.code(),
ErrorCode::ServerError(-10).code()
);
mock_sync_status.set_is_close_to_tip(false);
@ -1710,8 +1713,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.expect_err("needs an error when syncer is not close to tip");
assert_eq!(
get_block_template_sync_error.code,
ErrorCode::ServerError(-10)
get_block_template_sync_error.code(),
ErrorCode::ServerError(-10).code()
);
mock_chain_tip_sender.send_estimated_distance_to_network_chain_tip(Some(200));
@ -1721,8 +1724,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.expect_err("needs an error when syncer is not close to tip or estimated distance to network chain tip is far");
assert_eq!(
get_block_template_sync_error.code,
ErrorCode::ServerError(-10)
get_block_template_sync_error.code(),
ErrorCode::ServerError(-10).code()
);
let get_block_template_sync_error = get_block_template_rpc
@ -1733,7 +1736,10 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.await
.expect_err("needs an error when called in proposal mode without data");
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
assert_eq!(
get_block_template_sync_error.code(),
ErrorCode::InvalidParams.code()
);
let get_block_template_sync_error = get_block_template_rpc
.get_block_template(Some(get_block_template::JsonParameters {
@ -1743,7 +1749,10 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.await
.expect_err("needs an error when passing in block data in template mode");
assert_eq!(get_block_template_sync_error.code, ErrorCode::InvalidParams);
assert_eq!(
get_block_template_sync_error.code(),
ErrorCode::InvalidParams.code()
);
// The long poll id is valid, so it returns a state error instead
let get_block_template_sync_error = get_block_template_rpc
@ -1761,8 +1770,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
.expect_err("needs an error when the state is empty");
assert_eq!(
get_block_template_sync_error.code,
ErrorCode::ServerError(-10)
get_block_template_sync_error.code(),
ErrorCode::ServerError(-10).code()
);
// Try getting mempool transactions with a different tip hash

View File

@ -7,12 +7,11 @@
//! See the full list of
//! [Differences between JSON-RPC 1.0 and 2.0.](https://www.simple-is-better.org/rpc/#differences-between-1-0-and-2-0)
use std::{fmt, panic, thread::available_parallelism};
use std::{fmt, panic};
use cookie::Cookie;
use http_request_compatibility::With;
use jsonrpc_core::{Compatibility, MetaIoHandler};
use jsonrpc_http_server::{CloseHandle, ServerBuilder};
use jsonrpsee::server::middleware::rpc::RpcServiceBuilder;
use jsonrpsee::server::{Server, ServerHandle};
use tokio::task::JoinHandle;
use tower::Service;
use tracing::*;
@ -25,15 +24,15 @@ use zebra_node_services::mempool;
use crate::{
config::Config,
methods::{Rpc, RpcImpl},
methods::{RpcImpl, RpcServer as _},
server::{
http_request_compatibility::HttpRequestMiddleware,
http_request_compatibility::HttpRequestMiddlewareLayer,
rpc_call_compatibility::FixRpcResponseMiddleware,
},
};
#[cfg(feature = "getblocktemplate-rpcs")]
use crate::methods::{GetBlockTemplateRpc, GetBlockTemplateRpcImpl};
use crate::methods::{GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer};
pub mod cookie;
pub mod error;
@ -55,8 +54,8 @@ pub struct RpcServer {
/// Zebra's application version, with build metadata.
build_version: String,
/// A handle that shuts down the RPC server.
close_handle: CloseHandle,
/// A server handle used to shuts down the RPC server.
close_handle: ServerHandle,
}
impl fmt::Debug for RpcServer {
@ -68,7 +67,7 @@ impl fmt::Debug for RpcServer {
.field(
"close_handle",
// TODO: when it stabilises, use std::any::type_name_of_val(&self.close_handle)
&"CloseHandle",
&"ServerHandle",
)
.finish()
}
@ -77,6 +76,8 @@ impl fmt::Debug for RpcServer {
/// The message to log when logging the RPC server's listen address
pub const OPENED_RPC_ENDPOINT_MSG: &str = "Opened RPC endpoint at ";
type ServerTask = JoinHandle<Result<(), tower::BoxError>>;
impl RpcServer {
/// Start a new RPC server endpoint using the supplied configs and services.
///
@ -90,7 +91,7 @@ impl RpcServer {
// - put some of the configs or services in their own struct?
// - replace VersionString with semver::Version, and update the tests to provide valid versions
#[allow(clippy::too_many_arguments)]
pub fn spawn<
pub async fn spawn<
VersionString,
UserAgentString,
Mempool,
@ -115,7 +116,7 @@ impl RpcServer {
address_book: AddressBook,
latest_chain_tip: Tip,
network: Network,
) -> (JoinHandle<()>, JoinHandle<()>, Option<Self>)
) -> Result<(ServerTask, JoinHandle<()>), tower::BoxError>
where
VersionString: ToString + Clone + Send + 'static,
UserAgentString: ToString + Clone + Send + 'static,
@ -150,15 +151,11 @@ impl RpcServer {
SyncStatus: ChainSyncStatus + Clone + Send + Sync + 'static,
AddressBook: AddressBookPeers + Clone + Send + Sync + 'static,
{
if let Some(listen_addr) = config.listen_addr {
info!("Trying to open RPC endpoint at {}...", listen_addr,);
// Create handler compatible with V1 and V2 RPC protocols
let mut io: MetaIoHandler<(), _> =
MetaIoHandler::new(Compatibility::Both, FixRpcResponseMiddleware);
let listen_addr = config
.listen_addr
.expect("caller should make sure listen_addr is set");
#[cfg(feature = "getblocktemplate-rpcs")]
{
// Initialize the getblocktemplate rpc method handler
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
&network,
@ -171,9 +168,6 @@ impl RpcServer {
address_book,
);
io.extend_with(get_block_template_rpc_impl.to_delegate());
}
// Initialize the rpc methods with the zebra version
let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new(
build_version.clone(),
@ -189,97 +183,47 @@ impl RpcServer {
latest_chain_tip,
);
io.extend_with(rpc_impl.to_delegate());
// If zero, automatically scale threads to the number of CPU cores
let mut parallel_cpu_threads = config.parallel_cpu_threads;
if parallel_cpu_threads == 0 {
parallel_cpu_threads = available_parallelism().map(usize::from).unwrap_or(1);
}
// The server is a blocking task, which blocks on executor shutdown.
// So we need to start it in a std::thread.
// (Otherwise tokio panics on RPC port conflict, which shuts down the RPC server.)
let span = Span::current();
let start_server = move || {
span.in_scope(|| {
let middleware = if config.enable_cookie_auth {
let http_middleware_layer = if config.enable_cookie_auth {
let cookie = Cookie::default();
cookie::write_to_disk(&cookie, &config.cookie_dir)
.expect("Zebra must be able to write the auth cookie to the disk");
HttpRequestMiddleware::default().with(cookie)
HttpRequestMiddlewareLayer::new(Some(cookie))
} else {
HttpRequestMiddleware::default()
HttpRequestMiddlewareLayer::new(None)
};
// Use a different tokio executor from the rest of Zebra,
// so that large RPCs and any task handling bugs don't impact Zebra.
let server_instance = ServerBuilder::new(io)
.threads(parallel_cpu_threads)
// TODO: disable this security check if we see errors from lightwalletd
//.allowed_hosts(DomainsValidation::Disabled)
.request_middleware(middleware)
.start_http(&listen_addr)
let http_middleware = tower::ServiceBuilder::new().layer(http_middleware_layer);
let rpc_middleware = RpcServiceBuilder::new()
.rpc_logger(1024)
.layer_fn(FixRpcResponseMiddleware::new);
let server_instance = Server::builder()
.http_only()
.set_http_middleware(http_middleware)
.set_rpc_middleware(rpc_middleware)
.build(listen_addr)
.await
.expect("Unable to start RPC server");
let addr = server_instance
.local_addr()
.expect("Unable to get local address");
info!("{OPENED_RPC_ENDPOINT_MSG}{}", addr);
info!("{OPENED_RPC_ENDPOINT_MSG}{}", server_instance.address());
#[cfg(feature = "getblocktemplate-rpcs")]
let mut rpc_module = rpc_impl.into_rpc();
#[cfg(not(feature = "getblocktemplate-rpcs"))]
let rpc_module = rpc_impl.into_rpc();
#[cfg(feature = "getblocktemplate-rpcs")]
rpc_module
.merge(get_block_template_rpc_impl.into_rpc())
.unwrap();
let close_handle = server_instance.close_handle();
let rpc_server_handle = RpcServer {
config,
network,
build_version: build_version.to_string(),
close_handle,
};
(server_instance, rpc_server_handle)
})
};
// Propagate panics from the std::thread
let (server_instance, rpc_server_handle) = match std::thread::spawn(start_server).join()
{
Ok(rpc_server) => rpc_server,
Err(panic_object) => panic::resume_unwind(panic_object),
};
// The server is a blocking task, which blocks on executor shutdown.
// So we need to wait on it on a std::thread, inside a tokio blocking task.
// (Otherwise tokio panics when we shut down the RPC server.)
let span = Span::current();
let wait_on_server = move || {
span.in_scope(|| {
server_instance.wait();
info!("Stopped RPC endpoint");
})
};
let span = Span::current();
let rpc_server_task_handle = tokio::task::spawn_blocking(move || {
let thread_handle = std::thread::spawn(wait_on_server);
// Propagate panics from the inner std::thread to the outer tokio blocking task
span.in_scope(|| match thread_handle.join() {
Ok(()) => (),
Err(panic_object) => panic::resume_unwind(panic_object),
})
let server_task: JoinHandle<Result<(), tower::BoxError>> = tokio::spawn(async move {
server_instance.start(rpc_module).stopped().await;
Ok(())
});
(
rpc_server_task_handle,
rpc_tx_queue_task_handle,
Some(rpc_server_handle),
)
} else {
// There is no RPC port, so the RPC tasks do nothing.
(
tokio::task::spawn(futures::future::pending().in_current_span()),
tokio::task::spawn(futures::future::pending().in_current_span()),
None,
)
}
Ok((server_task, rpc_tx_queue_task_handle))
}
/// Shut down this RPC server, blocking the current thread.
@ -305,7 +249,7 @@ impl RpcServer {
/// Shuts down this RPC server using its `close_handle`.
///
/// See `shutdown_blocking()` for details.
fn shutdown_blocking_inner(close_handle: CloseHandle, config: Config) {
fn shutdown_blocking_inner(close_handle: ServerHandle, config: Config) {
// The server is a blocking task, so it can't run inside a tokio thread.
// See the note at wait_on_server.
let span = Span::current();
@ -321,7 +265,7 @@ impl RpcServer {
}
info!("Stopping RPC server");
close_handle.clone().close();
let _ = close_handle.stop();
debug!("Stopped RPC server");
})
};

View File

@ -1,4 +1,5 @@
//! RPC error codes & their handling.
use jsonrpsee_types::{ErrorCode, ErrorObject, ErrorObjectOwned};
/// Bitcoin RPC error codes
///
@ -51,22 +52,25 @@ pub enum LegacyCode {
ClientInvalidIpOrSubnet = -30,
}
impl From<LegacyCode> for jsonrpc_core::ErrorCode {
impl From<LegacyCode> for ErrorCode {
fn from(code: LegacyCode) -> Self {
Self::ServerError(code as i64)
Self::ServerError(code as i32)
}
}
/// A trait for mapping errors to [`jsonrpc_core::Error`].
pub(crate) trait MapError<T>: Sized {
/// Maps errors to [`jsonrpc_core::Error`] with a specific error code.
fn map_error(
self,
code: impl Into<jsonrpc_core::ErrorCode>,
) -> std::result::Result<T, jsonrpc_core::Error>;
impl From<LegacyCode> for i32 {
fn from(code: LegacyCode) -> Self {
code as i32
}
}
/// Maps errors to [`jsonrpc_core::Error`] with a [`LegacyCode::Misc`] error code.
fn map_misc_error(self) -> std::result::Result<T, jsonrpc_core::Error> {
/// A trait for mapping errors to [`jsonrpsee_types::ErrorObjectOwned`].
pub(crate) trait MapError<T>: Sized {
/// Maps errors to [`jsonrpsee_types::ErrorObjectOwned`] with a specific error code.
fn map_error(self, code: impl Into<ErrorCode>) -> std::result::Result<T, ErrorObjectOwned>;
/// Maps errors to [`jsonrpsee_types::ErrorObjectOwned`] with a [`LegacyCode::Misc`] error code.
fn map_misc_error(self) -> std::result::Result<T, ErrorObjectOwned> {
self.map_error(LegacyCode::Misc)
}
}
@ -77,15 +81,12 @@ pub(crate) trait OkOrError<T>: Sized {
/// message if conversion is to `Err`.
fn ok_or_error(
self,
code: impl Into<jsonrpc_core::ErrorCode>,
code: impl Into<ErrorCode>,
message: impl ToString,
) -> std::result::Result<T, jsonrpc_core::Error>;
) -> std::result::Result<T, ErrorObjectOwned>;
/// Converts the implementing type to `Result<T, jsonrpc_core::Error>`, using a [`LegacyCode::Misc`] error code.
fn ok_or_misc_error(
self,
message: impl ToString,
) -> std::result::Result<T, jsonrpc_core::Error> {
fn ok_or_misc_error(self, message: impl ToString) -> std::result::Result<T, ErrorObjectOwned> {
self.ok_or_error(LegacyCode::Misc, message)
}
}
@ -94,25 +95,21 @@ impl<T, E> MapError<T> for Result<T, E>
where
E: ToString,
{
fn map_error(self, code: impl Into<jsonrpc_core::ErrorCode>) -> Result<T, jsonrpc_core::Error> {
self.map_err(|error| jsonrpc_core::Error {
code: code.into(),
message: error.to_string(),
data: None,
})
fn map_error(self, code: impl Into<ErrorCode>) -> Result<T, ErrorObjectOwned> {
self.map_err(|error| ErrorObject::owned(code.into().code(), error.to_string(), None::<()>))
}
}
impl<T> OkOrError<T> for Option<T> {
fn ok_or_error(
self,
code: impl Into<jsonrpc_core::ErrorCode>,
code: impl Into<ErrorCode>,
message: impl ToString,
) -> Result<T, jsonrpc_core::Error> {
self.ok_or(jsonrpc_core::Error {
code: code.into(),
message: message.to_string(),
data: None,
})
) -> Result<T, ErrorObjectOwned> {
self.ok_or(ErrorObject::owned(
code.into().code(),
message.to_string(),
None::<()>,
))
}
}

View File

@ -2,16 +2,25 @@
//!
//! These fixes are applied at the HTTP level, before the RPC request is parsed.
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
use futures::TryStreamExt;
use jsonrpc_http_server::{
hyper::{body::Bytes, header, Body, Request},
RequestMiddleware, RequestMiddlewareAction,
use std::future::Future;
use std::pin::Pin;
use futures::{future, FutureExt};
use http_body_util::BodyExt;
use hyper::{body::Bytes, header};
use jsonrpsee::{
core::BoxError,
server::{HttpBody, HttpRequest, HttpResponse},
};
use jsonrpsee_types::ErrorObject;
use tower::Service;
use super::cookie::Cookie;
/// HTTP [`RequestMiddleware`] with compatibility workarounds.
use base64::{engine::general_purpose::URL_SAFE, Engine as _};
/// HTTP [`HttpRequestMiddleware`] with compatibility workarounds.
///
/// This middleware makes the following changes to HTTP requests:
///
@ -25,7 +34,7 @@ use super::cookie::Cookie;
/// ### Add missing `content-type` HTTP header
///
/// Some RPC clients don't include a `content-type` HTTP header.
/// But unlike web browsers, [`jsonrpc_http_server`] does not do content sniffing.
/// But unlike web browsers, [`jsonrpsee`] does not do content sniffing.
///
/// If there is no `content-type` header, we assume the content is JSON,
/// and let the parser error if we are incorrect.
@ -42,103 +51,30 @@ use super::cookie::Cookie;
/// Any user-specified data in RPC requests is hex or base58check encoded.
/// We assume lightwalletd validates data encodings before sending it on to Zebra.
/// So any fixes Zebra performs won't change user-specified data.
#[derive(Clone, Debug, Default)]
pub struct HttpRequestMiddleware {
#[derive(Clone, Debug)]
pub struct HttpRequestMiddleware<S> {
service: S,
cookie: Option<Cookie>,
}
/// A trait for updating an object, consuming it and returning the updated version.
pub trait With<T> {
/// Updates `self` with an instance of type `T` and returns the updated version of `self`.
fn with(self, _: T) -> Self;
}
impl With<Cookie> for HttpRequestMiddleware {
fn with(mut self, cookie: Cookie) -> Self {
self.cookie = Some(cookie);
self
}
}
impl RequestMiddleware for HttpRequestMiddleware {
fn on_request(&self, mut request: Request<Body>) -> RequestMiddlewareAction {
tracing::trace!(?request, "original HTTP request");
// Check if the request is authenticated
if !self.check_credentials(request.headers_mut()) {
let error = jsonrpc_core::Error {
code: jsonrpc_core::ErrorCode::ServerError(401),
message: "unauthenticated method".to_string(),
data: None,
};
return jsonrpc_http_server::Response {
code: jsonrpc_http_server::hyper::StatusCode::from_u16(401)
.expect("hard-coded status code should be valid"),
content_type: header::HeaderValue::from_static("application/json; charset=utf-8"),
content: serde_json::to_string(&jsonrpc_core::Response::from(error, None))
.expect("hard-coded result should serialize"),
}
.into();
impl<S> HttpRequestMiddleware<S> {
/// Create a new `HttpRequestMiddleware` with the given service and cookie.
pub fn new(service: S, cookie: Option<Cookie>) -> Self {
Self { service, cookie }
}
// Fix the request headers if needed and we can do so.
HttpRequestMiddleware::insert_or_replace_content_type_header(request.headers_mut());
// Fix the request body
let request = request.map(|body| {
let body = body.map_ok(|data| {
// To simplify data handling, we assume that any search strings won't be split
// across multiple `Bytes` data buffers.
//
// To simplify error handling, Zebra only supports valid UTF-8 requests,
// and uses lossy UTF-8 conversion.
//
// JSON-RPC requires all requests to be valid UTF-8.
// The lower layers should reject invalid requests with lossy changes.
// But if they accept some lossy changes, that's ok,
// because the request was non-standard anyway.
//
// We're not concerned about performance here, so we just clone the Cow<str>
let data = String::from_utf8_lossy(data.as_ref()).to_string();
// Fix up the request.
let data = Self::remove_json_1_fields(data);
Bytes::from(data)
});
Body::wrap_stream(body)
});
tracing::trace!(?request, "modified HTTP request");
RequestMiddlewareAction::Proceed {
// TODO: disable this security check if we see errors from lightwalletd.
should_continue_on_invalid_cors: false,
request,
}
}
}
impl HttpRequestMiddleware {
/// Remove any "jsonrpc: 1.0" fields in `data`, and return the resulting string.
pub fn remove_json_1_fields(data: String) -> String {
// Replace "jsonrpc = 1.0":
// - at the start or middle of a list, and
// - at the end of a list;
// with no spaces (lightwalletd format), and spaces after separators (example format).
//
// TODO: if we see errors from lightwalletd, make this replacement more accurate:
// - use a partial JSON fragment parser
// - combine the whole request into a single buffer, and use a JSON parser
// - use a regular expression
//
// We could also just handle the exact lightwalletd format,
// by replacing `{"jsonrpc":"1.0",` with `{`.
data.replace("\"jsonrpc\":\"1.0\",", "")
.replace("\"jsonrpc\": \"1.0\",", "")
.replace(",\"jsonrpc\":\"1.0\"", "")
.replace(", \"jsonrpc\": \"1.0\"", "")
/// Check if the request is authenticated.
pub fn check_credentials(&self, headers: &header::HeaderMap) -> bool {
self.cookie.as_ref().map_or(true, |internal_cookie| {
headers
.get(header::AUTHORIZATION)
.and_then(|auth_header| auth_header.to_str().ok())
.and_then(|auth_header| auth_header.split_whitespace().nth(1))
.and_then(|encoded| URL_SAFE.decode(encoded).ok())
.and_then(|decoded| String::from_utf8(decoded).ok())
.and_then(|request_cookie| request_cookie.split(':').nth(1).map(String::from))
.map_or(false, |passwd| internal_cookie.authenticate(passwd))
})
}
/// Insert or replace client supplied `content-type` HTTP header to `application/json` in the following cases:
@ -182,17 +118,110 @@ impl HttpRequestMiddleware {
}
}
/// Check if the request is authenticated.
pub fn check_credentials(&self, headers: &header::HeaderMap) -> bool {
self.cookie.as_ref().map_or(true, |internal_cookie| {
headers
.get(header::AUTHORIZATION)
.and_then(|auth_header| auth_header.to_str().ok())
.and_then(|auth_header| auth_header.split_whitespace().nth(1))
.and_then(|encoded| URL_SAFE.decode(encoded).ok())
.and_then(|decoded| String::from_utf8(decoded).ok())
.and_then(|request_cookie| request_cookie.split(':').nth(1).map(String::from))
.map_or(false, |passwd| internal_cookie.authenticate(passwd))
})
/// Remove any "jsonrpc: 1.0" fields in `data`, and return the resulting string.
pub fn remove_json_1_fields(data: String) -> String {
// Replace "jsonrpc = 1.0":
// - at the start or middle of a list, and
// - at the end of a list;
// with no spaces (lightwalletd format), and spaces after separators (example format).
//
// TODO: if we see errors from lightwalletd, make this replacement more accurate:
// - use a partial JSON fragment parser
// - combine the whole request into a single buffer, and use a JSON parser
// - use a regular expression
//
// We could also just handle the exact lightwalletd format,
// by replacing `{"jsonrpc":"1.0",` with `{"jsonrpc":"2.0`.
data.replace("\"jsonrpc\":\"1.0\",", "\"jsonrpc\":\"2.0\",")
.replace("\"jsonrpc\": \"1.0\",", "\"jsonrpc\": \"2.0\",")
.replace(",\"jsonrpc\":\"1.0\"", ",\"jsonrpc\":\"2.0\"")
.replace(", \"jsonrpc\": \"1.0\"", ", \"jsonrpc\": \"2.0\"")
}
}
/// Implement the Layer for HttpRequestMiddleware to allow injecting the cookie
#[derive(Clone)]
pub struct HttpRequestMiddlewareLayer {
cookie: Option<Cookie>,
}
impl HttpRequestMiddlewareLayer {
/// Create a new `HttpRequestMiddlewareLayer` with the given cookie.
pub fn new(cookie: Option<Cookie>) -> Self {
Self { cookie }
}
}
impl<S> tower::Layer<S> for HttpRequestMiddlewareLayer {
type Service = HttpRequestMiddleware<S>;
fn layer(&self, service: S) -> Self::Service {
HttpRequestMiddleware::new(service, self.cookie.clone())
}
}
/// A trait for updating an object, consuming it and returning the updated version.
pub trait With<T> {
/// Updates `self` with an instance of type `T` and returns the updated version of `self`.
fn with(self, _: T) -> Self;
}
impl<S> With<Cookie> for HttpRequestMiddleware<S> {
fn with(mut self, cookie: Cookie) -> Self {
self.cookie = Some(cookie);
self
}
}
impl<S> Service<HttpRequest<HttpBody>> for HttpRequestMiddleware<S>
where
S: Service<HttpRequest, Response = HttpResponse> + std::clone::Clone + Send + 'static,
S::Error: Into<BoxError> + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = BoxError;
type Future =
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
fn poll_ready(
&mut self,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, mut request: HttpRequest<HttpBody>) -> Self::Future {
// Check if the request is authenticated
if !self.check_credentials(request.headers_mut()) {
let error = ErrorObject::borrowed(401, "unauthenticated method", None);
// TODO: Error object is not being returned to the user but an empty response.
return future::err(BoxError::from(error)).boxed();
}
// Fix the request headers.
Self::insert_or_replace_content_type_header(request.headers_mut());
let mut service = self.service.clone();
let (parts, body) = request.into_parts();
async move {
let bytes = body
.collect()
.await
.expect("Failed to collect body data")
.to_bytes();
let data = String::from_utf8_lossy(bytes.as_ref()).to_string();
// Fix JSON-RPC 1.0 requests.
let data = Self::remove_json_1_fields(data);
let body = HttpBody::from(Bytes::from(data).as_ref().to_vec());
let request = HttpRequest::from_parts(parts, body);
service.call(request).await.map_err(Into::into)
}
.boxed()
}
}

View File

@ -3,116 +3,66 @@
//! These fixes are applied at the JSON-RPC call level,
//! after the RPC request is parsed and split into calls.
use std::future::Future;
use futures::future::{Either, FutureExt};
use jsonrpc_core::{
middleware::Middleware,
types::{Call, Failure, Output, Response},
BoxFuture, Metadata, MethodCall, Notification,
use jsonrpsee::{
server::middleware::rpc::{layer::ResponseFuture, RpcService, RpcServiceT},
MethodResponse,
};
use jsonrpsee_types::ErrorObject;
use crate::server;
/// JSON-RPC [`Middleware`] with compatibility workarounds.
/// JSON-RPC [`FixRpcResponseMiddleware`] with compatibility workarounds.
///
/// This middleware makes the following changes to JSON-RPC calls:
///
/// ## Make RPC framework response codes match `zcashd`
///
/// [`jsonrpc_core`] returns specific error codes while parsing requests:
/// <https://docs.rs/jsonrpc-core/18.0.0/jsonrpc_core/types/error/enum.ErrorCode.html#variants>
/// [`jsonrpsee_types`] returns specific error codes while parsing requests:
/// <https://docs.rs/jsonrpsee-types/latest/jsonrpsee_types/error/enum.ErrorCode.html>
///
/// But these codes are different from `zcashd`, and some RPC clients rely on the exact code.
///
/// ## Read-Only Functionality
///
/// This middleware also logs unrecognized RPC requests.
pub struct FixRpcResponseMiddleware;
impl<M: Metadata> Middleware<M> for FixRpcResponseMiddleware {
type Future = BoxFuture<Option<Response>>;
type CallFuture = BoxFuture<Option<Output>>;
fn on_call<Next, NextFuture>(
&self,
call: Call,
meta: M,
next: Next,
) -> Either<Self::CallFuture, NextFuture>
where
Next: Fn(Call, M) -> NextFuture + Send + Sync,
NextFuture: Future<Output = Option<Output>> + Send + 'static,
{
Either::Left(
next(call.clone(), meta)
.map(|mut output| {
Self::fix_error_codes(&mut output);
output
})
.inspect(|output| Self::log_if_error(output, call))
.boxed(),
)
}
/// Specifically, the [`jsonrpsee_types::error::INVALID_PARAMS_CODE`] is different:
/// <https://docs.rs/jsonrpsee-types/latest/jsonrpsee_types/error/constant.INVALID_PARAMS_CODE.html>
pub struct FixRpcResponseMiddleware {
service: RpcService,
}
impl FixRpcResponseMiddleware {
/// Replaces [`jsonrpc_core::ErrorCode`]s in the [`Output`] with their `zcashd` equivalents.
///
/// ## Replaced Codes
///
/// 1. [`jsonrpc_core::ErrorCode::InvalidParams`] -> [`server::error::LegacyCode::Misc`]
/// Rationale:
/// The `node-stratum-pool` mining pool library expects error code `-1` to detect available RPC methods:
/// <https://github.com/s-nomp/node-stratum-pool/blob/d86ae73f8ff968d9355bb61aac05e0ebef36ccb5/lib/pool.js#L459>
fn fix_error_codes(output: &mut Option<Output>) {
if let Some(Output::Failure(Failure { ref mut error, .. })) = output {
if matches!(error.code, jsonrpc_core::ErrorCode::InvalidParams) {
let original_code = error.code.clone();
error.code = server::error::LegacyCode::Misc.into();
tracing::debug!("Replacing RPC error: {original_code:?} with {error}");
}
}
}
/// Obtain a description string for a received request.
///
/// Prints out only the method name and the received parameters.
fn call_description(call: &Call) -> String {
const MAX_PARAMS_LOG_LENGTH: usize = 100;
match call {
Call::MethodCall(MethodCall { method, params, .. }) => {
let mut params = format!("{params:?}");
if params.len() >= MAX_PARAMS_LOG_LENGTH {
params.truncate(MAX_PARAMS_LOG_LENGTH);
params.push_str("...");
}
format!(r#"method = {method:?}, params = {params}"#)
}
Call::Notification(Notification { method, params, .. }) => {
let mut params = format!("{params:?}");
if params.len() >= MAX_PARAMS_LOG_LENGTH {
params.truncate(MAX_PARAMS_LOG_LENGTH);
params.push_str("...");
}
format!(r#"notification = {method:?}, params = {params}"#)
}
Call::Invalid { .. } => "invalid request".to_owned(),
}
}
/// Check RPC output and log any errors.
//
// TODO: do we want to ignore ErrorCode::ServerError(_), or log it at debug?
fn log_if_error(output: &Option<Output>, call: Call) {
if let Some(Output::Failure(Failure { error, .. })) = output {
let call_description = Self::call_description(&call);
tracing::info!("RPC error: {error} in call: {call_description}");
}
/// Create a new `FixRpcResponseMiddleware` with the given `service`.
pub fn new(service: RpcService) -> Self {
Self { service }
}
}
impl<'a> RpcServiceT<'a> for FixRpcResponseMiddleware {
type Future = ResponseFuture<futures::future::BoxFuture<'a, jsonrpsee::MethodResponse>>;
fn call(&self, request: jsonrpsee::types::Request<'a>) -> Self::Future {
let service = self.service.clone();
ResponseFuture::future(Box::pin(async move {
let response = service.call(request).await;
if response.is_error() {
let original_error_code = response
.as_error_code()
.expect("response should have an error code");
if original_error_code == jsonrpsee_types::ErrorCode::InvalidParams.code() {
let new_error_code = crate::server::error::LegacyCode::Misc.into();
tracing::debug!(
"Replacing RPC error: {original_error_code} with {new_error_code}"
);
let json: serde_json::Value =
serde_json::from_str(response.into_parts().0.as_str())
.expect("response string should be valid json");
let id = json["id"]
.as_str()
.expect("response json should have an id")
.to_string();
return MethodResponse::error(
jsonrpsee_types::Id::Str(id.into()),
ErrorObject::borrowed(new_error_code, "Invalid params", None),
);
}
}
response
}))
}
}

View File

@ -3,12 +3,8 @@
// These tests call functions which can take unit arguments if some features aren't enabled.
#![allow(clippy::unit_arg)]
use std::{
net::{Ipv4Addr, SocketAddrV4},
time::Duration,
};
use std::net::{Ipv4Addr, SocketAddrV4};
use futures::FutureExt;
use tower::buffer::Buffer;
use zebra_chain::{
@ -21,38 +17,26 @@ use zebra_test::mock_service::MockService;
use super::super::*;
/// Test that the JSON-RPC server spawns when configured with a single thread.
#[test]
fn rpc_server_spawn_single_thread() {
rpc_server_spawn(false)
}
/// Test that the JSON-RPC server spawns when configured with multiple threads.
#[test]
#[cfg(not(target_os = "windows"))]
fn rpc_server_spawn_parallel_threads() {
rpc_server_spawn(true)
/// Test that the JSON-RPC server spawns.
#[tokio::test]
async fn rpc_server_spawn_test() {
rpc_server_spawn().await
}
/// Test if the RPC server will spawn on a randomly generated port.
///
/// Set `parallel_cpu_threads` to true to auto-configure based on the number of CPU cores.
#[tracing::instrument]
fn rpc_server_spawn(parallel_cpu_threads: bool) {
async fn rpc_server_spawn() {
let _init_guard = zebra_test::init();
let config = Config {
listen_addr: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0).into()),
indexer_listen_addr: None,
parallel_cpu_threads: if parallel_cpu_threads { 2 } else { 1 },
parallel_cpu_threads: 0,
debug_force_finished_sync: false,
cookie_dir: Default::default(),
enable_cookie_auth: false,
};
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut block_verifier_router: MockService<_, _, _, BoxError> =
@ -60,7 +44,7 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) {
info!("spawning RPC server...");
let (rpc_server_task_handle, rpc_tx_queue_task_handle, _rpc_server) = RpcServer::spawn(
let _rpc_server_task_handle = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
@ -79,53 +63,25 @@ fn rpc_server_spawn(parallel_cpu_threads: bool) {
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
// The server and queue tasks should continue without errors or panics
let rpc_server_task_result = rpc_server_task_handle.now_or_never();
assert!(rpc_server_task_result.is_none());
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(rpc_tx_queue_task_result.is_none());
});
info!("waiting for RPC server to shut down...");
rt.shutdown_timeout(Duration::from_secs(1));
}
/// Test that the JSON-RPC server spawns when configured with a single thread,
/// on an OS-assigned unallocated port.
#[test]
fn rpc_server_spawn_unallocated_port_single_thread() {
rpc_server_spawn_unallocated_port(false, false)
/// Test that the JSON-RPC server spawns on an OS-assigned unallocated port.
#[tokio::test]
async fn rpc_server_spawn_unallocated_port() {
rpc_spawn_unallocated_port(false).await
}
/// Test that the JSON-RPC server spawns and shuts down when configured with a single thread,
/// on an OS-assigned unallocated port.
#[test]
fn rpc_server_spawn_unallocated_port_single_thread_shutdown() {
rpc_server_spawn_unallocated_port(false, true)
}
/// Test that the JSON-RPC server spawns when configured with multiple threads,
/// on an OS-assigned unallocated port.
#[test]
fn rpc_sever_spawn_unallocated_port_parallel_threads() {
rpc_server_spawn_unallocated_port(true, false)
}
/// Test that the JSON-RPC server spawns and shuts down when configured with multiple threads,
/// on an OS-assigned unallocated port.
#[test]
fn rpc_sever_spawn_unallocated_port_parallel_threads_shutdown() {
rpc_server_spawn_unallocated_port(true, true)
/// Test that the JSON-RPC server spawns and shuts down on an OS-assigned unallocated port.
#[tokio::test]
async fn rpc_server_spawn_unallocated_port_shutdown() {
rpc_spawn_unallocated_port(true).await
}
/// Test if the RPC server will spawn on an OS-assigned unallocated port.
///
/// Set `parallel_cpu_threads` to true to auto-configure based on the number of CPU cores,
/// and `do_shutdown` to true to close the server using the close handle.
/// Set `do_shutdown` to true to close the server using the close handle.
#[tracing::instrument]
fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bool) {
async fn rpc_spawn_unallocated_port(do_shutdown: bool) {
let _init_guard = zebra_test::init();
let port = zebra_test::net::random_unallocated_port();
@ -134,15 +90,12 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bo
let config = Config {
listen_addr: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into()),
indexer_listen_addr: None,
parallel_cpu_threads: if parallel_cpu_threads { 0 } else { 1 },
parallel_cpu_threads: 0,
debug_force_finished_sync: false,
cookie_dir: Default::default(),
enable_cookie_auth: false,
};
let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut block_verifier_router: MockService<_, _, _, BoxError> =
@ -150,7 +103,7 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bo
info!("spawning RPC server...");
let (rpc_server_task_handle, rpc_tx_queue_task_handle, rpc_server) = RpcServer::spawn(
let rpc_server_task_handle = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
@ -162,7 +115,9 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bo
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
)
.await
.expect("");
info!("spawned RPC server, checking services...");
@ -171,61 +126,31 @@ fn rpc_server_spawn_unallocated_port(parallel_cpu_threads: bool, do_shutdown: bo
block_verifier_router.expect_no_requests().await;
if do_shutdown {
rpc_server
.expect("unexpected missing RpcServer for configured RPC port")
.shutdown()
.await
.expect("unexpected panic during RpcServer shutdown");
// The server and queue tasks should shut down without errors or panics
let rpc_server_task_result = rpc_server_task_handle.await;
assert!(
matches!(rpc_server_task_result, Ok(())),
"unexpected server task panic during shutdown: {rpc_server_task_result:?}"
);
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.await;
assert!(
matches!(rpc_tx_queue_task_result, Ok(())),
"unexpected queue task panic during shutdown: {rpc_tx_queue_task_result:?}"
);
} else {
// The server and queue tasks should continue without errors or panics
let rpc_server_task_result = rpc_server_task_handle.now_or_never();
assert!(rpc_server_task_result.is_none());
let rpc_tx_queue_task_result = rpc_tx_queue_task_handle.now_or_never();
assert!(rpc_tx_queue_task_result.is_none());
rpc_server_task_handle.0.abort();
}
});
info!("waiting for RPC server to shut down...");
rt.shutdown_timeout(Duration::from_secs(1));
}
/// Test if the RPC server will panic correctly when there is a port conflict.
///
/// This test is sometimes unreliable on Windows, and hangs on macOS.
/// We believe this is a CI infrastructure issue, not a platform-specific issue.
#[test]
#[tokio::test]
#[should_panic(expected = "Unable to start RPC server")]
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn rpc_server_spawn_port_conflict() {
async fn rpc_server_spawn_port_conflict() {
use std::time::Duration;
let _init_guard = zebra_test::init();
let port = zebra_test::net::random_known_port();
let config = Config {
listen_addr: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into()),
indexer_listen_addr: None,
parallel_cpu_threads: 1,
debug_force_finished_sync: false,
parallel_cpu_threads: 0,
cookie_dir: Default::default(),
enable_cookie_auth: false,
};
let rt = tokio::runtime::Runtime::new().unwrap();
let test_task_handle = rt.spawn(async {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut block_verifier_router: MockService<_, _, _, BoxError> =
@ -233,8 +158,7 @@ fn rpc_server_spawn_port_conflict() {
info!("spawning RPC server 1...");
let (_rpc_server_1_task_handle, _rpc_tx_queue_1_task_handle, _rpc_server) =
RpcServer::spawn(
let _rpc_server_1_task_handle = RpcServer::spawn(
config.clone(),
Default::default(),
"RPC server 1 test",
@ -246,13 +170,14 @@ fn rpc_server_spawn_port_conflict() {
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
)
.await;
tokio::time::sleep(Duration::from_secs(3)).await;
info!("spawning conflicted RPC server 2...");
let (rpc_server_2_task_handle, _rpc_tx_queue_2_task_handle, _rpc_server) = RpcServer::spawn(
let _rpc_server_2_task_handle = RpcServer::spawn(
config,
Default::default(),
"RPC server 2 conflict test",
@ -264,170 +189,12 @@ fn rpc_server_spawn_port_conflict() {
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
)
.await;
info!("spawned RPC servers, checking services...");
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
// Because there is a panic inside a multi-threaded executor,
// we can't depend on the exact behaviour of the other tasks,
// particularly across different machines and OSes.
// The second server should panic, so its task handle should return the panic
let rpc_server_2_task_result = rpc_server_2_task_handle.await;
match rpc_server_2_task_result {
Ok(()) => panic!(
"RPC server with conflicting port should exit with an error: \
unexpected Ok result"
),
Err(join_error) => match join_error.try_into_panic() {
Ok(panic_object) => panic::resume_unwind(panic_object),
Err(cancelled_error) => panic!(
"RPC server with conflicting port should exit with an error: \
unexpected JoinError: {cancelled_error:?}"
),
},
}
// Ignore the queue task result
});
// Wait until the spawned task finishes
std::thread::sleep(Duration::from_secs(10));
info!("waiting for RPC server to shut down...");
rt.shutdown_timeout(Duration::from_secs(3));
match test_task_handle.now_or_never() {
Some(Ok(_never)) => unreachable!("test task always panics"),
None => panic!("unexpected test task hang"),
Some(Err(join_error)) => match join_error.try_into_panic() {
Ok(panic_object) => panic::resume_unwind(panic_object),
Err(cancelled_error) => panic!(
"test task should exit with a RPC server panic: \
unexpected non-panic JoinError: {cancelled_error:?}"
),
},
}
}
/// Check if the RPC server detects a port conflict when running parallel threads.
///
/// If this test fails, that's great!
/// We can make parallel the default, and remove the warnings in the config docs.
///
/// This test is sometimes unreliable on Windows, and hangs on macOS.
/// We believe this is a CI infrastructure issue, not a platform-specific issue.
#[test]
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
fn rpc_server_spawn_port_conflict_parallel_auto() {
let _init_guard = zebra_test::init();
let port = zebra_test::net::random_known_port();
let config = Config {
listen_addr: Some(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port).into()),
indexer_listen_addr: None,
parallel_cpu_threads: 2,
debug_force_finished_sync: false,
cookie_dir: Default::default(),
enable_cookie_auth: false,
};
let rt = tokio::runtime::Runtime::new().unwrap();
let test_task_handle = rt.spawn(async {
let mut mempool: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut state: MockService<_, _, _, BoxError> = MockService::build().for_unit_tests();
let mut block_verifier_router: MockService<_, _, _, BoxError> =
MockService::build().for_unit_tests();
info!("spawning parallel RPC server 1...");
let (_rpc_server_1_task_handle, _rpc_tx_queue_1_task_handle, _rpc_server) =
RpcServer::spawn(
config.clone(),
Default::default(),
"RPC server 1 test",
"RPC server 1 test",
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
Buffer::new(block_verifier_router.clone(), 1),
MockSyncStatus::default(),
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
tokio::time::sleep(Duration::from_secs(3)).await;
info!("spawning parallel conflicted RPC server 2...");
let (rpc_server_2_task_handle, _rpc_tx_queue_2_task_handle, _rpc_server) = RpcServer::spawn(
config,
Default::default(),
"RPC server 2 conflict test",
"RPC server 2 conflict test",
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
Buffer::new(block_verifier_router.clone(), 1),
MockSyncStatus::default(),
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
info!("spawned RPC servers, checking services...");
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
// Because there might be a panic inside a multi-threaded executor,
// we can't depend on the exact behaviour of the other tasks,
// particularly across different machines and OSes.
// The second server doesn't panic, but we'd like it to.
// (See the function docs for details.)
let rpc_server_2_task_result = rpc_server_2_task_handle.await;
match rpc_server_2_task_result {
Ok(()) => info!(
"Parallel RPC server with conflicting port should exit with an error: \
but we're ok with it ignoring the conflict for now"
),
Err(join_error) => match join_error.try_into_panic() {
Ok(panic_object) => panic::resume_unwind(panic_object),
Err(cancelled_error) => info!(
"Parallel RPC server with conflicting port should exit with an error: \
but we're ok with it ignoring the conflict for now: \
unexpected JoinError: {cancelled_error:?}"
),
},
}
// Ignore the queue task result
});
// Wait until the spawned task finishes
std::thread::sleep(Duration::from_secs(10));
info!("waiting for parallel RPC server to shut down...");
rt.shutdown_timeout(Duration::from_secs(3));
match test_task_handle.now_or_never() {
Some(Ok(())) => {
info!("parallel RPC server task successfully exited");
}
None => panic!("unexpected test task hang"),
Some(Err(join_error)) => match join_error.try_into_panic() {
Ok(panic_object) => panic::resume_unwind(panic_object),
Err(cancelled_error) => info!(
"Parallel RPC server with conflicting port should exit with an error: \
but we're ok with it ignoring the conflict for now: \
unexpected JoinError: {cancelled_error:?}"
),
},
}
}

View File

@ -382,9 +382,10 @@ impl SyncerRpcMethods for RpcRequestClient {
}
Err(err)
if err
.downcast_ref::<jsonrpc_core::Error>()
.downcast_ref::<jsonrpsee_types::ErrorCode>()
.is_some_and(|err| {
err.code == server::error::LegacyCode::InvalidParameter.into()
let code: i32 = server::error::LegacyCode::InvalidParameter.into();
err.code() == code
}) =>
{
Ok(None)

View File

@ -254,7 +254,7 @@ tonic-build = { version = "0.12.3", optional = true }
abscissa_core = { version = "0.7.0", features = ["testing"] }
hex = "0.4.3"
hex-literal = "0.4.1"
jsonrpc-core = "18.0.0"
jsonrpsee-types = "0.24.7"
once_cell = "1.20.2"
regex = "1.11.0"
insta = { version = "1.41.1", features = ["json"] }

View File

@ -243,8 +243,11 @@ impl StartCmd {
}
// Launch RPC server
let (rpc_task_handle, mut rpc_tx_queue_task_handle) =
if let Some(listen_addr) = config.rpc.listen_addr {
info!("spawning RPC server");
let (rpc_task_handle, rpc_tx_queue_task_handle, rpc_server) = RpcServer::spawn(
info!("Trying to open RPC endpoint at {}...", listen_addr,);
let rpc_task_handle = RpcServer::spawn(
config.rpc.clone(),
config.mining.clone(),
build_version(),
@ -257,6 +260,14 @@ impl StartCmd {
latest_chain_tip.clone(),
config.network.network.clone(),
);
rpc_task_handle.await.unwrap()
} else {
warn!("configure an listen_addr to start the RPC server");
(
tokio::spawn(std::future::pending().in_current_span()),
tokio::spawn(std::future::pending().in_current_span()),
)
};
// TODO: Add a shutdown signal and start the server with `serve_with_incoming_shutdown()` if
// any related unit tests sometimes crash with memory errors
@ -399,7 +410,6 @@ impl StartCmd {
// ongoing tasks
pin!(rpc_task_handle);
pin!(indexer_rpc_task_handle);
pin!(rpc_tx_queue_task_handle);
pin!(syncer_task_handle);
pin!(block_gossip_task_handle);
pin!(mempool_crawler_task_handle);
@ -425,17 +435,10 @@ impl StartCmd {
let mut exit_when_task_finishes = true;
let result = select! {
rpc_result = &mut rpc_task_handle => {
rpc_result
rpc_join_result = &mut rpc_task_handle => {
let rpc_server_result = rpc_join_result
.expect("unexpected panic in the rpc task");
info!("rpc task exited");
Ok(())
}
indexer_rpc_join_result = &mut indexer_rpc_task_handle => {
let indexer_rpc_server_result = indexer_rpc_join_result
.expect("unexpected panic in the rpc task");
info!(?indexer_rpc_server_result, "indexer rpc task exited");
info!(?rpc_server_result, "rpc task exited");
Ok(())
}
@ -446,6 +449,13 @@ impl StartCmd {
Ok(())
}
indexer_rpc_join_result = &mut indexer_rpc_task_handle => {
let indexer_rpc_server_result = indexer_rpc_join_result
.expect("unexpected panic in the indexer task");
info!(?indexer_rpc_server_result, "indexer rpc task exited");
Ok(())
}
sync_result = &mut syncer_task_handle => sync_result
.expect("unexpected panic in the syncer task")
.map(|_| info!("syncer task exited")),
@ -536,15 +546,6 @@ impl StartCmd {
state_checkpoint_verify_handle.abort();
old_databases_task_handle.abort();
// Wait until the RPC server shuts down.
// This can take around 150 seconds.
//
// Without this shutdown, Zebra's RPC unit tests sometimes crashed with memory errors.
if let Some(rpc_server) = rpc_server {
info!("waiting for RPC server to shut down");
rpc_server.shutdown_blocking();
}
info!("exiting Zebra: all tasks have been asked to stop, waiting for remaining tasks to finish");
exit_status

View File

@ -35,7 +35,7 @@ use zebra_rpc::{
GetBlockTemplateCapability::*, GetBlockTemplateRequestMode::*,
},
hex_data::HexData,
GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer,
},
};
use zebra_state::WatchReceiver;

View File

@ -3270,7 +3270,7 @@ async fn nu6_funding_streams_and_coinbase_balance() -> Result<()> {
types::submit_block,
},
hex_data::HexData,
GetBlockTemplateRpc, GetBlockTemplateRpcImpl,
GetBlockTemplateRpcImpl, GetBlockTemplateRpcServer,
};
use zebra_test::mock_service::MockService;
let _init_guard = zebra_test::init();

View File

@ -161,9 +161,10 @@ impl MiningRpcMethods for RpcRequestClient {
}
Err(err)
if err
.downcast_ref::<jsonrpc_core::Error>()
.downcast_ref::<jsonrpsee_types::ErrorObject>()
.is_some_and(|err| {
err.code == server::error::LegacyCode::InvalidParameter.into()
let error: i32 = server::error::LegacyCode::InvalidParameter.into();
err.code() == error
}) =>
{
Ok(None)

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "orchard",
"start_index": 0,
@ -13,6 +14,5 @@ expression: parsed
"end_height": 1707429
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "orchard",
"start_index": 338,
@ -13,6 +14,5 @@ expression: parsed
"end_height": 1888929
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "orchard",
"start_index": 585,
@ -13,6 +14,5 @@ expression: parsed
"end_height": 2000126
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "sapling",
"start_index": 0,
@ -13,6 +14,5 @@ expression: parsed
"end_height": 558822
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "sapling",
"start_index": 0,
@ -53,6 +54,5 @@ expression: parsed
"end_height": 1363036
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "sapling",
"start_index": 1090,
@ -33,6 +34,5 @@ expression: parsed
"end_height": 2056616
}
]
},
"id": 123
}
}

View File

@ -4,6 +4,7 @@ expression: parsed
---
{
"jsonrpc": "2.0",
"id": 123,
"result": {
"pool": "sapling",
"start_index": 17,
@ -13,6 +14,5 @@ expression: parsed
"end_height": 1703171
}
]
},
"id": 123
}
}