Merge branch 'main' into add-v5-txs

This commit is contained in:
Marek 2024-12-21 13:03:37 +01:00
commit 30f43f5fd2
73 changed files with 3448 additions and 3459 deletions

View File

@ -103,4 +103,4 @@ jobs:
run: cargo llvm-cov --lcov --no-run --output-path lcov.info
- name: Upload coverage report to Codecov
uses: codecov/codecov-action@v5.0.7
uses: codecov/codecov-action@v5.1.1

View File

@ -44,7 +44,7 @@ jobs:
- name: Rust files
id: changed-files-rust
uses: tj-actions/changed-files@v45.0.4
uses: tj-actions/changed-files@v45.0.5
with:
files: |
**/*.rs
@ -56,7 +56,7 @@ jobs:
- name: Workflow files
id: changed-files-workflows
uses: tj-actions/changed-files@v45.0.4
uses: tj-actions/changed-files@v45.0.5
with:
files: |
.github/workflows/*.yml

View File

@ -152,7 +152,7 @@ jobs:
# Setup Docker Buildx to use Docker Build Cloud
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3.7.1
uses: docker/setup-buildx-action@v3.8.0
with:
version: "lab:latest"
driver: cloud
@ -193,7 +193,7 @@ jobs:
# - `dev` for a pull request event
- name: Docker Scout
id: docker-scout
uses: docker/scout-action@v1.15.1
uses: docker/scout-action@v1.16.1
# We only run Docker Scout on the `runtime` target, as the other targets are not meant to be released
# and are commonly used for testing, and thus are ephemeral.
# TODO: Remove the `contains` check once we have a better way to determine if just new vulnerabilities are present.

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

@ -568,7 +568,7 @@ where
+ Copy
+ 'static,
{
let mut spend_restriction = transaction.coinbase_spend_restriction(height);
let mut spend_restriction = transaction.coinbase_spend_restriction(&Network::Mainnet, height);
let mut new_inputs = Vec::new();
let mut spent_outputs = HashMap::new();
@ -650,7 +650,8 @@ where
+ 'static,
{
let has_shielded_outputs = transaction.has_shielded_outputs();
let delete_transparent_outputs = CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height };
let delete_transparent_outputs =
CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height };
let mut attempts: usize = 0;
// choose an arbitrary spendable UTXO, in hash set order

View File

@ -232,6 +232,9 @@ pub struct ParametersBuilder {
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool,
/// Whether to allow transactions with transparent outputs to spend coinbase outputs,
/// similar to `fCoinbaseMustBeShielded` in zcashd.
should_allow_unshielded_coinbase_spends: bool,
/// The pre-Blossom halving interval for this network
pre_blossom_halving_interval: HeightDiff,
/// The post-Blossom halving interval for this network
@ -271,6 +274,7 @@ impl Default for ParametersBuilder {
should_lock_funding_stream_address_period: false,
pre_blossom_halving_interval: PRE_BLOSSOM_HALVING_INTERVAL,
post_blossom_halving_interval: POST_BLOSSOM_HALVING_INTERVAL,
should_allow_unshielded_coinbase_spends: false,
}
}
}
@ -439,6 +443,15 @@ impl ParametersBuilder {
self
}
/// Sets the `disable_pow` flag to be used in the [`Parameters`] being built.
pub fn with_unshielded_coinbase_spends(
mut self,
should_allow_unshielded_coinbase_spends: bool,
) -> Self {
self.should_allow_unshielded_coinbase_spends = should_allow_unshielded_coinbase_spends;
self
}
/// Sets the pre and post Blosssom halving intervals to be used in the [`Parameters`] being built.
pub fn with_halving_interval(mut self, pre_blossom_halving_interval: HeightDiff) -> Self {
if self.should_lock_funding_stream_address_period {
@ -464,6 +477,7 @@ impl ParametersBuilder {
should_lock_funding_stream_address_period: _,
target_difficulty_limit,
disable_pow,
should_allow_unshielded_coinbase_spends,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = self;
@ -478,6 +492,7 @@ impl ParametersBuilder {
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
should_allow_unshielded_coinbase_spends,
pre_blossom_halving_interval,
post_blossom_halving_interval,
}
@ -516,6 +531,7 @@ impl ParametersBuilder {
should_lock_funding_stream_address_period: _,
target_difficulty_limit,
disable_pow,
should_allow_unshielded_coinbase_spends,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = Self::default();
@ -528,6 +544,8 @@ impl ParametersBuilder {
&& self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow
&& self.should_allow_unshielded_coinbase_spends
== should_allow_unshielded_coinbase_spends
&& self.pre_blossom_halving_interval == pre_blossom_halving_interval
&& self.post_blossom_halving_interval == post_blossom_halving_interval
}
@ -560,6 +578,9 @@ pub struct Parameters {
target_difficulty_limit: ExpandedDifficulty,
/// A flag for disabling proof-of-work checks when Zebra is validating blocks
disable_pow: bool,
/// Whether to allow transactions with transparent outputs to spend coinbase outputs,
/// similar to `fCoinbaseMustBeShielded` in zcashd.
should_allow_unshielded_coinbase_spends: bool,
/// Pre-Blossom halving interval for this network
pre_blossom_halving_interval: HeightDiff,
/// Post-Blossom halving interval for this network
@ -597,6 +618,7 @@ impl Parameters {
// This value is chosen to match zcashd, see: <https://github.com/zcash/zcash/blob/master/src/chainparams.cpp#L654>
.with_target_difficulty_limit(U256::from_big_endian(&[0x0f; 32]))
.with_disable_pow(true)
.with_unshielded_coinbase_spends(true)
.with_slow_start_interval(Height::MIN)
// Removes default Testnet activation heights if not configured,
// most network upgrades are disabled by default for Regtest in zcashd
@ -645,6 +667,7 @@ impl Parameters {
post_nu6_funding_streams,
target_difficulty_limit,
disable_pow,
should_allow_unshielded_coinbase_spends,
pre_blossom_halving_interval,
post_blossom_halving_interval,
} = Self::new_regtest(None, None);
@ -657,6 +680,8 @@ impl Parameters {
&& self.post_nu6_funding_streams == post_nu6_funding_streams
&& self.target_difficulty_limit == target_difficulty_limit
&& self.disable_pow == disable_pow
&& self.should_allow_unshielded_coinbase_spends
== should_allow_unshielded_coinbase_spends
&& self.pre_blossom_halving_interval == pre_blossom_halving_interval
&& self.post_blossom_halving_interval == post_blossom_halving_interval
}
@ -711,6 +736,12 @@ impl Parameters {
self.disable_pow
}
/// Returns true if this network should allow transactions with transparent outputs
/// that spend coinbase outputs.
pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
self.should_allow_unshielded_coinbase_spends
}
/// Returns the pre-Blossom halving interval for this network
pub fn pre_blossom_halving_interval(&self) -> HeightDiff {
self.pre_blossom_halving_interval
@ -786,4 +817,14 @@ impl Network {
self.post_nu6_funding_streams()
}
}
/// Returns true if this network should allow transactions with transparent outputs
/// that spend coinbase outputs.
pub fn should_allow_unshielded_coinbase_spends(&self) -> bool {
if let Self::Testnet(params) = self {
params.should_allow_unshielded_coinbase_spends()
} else {
false
}
}
}

View File

@ -41,7 +41,7 @@ pub use unmined::{
use crate::{
amount::{Amount, Error as AmountError, NegativeAllowed, NonNegative},
block, orchard,
parameters::{ConsensusBranchId, NetworkUpgrade},
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
primitives::{ed25519, Bctv14Proof, Groth16Proof},
sapling,
serialization::ZcashSerialize,
@ -308,14 +308,15 @@ impl Transaction {
/// assuming it is mined at `spend_height`.
pub fn coinbase_spend_restriction(
&self,
network: &Network,
spend_height: block::Height,
) -> CoinbaseSpendRestriction {
if self.outputs().is_empty() {
// we know this transaction must have shielded outputs,
// because of other consensus rules
OnlyShieldedOutputs { spend_height }
if self.outputs().is_empty() || network.should_allow_unshielded_coinbase_spends() {
// we know this transaction must have shielded outputs if it has no
// transparent outputs, because of other consensus rules.
CheckCoinbaseMaturity { spend_height }
} else {
SomeTransparentOutputs
DisallowCoinbaseSpend
}
}

View File

@ -34,7 +34,6 @@ use std::{fmt, sync::Arc};
use proptest_derive::Arbitrary;
use hex::{FromHex, ToHex};
use serde::{Deserialize, Serialize};
use crate::serialization::{
ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize,
@ -56,7 +55,7 @@ use super::{txid::TxIdBuilder, AuthDigest, Transaction};
///
/// [ZIP-244]: https://zips.z.cash/zip-0244
/// [Spec: Transaction Identifiers]: https://zips.z.cash/protocol/protocol.pdf#txnidentifiers
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, Hash)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(any(test, feature = "proptest-impl"), derive(Arbitrary))]
pub struct Hash(pub [u8; 32]);

View File

@ -126,10 +126,14 @@ impl OrderedUtxo {
)]
pub enum CoinbaseSpendRestriction {
/// The UTXO is spent in a transaction with one or more transparent outputs
SomeTransparentOutputs,
/// on a network where coinbase outputs must not be spent by transactions
/// with transparent outputs.
DisallowCoinbaseSpend,
/// The UTXO is spent in a transaction which only has shielded outputs
OnlyShieldedOutputs {
/// The UTXO is spent in a transaction which only has shielded outputs, or
/// transactions spending coinbase outputs may have transparent outputs on
/// this network.
CheckCoinbaseMaturity {
/// The height at which the UTXO is spent
spend_height: block::Height,
},

View File

@ -8,6 +8,7 @@
//! verification, where it may be accepted or rejected.
use std::{
collections::HashSet,
future::Future,
pin::Pin,
sync::Arc,
@ -25,7 +26,7 @@ use zebra_chain::{
amount::Amount,
block,
parameters::{subsidy::FundingStreamReceiver, Network},
transparent,
transaction, transparent,
work::equihash,
};
use zebra_state as zs;
@ -232,13 +233,21 @@ where
&block,
&transaction_hashes,
));
for transaction in &block.transactions {
let known_outpoint_hashes: Arc<HashSet<transaction::Hash>> =
Arc::new(known_utxos.keys().map(|outpoint| outpoint.hash).collect());
for (&transaction_hash, transaction) in
transaction_hashes.iter().zip(block.transactions.iter())
{
let rsp = transaction_verifier
.ready()
.await
.expect("transaction verifier is always ready")
.call(tx::Request::Block {
transaction_hash,
transaction: transaction.clone(),
known_outpoint_hashes: known_outpoint_hashes.clone(),
known_utxos: known_utxos.clone(),
height,
time: block.header.time,

View File

@ -1,7 +1,7 @@
//! Asynchronous verification of transactions.
use std::{
collections::HashMap,
collections::{HashMap, HashSet},
future::Future,
pin::Pin,
sync::Arc,
@ -146,8 +146,12 @@ where
pub enum Request {
/// Verify the supplied transaction as part of a block.
Block {
/// The transaction hash.
transaction_hash: transaction::Hash,
/// The transaction itself.
transaction: Arc<Transaction>,
/// Set of transaction hashes that create new transparent outputs.
known_outpoint_hashes: Arc<HashSet<transaction::Hash>>,
/// Additional UTXOs which are known at the time of verification.
known_utxos: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
/// The height of the block containing this transaction.
@ -259,6 +263,16 @@ impl Request {
}
}
/// The mined transaction ID for the transaction in this request.
pub fn tx_mined_id(&self) -> transaction::Hash {
match self {
Request::Block {
transaction_hash, ..
} => *transaction_hash,
Request::Mempool { transaction, .. } => transaction.id.mined_id(),
}
}
/// The set of additional known unspent transaction outputs that's in this request.
pub fn known_utxos(&self) -> Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>> {
match self {
@ -267,6 +281,17 @@ impl Request {
}
}
/// The set of additional known [`transparent::OutPoint`]s of unspent transaction outputs that's in this request.
pub fn known_outpoint_hashes(&self) -> Arc<HashSet<transaction::Hash>> {
match self {
Request::Block {
known_outpoint_hashes,
..
} => known_outpoint_hashes.clone(),
Request::Mempool { .. } => HashSet::new().into(),
}
}
/// The height used to select the consensus rules for verifying this transaction.
pub fn height(&self) -> block::Height {
match self {
@ -377,6 +402,16 @@ where
async move {
tracing::trace!(?tx_id, ?req, "got tx verify request");
if let Some(result) = Self::try_find_verified_unmined_tx(&req, mempool.clone()).await {
let verified_tx = result?;
return Ok(Response::Block {
tx_id,
miner_fee: Some(verified_tx.miner_fee),
legacy_sigop_count: verified_tx.legacy_sigop_count
});
}
// Do quick checks first
check::has_inputs_and_outputs(&tx)?;
check::has_enough_orchard_flags(&tx)?;
@ -451,7 +486,7 @@ where
// WONTFIX: Return an error for Request::Block as well to replace this check in
// the state once #2336 has been implemented?
if req.is_mempool() {
Self::check_maturity_height(&req, &spent_utxos)?;
Self::check_maturity_height(&network, &req, &spent_utxos)?;
}
let cached_ffi_transaction =
@ -609,8 +644,52 @@ where
}
}
/// Waits for the UTXOs that are being spent by the given transaction to arrive in
/// the state for [`Block`](Request::Block) requests.
/// Attempts to find a transaction in the mempool by its transaction hash and checks
/// that all of its dependencies are available in the block.
///
/// Returns [`Some(Ok(VerifiedUnminedTx))`](VerifiedUnminedTx) if successful,
/// None if the transaction id was not found in the mempool,
/// or `Some(Err(TransparentInputNotFound))` if the transaction was found, but some of its
/// dependencies are missing in the block.
async fn try_find_verified_unmined_tx(
req: &Request,
mempool: Option<Timeout<Mempool>>,
) -> Option<Result<VerifiedUnminedTx, TransactionError>> {
if req.is_mempool() || req.transaction().is_coinbase() {
return None;
}
let mempool = mempool?;
let known_outpoint_hashes = req.known_outpoint_hashes();
let tx_id = req.tx_mined_id();
let mempool::Response::TransactionWithDeps {
transaction,
dependencies,
} = mempool
.oneshot(mempool::Request::TransactionWithDepsByMinedId(tx_id))
.await
.ok()?
else {
panic!("unexpected response to TransactionWithDepsByMinedId request");
};
// Note: This does not verify that the spends are in order, the spend order
// should be verified during contextual validation in zebra-state.
let has_all_tx_deps = dependencies
.into_iter()
.all(|dependency_id| known_outpoint_hashes.contains(&dependency_id));
let result = if has_all_tx_deps {
Ok(transaction)
} else {
Err(TransactionError::TransparentInputNotFound)
};
Some(result)
}
/// Wait for the UTXOs that are being spent by the given transaction.
///
/// Looks up UTXOs that are being spent by the given transaction in the state or waits
/// for them to be added to the mempool for [`Mempool`](Request::Mempool) requests.
@ -728,10 +807,12 @@ where
/// mature and valid for the request height, or a [`TransactionError`] if the transaction
/// spends transparent coinbase outputs that are immature and invalid for the request height.
pub fn check_maturity_height(
network: &Network,
request: &Request,
spent_utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<(), TransactionError> {
check::tx_transparent_coinbase_spends_maturity(
network,
request.transaction(),
request.height(),
request.known_utxos(),

View File

@ -476,6 +476,7 @@ fn validate_expiry_height_mined(
/// Returns `Ok(())` if spent transparent coinbase outputs are
/// valid for the block height, or a [`Err(TransactionError)`](TransactionError)
pub fn tx_transparent_coinbase_spends_maturity(
network: &Network,
tx: Arc<Transaction>,
height: Height,
block_new_outputs: Arc<HashMap<transparent::OutPoint, transparent::OrderedUtxo>>,
@ -488,7 +489,7 @@ pub fn tx_transparent_coinbase_spends_maturity(
.or_else(|| spent_utxos.get(&spend).cloned())
.expect("load_spent_utxos_fut.await should return an error if a utxo is missing");
let spend_restriction = tx.coinbase_spend_restriction(height);
let spend_restriction = tx.coinbase_spend_restriction(network, height);
zebra_state::check::transparent_coinbase_spend(spend, spend_restriction, &utxo)?;
}

View File

@ -2,7 +2,10 @@
//
// TODO: split fixed test vectors into a `vectors` module?
use std::{collections::HashMap, sync::Arc};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use chrono::{DateTime, TimeZone, Utc};
use color_eyre::eyre::Report;
@ -26,7 +29,7 @@ use zebra_chain::{
},
zip317, Hash, HashType, JoinSplitData, LockTime, Transaction,
},
transparent::{self, CoinbaseData},
transparent::{self, CoinbaseData, CoinbaseSpendRestriction},
};
use zebra_node_services::mempool;
@ -700,13 +703,180 @@ async fn mempool_request_with_unmined_output_spends_is_accepted() {
);
tokio::time::sleep(POLL_MEMPOOL_DELAY * 2).await;
// polled before AwaitOutput request and after a mempool transaction with transparent outputs
// is successfully verified
assert_eq!(
mempool.poll_count(),
2,
"the mempool service should have been polled twice, \
first before being called with an AwaitOutput request, \
then again shortly after a mempool transaction with transparent outputs \
is successfully verified"
"the mempool service should have been polled twice"
);
}
#[tokio::test]
async fn skips_verification_of_block_transactions_in_mempool() {
let mut state: MockService<_, _, _, _> = MockService::build().for_prop_tests();
let mempool: MockService<_, _, _, _> = MockService::build().for_prop_tests();
let (mempool_setup_tx, mempool_setup_rx) = tokio::sync::oneshot::channel();
let verifier = Verifier::new(&Network::Mainnet, state.clone(), mempool_setup_rx);
let verifier = Buffer::new(verifier, 1);
mempool_setup_tx
.send(mempool.clone())
.ok()
.expect("send should succeed");
let height = NetworkUpgrade::Nu6
.activation_height(&Network::Mainnet)
.expect("Canopy activation height is specified");
let fund_height = (height - 1).expect("fake source fund block height is too small");
let (input, output, known_utxos) = mock_transparent_transfer(
fund_height,
true,
0,
Amount::try_from(10001).expect("invalid value"),
);
// Create a non-coinbase V4 tx with the last valid expiry height.
let tx = Transaction::V5 {
network_upgrade: NetworkUpgrade::Nu6,
inputs: vec![input],
outputs: vec![output],
lock_time: LockTime::min_lock_time_timestamp(),
expiry_height: height,
sapling_shielded_data: None,
orchard_shielded_data: None,
};
let tx_hash = tx.hash();
let input_outpoint = match tx.inputs()[0] {
transparent::Input::PrevOut { outpoint, .. } => outpoint,
transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"),
};
tokio::spawn(async move {
state
.expect_request(zebra_state::Request::BestChainNextMedianTimePast)
.await
.expect("verifier should call mock state service with correct request")
.respond(zebra_state::Response::BestChainNextMedianTimePast(
DateTime32::MAX,
));
state
.expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint))
.await
.expect("verifier should call mock state service with correct request")
.respond(zebra_state::Response::UnspentBestChainUtxo(None));
state
.expect_request_that(|req| {
matches!(
req,
zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_)
)
})
.await
.expect("verifier should call mock state service with correct request")
.respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors);
});
let mut mempool_clone = mempool.clone();
tokio::spawn(async move {
mempool_clone
.expect_request(mempool::Request::AwaitOutput(input_outpoint))
.await
.expect("verifier should call mock state service with correct request")
.respond(mempool::Response::UnspentOutput(
known_utxos
.get(&input_outpoint)
.expect("input outpoint should exist in known_utxos")
.utxo
.output
.clone(),
));
});
let verifier_response = verifier
.clone()
.oneshot(Request::Mempool {
transaction: tx.clone().into(),
height,
})
.await;
assert!(
verifier_response.is_ok(),
"expected successful verification, got: {verifier_response:?}"
);
let crate::transaction::Response::Mempool {
transaction,
spent_mempool_outpoints,
} = verifier_response.expect("already checked that response is ok")
else {
panic!("unexpected response variant from transaction verifier for Mempool request")
};
assert_eq!(
spent_mempool_outpoints,
vec![input_outpoint],
"spent_mempool_outpoints in tx verifier response should match input_outpoint"
);
let mut mempool_clone = mempool.clone();
tokio::spawn(async move {
for _ in 0..2 {
mempool_clone
.expect_request(mempool::Request::TransactionWithDepsByMinedId(tx_hash))
.await
.expect("verifier should call mock state service with correct request")
.respond(mempool::Response::TransactionWithDeps {
transaction: transaction.clone(),
dependencies: [input_outpoint.hash].into(),
});
}
});
let make_request = |known_outpoint_hashes| Request::Block {
transaction_hash: tx_hash,
transaction: Arc::new(tx),
known_outpoint_hashes,
known_utxos: Arc::new(HashMap::new()),
height,
time: Utc::now(),
};
let crate::transaction::Response::Block { .. } = verifier
.clone()
.oneshot(make_request.clone()(Arc::new([input_outpoint.hash].into())))
.await
.expect("should return Ok without calling state service")
else {
panic!("unexpected response variant from transaction verifier for Block request")
};
let verifier_response_err = *verifier
.clone()
.oneshot(make_request(Arc::new(HashSet::new())))
.await
.expect_err("should return Err without calling state service")
.downcast::<TransactionError>()
.expect("tx verifier error type should be TransactionError");
assert_eq!(
verifier_response_err,
TransactionError::TransparentInputNotFound,
"should be a transparent input not found error"
);
tokio::time::sleep(POLL_MEMPOOL_DELAY * 2).await;
// polled before AwaitOutput request, after a mempool transaction with transparent outputs,
// is successfully verified, and twice more when checking if a transaction in a block is
// already the mempool.
assert_eq!(
mempool.poll_count(),
4,
"the mempool service should have been polled 4 times"
);
}
@ -745,7 +915,7 @@ async fn mempool_request_with_immature_spend_is_rejected() {
transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"),
};
let spend_restriction = tx.coinbase_spend_restriction(height);
let spend_restriction = tx.coinbase_spend_restriction(&Network::Mainnet, height);
let coinbase_spend_height = Height(5);
@ -813,6 +983,100 @@ async fn mempool_request_with_immature_spend_is_rejected() {
);
}
/// Tests that calls to the transaction verifier with a mempool request that spends
/// mature coinbase outputs to transparent outputs will return Ok() on Regtest.
#[tokio::test]
async fn mempool_request_with_transparent_coinbase_spend_is_accepted_on_regtest() {
let _init_guard = zebra_test::init();
let network = Network::new_regtest(None, Some(1_000));
let mut state: MockService<_, _, _, _> = MockService::build().for_unit_tests();
let verifier = Verifier::new_for_tests(&network, state.clone());
let height = NetworkUpgrade::Nu6
.activation_height(&network)
.expect("NU6 activation height is specified");
let fund_height = (height - 1).expect("fake source fund block height is too small");
let (input, output, known_utxos) = mock_transparent_transfer(
fund_height,
true,
0,
Amount::try_from(10001).expect("invalid value"),
);
// Create a non-coinbase V5 tx with the last valid expiry height.
let tx = Transaction::V5 {
network_upgrade: NetworkUpgrade::Nu6,
inputs: vec![input],
outputs: vec![output],
lock_time: LockTime::min_lock_time_timestamp(),
expiry_height: height,
sapling_shielded_data: None,
orchard_shielded_data: None,
};
let input_outpoint = match tx.inputs()[0] {
transparent::Input::PrevOut { outpoint, .. } => outpoint,
transparent::Input::Coinbase { .. } => panic!("requires a non-coinbase transaction"),
};
let spend_restriction = tx.coinbase_spend_restriction(&network, height);
assert_eq!(
spend_restriction,
CoinbaseSpendRestriction::CheckCoinbaseMaturity {
spend_height: height
}
);
let coinbase_spend_height = Height(5);
let utxo = known_utxos
.get(&input_outpoint)
.map(|utxo| {
let mut utxo = utxo.utxo.clone();
utxo.height = coinbase_spend_height;
utxo.from_coinbase = true;
utxo
})
.expect("known_utxos should contain the outpoint");
zebra_state::check::transparent_coinbase_spend(input_outpoint, spend_restriction, &utxo)
.expect("check should pass");
tokio::spawn(async move {
state
.expect_request(zebra_state::Request::BestChainNextMedianTimePast)
.await
.respond(zebra_state::Response::BestChainNextMedianTimePast(
DateTime32::MAX,
));
state
.expect_request(zebra_state::Request::UnspentBestChainUtxo(input_outpoint))
.await
.respond(zebra_state::Response::UnspentBestChainUtxo(Some(utxo)));
state
.expect_request_that(|req| {
matches!(
req,
zebra_state::Request::CheckBestChainTipNullifiersAndAnchors(_)
)
})
.await
.respond(zebra_state::Response::ValidBestChainTipNullifiersAndAnchors);
});
verifier
.oneshot(Request::Mempool {
transaction: tx.into(),
height,
})
.await
.expect("verification of transaction with mature spend to transparent outputs should pass");
}
/// Tests that errors from the read state service are correctly converted into
/// transaction verifier errors.
#[tokio::test]
@ -952,7 +1216,7 @@ fn v5_coinbase_transaction_with_enable_spends_flag_fails_validation() {
#[tokio::test]
async fn v5_transaction_is_rejected_before_nu5_activation() {
let canopy = NetworkUpgrade::Canopy;
let sapling = NetworkUpgrade::Sapling;
for net in Network::iter() {
let verifier = Verifier::new_for_tests(
@ -960,16 +1224,20 @@ async fn v5_transaction_is_rejected_before_nu5_activation() {
service_fn(|_| async { unreachable!("Service should not be called") }),
);
let tx = v5_transactions(net.block_iter()).next().expect("V5 tx");
assert_eq!(
verifier
.oneshot(Request::Block {
transaction: Arc::new(v5_transactions(net.block_iter()).next().expect("V5 tx")),
transaction_hash: tx.hash(),
transaction: Arc::new(tx),
known_utxos: Arc::new(HashMap::new()),
height: canopy.activation_height(&net).expect("height"),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: sapling.activation_height(&net).expect("height"),
time: DateTime::<Utc>::MAX_UTC,
})
.await,
Err(TransactionError::UnsupportedByNetworkUpgrade(5, canopy))
Err(TransactionError::UnsupportedByNetworkUpgrade(5, sapling))
);
}
}
@ -988,8 +1256,10 @@ async fn v5_transaction_is_accepted_after_nu5_activation() {
let verif_res = Verifier::new_for_tests(&net, state)
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: tx_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1040,8 +1310,10 @@ async fn v4_transaction_with_transparent_transfer_is_accepted() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1084,8 +1356,10 @@ async fn v4_transaction_with_last_valid_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1129,8 +1403,10 @@ async fn v4_coinbase_transaction_with_low_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1176,8 +1452,10 @@ async fn v4_transaction_with_too_low_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1226,8 +1504,10 @@ async fn v4_transaction_with_exceeding_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1279,8 +1559,10 @@ async fn v4_coinbase_transaction_with_exceeding_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1330,8 +1612,10 @@ async fn v4_coinbase_transaction_is_accepted() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1385,8 +1669,10 @@ async fn v4_transaction_with_transparent_transfer_is_rejected_by_the_script() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1440,8 +1726,10 @@ async fn v4_transaction_with_conflicting_transparent_spend_is_rejected() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1511,8 +1799,10 @@ fn v4_transaction_with_conflicting_sprout_nullifier_inside_joinsplit_is_rejected
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1587,8 +1877,10 @@ fn v4_transaction_with_conflicting_sprout_nullifier_across_joinsplits_is_rejecte
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1646,8 +1938,10 @@ async fn v5_transaction_with_transparent_transfer_is_accepted() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1692,8 +1986,10 @@ async fn v5_transaction_with_last_valid_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1737,8 +2033,10 @@ async fn v5_coinbase_transaction_expiry_height() {
let result = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1758,8 +2056,10 @@ async fn v5_coinbase_transaction_expiry_height() {
let result = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(new_transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1787,8 +2087,10 @@ async fn v5_coinbase_transaction_expiry_height() {
let result = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(new_transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1824,8 +2126,10 @@ async fn v5_coinbase_transaction_expiry_height() {
let verification_result = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(new_transaction.clone()),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: new_expiry_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1875,8 +2179,10 @@ async fn v5_transaction_with_too_low_expiry_height() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1924,8 +2230,10 @@ async fn v5_transaction_with_exceeding_expiry_height() {
let verification_result = Verifier::new_for_tests(&Network::Mainnet, state)
.oneshot(Request::Block {
transaction: Arc::new(transaction),
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction.clone()),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: height_max,
time: DateTime::<Utc>::MAX_UTC,
})
@ -1978,8 +2286,10 @@ async fn v5_coinbase_transaction_is_accepted() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2035,8 +2345,10 @@ async fn v5_transaction_with_transparent_transfer_is_rejected_by_the_script() {
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height: transaction_block_height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2083,8 +2395,10 @@ async fn v5_transaction_with_conflicting_transparent_spend_is_rejected() {
let verification_result = Verifier::new_for_tests(&network, state)
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2126,8 +2440,10 @@ fn v4_with_signed_sprout_transfer_is_accepted() {
// Test the transaction verifier
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction,
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2215,8 +2531,10 @@ async fn v4_with_joinsplit_is_rejected_for_modification(
let result = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction: transaction.clone(),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2261,8 +2579,10 @@ fn v4_with_sapling_spends() {
// Test the transaction verifier
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction,
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2303,8 +2623,10 @@ fn v4_with_duplicate_sapling_spends() {
// Test the transaction verifier
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction,
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2347,8 +2669,10 @@ fn v4_with_sapling_outputs_and_no_spends() {
// Test the transaction verifier
let result = verifier
.oneshot(Request::Block {
transaction_hash: transaction.hash(),
transaction,
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2387,8 +2711,10 @@ async fn v5_with_sapling_spends() {
assert_eq!(
verifier
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2424,8 +2750,10 @@ async fn v5_with_duplicate_sapling_spends() {
assert_eq!(
verifier
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2475,8 +2803,10 @@ async fn v5_with_duplicate_orchard_action() {
assert_eq!(
verifier
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx),
known_utxos: Arc::new(HashMap::new()),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: DateTime::<Utc>::MAX_UTC,
})
@ -2530,8 +2860,10 @@ async fn v5_consensus_branch_ids() {
let block_req = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx.clone()),
known_utxos: known_utxos.clone(),
known_outpoint_hashes: Arc::new(HashSet::new()),
// The consensus branch ID of the tx is outdated for this height.
height,
time: DateTime::<Utc>::MAX_UTC,
@ -2558,8 +2890,10 @@ async fn v5_consensus_branch_ids() {
let block_req = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx.clone()),
known_utxos: known_utxos.clone(),
known_outpoint_hashes: Arc::new(HashSet::new()),
// The consensus branch ID of the tx is supported by this height.
height,
time: DateTime::<Utc>::MAX_UTC,
@ -2615,8 +2949,10 @@ async fn v5_consensus_branch_ids() {
let block_req = verifier
.clone()
.oneshot(Request::Block {
transaction_hash: tx.hash(),
transaction: Arc::new(tx.clone()),
known_utxos: known_utxos.clone(),
known_outpoint_hashes: Arc::new(HashSet::new()),
// The consensus branch ID of the tx is not supported by this height.
height,
time: DateTime::<Utc>::MAX_UTC,

View File

@ -1,6 +1,9 @@
//! Randomised property tests for transaction verification.
use std::{collections::HashMap, sync::Arc};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
};
use chrono::{DateTime, Duration, Utc};
use proptest::{collection::vec, prelude::*};
@ -452,13 +455,16 @@ fn validate(
tower::service_fn(|_| async { unreachable!("State service should not be called") });
let verifier = transaction::Verifier::new_for_tests(&network, state_service);
let verifier = Buffer::new(verifier, 10);
let transaction_hash = transaction.hash();
// Test the transaction verifier
verifier
.clone()
.oneshot(transaction::Request::Block {
transaction_hash,
transaction: Arc::new(transaction),
known_utxos: Arc::new(known_utxos),
known_outpoint_hashes: Arc::new(HashSet::new()),
height,
time: block_time,
})

View File

@ -6,13 +6,10 @@ use std::collections::HashSet;
use tokio::sync::oneshot;
use zebra_chain::{
transaction::{self, UnminedTx, UnminedTxId},
transaction::{self, UnminedTx, UnminedTxId, VerifiedUnminedTx},
transparent,
};
#[cfg(feature = "getblocktemplate-rpcs")]
use zebra_chain::transaction::VerifiedUnminedTx;
use crate::BoxError;
mod gossip;
@ -58,6 +55,9 @@ pub enum Request {
/// Outdated requests are pruned on a regular basis.
AwaitOutput(transparent::OutPoint),
/// Request a [`VerifiedUnminedTx`] and its dependencies by its mined id.
TransactionWithDepsByMinedId(transaction::Hash),
/// Get all the [`VerifiedUnminedTx`] in the mempool.
///
/// Equivalent to `TransactionsById(TransactionIds)`,
@ -121,6 +121,14 @@ pub enum Response {
/// Response to [`Request::AwaitOutput`] with the transparent output
UnspentOutput(transparent::Output),
/// Response to [`Request::TransactionWithDepsByMinedId`].
TransactionWithDeps {
/// The queried transaction
transaction: VerifiedUnminedTx,
/// A list of dependencies of the queried transaction.
dependencies: HashSet<transaction::Hash>,
},
/// Returns all [`VerifiedUnminedTx`] in the mempool.
//
// TODO: make the Transactions response return VerifiedUnminedTx,

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

@ -1,44 +0,0 @@
//! Constants for RPC methods and server responses.
use jsonrpc_core::{Error, ErrorCode};
/// The RPC error code used by `zcashd` for incorrect RPC parameters.
///
/// [`jsonrpc_core`] uses these codes:
/// <https://github.com/paritytech/jsonrpc/blob/609d7a6cc160742d035510fa89fb424ccf077660/core/src/types/error.rs#L25-L36>
///
/// `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>
pub const INVALID_PARAMETERS_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-1);
/// The RPC error code used by `zcashd` for missing blocks, when looked up
/// by hash.
pub const INVALID_ADDRESS_OR_KEY_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-5);
/// The RPC error code used by `zcashd` for missing blocks.
///
/// `lightwalletd` expects error code `-8` when a block is not found:
/// <https://github.com/zcash/lightwalletd/blob/v0.4.16/common/common.go#L287-L290>
pub const MISSING_BLOCK_ERROR_CODE: ErrorCode = ErrorCode::ServerError(-8);
/// The RPC error code used by `zcashd` when there are no blocks in the state.
///
/// `lightwalletd` expects error code `0` when there are no blocks in the state.
//
// TODO: find the source code that expects or generates this error
pub const NO_BLOCKS_IN_STATE_ERROR_CODE: ErrorCode = ErrorCode::ServerError(0);
/// The RPC error used by `zcashd` when there are no blocks in the state.
//
// TODO: find the source code that expects or generates this error text, if there is any
// replace literal Error { ... } with this error
pub fn no_blocks_in_state_error() -> Error {
Error {
code: NO_BLOCKS_IN_STATE_ERROR_CODE,
message: "No blocks in state".to_string(),
data: None,
}
}
/// When logging parameter data, only log this much data.
pub const MAX_PARAMS_LOG_LENGTH: usize = 100;

View File

@ -5,7 +5,6 @@
#![doc(html_root_url = "https://docs.rs/zebra_rpc")]
pub mod config;
pub mod constants;
pub mod methods;
pub mod queue;
pub mod server;

File diff suppressed because it is too large Load Diff

View File

@ -1,37 +0,0 @@
//! Error conversions for Zebra's RPC methods.
use jsonrpc_core::ErrorCode;
pub(crate) trait MapServerError<T, E> {
fn map_server_error(self) -> std::result::Result<T, jsonrpc_core::Error>;
}
pub(crate) trait OkOrServerError<T> {
fn ok_or_server_error<S: ToString>(
self,
message: S,
) -> std::result::Result<T, jsonrpc_core::Error>;
}
impl<T, E> MapServerError<T, E> for Result<T, E>
where
E: ToString,
{
fn map_server_error(self) -> Result<T, jsonrpc_core::Error> {
self.map_err(|error| jsonrpc_core::Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})
}
}
impl<T> OkOrServerError<T> for Option<T> {
fn ok_or_server_error<S: ToString>(self, message: S) -> Result<T, jsonrpc_core::Error> {
self.ok_or(jsonrpc_core::Error {
code: ErrorCode::ServerError(0),
message: message.to_string(),
data: None,
})
}
}

File diff suppressed because it is too large Load Diff

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::{
@ -25,12 +26,12 @@ use zebra_consensus::{
use zebra_node_services::mempool::{self, TransactionDependencies};
use zebra_state::GetBlockTemplateChainInfo;
use crate::methods::{
errors::OkOrServerError,
get_block_template_rpcs::{
use crate::{
methods::get_block_template_rpcs::{
constants::{MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP, NOT_SYNCED_ERROR_CODE},
types::{default_roots::DefaultRoots, transaction::TransactionTemplate},
},
server::error::OkOrError,
};
pub use crate::methods::get_block_template_rpcs::types::get_block_template::*;
@ -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,
)),
}
}
@ -87,13 +86,9 @@ pub fn check_parameters(parameters: &Option<JsonParameters>) -> Result<()> {
pub fn check_miner_address(
miner_address: Option<transparent::Address>,
) -> Result<transparent::Address> {
miner_address.ok_or_else(|| Error {
code: ErrorCode::ServerError(0),
message: "configure mining.miner_address in zebrad.toml \
with a transparent address"
.to_string(),
data: None,
})
miner_address.ok_or_misc_error(
"set `mining.miner_address` in `zebrad.toml` to a transparent address".to_string(),
)
}
/// Attempts to validate block proposal against all of the server's
@ -135,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;
@ -181,7 +172,7 @@ where
// but this is ok for an estimate
let (estimated_distance_to_chain_tip, local_tip_height) = latest_chain_tip
.estimate_distance_to_network_chain_tip(network)
.ok_or_server_error("no chain tip available yet")?;
.ok_or_misc_error("no chain tip available yet")?;
if !sync_status.is_close_to_tip()
|| estimated_distance_to_chain_tip > MAX_ESTIMATED_DISTANCE_TO_NETWORK_CHAIN_TIP
@ -193,16 +184,14 @@ where
Hint: check your network connection, clock, and time zone settings."
);
return Err(Error {
code: NOT_SYNCED_ERROR_CODE,
message: format!(
"Zebra has not synced to the chain tip, \
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(())
@ -231,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,
@ -265,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

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,11 @@
//! cargo insta test --review --release -p zebra-rpc --lib -- test_rpc_response_data
//! ```
use std::collections::BTreeMap;
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::{
@ -229,12 +231,10 @@ async fn test_rpc_response_data_for_network(network: &Network) {
snapshot_rpc_getblockchaininfo("", get_blockchain_info, &settings);
// get the first transaction of the first block which is not the genesis
let first_block_first_transaction = &blocks[1].transactions[0];
let first_block_first_tx = &blocks[1].transactions[0];
// build addresses
let address = &first_block_first_transaction.outputs()[1]
.address(network)
.unwrap();
let address = &first_block_first_tx.outputs()[1].address(network).unwrap();
let addresses = vec![address.to_string()];
// `getaddressbalance`
@ -407,8 +407,9 @@ async fn test_rpc_response_data_for_network(network: &Network) {
// `getrawtransaction` verbosity=0
//
// - similar to `getrawmempool` described above, a mempool request will be made to get the requested
// transaction from the mempool, response will be empty as we have this transaction in state
// - Similarly to `getrawmempool` described above, a mempool request will be made to get the
// requested transaction from the mempool. Response will be empty as we have this transaction
// in the state.
let mempool_req = mempool
.expect_request_that(|request| {
matches!(request, mempool::Request::TransactionsByMinedId(_))
@ -417,13 +418,12 @@ async fn test_rpc_response_data_for_network(network: &Network) {
responder.respond(mempool::Response::Transactions(vec![]));
});
// make the api call
let get_raw_transaction =
rpc.get_raw_transaction(first_block_first_transaction.hash().encode_hex(), Some(0u8));
let (response, _) = futures::join!(get_raw_transaction, mempool_req);
let get_raw_transaction = response.expect("We should have a GetRawTransaction struct");
let txid = first_block_first_tx.hash().encode_hex::<String>();
snapshot_rpc_getrawtransaction("verbosity_0", get_raw_transaction, &settings);
let rpc_req = rpc.get_raw_transaction(txid.clone(), Some(0u8));
let (rsp, _) = futures::join!(rpc_req, mempool_req);
settings.bind(|| insta::assert_json_snapshot!(format!("getrawtransaction_verbosity=0"), rsp));
mempool.expect_no_requests().await;
// `getrawtransaction` verbosity=1
let mempool_req = mempool
@ -434,13 +434,31 @@ async fn test_rpc_response_data_for_network(network: &Network) {
responder.respond(mempool::Response::Transactions(vec![]));
});
// make the api call
let get_raw_transaction =
rpc.get_raw_transaction(first_block_first_transaction.hash().encode_hex(), Some(1u8));
let (response, _) = futures::join!(get_raw_transaction, mempool_req);
let get_raw_transaction = response.expect("We should have a GetRawTransaction struct");
let rpc_req = rpc.get_raw_transaction(txid, Some(1u8));
let (rsp, _) = futures::join!(rpc_req, mempool_req);
settings.bind(|| insta::assert_json_snapshot!(format!("getrawtransaction_verbosity=1"), rsp));
mempool.expect_no_requests().await;
snapshot_rpc_getrawtransaction("verbosity_1", get_raw_transaction, &settings);
// `getrawtransaction` with unknown txid
let mempool_req = mempool
.expect_request_that(|request| {
matches!(request, mempool::Request::TransactionsByMinedId(_))
})
.map(|responder| {
responder.respond(mempool::Response::Transactions(vec![]));
});
let rpc_req = rpc.get_raw_transaction(transaction::Hash::from([0; 32]).encode_hex(), Some(1));
let (rsp, _) = futures::join!(rpc_req, mempool_req);
settings.bind(|| insta::assert_json_snapshot!(format!("getrawtransaction_unknown_txid"), rsp));
mempool.expect_no_requests().await;
// `getrawtransaction` with an invalid TXID
let rsp = rpc
.get_raw_transaction("aBadC0de".to_owned(), Some(1))
.await;
settings.bind(|| insta::assert_json_snapshot!(format!("getrawtransaction_invalid_txid"), rsp));
mempool.expect_no_requests().await;
// `getaddresstxids`
let get_address_tx_ids = rpc
@ -666,17 +684,6 @@ fn snapshot_rpc_getrawmempool(raw_mempool: Vec<String>, settings: &insta::Settin
settings.bind(|| insta::assert_json_snapshot!("get_raw_mempool", raw_mempool));
}
/// Snapshot `getrawtransaction` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getrawtransaction(
variant: &'static str,
raw_transaction: GetRawTransaction,
settings: &insta::Settings,
) {
settings.bind(|| {
insta::assert_json_snapshot!(format!("get_raw_transaction_{variant}"), raw_transaction)
});
}
/// Snapshot valid `getaddressbalance` response, using `cargo insta` and JSON serialization.
fn snapshot_rpc_getaddresstxids_valid(
variant: &'static str,

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>(
@ -105,7 +105,8 @@ pub async fn test_responses<State, ReadState>(
extra_coinbase_data: None,
debug_like_zcashd: true,
// TODO: Use default field values when optional features are enabled in tests #8183
..Default::default()
#[cfg(feature = "internal-miner")]
internal_miner: true,
};
// nu5 block height
@ -487,20 +488,18 @@ 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))
.await
.expect("unexpected panic in z_list_unified_receivers RPC task")
.expect("unexpected error in z_list_unified_receivers RPC call");
let z_list_unified_receivers = get_block_template_rpc
.z_list_unified_receivers(ua1)
.await
.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))
.await
.expect("unexpected panic in z_list_unified_receivers RPC task")
.expect("unexpected error in z_list_unified_receivers RPC call");
let z_list_unified_receivers = get_block_template_rpc
.z_list_unified_receivers(ua2)
.await
.expect("unexpected error in z_list_unified_receivers RPC call");
snapshot_rpc_z_listunifiedreceivers("ua2", z_list_unified_receivers, &settings);
}

View File

@ -10,7 +10,11 @@ expression: block
"merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",
"finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000",
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000",
"height": 1,
"confirmations": 10
}
],
"time": 1477671596,
"nonce": "9057977ea6d4ae867decc96359fcf2db8cdebcbfb3bd549de4f21f16cfe83475",

View File

@ -10,7 +10,11 @@ expression: block
"merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",
"finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000",
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000",
"height": 1,
"confirmations": 10
}
],
"time": 1477674473,
"nonce": "0000e5739438a096ca89cde16bcf6001e0c5a7ce6f7c591d26314c26c2560000",

View File

@ -10,7 +10,11 @@ expression: block
"merkleroot": "851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609",
"finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000",
"tx": [
"851bf6fbf7a976327817c738c489d7fa657752445430922d94c983c0b9ed4609"
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000",
"height": 1,
"confirmations": 10
}
],
"time": 1477671596,
"nonce": "9057977ea6d4ae867decc96359fcf2db8cdebcbfb3bd549de4f21f16cfe83475",

View File

@ -10,7 +10,11 @@ expression: block
"merkleroot": "f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75",
"finalsaplingroot": "0000000000000000000000000000000000000000000000000000000000000000",
"tx": [
"f37e9f691fffb635de0999491d906ee85ba40cd36dae9f6e5911a8277d7c5f75"
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000",
"height": 1,
"confirmations": 10
}
],
"time": 1477674473,
"nonce": "0000e5739438a096ca89cde16bcf6001e0c5a7ce6f7c591d26314c26c2560000",

View File

@ -1,5 +0,0 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: raw_transaction
---
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000"

View File

@ -1,5 +0,0 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: raw_transaction
---
"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000"

View File

@ -1,9 +0,0 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: raw_transaction
---
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000",
"height": 1,
"confirmations": 10
}

View File

@ -1,9 +0,0 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: raw_transaction
---
{
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000",
"height": 1,
"confirmations": 10
}

View File

@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Err": {
"code": -5,
"message": "Invalid string length"
}
}

View File

@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Err": {
"code": -5,
"message": "Invalid string length"
}
}

View File

@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Err": {
"code": -5,
"message": "No such mempool or main chain transaction"
}
}

View File

@ -0,0 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Err": {
"code": -5,
"message": "No such mempool or main chain transaction"
}
}

View File

@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Ok": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000"
}

View File

@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Ok": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000"
}

View File

@ -0,0 +1,12 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Ok": {
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff025100ffffffff0250c30000000000002321027a46eb513588b01b37ea24303f4b628afd12cc20df789fede0921e43cad3e875acd43000000000000017a9147d46a730d31f97b1930d3368a967c309bd4d136a8700000000",
"height": 1,
"confirmations": 10
}
}

View File

@ -0,0 +1,12 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: rsp
snapshot_kind: text
---
{
"Ok": {
"hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff03510101ffffffff0250c30000000000002321025229e1240a21004cf8338db05679fa34753706e84f6aebba086ba04317fd8f99acd43000000000000017a914ef775f1f997f122a062fff1a2d7443abd1f9c6428700000000",
"height": 1,
"confirmations": 10
}
}

View File

@ -1,10 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: treestate
snapshot_kind: text
---
{
"Err": {
"code": -8,
"message": "the requested block was not found"
"message": "the requested block is not in the main chain"
}
}

View File

@ -1,10 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: treestate
snapshot_kind: text
---
{
"Err": {
"code": -8,
"message": "the requested block was not found"
"message": "the requested block is not in the main chain"
}
}

View File

@ -1,10 +1,11 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: treestate
snapshot_kind: text
---
{
"Err": {
"code": 0,
"code": -8,
"message": "parse error: could not convert the input string to a hash or height"
}
}

View File

@ -1,15 +1,18 @@
//! Fixed test vectors for RPC methods.
use std::ops::RangeInclusive;
use std::sync::Arc;
use futures::FutureExt;
use tower::buffer::Buffer;
use zebra_chain::serialization::ZcashSerialize;
use zebra_chain::{
amount::Amount,
block::Block,
chain_tip::{mock::MockChainTip, NoChainTip},
parameters::Network::*,
serialization::{ZcashDeserializeInto, ZcashSerialize},
serialization::ZcashDeserializeInto,
transaction::UnminedTxId,
};
use zebra_node_services::BoxError;
@ -176,7 +179,7 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Hash(tx.hash()))
.collect(),
trees,
size: None,
@ -219,7 +222,7 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Hash(tx.hash()))
.collect(),
trees,
size: None,
@ -262,7 +265,11 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Object(TransactionObject {
hex: (*tx).clone().into(),
height: Some(i.try_into().expect("valid u32")),
confirmations: Some((blocks.len() - i).try_into().expect("valid i64"))
}))
.collect(),
trees,
size: None,
@ -305,7 +312,11 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Object(TransactionObject {
hex: (*tx).clone().into(),
height: Some(i.try_into().expect("valid u32")),
confirmations: Some((blocks.len() - i).try_into().expect("valid i64"))
}))
.collect(),
trees,
size: None,
@ -348,7 +359,7 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Hash(tx.hash()))
.collect(),
trees,
size: None,
@ -391,7 +402,7 @@ async fn rpc_getblock() {
tx: block
.transactions
.iter()
.map(|tx| tx.hash().encode_hex())
.map(|tx| GetBlockTransaction::Hash(tx.hash()))
.collect(),
trees,
size: None,
@ -485,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
@ -493,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!(
@ -722,9 +732,12 @@ async fn rpc_getrawtransaction() {
conventional_fee: Amount::zero(),
}]));
});
let get_tx_req = rpc.get_raw_transaction(tx.hash().encode_hex(), Some(0u8));
let (response, _) = futures::join!(get_tx_req, mempool_req);
let get_tx = response.expect("We should have a GetRawTransaction struct");
let rpc_req = rpc.get_raw_transaction(tx.hash().encode_hex(), Some(0u8));
let (rsp, _) = futures::join!(rpc_req, mempool_req);
let get_tx = rsp.expect("we should have a `GetRawTransaction` struct");
if let GetRawTransaction::Raw(raw_tx) = get_tx {
assert_eq!(raw_tx.as_ref(), tx.zcash_serialize_to_vec().unwrap());
} else {
@ -752,12 +765,14 @@ async fn rpc_getrawtransaction() {
let run_state_test_case = |block_idx: usize, block: Arc<Block>, tx: Arc<Transaction>| {
let read_state = read_state.clone();
let tx_hash = tx.hash();
let get_tx_verbose_0_req = rpc.get_raw_transaction(tx_hash.encode_hex(), Some(0u8));
let get_tx_verbose_1_req = rpc.get_raw_transaction(tx_hash.encode_hex(), Some(1u8));
let txid = tx.hash();
let hex_txid = txid.encode_hex::<String>();
let get_tx_verbose_0_req = rpc.get_raw_transaction(hex_txid.clone(), Some(0u8));
let get_tx_verbose_1_req = rpc.get_raw_transaction(hex_txid, Some(1u8));
async move {
let (response, _) = futures::join!(get_tx_verbose_0_req, make_mempool_req(tx_hash));
let (response, _) = futures::join!(get_tx_verbose_0_req, make_mempool_req(txid));
let get_tx = response.expect("We should have a GetRawTransaction struct");
if let GetRawTransaction::Raw(raw_tx) = get_tx {
assert_eq!(raw_tx.as_ref(), tx.zcash_serialize_to_vec().unwrap());
@ -765,18 +780,22 @@ async fn rpc_getrawtransaction() {
unreachable!("Should return a Raw enum")
}
let (response, _) = futures::join!(get_tx_verbose_1_req, make_mempool_req(tx_hash));
let GetRawTransaction::Object {
let (response, _) = futures::join!(get_tx_verbose_1_req, make_mempool_req(txid));
let GetRawTransaction::Object(TransactionObject {
hex,
height,
confirmations,
} = response.expect("We should have a GetRawTransaction struct")
}) = response.expect("We should have a GetRawTransaction struct")
else {
unreachable!("Should return a Raw enum")
};
let height = height.expect("state requests should have height");
let confirmations = confirmations.expect("state requests should have confirmations");
assert_eq!(hex.as_ref(), tx.zcash_serialize_to_vec().unwrap());
assert_eq!(height, block_idx as i32);
assert_eq!(height, block_idx as u32);
let depth_response = read_state
.oneshot(zebra_state::ReadRequest::Depth(block.hash()))
@ -870,25 +889,18 @@ async fn rpc_getaddresstxids_invalid_arguments() {
);
// call the method with an invalid address string
let address = "11111111".to_string();
let addresses = vec![address.clone()];
let start: u32 = 1;
let end: u32 = 2;
let error = rpc
let rpc_rsp = rpc
.get_address_tx_ids(GetAddressTxIdsRequest {
addresses: addresses.clone(),
start,
end,
addresses: vec!["t1invalidaddress".to_owned()],
start: 1,
end: 2,
})
.await
.unwrap_err();
assert_eq!(
error.message,
format!(
"invalid address \"{}\": parse error: t-addr decoding error",
address.clone()
)
);
assert_eq!(rpc_rsp.code(), ErrorCode::ServerError(-5).code());
mempool.expect_no_requests().await;
// create a valid address
let address = "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".to_string();
@ -906,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()
);
@ -922,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()
);
@ -938,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()
);
@ -1078,17 +1090,13 @@ async fn rpc_getaddressutxos_invalid_arguments() {
);
// call the method with an invalid address string
let address = "11111111".to_string();
let addresses = vec![address.clone()];
let error = rpc
.0
.get_address_utxos(AddressStrings::new(addresses))
.get_address_utxos(AddressStrings::new(vec!["t1invalidaddress".to_owned()]))
.await
.unwrap_err();
assert_eq!(
error.message,
format!("invalid address \"{address}\": parse error: t-addr decoding error")
);
assert_eq!(error.code(), ErrorCode::ServerError(-5).code());
mempool.expect_no_requests().await;
state.expect_no_requests().await;
@ -1245,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;
}
@ -1552,7 +1563,8 @@ async fn rpc_getblocktemplate_mining_address(use_p2pkh: bool) {
extra_coinbase_data: None,
debug_like_zcashd: true,
// TODO: Use default field values when optional features are enabled in tests #8183
..Default::default()
#[cfg(feature = "internal-miner")]
internal_miner: true,
};
// nu5 block height
@ -1688,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);
@ -1701,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));
@ -1712,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
@ -1724,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 {
@ -1734,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
@ -1752,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
@ -2006,7 +2024,8 @@ async fn rpc_getdifficulty() {
extra_coinbase_data: None,
debug_like_zcashd: true,
// TODO: Use default field values when optional features are enabled in tests #8183
..Default::default()
#[cfg(feature = "internal-miner")]
internal_miner: true,
};
// nu5 block height

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,17 +24,18 @@ 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;
pub mod http_request_compatibility;
pub mod rpc_call_compatibility;
@ -54,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 {
@ -67,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()
}
@ -76,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.
///
@ -89,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,
@ -114,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,
@ -149,136 +151,79 @@ 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,);
let listen_addr = config
.listen_addr
.expect("caller should make sure listen_addr is set");
// Create handler compatible with V1 and V2 RPC protocols
let mut io: MetaIoHandler<(), _> =
MetaIoHandler::new(Compatibility::Both, FixRpcResponseMiddleware);
#[cfg(feature = "getblocktemplate-rpcs")]
// Initialize the getblocktemplate rpc method handler
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
&network,
mining_config.clone(),
mempool.clone(),
state.clone(),
latest_chain_tip.clone(),
block_verifier_router,
sync_status,
address_book,
);
// Initialize the rpc methods with the zebra version
let (rpc_impl, rpc_tx_queue_task_handle) = RpcImpl::new(
build_version.clone(),
user_agent,
network.clone(),
config.debug_force_finished_sync,
#[cfg(feature = "getblocktemplate-rpcs")]
{
// Initialize the getblocktemplate rpc method handler
let get_block_template_rpc_impl = GetBlockTemplateRpcImpl::new(
&network,
mining_config.clone(),
mempool.clone(),
state.clone(),
latest_chain_tip.clone(),
block_verifier_router,
sync_status,
address_book,
);
mining_config.debug_like_zcashd,
#[cfg(not(feature = "getblocktemplate-rpcs"))]
true,
mempool,
state,
latest_chain_tip,
);
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(),
user_agent,
network.clone(),
config.debug_force_finished_sync,
#[cfg(feature = "getblocktemplate-rpcs")]
mining_config.debug_like_zcashd,
#[cfg(not(feature = "getblocktemplate-rpcs"))]
true,
mempool,
state,
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 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)
} else {
HttpRequestMiddleware::default()
};
// 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)
.expect("Unable to start RPC server");
info!("{OPENED_RPC_ENDPOINT_MSG}{}", server_instance.address());
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),
})
});
(
rpc_server_task_handle,
rpc_tx_queue_task_handle,
Some(rpc_server_handle),
)
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");
HttpRequestMiddlewareLayer::new(Some(cookie))
} 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,
)
}
HttpRequestMiddlewareLayer::new(None)
};
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);
#[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 server_task: JoinHandle<Result<(), tower::BoxError>> = tokio::spawn(async move {
server_instance.start(rpc_module).stopped().await;
Ok(())
});
Ok((server_task, rpc_tx_queue_task_handle))
}
/// Shut down this RPC server, blocking the current thread.
@ -304,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();
@ -320,7 +265,7 @@ impl RpcServer {
}
info!("Stopping RPC server");
close_handle.clone().close();
let _ = close_handle.stop();
debug!("Stopped RPC server");
})
};

View File

@ -0,0 +1,115 @@
//! RPC error codes & their handling.
use jsonrpsee_types::{ErrorCode, ErrorObject, ErrorObjectOwned};
/// Bitcoin RPC error codes
///
/// Drawn from <https://github.com/zcash/zcash/blob/99ad6fdc3a549ab510422820eea5e5ce9f60a5fd/src/rpc/protocol.h#L32-L80>.
///
/// ## Notes
///
/// - All explicit discriminants fit within `i64`.
#[derive(Default)]
pub enum LegacyCode {
// General application defined errors
/// `std::exception` thrown in command handling
#[default]
Misc = -1,
/// Server is in safe mode, and command is not allowed in safe mode
ForbiddenBySafeMode = -2,
/// Unexpected type was passed as parameter
Type = -3,
/// Invalid address or key
InvalidAddressOrKey = -5,
/// Ran out of memory during operation
OutOfMemory = -7,
/// Invalid, missing or duplicate parameter
InvalidParameter = -8,
/// Database error
Database = -20,
/// Error parsing or validating structure in raw format
Deserialization = -22,
/// General error during transaction or block submission
Verify = -25,
/// Transaction or block was rejected by network rules
VerifyRejected = -26,
/// Transaction already in chain
VerifyAlreadyInChain = -27,
/// Client still warming up
InWarmup = -28,
// P2P client errors
/// Bitcoin is not connected
ClientNotConnected = -9,
/// Still downloading initial blocks
ClientInInitialDownload = -10,
/// Node is already added
ClientNodeAlreadyAdded = -23,
/// Node has not been added before
ClientNodeNotAdded = -24,
/// Node to disconnect not found in connected nodes
ClientNodeNotConnected = -29,
/// Invalid IP/Subnet
ClientInvalidIpOrSubnet = -30,
}
impl From<LegacyCode> for ErrorCode {
fn from(code: LegacyCode) -> Self {
Self::ServerError(code as i32)
}
}
impl From<LegacyCode> for i32 {
fn from(code: LegacyCode) -> Self {
code as i32
}
}
/// 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)
}
}
/// A trait for conditionally converting a value into a `Result<T, jsonrpc_core::Error>`.
pub(crate) trait OkOrError<T>: Sized {
/// Converts the implementing type to `Result<T, jsonrpc_core::Error>`, using an error code and
/// message if conversion is to `Err`.
fn ok_or_error(
self,
code: impl Into<ErrorCode>,
message: impl ToString,
) -> 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, ErrorObjectOwned> {
self.ok_or_error(LegacyCode::Misc, message)
}
}
impl<T, E> MapError<T> for Result<T, E>
where
E: ToString,
{
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<ErrorCode>,
message: impl ToString,
) -> 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<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 }
}
}
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();
}
// 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,106 +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, ErrorCode, Metadata, MethodCall, Notification,
use jsonrpsee::{
server::middleware::rpc::{layer::ResponseFuture, RpcService, RpcServiceT},
MethodResponse,
};
use jsonrpsee_types::ErrorObject;
use crate::constants::{INVALID_PARAMETERS_ERROR_CODE, MAX_PARAMS_LOG_LENGTH};
/// 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 {
/// Replace [`jsonrpc_core`] server error codes in `output` with the `zcashd` equivalents.
fn fix_error_codes(output: &mut Option<Output>) {
if let Some(Output::Failure(Failure { ref mut error, .. })) = output {
if matches!(error.code, ErrorCode::InvalidParams) {
let original_code = error.code.clone();
error.code = INVALID_PARAMETERS_ERROR_CODE;
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 {
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,111 +17,71 @@ 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();
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();
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> =
MockService::build().for_unit_tests();
info!("spawning RPC server...");
info!("spawning RPC server...");
let _rpc_server_task_handle = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
"RPC server test",
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
Buffer::new(block_verifier_router.clone(), 1),
MockSyncStatus::default(),
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
);
let (rpc_server_task_handle, rpc_tx_queue_task_handle, _rpc_server) = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
"RPC server 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 server, checking services...");
info!("spawned RPC server, checking services...");
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));
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
}
/// 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,300 +90,111 @@ 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();
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();
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> =
MockService::build().for_unit_tests();
info!("spawning RPC server...");
info!("spawning RPC server...");
let rpc_server_task_handle = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
"RPC server test",
Buffer::new(mempool.clone(), 1),
Buffer::new(state.clone(), 1),
Buffer::new(block_verifier_router.clone(), 1),
MockSyncStatus::default(),
MockAddressBookPeers::default(),
NoChainTip,
Mainnet,
)
.await
.expect("");
let (rpc_server_task_handle, rpc_tx_queue_task_handle, rpc_server) = RpcServer::spawn(
config,
Default::default(),
"RPC server test",
"RPC server 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 server, checking services...");
info!("spawned RPC server, checking services...");
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
mempool.expect_no_requests().await;
state.expect_no_requests().await;
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());
}
});
info!("waiting for RPC server to shut down...");
rt.shutdown_timeout(Duration::from_secs(1));
if do_shutdown {
rpc_server_task_handle.0.abort();
}
}
/// 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 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();
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 RPC server 1...");
info!("spawning RPC server 1...");
let _rpc_server_1_task_handle = 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,
)
.await;
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;
tokio::time::sleep(Duration::from_secs(3)).await;
info!("spawning conflicted RPC server 2...");
info!("spawning conflicted RPC server 2...");
let _rpc_server_2_task_handle = 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,
)
.await;
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...");
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:?}"
),
},
}
mempool.expect_no_requests().await;
state.expect_no_requests().await;
block_verifier_router.expect_no_requests().await;
}

View File

@ -21,8 +21,8 @@ use zebra_state::{
use zebra_chain::diagnostic::task::WaitForPanics;
use crate::{
constants::MISSING_BLOCK_ERROR_CODE,
methods::{hex_data::HexData, GetBlockHeightAndHash},
server,
};
/// How long to wait between calls to `getbestblockheightandhash` when it:
@ -382,8 +382,11 @@ impl SyncerRpcMethods for RpcRequestClient {
}
Err(err)
if err
.downcast_ref::<jsonrpc_core::Error>()
.is_some_and(|err| err.code == MISSING_BLOCK_ERROR_CODE) =>
.downcast_ref::<jsonrpsee_types::ErrorCode>()
.is_some_and(|err| {
let code: i32 = server::error::LegacyCode::InvalidParameter.into();
err.code() == code
}) =>
{
Ok(None)
}

View File

@ -1,24 +1,31 @@
//! Fixed Zebra RPC serialization test vectors.
use crate::methods::{GetBlock, GetRawTransaction};
use crate::methods::{GetBlock, GetRawTransaction, TransactionObject};
#[test]
pub fn test_transaction_serialization() {
let expected_tx = GetRawTransaction::Raw(vec![0x42].into());
let expected_json = r#""42""#;
let j = serde_json::to_string(&expected_tx).unwrap();
let tx = GetRawTransaction::Raw(vec![0x42].into());
assert_eq!(j, expected_json);
assert_eq!(serde_json::to_string(&tx).unwrap(), r#""42""#);
let expected_tx = GetRawTransaction::Object {
let tx = GetRawTransaction::Object(TransactionObject {
hex: vec![0x42].into(),
height: 1,
confirmations: 0,
};
let expected_json = r#"{"hex":"42","height":1,"confirmations":0}"#;
let j = serde_json::to_string(&expected_tx).unwrap();
height: Some(1),
confirmations: Some(0),
});
assert_eq!(j, expected_json);
assert_eq!(
serde_json::to_string(&tx).unwrap(),
r#"{"hex":"42","height":1,"confirmations":0}"#
);
let tx = GetRawTransaction::Object(TransactionObject {
hex: vec![0x42].into(),
height: None,
confirmations: None,
});
assert_eq!(serde_json::to_string(&tx).unwrap(), r#"{"hex":"42"}"#);
}
#[test]

View File

@ -48,7 +48,7 @@ fn accept_shielded_mature_coinbase_utxo_spend() {
let ordered_utxo = transparent::OrderedUtxo::new(output, created_height, 0);
let min_spend_height = Height(created_height.0 + MIN_TRANSPARENT_COINBASE_MATURITY);
let spend_restriction = transparent::CoinbaseSpendRestriction::OnlyShieldedOutputs {
let spend_restriction = transparent::CoinbaseSpendRestriction::CheckCoinbaseMaturity {
spend_height: min_spend_height,
};
@ -78,7 +78,7 @@ fn reject_unshielded_coinbase_utxo_spend() {
};
let ordered_utxo = transparent::OrderedUtxo::new(output, created_height, 0);
let spend_restriction = transparent::CoinbaseSpendRestriction::SomeTransparentOutputs;
let spend_restriction = transparent::CoinbaseSpendRestriction::DisallowCoinbaseSpend;
let result =
check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref());
@ -104,7 +104,7 @@ fn reject_immature_coinbase_utxo_spend() {
let min_spend_height = Height(created_height.0 + MIN_TRANSPARENT_COINBASE_MATURITY);
let spend_height = Height(min_spend_height.0 - 1);
let spend_restriction =
transparent::CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height };
transparent::CoinbaseSpendRestriction::CheckCoinbaseMaturity { spend_height };
let result =
check::utxo::transparent_coinbase_spend(outpoint, spend_restriction, ordered_utxo.as_ref());

View File

@ -72,8 +72,10 @@ pub fn transparent_spend(
// We don't want to use UTXOs from invalid pending blocks,
// so we check transparent coinbase maturity and shielding
// using known valid UTXOs during non-finalized chain validation.
let spend_restriction =
transaction.coinbase_spend_restriction(semantically_verified.height);
let spend_restriction = transaction.coinbase_spend_restriction(
&finalized_state.network(),
semantically_verified.height,
);
transparent_coinbase_spend(spend, spend_restriction, utxo.as_ref())?;
// We don't delete the UTXOs until the block is committed,
@ -195,7 +197,7 @@ pub fn transparent_coinbase_spend(
}
match spend_restriction {
OnlyShieldedOutputs { spend_height } => {
CheckCoinbaseMaturity { spend_height } => {
let min_spend_height = utxo.height + MIN_TRANSPARENT_COINBASE_MATURITY.into();
let min_spend_height =
min_spend_height.expect("valid UTXOs have coinbase heights far below Height::MAX");
@ -210,7 +212,7 @@ pub fn transparent_coinbase_spend(
})
}
}
SomeTransparentOutputs => Err(UnshieldedTransparentCoinbaseSpend { outpoint }),
DisallowCoinbaseSpend => Err(UnshieldedTransparentCoinbaseSpend { outpoint }),
}
}

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,20 +243,31 @@ impl StartCmd {
}
// Launch RPC server
info!("spawning RPC server");
let (rpc_task_handle, rpc_tx_queue_task_handle, rpc_server) = RpcServer::spawn(
config.rpc.clone(),
config.mining.clone(),
build_version(),
user_agent(),
mempool.clone(),
read_only_state_service.clone(),
block_verifier_router.clone(),
sync_status.clone(),
address_book.clone(),
latest_chain_tip.clone(),
config.network.network.clone(),
);
let (rpc_task_handle, mut rpc_tx_queue_task_handle) =
if let Some(listen_addr) = config.rpc.listen_addr {
info!("spawning RPC server");
info!("Trying to open RPC endpoint at {}...", listen_addr,);
let rpc_task_handle = RpcServer::spawn(
config.rpc.clone(),
config.mining.clone(),
build_version(),
user_agent(),
mempool.clone(),
read_only_state_service.clone(),
block_verifier_router.clone(),
sync_status.clone(),
address_book.clone(),
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

@ -737,6 +737,24 @@ impl Service<Request> for Mempool {
async move { Ok(Response::Transactions(res)) }.boxed()
}
Request::TransactionWithDepsByMinedId(tx_id) => {
trace!(?req, "got mempool request");
let res = if let Some((transaction, dependencies)) =
storage.transaction_with_deps(tx_id)
{
Ok(Response::TransactionWithDeps {
transaction,
dependencies,
})
} else {
Err("transaction not found in mempool".into())
};
trace!(?req, ?res, "answered mempool request");
async move { res }.boxed()
}
Request::AwaitOutput(outpoint) => {
trace!(?req, "got mempool request");
@ -832,7 +850,7 @@ impl Service<Request> for Mempool {
Request::TransactionsById(_) => Response::Transactions(Default::default()),
Request::TransactionsByMinedId(_) => Response::Transactions(Default::default()),
Request::AwaitOutput(_) => {
Request::TransactionWithDepsByMinedId(_) | Request::AwaitOutput(_) => {
return async move {
Err("mempool is not active: wait for Zebra to sync to the tip".into())
}

View File

@ -513,6 +513,23 @@ impl Storage {
.map(|(_, tx)| &tx.transaction)
}
/// Returns a transaction and the transaction ids of its dependencies, if it is in the verified set.
pub fn transaction_with_deps(
&self,
tx_id: transaction::Hash,
) -> Option<(VerifiedUnminedTx, HashSet<transaction::Hash>)> {
let tx = self.verified.transactions().get(&tx_id).cloned()?;
let deps = self
.verified
.transaction_dependencies()
.dependencies()
.get(&tx_id)
.cloned()
.unwrap_or_default();
Some((tx, deps))
}
/// Returns `true` if a transaction exactly matching an [`UnminedTxId`] is in
/// the mempool.
///

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

@ -17,7 +17,6 @@ use zebra_chain::{
};
use zebra_node_services::rpc_client::RpcRequestClient;
use zebra_rpc::{
constants::MISSING_BLOCK_ERROR_CODE,
methods::{
get_block_template_rpcs::{
get_block_template::{
@ -27,7 +26,7 @@ use zebra_rpc::{
},
hex_data::HexData,
},
server::OPENED_RPC_ENDPOINT_MSG,
server::{self, OPENED_RPC_ENDPOINT_MSG},
};
use zebra_test::args;
@ -162,8 +161,11 @@ impl MiningRpcMethods for RpcRequestClient {
}
Err(err)
if err
.downcast_ref::<jsonrpc_core::Error>()
.is_some_and(|err| err.code == MISSING_BLOCK_ERROR_CODE) =>
.downcast_ref::<jsonrpsee_types::ErrorObject>()
.is_some_and(|err| {
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
}
}