From 797218bf40c0a4013bd77eda8a1d5e24d57d3533 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 17 May 2024 02:58:11 +0100 Subject: [PATCH 01/40] added jsonrpc.rs --- Cargo.lock | 1635 ++++++++++++++++++++++++++++++---- Cargo.toml | 9 +- integration-tests/Cargo.toml | 2 +- zingo-proxyd/Cargo.toml | 11 +- zingo-rpc/Cargo.toml | 19 +- zingo-rpc/src/jsonrpc.rs | 615 +++++++++++++ zingo-rpc/src/lib.rs | 1 + 7 files changed, 2095 insertions(+), 197 deletions(-) create mode 100644 zingo-rpc/src/jsonrpc.rs diff --git a/Cargo.lock b/Cargo.lock index 59f5d79..cf8fbf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,7 +33,7 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cipher 0.3.0", "cpufeatures", "ctr 0.8.0", @@ -46,7 +46,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cipher 0.4.4", "cpufeatures", ] @@ -68,7 +68,7 @@ version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "version_check", "zerocopy", @@ -258,7 +258,7 @@ dependencies = [ "async-trait", "axum-core", "bitflags 1.3.2", - "bytes 1.5.0", + "bytes 1.6.0", "futures-util", "http", "http-body", @@ -284,7 +284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" dependencies = [ "async-trait", - "bytes 1.5.0", + "bytes 1.6.0", "futures-util", "http", "http-body", @@ -302,7 +302,7 @@ checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -333,6 +333,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.0.1" @@ -375,6 +381,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.49", + "which", +] + [[package]] name = "bip0039" version = "0.10.1" @@ -395,7 +424,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" dependencies = [ - "bs58 0.5.0", + "bs58 0.5.1", "hmac 0.12.1", "k256", "once_cell", @@ -450,9 +479,19 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bitflags-serde-legacy" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "5b64e60c28b6d25ad92e8b367801ff9aa12b41d05fc8798055d296bace4a60cc" +dependencies = [ + "bitflags 2.5.0", + "serde", +] [[package]] name = "bitvec" @@ -518,7 +557,7 @@ dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if", + "cfg-if 1.0.0", "constant_time_eq", "digest 0.10.7", ] @@ -611,6 +650,15 @@ dependencies = [ "x25519-dalek 2.0.1", ] +[[package]] +name = "bridgetree" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfcb6c5a091e80cb3d3b0c1a7f126af4631cd5065b1f9929b139f1be8f3fb62" +dependencies = [ + "incrementalmerkletree", +] + [[package]] name = "bs58" version = "0.4.0" @@ -619,14 +667,24 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2 0.10.8", "tinyvec", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "build_utils" version = "0.1.0" @@ -638,6 +696,12 @@ version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "byte-tools" version = "0.3.1" @@ -672,13 +736,24 @@ dependencies = [ [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "cbc" version = "0.1.2" @@ -690,14 +765,30 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +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" @@ -720,7 +811,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cipher 0.4.4", "cpufeatures", ] @@ -748,6 +839,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets 0.52.0", ] @@ -772,6 +864,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.1" @@ -831,6 +934,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "colorchoice" version = "1.0.0" @@ -921,7 +1034,7 @@ checksum = "8ed6aa9f904de106fa16443ad14ec2abe75e94ba003bb61c681c0e43d4c58d2a" dependencies = [ "digest 0.10.7", "ecdsa", - "ed25519-zebra", + "ed25519-zebra 3.1.0", "k256", "rand_core 0.6.4", "thiserror", @@ -1025,7 +1138,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1167,12 +1280,14 @@ version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", + "digest 0.10.7", "fiat-crypto", "platforms", "rustc_version", + "serde", "subtle 2.4.1", "zeroize", ] @@ -1237,7 +1352,7 @@ dependencies = [ "cosmwasm-std", "cw2", "schemars", - "semver 1.0.21", + "semver 1.0.23", "serde", "thiserror", ] @@ -1297,13 +1412,89 @@ dependencies = [ "serde", ] +[[package]] +name = "cxx" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048948e14bc2c2652ec606c8e3bb913407f0187288fb351a0b2d972beaf12070" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-gen" +version = "0.7.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a476ac5d29b1957ad93669eef9a030e9fc139103f9bb1ff9f15504c3f21236b0" +dependencies = [ + "codespan-reporting", + "proc-macro2", + "quote", + "syn 2.0.49", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af40b0467c68d3d9fb7550ef984edc8ad47252f703ef0f1f2d1052e0e4af8793" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743446286141c9f6d4497c493c01234eb848e14d2e20866ae9811eae0630cb9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.49", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.49", +] + [[package]] name = "dashmap" version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "hashbrown 0.14.3", "lock_api", "once_cell", @@ -1333,6 +1524,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -1445,6 +1637,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] + +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -1493,6 +1705,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", + "serde", "signature 2.2.0", ] @@ -1538,6 +1751,22 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek 4.1.2", + "ed25519 2.2.3", + "hashbrown 0.14.3", + "hex 0.4.3", + "rand_core 0.6.4", + "serde", + "sha2 0.10.8", + "zeroize", +] + [[package]] name = "either" version = "1.10.0" @@ -1569,7 +1798,7 @@ version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1605,6 +1834,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equihash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab579d7cf78477773b03e80bc2f89702ef02d7112c711d54ca93dcdce68533d5" +dependencies = [ + "blake2b_simd", + "byteorder", +] + [[package]] name = "equihash" version = "0.2.0" @@ -1646,6 +1885,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "f4jumble" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a83e8d7fd0c526af4aad893b7c9fe41e2699ed8a776a6c74aecdeafe05afc75" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "f4jumble" version = "0.1.0" @@ -1703,6 +1951,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1930,7 +2190,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", @@ -1943,7 +2203,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -1981,6 +2241,25 @@ dependencies = [ "url", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "gloo-net" version = "0.3.1" @@ -2069,16 +2348,16 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "fnv", "futures-core", "futures-sink", "futures-util", "http", - "indexmap 2.2.3", + "indexmap 2.2.6", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.11", "tracing", ] @@ -2230,6 +2509,9 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "hex-literal" @@ -2237,6 +2519,12 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hkdf" version = "0.11.0" @@ -2290,7 +2578,7 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "fnv", "itoa", ] @@ -2316,7 +2604,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "http", "pin-project-lite", ] @@ -2349,6 +2637,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "human_bytes" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" + [[package]] name = "humantime" version = "1.3.0" @@ -2380,7 +2674,7 @@ version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "futures-channel", "futures-core", "futures-util", @@ -2445,7 +2739,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "hyper", "native-tls", "tokio", @@ -2475,6 +2769,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -2485,14 +2785,36 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "incrementalmerkletree" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "361c467824d4d9d4f284be4b2608800839419dccc4d4608f28345237fe354623" +checksum = "eb1872810fb725b06b8c153dde9e86f3ec26747b9b60096da7a869883b549cbe" dependencies = [ "either", "proptest", + "rand 0.8.5", + "rand_core 0.6.4", ] [[package]] @@ -2514,12 +2836,13 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.3", + "serde", ] [[package]] @@ -2543,7 +2866,7 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -2559,9 +2882,9 @@ dependencies = [ "serde_json", "shardtree 0.3.1", "tokio", - "zcash_address", + "zcash_address 0.3.0", "zcash_client_backend", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zingo-netutils", "zingo-proxyd", "zingo-rpc", @@ -2642,9 +2965,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -2665,26 +2988,87 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" [[package]] -name = "jubjub" -version = "0.10.0" +name = "jsonrpc-core" +version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "bitvec", - "bls12_381 0.8.0", - "ff 0.13.0", - "group 0.13.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "k256" + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "jsonrpc-derive" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2" +dependencies = [ + "proc-macro-crate 0.1.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jsonrpc-http-server" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" +dependencies = [ + "futures", + "hyper", + "jsonrpc-core", + "jsonrpc-server-utils", + "log", + "net2", + "parking_lot 0.11.2", + "unicase", +] + +[[package]] +name = "jsonrpc-server-utils" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" +dependencies = [ + "bytes 1.6.0", + "futures", + "globset", + "jsonrpc-core", + "lazy_static", + "log", + "tokio", + "tokio-stream", + "tokio-util 0.6.10", + "unicase", +] + +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381 0.8.0", + "ff 0.13.0", + "group 0.13.0", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "k256" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "ecdsa", "elliptic-curve", "once_cell", @@ -2716,6 +3100,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.153" @@ -2734,6 +3124,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if 1.0.0", + "windows-targets 0.52.0", +] + [[package]] name = "libm" version = "0.2.8" @@ -2746,11 +3146,26 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall 0.4.1", ] +[[package]] +name = "librocksdb-sys" +version = "0.16.0+8.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", +] + [[package]] name = "libsodium-sys" version = "0.2.7" @@ -2786,6 +3201,15 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -2804,6 +3228,12 @@ dependencies = [ "keystream", ] +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + [[package]] name = "lock_api" version = "0.4.11" @@ -2857,6 +3287,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "lz4-sys" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "matchit" version = "0.7.3" @@ -2869,7 +3309,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "rayon", ] @@ -2888,6 +3328,38 @@ dependencies = [ "nonempty", ] +[[package]] +name = "metrics" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde3af1a009ed76a778cb84fdef9e7dbbdf5775ae3e4cc1f434a6a307f6f76c5" +dependencies = [ + "ahash 0.8.8", + "metrics-macros", + "portable-atomic", +] + +[[package]] +name = "metrics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +dependencies = [ + "ahash 0.8.8", + "portable-atomic", +] + +[[package]] +name = "metrics-macros" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b4faf00617defe497754acde3024865bc143d44a86799b24e191ecff91354f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.49", +] + [[package]] name = "mime" version = "0.3.17" @@ -2920,6 +3392,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mset" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4d16a3d2b0e89ec6e7d509cf791545fcb48cbc8fc2fb2e96a492defda9140" + [[package]] name = "multimap" version = "0.8.3" @@ -2944,6 +3422,17 @@ dependencies = [ "tempfile", ] +[[package]] +name = "net2" +version = "0.2.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "winapi", +] + [[package]] name = "nix" version = "0.25.1" @@ -2952,7 +3441,7 @@ checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.0", "libc", ] @@ -2962,8 +3451,8 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.2", - "cfg-if", + "bitflags 2.5.0", + "cfg-if 1.0.0", "libc", ] @@ -3107,7 +3596,7 @@ source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0 dependencies = [ "async-trait", "base64 0.21.7", - "cfg-if", + "cfg-if 1.0.0", "dashmap", "dirs 4.0.0", "futures", @@ -3487,9 +3976,9 @@ name = "nym-network-defaults" version = "0.1.0" source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0ccb01340bbe216e3b5e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dotenvy", - "hex-literal", + "hex-literal 0.3.4", "once_cell", "schemars", "serde", @@ -3520,7 +4009,7 @@ source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0 dependencies = [ "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.11", "wasmtimer", ] @@ -3567,7 +4056,7 @@ dependencies = [ "async-trait", "bip39", "bytecodec", - "bytes 1.5.0", + "bytes 1.6.0", "futures", "http", "httpcodec", @@ -3660,14 +4149,14 @@ name = "nym-socks5-proxy-helpers" version = "0.1.0" source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0ccb01340bbe216e3b5e" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "futures", "log", "nym-ordered-buffer", "nym-socks5-requests", "nym-task", "tokio", - "tokio-util", + "tokio-util 0.7.11", ] [[package]] @@ -3804,11 +4293,11 @@ name = "nym-sphinx-framing" version = "0.1.0" source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0ccb01340bbe216e3b5e" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "nym-sphinx-params", "nym-sphinx-types", "thiserror", - "tokio-util", + "tokio-util 0.7.11", ] [[package]] @@ -3950,7 +4439,7 @@ source = "git+https://github.com/nymtech/nym?branch=master#00d47958a7181d0c2ddb0 dependencies = [ "base64 0.21.7", "boringtun", - "bytes 1.5.0", + "bytes 1.6.0", "dashmap", "ip_network", "ip_network_table", @@ -3995,8 +4484,8 @@ version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags 2.4.2", - "cfg-if", + "bitflags 2.5.0", + "cfg-if 1.0.0", "foreign-types", "libc", "once_cell", @@ -4077,6 +4566,36 @@ dependencies = [ "zcash_note_encryption", ] +[[package]] +name = "orchard" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb255c3ffdccd3c84fe9ebed72aef64fdc72e6a3e4180dd411002d47abaad42" +dependencies = [ + "aes 0.8.4", + "bitvec", + "blake2b_simd", + "ff 0.13.0", + "fpe", + "group 0.13.0", + "halo2_gadgets", + "halo2_proofs", + "hex 0.4.3", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.5", + "reddsa", + "serde", + "subtle 2.4.1", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "orchard" version = "0.8.0" @@ -4116,6 +4635,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "ordered-map" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac8f4a4a06c811aa24b151dbb3fe19f687cb52e0d5cca0493671ed88f973970" +dependencies = [ + "quickcheck", + "quickcheck_macros", +] + [[package]] name = "overload" version = "0.1.1" @@ -4149,6 +4678,32 @@ dependencies = [ "group 0.13.0", ] +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "parking_lot" version = "0.11.2" @@ -4176,7 +4731,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "instant", "libc", "redox_syscall 0.2.16", @@ -4190,7 +4745,7 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall 0.4.1", "smallvec", @@ -4345,23 +4900,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", - "indexmap 2.2.3", + "indexmap 2.2.6", ] [[package]] name = "pin-project" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", @@ -4413,6 +4968,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "portpicker" version = "0.1.1" @@ -4454,6 +5015,35 @@ dependencies = [ "syn 2.0.49", ] +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -4495,7 +5085,7 @@ checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.5.0", "lazy_static", "num-traits", "rand 0.8.5", @@ -4513,7 +5103,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "prost-derive", ] @@ -4523,7 +5113,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "heck", "itertools 0.11.0", "log", @@ -4573,6 +5163,29 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" +[[package]] +name = "quickcheck" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44883e74aa97ad63db83c4bf8ca490f02b2fc02f92575e720c8551e843c945f" +dependencies = [ + "env_logger", + "log", + "rand 0.7.3", + "rand_core 0.5.1", +] + +[[package]] +name = "quickcheck_macros" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608c156fd8e97febc07dc9c2e2c80bf74cfc6ef26893eae3daf8bc2bc94a4b7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.35" @@ -4708,9 +5321,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4797,9 +5410,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -4840,7 +5453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", - "bytes 1.5.0", + "bytes 1.6.0", "encoding_rs", "futures-core", "futures-util", @@ -4936,6 +5549,25 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "rlimit" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3560f70f30a0f16d11d01ed078a07740fe6b489667abc7c7b029155d9f21c3d8" +dependencies = [ + "libc", +] + +[[package]] +name = "rocksdb" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" +dependencies = [ + "libc", + "librocksdb-sys", +] + [[package]] name = "rust-embed" version = "6.8.1" @@ -4976,13 +5608,25 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.21", + "semver 1.0.23", ] [[package]] @@ -4991,7 +5635,7 @@ version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -5100,25 +5744,57 @@ dependencies = [ ] [[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "schemars" -version = "0.8.16" +name = "sapling-crypto" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "02f4270033afcb0c74c5c7d59c73cfd1040367f67f224fe7ed9a919ae618f1b7" dependencies = [ - "dyn-clone", - "indexmap 1.9.3", - "schemars_derive", - "serde", - "serde_json", + "aes 0.8.4", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381 0.8.0", + "byteorder", + "document-features", + "ff 0.13.0", + "fpe", + "group 0.13.0", + "hex 0.4.3", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "rand 0.8.5", + "rand_core 0.6.4", + "redjubjub", + "subtle 2.4.1", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", ] [[package]] @@ -5180,6 +5856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4124a35fe33ae14259c490fd70fa199a32b9ce9502f2ee6bc4f81ec06fa65894" dependencies = [ "secp256k1-sys", + "serde", ] [[package]] @@ -5234,9 +5911,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "semver-parser" @@ -5249,13 +5926,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.196" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.5.0" @@ -5286,9 +5972,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.196" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -5308,10 +5994,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.113" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -5349,13 +6036,43 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex 0.4.3", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.49", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -5368,7 +6085,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -5380,7 +6097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", @@ -5392,7 +6109,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest 0.10.7", ] @@ -5412,7 +6129,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19f96dde3a8693874f7e7c53d95616569b4009379a903789efbd448f4ea9cc7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "either", "incrementalmerkletree", "tracing", @@ -5424,12 +6141,18 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cdd24424ce0b381646737fedddc33c4dcf7dcd2d545056b53f7982097bef5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "either", "incrementalmerkletree", "tracing", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -5593,7 +6316,7 @@ dependencies = [ "atoi 0.4.0", "bitflags 1.3.2", "byteorder", - "bytes 1.5.0", + "bytes 1.6.0", "crc 2.1.0", "crossbeam-queue", "either", @@ -5638,7 +6361,7 @@ dependencies = [ "atoi 1.0.0", "bitflags 1.3.2", "byteorder", - "bytes 1.5.0", + "bytes 1.6.0", "crc 3.0.1", "crossbeam-queue", "dotenvy", @@ -5753,9 +6476,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -5851,11 +6574,11 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "fastrand", "rustix", "windows-sys 0.52.0", @@ -5867,7 +6590,7 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "digest 0.10.7", "ed25519 2.2.3", "ed25519-consensus", @@ -5912,7 +6635,7 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "flex-error", "num-derive", "num-traits", @@ -5931,14 +6654,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" dependencies = [ "async-trait", - "bytes 1.5.0", + "bytes 1.6.0", "flex-error", "futures", "getrandom 0.2.12", "peg", "pin-project", "reqwest", - "semver 1.0.21", + "semver 1.0.23", "serde", "serde_bytes", "serde_json", @@ -5967,18 +6690,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", @@ -6001,7 +6724,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -6054,12 +6777,12 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", - "bytes 1.5.0", + "bytes 1.6.0", "libc", "mio", "num_cpus", @@ -6068,6 +6791,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", + "tracing", "windows-sys 0.48.0", ] @@ -6148,14 +6872,14 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util", + "tokio-util 0.7.11", ] [[package]] @@ -6174,17 +6898,30 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes 1.6.0", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "futures-core", "futures-sink", "pin-project-lite", "slab", "tokio", - "tracing", ] [[package]] @@ -6205,7 +6942,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -6223,13 +6960,24 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.10.2" @@ -6240,7 +6988,7 @@ dependencies = [ "async-trait", "axum", "base64 0.21.7", - "bytes 1.5.0", + "bytes 1.6.0", "h2", "http", "http-body", @@ -6289,12 +7037,39 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", ] +[[package]] +name = "tower-batch-control" +version = "0.2.41-beta.13" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "futures", + "futures-core", + "pin-project", + "rayon", + "tokio", + "tokio-util 0.7.11", + "tower", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tower-fallback" +version = "0.2.41-beta.13" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "futures-core", + "pin-project", + "tower", + "tracing", +] + [[package]] name = "tower-http" version = "0.2.5" @@ -6302,7 +7077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aba3f3efabf7fb41fae8534fc20a817013dd1c12cb45441efb6c82e6556b4cd8" dependencies = [ "bitflags 1.3.2", - "bytes 1.5.0", + "bytes 1.6.0", "futures-core", "futures-util", "http", @@ -6358,6 +7133,26 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.2.0" @@ -6425,7 +7220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ "byteorder", - "bytes 1.5.0", + "bytes 1.6.0", "data-encoding", "http", "httparse", @@ -6477,6 +7272,15 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.15" @@ -6504,6 +7308,12 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-width" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" + [[package]] name = "unicode_categories" version = "0.1.1" @@ -6596,7 +7406,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" dependencies = [ "anyhow", - "cfg-if", + "cfg-if 1.0.0", "enum-iterator", "getset", "git2", @@ -6612,6 +7422,56 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -6658,7 +7518,7 @@ version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] @@ -6683,7 +7543,7 @@ version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -7001,7 +7861,7 @@ version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-sys 0.48.0", ] @@ -7049,9 +7909,22 @@ version = "0.3.0" source = "git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration#1bd3be4a113760f5dd355ad7074de649d141e313" dependencies = [ "bech32", - "bs58 0.5.0", - "f4jumble", - "zcash_encoding", + "bs58 0.5.1", + "f4jumble 0.1.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", +] + +[[package]] +name = "zcash_address" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "827c17a1f7e3a69f0d44e991ff610c7a842228afdc9dc2325ffdd1a67fee01e9" +dependencies = [ + "bech32", + "bs58 0.5.1", + "f4jumble 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_protocol", ] [[package]] @@ -7062,7 +7935,7 @@ dependencies = [ "base64 0.21.7", "bech32", "bls12_381 0.8.0", - "bs58 0.5.0", + "bs58 0.5.1", "byteorder", "crossbeam-channel", "group 0.13.0", @@ -7083,10 +7956,20 @@ dependencies = [ "tonic-build", "tracing", "which", - "zcash_address", - "zcash_encoding", + "zcash_address 0.3.0", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zcash_note_encryption", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", +] + +[[package]] +name = "zcash_encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f03391b81727875efa6ac0661a20883022b6fba92365dc121c48fa9b00c5aac0" +dependencies = [ + "byteorder", + "nonempty", ] [[package]] @@ -7098,6 +7981,17 @@ dependencies = [ "nonempty", ] +[[package]] +name = "zcash_history" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fde17bf53792f9c756b313730da14880257d7661b5bfc69d0571c3a7c11a76d" +dependencies = [ + "blake2b_simd", + "byteorder", + "primitive-types", +] + [[package]] name = "zcash_note_encryption" version = "0.4.0" @@ -7111,6 +8005,42 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "zcash_primitives" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d17e4c94ca8d69d2fcf2be97522da5732a580eb2125cda3b150761952f8df8e6" +dependencies = [ + "aes 0.8.4", + "bip0039", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381 0.8.0", + "byteorder", + "equihash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ff 0.13.0", + "fpe", + "group 0.13.0", + "hdwallet", + "hex 0.4.3", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "nonempty", + "orchard 0.6.0", + "rand 0.8.5", + "rand_core 0.6.4", + "ripemd", + "secp256k1", + "sha2 0.10.8", + "subtle 2.4.1", + "zcash_address 0.3.2", + "zcash_encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", +] + [[package]] name = "zcash_primitives" version = "0.13.0" @@ -7124,7 +8054,7 @@ dependencies = [ "blake2s_simd", "bls12_381 0.8.0", "byteorder", - "equihash", + "equihash 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "ff 0.13.0", "fpe", "group 0.13.0", @@ -7142,9 +8072,68 @@ dependencies = [ "secp256k1", "sha2 0.10.8", "subtle 2.4.1", - "zcash_address", - "zcash_encoding", + "zcash_address 0.3.0", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", + "zcash_note_encryption", +] + +[[package]] +name = "zcash_primitives" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9070e084570bb78aed4f8d71fd6254492e62c87a5d01e084183980e98117092d" +dependencies = [ + "aes 0.8.4", + "bip0039", + "blake2b_simd", + "byteorder", + "document-features", + "equihash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "ff 0.13.0", + "fpe", + "group 0.13.0", + "hdwallet", + "hex 0.4.3", + "incrementalmerkletree", + "jubjub", + "memuse", + "nonempty", + "orchard 0.7.1", + "rand 0.8.5", + "rand_core 0.6.4", + "redjubjub", + "ripemd", + "sapling-crypto", + "secp256k1", + "sha2 0.10.8", + "subtle 2.4.1", + "tracing", + "zcash_address 0.3.2", + "zcash_encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "zcash_proofs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0c99f65a840ff256c106b28d67d702d9759d206112473d4982c92003262406" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381 0.8.0", + "group 0.13.0", + "home", + "jubjub", + "known-folders", + "lazy_static", + "rand_core 0.6.4", + "redjubjub", + "tracing", + "xdg", + "zcash_primitives 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -7164,7 +8153,79 @@ dependencies = [ "redjubjub", "tracing", "xdg", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", +] + +[[package]] +name = "zcash_proofs" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a02eb1f151d9b9a6e16408d2c55ff440bd2fb232b7377277146d0fa2df9bc8" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381 0.8.0", + "document-features", + "group 0.13.0", + "home", + "jubjub", + "known-folders", + "lazy_static", + "rand_core 0.6.4", + "redjubjub", + "sapling-crypto", + "tracing", + "xdg", + "zcash_primitives 0.14.0", +] + +[[package]] +name = "zcash_protocol" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f8189d4a304e8aa3aef3b75e89f3874bb0dc84b1cd623316a84e79e06cddabc" +dependencies = [ + "document-features", + "memuse", +] + +[[package]] +name = "zcash_script" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3febfe5f2abdab3597d17c8f71cc0071902d82f8433aa329abe52461eabaa9c4" +dependencies = [ + "bellman", + "bindgen", + "blake2b_simd", + "blake2s_simd", + "bls12_381 0.8.0", + "bridgetree", + "byteorder", + "cc", + "crossbeam-channel", + "cxx", + "cxx-gen", + "group 0.13.0", + "incrementalmerkletree", + "jubjub", + "libc", + "memuse", + "metrics 0.21.1", + "orchard 0.7.1", + "rand 0.8.5", + "rand_core 0.6.4", + "rayon", + "redjubjub", + "sapling-crypto", + "subtle 2.4.1", + "syn 1.0.109", + "tracing", + "zcash_address 0.3.2", + "zcash_encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", + "zcash_primitives 0.14.0", + "zcash_proofs 0.14.0", ] [[package]] @@ -7176,6 +8237,209 @@ dependencies = [ "blake2b_simd", ] +[[package]] +name = "zebra-chain" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "bitflags 2.5.0", + "bitflags-serde-legacy", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bridgetree", + "bs58 0.5.1", + "byteorder", + "chrono", + "displaydoc", + "ed25519-zebra 4.0.3", + "equihash 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "futures", + "group 0.13.0", + "halo2_proofs", + "hex 0.4.3", + "humantime 2.1.0", + "incrementalmerkletree", + "itertools 0.12.1", + "jubjub", + "lazy_static", + "num-integer", + "orchard 0.6.0", + "primitive-types", + "rand_core 0.6.4", + "rayon", + "reddsa", + "redjubjub", + "ripemd", + "secp256k1", + "serde", + "serde-big-array", + "serde_json", + "serde_with", + "sha2 0.10.8", + "static_assertions", + "thiserror", + "tokio", + "tracing", + "uint", + "x25519-dalek 2.0.1", + "zcash_address 0.3.2", + "zcash_encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_history", + "zcash_note_encryption", + "zcash_primitives 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_protocol", +] + +[[package]] +name = "zebra-consensus" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381 0.8.0", + "chrono", + "displaydoc", + "futures", + "futures-util", + "halo2_proofs", + "jubjub", + "lazy_static", + "metrics 0.22.3", + "once_cell", + "orchard 0.6.0", + "rand 0.8.5", + "rayon", + "serde", + "thiserror", + "tokio", + "tower", + "tower-batch-control", + "tower-fallback", + "tracing", + "tracing-futures", + "wagyu-zcash-parameters", + "zcash_proofs 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zebra-chain", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-network" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "bitflags 2.5.0", + "byteorder", + "bytes 1.6.0", + "chrono", + "dirs 5.0.1", + "futures", + "hex 0.4.3", + "humantime-serde", + "indexmap 2.2.6", + "itertools 0.12.1", + "lazy_static", + "metrics 0.22.3", + "num-integer", + "ordered-map", + "pin-project", + "rand 0.8.5", + "rayon", + "regex", + "serde", + "tempfile", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util 0.7.11", + "tower", + "tracing", + "tracing-error", + "tracing-futures", + "zebra-chain", +] + +[[package]] +name = "zebra-node-services" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "zebra-chain", +] + +[[package]] +name = "zebra-rpc" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "chrono", + "futures", + "hex 0.4.3", + "hyper", + "indexmap 2.2.6", + "jsonrpc-core", + "jsonrpc-derive", + "jsonrpc-http-server", + "serde", + "serde_json", + "tokio", + "tower", + "tracing", + "zebra-chain", + "zebra-consensus", + "zebra-network", + "zebra-node-services", + "zebra-script", + "zebra-state", +] + +[[package]] +name = "zebra-script" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "displaydoc", + "thiserror", + "zcash_script", + "zebra-chain", +] + +[[package]] +name = "zebra-state" +version = "1.0.0-beta.37" +source = "git+https://github.com/ZcashFoundation/zebra.git#0f5450f5650102aea1fff43ec88c065ef1976e79" +dependencies = [ + "bincode", + "chrono", + "dirs 5.0.1", + "futures", + "hex 0.4.3", + "hex-literal 0.4.1", + "human_bytes", + "humantime-serde", + "indexmap 2.2.6", + "itertools 0.12.1", + "lazy_static", + "metrics 0.22.3", + "mset", + "rayon", + "regex", + "rlimit", + "rocksdb", + "semver 1.0.23", + "serde", + "tempfile", + "thiserror", + "tokio", + "tower", + "tracing", + "zebra-chain", +] + [[package]] name = "zerocopy" version = "0.7.32" @@ -7221,11 +8485,11 @@ name = "zingo-memo" version = "0.1.0" source = "git+https://github.com/zingolabs/zingolib.git?branch=nym_integration#f5eeb37c04d7b1b58f8c6189291ce73349cec471" dependencies = [ - "zcash_address", + "zcash_address 0.3.0", "zcash_client_backend", - "zcash_encoding", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zcash_note_encryption", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", ] [[package]] @@ -7250,7 +8514,7 @@ dependencies = [ name = "zingo-proxyd" version = "0.1.0" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", "ctrlc", "http", "http-body", @@ -7278,11 +8542,13 @@ dependencies = [ name = "zingo-rpc" version = "0.1.0" dependencies = [ - "bytes 1.5.0", + "bytes 1.6.0", + "hex 0.4.3", "http", "http-body", "hyper", "hyper-rustls 0.23.2", + "hyper-tls", "nym-bin-common", "nym-sdk", "nym-sphinx-addressing", @@ -7290,12 +8556,17 @@ dependencies = [ "nym-validator-client", "prost", "rustls-pemfile", + "serde", + "serde_json", "tokio", "tokio-rustls 0.23.4", "tonic", "tower", "webpki-roots 0.21.1", "zcash_client_backend", + "zebra-chain", + "zebra-rpc", + "zebra-state", "zingo-netutils", ] @@ -7305,7 +8576,7 @@ version = "0.1.0" source = "git+https://github.com/zingolabs/zingolib.git?branch=nym_integration#f5eeb37c04d7b1b58f8c6189291ce73349cec471" dependencies = [ "serde_json", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", ] [[package]] @@ -7327,9 +8598,9 @@ dependencies = [ "tonic", "tonic-build", "tracing", - "zcash_address", + "zcash_address 0.3.0", "zcash_client_backend", - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zingo-netutils", "zingoconfig", "zingolib", @@ -7340,7 +8611,7 @@ name = "zingo-testvectors" version = "0.1.0" source = "git+https://github.com/zingolabs/zingolib.git?branch=nym_integration#f5eeb37c04d7b1b58f8c6189291ce73349cec471" dependencies = [ - "zcash_primitives", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zingoconfig", ] @@ -7353,8 +8624,8 @@ dependencies = [ "http", "log", "log4rs", - "zcash_address", - "zcash_primitives", + "zcash_address 0.3.0", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", ] [[package]] @@ -7412,12 +8683,12 @@ dependencies = [ "tower-http", "tracing", "tracing-subscriber", - "zcash_address", + "zcash_address 0.3.0", "zcash_client_backend", - "zcash_encoding", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zcash_note_encryption", - "zcash_primitives", - "zcash_proofs", + "zcash_primitives 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", + "zcash_proofs 0.13.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zingo-memo", "zingo-netutils", "zingo-status", diff --git a/Cargo.toml b/Cargo.toml index 1fe02d6..1449c3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,14 @@ nym-bin-common = { git = "https://github.com/nymtech/nym", branch = "master" } nym-sphinx-anonymous-replies = { git = "https://github.com/nymtech/nym", branch = "master" } http = "0.2.4" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1.37.0", features = ["full"] } tonic = "0.10.2" prost = "0.12" bytes = "1.1" http-body = "0.4.4" - - +hyper = { version = "0.14.28", features = ["full"] } +hyper-rustls = { version = "0.23", features = ["http2"] } +tower = { version = "0.4.13" } +tokio-rustls = "0.23" +rustls-pemfile = "1.0.0" diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 9614e39..d8c871c 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -33,5 +33,5 @@ shardtree = "0.3" json = "0.12.4" log = "0.4.17" itertools = "0.10.5" -serde_json = "1.0.107" +serde_json = "1.0.117" hex = "0.4" diff --git a/zingo-proxyd/Cargo.toml b/zingo-proxyd/Cargo.toml index f4fa7c8..2838ec3 100644 --- a/zingo-proxyd/Cargo.toml +++ b/zingo-proxyd/Cargo.toml @@ -36,14 +36,13 @@ tonic = { workspace = true } prost = { workspace = true } bytes = { workspace = true } http-body = { workspace = true } - +tower = { workspace = true } +hyper-rustls = { workspace = true, features = ["http2"] } +hyper = { workspace = true, features = ["full"] } +tokio-rustls = { workspace = true } +rustls-pemfile = { workspace = true } tokio-socks = "0.5" ctrlc = "3.2.1" -tower = { version = "0.4" } -hyper-rustls = { version = "0.23", features = ["http2"] } -tokio-rustls = "0.23" -hyper = { version = "0.14", features = ["full"] } webpki-roots = "0.21.0" -rustls-pemfile = "1.0.0" diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 783a6a1..44dd57f 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -10,6 +10,10 @@ test = [] nym_poc = [] [dependencies] +zebra-rpc = { git = "https://github.com/ZcashFoundation/zebra.git" } +zebra-chain = { git = "https://github.com/ZcashFoundation/zebra.git" } +zebra-state = { git = "https://github.com/ZcashFoundation/zebra.git" } + zcash_client_backend = { workspace = true, features = ["lightwalletd-tonic"] } zingo-netutils = { workspace = true } @@ -25,10 +29,15 @@ tonic = { workspace = true } prost = { workspace = true } bytes = { workspace = true } http-body = { workspace = true } +tower = { workspace = true } +hyper-rustls = { workspace = true, features = ["http2"] } +hyper = { workspace = true, features = ["full"] } +tokio-rustls = { workspace = true } +rustls-pemfile = { workspace = true } -tokio-rustls = "0.23.3" -tower = { version = "0.4" } -hyper-rustls = { version = "0.23", features = ["http2"] } webpki-roots = "0.21.0" -hyper = { version = "0.14", features = ["full"] } -rustls-pemfile = "1.0.0" +serde = { version = "1.0.201", features = ["derive"] } +serde_json = "1.0.117" +hyper-tls = "0.5" +hex = { version = "0.4.3", features = ["serde"] } + diff --git a/zingo-rpc/src/jsonrpc.rs b/zingo-rpc/src/jsonrpc.rs new file mode 100644 index 0000000..3166c11 --- /dev/null +++ b/zingo-rpc/src/jsonrpc.rs @@ -0,0 +1,615 @@ +//! JsonRPC client used to send requests to Zebrad. + +use hex::{FromHex, ToHex}; +use http::Uri; +use hyper::{http, Body, Client, Request}; +use hyper_tls::HttpsConnector; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use zebra_chain::{ + block::{self, Height, SerializedBlock}, + subtree::NoteCommitmentSubtreeIndex, + transaction::{self, SerializedTransaction}, + transparent, +}; +use zebra_rpc::methods::{ + trees::{GetSubtrees, SubtreeRpcData}, + AddressBalance, GetAddressUtxos, GetBlock, GetBlockChainInfo, GetBlockHash, GetBlockTrees, + GetInfo, GetRawTransaction, GetTreestate, SentTransactionHash, +}; + +#[derive(Serialize, Deserialize, Debug)] +struct RpcRequest { + jsonrpc: String, + method: String, + params: T, + id: i32, +} + +#[derive(Serialize, Deserialize, Debug)] +struct RpcResponse { + id: i32, + jsonrpc: String, + result: T, + error: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct RpcError { + code: i32, + message: String, + data: Option, +} + +/// List of transparent address strings. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_address_balance`] and [`JsonRpcConnector::get_address_utxos`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct AddressStringsRequest { + /// A list of transparent address strings. + addresses: Vec, +} + +/// Hex-encoded raw transaction. +/// +/// This is used for the input parameter of [`JsonRpcConnector::send_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct SendTransactionRequest { + /// - Hex-encoded raw transaction bytes. + raw_transaction_hex: String, +} + +/// Block to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_block`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct GetBlockRequest { + /// The hash or height for the block to be returned. + hash_or_height: String, + /// 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. Default=1. + verbosity: Option, +} + +/// Block to be examined. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_treestate`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct GetTreestateRequest { + /// The block hash or height. + hash_or_height: String, +} + +/// Subtrees to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_subtrees_by_index`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct GetSubtreesRequest { + /// The pool from which subtrees should be returned. Either "sapling" or "orchard". + pool: String, + /// The index of the first 2^16-leaf subtree to return. + start_index: u16, + /// The maximum number of subtree values to return. + limit: Option, +} + +/// Transaction to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct GetTransactionRequest { + /// The transaction ID of the transaction to be returned. + txid_hex: String, + /// If 0, return a string of hex-encoded data, otherwise return a JSON object. Default=0. + verbose: Option, +} + +/// List of transparent address strings and range of blocks to fetch Txids from. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_address_tx_ids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +struct TxidsByAddressRequest { + // A list of addresses to get transactions from. + addresses: Vec, + // The height to start looking for transactions. + start: u32, + // The height to end looking for transactions. + end: u32, +} + +/// Vec of transaction ids, as a JSON array. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct TxidsResponse { + /// Vec of txids. + transactions: Vec, +} + +/// The transparent balance of a set of addresses. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_address_balance`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetBalanceResponse { + /// The total transparent balance. + balance: u64, +} + +/// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HexSerializedBlock(SerializedBlock); + +impl Serialize for HexSerializedBlock { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let hex_string = self.as_ref().encode_hex::(); + serializer.serialize_str(&hex_string) + } +} + +impl<'de> Deserialize<'de> for HexSerializedBlock { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct HexVisitor; + + impl<'de> serde::de::Visitor<'de> for HexVisitor { + type Value = HexSerializedBlock; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a hex-encoded string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let bytes = hex::decode(value).map_err(serde::de::Error::custom)?; + Ok(HexSerializedBlock(SerializedBlock::from(bytes))) + } + } + + deserializer.deserialize_str(HexVisitor) + } +} + +impl FromHex for HexSerializedBlock { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + hex::decode(hex) + .map(|bytes| HexSerializedBlock(SerializedBlock::from(bytes))) + .map_err(|e| e.into()) + } +} + +impl AsRef<[u8]> for HexSerializedBlock { + fn as_ref(&self) -> &[u8] { + &self.0.as_ref() + } +} + +/// Contains the hex-encoded hash of the sent transaction. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_block`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +pub enum GetBlockResponse { + /// The request block, hex-encoded. + Raw(#[serde(with = "hex")] HexSerializedBlock), + /// The block object. + Object { + /// The hash of the requested block. + hash: GetBlockHash, + + /// The number of confirmations of this block in the best chain, + /// or -1 if it is not in the best chain. + confirmations: i64, + + /// The height of the requested block. + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + + /// The height of the requested block. + #[serde(skip_serializing_if = "Option::is_none")] + time: Option, + + /// List of transaction IDs in block order, hex-encoded. + tx: Vec, + + /// Information about the note commitment trees. + trees: GetBlockTrees, + }, +} + +/// Zingo-Proxy commitment tree structure replicating functionality in Zebra. +/// +/// A wrapper that contains either an Orchard or Sapling note commitment tree. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ProxyCommitments> { + #[serde(with = "hex")] + #[serde(rename = "finalState")] + final_state: Tree, +} + +impl + FromHex> ProxyCommitments { + /// Creates a new instance of `ProxyCommitments` from a hex string. + pub fn new_from_hex(hex_encoded_data: &str) -> Result { + let tree = Tree::from_hex(hex_encoded_data)?; + Ok(Self { final_state: tree }) + } + + /// Checks if the internal tree is empty. + pub fn is_empty(&self) -> bool { + self.final_state.as_ref().is_empty() + } +} + +/// Zingo-Proxy treestate structure replicating functionality in Zebra. +/// +/// A treestate that is included in the [`z_gettreestate`][1] RPC response. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ProxyTreestate> { + commitments: ProxyCommitments, +} + +impl + FromHex> ProxyTreestate { + /// Creates a new instance of `ProxyTreestate`. + pub fn new(commitments: ProxyCommitments) -> Self { + Self { commitments } + } + + /// Checks if the internal tree is empty. + pub fn is_empty(&self) -> bool { + self.commitments.is_empty() + } +} + +impl<'de, Tree: AsRef<[u8]> + FromHex + Deserialize<'de>> + Deserialize<'de> for ProxyTreestate +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string: String = Deserialize::deserialize(deserializer)?; + let tree = Tree::from_hex(&hex_string).map_err(serde::de::Error::custom)?; + Ok(ProxyTreestate::new(ProxyCommitments { final_state: tree })) + } +} + +/// Wrapper struct for trees that need to be serialized and deserialized from hex. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HexEncodedSerializedTree { + inner: T, +} + +impl HexEncodedSerializedTree +where + T: From> + AsRef<[u8]>, +{ + pub fn new(inner: T) -> Self { + Self { inner } + } +} + +impl Serialize for HexEncodedSerializedTree +where + T: AsRef<[u8]> + Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let hex_string = hex::encode(self.inner.as_ref()); + serializer.serialize_str(&hex_string) + } +} + +impl<'de, T> Deserialize<'de> for HexEncodedSerializedTree +where + T: AsRef<[u8]> + From> + Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string = String::deserialize(deserializer)?; + let bytes = hex::decode(&hex_string).map_err(serde::de::Error::custom)?; + Ok(HexEncodedSerializedTree { + inner: T::from(bytes), + }) + } +} + +impl FromHex for HexEncodedSerializedTree +where + T: From>, +{ + type Error = hex::FromHexError; + + fn from_hex>(hex: S) -> Result { + let bytes = hex::decode(hex).map_err(serde::de::Error::custom)?; + Ok(HexEncodedSerializedTree { + inner: T::from(bytes), + }) + } +} + +impl AsRef<[u8]> for HexEncodedSerializedTree +where + T: AsRef<[u8]>, +{ + fn as_ref(&self) -> &[u8] { + self.inner.as_ref() + } +} + +/// Contains the hex-encoded Sapling & Orchard note commitment trees, and their +/// corresponding [`block::Hash`], [`Height`], and block time. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_treestate`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetTreestateResponse { + /// The block hash corresponding to the treestate, hex-encoded. + #[serde(with = "hex")] + hash: block::Hash, + + /// The block height corresponding to the treestate, numeric. + height: Height, + + /// Unix time when the block corresponding to the treestate was mined, + /// numeric. + /// + /// UTC seconds since the Unix 1970-01-01 epoch. + time: u32, + + /// A treestate containing a Sapling note commitment tree, hex-encoded. + #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] + sapling: ProxyTreestate>, + + /// A treestate containing an Orchard note commitment tree, hex-encoded. + #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] + orchard: ProxyTreestate>, +} + +/// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, +/// and a list of subtree roots and end heights. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_subtrees_by_index`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetSubtreesResponse { + /// The shielded pool to which the subtrees belong. + pub pool: String, + + /// The index of the first subtree. + pub start_index: NoteCommitmentSubtreeIndex, + + /// A sequential list of complete subtrees, in `index` order. + /// + /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. + // #[serde(skip_serializing_if = "Vec::is_empty")] + pub subtrees: Vec, +} + +/// Contains raw transaction, encoded as hex bytes. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub enum GetTransactionResponse { + /// The raw transaction, encoded as hex bytes. + Raw(#[serde(with = "hex")] SerializedTransaction), + /// The transaction object. + Object { + /// The raw transaction, encoded as hex bytes. + #[serde(with = "hex")] + hex: SerializedTransaction, + /// The height of the block in the best chain that contains the transaction, or -1 if + /// the transaction is in the mempool. + height: i32, + /// The confirmations of the block in the best chain that contains the transaction, + /// or 0 if the transaction is in the mempool. + confirmations: u32, + }, +} + +/// . +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_address_utxos`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetUtxosResponse { + /// The transparent address, base58check encoded + address: transparent::Address, + + /// The output txid, in big-endian order, hex-encoded + #[serde(with = "hex")] + txid: transaction::Hash, + + /// The transparent output index, numeric + #[serde(rename = "outputIndex")] + output_index: zebra_state::OutputIndex, + + /// The transparent output script, hex encoded + #[serde(with = "hex")] + script: transparent::Script, + + /// The amount of zatoshis in the transparent output + satoshis: u64, + + /// The block height, numeric. + height: Height, +} + +/// JsonRPC Client config data. +pub struct JsonRpcConnector { + uri: http::Uri, +} + +impl JsonRpcConnector { + pub fn new(uri: http::Uri) -> Self { + Self { uri } + } + + pub fn uri(&self) -> &Uri { + &self.uri + } + + pub async fn send_request Deserialize<'de>>( + &self, + method: &str, + params: T, + id: i32, + ) -> Result> { + let client = Client::builder().build(HttpsConnector::new()); + let req = RpcRequest { + jsonrpc: "2.0".to_string(), + method: method.to_string(), + params, + id, + }; + let request = Request::builder() + .method("POST") + .uri(self.uri.clone()) + .header("Content-Type", "application/json") + .body(Body::from(serde_json::to_string(&req)?))?; + + let response = client.request(request).await?; + let body_bytes = hyper::body::to_bytes(response.into_body()).await?; + let response: RpcResponse = serde_json::from_slice(&body_bytes)?; + + match response.error { + Some(error) => Err(format!("RPC Error {}: {}", error.code, error.message).into()), + None => Ok(response.result), + } + } +} + +impl JsonRpcConnector { + /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. + /// + /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) + /// method: post + /// tags: control + pub async fn get_info(&self, id: i32) -> Result> { + self.send_request::<(), GetInfo>("getinfo", (), id).await + } + + pub async fn get_blockchain_info( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), GetBlockChainInfo>("getblockchaininfo", (), id) + .await + } + + pub async fn get_address_balance( + &self, + addresses: Vec, + id: i32, + ) -> Result> { + let params = AddressStringsRequest { addresses }; + self.send_request("getaddressbalance", params, id).await + } + + pub async fn send_raw_transaction( + &self, + raw_transaction_hex: String, + id: i32, + ) -> Result> { + let params = SendTransactionRequest { + raw_transaction_hex, + }; + self.send_request("sendrawtransaction", params, id).await + } + + pub async fn get_block( + &self, + hash_or_height: String, + verbosity: Option, + id: i32, + ) -> Result> { + let params = GetBlockRequest { + hash_or_height, + verbosity, + }; + self.send_request("getblock", params, id).await + } + + pub async fn get_best_block_hash( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), GetBlockHash>("getbestblockhash", (), id) + .await + } + + pub async fn get_raw_mempool( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), TxidsResponse>("getrawmempool", (), id) + .await + } + + pub async fn get_treestate( + &self, + hash_or_height: String, + id: i32, + ) -> Result> { + let params = GetTreestateRequest { hash_or_height }; + self.send_request("z_gettreestate", params, id).await + } + + pub async fn get_subtrees_by_index( + &self, + pool: String, + start_index: u16, + limit: Option, + id: i32, + ) -> Result> { + let params = GetSubtreesRequest { + pool, + start_index, + limit, + }; + self.send_request("z_getsubtreesbyindex", params, id).await + } + + pub async fn get_raw_transaction( + &self, + txid_hex: String, + verbose: Option, + id: i32, + ) -> Result> { + let params = GetTransactionRequest { txid_hex, verbose }; + self.send_request("getrawtransaction", params, id).await + } + + pub async fn get_address_tx_ids( + &self, + addresses: Vec, + start: u32, + end: u32, + id: i32, + ) -> Result> { + let params = TxidsByAddressRequest { + addresses, + start, + end, + }; + + self.send_request("getaddresstxids", params, id).await + } + + pub async fn get_address_utxos( + &self, + addresses: Vec, + id: i32, + ) -> Result, Box> { + let params = AddressStringsRequest { addresses }; + self.send_request("getaddressutxos", params, id).await + } +} diff --git a/zingo-rpc/src/lib.rs b/zingo-rpc/src/lib.rs index 41bd913..30f6833 100644 --- a/zingo-rpc/src/lib.rs +++ b/zingo-rpc/src/lib.rs @@ -3,6 +3,7 @@ #![warn(missing_docs)] #![forbid(unsafe_code)] +pub mod jsonrpc; pub mod nym; pub mod primitives; pub mod rpc; From 0514f545a5158690c7061b5b492b8c78592b8a65 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 17 May 2024 03:23:05 +0100 Subject: [PATCH 02/40] added doc comments --- zingo-rpc/src/jsonrpc.rs | 103 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 2 deletions(-) diff --git a/zingo-rpc/src/jsonrpc.rs b/zingo-rpc/src/jsonrpc.rs index 3166c11..e848093 100644 --- a/zingo-rpc/src/jsonrpc.rs +++ b/zingo-rpc/src/jsonrpc.rs @@ -449,14 +449,17 @@ pub struct JsonRpcConnector { } impl JsonRpcConnector { + /// Returns a new JsonRpcConnector instance. pub fn new(uri: http::Uri) -> Self { Self { uri } } + /// Returns the uri the JsonRpcConnector is configured to send requests to. pub fn uri(&self) -> &Uri { &self.uri } + /// Sends a jsonRPC request and returns the response.`` pub async fn send_request Deserialize<'de>>( &self, method: &str, @@ -485,9 +488,7 @@ impl JsonRpcConnector { None => Ok(response.result), } } -} -impl JsonRpcConnector { /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. /// /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) @@ -497,6 +498,11 @@ impl JsonRpcConnector { self.send_request::<(), GetInfo>("getinfo", (), id).await } + /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct. + /// + /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html) + /// method: post + /// tags: blockchain pub async fn get_blockchain_info( &self, id: i32, @@ -505,6 +511,16 @@ impl JsonRpcConnector { .await } + /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance. + /// + /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry + /// - `addresses`: (array of strings) A list of base-58 encoded addresses. pub async fn get_address_balance( &self, addresses: Vec, @@ -514,6 +530,16 @@ impl JsonRpcConnector { self.send_request("getaddressbalance", params, id).await } + /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid. + /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. + /// + /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html) + /// method: post + /// tags: transaction + /// + /// # Parameters + /// + /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes. pub async fn send_raw_transaction( &self, raw_transaction_hex: String, @@ -525,6 +551,18 @@ impl JsonRpcConnector { self.send_request("sendrawtransaction", params, id).await } + /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. + /// If the block is not in Zebra's state, returns + /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) + /// + /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. + /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. pub async fn get_block( &self, hash_or_height: String, @@ -538,6 +576,11 @@ impl JsonRpcConnector { self.send_request("getblock", params, id).await } + /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. + /// + /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html) + /// method: post + /// tags: blockchain pub async fn get_best_block_hash( &self, id: i32, @@ -546,6 +589,11 @@ impl JsonRpcConnector { .await } + /// Returns all transaction ids in the memory pool, as a JSON array. + /// + /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) + /// method: post + /// tags: blockchain pub async fn get_raw_mempool( &self, id: i32, @@ -554,6 +602,15 @@ impl JsonRpcConnector { .await } + /// Returns information about the given block's Sapling & Orchard tree state. + /// + /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height. pub async fn get_treestate( &self, hash_or_height: String, @@ -563,6 +620,17 @@ impl JsonRpcConnector { self.send_request("z_gettreestate", params, id).await } + /// Returns information about a range of Sapling or Orchard subtrees. + /// + /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard". + /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return. + /// - `limit`: (number, optional) The maximum number of subtree values to return. pub async fn get_subtrees_by_index( &self, pool: String, @@ -578,6 +646,16 @@ impl JsonRpcConnector { self.send_request("z_getsubtreesbyindex", params, id).await } + /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure. + /// + /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html) + /// method: post + /// tags: transaction + /// + /// # Parameters + /// + /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned. + /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object. pub async fn get_raw_transaction( &self, txid_hex: String, @@ -588,6 +666,18 @@ impl JsonRpcConnector { self.send_request("getrawtransaction", params, id).await } + /// Returns the transaction ids made by the provided transparent addresses. + /// + /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields: + /// - `addresses`: (json array of string, required) The addresses to get transactions from. + /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). + /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). pub async fn get_address_tx_ids( &self, addresses: Vec, @@ -604,6 +694,15 @@ impl JsonRpcConnector { self.send_request("getaddresstxids", params, id).await } + /// Returns all unspent outputs for a list of addresses. + /// + /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from. pub async fn get_address_utxos( &self, addresses: Vec, From 46051274f73a7630bdd4a7efebd1d3624270f717 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 17 May 2024 17:57:39 +0100 Subject: [PATCH 03/40] jsonrpc builds --- zingo-rpc/src/jsonrpc.rs | 226 ++++++++++++++++++++++++++++----------- 1 file changed, 165 insertions(+), 61 deletions(-) diff --git a/zingo-rpc/src/jsonrpc.rs b/zingo-rpc/src/jsonrpc.rs index e848093..ad52942 100644 --- a/zingo-rpc/src/jsonrpc.rs +++ b/zingo-rpc/src/jsonrpc.rs @@ -7,6 +7,8 @@ use hyper_tls::HttpsConnector; use serde::{Deserialize, Serialize}; use serde_json::Value; +use serde::ser::SerializeStruct; + use zebra_chain::{ block::{self, Height, SerializedBlock}, subtree::NoteCommitmentSubtreeIndex, @@ -281,70 +283,24 @@ impl<'de, Tree: AsRef<[u8]> + FromHex + Deserialize<' } } -/// Wrapper struct for trees that need to be serialized and deserialized from hex. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct HexEncodedSerializedTree { - inner: T, -} - -impl HexEncodedSerializedTree -where - T: From> + AsRef<[u8]>, -{ - pub fn new(inner: T) -> Self { - Self { inner } - } -} - -impl Serialize for HexEncodedSerializedTree -where - T: AsRef<[u8]> + Serialize, -{ - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let hex_string = hex::encode(self.inner.as_ref()); - serializer.serialize_str(&hex_string) - } -} - -impl<'de, T> Deserialize<'de> for HexEncodedSerializedTree -where - T: AsRef<[u8]> + From> + Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let hex_string = String::deserialize(deserializer)?; - let bytes = hex::decode(&hex_string).map_err(serde::de::Error::custom)?; - Ok(HexEncodedSerializedTree { - inner: T::from(bytes), - }) - } -} +/// A serialized Sapling note commitment tree +/// +/// Replicates functionality used in Zebra. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxySerializedTree(Vec); -impl FromHex for HexEncodedSerializedTree -where - T: From>, -{ +impl FromHex for ProxySerializedTree { type Error = hex::FromHexError; - fn from_hex>(hex: S) -> Result { - let bytes = hex::decode(hex).map_err(serde::de::Error::custom)?; - Ok(HexEncodedSerializedTree { - inner: T::from(bytes), - }) + fn from_hex>(hex: T) -> Result { + let bytes = hex::decode(hex)?; + Ok(ProxySerializedTree(bytes)) } } -impl AsRef<[u8]> for HexEncodedSerializedTree -where - T: AsRef<[u8]>, -{ +impl AsRef<[u8]> for ProxySerializedTree { fn as_ref(&self) -> &[u8] { - self.inner.as_ref() + &self.0 } } @@ -369,11 +325,84 @@ pub struct GetTreestateResponse { /// A treestate containing a Sapling note commitment tree, hex-encoded. #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - sapling: ProxyTreestate>, + sapling: ProxyTreestate, /// A treestate containing an Orchard note commitment tree, hex-encoded. #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - orchard: ProxyTreestate>, + orchard: ProxyTreestate, + // NOTE: CREATE PoxySerializedTree TO SIMPLIFY CODING>> +} + +/// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProxySubtreeRpcData { + /// Merkle root of the 2^16-leaf subtree. + pub root: String, + /// Height of the block containing the note that completed this subtree. + pub height: Height, +} + +impl ProxySubtreeRpcData { + /// Returns new instance of ProxySubtreeRpcData + pub fn new(root: String, height: Height) -> Self { + Self { root, height } + } +} + +impl serde::Serialize for ProxySubtreeRpcData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ProxySubtreeRpcData", 2)?; + state.serialize_field("root", &self.root)?; + state.serialize_field("height", &self.height)?; + state.end() + } +} + +impl<'de> serde::Deserialize<'de> for ProxySubtreeRpcData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Inner { + root: String, + height: Height, + } + + let inner = Inner::deserialize(deserializer)?; + Ok(ProxySubtreeRpcData { + root: inner.root, + height: inner.height, + }) + } +} + +impl FromHex for ProxySubtreeRpcData { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let hex_str = std::str::from_utf8(hex.as_ref()) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + if hex_str.len() < 8 { + return Err(hex::FromHexError::OddLength); + } + + let root_end_index = hex_str.len() - 8; + let (root_hex, height_hex) = hex_str.split_at(root_end_index); + + let root = root_hex.to_string(); + let height = u32::from_str_radix(height_hex, 16) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + Ok(ProxySubtreeRpcData { + root, + height: Height(height), + }) + } } /// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, @@ -392,7 +421,7 @@ pub struct GetSubtreesResponse { /// /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. // #[serde(skip_serializing_if = "Vec::is_empty")] - pub subtrees: Vec, + pub subtrees: Vec, } /// Contains raw transaction, encoded as hex bytes. @@ -416,6 +445,81 @@ pub enum GetTransactionResponse { }, } +/// Zingo-Proxy encoding of a Bitcoin script. +#[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxyScript { + /// # Correctness + /// + /// Consensus-critical serialization uses [`ZcashSerialize`]. + /// [`serde`]-based hex serialization must only be used for RPCs and testing. + #[serde(with = "hex")] + script: Vec, +} + +impl ProxyScript { + /// Create a new Bitcoin script from its raw bytes. + /// The raw bytes must not contain the length prefix. + pub fn new(raw_bytes: &[u8]) -> Self { + Self { + script: raw_bytes.to_vec(), + } + } + + /// Return the raw bytes of the script without the length prefix. + /// + /// # Correctness + /// + /// These raw bytes do not have a length prefix. + /// The Zcash serialization format requires a length prefix; use `zcash_serialize` + /// and `zcash_deserialize` to create byte data with a length prefix. + pub fn as_raw_bytes(&self) -> &[u8] { + &self.script + } +} + +impl core::fmt::Display for ProxyScript { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str(&self.encode_hex::()) + } +} + +impl core::fmt::Debug for ProxyScript { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_tuple("Script") + .field(&hex::encode(&self.script)) + .finish() + } +} + +impl ToHex for &ProxyScript { + fn encode_hex>(&self) -> T { + self.as_raw_bytes().encode_hex() + } + + fn encode_hex_upper>(&self) -> T { + self.as_raw_bytes().encode_hex_upper() + } +} + +impl ToHex for ProxyScript { + fn encode_hex>(&self) -> T { + (&self).encode_hex() + } + + fn encode_hex_upper>(&self) -> T { + (&self).encode_hex_upper() + } +} + +impl FromHex for ProxyScript { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let bytes = Vec::from_hex(hex)?; + Ok(Self { script: bytes }) + } +} + /// . /// /// This is used for the output parameter of [`JsonRpcConnector::get_address_utxos`]. @@ -434,7 +538,7 @@ pub struct GetUtxosResponse { /// The transparent output script, hex encoded #[serde(with = "hex")] - script: transparent::Script, + script: ProxyScript, /// The amount of zatoshis in the transparent output satoshis: u64, From 35bd4c71016e578d5f8ed195d56fba8c2b9ed024 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 17 May 2024 18:22:22 +0100 Subject: [PATCH 04/40] move jsonrpc into modules --- zingo-proxyd/src/bin/zingoproxyd.rs | 1 + zingo-rpc/src/jsonrpc.rs | 818 +--------------------------- zingo-rpc/src/jsonrpc/connector.rs | 309 +++++++++++ zingo-rpc/src/jsonrpc/primitives.rs | 517 ++++++++++++++++++ 4 files changed, 829 insertions(+), 816 deletions(-) create mode 100644 zingo-rpc/src/jsonrpc/connector.rs create mode 100644 zingo-rpc/src/jsonrpc/primitives.rs diff --git a/zingo-proxyd/src/bin/zingoproxyd.rs b/zingo-proxyd/src/bin/zingoproxyd.rs index 8d2ca91..63d8b2f 100644 --- a/zingo-proxyd/src/bin/zingoproxyd.rs +++ b/zingo-proxyd/src/bin/zingoproxyd.rs @@ -25,6 +25,7 @@ async fn main() { nym_bin_common::logging::setup_logging(); + #[allow(unused_mut)] let mut proxy_port: u16 = 8080; #[cfg(feature = "nym_poc")] { diff --git a/zingo-rpc/src/jsonrpc.rs b/zingo-rpc/src/jsonrpc.rs index ad52942..8758253 100644 --- a/zingo-rpc/src/jsonrpc.rs +++ b/zingo-rpc/src/jsonrpc.rs @@ -1,818 +1,4 @@ //! JsonRPC client used to send requests to Zebrad. -use hex::{FromHex, ToHex}; -use http::Uri; -use hyper::{http, Body, Client, Request}; -use hyper_tls::HttpsConnector; -use serde::{Deserialize, Serialize}; -use serde_json::Value; - -use serde::ser::SerializeStruct; - -use zebra_chain::{ - block::{self, Height, SerializedBlock}, - subtree::NoteCommitmentSubtreeIndex, - transaction::{self, SerializedTransaction}, - transparent, -}; -use zebra_rpc::methods::{ - trees::{GetSubtrees, SubtreeRpcData}, - AddressBalance, GetAddressUtxos, GetBlock, GetBlockChainInfo, GetBlockHash, GetBlockTrees, - GetInfo, GetRawTransaction, GetTreestate, SentTransactionHash, -}; - -#[derive(Serialize, Deserialize, Debug)] -struct RpcRequest { - jsonrpc: String, - method: String, - params: T, - id: i32, -} - -#[derive(Serialize, Deserialize, Debug)] -struct RpcResponse { - id: i32, - jsonrpc: String, - result: T, - error: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -struct RpcError { - code: i32, - message: String, - data: Option, -} - -/// List of transparent address strings. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_address_balance`] and [`JsonRpcConnector::get_address_utxos`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct AddressStringsRequest { - /// A list of transparent address strings. - addresses: Vec, -} - -/// Hex-encoded raw transaction. -/// -/// This is used for the input parameter of [`JsonRpcConnector::send_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct SendTransactionRequest { - /// - Hex-encoded raw transaction bytes. - raw_transaction_hex: String, -} - -/// Block to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_block`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct GetBlockRequest { - /// The hash or height for the block to be returned. - hash_or_height: String, - /// 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. Default=1. - verbosity: Option, -} - -/// Block to be examined. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_treestate`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct GetTreestateRequest { - /// The block hash or height. - hash_or_height: String, -} - -/// Subtrees to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_subtrees_by_index`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct GetSubtreesRequest { - /// The pool from which subtrees should be returned. Either "sapling" or "orchard". - pool: String, - /// The index of the first 2^16-leaf subtree to return. - start_index: u16, - /// The maximum number of subtree values to return. - limit: Option, -} - -/// Transaction to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct GetTransactionRequest { - /// The transaction ID of the transaction to be returned. - txid_hex: String, - /// If 0, return a string of hex-encoded data, otherwise return a JSON object. Default=0. - verbose: Option, -} - -/// List of transparent address strings and range of blocks to fetch Txids from. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_address_tx_ids`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -struct TxidsByAddressRequest { - // A list of addresses to get transactions from. - addresses: Vec, - // The height to start looking for transactions. - start: u32, - // The height to end looking for transactions. - end: u32, -} - -/// Vec of transaction ids, as a JSON array. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct TxidsResponse { - /// Vec of txids. - transactions: Vec, -} - -/// The transparent balance of a set of addresses. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_address_balance`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetBalanceResponse { - /// The total transparent balance. - balance: u64, -} - -/// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct HexSerializedBlock(SerializedBlock); - -impl Serialize for HexSerializedBlock { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let hex_string = self.as_ref().encode_hex::(); - serializer.serialize_str(&hex_string) - } -} - -impl<'de> Deserialize<'de> for HexSerializedBlock { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct HexVisitor; - - impl<'de> serde::de::Visitor<'de> for HexVisitor { - type Value = HexSerializedBlock; - - fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { - formatter.write_str("a hex-encoded string") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - let bytes = hex::decode(value).map_err(serde::de::Error::custom)?; - Ok(HexSerializedBlock(SerializedBlock::from(bytes))) - } - } - - deserializer.deserialize_str(HexVisitor) - } -} - -impl FromHex for HexSerializedBlock { - type Error = hex::FromHexError; - - fn from_hex>(hex: T) -> Result { - hex::decode(hex) - .map(|bytes| HexSerializedBlock(SerializedBlock::from(bytes))) - .map_err(|e| e.into()) - } -} - -impl AsRef<[u8]> for HexSerializedBlock { - fn as_ref(&self) -> &[u8] { - &self.0.as_ref() - } -} - -/// Contains the hex-encoded hash of the sent transaction. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_block`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -#[serde(untagged)] -pub enum GetBlockResponse { - /// The request block, hex-encoded. - Raw(#[serde(with = "hex")] HexSerializedBlock), - /// The block object. - Object { - /// The hash of the requested block. - hash: GetBlockHash, - - /// The number of confirmations of this block in the best chain, - /// or -1 if it is not in the best chain. - confirmations: i64, - - /// The height of the requested block. - #[serde(skip_serializing_if = "Option::is_none")] - height: Option, - - /// The height of the requested block. - #[serde(skip_serializing_if = "Option::is_none")] - time: Option, - - /// List of transaction IDs in block order, hex-encoded. - tx: Vec, - - /// Information about the note commitment trees. - trees: GetBlockTrees, - }, -} - -/// Zingo-Proxy commitment tree structure replicating functionality in Zebra. -/// -/// A wrapper that contains either an Orchard or Sapling note commitment tree. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ProxyCommitments> { - #[serde(with = "hex")] - #[serde(rename = "finalState")] - final_state: Tree, -} - -impl + FromHex> ProxyCommitments { - /// Creates a new instance of `ProxyCommitments` from a hex string. - pub fn new_from_hex(hex_encoded_data: &str) -> Result { - let tree = Tree::from_hex(hex_encoded_data)?; - Ok(Self { final_state: tree }) - } - - /// Checks if the internal tree is empty. - pub fn is_empty(&self) -> bool { - self.final_state.as_ref().is_empty() - } -} - -/// Zingo-Proxy treestate structure replicating functionality in Zebra. -/// -/// A treestate that is included in the [`z_gettreestate`][1] RPC response. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ProxyTreestate> { - commitments: ProxyCommitments, -} - -impl + FromHex> ProxyTreestate { - /// Creates a new instance of `ProxyTreestate`. - pub fn new(commitments: ProxyCommitments) -> Self { - Self { commitments } - } - - /// Checks if the internal tree is empty. - pub fn is_empty(&self) -> bool { - self.commitments.is_empty() - } -} - -impl<'de, Tree: AsRef<[u8]> + FromHex + Deserialize<'de>> - Deserialize<'de> for ProxyTreestate -{ - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let hex_string: String = Deserialize::deserialize(deserializer)?; - let tree = Tree::from_hex(&hex_string).map_err(serde::de::Error::custom)?; - Ok(ProxyTreestate::new(ProxyCommitments { final_state: tree })) - } -} - -/// A serialized Sapling note commitment tree -/// -/// Replicates functionality used in Zebra. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct ProxySerializedTree(Vec); - -impl FromHex for ProxySerializedTree { - type Error = hex::FromHexError; - - fn from_hex>(hex: T) -> Result { - let bytes = hex::decode(hex)?; - Ok(ProxySerializedTree(bytes)) - } -} - -impl AsRef<[u8]> for ProxySerializedTree { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// Contains the hex-encoded Sapling & Orchard note commitment trees, and their -/// corresponding [`block::Hash`], [`Height`], and block time. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_treestate`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetTreestateResponse { - /// The block hash corresponding to the treestate, hex-encoded. - #[serde(with = "hex")] - hash: block::Hash, - - /// The block height corresponding to the treestate, numeric. - height: Height, - - /// Unix time when the block corresponding to the treestate was mined, - /// numeric. - /// - /// UTC seconds since the Unix 1970-01-01 epoch. - time: u32, - - /// A treestate containing a Sapling note commitment tree, hex-encoded. - #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - sapling: ProxyTreestate, - - /// A treestate containing an Orchard note commitment tree, hex-encoded. - #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - orchard: ProxyTreestate, - // NOTE: CREATE PoxySerializedTree TO SIMPLIFY CODING>> -} - -/// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProxySubtreeRpcData { - /// Merkle root of the 2^16-leaf subtree. - pub root: String, - /// Height of the block containing the note that completed this subtree. - pub height: Height, -} - -impl ProxySubtreeRpcData { - /// Returns new instance of ProxySubtreeRpcData - pub fn new(root: String, height: Height) -> Self { - Self { root, height } - } -} - -impl serde::Serialize for ProxySubtreeRpcData { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("ProxySubtreeRpcData", 2)?; - state.serialize_field("root", &self.root)?; - state.serialize_field("height", &self.height)?; - state.end() - } -} - -impl<'de> serde::Deserialize<'de> for ProxySubtreeRpcData { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct Inner { - root: String, - height: Height, - } - - let inner = Inner::deserialize(deserializer)?; - Ok(ProxySubtreeRpcData { - root: inner.root, - height: inner.height, - }) - } -} - -impl FromHex for ProxySubtreeRpcData { - type Error = hex::FromHexError; - - fn from_hex>(hex: T) -> Result { - let hex_str = std::str::from_utf8(hex.as_ref()) - .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; - - if hex_str.len() < 8 { - return Err(hex::FromHexError::OddLength); - } - - let root_end_index = hex_str.len() - 8; - let (root_hex, height_hex) = hex_str.split_at(root_end_index); - - let root = root_hex.to_string(); - let height = u32::from_str_radix(height_hex, 16) - .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; - - Ok(ProxySubtreeRpcData { - root, - height: Height(height), - }) - } -} - -/// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, -/// and a list of subtree roots and end heights. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_subtrees_by_index`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetSubtreesResponse { - /// The shielded pool to which the subtrees belong. - pub pool: String, - - /// The index of the first subtree. - pub start_index: NoteCommitmentSubtreeIndex, - - /// A sequential list of complete subtrees, in `index` order. - /// - /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. - // #[serde(skip_serializing_if = "Vec::is_empty")] - pub subtrees: Vec, -} - -/// Contains raw transaction, encoded as hex bytes. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub enum GetTransactionResponse { - /// The raw transaction, encoded as hex bytes. - Raw(#[serde(with = "hex")] SerializedTransaction), - /// The transaction object. - Object { - /// The raw transaction, encoded as hex bytes. - #[serde(with = "hex")] - hex: SerializedTransaction, - /// The height of the block in the best chain that contains the transaction, or -1 if - /// the transaction is in the mempool. - height: i32, - /// The confirmations of the block in the best chain that contains the transaction, - /// or 0 if the transaction is in the mempool. - confirmations: u32, - }, -} - -/// Zingo-Proxy encoding of a Bitcoin script. -#[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct ProxyScript { - /// # Correctness - /// - /// Consensus-critical serialization uses [`ZcashSerialize`]. - /// [`serde`]-based hex serialization must only be used for RPCs and testing. - #[serde(with = "hex")] - script: Vec, -} - -impl ProxyScript { - /// Create a new Bitcoin script from its raw bytes. - /// The raw bytes must not contain the length prefix. - pub fn new(raw_bytes: &[u8]) -> Self { - Self { - script: raw_bytes.to_vec(), - } - } - - /// Return the raw bytes of the script without the length prefix. - /// - /// # Correctness - /// - /// These raw bytes do not have a length prefix. - /// The Zcash serialization format requires a length prefix; use `zcash_serialize` - /// and `zcash_deserialize` to create byte data with a length prefix. - pub fn as_raw_bytes(&self) -> &[u8] { - &self.script - } -} - -impl core::fmt::Display for ProxyScript { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.write_str(&self.encode_hex::()) - } -} - -impl core::fmt::Debug for ProxyScript { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_tuple("Script") - .field(&hex::encode(&self.script)) - .finish() - } -} - -impl ToHex for &ProxyScript { - fn encode_hex>(&self) -> T { - self.as_raw_bytes().encode_hex() - } - - fn encode_hex_upper>(&self) -> T { - self.as_raw_bytes().encode_hex_upper() - } -} - -impl ToHex for ProxyScript { - fn encode_hex>(&self) -> T { - (&self).encode_hex() - } - - fn encode_hex_upper>(&self) -> T { - (&self).encode_hex_upper() - } -} - -impl FromHex for ProxyScript { - type Error = hex::FromHexError; - - fn from_hex>(hex: T) -> Result { - let bytes = Vec::from_hex(hex)?; - Ok(Self { script: bytes }) - } -} - -/// . -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_address_utxos`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetUtxosResponse { - /// The transparent address, base58check encoded - address: transparent::Address, - - /// The output txid, in big-endian order, hex-encoded - #[serde(with = "hex")] - txid: transaction::Hash, - - /// The transparent output index, numeric - #[serde(rename = "outputIndex")] - output_index: zebra_state::OutputIndex, - - /// The transparent output script, hex encoded - #[serde(with = "hex")] - script: ProxyScript, - - /// The amount of zatoshis in the transparent output - satoshis: u64, - - /// The block height, numeric. - height: Height, -} - -/// JsonRPC Client config data. -pub struct JsonRpcConnector { - uri: http::Uri, -} - -impl JsonRpcConnector { - /// Returns a new JsonRpcConnector instance. - pub fn new(uri: http::Uri) -> Self { - Self { uri } - } - - /// Returns the uri the JsonRpcConnector is configured to send requests to. - pub fn uri(&self) -> &Uri { - &self.uri - } - - /// Sends a jsonRPC request and returns the response.`` - pub async fn send_request Deserialize<'de>>( - &self, - method: &str, - params: T, - id: i32, - ) -> Result> { - let client = Client::builder().build(HttpsConnector::new()); - let req = RpcRequest { - jsonrpc: "2.0".to_string(), - method: method.to_string(), - params, - id, - }; - let request = Request::builder() - .method("POST") - .uri(self.uri.clone()) - .header("Content-Type", "application/json") - .body(Body::from(serde_json::to_string(&req)?))?; - - let response = client.request(request).await?; - let body_bytes = hyper::body::to_bytes(response.into_body()).await?; - let response: RpcResponse = serde_json::from_slice(&body_bytes)?; - - match response.error { - Some(error) => Err(format!("RPC Error {}: {}", error.code, error.message).into()), - None => Ok(response.result), - } - } - - /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. - /// - /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) - /// method: post - /// tags: control - pub async fn get_info(&self, id: i32) -> Result> { - self.send_request::<(), GetInfo>("getinfo", (), id).await - } - - /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct. - /// - /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html) - /// method: post - /// tags: blockchain - pub async fn get_blockchain_info( - &self, - id: i32, - ) -> Result> { - self.send_request::<(), GetBlockChainInfo>("getblockchaininfo", (), id) - .await - } - - /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance. - /// - /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html) - /// method: post - /// tags: address - /// - /// # Parameters - /// - /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry - /// - `addresses`: (array of strings) A list of base-58 encoded addresses. - pub async fn get_address_balance( - &self, - addresses: Vec, - id: i32, - ) -> Result> { - let params = AddressStringsRequest { addresses }; - self.send_request("getaddressbalance", params, id).await - } - - /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid. - /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. - /// - /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html) - /// method: post - /// tags: transaction - /// - /// # Parameters - /// - /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes. - pub async fn send_raw_transaction( - &self, - raw_transaction_hex: String, - id: i32, - ) -> Result> { - let params = SendTransactionRequest { - raw_transaction_hex, - }; - self.send_request("sendrawtransaction", params, id).await - } - - /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. - /// If the block is not in Zebra's state, returns - /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) - /// - /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html) - /// method: post - /// tags: blockchain - /// - /// # Parameters - /// - /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. - /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. - pub async fn get_block( - &self, - hash_or_height: String, - verbosity: Option, - id: i32, - ) -> Result> { - let params = GetBlockRequest { - hash_or_height, - verbosity, - }; - self.send_request("getblock", params, id).await - } - - /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. - /// - /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html) - /// method: post - /// tags: blockchain - pub async fn get_best_block_hash( - &self, - id: i32, - ) -> Result> { - self.send_request::<(), GetBlockHash>("getbestblockhash", (), id) - .await - } - - /// Returns all transaction ids in the memory pool, as a JSON array. - /// - /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) - /// method: post - /// tags: blockchain - pub async fn get_raw_mempool( - &self, - id: i32, - ) -> Result> { - self.send_request::<(), TxidsResponse>("getrawmempool", (), id) - .await - } - - /// Returns information about the given block's Sapling & Orchard tree state. - /// - /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html) - /// method: post - /// tags: blockchain - /// - /// # Parameters - /// - /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height. - pub async fn get_treestate( - &self, - hash_or_height: String, - id: i32, - ) -> Result> { - let params = GetTreestateRequest { hash_or_height }; - self.send_request("z_gettreestate", params, id).await - } - - /// Returns information about a range of Sapling or Orchard subtrees. - /// - /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link - /// method: post - /// tags: blockchain - /// - /// # Parameters - /// - /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard". - /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return. - /// - `limit`: (number, optional) The maximum number of subtree values to return. - pub async fn get_subtrees_by_index( - &self, - pool: String, - start_index: u16, - limit: Option, - id: i32, - ) -> Result> { - let params = GetSubtreesRequest { - pool, - start_index, - limit, - }; - self.send_request("z_getsubtreesbyindex", params, id).await - } - - /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure. - /// - /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html) - /// method: post - /// tags: transaction - /// - /// # Parameters - /// - /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned. - /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object. - pub async fn get_raw_transaction( - &self, - txid_hex: String, - verbose: Option, - id: i32, - ) -> Result> { - let params = GetTransactionRequest { txid_hex, verbose }; - self.send_request("getrawtransaction", params, id).await - } - - /// Returns the transaction ids made by the provided transparent addresses. - /// - /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html) - /// method: post - /// tags: address - /// - /// # Parameters - /// - /// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields: - /// - `addresses`: (json array of string, required) The addresses to get transactions from. - /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). - /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). - pub async fn get_address_tx_ids( - &self, - addresses: Vec, - start: u32, - end: u32, - id: i32, - ) -> Result> { - let params = TxidsByAddressRequest { - addresses, - start, - end, - }; - - self.send_request("getaddresstxids", params, id).await - } - - /// Returns all unspent outputs for a list of addresses. - /// - /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html) - /// method: post - /// tags: address - /// - /// # Parameters - /// - /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from. - pub async fn get_address_utxos( - &self, - addresses: Vec, - id: i32, - ) -> Result, Box> { - let params = AddressStringsRequest { addresses }; - self.send_request("getaddressutxos", params, id).await - } -} +pub mod connector; +pub mod primitives; diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs new file mode 100644 index 0000000..8dab352 --- /dev/null +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -0,0 +1,309 @@ +//! JsonRPC client implementation. + +use http::Uri; +use hyper::{http, Body, Client, Request}; +use hyper_tls::HttpsConnector; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use zebra_rpc::methods::{GetBlockChainInfo, GetBlockHash, GetInfo, SentTransactionHash}; + +use super::primitives::{ + AddressStringsRequest, GetBalanceResponse, GetBlockRequest, GetBlockResponse, + GetSubtreesRequest, GetSubtreesResponse, GetTransactionRequest, GetTransactionResponse, + GetTreestateRequest, GetTreestateResponse, GetUtxosResponse, SendTransactionRequest, + TxidsByAddressRequest, TxidsResponse, +}; + +#[derive(Serialize, Deserialize, Debug)] +struct RpcRequest { + jsonrpc: String, + method: String, + params: T, + id: i32, +} + +#[derive(Serialize, Deserialize, Debug)] +struct RpcResponse { + id: i32, + jsonrpc: String, + result: T, + error: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct RpcError { + code: i32, + message: String, + data: Option, +} + +/// JsonRPC Client config data. +pub struct JsonRpcConnector { + uri: http::Uri, +} + +impl JsonRpcConnector { + /// Returns a new JsonRpcConnector instance. + pub fn new(uri: http::Uri) -> Self { + Self { uri } + } + + /// Returns the uri the JsonRpcConnector is configured to send requests to. + pub fn uri(&self) -> &Uri { + &self.uri + } + + /// Sends a jsonRPC request and returns the response.`` + pub async fn send_request Deserialize<'de>>( + &self, + method: &str, + params: T, + id: i32, + ) -> Result> { + let client = Client::builder().build(HttpsConnector::new()); + let req = RpcRequest { + jsonrpc: "2.0".to_string(), + method: method.to_string(), + params, + id, + }; + let request = Request::builder() + .method("POST") + .uri(self.uri.clone()) + .header("Content-Type", "application/json") + .body(Body::from(serde_json::to_string(&req)?))?; + + let response = client.request(request).await?; + let body_bytes = hyper::body::to_bytes(response.into_body()).await?; + let response: RpcResponse = serde_json::from_slice(&body_bytes)?; + + match response.error { + Some(error) => Err(format!("RPC Error {}: {}", error.code, error.message).into()), + None => Ok(response.result), + } + } + + /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. + /// + /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) + /// method: post + /// tags: control + pub async fn get_info(&self, id: i32) -> Result> { + self.send_request::<(), GetInfo>("getinfo", (), id).await + } + + /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct. + /// + /// zcashd reference: [`getblockchaininfo`](https://zcash.github.io/rpc/getblockchaininfo.html) + /// method: post + /// tags: blockchain + pub async fn get_blockchain_info( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), GetBlockChainInfo>("getblockchaininfo", (), id) + .await + } + + /// Returns the total balance of a provided `addresses` in an [`AddressBalance`] instance. + /// + /// zcashd reference: [`getaddressbalance`](https://zcash.github.io/rpc/getaddressbalance.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry + /// - `addresses`: (array of strings) A list of base-58 encoded addresses. + pub async fn get_address_balance( + &self, + addresses: Vec, + id: i32, + ) -> Result> { + let params = AddressStringsRequest { addresses }; + self.send_request("getaddressbalance", params, id).await + } + + /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid. + /// Returns the [`SentTransactionHash`] for the transaction, as a JSON string. + /// + /// zcashd reference: [`sendrawtransaction`](https://zcash.github.io/rpc/sendrawtransaction.html) + /// method: post + /// tags: transaction + /// + /// # Parameters + /// + /// - `raw_transaction_hex`: (string, required, example="signedhex") The hex-encoded raw transaction bytes. + pub async fn send_raw_transaction( + &self, + raw_transaction_hex: String, + id: i32, + ) -> Result> { + let params = SendTransactionRequest { + raw_transaction_hex, + }; + self.send_request("sendrawtransaction", params, id).await + } + + /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. + /// If the block is not in Zebra's state, returns + /// [error code `-8`.](https://github.com/zcash/zcash/issues/5758) + /// + /// zcashd reference: [`getblock`](https://zcash.github.io/rpc/getblock.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. + /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. + pub async fn get_block( + &self, + hash_or_height: String, + verbosity: Option, + id: i32, + ) -> Result> { + let params = GetBlockRequest { + hash_or_height, + verbosity, + }; + self.send_request("getblock", params, id).await + } + + /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. + /// + /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html) + /// method: post + /// tags: blockchain + pub async fn get_best_block_hash( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), GetBlockHash>("getbestblockhash", (), id) + .await + } + + /// Returns all transaction ids in the memory pool, as a JSON array. + /// + /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) + /// method: post + /// tags: blockchain + pub async fn get_raw_mempool( + &self, + id: i32, + ) -> Result> { + self.send_request::<(), TxidsResponse>("getrawmempool", (), id) + .await + } + + /// Returns information about the given block's Sapling & Orchard tree state. + /// + /// zcashd reference: [`z_gettreestate`](https://zcash.github.io/rpc/z_gettreestate.html) + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `hash | height`: (string, required, example="00000000febc373a1da2bd9f887b105ad79ddc26ac26c2b28652d64e5207c5b5") The block hash or height. + pub async fn get_treestate( + &self, + hash_or_height: String, + id: i32, + ) -> Result> { + let params = GetTreestateRequest { hash_or_height }; + self.send_request("z_gettreestate", params, id).await + } + + /// Returns information about a range of Sapling or Orchard subtrees. + /// + /// zcashd reference: [`z_getsubtreesbyindex`](https://zcash.github.io/rpc/z_getsubtreesbyindex.html) - TODO: fix link + /// method: post + /// tags: blockchain + /// + /// # Parameters + /// + /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard". + /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return. + /// - `limit`: (number, optional) The maximum number of subtree values to return. + pub async fn get_subtrees_by_index( + &self, + pool: String, + start_index: u16, + limit: Option, + id: i32, + ) -> Result> { + let params = GetSubtreesRequest { + pool, + start_index, + limit, + }; + self.send_request("z_getsubtreesbyindex", params, id).await + } + + /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure. + /// + /// zcashd reference: [`getrawtransaction`](https://zcash.github.io/rpc/getrawtransaction.html) + /// method: post + /// tags: transaction + /// + /// # Parameters + /// + /// - `txid`: (string, required, example="mytxid") The transaction ID of the transaction to be returned. + /// - `verbose`: (number, optional, default=0, example=1) If 0, return a string of hex-encoded data, otherwise return a JSON object. + pub async fn get_raw_transaction( + &self, + txid_hex: String, + verbose: Option, + id: i32, + ) -> Result> { + let params = GetTransactionRequest { txid_hex, verbose }; + self.send_request("getrawtransaction", params, id).await + } + + /// Returns the transaction ids made by the provided transparent addresses. + /// + /// zcashd reference: [`getaddresstxids`](https://zcash.github.io/rpc/getaddresstxids.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `request`: (object, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"], \"start\": 1000, \"end\": 2000}) A struct with the following named fields: + /// - `addresses`: (json array of string, required) The addresses to get transactions from. + /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). + /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). + pub async fn get_address_tx_ids( + &self, + addresses: Vec, + start: u32, + end: u32, + id: i32, + ) -> Result> { + let params = TxidsByAddressRequest { + addresses, + start, + end, + }; + + self.send_request("getaddresstxids", params, id).await + } + + /// Returns all unspent outputs for a list of addresses. + /// + /// zcashd reference: [`getaddressutxos`](https://zcash.github.io/rpc/getaddressutxos.html) + /// method: post + /// tags: address + /// + /// # Parameters + /// + /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from. + pub async fn get_address_utxos( + &self, + addresses: Vec, + id: i32, + ) -> Result, Box> { + let params = AddressStringsRequest { addresses }; + self.send_request("getaddressutxos", params, id).await + } +} diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs new file mode 100644 index 0000000..1dd6215 --- /dev/null +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -0,0 +1,517 @@ +//! Request and response types for jsonRPC client. + +use hex::{FromHex, ToHex}; +use serde::{Deserialize, Serialize}; + +use serde::ser::SerializeStruct; + +use zebra_chain::{ + block::{self, Height, SerializedBlock}, + subtree::NoteCommitmentSubtreeIndex, + transaction::{self, SerializedTransaction}, + transparent, +}; +use zebra_rpc::methods::{GetBlockHash, GetBlockTrees}; + +/// List of transparent address strings. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_address_balance`] and [`JsonRpcConnector::get_address_utxos`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct AddressStringsRequest { + /// A list of transparent address strings. + pub addresses: Vec, +} + +/// Hex-encoded raw transaction. +/// +/// This is used for the input parameter of [`JsonRpcConnector::send_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct SendTransactionRequest { + /// - Hex-encoded raw transaction bytes. + pub raw_transaction_hex: String, +} + +/// Block to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_block`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GetBlockRequest { + /// The hash or height for the block to be returned. + pub hash_or_height: String, + /// 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. Default=1. + pub verbosity: Option, +} + +/// Block to be examined. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_treestate`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GetTreestateRequest { + /// The block hash or height. + pub hash_or_height: String, +} + +/// Subtrees to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_subtrees_by_index`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GetSubtreesRequest { + /// The pool from which subtrees should be returned. Either "sapling" or "orchard". + pub pool: String, + /// The index of the first 2^16-leaf subtree to return. + pub start_index: u16, + /// The maximum number of subtree values to return. + pub limit: Option, +} + +/// Transaction to be fetched. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct GetTransactionRequest { + /// The transaction ID of the transaction to be returned. + pub txid_hex: String, + /// If 0, return a string of hex-encoded data, otherwise return a JSON object. Default=0. + pub verbose: Option, +} + +/// List of transparent address strings and range of blocks to fetch Txids from. +/// +/// This is used for the input parameter of [`JsonRpcConnector::get_address_tx_ids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TxidsByAddressRequest { + /// A list of addresses to get transactions from. + pub addresses: Vec, + /// The height to start looking for transactions. + pub start: u32, + /// The height to end looking for transactions. + pub end: u32, +} + +/// Vec of transaction ids, as a JSON array. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct TxidsResponse { + /// Vec of txids. + pub transactions: Vec, +} + +/// The transparent balance of a set of addresses. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_address_balance`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetBalanceResponse { + /// The total transparent balance. + pub balance: u64, +} + +/// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct HexSerializedBlock(SerializedBlock); + +impl Serialize for HexSerializedBlock { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let hex_string = self.as_ref().encode_hex::(); + serializer.serialize_str(&hex_string) + } +} + +impl<'de> Deserialize<'de> for HexSerializedBlock { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct HexVisitor; + + impl<'de> serde::de::Visitor<'de> for HexVisitor { + type Value = HexSerializedBlock; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a hex-encoded string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let bytes = hex::decode(value).map_err(serde::de::Error::custom)?; + Ok(HexSerializedBlock(SerializedBlock::from(bytes))) + } + } + + deserializer.deserialize_str(HexVisitor) + } +} + +impl FromHex for HexSerializedBlock { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + hex::decode(hex) + .map(|bytes| HexSerializedBlock(SerializedBlock::from(bytes))) + .map_err(|e| e.into()) + } +} + +impl AsRef<[u8]> for HexSerializedBlock { + fn as_ref(&self) -> &[u8] { + &self.0.as_ref() + } +} + +/// Contains the hex-encoded hash of the sent transaction. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_block`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(untagged)] +pub enum GetBlockResponse { + /// The request block, hex-encoded. + Raw(#[serde(with = "hex")] HexSerializedBlock), + /// The block object. + Object { + /// The hash of the requested block. + hash: GetBlockHash, + + /// The number of confirmations of this block in the best chain, + /// or -1 if it is not in the best chain. + confirmations: i64, + + /// The height of the requested block. + #[serde(skip_serializing_if = "Option::is_none")] + height: Option, + + /// The height of the requested block. + #[serde(skip_serializing_if = "Option::is_none")] + time: Option, + + /// List of transaction IDs in block order, hex-encoded. + tx: Vec, + + /// Information about the note commitment trees. + trees: GetBlockTrees, + }, +} + +/// Zingo-Proxy commitment tree structure replicating functionality in Zebra. +/// +/// A wrapper that contains either an Orchard or Sapling note commitment tree. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ProxyCommitments> { + #[serde(with = "hex")] + #[serde(rename = "finalState")] + final_state: Tree, +} + +impl + FromHex> ProxyCommitments { + /// Creates a new instance of `ProxyCommitments` from a hex string. + pub fn new_from_hex(hex_encoded_data: &str) -> Result { + let tree = Tree::from_hex(hex_encoded_data)?; + Ok(Self { final_state: tree }) + } + + /// Checks if the internal tree is empty. + pub fn is_empty(&self) -> bool { + self.final_state.as_ref().is_empty() + } +} + +/// Zingo-Proxy treestate structure replicating functionality in Zebra. +/// +/// A treestate that is included in the [`z_gettreestate`][1] RPC response. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct ProxyTreestate> { + commitments: ProxyCommitments, +} + +impl + FromHex> ProxyTreestate { + /// Creates a new instance of `ProxyTreestate`. + pub fn new(commitments: ProxyCommitments) -> Self { + Self { commitments } + } + + /// Checks if the internal tree is empty. + pub fn is_empty(&self) -> bool { + self.commitments.is_empty() + } +} + +impl<'de, Tree: AsRef<[u8]> + FromHex + Deserialize<'de>> + Deserialize<'de> for ProxyTreestate +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let hex_string: String = Deserialize::deserialize(deserializer)?; + let tree = Tree::from_hex(&hex_string).map_err(serde::de::Error::custom)?; + Ok(ProxyTreestate::new(ProxyCommitments { final_state: tree })) + } +} + +/// A serialized Sapling note commitment tree +/// +/// Replicates functionality used in Zebra. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxySerializedTree(Vec); + +impl FromHex for ProxySerializedTree { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let bytes = hex::decode(hex)?; + Ok(ProxySerializedTree(bytes)) + } +} + +impl AsRef<[u8]> for ProxySerializedTree { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +/// Contains the hex-encoded Sapling & Orchard note commitment trees, and their +/// corresponding [`block::Hash`], [`Height`], and block time. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_treestate`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetTreestateResponse { + /// The block hash corresponding to the treestate, hex-encoded. + #[serde(with = "hex")] + hash: block::Hash, + + /// The block height corresponding to the treestate, numeric. + height: Height, + + /// Unix time when the block corresponding to the treestate was mined, + /// numeric. + /// + /// UTC seconds since the Unix 1970-01-01 epoch. + time: u32, + + /// A treestate containing a Sapling note commitment tree, hex-encoded. + #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] + sapling: ProxyTreestate, + + /// A treestate containing an Orchard note commitment tree, hex-encoded. + #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] + orchard: ProxyTreestate, + // NOTE: CREATE PoxySerializedTree TO SIMPLIFY CODING>> +} + +/// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProxySubtreeRpcData { + /// Merkle root of the 2^16-leaf subtree. + pub root: String, + /// Height of the block containing the note that completed this subtree. + pub height: Height, +} + +impl ProxySubtreeRpcData { + /// Returns new instance of ProxySubtreeRpcData + pub fn new(root: String, height: Height) -> Self { + Self { root, height } + } +} + +impl serde::Serialize for ProxySubtreeRpcData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ProxySubtreeRpcData", 2)?; + state.serialize_field("root", &self.root)?; + state.serialize_field("height", &self.height)?; + state.end() + } +} + +impl<'de> serde::Deserialize<'de> for ProxySubtreeRpcData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Inner { + root: String, + height: Height, + } + + let inner = Inner::deserialize(deserializer)?; + Ok(ProxySubtreeRpcData { + root: inner.root, + height: inner.height, + }) + } +} + +impl FromHex for ProxySubtreeRpcData { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let hex_str = std::str::from_utf8(hex.as_ref()) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + if hex_str.len() < 8 { + return Err(hex::FromHexError::OddLength); + } + + let root_end_index = hex_str.len() - 8; + let (root_hex, height_hex) = hex_str.split_at(root_end_index); + + let root = root_hex.to_string(); + let height = u32::from_str_radix(height_hex, 16) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + Ok(ProxySubtreeRpcData { + root, + height: Height(height), + }) + } +} + +/// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, +/// and a list of subtree roots and end heights. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_subtrees_by_index`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetSubtreesResponse { + /// The shielded pool to which the subtrees belong. + pub pool: String, + + /// The index of the first subtree. + pub start_index: NoteCommitmentSubtreeIndex, + + /// A sequential list of complete subtrees, in `index` order. + /// + /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. + // #[serde(skip_serializing_if = "Vec::is_empty")] + pub subtrees: Vec, +} + +/// Contains raw transaction, encoded as hex bytes. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub enum GetTransactionResponse { + /// The raw transaction, encoded as hex bytes. + Raw(#[serde(with = "hex")] SerializedTransaction), + /// The transaction object. + Object { + /// The raw transaction, encoded as hex bytes. + #[serde(with = "hex")] + hex: SerializedTransaction, + /// The height of the block in the best chain that contains the transaction, or -1 if + /// the transaction is in the mempool. + height: i32, + /// The confirmations of the block in the best chain that contains the transaction, + /// or 0 if the transaction is in the mempool. + confirmations: u32, + }, +} + +/// Zingo-Proxy encoding of a Bitcoin script. +#[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxyScript { + /// # Correctness + /// + /// Consensus-critical serialization uses [`ZcashSerialize`]. + /// [`serde`]-based hex serialization must only be used for RPCs and testing. + #[serde(with = "hex")] + script: Vec, +} + +impl ProxyScript { + /// Create a new Bitcoin script from its raw bytes. + /// The raw bytes must not contain the length prefix. + pub fn new(raw_bytes: &[u8]) -> Self { + Self { + script: raw_bytes.to_vec(), + } + } + + /// Return the raw bytes of the script without the length prefix. + /// + /// # Correctness + /// + /// These raw bytes do not have a length prefix. + /// The Zcash serialization format requires a length prefix; use `zcash_serialize` + /// and `zcash_deserialize` to create byte data with a length prefix. + pub fn as_raw_bytes(&self) -> &[u8] { + &self.script + } +} + +impl core::fmt::Display for ProxyScript { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.write_str(&self.encode_hex::()) + } +} + +impl core::fmt::Debug for ProxyScript { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_tuple("Script") + .field(&hex::encode(&self.script)) + .finish() + } +} + +impl ToHex for &ProxyScript { + fn encode_hex>(&self) -> T { + self.as_raw_bytes().encode_hex() + } + + fn encode_hex_upper>(&self) -> T { + self.as_raw_bytes().encode_hex_upper() + } +} + +impl ToHex for ProxyScript { + fn encode_hex>(&self) -> T { + (&self).encode_hex() + } + + fn encode_hex_upper>(&self) -> T { + (&self).encode_hex_upper() + } +} + +impl FromHex for ProxyScript { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let bytes = Vec::from_hex(hex)?; + Ok(Self { script: bytes }) + } +} + +/// . +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_address_utxos`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetUtxosResponse { + /// The transparent address, base58check encoded + address: transparent::Address, + + /// The output txid, in big-endian order, hex-encoded + #[serde(with = "hex")] + txid: transaction::Hash, + + /// The transparent output index, numeric + #[serde(rename = "outputIndex")] + output_index: zebra_state::OutputIndex, + + /// The transparent output script, hex encoded + #[serde(with = "hex")] + script: ProxyScript, + + /// The amount of zatoshis in the transparent output + satoshis: u64, + + /// The block height, numeric. + height: Height, +} From 0d3075eb497c97f350e57cce0f8decfef8cd5dca Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 17 May 2024 19:03:49 +0100 Subject: [PATCH 05/40] renamed HexSerializedBlock to ProxySerializedBlock --- zingo-rpc/src/jsonrpc/primitives.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 1dd6215..ae313d3 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -108,9 +108,9 @@ pub struct GetBalanceResponse { /// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct HexSerializedBlock(SerializedBlock); +pub struct ProxySerializedBlock(SerializedBlock); -impl Serialize for HexSerializedBlock { +impl Serialize for ProxySerializedBlock { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -120,7 +120,7 @@ impl Serialize for HexSerializedBlock { } } -impl<'de> Deserialize<'de> for HexSerializedBlock { +impl<'de> Deserialize<'de> for ProxySerializedBlock { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, @@ -128,7 +128,7 @@ impl<'de> Deserialize<'de> for HexSerializedBlock { struct HexVisitor; impl<'de> serde::de::Visitor<'de> for HexVisitor { - type Value = HexSerializedBlock; + type Value = ProxySerializedBlock; fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { formatter.write_str("a hex-encoded string") @@ -139,7 +139,7 @@ impl<'de> Deserialize<'de> for HexSerializedBlock { E: serde::de::Error, { let bytes = hex::decode(value).map_err(serde::de::Error::custom)?; - Ok(HexSerializedBlock(SerializedBlock::from(bytes))) + Ok(ProxySerializedBlock(SerializedBlock::from(bytes))) } } @@ -147,17 +147,17 @@ impl<'de> Deserialize<'de> for HexSerializedBlock { } } -impl FromHex for HexSerializedBlock { +impl FromHex for ProxySerializedBlock { type Error = hex::FromHexError; fn from_hex>(hex: T) -> Result { hex::decode(hex) - .map(|bytes| HexSerializedBlock(SerializedBlock::from(bytes))) + .map(|bytes| ProxySerializedBlock(SerializedBlock::from(bytes))) .map_err(|e| e.into()) } } -impl AsRef<[u8]> for HexSerializedBlock { +impl AsRef<[u8]> for ProxySerializedBlock { fn as_ref(&self) -> &[u8] { &self.0.as_ref() } @@ -170,7 +170,7 @@ impl AsRef<[u8]> for HexSerializedBlock { #[serde(untagged)] pub enum GetBlockResponse { /// The request block, hex-encoded. - Raw(#[serde(with = "hex")] HexSerializedBlock), + Raw(#[serde(with = "hex")] ProxySerializedBlock), /// The block object. Object { /// The hash of the requested block. From 8bc6702d2432971bf4596ef5451f9718ffaa6843 Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 20 May 2024 21:08:41 +0100 Subject: [PATCH 06/40] get_lightd_info implemented - working on testnet, not in tests --- Cargo.lock | 2 + zingo-rpc/Cargo.toml | 7 +- zingo-rpc/build.rs | 38 +++++ zingo-rpc/src/jsonrpc/connector.rs | 207 +++++++++++++++++++++------- zingo-rpc/src/jsonrpc/primitives.rs | 175 ++++++++++++++++++++--- zingo-rpc/src/rpc/service.rs | 87 +++++++++++- zingo-rpc/src/utils.rs | 10 ++ 7 files changed, 447 insertions(+), 79 deletions(-) create mode 100644 zingo-rpc/build.rs diff --git a/Cargo.lock b/Cargo.lock index cf8fbf8..e8006ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8549,6 +8549,8 @@ dependencies = [ "hyper", "hyper-rustls 0.23.2", "hyper-tls", + "indexmap 2.2.6", + "jsonrpc-core", "nym-bin-common", "nym-sdk", "nym-sphinx-addressing", diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 44dd57f..2eebebc 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -3,6 +3,7 @@ name = "zingo-rpc" version = "0.1.0" authors = ["zingo@zingolabs.org"] edition = "2021" +build = "build.rs" [features] test = [] @@ -37,7 +38,11 @@ rustls-pemfile = { workspace = true } webpki-roots = "0.21.0" serde = { version = "1.0.201", features = ["derive"] } -serde_json = "1.0.117" hyper-tls = "0.5" hex = { version = "0.4.3", features = ["serde"] } +# The preserve_order feature in serde_jsonn is a dependency of jsonrpc-core +serde_json = { version = "1.0.117", features = ["preserve_order"] } +jsonrpc-core = "18.0.0" + +indexmap = { version = "2.2.6", features = ["serde"] } diff --git a/zingo-rpc/build.rs b/zingo-rpc/build.rs new file mode 100644 index 0000000..4dd60db --- /dev/null +++ b/zingo-rpc/build.rs @@ -0,0 +1,38 @@ +use std::env; +use std::process::Command; + +fn main() { + // Fetch the commit hash + let commit_hash = Command::new("git") + .args(&["rev-parse", "HEAD"]) + .output() + .expect("Failed to get commit hash") + .stdout; + let commit_hash = String::from_utf8(commit_hash).expect("Invalid UTF-8 sequence"); + println!("cargo:rustc-env=GIT_COMMIT={}", commit_hash.trim()); + + // Fetch the current branch + let branch = Command::new("git") + .args(&["rev-parse", "--abbrev-ref", "HEAD"]) + .output() + .expect("Failed to get branch") + .stdout; + let branch = String::from_utf8(branch).expect("Invalid UTF-8 sequence"); + println!("cargo:rustc-env=BRANCH={}", branch.trim()); + + // Set the build date + let build_date = Command::new("date") + .output() + .expect("Failed to get build date") + .stdout; + let build_date = String::from_utf8(build_date).expect("Invalid UTF-8 sequence"); + println!("cargo:rustc-env=BUILD_DATE={}", build_date.trim()); + + // Set the build user + let build_user = env::var("USER").expect("Failed to get build user"); + println!("cargo:rustc-env=BUILD_USER={}", build_user); + + // Set the version from Cargo.toml + let version = env::var("CARGO_PKG_VERSION").expect("Failed to get version from Cargo.toml"); + println!("cargo:rustc-env=VERSION={}", version); +} diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 8dab352..479c824 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -5,13 +5,13 @@ use hyper::{http, Body, Client, Request}; use hyper_tls::HttpsConnector; use serde::{Deserialize, Serialize}; use serde_json::Value; - -use zebra_rpc::methods::{GetBlockChainInfo, GetBlockHash, GetInfo, SentTransactionHash}; +use std::sync::atomic::{AtomicI32, Ordering}; use super::primitives::{ - AddressStringsRequest, GetBalanceResponse, GetBlockRequest, GetBlockResponse, - GetSubtreesRequest, GetSubtreesResponse, GetTransactionRequest, GetTransactionResponse, - GetTreestateRequest, GetTreestateResponse, GetUtxosResponse, SendTransactionRequest, + AddressStringsRequest, BestBlockHashResponse, GetBalanceResponse, GetBlockRequest, + GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, GetSubtreesRequest, + GetSubtreesResponse, GetTransactionRequest, GetTransactionResponse, GetTreestateRequest, + GetTreestateResponse, GetUtxosResponse, SendTransactionRequest, SendTransactionResponse, TxidsByAddressRequest, TxidsResponse, }; @@ -38,15 +38,108 @@ struct RpcError { data: Option, } +/// General error type for handling JsonRpcConnector errors. +#[derive(Debug)] +pub struct JsonRpcConnectorError { + details: String, + source: Option>, +} + +impl JsonRpcConnectorError { + /// Constructor for errors without an underlying source + pub fn new(msg: impl Into) -> Self { + Self { + details: msg.into(), + source: None, + } + } + + /// Constructor for errors with an underlying source + pub fn new_with_source( + msg: impl Into, + source: Box, + ) -> Self { + Self { + details: msg.into(), + source: Some(source), + } + } + + /// Maps JsonRpcConnectorError to tonic::Status + pub fn to_grpc_status(&self) -> tonic::Status { + eprintln!("Error occurred: {}", self); + + if let Some(source) = &self.source { + if source.is::() { + return tonic::Status::invalid_argument(self.to_string()); + } else if source.is::() { + return tonic::Status::unavailable(self.to_string()); + } else if source.is::() { + return tonic::Status::internal(self.to_string()); + } + } + + tonic::Status::internal(self.to_string()) + } +} + +impl std::error::Error for JsonRpcConnectorError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + self.source + .as_deref() + .map(|e| e as &(dyn std::error::Error + 'static)) + } +} + +impl std::fmt::Display for JsonRpcConnectorError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.details) + } +} + +impl From for JsonRpcConnectorError { + fn from(err: serde_json::Error) -> Self { + JsonRpcConnectorError::new_with_source( + format!("Serialization/Deserialization Error: {}", err), + Box::new(err), + ) + } +} + +impl From for JsonRpcConnectorError { + fn from(err: hyper::Error) -> Self { + JsonRpcConnectorError::new_with_source( + format!("HTTP Request Error: {}", err), + Box::new(err), + ) + } +} + +impl From for JsonRpcConnectorError { + fn from(err: http::Error) -> Self { + JsonRpcConnectorError::new_with_source(format!("HTTP Error: {}", err), Box::new(err)) + } +} + +impl From for JsonRpcConnectorError { + fn from(err: String) -> Self { + JsonRpcConnectorError::new(err) + } +} + /// JsonRPC Client config data. pub struct JsonRpcConnector { uri: http::Uri, + id_counter: AtomicI32, } impl JsonRpcConnector { /// Returns a new JsonRpcConnector instance. pub fn new(uri: http::Uri) -> Self { - Self { uri } + Self { + uri, + id_counter: AtomicI32::new(0), + } } /// Returns the uri the JsonRpcConnector is configured to send requests to. @@ -59,8 +152,8 @@ impl JsonRpcConnector { &self, method: &str, params: T, - id: i32, - ) -> Result> { + ) -> Result { + let id = self.id_counter.fetch_add(1, Ordering::SeqCst); let client = Client::builder().build(HttpsConnector::new()); let req = RpcRequest { jsonrpc: "2.0".to_string(), @@ -68,18 +161,44 @@ impl JsonRpcConnector { params, id, }; + let request_body = serde_json::to_string(&req).map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) + })?; let request = Request::builder() .method("POST") .uri(self.uri.clone()) .header("Content-Type", "application/json") - .body(Body::from(serde_json::to_string(&req)?))?; + .body(Body::from(request_body)) + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + })?; + let response = client.request(request).await.map_err(|e| { + JsonRpcConnectorError::new_with_source("HTTP request failed", Box::new(e)) + })?; + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) + })?; + + // Test Code!!! + let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { + JsonRpcConnectorError::new_with_source( + "Failed to convert response body to string", + Box::new(e), + ) + })?; + println!("Raw response body: {}", body_str); - let response = client.request(request).await?; - let body_bytes = hyper::body::to_bytes(response.into_body()).await?; - let response: RpcResponse = serde_json::from_slice(&body_bytes)?; + let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) + })?; match response.error { - Some(error) => Err(format!("RPC Error {}: {}", error.code, error.message).into()), + Some(error) => Err(JsonRpcConnectorError::new(format!( + "RPC Error {}: {}", + error.code, error.message + ))), None => Ok(response.result), } } @@ -89,8 +208,9 @@ impl JsonRpcConnector { /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) /// method: post /// tags: control - pub async fn get_info(&self, id: i32) -> Result> { - self.send_request::<(), GetInfo>("getinfo", (), id).await + pub async fn get_info(&self) -> Result { + self.send_request::<(), GetInfoResponse>("getinfo", ()) + .await } /// Returns blockchain state information, as a [`GetBlockChainInfo`] JSON struct. @@ -100,9 +220,8 @@ impl JsonRpcConnector { /// tags: blockchain pub async fn get_blockchain_info( &self, - id: i32, - ) -> Result> { - self.send_request::<(), GetBlockChainInfo>("getblockchaininfo", (), id) + ) -> Result { + self.send_request::<(), GetBlockchainInfoResponse>("getblockchaininfo", ()) .await } @@ -119,10 +238,9 @@ impl JsonRpcConnector { pub async fn get_address_balance( &self, addresses: Vec, - id: i32, - ) -> Result> { + ) -> Result { let params = AddressStringsRequest { addresses }; - self.send_request("getaddressbalance", params, id).await + self.send_request("getaddressbalance", params).await } /// Sends the raw bytes of a signed transaction to the local node's mempool, if the transaction is valid. @@ -138,12 +256,11 @@ impl JsonRpcConnector { pub async fn send_raw_transaction( &self, raw_transaction_hex: String, - id: i32, - ) -> Result> { + ) -> Result { let params = SendTransactionRequest { raw_transaction_hex, }; - self.send_request("sendrawtransaction", params, id).await + self.send_request("sendrawtransaction", params).await } /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. @@ -162,13 +279,12 @@ impl JsonRpcConnector { &self, hash_or_height: String, verbosity: Option, - id: i32, - ) -> Result> { + ) -> Result { let params = GetBlockRequest { hash_or_height, verbosity, }; - self.send_request("getblock", params, id).await + self.send_request("getblock", params).await } /// Returns the hash of the current best blockchain tip block, as a [`GetBlockHash`] JSON string. @@ -178,9 +294,8 @@ impl JsonRpcConnector { /// tags: blockchain pub async fn get_best_block_hash( &self, - id: i32, - ) -> Result> { - self.send_request::<(), GetBlockHash>("getbestblockhash", (), id) + ) -> Result { + self.send_request::<(), BestBlockHashResponse>("getbestblockhash", ()) .await } @@ -189,11 +304,8 @@ impl JsonRpcConnector { /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) /// method: post /// tags: blockchain - pub async fn get_raw_mempool( - &self, - id: i32, - ) -> Result> { - self.send_request::<(), TxidsResponse>("getrawmempool", (), id) + pub async fn get_raw_mempool(&self) -> Result { + self.send_request::<(), TxidsResponse>("getrawmempool", ()) .await } @@ -209,10 +321,9 @@ impl JsonRpcConnector { pub async fn get_treestate( &self, hash_or_height: String, - id: i32, - ) -> Result> { + ) -> Result { let params = GetTreestateRequest { hash_or_height }; - self.send_request("z_gettreestate", params, id).await + self.send_request("z_gettreestate", params).await } /// Returns information about a range of Sapling or Orchard subtrees. @@ -231,14 +342,13 @@ impl JsonRpcConnector { pool: String, start_index: u16, limit: Option, - id: i32, - ) -> Result> { + ) -> Result { let params = GetSubtreesRequest { pool, start_index, limit, }; - self.send_request("z_getsubtreesbyindex", params, id).await + self.send_request("z_getsubtreesbyindex", params).await } /// Returns the raw transaction data, as a [`GetRawTransaction`] JSON string or structure. @@ -255,10 +365,9 @@ impl JsonRpcConnector { &self, txid_hex: String, verbose: Option, - id: i32, - ) -> Result> { + ) -> Result { let params = GetTransactionRequest { txid_hex, verbose }; - self.send_request("getrawtransaction", params, id).await + self.send_request("getrawtransaction", params).await } /// Returns the transaction ids made by the provided transparent addresses. @@ -278,15 +387,14 @@ impl JsonRpcConnector { addresses: Vec, start: u32, end: u32, - id: i32, - ) -> Result> { + ) -> Result { let params = TxidsByAddressRequest { addresses, start, end, }; - self.send_request("getaddresstxids", params, id).await + self.send_request("getaddresstxids", params).await } /// Returns all unspent outputs for a list of addresses. @@ -301,9 +409,8 @@ impl JsonRpcConnector { pub async fn get_address_utxos( &self, addresses: Vec, - id: i32, - ) -> Result, Box> { + ) -> Result, JsonRpcConnectorError> { let params = AddressStringsRequest { addresses }; - self.send_request("getaddressutxos", params, id).await + self.send_request("getaddressutxos", params).await } } diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index ae313d3..3a79dbe 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -1,9 +1,9 @@ //! Request and response types for jsonRPC client. use hex::{FromHex, ToHex}; -use serde::{Deserialize, Serialize}; - +use indexmap::IndexMap; use serde::ser::SerializeStruct; +use serde::{Deserialize, Serialize}; use zebra_chain::{ block::{self, Height, SerializedBlock}, @@ -22,6 +22,35 @@ pub struct AddressStringsRequest { pub addresses: Vec, } +impl AddressStringsRequest { + /// Creates a new `AddressStrings` given a vector. + #[cfg(test)] + pub fn new(addresses: Vec) -> Self { + AddressStringsRequest { addresses } + } + + /// Given a list of addresses as strings: + /// - check if provided list have all valid transparent addresses. + /// - return valid addresses as a set of `Address`. + pub fn valid_addresses( + self, + ) -> jsonrpc_core::Result> { + let valid_addresses: std::collections::HashSet = self + .addresses + .into_iter() + .map(|address| { + address.parse().map_err(|error| { + jsonrpc_core::Error::invalid_params(format!( + "invalid address {address:?}: {error}" + )) + }) + }) + .collect::>()?; + + Ok(valid_addresses) + } +} + /// Hex-encoded raw transaction. /// /// This is used for the input parameter of [`JsonRpcConnector::send_raw_transaction`]. @@ -88,13 +117,96 @@ pub struct TxidsByAddressRequest { pub end: u32, } -/// Vec of transaction ids, as a JSON array. +/// Response to a `getinfo` RPC request. /// -/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. +/// This is used for the output parameter of [`JsonRpcConnector::get_info`]. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct TxidsResponse { - /// Vec of txids. - pub transactions: Vec, +pub struct GetInfoResponse { + /// The node version build number + pub build: String, + /// The server sub-version identifier, used as the network protocol user-agent + pub subversion: String, +} + +/// A hex-encoded [`ConsensusBranchId`] string. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)] +pub struct ProxyConsensusBranchIdHex( + #[serde(with = "hex")] pub zebra_chain::parameters::ConsensusBranchId, +); + +/// The activation status of a [`NetworkUpgrade`]. +#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum ProxyNetworkUpgradeStatus { + /// The network upgrade is currently active. + /// + /// Includes all network upgrades that have previously activated, + /// even if they are not the most recent network upgrade. + #[serde(rename = "active")] + Active, + + /// The network upgrade does not have an activation height. + #[serde(rename = "disabled")] + Disabled, + + /// The network upgrade has an activation height, but we haven't reached it yet. + #[serde(rename = "pending")] + Pending, +} + +/// Information about [`NetworkUpgrade`] activation. +#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ProxyNetworkUpgradeInfo { + /// Name of upgrade, string. + pub name: zebra_chain::parameters::NetworkUpgrade, + + /// Block height of activation, numeric. + #[serde(rename = "activationheight")] + pub activation_height: Height, + + /// Status of upgrade, string. + pub status: ProxyNetworkUpgradeStatus, +} + +/// The [`ConsensusBranchId`]s for the tip and the next block. +/// +/// These branch IDs are different when the next block is a network upgrade activation block. +#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ProxyTipConsensusBranch { + /// Branch ID used to validate the current chain tip, big-endian, hex-encoded. + #[serde(rename = "chaintip")] + pub chain_tip: ProxyConsensusBranchIdHex, + + /// Branch ID used to validate the next block, big-endian, hex-encoded. + #[serde(rename = "nextblock")] + pub next_block: ProxyConsensusBranchIdHex, +} + +/// Response to a `getblockchaininfo` RPC request. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_blockchain_info`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetBlockchainInfoResponse { + /// Current network name as defined in BIP70 (main, test, regtest) + pub chain: String, + + /// The current number of blocks processed in the server, numeric + pub blocks: Height, + + /// The hash of the currently best block, in big-endian order, hex-encoded + #[serde(rename = "bestblockhash", with = "hex")] + pub best_block_hash: block::Hash, + + /// If syncing, the estimated height of the chain, else the current best height, numeric. + /// + /// In Zebra, this is always the height estimate, so it might be a little inaccurate. + #[serde(rename = "estimatedheight")] + pub estimated_height: Height, + + /// Status of network upgrades + pub upgrades: IndexMap, + + /// Branch IDs of the current and upcoming consensus rules + pub consensus: ProxyTipConsensusBranch, } /// The transparent balance of a set of addresses. @@ -106,6 +218,12 @@ pub struct GetBalanceResponse { pub balance: u64, } +/// Contains the hex-encoded hash of the sent transaction. +/// +/// This is used for the output parameter of [`JsonRpcConnector::send_raw_transaction`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct SendTransactionResponse(#[serde(with = "hex")] transaction::Hash); + /// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. #[derive(Clone, Debug, Eq, PartialEq)] pub struct ProxySerializedBlock(SerializedBlock); @@ -196,6 +314,22 @@ pub enum GetBlockResponse { }, } +/// Contains the hex-encoded hash of the requested block. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_best_block_hash`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(transparent)] +pub struct BestBlockHashResponse(#[serde(with = "hex")] pub block::Hash); + +/// Vec of transaction ids, as a JSON array. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct TxidsResponse { + /// Vec of txids. + pub transactions: Vec, +} + /// Zingo-Proxy commitment tree structure replicating functionality in Zebra. /// /// A wrapper that contains either an Orchard or Sapling note commitment tree. @@ -203,7 +337,7 @@ pub enum GetBlockResponse { pub struct ProxyCommitments> { #[serde(with = "hex")] #[serde(rename = "finalState")] - final_state: Tree, + pub final_state: Tree, } impl + FromHex> ProxyCommitments { @@ -281,25 +415,24 @@ impl AsRef<[u8]> for ProxySerializedTree { pub struct GetTreestateResponse { /// The block hash corresponding to the treestate, hex-encoded. #[serde(with = "hex")] - hash: block::Hash, + pub hash: block::Hash, /// The block height corresponding to the treestate, numeric. - height: Height, + pub height: Height, /// Unix time when the block corresponding to the treestate was mined, /// numeric. /// /// UTC seconds since the Unix 1970-01-01 epoch. - time: u32, + pub time: u32, /// A treestate containing a Sapling note commitment tree, hex-encoded. #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - sapling: ProxyTreestate, + pub sapling: ProxyTreestate, /// A treestate containing an Orchard note commitment tree, hex-encoded. #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - orchard: ProxyTreestate, - // NOTE: CREATE PoxySerializedTree TO SIMPLIFY CODING>> + pub orchard: ProxyTreestate, } /// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. @@ -422,7 +555,7 @@ pub struct ProxyScript { /// Consensus-critical serialization uses [`ZcashSerialize`]. /// [`serde`]-based hex serialization must only be used for RPCs and testing. #[serde(with = "hex")] - script: Vec, + pub script: Vec, } impl ProxyScript { @@ -495,23 +628,23 @@ impl FromHex for ProxyScript { #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct GetUtxosResponse { /// The transparent address, base58check encoded - address: transparent::Address, + pub address: transparent::Address, /// The output txid, in big-endian order, hex-encoded #[serde(with = "hex")] - txid: transaction::Hash, + pub txid: transaction::Hash, /// The transparent output index, numeric #[serde(rename = "outputIndex")] - output_index: zebra_state::OutputIndex, + pub output_index: zebra_state::OutputIndex, /// The transparent output script, hex encoded #[serde(with = "hex")] - script: ProxyScript, + pub script: ProxyScript, /// The amount of zatoshis in the transparent output - satoshis: u64, + pub satoshis: u64, /// The block height, numeric. - height: Height, + pub height: Height, } diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 3fbe786..5dfd71f 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -1,5 +1,6 @@ //! Lightwallet service RPC implementations. +use hex::FromHex; use zcash_client_backend::proto::{ compact_formats::{CompactBlock, CompactTx}, service::{ @@ -9,8 +10,14 @@ use zcash_client_backend::proto::{ SendResponse, SubtreeRoot, TransparentAddressBlockFilter, TreeState, TxFilter, }, }; +use zebra_chain::block::Height; -use crate::{define_grpc_passthrough, primitives::ProxyClient}; +use crate::{ + define_grpc_passthrough, + jsonrpc::{connector::JsonRpcConnector, primitives::ProxyConsensusBranchIdHex}, + primitives::ProxyClient, + utils::get_build_info, +}; impl CompactTxStreamer for ProxyClient { // fn get_latest_block<'life0, 'async_trait>( @@ -159,12 +166,78 @@ impl CompactTxStreamer for ProxyClient { ) -> tonic::Streaming ); - define_grpc_passthrough!( - fn get_lightd_info( - &self, - request: tonic::Request, - ) -> LightdInfo - ); + // define_grpc_passthrough!( + // fn get_lightd_info( + // &self, + // request: tonic::Request, + // ) -> LightdInfo + // ); + + fn get_lightd_info<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("received call of get_lightd_info"); + Box::pin(async { + let zebrad_client = JsonRpcConnector::new(self.zebrad_uri.clone()); + + let zebra_info = zebrad_client + .get_info() + .await + .map_err(|e| e.to_grpc_status())?; + let blockchain_info = zebrad_client + .get_blockchain_info() + .await + .map_err(|e| e.to_grpc_status())?; + + println!("zebra_info: {:#?}", zebra_info); + println!("blockchain_info: {:#?}", blockchain_info); + + let sapling_id_str = "76b809bb"; + let sapling_id = ProxyConsensusBranchIdHex( + zebra_chain::parameters::ConsensusBranchId::from_hex(sapling_id_str).unwrap(), + ); + let sapling_height = blockchain_info + .upgrades + .get(&sapling_id) + .map_or(Height(1), |sapling_json| sapling_json.activation_height); + + let (git_commit, branch, build_date, build_user, version) = get_build_info(); + + let lightd_info = LightdInfo { + version, + vendor: "ZingoLabs - Zingo-Proxy".to_string(), + taddr_support: true, + chain_name: blockchain_info.chain, + sapling_activation_height: sapling_height.0 as u64, + consensus_branch_id: blockchain_info.consensus.chain_tip.0.to_string(), + block_height: blockchain_info.blocks.0 as u64, + git_commit, + branch, + build_date, + build_user, + estimated_height: blockchain_info.estimated_height.0 as u64, + zcashd_build: zebra_info.build, + zcashd_subversion: zebra_info.subversion, + }; + + Ok(tonic::Response::new(lightd_info)) + }) + } define_grpc_passthrough!( fn ping( diff --git a/zingo-rpc/src/utils.rs b/zingo-rpc/src/utils.rs index bb2173a..1bbc25b 100644 --- a/zingo-rpc/src/utils.rs +++ b/zingo-rpc/src/utils.rs @@ -35,3 +35,13 @@ macro_rules! define_grpc_passthrough { } }; } + +/// Returns build info for Zingo-Proxy. +pub fn get_build_info() -> (String, String, String, String, String) { + let commit_hash = env!("GIT_COMMIT").to_string(); + let branch = env!("BRANCH").to_string(); + let build_date = env!("BUILD_DATE").to_string(); + let build_user = env!("BUILD_USER").to_string(); + let version = env!("VERSION").to_string(); + (commit_hash, branch, build_date, build_user, version) +} From cd37ea6e0ba533b3037b56ab1f00d883bd5cb502 Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 20 May 2024 21:14:56 +0100 Subject: [PATCH 07/40] get_lightd_info implemented - working on testnet, not in tests --- zingo-rpc/src/rpc/service.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 5dfd71f..7c6b151 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -195,6 +195,8 @@ impl CompactTxStreamer for ProxyClient { Box::pin(async { let zebrad_client = JsonRpcConnector::new(self.zebrad_uri.clone()); + println!("Sending RPC to: {}", self.zebrad_uri.clone()); + let zebra_info = zebrad_client .get_info() .await From ee402a006c35eafc6a5d7417722717943d0ec7cc Mon Sep 17 00:00:00 2001 From: idky137 Date: Wed, 22 May 2024 19:34:43 +0100 Subject: [PATCH 08/40] connects to zebra or zcash, doesnt run unless one is running.. fix this --- Cargo.lock | 1 + integration-tests/tests/integrations.rs | 13 +- zingo-proxyd/src/bin/zingoproxyd.rs | 2 +- zingo-proxyd/src/nym_server.rs | 21 ++- zingo-proxyd/src/proxy.rs | 17 ++- zingo-proxyd/src/server.rs | 23 ++-- zingo-rpc/Cargo.toml | 2 + zingo-rpc/src/jsonrpc/connector.rs | 164 +++++++++++++++++++++--- zingo-rpc/src/jsonrpc/primitives.rs | 1 + zingo-rpc/src/nym/utils.rs | 4 +- zingo-rpc/src/rpc/nymwalletservice.rs | 22 +++- zingo-rpc/src/rpc/service.rs | 34 ++--- zingo-rpc/src/utils.rs | 2 +- zingoproxy-testutils/src/lib.rs | 25 ++-- 14 files changed, 256 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8006ee..5f0747d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8542,6 +8542,7 @@ dependencies = [ name = "zingo-rpc" version = "0.1.0" dependencies = [ + "base64 0.13.1", "bytes 1.6.0", "hex 0.4.3", "http", diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 12aa5e4..4351eae 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -18,7 +18,7 @@ mod proxy { TestManager::launch(online.clone()).await; println!( - "Attempting to connect to GRPC server at URI: {}", + "@zingoproxytest: Attempting to connect to GRPC server at URI: {}.", test_manager.get_proxy_uri() ); @@ -32,7 +32,10 @@ mod proxy { .await .expect("Failed to retrieve lightd info from GRPC server"); - println!("{:#?}", lightd_info.into_inner()); + println!( + "@zingoproxytest: Lightd_info response:\n{:#?}.", + lightd_info.into_inner() + ); drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), @@ -53,7 +56,7 @@ mod proxy { zingo_client.do_sync(false).await.unwrap(); println!( - "zingo_client balance: {:#?}", + "@zingoproxytest: zingo_client balance: {:#?}.", zingo_client.do_balance().await ); @@ -68,7 +71,7 @@ mod proxy { zingo_client.do_sync(false).await.unwrap(); println!( - "zingo_client balance: {:#?}", + "@zingoproxytest: zingo_client balance: {:#?}.", zingo_client.do_balance().await ); @@ -87,7 +90,7 @@ mod proxy { } mod nym { - // TODO: Build nym encanhed zingolib version using zingo-rpc::walletrpc::service. + // TODO: Build nym enhanced zingolib version using zingo-rpc::walletrpc::service. } mod darkside { diff --git a/zingo-proxyd/src/bin/zingoproxyd.rs b/zingo-proxyd/src/bin/zingoproxyd.rs index 63d8b2f..0880b0a 100644 --- a/zingo-proxyd/src/bin/zingoproxyd.rs +++ b/zingo-proxyd/src/bin/zingoproxyd.rs @@ -17,7 +17,7 @@ async fn main() { let online = Arc::new(AtomicBool::new(true)); let online_ctrlc = online.clone(); ctrlc::set_handler(move || { - println!("Received Ctrl+C, exiting."); + println!("@zingoproxyd: Received Ctrl+C, exiting."); online_ctrlc.store(false, Ordering::SeqCst); process::exit(0); }) diff --git a/zingo-proxyd/src/nym_server.rs b/zingo-proxyd/src/nym_server.rs index 38dbfa9..7d85128 100644 --- a/zingo-proxyd/src/nym_server.rs +++ b/zingo-proxyd/src/nym_server.rs @@ -1,4 +1,8 @@ //! Nym-gRPC server implementation. +//! +//! TODO: - Add NymServerError error type and rewrite functions to return >, propagating internal errors. Include NymClientError from zingo-rpc::nym::utils. +//! - Update NymServer to handle all service RPCs (currently only accepts send_command). [Return "Not Yet Implemented" for unimplemented RPC's?] +//! - Update NymServer to handle multiple requests, from multiple clients, simultaniously. [Combine with zingoproxyd "queue" logic when implemented?] use std::sync::{ atomic::{AtomicBool, Ordering}, @@ -39,8 +43,14 @@ impl NymServer { .unwrap(); //print request for testing - println!("request received: {:?}", &request_vu8[..]); - println!("request length: {}", &request_vu8[..].len()); + println!( + "@zingoproxyd[nym]: request received: {:?}", + &request_vu8[..] + ); + println!( + "@zingoproxyd[nym]: request length: {}", + &request_vu8[..].len() + ); let request = RawTransaction::decode(&request_vu8[..]).unwrap(); let response = NymClient::nym_send_transaction(&request).await.unwrap(); @@ -48,8 +58,11 @@ impl NymServer { response.encode(&mut response_vu8).unwrap(); //print response for testing - println!("response sent: {:?}", &response_vu8[..]); - println!("response length: {}", &response_vu8[..].len()); + println!("@zingoproxyd[nym]: response sent: {:?}", &response_vu8[..]); + println!( + "@zingoproxyd[nym]: response length: {}", + &response_vu8[..].len() + ); let return_recipient = AnonymousSenderTag::try_from_base58_string( request_in[0].sender_tag.unwrap().to_base58_string(), diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 49049ec..e5a1b46 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -1,4 +1,7 @@ //! Zingo-Proxy server implementation. +//! +//! TODO: - Add ProxyServerError error type and rewrite functions to return >, propagating internal errors. +//! - Update spawn_server and nym_spawn to return > and > and use here. use crate::{nym_server::NymServer, server::spawn_server}; use zingo_rpc::primitives::NymClient; @@ -7,8 +10,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use tokio::task::JoinHandle; -use tonic::transport::Error as TonicError; - /// Launches test Zingo_Proxy server. pub async fn spawn_proxy( proxy_port: &u16, @@ -16,20 +17,28 @@ pub async fn spawn_proxy( zebrad_port: &u16, nym_conf_path: &str, online: Arc, -) -> (Vec>>, Option) { +) -> ( + Vec>>, + Option, +) { let mut handles = vec![]; let nym_addr_out: Option; - println!("Loading Zing-Proxy.."); + println!("@zingoproxyd: Launching Zingo-Proxy.."); #[cfg(not(feature = "nym_poc"))] { + println!("@zingoproxyd[nym]: Launching Nym Server.."); + let nym_server: NymServer = NymServer(NymClient::nym_spawn(nym_conf_path).await); nym_addr_out = Some(nym_server.0 .0.nym_address().to_string()); let nym_proxy_handle = nym_server.serve(online.clone()).await; handles.push(nym_proxy_handle); } + + println!("@zingoproxyd: Launching gRPC Server.."); + let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online).await; handles.push(proxy_handle); diff --git a/zingo-proxyd/src/server.rs b/zingo-proxyd/src/server.rs index 3d4d575..1eeffc3 100644 --- a/zingo-proxyd/src/server.rs +++ b/zingo-proxyd/src/server.rs @@ -1,4 +1,7 @@ //! gRPC server implementation. +//! +//! TODO: - Add GrpcServerError error type and rewrite functions to return >, propagating internal errors. +//! - Add user and password as fields of ProxyClient and use here. use std::{ net::{Ipv4Addr, SocketAddr}, @@ -10,7 +13,7 @@ use std::{ use http::Uri; use zcash_client_backend::proto::service::compact_tx_streamer_server::CompactTxStreamerServer; -use zingo_rpc::primitives::ProxyClient; +use zingo_rpc::{jsonrpc::connector::JsonRpcConnector, primitives::ProxyClient}; /// Configuration data for gRPC server. pub struct ProxyServer(pub ProxyClient); @@ -25,7 +28,7 @@ impl ProxyServer { tokio::task::spawn(async move { let svc = CompactTxStreamerServer::new(self.0); let sockaddr = SocketAddr::new(std::net::IpAddr::V4(Ipv4Addr::LOCALHOST), port.into()); - println!("GRPC server listening on: {sockaddr}"); + println!("@zingoproxyd: GRPC server listening on: {sockaddr}."); while online.load(Ordering::SeqCst) { let server = tonic::transport::Server::builder() .add_service(svc.clone()) @@ -63,12 +66,16 @@ pub async fn spawn_server( .path_and_query("/") .build() .unwrap(); - let zebra_uri = Uri::builder() - .scheme("http") - .authority(format!("localhost:{zebrad_port}")) - .path_and_query("/") - .build() - .unwrap(); + + // TODO Add user and password as fields of ProxyClient and use here. + let zebra_uri = JsonRpcConnector::test_and_return_uri( + zebrad_port, + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .unwrap(); + let server = ProxyServer::new(lwd_uri, zebra_uri); server.serve(proxy_port.clone(), online) } diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 2eebebc..04c680d 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -46,3 +46,5 @@ serde_json = { version = "1.0.117", features = ["preserve_order"] } jsonrpc-core = "18.0.0" indexmap = { version = "2.2.6", features = ["serde"] } + +base64 = "0.13.0" diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 479c824..ee9ea8f 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -1,4 +1,6 @@ //! JsonRPC client implementation. +//! +//! TODO: - Add option for http connector. use http::Uri; use hyper::{http, Body, Client, Request}; @@ -26,7 +28,7 @@ struct RpcRequest { #[derive(Serialize, Deserialize, Debug)] struct RpcResponse { id: i32, - jsonrpc: String, + jsonrpc: Option, result: T, error: Option, } @@ -67,7 +69,7 @@ impl JsonRpcConnectorError { /// Maps JsonRpcConnectorError to tonic::Status pub fn to_grpc_status(&self) -> tonic::Status { - eprintln!("Error occurred: {}", self); + eprintln!("@zingoproxyd: Error occurred: {}.", self); if let Some(source) = &self.source { if source.is::() { @@ -127,19 +129,68 @@ impl From for JsonRpcConnectorError { } } +impl From for JsonRpcConnectorError { + fn from(err: std::string::FromUtf8Error) -> Self { + JsonRpcConnectorError::new_with_source("UTF-8 Conversion Error", Box::new(err)) + } +} + +impl From for JsonRpcConnectorError { + fn from(err: tokio::time::error::Elapsed) -> Self { + JsonRpcConnectorError::new_with_source("Request Timeout Error", Box::new(err)) + } +} + +impl From for tonic::Status { + fn from(err: JsonRpcConnectorError) -> Self { + tonic::Status::internal(err.to_string()) + } +} + /// JsonRPC Client config data. pub struct JsonRpcConnector { uri: http::Uri, id_counter: AtomicI32, + user: Option, + password: Option, } impl JsonRpcConnector { - /// Returns a new JsonRpcConnector instance. - pub fn new(uri: http::Uri) -> Self { - Self { + /// Returns a new JsonRpcConnector instance, tests uri and returns error if connection is not established. + pub async fn new( + uri: http::Uri, + user: Option, + password: Option, + ) -> Result { + if let Some(port) = uri.port_u16() { + let checked_uri = + JsonRpcConnector::test_and_return_uri(&port, user.clone(), password.clone()) + .await?; + Ok(Self { + uri: checked_uri, + id_counter: AtomicI32::new(0), + user, + password, + }) + } else { + Err(JsonRpcConnectorError::new("No port number found in URI.")) + } + } + + /// Returns a new JsonRpcConnector instance from zebrad uri port, tests port and returns error if connection not established. + pub async fn new_from_port( + port: &u16, + user: Option, + password: Option, + ) -> Result { + let uri = + JsonRpcConnector::test_and_return_uri(port, user.clone(), password.clone()).await?; + Ok(Self { uri, id_counter: AtomicI32::new(0), - } + user, + password, + }) } /// Returns the uri the JsonRpcConnector is configured to send requests to. @@ -155,6 +206,14 @@ impl JsonRpcConnector { ) -> Result { let id = self.id_counter.fetch_add(1, Ordering::SeqCst); let client = Client::builder().build(HttpsConnector::new()); + let mut request_builder = Request::builder() + .method("POST") + .uri(self.uri.clone()) + .header("Content-Type", "application/json"); + if let (Some(user), Some(password)) = (&self.user, &self.password) { + let auth = base64::encode(format!("{}:{}", user, password)); + request_builder = request_builder.header("Authorization", format!("Basic {}", auth)); + } let req = RpcRequest { jsonrpc: "2.0".to_string(), method: method.to_string(), @@ -164,10 +223,7 @@ impl JsonRpcConnector { let request_body = serde_json::to_string(&req).map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) })?; - let request = Request::builder() - .method("POST") - .uri(self.uri.clone()) - .header("Content-Type", "application/json") + let request = request_builder .body(Body::from(request_body)) .map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) @@ -180,20 +236,9 @@ impl JsonRpcConnector { .map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) })?; - - // Test Code!!! - let body_str = String::from_utf8(body_bytes.to_vec()).map_err(|e| { - JsonRpcConnectorError::new_with_source( - "Failed to convert response body to string", - Box::new(e), - ) - })?; - println!("Raw response body: {}", body_str); - let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) })?; - match response.error { Some(error) => Err(JsonRpcConnectorError::new(format!( "RPC Error {}: {}", @@ -203,6 +248,83 @@ impl JsonRpcConnector { } } + /// Tests connection with zebrad / zebrad. + pub async fn test_connection( + uri: Uri, + user: Option, + password: Option, + ) -> Result<(), JsonRpcConnectorError> { + let client = Client::builder().build::<_, Body>(HttpsConnector::new()); + + let user = user.unwrap_or_else(|| "xxxxxx".to_string()); + let password = password.unwrap_or_else(|| "xxxxxx".to_string()); + let encoded_auth = base64::encode(format!("{}:{}", user, password)); + + let request = Request::builder() + .method("POST") + .uri(uri.clone()) + .header("Content-Type", "application/json") + .header("Authorization", format!("Basic {}", encoded_auth)) + .body(Body::from( + r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, + )) + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + })?; + let response = + tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) + })??; + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) + })?; + let _response: RpcResponse = serde_json::from_slice(&body_bytes) + .map_err(|e| { + JsonRpcConnectorError::new_with_source( + "Failed to deserialize response", + Box::new(e), + ) + })?; + Ok(()) + } + + /// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. + pub async fn test_and_return_uri( + port: &u16, + user: Option, + password: Option, + ) -> Result { + let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) + })?; + let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) + })?; + + match JsonRpcConnector::test_connection(ipv4_uri.clone(), user.clone(), password.clone()) + .await + { + Ok(_) => { + println!("@zingoproxyd: Connected to node using IPv4."); + return Ok(ipv4_uri); + } + Err(e_ipv4) => { + eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying on IPv6.", e_ipv4); + match JsonRpcConnector::test_connection(ipv6_uri.clone(), user, password).await { + Ok(_) => { + println!("@zingoproxyd: Connected to node using IPv6."); + Ok(ipv6_uri) + } + Err(e_ipv6) => Err(e_ipv6), + } + } + } + } + /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. /// /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 3a79dbe..84b316c 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -335,6 +335,7 @@ pub struct TxidsResponse { /// A wrapper that contains either an Orchard or Sapling note commitment tree. #[derive(Clone, Debug, Eq, PartialEq, Serialize)] pub struct ProxyCommitments> { + /// Commitment tree state #[serde(with = "hex")] #[serde(rename = "finalState")] pub final_state: Tree, diff --git a/zingo-rpc/src/nym/utils.rs b/zingo-rpc/src/nym/utils.rs index f97ead9..6c4df59 100644 --- a/zingo-rpc/src/nym/utils.rs +++ b/zingo-rpc/src/nym/utils.rs @@ -1,4 +1,6 @@ //! Utility functions for Nym-Proxy +//! +//! TODO: - Add NymClientError error type and rewrite functions to return >. use nym_sdk::mixnet::{ MixnetClientBuilder, MixnetMessageSender, Recipient, ReconstructedMessage, StoragePaths, @@ -23,7 +25,7 @@ impl NymClient { .unwrap(); let nym_addr = client.nym_address().to_string(); - println!("Nym server listening on: {nym_addr}"); + println!("@zingoproxyd[nym]: Nym server listening on: {nym_addr}."); Self(client) } diff --git a/zingo-rpc/src/rpc/nymwalletservice.rs b/zingo-rpc/src/rpc/nymwalletservice.rs index cf798f0..0be38ed 100644 --- a/zingo-rpc/src/rpc/nymwalletservice.rs +++ b/zingo-rpc/src/rpc/nymwalletservice.rs @@ -57,7 +57,7 @@ impl CompactTxStreamer for ProxyClient { &self, request: Request, ) -> Result, Status> { - println!("Received call to send_transaction"); + println!("@zingoproxyd[nym]: Received call of send_transaction."); //serialize RawTransaction let serialized_request = match serialize_request(&request.into_inner()).await { @@ -71,8 +71,14 @@ impl CompactTxStreamer for ProxyClient { }; //print request for testing: - println!("request sent: {:?}", serialized_request); - println!("request length: {}", serialized_request.len()); + println!( + "@zingoproxyd[nym][TEST]: Request sent: {:?}.", + serialized_request + ); + println!( + "@zingoproxyd[nym][TEST]: Request length: {}.", + serialized_request.len() + ); // -- forward request over nym let args: Vec = env::args().collect(); @@ -86,8 +92,14 @@ impl CompactTxStreamer for ProxyClient { client.nym_close().await; //print response for testing - println!("response received: {:?}", response_data); - println!("response length: {}", response_data.len()); + println!( + "@zingoproxyd[nym][TEST]: Response received: {:?}.", + response_data + ); + println!( + "@zingoproxyd[nym][TEST]: Response length: {}.", + response_data.len() + ); //deserialize SendResponse let response: SendResponse = match deserialize_response(response_data.as_slice()).await { diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 7c6b151..b55a57e 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -38,7 +38,7 @@ impl CompactTxStreamer for ProxyClient { // 'life0: 'async_trait, // Self: 'async_trait, // { - // println!("received call of get_latest_block"); + // println!("@zingoproxyd[nym]: Received call of get_latest_block."); // Box::pin(async { // let zebrad_info = ::zingo_netutils::GrpcConnector::new(self.zebrad_uri.clone()) // .get_client() @@ -46,7 +46,6 @@ impl CompactTxStreamer for ProxyClient { // todo!("get_latest_block not yet implemented") // }) // } - define_grpc_passthrough!( fn get_latest_block( &self, @@ -166,13 +165,6 @@ impl CompactTxStreamer for ProxyClient { ) -> tonic::Streaming ); - // define_grpc_passthrough!( - // fn get_lightd_info( - // &self, - // request: tonic::Request, - // ) -> LightdInfo - // ); - fn get_lightd_info<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -191,11 +183,16 @@ impl CompactTxStreamer for ProxyClient { 'life0: 'async_trait, Self: 'async_trait, { - println!("received call of get_lightd_info"); + println!("@zingoproxyd: Received call of get_lightd_info."); + // TODO Add user and password as fields of ProxyClient and use here. Box::pin(async { - let zebrad_client = JsonRpcConnector::new(self.zebrad_uri.clone()); - - println!("Sending RPC to: {}", self.zebrad_uri.clone()); + let zebrad_client = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .unwrap(); let zebra_info = zebrad_client .get_info() @@ -206,9 +203,6 @@ impl CompactTxStreamer for ProxyClient { .await .map_err(|e| e.to_grpc_status())?; - println!("zebra_info: {:#?}", zebra_info); - println!("blockchain_info: {:#?}", blockchain_info); - let sapling_id_str = "76b809bb"; let sapling_id = ProxyConsensusBranchIdHex( zebra_chain::parameters::ConsensusBranchId::from_hex(sapling_id_str).unwrap(), @@ -222,7 +216,7 @@ impl CompactTxStreamer for ProxyClient { let lightd_info = LightdInfo { version, - vendor: "ZingoLabs - Zingo-Proxy".to_string(), + vendor: "ZingoLabs ZingoProxyD".to_string(), taddr_support: true, chain_name: blockchain_info.chain, sapling_activation_height: sapling_height.0 as u64, @@ -240,6 +234,12 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(lightd_info)) }) } + // define_grpc_passthrough!( + // fn get_lightd_info( + // &self, + // request: tonic::Request, + // ) -> LightdInfo + // ); define_grpc_passthrough!( fn ping( diff --git a/zingo-rpc/src/utils.rs b/zingo-rpc/src/utils.rs index 1bbc25b..e0fbdee 100644 --- a/zingo-rpc/src/utils.rs +++ b/zingo-rpc/src/utils.rs @@ -23,7 +23,7 @@ macro_rules! define_grpc_passthrough { 'life0: 'async_trait, Self: 'async_trait, { - println!("received call of {}", stringify!($name)); + println!("@zingoproxyd: Received call of {}.", stringify!($name)); Box::pin(async { ::zingo_netutils::GrpcConnector::new($self.lightwalletd_uri.clone()) .get_client() diff --git a/zingoproxy-testutils/src/lib.rs b/zingoproxy-testutils/src/lib.rs index e2a6c5f..167afaa 100644 --- a/zingoproxy-testutils/src/lib.rs +++ b/zingoproxy-testutils/src/lib.rs @@ -113,14 +113,17 @@ pub async fn drop_test_manager( if let Some(ref path) = temp_conf_path { if let Err(e) = std::fs::remove_dir_all(&path) { eprintln!( - "Failed to delete temporary regtest configuration directory: {:?}", + "@zingoproxyd: Failed to delete temporary regtest configuration directory: {:?}.", e ); } } if let Some(ref path) = Some(temp_wallet_path) { if let Err(e) = std::fs::remove_dir_all(&path) { - eprintln!("Failed to delete temporary directory: {:?}", e); + eprintln!( + "@zingoproxyd: Failed to delete temporary directory: {:?}.", + e + ); } } } @@ -150,14 +153,17 @@ fn set_custom_drops( if let Some(ref path) = temp_conf_path_panic { if let Err(e) = std::fs::remove_dir_all(&path) { eprintln!( - "Failed to delete temporary regtest config directory: {:?}", + "@zingoproxyd: Failed to delete temporary regtest config directory: {:?}.", e ); } } if let Some(ref path) = temp_wallet_path_panic { if let Err(e) = std::fs::remove_dir_all(&path) { - eprintln!("Failed to delete temporary wallet directory: {:?}", e); + eprintln!( + "@zingoproxyd: Failed to delete temporary wallet directory: {:?}.", + e + ); } } std::process::exit(0); @@ -165,19 +171,22 @@ fn set_custom_drops( CTRL_C_ONCE.call_once(|| { ctrlc::set_handler(move || { - println!("Received Ctrl+C, exiting."); + println!("@zingoproxyd: Received Ctrl+C, exiting."); online_ctrlc.store(false, std::sync::atomic::Ordering::SeqCst); if let Some(ref path) = temp_conf_path_ctrlc { if let Err(e) = std::fs::remove_dir_all(&path) { eprintln!( - "Failed to delete temporary regtest config directory: {:?}", + "@zingoproxyd: Failed to delete temporary regtest config directory: {:?}.", e ); } } if let Some(ref path) = temp_wallet_path_ctrlc { if let Err(e) = std::fs::remove_dir_all(&path) { - eprintln!("Failed to delete temporary wallet directory: {:?}", e); + eprintln!( + "@zingoproxyd: Failed to delete temporary wallet directory: {:?}.", + e + ); } } std::process::exit(0); @@ -219,7 +228,7 @@ fn write_zcash_conf(dir: &std::path::Path, rpcport: u16) -> Result<(), Box Date: Thu, 23 May 2024 17:17:27 +0100 Subject: [PATCH 09/40] added startup connection checker --- integration-tests/tests/integrations.rs | 8 +- zingo-proxyd/src/proxy.rs | 16 +- zingo-proxyd/src/server.rs | 4 +- zingo-rpc/src/jsonrpc/connector.rs | 343 ++++++++++++++++-------- zingo-rpc/src/rpc/service.rs | 3 +- zingoproxy-testutils/src/lib.rs | 24 +- 6 files changed, 269 insertions(+), 129 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 4351eae..cb8f6a9 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -12,7 +12,7 @@ mod proxy { use super::*; #[tokio::test] - async fn connect_to_lwd_get_info() { + async fn proxy_connect_to_node_get_info() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; @@ -46,7 +46,7 @@ mod proxy { } #[tokio::test] - async fn send_over_proxy() { + async fn proxy_send_transaction() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; @@ -56,7 +56,7 @@ mod proxy { zingo_client.do_sync(false).await.unwrap(); println!( - "@zingoproxytest: zingo_client balance: {:#?}.", + "@zingoproxytest: zingo_client balance: \n{:#?}.", zingo_client.do_balance().await ); @@ -71,7 +71,7 @@ mod proxy { zingo_client.do_sync(false).await.unwrap(); println!( - "@zingoproxytest: zingo_client balance: {:#?}.", + "@zingoproxytest: zingo_client balance: \n{:#?}.", zingo_client.do_balance().await ); diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index e5a1b46..4f83ef8 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -24,26 +24,22 @@ pub async fn spawn_proxy( let mut handles = vec![]; let nym_addr_out: Option; - println!("@zingoproxyd: Launching Zingo-Proxy.."); + println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); + let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; + handles.push(proxy_handle); + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; #[cfg(not(feature = "nym_poc"))] { println!("@zingoproxyd[nym]: Launching Nym Server.."); - let nym_server: NymServer = NymServer(NymClient::nym_spawn(nym_conf_path).await); nym_addr_out = Some(nym_server.0 .0.nym_address().to_string()); - let nym_proxy_handle = nym_server.serve(online.clone()).await; + let nym_proxy_handle = nym_server.serve(online).await; handles.push(nym_proxy_handle); + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; } - println!("@zingoproxyd: Launching gRPC Server.."); - - let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online).await; - handles.push(proxy_handle); - - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; - #[cfg(feature = "nym_poc")] { nym_addr_out = None; diff --git a/zingo-proxyd/src/server.rs b/zingo-proxyd/src/server.rs index 1eeffc3..79bcfa4 100644 --- a/zingo-proxyd/src/server.rs +++ b/zingo-proxyd/src/server.rs @@ -13,7 +13,7 @@ use std::{ use http::Uri; use zcash_client_backend::proto::service::compact_tx_streamer_server::CompactTxStreamerServer; -use zingo_rpc::{jsonrpc::connector::JsonRpcConnector, primitives::ProxyClient}; +use zingo_rpc::{jsonrpc::connector::test_node_and_return_uri, primitives::ProxyClient}; /// Configuration data for gRPC server. pub struct ProxyServer(pub ProxyClient); @@ -68,7 +68,7 @@ pub async fn spawn_server( .unwrap(); // TODO Add user and password as fields of ProxyClient and use here. - let zebra_uri = JsonRpcConnector::test_and_return_uri( + let zebra_uri = test_node_and_return_uri( zebrad_port, Some("xxxxxx".to_string()), Some("xxxxxx".to_string()), diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index ee9ea8f..bc457be 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -157,42 +157,31 @@ pub struct JsonRpcConnector { impl JsonRpcConnector { /// Returns a new JsonRpcConnector instance, tests uri and returns error if connection is not established. - pub async fn new( - uri: http::Uri, - user: Option, - password: Option, - ) -> Result { - if let Some(port) = uri.port_u16() { - let checked_uri = - JsonRpcConnector::test_and_return_uri(&port, user.clone(), password.clone()) - .await?; - Ok(Self { - uri: checked_uri, - id_counter: AtomicI32::new(0), - user, - password, - }) - } else { - Err(JsonRpcConnectorError::new("No port number found in URI.")) - } - } - - /// Returns a new JsonRpcConnector instance from zebrad uri port, tests port and returns error if connection not established. - pub async fn new_from_port( - port: &u16, - user: Option, - password: Option, - ) -> Result { - let uri = - JsonRpcConnector::test_and_return_uri(port, user.clone(), password.clone()).await?; - Ok(Self { + pub async fn new(uri: http::Uri, user: Option, password: Option) -> Self { + Self { uri, id_counter: AtomicI32::new(0), user, password, - }) + } } + // /// Returns a new JsonRpcConnector instance from zebrad uri port, tests port and returns error if connection not established. + // pub async fn new_from_port( + // port: &u16, + // user: Option, + // password: Option, + // ) -> Result { + // let uri = + // JsonRpcConnector::test_and_return_uri(port, user.clone(), password.clone()).await?; + // Ok(Self { + // uri, + // id_counter: AtomicI32::new(0), + // user, + // password, + // }) + // } + /// Returns the uri the JsonRpcConnector is configured to send requests to. pub fn uri(&self) -> &Uri { &self.uri @@ -248,82 +237,134 @@ impl JsonRpcConnector { } } - /// Tests connection with zebrad / zebrad. - pub async fn test_connection( - uri: Uri, - user: Option, - password: Option, - ) -> Result<(), JsonRpcConnectorError> { - let client = Client::builder().build::<_, Body>(HttpsConnector::new()); - - let user = user.unwrap_or_else(|| "xxxxxx".to_string()); - let password = password.unwrap_or_else(|| "xxxxxx".to_string()); - let encoded_auth = base64::encode(format!("{}:{}", user, password)); - - let request = Request::builder() - .method("POST") - .uri(uri.clone()) - .header("Content-Type", "application/json") - .header("Authorization", format!("Basic {}", encoded_auth)) - .body(Body::from( - r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, - )) - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) - })?; - let response = - tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) - .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) - })??; - let body_bytes = hyper::body::to_bytes(response.into_body()) - .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) - })?; - let _response: RpcResponse = serde_json::from_slice(&body_bytes) - .map_err(|e| { - JsonRpcConnectorError::new_with_source( - "Failed to deserialize response", - Box::new(e), - ) - })?; - Ok(()) - } - - /// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. - pub async fn test_and_return_uri( - port: &u16, - user: Option, - password: Option, - ) -> Result { - let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) - })?; - let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) - })?; - - match JsonRpcConnector::test_connection(ipv4_uri.clone(), user.clone(), password.clone()) - .await - { - Ok(_) => { - println!("@zingoproxyd: Connected to node using IPv4."); - return Ok(ipv4_uri); - } - Err(e_ipv4) => { - eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying on IPv6.", e_ipv4); - match JsonRpcConnector::test_connection(ipv6_uri.clone(), user, password).await { - Ok(_) => { - println!("@zingoproxyd: Connected to node using IPv6."); - Ok(ipv6_uri) - } - Err(e_ipv6) => Err(e_ipv6), - } - } - } - } + // /// Tests connection with zebrad / zebrad. + // pub async fn test_connection( + // uri: Uri, + // user: Option, + // password: Option, + // ) -> Result<(), JsonRpcConnectorError> { + // let client = Client::builder().build::<_, Body>(HttpsConnector::new()); + + // let user = user.unwrap_or_else(|| "xxxxxx".to_string()); + // let password = password.unwrap_or_else(|| "xxxxxx".to_string()); + // let encoded_auth = base64::encode(format!("{}:{}", user, password)); + + // let request = Request::builder() + // .method("POST") + // .uri(uri.clone()) + // .header("Content-Type", "application/json") + // .header("Authorization", format!("Basic {}", encoded_auth)) + // .body(Body::from( + // r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, + // )) + // .map_err(|e| { + // JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + // })?; + // let response = + // tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) + // .await + // .map_err(|e| { + // JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) + // })??; + // let body_bytes = hyper::body::to_bytes(response.into_body()) + // .await + // .map_err(|e| { + // JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) + // })?; + // let _response: RpcResponse = serde_json::from_slice(&body_bytes) + // .map_err(|e| { + // JsonRpcConnectorError::new_with_source( + // "Failed to deserialize response", + // Box::new(e), + // ) + // })?; + // Ok(()) + // } + + // /// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. + // // pub async fn test_and_return_uri( + // // port: &u16, + // // user: Option, + // // password: Option, + // // ) -> Result { + // // let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { + // // JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) + // // })?; + // // let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { + // // JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) + // // })?; + + // // match JsonRpcConnector::test_connection(ipv4_uri.clone(), user.clone(), password.clone()) + // // .await + // // { + // // Ok(_) => { + // // println!("@zingoproxyd: Connected to node using IPv4."); + // // return Ok(ipv4_uri); + // // } + // // Err(e_ipv4) => { + // // eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying on IPv6.", e_ipv4); + // // match JsonRpcConnector::test_connection(ipv6_uri.clone(), user, password).await { + // // Ok(_) => { + // // println!("@zingoproxyd: Connected to node using IPv6."); + // // Ok(ipv6_uri) + // // } + // // Err(e_ipv6) => Err(e_ipv6), + // // } + // // } + // // } + // // } + // pub async fn test_and_return_uri( + // port: &u16, + // user: Option, + // password: Option, + // ) -> Result { + // let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { + // JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) + // })?; + // let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { + // JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) + // })?; + + // for _ in 0..3 { + // println!("@zingoproxyd: Trying connection on IPv4."); + // match JsonRpcConnector::test_connection( + // ipv4_uri.clone(), + // user.clone(), + // password.clone(), + // ) + // .await + // { + // Ok(_) => { + // println!("@zingoproxyd: Connected to node using IPv4."); + // return Ok(ipv4_uri); + // } + // Err(e_ipv4) => { + // eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying connection on IPv6.", e_ipv4); + // match JsonRpcConnector::test_connection( + // ipv6_uri.clone(), + // user.clone(), + // password.clone(), + // ) + // .await + // { + // Ok(_) => { + // println!("@zingoproxyd: Connected to node using IPv6."); + // return Ok(ipv6_uri); + // } + // Err(e_ipv6) => { + // eprintln!("@zingoproxyd: Failed to connect to node using IPv6 with error: {}.\n@zingoproxyd: Connection not established. Retrying..", e_ipv6); + // tokio::time::sleep(std::time::Duration::from_secs(3)).await; + // } + // } + // } + // } + // } + + // eprintln!("@zingoproxyd: Could not establish connection with node. Please check config and confirm node is listening at the correct address."); + // Err(JsonRpcConnectorError::new( + // "Could not establish connection with node. Exiting..", + // )) + // } /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. /// @@ -536,3 +577,93 @@ impl JsonRpcConnector { self.send_request("getaddressutxos", params).await } } + +/// Tests connection with zebrad / zebrad. +pub async fn test_node_connection( + uri: Uri, + user: Option, + password: Option, +) -> Result<(), JsonRpcConnectorError> { + let client = Client::builder().build::<_, Body>(HttpsConnector::new()); + + let user = user.unwrap_or_else(|| "xxxxxx".to_string()); + let password = password.unwrap_or_else(|| "xxxxxx".to_string()); + let encoded_auth = base64::encode(format!("{}:{}", user, password)); + + let request = Request::builder() + .method("POST") + .uri(uri.clone()) + .header("Content-Type", "application/json") + .header("Authorization", format!("Basic {}", encoded_auth)) + .body(Body::from( + r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, + )) + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + })?; + let response = + tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) + })??; + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) + })?; + let _response: RpcResponse = + serde_json::from_slice(&body_bytes).map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) + })?; + Ok(()) +} + +/// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. +pub async fn test_node_and_return_uri( + port: &u16, + user: Option, + password: Option, +) -> Result { + let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) + })?; + let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) + })?; + + for _ in 0..3 { + println!("@zingoproxyd: Trying connection on IPv4."); + match test_node_connection(ipv4_uri.clone(), user.clone(), password.clone()).await { + Ok(_) => { + println!( + "@zingoproxyd: Connected to node using IPv4 at address {}.", + ipv4_uri + ); + return Ok(ipv4_uri); + } + Err(e_ipv4) => { + eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying connection on IPv6.", e_ipv4); + match test_node_connection(ipv6_uri.clone(), user.clone(), password.clone()).await { + Ok(_) => { + println!( + "@zingoproxyd: Connected to node using IPv6 at address {}.", + ipv6_uri + ); + return Ok(ipv6_uri); + } + Err(e_ipv6) => { + eprintln!("@zingoproxyd: Failed to connect to node using IPv6 with error: {}.\n@zingoproxyd: Connection not established. Retrying..", e_ipv6); + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + } + } + } + } + } + + eprintln!("@zingoproxyd: Could not establish connection with node. Please check config and confirm node is listening at the correct address. \n@zingoproxyd: Exiting.."); + std::process::exit(1); + // Err(JsonRpcConnectorError::new( + // "Could not establish connection with node. Exiting..", + // )) +} diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index b55a57e..9810661 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -191,8 +191,7 @@ impl CompactTxStreamer for ProxyClient { Some("xxxxxx".to_string()), Some("xxxxxx".to_string()), ) - .await - .unwrap(); + .await; let zebra_info = zebrad_client .get_info() diff --git a/zingoproxy-testutils/src/lib.rs b/zingoproxy-testutils/src/lib.rs index 167afaa..bfb0325 100644 --- a/zingoproxy-testutils/src/lib.rs +++ b/zingoproxy-testutils/src/lib.rs @@ -16,10 +16,12 @@ pub struct TestManager { pub regtest_manager: zingo_testutils::regtest::RegtestManager, /// Zingolib regtest network. pub regtest_network: zingoconfig::RegtestNetwork, - /// Zing-Proxy gRPC listen port. + /// Zingo-Proxy gRPC listen port. pub proxy_port: u16, /// Zingo-Proxy Nym listen address. pub nym_addr: Option, + /// Zebrad/Zcashd JsonRpc listen port. + pub zebrad_port: u16, /// Online status of Zingo-Proxy. pub online: std::sync::Arc, } @@ -34,10 +36,10 @@ impl TestManager { Vec>>, ) { let lwd_port = portpicker::pick_unused_port().expect("No ports free"); - let zcashd_port = portpicker::pick_unused_port().expect("No ports free"); + let zebrad_port = portpicker::pick_unused_port().expect("No ports free"); let proxy_port = portpicker::pick_unused_port().expect("No ports free"); - let temp_conf_dir = create_temp_conf_files(lwd_port, zcashd_port).unwrap(); + let temp_conf_dir = create_temp_conf_files(lwd_port, zebrad_port).unwrap(); let temp_conf_path = temp_conf_dir.path().to_path_buf(); let nym_conf_path = temp_conf_path.join("nym"); @@ -53,7 +55,7 @@ impl TestManager { let (proxy_handler, nym_addr) = zingoproxylib::proxy::spawn_proxy( &proxy_port, &lwd_port, - &zcashd_port, + &zebrad_port, nym_conf_path.to_str().unwrap(), online.clone(), ) @@ -66,6 +68,7 @@ impl TestManager { regtest_network, proxy_port, nym_addr, + zebrad_port, online, }, regtest_handler, @@ -73,7 +76,7 @@ impl TestManager { ) } - /// Returns zingo-proxy listen port. + /// Returns zingo-proxy listen address. pub fn get_proxy_uri(&self) -> http::Uri { http::Uri::builder() .scheme("http") @@ -83,6 +86,17 @@ impl TestManager { .unwrap() } + /// Returns zebrad listen address. + pub async fn test_and_return_zebrad_uri(&self) -> http::Uri { + zingo_rpc::jsonrpc::connector::test_node_and_return_uri( + &self.zebrad_port, + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .unwrap() + } + /// Builds aand returns Zingolib lightclient. pub async fn build_lightclient(&self) -> zingolib::lightclient::LightClient { let mut client_builder = zingo_testutils::scenarios::setup::ClientBuilder::new( From 261d638d96d394bf40d51f21e368580949e1598a Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 23 May 2024 17:24:12 +0100 Subject: [PATCH 10/40] removed commented code --- zingo-rpc/src/jsonrpc/connector.rs | 150 +---------------------------- 1 file changed, 1 insertion(+), 149 deletions(-) diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index bc457be..10b6958 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -166,22 +166,6 @@ impl JsonRpcConnector { } } - // /// Returns a new JsonRpcConnector instance from zebrad uri port, tests port and returns error if connection not established. - // pub async fn new_from_port( - // port: &u16, - // user: Option, - // password: Option, - // ) -> Result { - // let uri = - // JsonRpcConnector::test_and_return_uri(port, user.clone(), password.clone()).await?; - // Ok(Self { - // uri, - // id_counter: AtomicI32::new(0), - // user, - // password, - // }) - // } - /// Returns the uri the JsonRpcConnector is configured to send requests to. pub fn uri(&self) -> &Uri { &self.uri @@ -237,135 +221,6 @@ impl JsonRpcConnector { } } - // /// Tests connection with zebrad / zebrad. - // pub async fn test_connection( - // uri: Uri, - // user: Option, - // password: Option, - // ) -> Result<(), JsonRpcConnectorError> { - // let client = Client::builder().build::<_, Body>(HttpsConnector::new()); - - // let user = user.unwrap_or_else(|| "xxxxxx".to_string()); - // let password = password.unwrap_or_else(|| "xxxxxx".to_string()); - // let encoded_auth = base64::encode(format!("{}:{}", user, password)); - - // let request = Request::builder() - // .method("POST") - // .uri(uri.clone()) - // .header("Content-Type", "application/json") - // .header("Authorization", format!("Basic {}", encoded_auth)) - // .body(Body::from( - // r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, - // )) - // .map_err(|e| { - // JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) - // })?; - // let response = - // tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) - // .await - // .map_err(|e| { - // JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) - // })??; - // let body_bytes = hyper::body::to_bytes(response.into_body()) - // .await - // .map_err(|e| { - // JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) - // })?; - // let _response: RpcResponse = serde_json::from_slice(&body_bytes) - // .map_err(|e| { - // JsonRpcConnectorError::new_with_source( - // "Failed to deserialize response", - // Box::new(e), - // ) - // })?; - // Ok(()) - // } - - // /// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. - // // pub async fn test_and_return_uri( - // // port: &u16, - // // user: Option, - // // password: Option, - // // ) -> Result { - // // let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { - // // JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) - // // })?; - // // let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { - // // JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) - // // })?; - - // // match JsonRpcConnector::test_connection(ipv4_uri.clone(), user.clone(), password.clone()) - // // .await - // // { - // // Ok(_) => { - // // println!("@zingoproxyd: Connected to node using IPv4."); - // // return Ok(ipv4_uri); - // // } - // // Err(e_ipv4) => { - // // eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying on IPv6.", e_ipv4); - // // match JsonRpcConnector::test_connection(ipv6_uri.clone(), user, password).await { - // // Ok(_) => { - // // println!("@zingoproxyd: Connected to node using IPv6."); - // // Ok(ipv6_uri) - // // } - // // Err(e_ipv6) => Err(e_ipv6), - // // } - // // } - // // } - // // } - // pub async fn test_and_return_uri( - // port: &u16, - // user: Option, - // password: Option, - // ) -> Result { - // let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { - // JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) - // })?; - // let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { - // JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) - // })?; - - // for _ in 0..3 { - // println!("@zingoproxyd: Trying connection on IPv4."); - // match JsonRpcConnector::test_connection( - // ipv4_uri.clone(), - // user.clone(), - // password.clone(), - // ) - // .await - // { - // Ok(_) => { - // println!("@zingoproxyd: Connected to node using IPv4."); - // return Ok(ipv4_uri); - // } - // Err(e_ipv4) => { - // eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying connection on IPv6.", e_ipv4); - // match JsonRpcConnector::test_connection( - // ipv6_uri.clone(), - // user.clone(), - // password.clone(), - // ) - // .await - // { - // Ok(_) => { - // println!("@zingoproxyd: Connected to node using IPv6."); - // return Ok(ipv6_uri); - // } - // Err(e_ipv6) => { - // eprintln!("@zingoproxyd: Failed to connect to node using IPv6 with error: {}.\n@zingoproxyd: Connection not established. Retrying..", e_ipv6); - // tokio::time::sleep(std::time::Duration::from_secs(3)).await; - // } - // } - // } - // } - // } - - // eprintln!("@zingoproxyd: Could not establish connection with node. Please check config and confirm node is listening at the correct address."); - // Err(JsonRpcConnectorError::new( - // "Could not establish connection with node. Exiting..", - // )) - // } - /// Returns software information from the RPC server, as a [`GetInfo`] JSON struct. /// /// zcashd reference: [`getinfo`](https://zcash.github.io/rpc/getinfo.html) @@ -619,7 +474,7 @@ pub async fn test_node_connection( Ok(()) } -/// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type. +/// Tries to connect to zebrad/zcashd using IPv4 and IPv6 and returns the correct uri type, exits program with error message if connection cannot be established. pub async fn test_node_and_return_uri( port: &u16, user: Option, @@ -663,7 +518,4 @@ pub async fn test_node_and_return_uri( eprintln!("@zingoproxyd: Could not establish connection with node. Please check config and confirm node is listening at the correct address. \n@zingoproxyd: Exiting.."); std::process::exit(1); - // Err(JsonRpcConnectorError::new( - // "Could not establish connection with node. Exiting..", - // )) } From a4d24dfbf34d9740a0c34103c2cbc9d442c2b0eb Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 23 May 2024 17:50:54 +0100 Subject: [PATCH 11/40] added welcome image --- zingo-proxyd/src/proxy.rs | 57 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 4f83ef8..b408bf2 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -24,6 +24,7 @@ pub async fn spawn_proxy( let mut handles = vec![]; let nym_addr_out: Option; + startup_message(); println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; handles.push(proxy_handle); @@ -51,3 +52,59 @@ pub async fn spawn_proxy( pub async fn close_proxy(online: Arc) { online.store(false, Ordering::SeqCst); } + +fn startup_message() { + let welcome_message = r#" +@@@@@@@@@@@@@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@@@@@@@@@ +@@@@@@@@@@@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@@@@@@@ +@@@@@@@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@@%(**/#@@@&&&&&&&&&&&&@@@ +@@@@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@&. /@&&&&&&&&&&&&&@ +@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&@@ (@&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&@@. (@&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&@@, . %@&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&@@%@@#&@@&&&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&@@&&&&&&&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&%%&&&&&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&# %&%%%%&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&( /@%%%%&&&&&&&&&&&& +&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%@&# %@@&%%%%&&&&&&&&&& +&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#####################%%%%%%%%%%%%%%%%%%%%%& %&%%%%%%%%&&&&&&&& +&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%##################################%%%%%%%%%%%%%%&& %&%%%%%%%%%%&&&&&& +&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%###########################################%%%%%%%%@&#& %&%%%%%%%%%%%&&&&& +&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%############################################%%&@@&%#(((((@ %&%%%%%%%%%%%%%&&& +&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%#############################%&@@@@@&%%##(((((((((((((((((((@ %&%%%%%%%%%%%%%%&& +&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%#%###%@@@@@&%########%&@&%#((((((((((((((((((((((((((((((((((((@ #&%%%%%%%%%%%%%%%& +&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%###%@# .&@@@@@&(((((((((((((((((((((((((((((((((((((((((((#%%%@&&&%%%%%%%%%%%%%%%%% +&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%###@& ,*@%((((((((((((((((((((((((((((((((((((((((((((((((((@%%%%%%%%%%%%%%%%%%%% +&&&%%%%%%%%%%%%%%%%%%%%%%%%%####%@/ %@(((((((((((((((((((((((((((((((((((((((((((((((((%&%%%%%%%%%%%%%%%%%%%% +&&&%%%%%%%%%%%%%%%%%%%%%%%%%#####&@. *@#((((((((((((((((((((((((((((((((((((((((((((((((#@%%%%%%%%%%%%%%%%%%%%% +&&%%%%%%%%%%%%%%%%%%%%%%%%#######@@@&. *@&((((((((((((((((((((((((((((((((((((((((((((((((((&%%%%%%%%%%%%%%%%%%%%%% +%&%%%%%%%%%%%%%%%%%%%%%%%%#####%@@%(((#&&&%#((((((((((((((((((((((((((((((((((((((((((((((((((((&%##%%%%%%%%%%%%%%%%%%%% +&%%%%%%%%%%%%%%%%%%%%%%%%#####%@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((&%####%%%%%%%%%%%%%%%%%%% +&%%%%%%%%%%%%%%%%%%%%%%%%####&%(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((&%#####%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%###%@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((@%######%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%##%@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((&&########%%%%%%%%%%%%%%%%%%% +&%%%%%%%%%%%%%%%%%%%%%%%#%@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((#&##########%%%%%%%%%%%%%%%%%%% +&%%%%%%%%%%%%%%%%%%%%%%%%@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((#@############%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%@#(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%@#############%%%%%%%%%%%%%%%%%%%% +&&%%%%%%%%%%%%%%%%%%%%%&&((((((((((((((((((((((((((((((((((((((((((((((((((((((((((&&################%%%%%%%%%%%%%%%%%%% +&&%%%%%%%%%%%%%%%%%%%%%@((((((((((((((((((((((((((((((((((((((((((((((((((((((((%@#################%%%%%%%%%%%%%%%%%%%%% +&&&%%%%%%%%%%%%%%%%%%%@%(((((((((((((((((((((((((((((((((((((((((((((((((((((%@%##################%%%%%%%%%%%%%%%%%%%%%% +&&&%%%%%%%%%%%%%%%%%%%@(((((((((((((((((((((((((((((((((((((((((((((((((((&&#####################%%%%%%%%%%%%%%%%%%%%%%% +&&&&&%%%%%%%%%%%%%%%%&%(((((((((((((((((((((((((((((((((((((((((((((((%@%######################%%%%%%%%%%%%%%%%%%%%%%%%% +&&&&&&%%%%%%%%%%%%%%%@#(((((((((((((((((((((((((((((((((((((((((((%@&#########################%%%%%%%%%%%%%%%%%%%%%%%%%% +&&&&&&%%%%%%%%%%%%%%%@(((((((((((((((((((((((((((((((((((((((#&@%###########################%%%%%%%%%%%%%%%%%%%%%%%%%%%& +&&&&&&&&%%%%%%%%%%%%&@((((((((((((((((((((((((((((((((((#&@&#############################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&& +&&&&&&&&&%%%%%%%%%%%&%((((((((((((((((((((((((((((#&@&%###############################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&& +&&&&&&&&&&%%%%%%%&@@@&@@@%((((((((((((((((((#&@&%%##################################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&& +&&&&&&&&&&&&%%&@( #@#((((((((#&@&%%%%%%###############################%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&& +&&&&&&&&&&&&&&@. *@%&@@&%%%%%%%%%%%%%%%%%%%%%%%#%#%####%%##%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&& +&&&&&&&&&&&&&&@ .@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&& +&&&&&&&&&&&&&&@& .@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&@@#, ,#@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&& +Thank you for using ZingoLabs ZingoProxyD! +Donate at https://free2z.cash/zingolabs. + "#; + println!("{}", welcome_message); +} From b9185f14258328246a3ef7b966a935d6a29e4efb Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 23 May 2024 17:54:20 +0100 Subject: [PATCH 12/40] added welcome image --- zingo-proxyd/src/proxy.rs | 2 ++ zingo-rpc/src/jsonrpc/connector.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index b408bf2..3ba7b77 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -105,6 +105,8 @@ fn startup_message() { &&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&& Thank you for using ZingoLabs ZingoProxyD! Donate at https://free2z.cash/zingolabs. + +Please note ZingoProxyD is currently in development and should not be used to run mainnet nodes. "#; println!("{}", welcome_message); } diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 10b6958..9ecac04 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -498,7 +498,7 @@ pub async fn test_node_and_return_uri( return Ok(ipv4_uri); } Err(e_ipv4) => { - eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd[nym]: Trying connection on IPv6.", e_ipv4); + eprintln!("@zingoproxyd: Failed to connect to node using IPv4 with error: {}\n@zingoproxyd: Trying connection on IPv6.", e_ipv4); match test_node_connection(ipv6_uri.clone(), user.clone(), password.clone()).await { Ok(_) => { println!( From 336040ac297256f43a9e76498c3e2586520850d3 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 24 May 2024 15:17:25 +0100 Subject: [PATCH 13/40] small fixes --- zingo-proxyd/src/proxy.rs | 7 +++++-- zingo-rpc/src/jsonrpc/connector.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 3ba7b77..9e15e9b 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -104,9 +104,12 @@ fn startup_message() { &&&&&&&&&&&&&&&&@@#, ,#@@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&& Thank you for using ZingoLabs ZingoProxyD! -Donate at https://free2z.cash/zingolabs. +- Donate to us at https://free2z.cash/zingolabs. +- Submit any security conserns to us at zingodisclosure@proton.me. -Please note ZingoProxyD is currently in development and should not be used to run mainnet nodes. +****** Please note ZingoProxyD is currently in development and should not be used to run mainnet nodes. ****** + +****** Currently LightwalletD is required for full functionality. ****** "#; println!("{}", welcome_message); } diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 9ecac04..bc54bfd 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -516,6 +516,6 @@ pub async fn test_node_and_return_uri( } } - eprintln!("@zingoproxyd: Could not establish connection with node. Please check config and confirm node is listening at the correct address. \n@zingoproxyd: Exiting.."); + eprintln!("@zingoproxyd: Could not establish connection with node. \n@zingoproxyd: Please check config and confirm node is listening at the correct address and the correct authorisation details have been entered. \n@zingoproxyd: Exiting.."); std::process::exit(1); } From be59ed85a48a956ed07040beddef245c2f725ea0 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 24 May 2024 16:01:59 +0100 Subject: [PATCH 14/40] small fixes --- zingo-rpc/src/rpc/service.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 9810661..8e3bd52 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -184,7 +184,8 @@ impl CompactTxStreamer for ProxyClient { Self: 'async_trait, { println!("@zingoproxyd: Received call of get_lightd_info."); - // TODO Add user and password as fields of ProxyClient and use here. + // TODO: Add user and password as fields of ProxyClient and use here. + // TODO: Return Nym_Address in get_lightd_info response, for use buy wallets. Box::pin(async { let zebrad_client = JsonRpcConnector::new( self.zebrad_uri.clone(), From 31998dad6595426d8018b249a3430147239027d3 Mon Sep 17 00:00:00 2001 From: idky137 Date: Wed, 29 May 2024 03:25:02 +0100 Subject: [PATCH 15/40] implemented send_transaction and get_best_block --- integration-tests/tests/integrations.rs | 33 +- zingo-proxyd/src/proxy.rs | 2 +- zingo-rpc/src/jsonrpc/connector.rs | 17 +- zingo-rpc/src/jsonrpc/primitives.rs | 11 +- zingo-rpc/src/rpc/service.rs | 652 ++++++++++++++++++++---- 5 files changed, 577 insertions(+), 138 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index cb8f6a9..d1604ca 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -8,11 +8,11 @@ use zingoproxy_testutils::{drop_test_manager, TestManager}; use zingo_netutils::GrpcConnector; -mod proxy { +mod wallet { use super::*; #[tokio::test] - async fn proxy_connect_to_node_get_info() { + async fn connect_to_node_get_info() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; @@ -21,12 +21,10 @@ mod proxy { "@zingoproxytest: Attempting to connect to GRPC server at URI: {}.", test_manager.get_proxy_uri() ); - let mut client = GrpcConnector::new(test_manager.get_proxy_uri()) .get_client() .await .expect("Failed to create GRPC client"); - let lightd_info = client .get_lightd_info(zcash_client_backend::proto::service::Empty {}) .await @@ -36,7 +34,6 @@ mod proxy { "@zingoproxytest: Lightd_info response:\n{:#?}.", lightd_info.into_inner() ); - drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), regtest_handler, @@ -46,7 +43,7 @@ mod proxy { } #[tokio::test] - async fn proxy_send_transaction() { + async fn send_and_sync() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; @@ -54,12 +51,6 @@ mod proxy { test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); - - println!( - "@zingoproxytest: zingo_client balance: \n{:#?}.", - zingo_client.do_balance().await - ); - zingo_client .do_send(vec![( &zingolib::get_base_address!(zingo_client, "sapling"), @@ -68,18 +59,12 @@ mod proxy { )]) .await .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); - println!( - "@zingoproxytest: zingo_client balance: \n{:#?}.", - zingo_client.do_balance().await - ); - - assert_eq!( - zingo_client.do_balance().await.sapling_balance.unwrap(), - 250_000 - ); - + assert_eq!(balance.sapling_balance.unwrap(), 250_000); drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), regtest_handler, @@ -87,6 +72,10 @@ mod proxy { ) .await; } + + // TODO: Add test for get_mempool_stream: lightclient::start_mempool_monitor. + // #[tokio::test] + // async fn mempool_monitor() {} } mod nym { diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 9e15e9b..581302d 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -24,7 +24,7 @@ pub async fn spawn_proxy( let mut handles = vec![]; let nym_addr_out: Option; - startup_message(); + // startup_message(); println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; handles.push(proxy_handle); diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index bc54bfd..48e5321 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -13,8 +13,8 @@ use super::primitives::{ AddressStringsRequest, BestBlockHashResponse, GetBalanceResponse, GetBlockRequest, GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, GetSubtreesRequest, GetSubtreesResponse, GetTransactionRequest, GetTransactionResponse, GetTreestateRequest, - GetTreestateResponse, GetUtxosResponse, SendTransactionRequest, SendTransactionResponse, - TxidsByAddressRequest, TxidsResponse, + GetTreestateResponse, GetUtxosResponse, SendTransactionResponse, TxidsByAddressRequest, + TxidsResponse, }; #[derive(Serialize, Deserialize, Debug)] @@ -209,6 +209,13 @@ impl JsonRpcConnector { .map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) })?; + + println!( + "@zingoproxyd: Received response from {} call to node: {:?}", + method.to_string(), + body_bytes + ); + let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) })?; @@ -275,10 +282,8 @@ impl JsonRpcConnector { &self, raw_transaction_hex: String, ) -> Result { - let params = SendTransactionRequest { - raw_transaction_hex, - }; - self.send_request("sendrawtransaction", params).await + self.send_request("sendrawtransaction", [raw_transaction_hex]) + .await } /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 84b316c..817f6a8 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -51,15 +51,6 @@ impl AddressStringsRequest { } } -/// Hex-encoded raw transaction. -/// -/// This is used for the input parameter of [`JsonRpcConnector::send_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct SendTransactionRequest { - /// - Hex-encoded raw transaction bytes. - pub raw_transaction_hex: String, -} - /// Block to be fetched. /// /// This is used for the input parameter of [`JsonRpcConnector::get_block`]. @@ -222,7 +213,7 @@ pub struct GetBalanceResponse { /// /// This is used for the output parameter of [`JsonRpcConnector::send_raw_transaction`]. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct SendTransactionResponse(#[serde(with = "hex")] transaction::Hash); +pub struct SendTransactionResponse(#[serde(with = "hex")] pub transaction::Hash); /// Wrapper for `SerializedBlock` to handle hex serialization/deserialization. #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 8e3bd52..258c8e0 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -20,14 +20,133 @@ use crate::{ }; impl CompactTxStreamer for ProxyClient { - // fn get_latest_block<'life0, 'async_trait>( + /// Return the height of the tip of the best chain. + fn get_latest_block<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_latest_block."); + Box::pin(async { + let blockchain_info = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .get_blockchain_info() + .await + .map_err(|e| e.to_grpc_status())?; + + let block_id = BlockId { + height: blockchain_info.blocks.0 as u64, + hash: blockchain_info.best_block_hash.0.to_vec(), + }; + + Ok(tonic::Response::new(block_id)) + }) + } + // define_grpc_passthrough!( + // fn get_latest_block( + // &self, + // request: tonic::Request, + // ) -> BlockId + // ); + + /// Return the compact block corresponding to the given block identifier. + /// + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_block<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_block not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_block( + // &self, + // request: tonic::Request, + // ) -> CompactBlock + // ); + + /// Same as GetBlock except actions contain only nullifiers. + /// + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_block_nullifiers<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_nullifiers."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_block_nullifiers not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_block_nullifiers( + // &self, + // request: tonic::Request, + // ) -> CompactBlock + // ); + + /// Server streaming response type for the GetBlockRange method. + #[doc = "Server streaming response type for the GetBlockRange method."] + type GetBlockRangeStream = tonic::Streaming; + + // /// Return a list of consecutive compact blocks. + // fn get_block_range<'life0, 'async_trait>( // &'life0 self, - // request: tonic::Request, + // request: tonic::Request, // ) -> core::pin::Pin< // Box< // dyn core::future::Future< // Output = std::result::Result< - // tonic::Response, + // tonic::Response, // tonic::Status, // >, // > + core::marker::Send @@ -38,31 +157,9 @@ impl CompactTxStreamer for ProxyClient { // 'life0: 'async_trait, // Self: 'async_trait, // { - // println!("@zingoproxyd[nym]: Received call of get_latest_block."); - // Box::pin(async { - // let zebrad_info = ::zingo_netutils::GrpcConnector::new(self.zebrad_uri.clone()) - // .get_client() - // .await; - // todo!("get_latest_block not yet implemented") - // }) + // println!("@zingoproxyd: Received call of get_block_range."); + // Box::pin(async { todo!("get_block_range not yet implemented") }) // } - define_grpc_passthrough!( - fn get_latest_block( - &self, - request: tonic::Request, - ) -> BlockId - ); - - define_grpc_passthrough!( - fn get_block( - &self, - request: tonic::Request, - ) -> CompactBlock - ); - - #[doc = "Server streaming response type for the GetBlockRange method."] - type GetBlockRangeStream = tonic::Streaming; - define_grpc_passthrough!( fn get_block_range( &self, @@ -70,6 +167,66 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetBlockRangeStream ); + /// Server streaming response type for the GetBlockRangeNullifiers method. + #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] + type GetBlockRangeNullifiersStream = tonic::Streaming; + + /// Same as GetBlockRange except actions contain only nullifiers. + /// + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_block_range_nullifiers<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_range_nullifiers."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_block_range_nullifiers not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_block_range_nullifiers( + // &self, + // request: tonic::request, + // ) -> self::getblockrangenullifiersstream + // ); + + // /// Return the requested full (not compact) transaction (as from zcashd). + // fn get_transaction<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_transaction."); + // Box::pin(async { todo!("get_transaction not yet implemented") }) + // } define_grpc_passthrough!( fn get_transaction( &self, @@ -77,16 +234,81 @@ impl CompactTxStreamer for ProxyClient { ) -> RawTransaction ); - define_grpc_passthrough!( - fn send_transaction( - &self, - request: tonic::Request, - ) -> SendResponse - ); + /// Submit the given transaction to the Zcash network. + fn send_transaction<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of send_transaction."); + Box::pin(async { + let hex_tx = hex::encode(request.into_inner().data); + let tx_output = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .send_raw_transaction(hex_tx) + .await + .map_err(|e| e.to_grpc_status())?; + + Ok(tonic::Response::new( + zcash_client_backend::proto::service::SendResponse { + error_code: 0, + error_message: tx_output.0.to_string(), + }, + )) + }) + } + // define_grpc_passthrough!( + // fn send_transaction( + // &self, + // request: tonic::Request, + // ) -> SendResponse + // ); + /// Server streaming response type for the GetTaddressTxids method. #[doc = "Server streaming response type for the GetTaddressTxids method."] type GetTaddressTxidsStream = tonic::Streaming; + // /// Return the txids corresponding to the given t-address within the given block range. + // fn get_taddress_txids<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request< + // zcash_client_backend::proto::service::TransparentAddressBlockFilter, + // >, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_taddress_txids."); + // Box::pin(async { todo!("get_taddress_txids not yet implemented") }) + // } define_grpc_passthrough!( fn get_taddress_txids( &self, @@ -94,14 +316,40 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetTaddressTxidsStream ); - define_grpc_passthrough!( - fn get_taddress_balance( - &self, - request: tonic::Request, - ) -> Balance - ); + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_taddress_balance<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_taddress_balance."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_taddress_balance not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_taddress_balance( + // &self, + // request: tonic::Request, + // ) -> Balance + // ); - /// This isn't easily definable with the macro, and I beleive it to be unused + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). #[must_use] #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] fn get_taddress_balance_stream<'life0, 'async_trait>( @@ -118,22 +366,85 @@ impl CompactTxStreamer for ProxyClient { 'life0: 'async_trait, Self: 'async_trait, { - todo!("this isn't expected to be called. Please implement this if you need it") + println!("@zingoproxyd: Received call of get_taddress_balance_stream."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_taddress_balance_stream not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) } + /// Server streaming response type for the GetMempoolTx method. #[doc = "Server streaming response type for the GetMempoolTx method."] type GetMempoolTxStream = tonic::Streaming; - define_grpc_passthrough!( - fn get_mempool_tx( - &self, - request: tonic::Request, - ) -> Self::GetMempoolTxStream - ); + /// Return the compact transactions currently in the mempool; the results + /// can be a few seconds out of date. If the Exclude list is empty, return + /// all transactions; otherwise return all *except* those in the Exclude list + /// (if any); this allows the client to avoid receiving transactions that it + /// already has (from an earlier call to this rpc). The transaction IDs in the + /// Exclude list can be shortened to any number of bytes to make the request + /// more bandwidth-efficient; if two or more transactions in the mempool + /// match a shortened txid, they are all sent (none is excluded). Transactions + /// in the exclude list that don't exist in the mempool are ignored. + /// + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_mempool_tx<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_mempool_tx."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_mempool_tx not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_mempool_tx( + // &self, + // request: tonic::Request, + // ) -> Self::GetMempoolTxStream + // ); + /// Server streaming response type for the GetMempoolStream method. #[doc = "Server streaming response type for the GetMempoolStream method."] type GetMempoolStreamStream = tonic::Streaming; + // /// Return a stream of current Mempool transactions. This will keep the output stream open while + // /// there are mempool transactions. It will close the returned stream when a new block is mined. + // fn get_mempool_stream<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_mempool_stream."); + // Box::pin(async { todo!("get_mempool_stream not yet implemented") }) + // } define_grpc_passthrough!( fn get_mempool_stream( &self, @@ -141,6 +452,31 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetMempoolStreamStream ); + // /// GetTreeState returns the note commitment tree state corresponding to the given block. + // /// See section 3.7 of the Zcash protocol specification. It returns several other useful + // /// values also (even though they can be obtained using GetBlock). + // /// The block can be specified by either height or hash. + // fn get_tree_state<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_tree_state."); + // Box::pin(async { todo!("get_tree_state not yet implemented") }) + // } define_grpc_passthrough!( fn get_tree_state( &self, @@ -148,23 +484,148 @@ impl CompactTxStreamer for ProxyClient { ) -> TreeState ); - define_grpc_passthrough!( - fn get_address_utxos( - &self, - request: tonic::Request, - ) -> GetAddressUtxosReplyList - ); + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_latest_tree_state<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_latest_tree_state."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_latest_tree_state not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_latest_tree_state( + // &self, + // request: tonic::Request, + // ) -> TreeState + // ); + + /// Server streaming response type for the GetSubtreeRoots method. + #[doc = " Server streaming response type for the GetSubtreeRoots method."] + type GetSubtreeRootsStream = tonic::Streaming; + + /// Returns a stream of information about roots of subtrees of the Sapling and Orchard + /// note commitment trees. + /// + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_subtree_roots<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_subtree_roots."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_subtree_roots not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_subtree_roots( + // &self, + // request: tonic::Request, + // ) -> Self::GetSubtreeRootsStream + // ); + + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_address_utxos<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response< + zcash_client_backend::proto::service::GetAddressUtxosReplyList, + >, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_address_utxos."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_address_utxos not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_address_utxos( + // &self, + // request: tonic::Request, + // ) -> GetAddressUtxosReplyList + // ); + /// Server streaming response type for the GetAddressUtxosStream method. #[doc = "Server streaming response type for the GetAddressUtxosStream method."] type GetAddressUtxosStreamStream = tonic::Streaming; - define_grpc_passthrough!( - fn get_address_utxos_stream( - &self, - request: tonic::Request, - ) -> tonic::Streaming - ); + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn get_address_utxos_stream<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_address_utxos_stream."); + Box::pin(async { + Err(tonic::Status::unimplemented("get_address_utxos_stream not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn get_address_utxos_stream( + // &self, + // request: tonic::Request, + // ) -> tonic::Streaming + // ); + /// Return information about this lightwalletd instance and the blockchain fn get_lightd_info<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -185,7 +646,7 @@ impl CompactTxStreamer for ProxyClient { { println!("@zingoproxyd: Received call of get_lightd_info."); // TODO: Add user and password as fields of ProxyClient and use here. - // TODO: Return Nym_Address in get_lightd_info response, for use buy wallets. + // TODO: Return Nym_Address in get_lightd_info response, for use by wallets. Box::pin(async { let zebrad_client = JsonRpcConnector::new( self.zebrad_uri.clone(), @@ -241,43 +702,36 @@ impl CompactTxStreamer for ProxyClient { // ) -> LightdInfo // ); - define_grpc_passthrough!( - fn ping( - &self, - request: tonic::Request, - ) -> PingResponse - ); - - define_grpc_passthrough!( - fn get_block_nullifiers( - &self, - request: tonic::Request, - ) -> CompactBlock - ); - - define_grpc_passthrough!( - fn get_block_range_nullifiers( - &self, - request: tonic::Request, - ) -> Self::GetBlockRangeNullifiersStream - ); - #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] - type GetBlockRangeNullifiersStream = tonic::Streaming; - - define_grpc_passthrough!( - fn get_latest_tree_state( - &self, - request: tonic::Request, - ) -> TreeState - ); - - define_grpc_passthrough!( - fn get_subtree_roots( - &self, - request: tonic::Request, - ) -> Self::GetSubtreeRootsStream - ); - - #[doc = " Server streaming response type for the GetSubtreeRoots method."] - type GetSubtreeRootsStream = tonic::Streaming; + // /// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) [from zebrad] + /// This RPC has not been implemented as it is not currently used by zingolib. + /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + fn ping<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of ping."); + Box::pin(async { + Err(tonic::Status::unimplemented("ping not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) + }) + } + // define_grpc_passthrough!( + // fn ping( + // &self, + // request: tonic::Request, + // ) -> PingResponse + // ); } From 551c4ba60feb6b5625bdcd1a7b24e043ea3bb2d7 Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 3 Jun 2024 20:13:25 +0100 Subject: [PATCH 16/40] implemented get_taddress_txids --- Cargo.lock | 2 + integration-tests/tests/integrations.rs | 41 ++- zingo-rpc/Cargo.toml | 2 + zingo-rpc/src/jsonrpc/connector.rs | 88 ++++-- zingo-rpc/src/jsonrpc/primitives.rs | 348 +++++++++++----------- zingo-rpc/src/rpc/service.rs | 367 ++++++++++++++++++------ 6 files changed, 548 insertions(+), 300 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f0747d..46b3c9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8544,6 +8544,7 @@ version = "0.1.0" dependencies = [ "base64 0.13.1", "bytes 1.6.0", + "futures", "hex 0.4.3", "http", "http-body", @@ -8563,6 +8564,7 @@ dependencies = [ "serde_json", "tokio", "tokio-rustls 0.23.4", + "tokio-stream", "tonic", "tower", "webpki-roots 0.21.1", diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index d1604ca..9d3de59 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -43,7 +43,7 @@ mod wallet { } #[tokio::test] - async fn send_and_sync() { + async fn send_and_sync_shielded() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; @@ -73,6 +73,45 @@ mod wallet { .await; } + #[tokio::test] + async fn send_and_sync_transparent() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + + assert_eq!(balance.transparent_balance.unwrap(), 500_000); + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + // TODO: Add test for get_mempool_stream: lightclient::start_mempool_monitor. // #[tokio::test] // async fn mempool_monitor() {} diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 04c680d..9d2dfeb 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -48,3 +48,5 @@ jsonrpc-core = "18.0.0" indexmap = { version = "2.2.6", features = ["serde"] } base64 = "0.13.0" +tokio-stream = "0.1" +futures = "0.3.30" diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 48e5321..fe89834 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -10,11 +10,9 @@ use serde_json::Value; use std::sync::atomic::{AtomicI32, Ordering}; use super::primitives::{ - AddressStringsRequest, BestBlockHashResponse, GetBalanceResponse, GetBlockRequest, - GetBlockResponse, GetBlockchainInfoResponse, GetInfoResponse, GetSubtreesRequest, - GetSubtreesResponse, GetTransactionRequest, GetTransactionResponse, GetTreestateRequest, - GetTreestateResponse, GetUtxosResponse, SendTransactionResponse, TxidsByAddressRequest, - TxidsResponse, + BestBlockHashResponse, GetBalanceResponse, GetBlockResponse, GetBlockchainInfoResponse, + GetInfoResponse, GetSubtreesResponse, GetTransactionResponse, GetTreestateResponse, + GetUtxosResponse, SendTransactionResponse, TxidsResponse, }; #[derive(Serialize, Deserialize, Debug)] @@ -196,11 +194,13 @@ impl JsonRpcConnector { let request_body = serde_json::to_string(&req).map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) })?; + // println!("request body`: {:?}", request_body); let request = request_builder .body(Body::from(request_body)) .map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) })?; + // println!("request: {:?}", request); let response = client.request(request).await.map_err(|e| { JsonRpcConnectorError::new_with_source("HTTP request failed", Box::new(e)) })?; @@ -211,7 +211,7 @@ impl JsonRpcConnector { })?; println!( - "@zingoproxyd: Received response from {} call to node: {:?}", + "@zingoproxyd: Received response from {} call to node: {:#?}", method.to_string(), body_bytes ); @@ -260,11 +260,13 @@ impl JsonRpcConnector { /// /// - `address_strings`: (object, example={"addresses": ["tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ"]}) A JSON map with a single entry /// - `addresses`: (array of strings) A list of base-58 encoded addresses. + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_address_balance( &self, addresses: Vec, ) -> Result { - let params = AddressStringsRequest { addresses }; + let params = vec![serde_json::to_value(addresses)?]; self.send_request("getaddressbalance", params).await } @@ -282,8 +284,8 @@ impl JsonRpcConnector { &self, raw_transaction_hex: String, ) -> Result { - self.send_request("sendrawtransaction", [raw_transaction_hex]) - .await + let params = vec![serde_json::to_value(raw_transaction_hex)?]; + self.send_request("sendrawtransaction", params).await } /// Returns the requested block by hash or height, as a [`GetBlock`] JSON string. @@ -298,14 +300,22 @@ impl JsonRpcConnector { /// /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_block( &self, hash_or_height: String, verbosity: Option, ) -> Result { - let params = GetBlockRequest { - hash_or_height, - verbosity, + let params = match verbosity { + Some(v) => vec![ + serde_json::to_value(hash_or_height)?, + serde_json::to_value(v)?, + ], + None => vec![ + serde_json::to_value(hash_or_height)?, + serde_json::to_value(1)?, + ], }; self.send_request("getblock", params).await } @@ -315,6 +325,8 @@ impl JsonRpcConnector { /// zcashd reference: [`getbestblockhash`](https://zcash.github.io/rpc/getbestblockhash.html) /// method: post /// tags: blockchain + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_best_block_hash( &self, ) -> Result { @@ -327,6 +339,8 @@ impl JsonRpcConnector { /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) /// method: post /// tags: blockchain + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_raw_mempool(&self) -> Result { self.send_request::<(), TxidsResponse>("getrawmempool", ()) .await @@ -345,7 +359,7 @@ impl JsonRpcConnector { &self, hash_or_height: String, ) -> Result { - let params = GetTreestateRequest { hash_or_height }; + let params = vec![serde_json::to_value(hash_or_height)?]; self.send_request("z_gettreestate", params).await } @@ -360,16 +374,24 @@ impl JsonRpcConnector { /// - `pool`: (string, required) The pool from which subtrees should be returned. Either "sapling" or "orchard". /// - `start_index`: (number, required) The index of the first 2^16-leaf subtree to return. /// - `limit`: (number, optional) The maximum number of subtree values to return. + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_subtrees_by_index( &self, pool: String, start_index: u16, limit: Option, ) -> Result { - let params = GetSubtreesRequest { - pool, - start_index, - limit, + let params = match limit { + Some(v) => vec![ + serde_json::to_value(pool)?, + serde_json::to_value(start_index)?, + serde_json::to_value(v)?, + ], + None => vec![ + serde_json::to_value(pool)?, + serde_json::to_value(start_index)?, + ], }; self.send_request("z_getsubtreesbyindex", params).await } @@ -389,7 +411,11 @@ impl JsonRpcConnector { txid_hex: String, verbose: Option, ) -> Result { - let params = GetTransactionRequest { txid_hex, verbose }; + let params = match verbose { + Some(v) => vec![serde_json::to_value(txid_hex)?, serde_json::to_value(v)?], + None => vec![serde_json::to_value(txid_hex)?, serde_json::to_value(0)?], + }; + self.send_request("getrawtransaction", params).await } @@ -405,19 +431,25 @@ impl JsonRpcConnector { /// - `addresses`: (json array of string, required) The addresses to get transactions from. /// - `start`: (numeric, required) The lower height to start looking for transactions (inclusive). /// - `end`: (numeric, required) The top height to stop looking for transactions (inclusive). - pub async fn get_address_tx_ids( + pub async fn get_address_txids( &self, addresses: Vec, start: u32, end: u32, ) -> Result { - let params = TxidsByAddressRequest { - addresses, - start, - end, - }; - - self.send_request("getaddresstxids", params).await + // let params = vec![ + // serde_json::to_value(addresses)?, + // serde_json::to_value(start)?, + // serde_json::to_value(end)?, + // ]; + + let params = serde_json::json!({ // Using json! macro for clarity and correctness + "addresses": addresses, + "start": start, + "end": end + }); + + self.send_request("getaddresstxids", vec![params]).await } /// Returns all unspent outputs for a list of addresses. @@ -429,11 +461,13 @@ impl JsonRpcConnector { /// # Parameters /// /// - `addresses`: (array, required, example={\"addresses\": [\"tmYXBYJj1K7vhejSec5osXK2QsGa5MTisUQ\"]}) The addresses to get outputs from. + /// + /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_address_utxos( &self, addresses: Vec, ) -> Result, JsonRpcConnectorError> { - let params = AddressStringsRequest { addresses }; + let params = vec![serde_json::to_value(addresses)?]; self.send_request("getaddressutxos", params).await } } diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 817f6a8..d198369 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -8,106 +8,11 @@ use serde::{Deserialize, Serialize}; use zebra_chain::{ block::{self, Height, SerializedBlock}, subtree::NoteCommitmentSubtreeIndex, - transaction::{self, SerializedTransaction}, + transaction::{self}, transparent, }; use zebra_rpc::methods::{GetBlockHash, GetBlockTrees}; -/// List of transparent address strings. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_address_balance`] and [`JsonRpcConnector::get_address_utxos`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct AddressStringsRequest { - /// A list of transparent address strings. - pub addresses: Vec, -} - -impl AddressStringsRequest { - /// Creates a new `AddressStrings` given a vector. - #[cfg(test)] - pub fn new(addresses: Vec) -> Self { - AddressStringsRequest { addresses } - } - - /// Given a list of addresses as strings: - /// - check if provided list have all valid transparent addresses. - /// - return valid addresses as a set of `Address`. - pub fn valid_addresses( - self, - ) -> jsonrpc_core::Result> { - let valid_addresses: std::collections::HashSet = self - .addresses - .into_iter() - .map(|address| { - address.parse().map_err(|error| { - jsonrpc_core::Error::invalid_params(format!( - "invalid address {address:?}: {error}" - )) - }) - }) - .collect::>()?; - - Ok(valid_addresses) - } -} - -/// Block to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_block`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct GetBlockRequest { - /// The hash or height for the block to be returned. - pub hash_or_height: String, - /// 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. Default=1. - pub verbosity: Option, -} - -/// Block to be examined. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_treestate`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct GetTreestateRequest { - /// The block hash or height. - pub hash_or_height: String, -} - -/// Subtrees to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_subtrees_by_index`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct GetSubtreesRequest { - /// The pool from which subtrees should be returned. Either "sapling" or "orchard". - pub pool: String, - /// The index of the first 2^16-leaf subtree to return. - pub start_index: u16, - /// The maximum number of subtree values to return. - pub limit: Option, -} - -/// Transaction to be fetched. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct GetTransactionRequest { - /// The transaction ID of the transaction to be returned. - pub txid_hex: String, - /// If 0, return a string of hex-encoded data, otherwise return a JSON object. Default=0. - pub verbose: Option, -} - -/// List of transparent address strings and range of blocks to fetch Txids from. -/// -/// This is used for the input parameter of [`JsonRpcConnector::get_address_tx_ids`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct TxidsByAddressRequest { - /// A list of addresses to get transactions from. - pub addresses: Vec, - /// The height to start looking for transactions. - pub start: u32, - /// The height to end looking for transactions. - pub end: u32, -} - /// Response to a `getinfo` RPC request. /// /// This is used for the output parameter of [`JsonRpcConnector::get_info`]. @@ -314,117 +219,122 @@ pub struct BestBlockHashResponse(#[serde(with = "hex")] pub block::Hash); /// Vec of transaction ids, as a JSON array. /// -/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_tx_ids`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +/// This is used for the output parameter of [`JsonRpcConnector::get_raw_mempool`] and [`JsonRpcConnector::get_address_txids`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct TxidsResponse { /// Vec of txids. pub transactions: Vec, } -/// Zingo-Proxy commitment tree structure replicating functionality in Zebra. -/// -/// A wrapper that contains either an Orchard or Sapling note commitment tree. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ProxyCommitments> { - /// Commitment tree state - #[serde(with = "hex")] - #[serde(rename = "finalState")] - pub final_state: Tree, -} - -impl + FromHex> ProxyCommitments { - /// Creates a new instance of `ProxyCommitments` from a hex string. - pub fn new_from_hex(hex_encoded_data: &str) -> Result { - let tree = Tree::from_hex(hex_encoded_data)?; - Ok(Self { final_state: tree }) - } - - /// Checks if the internal tree is empty. - pub fn is_empty(&self) -> bool { - self.final_state.as_ref().is_empty() - } -} - -/// Zingo-Proxy treestate structure replicating functionality in Zebra. -/// -/// A treestate that is included in the [`z_gettreestate`][1] RPC response. -#[derive(Clone, Debug, Eq, PartialEq, Serialize)] -pub struct ProxyTreestate> { - commitments: ProxyCommitments, -} - -impl + FromHex> ProxyTreestate { - /// Creates a new instance of `ProxyTreestate`. - pub fn new(commitments: ProxyCommitments) -> Self { - Self { commitments } - } - - /// Checks if the internal tree is empty. - pub fn is_empty(&self) -> bool { - self.commitments.is_empty() - } -} - -impl<'de, Tree: AsRef<[u8]> + FromHex + Deserialize<'de>> - Deserialize<'de> for ProxyTreestate -{ +impl<'de> Deserialize<'de> for TxidsResponse { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let hex_string: String = Deserialize::deserialize(deserializer)?; - let tree = Tree::from_hex(&hex_string).map_err(serde::de::Error::custom)?; - Ok(ProxyTreestate::new(ProxyCommitments { final_state: tree })) + let v = serde_json::Value::deserialize(deserializer)?; + + let transactions = v + .as_array() + .ok_or_else(|| serde::de::Error::custom("Expected the JSON to be an array"))? + .iter() + .filter_map(|item| item.as_str().map(String::from)) + .collect::>(); + + Ok(TxidsResponse { transactions }) } } -/// A serialized Sapling note commitment tree +/// Zingo-Proxy commitment tree structure replicating functionality in Zebra. /// -/// Replicates functionality used in Zebra. +/// A wrapper that contains either an Orchard or Sapling note commitment tree. #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct ProxySerializedTree(Vec); - -impl FromHex for ProxySerializedTree { - type Error = hex::FromHexError; +pub struct ProxyCommitments { + /// Commitment tree state + pub final_state: String, +} - fn from_hex>(hex: T) -> Result { - let bytes = hex::decode(hex)?; - Ok(ProxySerializedTree(bytes)) - } +/// Zingo-Proxy sapling treestate. +/// +/// A treestate that is included in the [`z_gettreestate`][1] RPC response. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxySaplingTreestate { + /// Sapling note commitment tree. + pub commitments: ProxyCommitments, } -impl AsRef<[u8]> for ProxySerializedTree { - fn as_ref(&self) -> &[u8] { - &self.0 - } +/// Zingo-Proxy orchard treestate. +/// +/// A treestate that is included in the [`z_gettreestate`][1] RPC response. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxyOrchardTreestate { + /// Sapling note commitment tree. + pub commitments: ProxyCommitments, } /// Contains the hex-encoded Sapling & Orchard note commitment trees, and their /// corresponding [`block::Hash`], [`Height`], and block time. /// /// This is used for the output parameter of [`JsonRpcConnector::get_treestate`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub struct GetTreestateResponse { - /// The block hash corresponding to the treestate, hex-encoded. - #[serde(with = "hex")] - pub hash: block::Hash, - /// The block height corresponding to the treestate, numeric. - pub height: Height, + pub height: i32, - /// Unix time when the block corresponding to the treestate was mined, - /// numeric. + /// The block hash corresponding to the treestate, hex-encoded. + pub hash: String, + + /// Unix time when the block corresponding to the treestate was mined, numeric. /// /// UTC seconds since the Unix 1970-01-01 epoch. pub time: u32, /// A treestate containing a Sapling note commitment tree, hex-encoded. - #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - pub sapling: ProxyTreestate, + pub sapling: ProxySaplingTreestate, /// A treestate containing an Orchard note commitment tree, hex-encoded. - #[serde(skip_serializing_if = "ProxyTreestate::is_empty")] - pub orchard: ProxyTreestate, + pub orchard: ProxyOrchardTreestate, +} + +impl<'de> Deserialize<'de> for GetTreestateResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = serde_json::Value::deserialize(deserializer)?; + let height = v["height"] + .as_i64() + .ok_or_else(|| serde::de::Error::missing_field("height"))? as i32; + let hash = v["hash"] + .as_str() // This directly accesses the string value + .ok_or_else(|| serde::de::Error::missing_field("hash"))? // Converts Option to Result + .to_string(); + let time = v["time"] + .as_i64() + .ok_or_else(|| serde::de::Error::missing_field("time"))? as u32; + let sapling_final_state = v["sapling"]["commitments"]["finalState"] + .as_str() + .ok_or_else(|| serde::de::Error::missing_field("sapling final state"))? + .to_string(); + let orchard_final_state = v["orchard"]["commitments"]["finalState"] + .as_str() + .ok_or_else(|| serde::de::Error::missing_field("orchard final state"))? + .to_string(); + Ok(GetTreestateResponse { + height, + hash, + time, + sapling: ProxySaplingTreestate { + commitments: ProxyCommitments { + final_state: sapling_final_state, + }, + }, + orchard: ProxyOrchardTreestate { + commitments: ProxyCommitments { + final_state: orchard_final_state, + }, + }, + }) + } } /// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. @@ -518,18 +428,82 @@ pub struct GetSubtreesResponse { pub subtrees: Vec, } +/// A serialized transaction. +/// +/// Stores bytes that are guaranteed to be deserializable into a [`Transaction`]. +/// +/// Sorts in lexicographic order of the transaction's serialized data. +#[derive(Clone, Eq, PartialEq, serde::Serialize)] +pub struct ProxySerializedTransaction { + /// Transaction bytes. + pub bytes: Vec, +} + +impl std::fmt::Display for ProxySerializedTransaction { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(&hex::encode(&self.bytes)) + } +} + +impl std::fmt::Debug for ProxySerializedTransaction { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let data_hex = hex::encode(&self.bytes); + + f.debug_tuple("ProxySerializedTransaction") + .field(&data_hex) + .finish() + } +} + +impl AsRef<[u8]> for ProxySerializedTransaction { + fn as_ref(&self) -> &[u8] { + self.bytes.as_ref() + } +} + +impl From> for ProxySerializedTransaction { + fn from(bytes: Vec) -> Self { + Self { bytes } + } +} + +impl FromHex for ProxySerializedTransaction { + type Error = as FromHex>::Error; + + fn from_hex>(hex: T) -> Result { + let bytes = >::from_hex(hex)?; + + Ok(bytes.into()) + } +} + +impl<'de> Deserialize<'de> for ProxySerializedTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = serde_json::Value::deserialize(deserializer)?; + if let Some(hex_str) = v.as_str() { + let bytes = hex::decode(hex_str).map_err(serde::de::Error::custom)?; + Ok(ProxySerializedTransaction { bytes }) + } else { + Err(serde::de::Error::custom("expected a hex string")) + } + } +} + /// Contains raw transaction, encoded as hex bytes. /// /// This is used for the output parameter of [`JsonRpcConnector::get_raw_transaction`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum GetTransactionResponse { /// The raw transaction, encoded as hex bytes. - Raw(#[serde(with = "hex")] SerializedTransaction), + Raw(#[serde(with = "hex")] ProxySerializedTransaction), /// The transaction object. Object { /// The raw transaction, encoded as hex bytes. #[serde(with = "hex")] - hex: SerializedTransaction, + hex: ProxySerializedTransaction, /// The height of the block in the best chain that contains the transaction, or -1 if /// the transaction is in the mempool. height: i32, @@ -539,6 +513,26 @@ pub enum GetTransactionResponse { }, } +impl<'de> Deserialize<'de> for GetTransactionResponse { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = serde_json::Value::deserialize(deserializer)?; + if v.get("height").is_some() && v.get("confirmations").is_some() { + let obj = GetTransactionResponse::Object { + hex: serde_json::from_value(v["hex"].clone()).unwrap(), + height: v["height"].as_i64().unwrap() as i32, + confirmations: v["confirmations"].as_u64().unwrap() as u32, + }; + Ok(obj) + } else { + let raw = GetTransactionResponse::Raw(serde_json::from_value(v.clone()).unwrap()); + Ok(raw) + } + } +} + /// Zingo-Proxy encoding of a Bitcoin script. #[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ProxyScript { diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 258c8e0..8036fb7 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -1,24 +1,57 @@ //! Lightwallet service RPC implementations. use hex::FromHex; +use tokio_stream::wrappers::ReceiverStream; use zcash_client_backend::proto::{ compact_formats::{CompactBlock, CompactTx}, service::{ - compact_tx_streamer_server::CompactTxStreamer, Address, AddressList, Balance, BlockId, - BlockRange, ChainSpec, Empty, Exclude, GetAddressUtxosArg, GetAddressUtxosReply, - GetAddressUtxosReplyList, GetSubtreeRootsArg, LightdInfo, PingResponse, RawTransaction, - SendResponse, SubtreeRoot, TransparentAddressBlockFilter, TreeState, TxFilter, + compact_tx_streamer_server::CompactTxStreamer, Address, Balance, BlockId, BlockRange, + Empty, GetAddressUtxosReply, LightdInfo, RawTransaction, SubtreeRoot, }, }; use zebra_chain::block::Height; use crate::{ define_grpc_passthrough, - jsonrpc::{connector::JsonRpcConnector, primitives::ProxyConsensusBranchIdHex}, + jsonrpc::{ + connector::JsonRpcConnector, + primitives::{GetTransactionResponse, ProxyConsensusBranchIdHex}, + }, primitives::ProxyClient, utils::get_build_info, }; +/// Stream of RawTransactions, output type of get_taddress_txids. +pub struct RawTransactionStream { + inner: ReceiverStream>, +} + +impl RawTransactionStream { + /// Returns new instanse of RawTransactionStream. + pub fn new(rx: tokio::sync::mpsc::Receiver>) -> Self { + RawTransactionStream { + inner: ReceiverStream::new(rx), + } + } +} + +impl futures::Stream for RawTransactionStream { + type Item = Result; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let poll = std::pin::Pin::new(&mut self.inner).poll_next(cx); + match poll { + std::task::Poll::Ready(Some(Ok(raw_tx))) => std::task::Poll::Ready(Some(Ok(raw_tx))), + std::task::Poll::Ready(Some(Err(e))) => std::task::Poll::Ready(Some(Err(e))), + std::task::Poll::Ready(None) => std::task::Poll::Ready(None), + std::task::Poll::Pending => std::task::Poll::Pending, + } + } +} + impl CompactTxStreamer for ProxyClient { /// Return the height of the tip of the best chain. fn get_latest_block<'life0, 'async_trait>( @@ -70,6 +103,8 @@ impl CompactTxStreamer for ProxyClient { /// /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). + /// + /// NOTE: This RPC should be implemented alongside the block cache. fn get_block<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -139,6 +174,10 @@ impl CompactTxStreamer for ProxyClient { type GetBlockRangeStream = tonic::Streaming; // /// Return a list of consecutive compact blocks. + // /// + // /// NOTE: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + // /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. + // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. // fn get_block_range<'life0, 'async_trait>( // &'life0 self, // request: tonic::Request, @@ -205,34 +244,67 @@ impl CompactTxStreamer for ProxyClient { // ) -> self::getblockrangenullifiersstream // ); - // /// Return the requested full (not compact) transaction (as from zcashd). - // fn get_transaction<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_transaction."); - // Box::pin(async { todo!("get_transaction not yet implemented") }) - // } - define_grpc_passthrough!( - fn get_transaction( - &self, - request: tonic::Request, - ) -> RawTransaction - ); + /// Return the requested full (not compact) transaction (as from zcashd). + fn get_transaction<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_transaction."); + Box::pin(async { + let hash = request.into_inner().hash; + if hash.len() == 32 { + let reversed_hash = hash.iter().rev().copied().collect::>(); + let hash_hex = hex::encode(reversed_hash); + let tx = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .get_raw_transaction(hash_hex, Some(1)) + .await + .map_err(|e| e.to_grpc_status())?; + + let (hex, height) = if let GetTransactionResponse::Object { hex, height, .. } = tx { + (hex, height) + } else { + return Err(tonic::Status::not_found("Transaction not received")); + }; + + // TODO: Remove unwrap on height and handle error. + Ok(tonic::Response::new( + zcash_client_backend::proto::service::RawTransaction { + data: hex.bytes, + height: height.try_into().unwrap(), + }, + )) + } else { + Err(tonic::Status::invalid_argument( + "Transaction hash incorrect", + )) + } + }) + } + // define_grpc_passthrough!( + // fn get_transaction( + // &self, + // request: tonic::Request, + // ) -> RawTransaction + // ); /// Submit the given transaction to the Zcash network. fn send_transaction<'life0, 'async_trait>( @@ -283,38 +355,108 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetTaddressTxids method. #[doc = "Server streaming response type for the GetTaddressTxids method."] - type GetTaddressTxidsStream = tonic::Streaming; + // type GetTaddressTxidsStream = tonic::Streaming; + type GetTaddressTxidsStream = std::pin::Pin>; - // /// Return the txids corresponding to the given t-address within the given block range. - // fn get_taddress_txids<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request< - // zcash_client_backend::proto::service::TransparentAddressBlockFilter, - // >, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_taddress_txids."); - // Box::pin(async { todo!("get_taddress_txids not yet implemented") }) - // } - define_grpc_passthrough!( - fn get_taddress_txids( - &self, - request: tonic::Request, - ) -> Self::GetTaddressTxidsStream - ); + /// This name is misleading, returns the full transactions that have either inputs or outputs connected to the given transparent address. + fn get_taddress_txids<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request< + zcash_client_backend::proto::service::TransparentAddressBlockFilter, + >, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_taddress_txids."); + Box::pin(async move { + let block_filter = request.into_inner(); + let address = block_filter.address; + let start = block_filter + .range + .clone() + .and_then(|r| r.start) + .map(|s| s.height as u32) + .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + let end = block_filter + .range + .and_then(|r| r.end) + .map(|e| e.height as u32) + .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + + let zebrad_client = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + let txids = zebrad_client + .get_address_txids(vec![address], start, end) + .await + .map_err(|e| e.to_grpc_status())?; + + let (tx, rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + for txid in txids.transactions { + let transaction = zebrad_client.get_raw_transaction(txid, Some(1)).await; + match transaction { + Ok(GetTransactionResponse::Object { hex, height, .. }) => { + if tx + .send(Ok(RawTransaction { + data: hex.bytes, + height: height as u64, + })) + .await + .is_err() + { + break; + } + } + Ok(GetTransactionResponse::Raw(_)) => { + if tx + .send(Err(tonic::Status::internal( + "Received raw transaction type, this should not be impossible.", + ))) + .await + .is_err() + { + break; + } + } + Err(e) => { + if tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + }); + let output_stream = RawTransactionStream::new(rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_taddress_txids( + // &self, + // request: tonic::Request, + // ) -> Self::GetTaddressTxidsStream + // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). @@ -452,37 +594,72 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetMempoolStreamStream ); - // /// GetTreeState returns the note commitment tree state corresponding to the given block. - // /// See section 3.7 of the Zcash protocol specification. It returns several other useful - // /// values also (even though they can be obtained using GetBlock). - // /// The block can be specified by either height or hash. - // fn get_tree_state<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_tree_state."); - // Box::pin(async { todo!("get_tree_state not yet implemented") }) - // } - define_grpc_passthrough!( - fn get_tree_state( - &self, - request: tonic::Request, - ) -> TreeState - ); + /// GetTreeState returns the note commitment tree state corresponding to the given block. + /// See section 3.7 of the Zcash protocol specification. It returns several other useful + /// values also (even though they can be obtained using GetBlock). + /// The block can be specified by either height or hash. + fn get_tree_state<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_tree_state."); + Box::pin(async { + let block_id = request.into_inner(); + let hash_or_height = if block_id.height != 0 { + block_id.height.to_string() + } else { + hex::encode(block_id.hash) + }; + + let zebrad_client = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + + // TODO: This is slow. Chain, along with other blockchain info should be saved on startup and used here [blockcache?]. + let chain = zebrad_client + .get_blockchain_info() + .await + .map_err(|e| e.to_grpc_status())? + .chain; + let treestate = zebrad_client + .get_treestate(hash_or_height) + .await + .map_err(|e| e.to_grpc_status())?; + Ok(tonic::Response::new( + zcash_client_backend::proto::service::TreeState { + network: chain, + height: treestate.height as u64, + hash: treestate.hash.to_string(), + time: treestate.time, + sapling_tree: treestate.sapling.commitments.final_state.to_string(), + orchard_tree: treestate.orchard.commitments.final_state.to_string(), + }, + )) + }) + } + // define_grpc_passthrough!( + // fn get_tree_state( + // &self, + // request: tonic::Request, + // ) -> TreeState + // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). From dfdd17c5ffeb0300f83207aaeeae62e2ae0c7c9e Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 4 Jun 2024 16:30:58 +0100 Subject: [PATCH 17/40] started implementing get_block_range - parser required first --- zingo-rpc/src/rpc/service.rs | 224 +++++++++++++++++++++++++++++------ 1 file changed, 188 insertions(+), 36 deletions(-) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 8036fb7..e5107d8 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -3,7 +3,7 @@ use hex::FromHex; use tokio_stream::wrappers::ReceiverStream; use zcash_client_backend::proto::{ - compact_formats::{CompactBlock, CompactTx}, + compact_formats::{ChainMetadata, CompactBlock, CompactTx}, service::{ compact_tx_streamer_server::CompactTxStreamer, Address, Balance, BlockId, BlockRange, Empty, GetAddressUtxosReply, LightdInfo, RawTransaction, SubtreeRoot, @@ -15,7 +15,7 @@ use crate::{ define_grpc_passthrough, jsonrpc::{ connector::JsonRpcConnector, - primitives::{GetTransactionResponse, ProxyConsensusBranchIdHex}, + primitives::{GetBlockResponse, GetTransactionResponse, ProxyConsensusBranchIdHex}, }, primitives::ProxyClient, utils::get_build_info, @@ -52,6 +52,37 @@ impl futures::Stream for RawTransactionStream { } } +/// Stream of CompactBlocks, output type of get_block_range. +pub struct CompactBlockStream { + inner: ReceiverStream>, +} + +impl CompactBlockStream { + /// Returns new instanse of CompactBlockStream. + pub fn new(rx: tokio::sync::mpsc::Receiver>) -> Self { + CompactBlockStream { + inner: ReceiverStream::new(rx), + } + } +} + +impl futures::Stream for CompactBlockStream { + type Item = Result; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + let poll = std::pin::Pin::new(&mut self.inner).poll_next(cx); + match poll { + std::task::Poll::Ready(Some(Ok(raw_tx))) => std::task::Poll::Ready(Some(Ok(raw_tx))), + std::task::Poll::Ready(Some(Err(e))) => std::task::Poll::Ready(Some(Err(e))), + std::task::Poll::Ready(None) => std::task::Poll::Ready(None), + std::task::Poll::Pending => std::task::Poll::Pending, + } + } +} + impl CompactTxStreamer for ProxyClient { /// Return the height of the tip of the best chain. fn get_latest_block<'life0, 'async_trait>( @@ -171,40 +202,159 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - type GetBlockRangeStream = tonic::Streaming; - - // /// Return a list of consecutive compact blocks. - // /// - // /// NOTE: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - // /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. - // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - // fn get_block_range<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_block_range."); - // Box::pin(async { todo!("get_block_range not yet implemented") }) - // } - define_grpc_passthrough!( - fn get_block_range( - &self, - request: tonic::Request, - ) -> Self::GetBlockRangeStream - ); + // type GetBlockRangeStream = tonic::Streaming; + type GetBlockRangeStream = std::pin::Pin>; + + /// Return a list of consecutive compact blocks. + /// + /// NOTE: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. + /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. + fn get_block_range<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_range."); + let zebrad_uri = self.zebrad_uri.clone(); + Box::pin(async move { + let blockrange = request.into_inner(); + let mut start = blockrange + .start + .map(|s| s.height as u32) + .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + let mut end = blockrange + .end + .map(|e| e.height as u32) + .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + if start > end { + (start, end) = (end, start); + } + + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + let zebrad_client = JsonRpcConnector::new( + zebrad_uri, + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + + for height in start..end { + let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; + match block_1 { + Ok(GetBlockResponse::Object { + hash, + confirmations: _, + height, + time, + tx, + trees, + }) => { + let block_0 = + zebrad_client.get_block(hash.0.to_string(), Some(2)).await; + match block_0 { + Ok(GetBlockResponse::Object { + hash: _, + confirmations: _, + height: _, + time: _, + tx: _, + trees: _, + }) => { + if channel_tx + .send(Err(tonic::Status::internal("Received object block type, this should not be impossible here.", + ))) + .await + .is_err() + { + break; + } + } + Ok(GetBlockResponse::Raw(block_hex)) => { + let block_hash: Vec = todo!(); //block_hash_0; + let block_height: u64 = height.unwrap().0 as u64; + let block_time: u32 = time.unwrap() as u32; + let block_tx: Vec = todo!(); //tx; + let block_metadata: Option = Some(ChainMetadata { + sapling_commitment_tree_size: todo!(), //trees.sapling.size, + orchard_commitment_tree_size: todo!(), //trees.orchard.size, + }); + if channel_tx + .send(Ok(CompactBlock { + proto_version: todo!(), + height: block_height, + hash: block_hash, + prev_hash: todo!(), + time: block_time, + header: todo!(), + vtx: block_tx, + chain_metadata: block_metadata, + })) + .await + .is_err() + { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + Ok(GetBlockResponse::Raw(_)) => { + if channel_tx + .send(Err(tonic::Status::internal( + "Received raw block type, this should not be impossible here.", + ))) + .await + .is_err() + { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + }); + let output_stream = CompactBlockStream::new(channel_rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_block_range( + // &self, + // request: tonic::Request, + // ) -> Self::GetBlockRangeStream + // ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] @@ -359,6 +509,8 @@ impl CompactTxStreamer for ProxyClient { type GetTaddressTxidsStream = std::pin::Pin>; /// This name is misleading, returns the full transactions that have either inputs or outputs connected to the given transparent address. + /// + /// TODO: Add 30 second timout. fn get_taddress_txids<'life0, 'async_trait>( &'life0 self, request: tonic::Request< From cfa1d21fd8cff0504f94bfbff03f3b22f97dcd63 Mon Sep 17 00:00:00 2001 From: idky137 Date: Wed, 5 Jun 2024 14:15:51 +0100 Subject: [PATCH 18/40] updated todos --- zingo-rpc/src/rpc/service.rs | 302 +++++++++++++++++------------------ 1 file changed, 151 insertions(+), 151 deletions(-) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index e5107d8..88523a3 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -135,7 +135,7 @@ impl CompactTxStreamer for ProxyClient { /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). /// - /// NOTE: This RPC should be implemented alongside the block cache. + /// TODO: This RPC should be implemented alongside the block cache. fn get_block<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -202,159 +202,159 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - // type GetBlockRangeStream = tonic::Streaming; - type GetBlockRangeStream = std::pin::Pin>; + type GetBlockRangeStream = tonic::Streaming; + // type GetBlockRangeStream = std::pin::Pin>; - /// Return a list of consecutive compact blocks. - /// - /// NOTE: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. - /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - fn get_block_range<'life0, 'async_trait>( - &'life0 self, - request: tonic::Request, - ) -> core::pin::Pin< - Box< - dyn core::future::Future< - Output = std::result::Result< - tonic::Response, - tonic::Status, - >, - > + core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { - println!("@zingoproxyd: Received call of get_block_range."); - let zebrad_uri = self.zebrad_uri.clone(); - Box::pin(async move { - let blockrange = request.into_inner(); - let mut start = blockrange - .start - .map(|s| s.height as u32) - .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - let mut end = blockrange - .end - .map(|e| e.height as u32) - .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - if start > end { - (start, end) = (end, start); - } + // /// Return a list of consecutive compact blocks. + // /// + // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + // /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. + // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. + // fn get_block_range<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_block_range."); + // let zebrad_uri = self.zebrad_uri.clone(); + // Box::pin(async move { + // let blockrange = request.into_inner(); + // let mut start = blockrange + // .start + // .map(|s| s.height as u32) + // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + // let mut end = blockrange + // .end + // .map(|e| e.height as u32) + // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + // if start > end { + // (start, end) = (end, start); + // } - let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - tokio::spawn(async move { - let zebrad_client = JsonRpcConnector::new( - zebrad_uri, - Some("xxxxxx".to_string()), - Some("xxxxxx".to_string()), - ) - .await; + // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + // tokio::spawn(async move { + // let zebrad_client = JsonRpcConnector::new( + // zebrad_uri, + // Some("xxxxxx".to_string()), + // Some("xxxxxx".to_string()), + // ) + // .await; - for height in start..end { - let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; - match block_1 { - Ok(GetBlockResponse::Object { - hash, - confirmations: _, - height, - time, - tx, - trees, - }) => { - let block_0 = - zebrad_client.get_block(hash.0.to_string(), Some(2)).await; - match block_0 { - Ok(GetBlockResponse::Object { - hash: _, - confirmations: _, - height: _, - time: _, - tx: _, - trees: _, - }) => { - if channel_tx - .send(Err(tonic::Status::internal("Received object block type, this should not be impossible here.", - ))) - .await - .is_err() - { - break; - } - } - Ok(GetBlockResponse::Raw(block_hex)) => { - let block_hash: Vec = todo!(); //block_hash_0; - let block_height: u64 = height.unwrap().0 as u64; - let block_time: u32 = time.unwrap() as u32; - let block_tx: Vec = todo!(); //tx; - let block_metadata: Option = Some(ChainMetadata { - sapling_commitment_tree_size: todo!(), //trees.sapling.size, - orchard_commitment_tree_size: todo!(), //trees.orchard.size, - }); - if channel_tx - .send(Ok(CompactBlock { - proto_version: todo!(), - height: block_height, - hash: block_hash, - prev_hash: todo!(), - time: block_time, - header: todo!(), - vtx: block_tx, - chain_metadata: block_metadata, - })) - .await - .is_err() - { - break; - } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } - } - } - Ok(GetBlockResponse::Raw(_)) => { - if channel_tx - .send(Err(tonic::Status::internal( - "Received raw block type, this should not be impossible here.", - ))) - .await - .is_err() - { - break; - } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } - } - } - }); - let output_stream = CompactBlockStream::new(channel_rx); - let stream_boxed = Box::pin(output_stream); - Ok(tonic::Response::new(stream_boxed)) - }) - } - // define_grpc_passthrough!( - // fn get_block_range( - // &self, - // request: tonic::Request, - // ) -> Self::GetBlockRangeStream - // ); + // for height in start..end { + // let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; + // match block_1 { + // Ok(GetBlockResponse::Object { + // hash, + // confirmations: _, + // height, + // time, + // tx, + // trees, + // }) => { + // let block_0 = + // zebrad_client.get_block(hash.0.to_string(), Some(2)).await; + // match block_0 { + // Ok(GetBlockResponse::Object { + // hash: _, + // confirmations: _, + // height: _, + // time: _, + // tx: _, + // trees: _, + // }) => { + // if channel_tx + // .send(Err(tonic::Status::internal("Received object block type, this should not be impossible here.", + // ))) + // .await + // .is_err() + // { + // break; + // } + // } + // Ok(GetBlockResponse::Raw(block_hex)) => { + // let block_hash: Vec = todo!(); //block_hash_0; + // let block_height: u64 = height.unwrap().0 as u64; + // let block_time: u32 = time.unwrap() as u32; + // let block_tx: Vec = todo!(); //tx; + // let block_metadata: Option = Some(ChainMetadata { + // sapling_commitment_tree_size: todo!(), //trees.sapling.size, + // orchard_commitment_tree_size: todo!(), //trees.orchard.size, + // }); + // if channel_tx + // .send(Ok(CompactBlock { + // proto_version: todo!(), + // height: block_height, + // hash: block_hash, + // prev_hash: todo!(), + // time: block_time, + // header: todo!(), + // vtx: block_tx, + // chain_metadata: block_metadata, + // })) + // .await + // .is_err() + // { + // break; + // } + // } + // Err(e) => { + // if channel_tx + // .send(Err(tonic::Status::internal(e.to_string()))) + // .await + // .is_err() + // { + // break; + // } + // } + // } + // } + // Ok(GetBlockResponse::Raw(_)) => { + // if channel_tx + // .send(Err(tonic::Status::internal( + // "Received raw block type, this should not be impossible here.", + // ))) + // .await + // .is_err() + // { + // break; + // } + // } + // Err(e) => { + // if channel_tx + // .send(Err(tonic::Status::internal(e.to_string()))) + // .await + // .is_err() + // { + // break; + // } + // } + // } + // } + // }); + // let output_stream = CompactBlockStream::new(channel_rx); + // let stream_boxed = Box::pin(output_stream); + // Ok(tonic::Response::new(stream_boxed)) + // }) + // } + define_grpc_passthrough!( + fn get_block_range( + &self, + request: tonic::Request, + ) -> Self::GetBlockRangeStream + ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] From b1b09d398c9e50a51ec637f6666edcb6598f5f7d Mon Sep 17 00:00:00 2001 From: idky137 Date: Wed, 5 Jun 2024 15:28:55 +0100 Subject: [PATCH 19/40] removed extern crate --- zingo-proxyd/src/bin/zingoproxyd.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zingo-proxyd/src/bin/zingoproxyd.rs b/zingo-proxyd/src/bin/zingoproxyd.rs index 0880b0a..b5ea496 100644 --- a/zingo-proxyd/src/bin/zingoproxyd.rs +++ b/zingo-proxyd/src/bin/zingoproxyd.rs @@ -10,7 +10,7 @@ use std::{ use zingoproxylib::proxy::spawn_proxy; -extern crate ctrlc; +use ctrlc; #[tokio::main] async fn main() { From f4d6e2870d7b44875111b79a5e7d7f0fa05f988e Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 6 Jun 2024 13:51:57 +0100 Subject: [PATCH 20/40] started adding block parser --- zingo-rpc/src/blockcache.rs | 5 ++ zingo-rpc/src/blockcache/block.rs | 39 +++++++++++ zingo-rpc/src/blockcache/transaction.rs | 88 +++++++++++++++++++++++++ zingo-rpc/src/blockcache/utils.rs | 14 ++++ zingo-rpc/src/lib.rs | 1 + 5 files changed, 147 insertions(+) create mode 100644 zingo-rpc/src/blockcache.rs create mode 100644 zingo-rpc/src/blockcache/block.rs create mode 100644 zingo-rpc/src/blockcache/transaction.rs create mode 100644 zingo-rpc/src/blockcache/utils.rs diff --git a/zingo-rpc/src/blockcache.rs b/zingo-rpc/src/blockcache.rs new file mode 100644 index 0000000..0525271 --- /dev/null +++ b/zingo-rpc/src/blockcache.rs @@ -0,0 +1,5 @@ +//! Zingo-Proxy Block Cache and Mempool State Engine. + +pub mod block; +pub mod transaction; +pub mod utils; diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs new file mode 100644 index 0000000..37d9dfc --- /dev/null +++ b/zingo-rpc/src/blockcache/block.rs @@ -0,0 +1,39 @@ +//! Block fetching and deserialization functionality. + +/// Block header data. +#[derive(Debug)] +pub struct BlockHeaderData { + pub version: i32, + pub hash_prev_block: Vec, + pub hash_merkle_root: Vec, + pub hash_final_sapling_root: Vec, + pub time: u32, + pub n_bits_bytes: Vec, + pub nonce: Vec, + pub solution: Vec, +} + +/// Complete block header. +#[derive(Debug)] +pub struct FullBlockHeader { + pub raw_block_header: BlockHeaderData, + pub cached_hash: Vec, +} + +/// Zingo-Proxy Block. +#[derive(Debug)] +pub struct FullBlock { + pub hdr: FullBlockHeader, + pub vtx: Vec, + pub height: i32, +} + +// impl parse_from_slice for block_header(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_from_slice for full_block(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_full_block(&[u8]) -> Result + +// impl to_compact(Self) -> Result + +// impl parse_to_compact(&[u8]) -> Result diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs new file mode 100644 index 0000000..f7723d1 --- /dev/null +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -0,0 +1,88 @@ +//! Transaction fetching and deserialization functionality. + +/// Transparent input. +#[derive(Debug)] +pub struct TxIn { + pub script_sig: Vec, +} + +/// Transparent output. +#[derive(Debug)] +pub struct TxOut { + pub value: u64, +} + +/// Sapling input. +#[derive(Debug)] +pub struct Spend { + pub nullifier: Vec, +} + +/// Sapling output. +#[derive(Debug)] +pub struct Output { + pub cmu: Vec, + pub ephemeral_key: Vec, + pub enc_ciphertext: Vec, +} + +#[derive(Debug)] +pub struct JoinSplit { + // Define fields based on your needs +} + +/// Orchard actions. +#[derive(Debug)] +pub struct Action { + pub nullifier: Vec, + pub cmx: Vec, + pub ephemeral_key: Vec, + pub enc_ciphertext: Vec, +} + +/// Raw transactrion. +#[derive(Debug)] +pub struct TransactionData { + pub f_overwintered: bool, + pub version: u32, + pub n_version_group_id: u32, + pub consensus_branch_id: u32, + pub transparent_inputs: Vec, + pub transparent_outputs: Vec, + pub shielded_spends: Vec, + pub shielded_outputs: Vec, + pub join_splits: Vec, + pub orchard_actions: Vec, +} + +/// Zingo-Proxy transaction data. +#[derive(Debug)] +pub struct FullTransaction { + pub raw_transaction: TransactionData, + pub raw_bytes: Vec, + pub tx_id: Vec, +} + +// impl parse_from_slice for TxIn(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_from_slice for TxOut(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// imple parse_transparent(&[u8]) -> Result<(????, &[u8]), ParseError> + +// impl parse_from_slice for Spend(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_from_slice for Output(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_from_slice for JoinSplit(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_from_slice for Action(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl parse_v4(&[u8]) -> Result<(????, &[u8]), ParseError> + +// impl parse_v5(&[u8]) -> Result<(????, &[u8]), ParseError> + +// impl parse_from_slice for transaction(&[u8]) -> Result<(Self, &[u8]), ParseError> + +// impl to_compact(Self) -> Result + +// impl parse_to_compact(&[u8]) -> Result<(compact_transaction, &[u8]), Error> diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs new file mode 100644 index 0000000..6c3e7e0 --- /dev/null +++ b/zingo-rpc/src/blockcache/utils.rs @@ -0,0 +1,14 @@ +//! Blockcache utility functionality. + +/// Parser Error Type. +#[derive(Debug)] +pub enum ParseError { + Io(std::io::Error), + InvalidData(String), +} + +trait ParseFromSlice { + fn parse_from_slice(data: &[u8]) -> Result<(&[u8], Self), ParseError> + where + Self: Sized; +} diff --git a/zingo-rpc/src/lib.rs b/zingo-rpc/src/lib.rs index 30f6833..195644d 100644 --- a/zingo-rpc/src/lib.rs +++ b/zingo-rpc/src/lib.rs @@ -3,6 +3,7 @@ #![warn(missing_docs)] #![forbid(unsafe_code)] +pub mod blockcache; pub mod jsonrpc; pub mod nym; pub mod primitives; From af5cc8a6bb8c2bd8099f0e839918f77751e28d53 Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 6 Jun 2024 16:49:48 +0100 Subject: [PATCH 21/40] added block and transaction structs --- zingo-rpc/src/blockcache/block.rs | 79 +++++++++++++++- zingo-rpc/src/blockcache/transaction.rs | 119 ++++++++++++++++++++++-- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index 37d9dfc..100dc22 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -1,30 +1,107 @@ //! Block fetching and deserialization functionality. -/// Block header data. +/// A block header, containing metadata about a block. +/// +/// How are blocks chained together? They are chained together via the +/// backwards reference (previous header hash) present in the block +/// header. Each block points backwards to its parent, all the way +/// back to the genesis block (the first block in the blockchain). #[derive(Debug)] pub struct BlockHeaderData { + /// The block's version field. This is supposed to be `4`: + /// + /// > The current and only defined block version number for Zcash is 4. + /// + /// but this was not enforced by the consensus rules, and defective mining + /// software created blocks with other versions, so instead it's effectively + /// a free field. The only constraint is that it must be at least `4` when + /// interpreted as an `i32`. + /// + /// Size [bytes]: 4 pub version: i32, + + /// The hash of the previous block, used to create a chain of blocks back to + /// the genesis block. + /// + /// This ensures no previous block can be changed without also changing this + /// block's header. + /// + /// Size [bytes]: 32 pub hash_prev_block: Vec, + + /// The root of the Bitcoin-inherited transaction Merkle tree, binding the + /// block header to the transactions in the block. + /// + /// Note that because of a flaw in Bitcoin's design, the `merkle_root` does + /// not always precisely bind the contents of the block (CVE-2012-2459). It + /// is sometimes possible for an attacker to create multiple distinct sets of + /// transactions with the same Merkle root, although only one set will be + /// valid. + /// + /// Size [bytes]: 32 pub hash_merkle_root: Vec, + + /// [Pre-Sapling] A reserved field which should be ignored. + /// [Sapling onward] The root LEBS2OSP_256(rt) of the Sapling note + /// commitment tree corresponding to the final Sapling treestate of this + /// block. + /// + /// Size [bytes]: 32 pub hash_final_sapling_root: Vec, + + /// The block timestamp is a Unix epoch time (UTC) when the miner + /// started hashing the header (according to the miner). + /// + /// Size [bytes]: 4 pub time: u32, + + /// An encoded version of the target threshold this block's header + /// hash must be less than or equal to, in the same nBits format + /// used by Bitcoin. + /// + /// For a block at block height `height`, bits MUST be equal to + /// `ThresholdBits(height)`. + /// + /// [Bitcoin-nBits](https://bitcoin.org/en/developer-reference#target-nbits) + /// + /// Size [bytes]: 4 pub n_bits_bytes: Vec, + + /// An arbitrary field that miners can change to modify the header + /// hash in order to produce a hash less than or equal to the + /// target threshold. + /// + /// Size [bytes]: 32 pub nonce: Vec, + + /// The Equihash solution. + /// + /// Size [bytes]: CompactLength pub solution: Vec, } /// Complete block header. #[derive(Debug)] pub struct FullBlockHeader { + /// Block header data. pub raw_block_header: BlockHeaderData, + + /// Hash of the current block. pub cached_hash: Vec, } /// Zingo-Proxy Block. #[derive(Debug)] pub struct FullBlock { + /// The block header, containing block metadata. + /// + /// Size [bytes]: 140+CompactLength pub hdr: FullBlockHeader, + + /// The block transactions. pub vtx: Vec, + + /// Block height. pub height: i32, } diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index f7723d1..59aa3c9 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -1,65 +1,168 @@ //! Transaction fetching and deserialization functionality. -/// Transparent input. +/// Txin format as described in https://en.bitcoin.it/wiki/Transaction #[derive(Debug)] pub struct TxIn { + // PrevTxHash [IGNORED] - Size[bytes]: 32 + // PrevTxOutIndex [IGNORED] - Size[bytes]: 4 + /// CompactSize-prefixed, could be a pubkey or a script + /// + /// Size[bytes]: CompactSize pub script_sig: Vec, + // SequenceNumber [IGNORED] - Size[bytes]: 4 } -/// Transparent output. +/// Txout format as described in https://en.bitcoin.it/wiki/Transaction #[derive(Debug)] pub struct TxOut { + /// Non-negative int giving the number of zatoshis to be transferred + /// + /// Size[bytes]: 8 pub value: u64, + // Script [IGNORED] - Size[bytes]: CompactSize } -/// Sapling input. +/// spend is a Sapling Spend Description as described in 7.3 of the Zcash +/// protocol specification. #[derive(Debug)] pub struct Spend { + // Cv [IGNORED] - Size[bytes]: 32 + // Anchor [IGNORED] - Size[bytes]: 32 + /// A nullifier to a sapling note. + /// + /// Size[bytes]: 32 pub nullifier: Vec, + // Rk [IGNORED] - Size[bytes]: 32 + // Zkproof [IGNORED] - Size[bytes]: 192 + // SpendAuthSig [IGNORED] - Size[bytes]: 64 } -/// Sapling output. +/// output is a Sapling Output Description as described in section 7.4 of the +/// Zcash protocol spec. #[derive(Debug)] pub struct Output { + // Cv [IGNORED] - Size[bytes]: 32 + /// U-coordinate of the note commitment, derived from the note's value, recipient, and a + /// random value. + /// + /// Size[bytes]: 32 pub cmu: Vec, + /// Ephemeral public key for Diffie-Hellman key exchange. + /// + /// Size[bytes]: 32 pub ephemeral_key: Vec, + /// Encrypted transaction details including value transferred and an optional memo. + /// + /// Size[bytes]: 580 pub enc_ciphertext: Vec, + // OutCiphertext [IGNORED] - Size[bytes]: 80 + // Zkproof [IGNORED] - Size[bytes]: 192 } +/// joinSplit is a JoinSplit description as described in 7.2 of the Zcash +/// protocol spec. Its exact contents differ by transaction version and network +/// upgrade level. Only version 4 is supported, no need for proofPHGR13. +/// +/// NOTE: Legacy, no longer used but included for consistency. #[derive(Debug)] pub struct JoinSplit { - // Define fields based on your needs + //vpubOld [IGNORED] - Size[bytes]: 8 + //vpubNew [IGNORED] - Size[bytes]: 8 + //anchor [IGNORED] - Size[bytes]: 32 + //nullifiers [IGNORED] - Size[bytes]: 64/32 + //commitments [IGNORED] - Size[bytes]: 64/32 + //ephemeralKey [IGNORED] - Size[bytes]: 32 + //randomSeed [IGNORED] - Size[bytes]: 32 + //vmacs [IGNORED] - Size[bytes]: 64/32 + //proofGroth16 [IGNORED] - Size[bytes]: 192 + //encCiphertexts [IGNORED] - Size[bytes]: 1202 } -/// Orchard actions. +/// An Orchard action. #[derive(Debug)] pub struct Action { + // Cv [IGNORED] - Size[bytes]: 32 + /// A nullifier to a orchard note. + /// + /// Size[bytes]: 32 pub nullifier: Vec, + // Rk [IGNORED] - Size[bytes]: 32 + /// X-coordinate of the commitment to the note. + /// + /// Size[bytes]: 32 pub cmx: Vec, + /// Ephemeral public key. + /// + /// Size[bytes]: 32 pub ephemeral_key: Vec, + /// Encrypted details of the new note, including its value and recipient's data. + /// + /// Size[bytes]: 580 pub enc_ciphertext: Vec, + // OutCiphertext [IGNORED] - Size[bytes]: 80 } -/// Raw transactrion. +/// Full Zcash Transactrion data. #[derive(Debug)] pub struct TransactionData { + /// Indicates if the transaction is an Overwinter-enabled transaction. + /// + /// Size[bytes]: [in 4 byte header] pub f_overwintered: bool, + /// The transaction format version. + /// + /// Size[bytes]: [in 4 byte header] pub version: u32, + /// Version group ID, used to specify transaction type and validate its components. + /// + /// Size[bytes]: 4 pub n_version_group_id: u32, + /// Consensus branch ID, used to identify the network upgrade that the transaction is valid for. + /// + /// Size[bytes]: 4 pub consensus_branch_id: u32, + /// List of transparent inputs in a transaction. + /// + /// Size[bytes]: Vec<40+CompactSize> pub transparent_inputs: Vec, + /// List of transparent outputs in a transaction. + /// + /// Size[bytes]: Vec<8+CompactSize> pub transparent_outputs: Vec, + // NLockTime [IGNORED] - Size[bytes]: 4 + // NExpiryHeight [IGNORED] - Size[bytes]: 4 + // ValueBalanceSapling [IGNORED] - Size[bytes]: 8 + /// List of shielded spends from the Sapling pool + /// + /// Size[bytes]: Vec<384> pub shielded_spends: Vec, + /// List of shielded outputs from the Sapling pool + /// + /// Size[bytes]: Vec<948> pub shielded_outputs: Vec, + /// List of JoinSplit descriptions in a transaction, no longer supported. + /// + /// Size[bytes]: Vec<1602-1698> pub join_splits: Vec, + //joinSplitPubKey [IGNORED] - Size[bytes]: 32 + //joinSplitSig [IGNORED] - Size[bytes]: 64 + //bindingSigSapling [IGNORED] - Size[bytes]: 64 + ///List of Orchard actions. + /// + /// Size[bytes]: Vec<820> pub orchard_actions: Vec, } -/// Zingo-Proxy transaction data. +/// Zingo-Proxy struct for a full zcash transaction. #[derive(Debug)] pub struct FullTransaction { + /// Full transaction data. pub raw_transaction: TransactionData, + + /// Raw transaction bytes. pub raw_bytes: Vec, + + /// Transaction Id, fetched using get_block JsonRPC with verbose = 1. pub tx_id: Vec, } From c07033761bc1a50c69c297b66fecdc9c617d5c50 Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 6 Jun 2024 17:10:14 +0100 Subject: [PATCH 22/40] added comment to show untested code --- zingo-rpc/src/jsonrpc/connector.rs | 8 +- zingo-rpc/src/jsonrpc/primitives.rs | 184 ++++++++++++++-------------- 2 files changed, 94 insertions(+), 98 deletions(-) diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index fe89834..ce2880d 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -437,13 +437,7 @@ impl JsonRpcConnector { start: u32, end: u32, ) -> Result { - // let params = vec![ - // serde_json::to_value(addresses)?, - // serde_json::to_value(start)?, - // serde_json::to_value(end)?, - // ]; - - let params = serde_json::json!({ // Using json! macro for clarity and correctness + let params = serde_json::json!({ "addresses": addresses, "start": start, "end": end diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index d198369..881924b 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -337,97 +337,6 @@ impl<'de> Deserialize<'de> for GetTreestateResponse { } } -/// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProxySubtreeRpcData { - /// Merkle root of the 2^16-leaf subtree. - pub root: String, - /// Height of the block containing the note that completed this subtree. - pub height: Height, -} - -impl ProxySubtreeRpcData { - /// Returns new instance of ProxySubtreeRpcData - pub fn new(root: String, height: Height) -> Self { - Self { root, height } - } -} - -impl serde::Serialize for ProxySubtreeRpcData { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_struct("ProxySubtreeRpcData", 2)?; - state.serialize_field("root", &self.root)?; - state.serialize_field("height", &self.height)?; - state.end() - } -} - -impl<'de> serde::Deserialize<'de> for ProxySubtreeRpcData { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct Inner { - root: String, - height: Height, - } - - let inner = Inner::deserialize(deserializer)?; - Ok(ProxySubtreeRpcData { - root: inner.root, - height: inner.height, - }) - } -} - -impl FromHex for ProxySubtreeRpcData { - type Error = hex::FromHexError; - - fn from_hex>(hex: T) -> Result { - let hex_str = std::str::from_utf8(hex.as_ref()) - .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; - - if hex_str.len() < 8 { - return Err(hex::FromHexError::OddLength); - } - - let root_end_index = hex_str.len() - 8; - let (root_hex, height_hex) = hex_str.split_at(root_end_index); - - let root = root_hex.to_string(); - let height = u32::from_str_radix(height_hex, 16) - .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; - - Ok(ProxySubtreeRpcData { - root, - height: Height(height), - }) - } -} - -/// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, -/// and a list of subtree roots and end heights. -/// -/// This is used for the output parameter of [`JsonRpcConnector::get_subtrees_by_index`]. -#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct GetSubtreesResponse { - /// The shielded pool to which the subtrees belong. - pub pool: String, - - /// The index of the first subtree. - pub start_index: NoteCommitmentSubtreeIndex, - - /// A sequential list of complete subtrees, in `index` order. - /// - /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. - // #[serde(skip_serializing_if = "Vec::is_empty")] - pub subtrees: Vec, -} - /// A serialized transaction. /// /// Stores bytes that are guaranteed to be deserializable into a [`Transaction`]. @@ -533,6 +442,99 @@ impl<'de> Deserialize<'de> for GetTransactionResponse { } } +/// *** THE FOLLOWING CODE IS CURRENTLY UNUSED BY ZINGO-PROXY AND UNTESTED! *** + +/// Wrapper type that can hold Sapling or Orchard subtree roots with hex encoding. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ProxySubtreeRpcData { + /// Merkle root of the 2^16-leaf subtree. + pub root: String, + /// Height of the block containing the note that completed this subtree. + pub height: Height, +} + +impl ProxySubtreeRpcData { + /// Returns new instance of ProxySubtreeRpcData + pub fn new(root: String, height: Height) -> Self { + Self { root, height } + } +} + +impl serde::Serialize for ProxySubtreeRpcData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_struct("ProxySubtreeRpcData", 2)?; + state.serialize_field("root", &self.root)?; + state.serialize_field("height", &self.height)?; + state.end() + } +} + +impl<'de> serde::Deserialize<'de> for ProxySubtreeRpcData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct Inner { + root: String, + height: Height, + } + + let inner = Inner::deserialize(deserializer)?; + Ok(ProxySubtreeRpcData { + root: inner.root, + height: inner.height, + }) + } +} + +impl FromHex for ProxySubtreeRpcData { + type Error = hex::FromHexError; + + fn from_hex>(hex: T) -> Result { + let hex_str = std::str::from_utf8(hex.as_ref()) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + if hex_str.len() < 8 { + return Err(hex::FromHexError::OddLength); + } + + let root_end_index = hex_str.len() - 8; + let (root_hex, height_hex) = hex_str.split_at(root_end_index); + + let root = root_hex.to_string(); + let height = u32::from_str_radix(height_hex, 16) + .map_err(|_| hex::FromHexError::InvalidHexCharacter { c: '�', index: 0 })?; + + Ok(ProxySubtreeRpcData { + root, + height: Height(height), + }) + } +} + +/// Contains the Sapling or Orchard pool label, the index of the first subtree in the list, +/// and a list of subtree roots and end heights. +/// +/// This is used for the output parameter of [`JsonRpcConnector::get_subtrees_by_index`]. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct GetSubtreesResponse { + /// The shielded pool to which the subtrees belong. + pub pool: String, + + /// The index of the first subtree. + pub start_index: NoteCommitmentSubtreeIndex, + + /// A sequential list of complete subtrees, in `index` order. + /// + /// The generic subtree root type is a hex-encoded Sapling or Orchard subtree root string. + // #[serde(skip_serializing_if = "Vec::is_empty")] + pub subtrees: Vec, +} + /// Zingo-Proxy encoding of a Bitcoin script. #[derive(Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ProxyScript { From d56984ee078d60639ae630b815d0e401bce4fd3c Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 7 Jun 2024 16:23:56 +0100 Subject: [PATCH 23/40] added BuildInfo struct and updated get_build_info --- zingo-rpc/src/rpc/service.rs | 12 ++++++------ zingo-rpc/src/utils.rs | 29 ++++++++++++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 88523a3..960d238 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -1002,20 +1002,20 @@ impl CompactTxStreamer for ProxyClient { .get(&sapling_id) .map_or(Height(1), |sapling_json| sapling_json.activation_height); - let (git_commit, branch, build_date, build_user, version) = get_build_info(); + let build_info = get_build_info(); let lightd_info = LightdInfo { - version, + version: build_info.version, vendor: "ZingoLabs ZingoProxyD".to_string(), taddr_support: true, chain_name: blockchain_info.chain, sapling_activation_height: sapling_height.0 as u64, consensus_branch_id: blockchain_info.consensus.chain_tip.0.to_string(), block_height: blockchain_info.blocks.0 as u64, - git_commit, - branch, - build_date, - build_user, + git_commit: build_info.commit_hash, + branch: build_info.branch, + build_date: build_info.build_date, + build_user: build_info.build_user, estimated_height: blockchain_info.estimated_height.0 as u64, zcashd_build: zebra_info.build, zcashd_subversion: zebra_info.subversion, diff --git a/zingo-rpc/src/utils.rs b/zingo-rpc/src/utils.rs index e0fbdee..ba077b4 100644 --- a/zingo-rpc/src/utils.rs +++ b/zingo-rpc/src/utils.rs @@ -36,12 +36,27 @@ macro_rules! define_grpc_passthrough { }; } +/// Zingo-Proxy build info. +pub struct BuildInfo { + /// Git commit hash. + pub commit_hash: String, + /// Git Branch. + pub branch: String, + /// Build date. + pub build_date: String, + /// Build user. + pub build_user: String, + /// Zingo-Proxy version. + pub version: String, +} + /// Returns build info for Zingo-Proxy. -pub fn get_build_info() -> (String, String, String, String, String) { - let commit_hash = env!("GIT_COMMIT").to_string(); - let branch = env!("BRANCH").to_string(); - let build_date = env!("BUILD_DATE").to_string(); - let build_user = env!("BUILD_USER").to_string(); - let version = env!("VERSION").to_string(); - (commit_hash, branch, build_date, build_user, version) +pub fn get_build_info() -> BuildInfo { + BuildInfo { + commit_hash: env!("GIT_COMMIT").to_string(), + branch: env!("BRANCH").to_string(), + build_date: env!("BUILD_DATE").to_string(), + build_user: env!("BUILD_USER").to_string(), + version: env!("VERSION").to_string(), + } } From bd0c4c088424f7131f26eb96595ca5176d5e1314 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 7 Jun 2024 17:38:08 +0100 Subject: [PATCH 24/40] added gRPC server health check --- zingo-proxyd/src/proxy.rs | 50 +++++++++++++++++++++++++++++- zingo-rpc/src/jsonrpc/connector.rs | 3 ++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 581302d..8e06bde 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -4,6 +4,7 @@ //! - Update spawn_server and nym_spawn to return > and > and use here. use crate::{nym_server::NymServer, server::spawn_server}; +use zcash_client_backend::proto::service::compact_tx_streamer_client::CompactTxStreamerClient; use zingo_rpc::primitives::NymClient; use std::sync::atomic::{AtomicBool, Ordering}; @@ -28,7 +29,7 @@ pub async fn spawn_proxy( println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; handles.push(proxy_handle); - tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + wait_on_grpc_startup(proxy_port, online.clone()).await; #[cfg(not(feature = "nym_poc"))] { @@ -38,6 +39,7 @@ pub async fn spawn_proxy( let nym_proxy_handle = nym_server.serve(online).await; handles.push(nym_proxy_handle); + // TODO: Add wait_on_nym_startup(nym_addr_out, online.clone()) function to test nym server. tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; } @@ -53,6 +55,52 @@ pub async fn close_proxy(online: Arc) { online.store(false, Ordering::SeqCst); } +/// Tries to connect to the gRPC server and retruns if connection established. Shuts down with error message if connection with server cannot be established after 3 attempts. +async fn wait_on_grpc_startup(proxy_port: &u16, online: Arc) { + let proxy_uri = http::Uri::builder() + .scheme("http") + .authority(format!("localhost:{proxy_port}")) + .path_and_query("/") + .build() + .unwrap(); + let mut attempts = 0; + let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(500)); + interval.tick().await; + while attempts < 3 { + match CompactTxStreamerClient::connect(proxy_uri.clone()).await { + Ok(mut client) => match client + .get_lightd_info(tonic::Request::new( + zcash_client_backend::proto::service::Empty {}, + )) + .await + { + Ok(_) => { + return; + } + Err(e) => { + println!( + "@zingoproxyd: GRPC server connection attempt {} failed with error: {}. Re", + attempts + 1, + e + ); + } + }, + Err(e) => { + println!( + "@zingoproxyd: GRPC server attempt {} failed to connect with error: {}", + attempts + 1, + e + ); + } + } + attempts += 1; + interval.tick().await; + } + println!("@zingoproxyd: Failed to start gRPC server, please check system config. Exiting Zingo-Proxy..."); + online.store(false, Ordering::SeqCst); + std::process::exit(1); +} + fn startup_message() { let welcome_message = r#" @@@@@@@@@@@@@@@&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&@@@@@@@@@ diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index ce2880d..8ca8c40 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -210,6 +210,7 @@ impl JsonRpcConnector { JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) })?; + // NOTE: This is useful for development but is not clear to users and should be simplified or completely removed before production. println!( "@zingoproxyd: Received response from {} call to node: {:#?}", method.to_string(), @@ -519,6 +520,7 @@ pub async fn test_node_and_return_uri( let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) })?; + let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(500)); for _ in 0..3 { println!("@zingoproxyd: Trying connection on IPv4."); @@ -547,6 +549,7 @@ pub async fn test_node_and_return_uri( } } } + interval.tick().await; } eprintln!("@zingoproxyd: Could not establish connection with node. \n@zingoproxyd: Please check config and confirm node is listening at the correct address and the correct authorisation details have been entered. \n@zingoproxyd: Exiting.."); From fd3257b3fd7ddcb590dc7a5e1d7f9090c1a6479c Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 10 Jun 2024 22:43:11 +0100 Subject: [PATCH 25/40] added parse_from_slice --- Cargo.lock | 2 + zingo-rpc/Cargo.toml | 4 + zingo-rpc/src/blockcache/block.rs | 122 +++++- zingo-rpc/src/blockcache/transaction.rs | 498 +++++++++++++++++++++++- zingo-rpc/src/blockcache/utils.rs | 66 +++- zingo-rpc/src/rpc/service.rs | 290 +++++++------- 6 files changed, 815 insertions(+), 167 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46b3c9b..cceaeaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8543,6 +8543,7 @@ name = "zingo-rpc" version = "0.1.0" dependencies = [ "base64 0.13.1", + "byteorder", "bytes 1.6.0", "futures", "hex 0.4.3", @@ -8569,6 +8570,7 @@ dependencies = [ "tower", "webpki-roots 0.21.1", "zcash_client_backend", + "zcash_encoding 0.2.0 (git+https://github.com/zingolabs/librustzcash.git?branch=nym_integration)", "zebra-chain", "zebra-rpc", "zebra-state", diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 9d2dfeb..5ad248f 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -17,6 +17,7 @@ zebra-state = { git = "https://github.com/ZcashFoundation/zebra.git" } zcash_client_backend = { workspace = true, features = ["lightwalletd-tonic"] } zingo-netutils = { workspace = true } +zcash_encoding = { git = "https://github.com/zingolabs/librustzcash.git", branch = "nym_integration" } nym-sdk = { workspace = true } nym-sphinx-addressing = { workspace = true } @@ -50,3 +51,6 @@ indexmap = { version = "2.2.6", features = ["serde"] } base64 = "0.13.0" tokio-stream = "0.1" futures = "0.3.30" + +byteorder = "1" + diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index 100dc22..a398e64 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -1,5 +1,12 @@ //! Block fetching and deserialization functionality. +use crate::blockcache::{ + transaction::FullTransaction, + utils::{read_bytes, read_i32, read_u32, ParseError, ParseFromSlice}, +}; +use std::io::Cursor; +use zcash_encoding::CompactSize; + /// A block header, containing metadata about a block. /// /// How are blocks chained together? They are chained together via the @@ -80,6 +87,64 @@ pub struct BlockHeaderData { pub solution: Vec, } +impl ParseFromSlice for BlockHeaderData { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for BlockHeaderData::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + let version = read_i32(&mut cursor, "Error reading BlockHeaderData::version")?; + let hash_prev_block = read_bytes( + &mut cursor, + 32, + "Error reading BlockHeaderData::hash_prev_block", + )?; + let hash_merkle_root = read_bytes( + &mut cursor, + 32, + "Error reading BlockHeaderData::hash_merkle_root", + )?; + let hash_final_sapling_root = read_bytes( + &mut cursor, + 32, + "Error reading BlockHeaderData::hash_final_sapling_root", + )?; + let time = read_u32(&mut cursor, "Error reading BlockHeaderData::time")?; + let n_bits_bytes = read_bytes( + &mut cursor, + 4, + "Error reading BlockHeaderData::n_bits_bytes", + )?; + let nonce = read_bytes(&mut cursor, 32, "Error reading BlockHeaderData::nonce")?; + + let solution = { + let compact_length = CompactSize::read(&mut cursor)?; + read_bytes( + &mut cursor, + compact_length as usize, + "Error reading BlockHeaderData::solution", + )? + }; + + Ok(( + &data[cursor.position() as usize..], + BlockHeaderData { + version, + hash_prev_block, + hash_merkle_root, + hash_final_sapling_root, + time, + n_bits_bytes, + nonce, + solution, + }, + )) + } +} + /// Complete block header. #[derive(Debug)] pub struct FullBlockHeader { @@ -105,12 +170,59 @@ pub struct FullBlock { pub height: i32, } -// impl parse_from_slice for block_header(&[u8]) -> Result<(Self, &[u8]), ParseError> - -// impl parse_from_slice for full_block(&[u8]) -> Result<(Self, &[u8]), ParseError> +impl ParseFromSlice for FullBlock { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + let txid = txid.ok_or_else(|| { + ParseError::InvalidData("txid must be used for FullBlock::parse_from_slice".to_string()) + })?; + let mut cursor = Cursor::new(data); + + let (remaining_data, block_header_data) = + BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None)?; + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + + let tx_count = CompactSize::read(&mut cursor)?; + if txid.len() != tx_count as usize { + return Err(ParseError::InvalidData(format!( + "number of txids ({}) does not match tx_count ({})", + txid.len(), + tx_count + ))); + } + let mut transactions = Vec::with_capacity(tx_count as usize); + let mut remaining_data = &data[cursor.position() as usize..]; + for txid_item in txid.iter() { + if remaining_data.is_empty() { + return Err(ParseError::InvalidData(format!( + "parsing block transactions: not enough data for transaction.", + ))); + } + let (new_remaining_data, tx) = FullTransaction::parse_from_slice( + &data[cursor.position() as usize..], + Some(vec![txid_item.clone()]), + )?; + transactions.push(tx); + remaining_data = new_remaining_data; + } + + Ok(( + remaining_data, + FullBlock { + hdr: FullBlockHeader { + raw_block_header: block_header_data, + cached_hash: Vec::new(), // return actual hash + }, + vtx: transactions, + height: 0, // return actual height + }, + )) + } +} // impl parse_full_block(&[u8]) -> Result -// impl to_compact(Self) -> Result +// impl to_compact(Self) -> Result + +// impl parse_to_compact(&[u8]) -> Result -// impl parse_to_compact(&[u8]) -> Result +// impl get_block_from_node(height: usize) -> Result diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index 59aa3c9..4858e24 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -1,5 +1,11 @@ //! Transaction fetching and deserialization functionality. +use crate::blockcache::utils::{ + read_bool, read_bytes, read_u32, read_u64, skip_bytes, ParseError, ParseFromSlice, +}; +use std::io::Cursor; +use zcash_encoding::CompactSize; + /// Txin format as described in https://en.bitcoin.it/wiki/Transaction #[derive(Debug)] pub struct TxIn { @@ -12,6 +18,31 @@ pub struct TxIn { // SequenceNumber [IGNORED] - Size[bytes]: 4 } +impl ParseFromSlice for TxIn { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for TxIn::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + skip_bytes(&mut cursor, 32, "Error skipping TxIn::PrevTxHash")?; + skip_bytes(&mut cursor, 4, "Error skipping TxIn::PrevTxOutIndex")?; + let script_sig = { + let compact_length = CompactSize::read(&mut cursor)?; + read_bytes( + &mut cursor, + compact_length as usize, + "Error reading TxIn::ScriptSig", + )? + }; + skip_bytes(&mut cursor, 4, "Error skipping TxIn::SequenceNumber")?; + + Ok((&data[cursor.position() as usize..], TxIn { script_sig })) + } +} + /// Txout format as described in https://en.bitcoin.it/wiki/Transaction #[derive(Debug)] pub struct TxOut { @@ -22,6 +53,50 @@ pub struct TxOut { // Script [IGNORED] - Size[bytes]: CompactSize } +impl ParseFromSlice for TxOut { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for TxOut::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + let value = read_u64(&mut cursor, "Error TxOut::reading Value")?; + let compact_length = CompactSize::read(&mut cursor)?; + skip_bytes( + &mut cursor, + compact_length as usize, + "Error skipping TxOut::Script", + )?; + + Ok((&data[cursor.position() as usize..], TxOut { value })) + } +} + +fn parse_transparent(data: &[u8]) -> Result<(&[u8], Vec, Vec), ParseError> { + let mut cursor = Cursor::new(data); + + let tx_in_count = CompactSize::read(&mut cursor)?; + let mut tx_ins = Vec::with_capacity(tx_in_count as usize); + for _ in 0..tx_in_count { + let (remaining_data, tx_in) = + TxIn::parse_from_slice(&data[cursor.position() as usize..], None)?; + tx_ins.push(tx_in); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + let tx_out_count = CompactSize::read(&mut cursor)?; + let mut tx_outs = Vec::with_capacity(tx_out_count as usize); + for _ in 0..tx_out_count { + let (remaining_data, tx_out) = + TxOut::parse_from_slice(&data[cursor.position() as usize..], None)?; + tx_outs.push(tx_out); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + + Ok((&data[cursor.position() as usize..], tx_ins, tx_outs)) +} + /// spend is a Sapling Spend Description as described in 7.3 of the Zcash /// protocol specification. #[derive(Debug)] @@ -37,6 +112,26 @@ pub struct Spend { // SpendAuthSig [IGNORED] - Size[bytes]: 64 } +impl ParseFromSlice for Spend { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for Spend::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + skip_bytes(&mut cursor, 32, "Error skipping Spend::Cv")?; + skip_bytes(&mut cursor, 32, "Error skipping Spend::Anchor")?; + let nullifier = read_bytes(&mut cursor, 32, "Error reading Spend::nullifier")?; + skip_bytes(&mut cursor, 32, "Error skipping Spend::Rk")?; + skip_bytes(&mut cursor, 32, "Error skipping Spend::Zkproof")?; + skip_bytes(&mut cursor, 32, "Error skipping Spend::SpendAuthSig")?; + + Ok((&data[cursor.position() as usize..], Spend { nullifier })) + } +} + /// output is a Sapling Output Description as described in section 7.4 of the /// Zcash protocol spec. #[derive(Debug)] @@ -59,6 +154,33 @@ pub struct Output { // Zkproof [IGNORED] - Size[bytes]: 192 } +impl ParseFromSlice for Output { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for Output::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + skip_bytes(&mut cursor, 32, "Error skipping Output::Cv")?; + let cmu = read_bytes(&mut cursor, 32, "Error reading Output::cmu")?; + let ephemeral_key = read_bytes(&mut cursor, 32, "Error reading Output::ephemeral_key")?; + let enc_ciphertext = read_bytes(&mut cursor, 580, "Error reading Output::enc_ciphertext")?; + skip_bytes(&mut cursor, 80, "Error skipping Output::OutCiphertext")?; + skip_bytes(&mut cursor, 192, "Error skipping Output::Zkproof")?; + + Ok(( + &data[cursor.position() as usize..], + Output { + cmu, + ephemeral_key, + enc_ciphertext, + }, + )) + } +} + /// joinSplit is a JoinSplit description as described in 7.2 of the Zcash /// protocol spec. Its exact contents differ by transaction version and network /// upgrade level. Only version 4 is supported, no need for proofPHGR13. @@ -78,6 +200,34 @@ pub struct JoinSplit { //encCiphertexts [IGNORED] - Size[bytes]: 1202 } +impl ParseFromSlice for JoinSplit { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for JoinSplit::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + skip_bytes(&mut cursor, 8, "Error skipping JoinSplit::vpubOld")?; + skip_bytes(&mut cursor, 8, "Error skipping JoinSplit::vpubNew")?; + skip_bytes(&mut cursor, 32, "Error skipping JoinSplit::anchor")?; + skip_bytes(&mut cursor, 64, "Error skipping JoinSplit::nullifiers")?; + skip_bytes(&mut cursor, 64, "Error skipping JoinSplit::commitments")?; + skip_bytes(&mut cursor, 32, "Error skipping JoinSplit::ephemeralKey")?; + skip_bytes(&mut cursor, 32, "Error skipping JoinSplit::randomSeed")?; + skip_bytes(&mut cursor, 64, "Error skipping JoinSplit::vmacs")?; + skip_bytes(&mut cursor, 192, "Error skipping JoinSplit::proofGroth16")?; + skip_bytes( + &mut cursor, + 1202, + "Error skipping JoinSplit::encCiphertexts", + )?; + + Ok((&data[cursor.position() as usize..], JoinSplit {})) + } +} + /// An Orchard action. #[derive(Debug)] pub struct Action { @@ -102,6 +252,35 @@ pub struct Action { // OutCiphertext [IGNORED] - Size[bytes]: 80 } +impl ParseFromSlice for Action { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + if txid != None { + return Err(ParseError::InvalidData( + "txid must be None for Action::parse_from_slice".to_string(), + )); + } + let mut cursor = Cursor::new(data); + + skip_bytes(&mut cursor, 32, "Error skipping Action::Cv")?; + let nullifier = read_bytes(&mut cursor, 32, "Error reading Action::nullifier")?; + skip_bytes(&mut cursor, 32, "Error skipping Action::Rk")?; + let cmx = read_bytes(&mut cursor, 32, "Error reading Action::cmx")?; + let ephemeral_key = read_bytes(&mut cursor, 32, "Error reading Action::ephemeral_key")?; + let enc_ciphertext = read_bytes(&mut cursor, 580, "Error reading Action::enc_ciphertext")?; + skip_bytes(&mut cursor, 80, "Error skipping Action::OutCiphertext")?; + + Ok(( + &data[cursor.position() as usize..], + Action { + nullifier, + cmx, + ephemeral_key, + enc_ciphertext, + }, + )) + } +} + /// Full Zcash Transactrion data. #[derive(Debug)] pub struct TransactionData { @@ -153,6 +332,263 @@ pub struct TransactionData { pub orchard_actions: Vec, } +impl TransactionData { + fn parse_v4( + data: &[u8], + version: u32, + n_version_group_id: u32, + ) -> Result<(&[u8], Self), ParseError> { + if n_version_group_id != 0x892F2085 { + return Err(ParseError::InvalidData(format!( + "version group ID {:x} must be 0x892F2085 for v4 transactions", + n_version_group_id + ))); + } + let mut cursor = Cursor::new(data); + + let (remaining_data, transparent_inputs, transparent_outputs) = + parse_transparent(&data[cursor.position() as usize..])?; + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + + skip_bytes(&mut cursor, 4, "Error skipping TransactionData::nLockTime")?; + skip_bytes( + &mut cursor, + 4, + "Error skipping TransactionData::nExpiryHeight", + )?; + skip_bytes( + &mut cursor, + 8, + "Error skipping TransactionData::valueBalance", + )?; + + let spend_count = CompactSize::read(&mut cursor)?; + let mut shielded_spends = Vec::with_capacity(spend_count as usize); + for _ in 0..spend_count { + let (remaining_data, spend) = + Spend::parse_from_slice(&data[cursor.position() as usize..], None)?; + shielded_spends.push(spend); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + let output_count = CompactSize::read(&mut cursor)?; + let mut shielded_outputs = Vec::with_capacity(output_count as usize); + for _ in 0..output_count { + let (remaining_data, output) = + Output::parse_from_slice(&data[cursor.position() as usize..], None)?; + shielded_outputs.push(output); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + let join_split_count = CompactSize::read(&mut cursor)?; + let mut join_splits = Vec::with_capacity(join_split_count as usize); + for _ in 0..join_split_count { + let (remaining_data, join_split) = + JoinSplit::parse_from_slice(&data[cursor.position() as usize..], None)?; + join_splits.push(join_split); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + + if join_split_count > 0 { + skip_bytes( + &mut cursor, + 32, + "Error skipping TransactionData::joinSplitPubKey", + )?; + skip_bytes( + &mut cursor, + 64, + "could not skip TransactionData::joinSplitSig", + )?; + } + if spend_count + output_count > 0 { + skip_bytes( + &mut cursor, + 64, + "Error skipping TransactionData::bindingSigSapling", + )?; + } + + Ok(( + &data[cursor.position() as usize..], + TransactionData { + f_overwintered: true, + version, + n_version_group_id, + consensus_branch_id: 0, + transparent_inputs, + transparent_outputs, + shielded_spends, + shielded_outputs, + join_splits, + orchard_actions: Vec::new(), + }, + )) + } + + fn parse_v5( + data: &[u8], + version: u32, + n_version_group_id: u32, + ) -> Result<(&[u8], Self), ParseError> { + if n_version_group_id != 0x26A7270A { + return Err(ParseError::InvalidData(format!( + "version group ID {:x} must be 0x892F2085 for v5 transactions", + n_version_group_id + ))); + } + let mut cursor = Cursor::new(data); + + let consensus_branch_id = read_u32( + &mut cursor, + "Error reading TransactionData::ConsensusBranchId", + )?; + + skip_bytes(&mut cursor, 4, "Error skipping TransactionData::nLockTime")?; + skip_bytes( + &mut cursor, + 4, + "Error skipping TransactionData::nExpiryHeight", + )?; + + let (remaining_data, transparent_inputs, transparent_outputs) = + parse_transparent(&data[cursor.position() as usize..])?; + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + + let spend_count = CompactSize::read(&mut cursor)?; + if spend_count >= (1 << 16) { + return Err(ParseError::InvalidData(format!( + "spendCount ({}) must be less than 2^16", + spend_count + ))); + } + let mut shielded_spends = Vec::with_capacity(spend_count as usize); + for _ in 0..spend_count { + let (remaining_data, spend) = + Spend::parse_from_slice(&data[cursor.position() as usize..], None)?; + shielded_spends.push(spend); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + let output_count = CompactSize::read(&mut cursor)?; + if output_count >= (1 << 16) { + return Err(ParseError::InvalidData(format!( + "outputCount ({}) must be less than 2^16", + output_count + ))); + } + let mut shielded_outputs = Vec::with_capacity(output_count as usize); + for _ in 0..output_count { + let (remaining_data, output) = + Output::parse_from_slice(&data[cursor.position() as usize..], None)?; + shielded_outputs.push(output); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + + if spend_count + output_count > 0 { + skip_bytes( + &mut cursor, + 8, + "Error skipping TransactionData::valueBalance", + )?; + } + if spend_count > 0 { + skip_bytes( + &mut cursor, + 32, + "Error skipping TransactionData::anchorSapling", + )?; + skip_bytes( + &mut cursor, + (192 * spend_count) as usize, + "Error skipping TransactionData::vSpendProofsSapling", + )?; + skip_bytes( + &mut cursor, + (64 * spend_count) as usize, + "Error skipping TransactionData::vSpendAuthSigsSapling", + )?; + } + if output_count > 0 { + skip_bytes( + &mut cursor, + (192 * output_count) as usize, + "Error skipping TransactionData::vOutputProofsSapling", + )?; + } + if spend_count + output_count > 0 { + skip_bytes( + &mut cursor, + 64, + "Error skipping TransactionData::bindingSigSapling", + )?; + } + + let actions_count = CompactSize::read(&mut cursor)?; + if actions_count >= (1 << 16) { + return Err(ParseError::InvalidData(format!( + "actionsCount ({}) must be less than 2^16", + actions_count + ))); + } + let mut orchard_actions = Vec::with_capacity(actions_count as usize); + for _ in 0..actions_count { + let (remaining_data, action) = + Action::parse_from_slice(&data[cursor.position() as usize..], None)?; + orchard_actions.push(action); + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + } + + if actions_count > 0 { + skip_bytes( + &mut cursor, + 1, + "Error skipping TransactionData::flagsOrchard", + )?; + skip_bytes( + &mut cursor, + 8, + "Error skipping TransactionData::valueBalanceOrchard", + )?; + skip_bytes( + &mut cursor, + 32, + "Error skipping TransactionData::anchorOrchard", + )?; + + let proofs_count = CompactSize::read(&mut cursor)?; + skip_bytes( + &mut cursor, + proofs_count as usize, + "Error skipping TransactionData::proofsOrchard", + )?; + skip_bytes( + &mut cursor, + (64 * actions_count) as usize, + "Error skipping TransactionData::vSpendAuthSigsOrchard", + )?; + skip_bytes( + &mut cursor, + 64, + "Error skipping TransactionData::bindingSigOrchard", + )?; + } + + Ok(( + &data[cursor.position() as usize..], + TransactionData { + f_overwintered: true, + version, + n_version_group_id, + consensus_branch_id, + transparent_inputs, + transparent_outputs, + shielded_spends, + shielded_outputs, + join_splits: Vec::new(), + orchard_actions, + }, + )) + } +} + /// Zingo-Proxy struct for a full zcash transaction. #[derive(Debug)] pub struct FullTransaction { @@ -166,25 +602,57 @@ pub struct FullTransaction { pub tx_id: Vec, } -// impl parse_from_slice for TxIn(&[u8]) -> Result<(Self, &[u8]), ParseError> +impl ParseFromSlice for FullTransaction { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + let txid = txid.ok_or_else(|| { + ParseError::InvalidData( + "txid must be used for FullTransaction::parse_from_slice".to_string(), + ) + })?; + let mut cursor = Cursor::new(data); -// impl parse_from_slice for TxOut(&[u8]) -> Result<(Self, &[u8]), ParseError> + let header = read_u32(&mut cursor, "Error reading FullTransaction::header")?; + let f_overwintered = (header >> 31) == 1; + if !f_overwintered { + return Err(ParseError::InvalidData( + "fOverwinter flag must be set".to_string(), + )); + } + let version = header & 0x7FFFFFFF; + if version < 4 { + return Err(ParseError::InvalidData(format!( + "version number {} must be greater or equal to 4", + version + ))); + } + let n_version_group_id = read_u32( + &mut cursor, + "Error reading FullTransaction::n_version_group_id", + )?; -// imple parse_transparent(&[u8]) -> Result<(????, &[u8]), ParseError> + let (remaining_data, transaction_data) = if version <= 4 { + TransactionData::parse_v4( + &data[cursor.position() as usize..], + version, + n_version_group_id, + )? + } else { + TransactionData::parse_v5( + &data[cursor.position() as usize..], + version, + n_version_group_id, + )? + }; -// impl parse_from_slice for Spend(&[u8]) -> Result<(Self, &[u8]), ParseError> + let full_transaction = FullTransaction { + raw_transaction: transaction_data, + raw_bytes: data[..(data.len() - remaining_data.len())].to_vec(), + tx_id: txid, + }; -// impl parse_from_slice for Output(&[u8]) -> Result<(Self, &[u8]), ParseError> - -// impl parse_from_slice for JoinSplit(&[u8]) -> Result<(Self, &[u8]), ParseError> - -// impl parse_from_slice for Action(&[u8]) -> Result<(Self, &[u8]), ParseError> - -// impl parse_v4(&[u8]) -> Result<(????, &[u8]), ParseError> - -// impl parse_v5(&[u8]) -> Result<(????, &[u8]), ParseError> - -// impl parse_from_slice for transaction(&[u8]) -> Result<(Self, &[u8]), ParseError> + Ok((remaining_data, full_transaction)) + } +} // impl to_compact(Self) -> Result diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index 6c3e7e0..0aa935c 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -1,5 +1,8 @@ //! Blockcache utility functionality. +use byteorder::{LittleEndian, ReadBytesExt}; +use std::io::{Cursor, Read}; + /// Parser Error Type. #[derive(Debug)] pub enum ParseError { @@ -7,8 +10,67 @@ pub enum ParseError { InvalidData(String), } -trait ParseFromSlice { - fn parse_from_slice(data: &[u8]) -> Result<(&[u8], Self), ParseError> +impl From for ParseError { + fn from(err: std::io::Error) -> ParseError { + ParseError::Io(err) + } +} + +pub trait ParseFromSlice { + fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> where Self: Sized; } + +pub fn skip_bytes(cursor: &mut Cursor<&[u8]>, n: usize, error_msg: &str) -> Result<(), ParseError> { + if cursor.get_ref().len() < (cursor.position() + n as u64) as usize { + return Err(ParseError::InvalidData(error_msg.to_string())); + } + cursor.set_position(cursor.position() + n as u64); + Ok(()) +} + +pub fn read_bytes( + cursor: &mut Cursor<&[u8]>, + n: usize, + error_msg: &str, +) -> Result, ParseError> { + let mut buf = vec![0; n]; + cursor + .read_exact(&mut buf) + .map_err(|_| ParseError::InvalidData(error_msg.to_string()))?; + Ok(buf) +} + +pub fn read_u64(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result { + cursor + .read_u64::() + .map_err(ParseError::from) + .map_err(|_| ParseError::InvalidData(error_msg.to_string())) +} + +pub fn read_u32(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result { + cursor + .read_u32::() + .map_err(ParseError::from) + .map_err(|_| ParseError::InvalidData(error_msg.to_string())) +} + +pub fn read_i32(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result { + cursor + .read_i32::() + .map_err(ParseError::from) + .map_err(|_| ParseError::InvalidData(error_msg.to_string())) +} + +pub fn read_bool(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result { + let byte = cursor + .read_u8() + .map_err(ParseError::from) + .map_err(|_| ParseError::InvalidData(error_msg.to_string()))?; + match byte { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(ParseError::InvalidData(error_msg.to_string())), + } +} diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 960d238..92c971e 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -202,159 +202,159 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - type GetBlockRangeStream = tonic::Streaming; - // type GetBlockRangeStream = std::pin::Pin>; + // type GetBlockRangeStream = tonic::Streaming; + type GetBlockRangeStream = std::pin::Pin>; // /// Return a list of consecutive compact blocks. // /// // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. // /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - // fn get_block_range<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_block_range."); - // let zebrad_uri = self.zebrad_uri.clone(); - // Box::pin(async move { - // let blockrange = request.into_inner(); - // let mut start = blockrange - // .start - // .map(|s| s.height as u32) - // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - // let mut end = blockrange - // .end - // .map(|e| e.height as u32) - // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - // if start > end { - // (start, end) = (end, start); - // } + fn get_block_range<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_range."); + let zebrad_uri = self.zebrad_uri.clone(); + Box::pin(async move { + let blockrange = request.into_inner(); + let mut start = blockrange + .start + .map(|s| s.height as u32) + .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + let mut end = blockrange + .end + .map(|e| e.height as u32) + .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + if start > end { + (start, end) = (end, start); + } - // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - // tokio::spawn(async move { - // let zebrad_client = JsonRpcConnector::new( - // zebrad_uri, - // Some("xxxxxx".to_string()), - // Some("xxxxxx".to_string()), - // ) - // .await; + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + let zebrad_client = JsonRpcConnector::new( + zebrad_uri, + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; - // for height in start..end { - // let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; - // match block_1 { - // Ok(GetBlockResponse::Object { - // hash, - // confirmations: _, - // height, - // time, - // tx, - // trees, - // }) => { - // let block_0 = - // zebrad_client.get_block(hash.0.to_string(), Some(2)).await; - // match block_0 { - // Ok(GetBlockResponse::Object { - // hash: _, - // confirmations: _, - // height: _, - // time: _, - // tx: _, - // trees: _, - // }) => { - // if channel_tx - // .send(Err(tonic::Status::internal("Received object block type, this should not be impossible here.", - // ))) - // .await - // .is_err() - // { - // break; - // } - // } - // Ok(GetBlockResponse::Raw(block_hex)) => { - // let block_hash: Vec = todo!(); //block_hash_0; - // let block_height: u64 = height.unwrap().0 as u64; - // let block_time: u32 = time.unwrap() as u32; - // let block_tx: Vec = todo!(); //tx; - // let block_metadata: Option = Some(ChainMetadata { - // sapling_commitment_tree_size: todo!(), //trees.sapling.size, - // orchard_commitment_tree_size: todo!(), //trees.orchard.size, - // }); - // if channel_tx - // .send(Ok(CompactBlock { - // proto_version: todo!(), - // height: block_height, - // hash: block_hash, - // prev_hash: todo!(), - // time: block_time, - // header: todo!(), - // vtx: block_tx, - // chain_metadata: block_metadata, - // })) - // .await - // .is_err() - // { - // break; - // } - // } - // Err(e) => { - // if channel_tx - // .send(Err(tonic::Status::internal(e.to_string()))) - // .await - // .is_err() - // { - // break; - // } - // } - // } - // } - // Ok(GetBlockResponse::Raw(_)) => { - // if channel_tx - // .send(Err(tonic::Status::internal( - // "Received raw block type, this should not be impossible here.", - // ))) - // .await - // .is_err() - // { - // break; - // } - // } - // Err(e) => { - // if channel_tx - // .send(Err(tonic::Status::internal(e.to_string()))) - // .await - // .is_err() - // { - // break; - // } - // } - // } - // } - // }); - // let output_stream = CompactBlockStream::new(channel_rx); - // let stream_boxed = Box::pin(output_stream); - // Ok(tonic::Response::new(stream_boxed)) - // }) - // } - define_grpc_passthrough!( - fn get_block_range( - &self, - request: tonic::Request, - ) -> Self::GetBlockRangeStream - ); + for height in start..end { + let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; + match block_1 { + Ok(GetBlockResponse::Object { + hash, + confirmations: _, + height, + time, + tx, + trees, + }) => { + let block_0 = + zebrad_client.get_block(hash.0.to_string(), Some(2)).await; + match block_0 { + Ok(GetBlockResponse::Object { + hash: _, + confirmations: _, + height: _, + time: _, + tx: _, + trees: _, + }) => { + if channel_tx + .send(Err(tonic::Status::internal("Received object block type, this should not be possible here.", + ))) + .await + .is_err() + { + break; + } + } + Ok(GetBlockResponse::Raw(block_hex)) => { + let block_hash: Vec = todo!(); //block_hash_0; + let block_height: u64 = height.unwrap().0 as u64; + let block_time: u32 = time.unwrap() as u32; + let block_tx: Vec = todo!(); //tx; + let block_metadata: Option = Some(ChainMetadata { + sapling_commitment_tree_size: todo!(), //trees.sapling.size, + orchard_commitment_tree_size: todo!(), //trees.orchard.size, + }); + if channel_tx + .send(Ok(CompactBlock { + proto_version: todo!(), + height: block_height, + hash: block_hash, + prev_hash: todo!(), + time: block_time, + header: todo!(), + vtx: block_tx, + chain_metadata: block_metadata, + })) + .await + .is_err() + { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + Ok(GetBlockResponse::Raw(_)) => { + if channel_tx + .send(Err(tonic::Status::internal( + "Received raw block type, this should not be possible here.", + ))) + .await + .is_err() + { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + }); + let output_stream = CompactBlockStream::new(channel_rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_block_range( + // &self, + // request: tonic::Request, + // ) -> Self::GetBlockRangeStream + // ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] From c6a317481c1ea08acba61b3e61c6966f59112f5c Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 11 Jun 2024 16:56:03 +0100 Subject: [PATCH 26/40] added block_height and block_hash getter logic --- Cargo.lock | 1 + zingo-rpc/Cargo.toml | 2 + zingo-rpc/src/blockcache/block.rs | 100 ++++++++++++++++++++++-- zingo-rpc/src/blockcache/transaction.rs | 12 ++- zingo-rpc/src/blockcache/utils.rs | 38 +++++++++ 5 files changed, 142 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cceaeaf..8822c79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8563,6 +8563,7 @@ dependencies = [ "rustls-pemfile", "serde", "serde_json", + "sha2 0.10.8", "tokio", "tokio-rustls 0.23.4", "tokio-stream", diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 5ad248f..645c39d 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -53,4 +53,6 @@ tokio-stream = "0.1" futures = "0.3.30" byteorder = "1" +sha2 = "0.10" + diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index a398e64..c2c2bce 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -2,9 +2,11 @@ use crate::blockcache::{ transaction::FullTransaction, - utils::{read_bytes, read_i32, read_u32, ParseError, ParseFromSlice}, + utils::{read_bytes, read_i32, read_u32, read_zcash_script_i64, ParseError, ParseFromSlice}, }; +use sha2::{Digest, Sha256}; use std::io::Cursor; +use zcash_client_backend::proto::compact_formats::CompactBlock; use zcash_encoding::CompactSize; /// A block header, containing metadata about a block. @@ -145,6 +147,40 @@ impl ParseFromSlice for BlockHeaderData { } } +impl BlockHeaderData { + /// Serializes the block header into a byte vector. + pub fn to_binary(&self) -> Result, ParseError> { + let mut buffer = Vec::new(); + + buffer.extend(&self.version.to_le_bytes()); + buffer.extend(&self.hash_prev_block); + buffer.extend(&self.hash_merkle_root); + buffer.extend(&self.hash_final_sapling_root); + buffer.extend(&self.time.to_le_bytes()); + buffer.extend(&self.n_bits_bytes); + buffer.extend(&self.nonce); + let mut solution_compact_size = Vec::new(); + CompactSize::write(&mut solution_compact_size, self.solution.len())?; + buffer.extend(solution_compact_size); + buffer.extend(&self.solution); + + Ok(buffer) + } + + /// Extracts the block hash from the block header. + pub fn get_block_hash(&self) -> Result, ParseError> { + let serialized_header = self.to_binary()?; + + let mut hasher = Sha256::new(); + hasher.update(&serialized_header); + let digest = hasher.finalize_reset(); + hasher.update(&digest); + let final_digest = hasher.finalize(); + + Ok(final_digest.to_vec()) + } +} + /// Complete block header. #[derive(Debug)] pub struct FullBlockHeader { @@ -204,25 +240,75 @@ impl ParseFromSlice for FullBlock { transactions.push(tx); remaining_data = new_remaining_data; } + let block_height = Self::get_block_height(&transactions)?; + let block_hash = block_header_data.get_block_hash()?; Ok(( remaining_data, FullBlock { hdr: FullBlockHeader { raw_block_header: block_header_data, - cached_hash: Vec::new(), // return actual hash + cached_hash: block_hash, }, vtx: transactions, - height: 0, // return actual height + height: block_height, }, )) } } -// impl parse_full_block(&[u8]) -> Result +/// Genesis block special case. +/// +/// From LoightWalletD: +/// see https://github.com/zcash/lightwalletd/issues/17#issuecomment-467110828. +const GENESIS_TARGET_DIFFICULTY: u32 = 520617983; + +impl FullBlock { + /// Extracts the block height from the coinbase transaction. + pub fn get_block_height(transactions: &Vec) -> Result { + let coinbase_script = transactions[0].raw_transaction.transparent_inputs[0] + .script_sig + .as_slice(); + let mut cursor = Cursor::new(coinbase_script); + + let height_num: i64 = read_zcash_script_i64(&mut cursor)?; + if height_num < 0 { + return Ok(-1); + } + if height_num > i64::from(u32::MAX) { + return Ok(-1); + } + if (height_num as u32) == GENESIS_TARGET_DIFFICULTY { + return Ok(0); + } + + Ok(height_num as i32) + } + + /// Decodes a hex encoded zcash full block into a FullBlock struct. + pub fn parse_full_block(data: &[u8], txid: Option>) -> Result { + todo!() + } -// impl to_compact(Self) -> Result + /// Converts a zcash full block into a compact block. + pub fn to_compact(self) -> Result { + todo!() + } -// impl parse_to_compact(&[u8]) -> Result + /// Decodes a hex encoded zcash full block into a CompactBlock struct. + pub fn parse_to_compact( + data: &[u8], + txid: Option>, + ) -> Result { + todo!() + } +} -// impl get_block_from_node(height: usize) -> Result +/// Returns a compact block. +/// +/// Retrieves a full block from zebrad/zcashd using 2 get_block calls. +/// This is because a get_block verbose = 1 call is require to fetch txids. +/// TODO: Save retrieved CompactBlock to the BlockCache. +pub fn get_block_from_node(height: usize) -> Result { + todo!() +} diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index 4858e24..b8eaf90 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -1,9 +1,10 @@ //! Transaction fetching and deserialization functionality. use crate::blockcache::utils::{ - read_bool, read_bytes, read_u32, read_u64, skip_bytes, ParseError, ParseFromSlice, + read_bytes, read_u32, read_u64, skip_bytes, ParseError, ParseFromSlice, }; use std::io::Cursor; +use zcash_client_backend::proto::compact_formats::{CompactBlock, CompactTx}; use zcash_encoding::CompactSize; /// Txin format as described in https://en.bitcoin.it/wiki/Transaction @@ -654,6 +655,9 @@ impl ParseFromSlice for FullTransaction { } } -// impl to_compact(Self) -> Result - -// impl parse_to_compact(&[u8]) -> Result<(compact_transaction, &[u8]), Error> +impl FullTransaction { + /// Converts a zcash full transaction into a compact transaction. + pub fn to_compact(self) -> Result { + todo!() + } +} diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index 0aa935c..eb9e49b 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -6,7 +6,9 @@ use std::io::{Cursor, Read}; /// Parser Error Type. #[derive(Debug)] pub enum ParseError { + /// Io Error Io(std::io::Error), + /// Parse Error. InvalidData(String), } @@ -16,12 +18,17 @@ impl From for ParseError { } } +/// Used for decoding zcash blocks from a bytestring. pub trait ParseFromSlice { + /// Reads data from a bytestring, consuming data read, and returns an instance of self along with the remaining data in the bytestring given. + /// + /// Txid is givin as an input as this is taken from a get_block verbose=1 call. fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> where Self: Sized; } +/// Skips the next n bytes in cursor, returns error message given if eof is reached. pub fn skip_bytes(cursor: &mut Cursor<&[u8]>, n: usize, error_msg: &str) -> Result<(), ParseError> { if cursor.get_ref().len() < (cursor.position() + n as u64) as usize { return Err(ParseError::InvalidData(error_msg.to_string())); @@ -30,6 +37,7 @@ pub fn skip_bytes(cursor: &mut Cursor<&[u8]>, n: usize, error_msg: &str) -> Resu Ok(()) } +/// Reads the next n bytes from cursor into a vec, returns error message given if eof is reached.. pub fn read_bytes( cursor: &mut Cursor<&[u8]>, n: usize, @@ -42,6 +50,7 @@ pub fn read_bytes( Ok(buf) } +/// Reads the next 8 bytes from cursor into a u64, returns error message given if eof is reached.. pub fn read_u64(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result { cursor .read_u64::() @@ -49,6 +58,7 @@ pub fn read_u64(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result, error_msg: &str) -> Result { cursor .read_u32::() @@ -56,6 +66,7 @@ pub fn read_u32(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result, error_msg: &str) -> Result { cursor .read_i32::() @@ -63,6 +74,7 @@ pub fn read_i32(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result, error_msg: &str) -> Result { let byte = cursor .read_u8() @@ -74,3 +86,29 @@ pub fn read_bool(cursor: &mut Cursor<&[u8]>, error_msg: &str) -> Result Err(ParseError::InvalidData(error_msg.to_string())), } } + +/// read_zcash_script_int64 OP codes. +const OP_0: u8 = 0x00; +const OP_1_NEGATE: u8 = 0x4f; +const OP_1: u8 = 0x51; +const OP_16: u8 = 0x60; + +/// Reads and interprets a Zcash (Bitcoin)-custom compact integer encoding used for int64 numbers in scripts. +pub fn read_zcash_script_i64(cursor: &mut Cursor<&[u8]>) -> Result { + let first_byte = read_bytes(cursor, 1, "Error reading first byte in i64 script hash")?[0]; + + match first_byte { + OP_1_NEGATE => Ok(-1), + OP_0 => Ok(0), + OP_1..=OP_16 => Ok((u64::from(first_byte) - u64::from(OP_1 - 1)) as i64), + _ => { + let num_bytes = + read_bytes(cursor, first_byte as usize, "Error reading i64 script hash")?; + let number = num_bytes + .iter() + .rev() + .fold(0, |acc, &byte| (acc << 8) | u64::from(byte)); + Ok(number as i64) + } + } +} From 77f057d6237b69458fb77ffc1e3ebe7496009633 Mon Sep 17 00:00:00 2001 From: idky137 Date: Wed, 12 Jun 2024 12:14:04 +0100 Subject: [PATCH 27/40] builds, get_block_range not returning correct response --- integration-tests/tests/integrations.rs | 3 + zingo-rpc/src/blockcache/block.rs | 146 ++++++++++++++-- zingo-rpc/src/blockcache/transaction.rs | 96 +++++++++-- zingo-rpc/src/blockcache/utils.rs | 33 +++- zingo-rpc/src/jsonrpc/primitives.rs | 13 +- zingo-rpc/src/rpc/service.rs | 220 ++++++++---------------- zingoproxy-testutils/Cargo.toml | 3 + 7 files changed, 334 insertions(+), 180 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 9d3de59..1d0fef2 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -51,6 +51,9 @@ mod wallet { test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); + + // std::thread::sleep(std::time::Duration::from_secs(10)); + zingo_client .do_send(vec![( &zingolib::get_base_address!(zingo_client, "sapling"), diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index c2c2bce..de3c3f8 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -1,12 +1,17 @@ //! Block fetching and deserialization functionality. -use crate::blockcache::{ - transaction::FullTransaction, - utils::{read_bytes, read_i32, read_u32, read_zcash_script_i64, ParseError, ParseFromSlice}, +use crate::{ + blockcache::{ + transaction::FullTransaction, + utils::{ + read_bytes, read_i32, read_u32, read_zcash_script_i64, ParseError, ParseFromSlice, + }, + }, + jsonrpc::{connector::JsonRpcConnector, primitives::GetBlockResponse}, }; use sha2::{Digest, Sha256}; use std::io::Cursor; -use zcash_client_backend::proto::compact_formats::CompactBlock; +use zcash_client_backend::proto::compact_formats::{ChainMetadata, CompactBlock}; use zcash_encoding::CompactSize; /// A block header, containing metadata about a block. @@ -90,7 +95,10 @@ pub struct BlockHeaderData { } impl ParseFromSlice for BlockHeaderData { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for BlockHeaderData::parse_from_slice".to_string(), @@ -168,7 +176,7 @@ impl BlockHeaderData { } /// Extracts the block hash from the block header. - pub fn get_block_hash(&self) -> Result, ParseError> { + pub fn get_hash(&self) -> Result, ParseError> { let serialized_header = self.to_binary()?; let mut hasher = Sha256::new(); @@ -207,7 +215,10 @@ pub struct FullBlock { } impl ParseFromSlice for FullBlock { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { let txid = txid.ok_or_else(|| { ParseError::InvalidData("txid must be used for FullBlock::parse_from_slice".to_string()) })?; @@ -217,6 +228,8 @@ impl ParseFromSlice for FullBlock { BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None)?; cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + println!("\nBlockHeaderData: {:?}\n", block_header_data); + let tx_count = CompactSize::read(&mut cursor)?; if txid.len() != tx_count as usize { return Err(ParseError::InvalidData(format!( @@ -240,8 +253,10 @@ impl ParseFromSlice for FullBlock { transactions.push(tx); remaining_data = new_remaining_data; } + println!("\nTransactions: {:?}\n", transactions); + let block_height = Self::get_block_height(&transactions)?; - let block_hash = block_header_data.get_block_hash()?; + let block_hash = block_header_data.get_hash()?; Ok(( remaining_data, @@ -259,7 +274,7 @@ impl ParseFromSlice for FullBlock { /// Genesis block special case. /// -/// From LoightWalletD: +/// From LightWalletD: /// see https://github.com/zcash/lightwalletd/issues/17#issuecomment-467110828. const GENESIS_TARGET_DIFFICULTY: u32 = 520617983; @@ -286,21 +301,63 @@ impl FullBlock { } /// Decodes a hex encoded zcash full block into a FullBlock struct. - pub fn parse_full_block(data: &[u8], txid: Option>) -> Result { - todo!() + pub fn parse_full_block(data: &[u8], txid: Option>>) -> Result { + let (remaining_data, full_block) = Self::parse_from_slice(data, txid)?; + if remaining_data.len() != 0 { + return Err(ParseError::InvalidData( + "Error decoding full block, data ramaining: ({remaining_data})".to_string(), + )); + } + Ok(full_block) } /// Converts a zcash full block into a compact block. - pub fn to_compact(self) -> Result { - todo!() + pub fn to_compact( + self, + sapling_commitment_tree_size: u32, + orchard_commitment_tree_size: u32, + ) -> Result { + let vtx = self + .vtx + .into_iter() + .enumerate() + .filter_map(|(index, tx)| { + if tx.has_shielded_elements() { + Some(tx.to_compact(index as u64)) + } else { + None + } + }) + .collect::, _>>()?; + + let header = self.hdr.raw_block_header.to_binary()?; + + let compact_block = CompactBlock { + proto_version: 1, // TODO: check this is correct! + height: self.height as u64, + hash: self.hdr.cached_hash.clone(), + prev_hash: self.hdr.raw_block_header.hash_prev_block.clone(), + time: self.hdr.raw_block_header.time, + header, + vtx, + chain_metadata: Some(ChainMetadata { + sapling_commitment_tree_size, + orchard_commitment_tree_size, + }), + }; + + Ok(compact_block) } /// Decodes a hex encoded zcash full block into a CompactBlock struct. pub fn parse_to_compact( data: &[u8], - txid: Option>, + txid: Option>>, + sapling_commitment_tree_size: u32, + orchard_commitment_tree_size: u32, ) -> Result { - todo!() + Ok(Self::parse_full_block(data, txid)? + .to_compact(sapling_commitment_tree_size, orchard_commitment_tree_size)?) } } @@ -309,6 +366,61 @@ impl FullBlock { /// Retrieves a full block from zebrad/zcashd using 2 get_block calls. /// This is because a get_block verbose = 1 call is require to fetch txids. /// TODO: Save retrieved CompactBlock to the BlockCache. -pub fn get_block_from_node(height: usize) -> Result { - todo!() +/// TODO: Return more representative error type. +pub async fn get_block_from_node( + zebra_uri: &http::Uri, + height: &u32, +) -> Result { + let zebrad_client = JsonRpcConnector::new( + zebra_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; + match block_1 { + Ok(GetBlockResponse::Object { + hash, + confirmations: _, + height: _, + time: _, + tx, + trees, + }) => { + let block_0 = zebrad_client.get_block(hash.0.to_string(), Some(0)).await; + match block_0 { + Ok(GetBlockResponse::Object { + hash: _, + confirmations: _, + height: _, + time: _, + tx: _, + trees: _, + }) => { + return Err(ParseError::InvalidData( + "Received object block type, this should not be possible here.".to_string(), + )); + } + Ok(GetBlockResponse::Raw(block_hex)) => { + Ok(FullBlock::parse_to_compact( + block_hex.as_ref(), + Some(tx.into_iter().map(|s| s.into_bytes()).collect()), + 0, //trees.sapling as u32, + 2, //trees.orchard as u32, + )?) + } + Err(e) => { + return Err(e.into()); + } + } + } + Ok(GetBlockResponse::Raw(_)) => { + return Err(ParseError::InvalidData( + "Received raw block type, this should not be possible here.".to_string(), + )); + } + Err(e) => { + return Err(e.into()); + } + } } diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index b8eaf90..d9c88f1 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -4,7 +4,9 @@ use crate::blockcache::utils::{ read_bytes, read_u32, read_u64, skip_bytes, ParseError, ParseFromSlice, }; use std::io::Cursor; -use zcash_client_backend::proto::compact_formats::{CompactBlock, CompactTx}; +use zcash_client_backend::proto::compact_formats::{ + CompactOrchardAction, CompactSaplingOutput, CompactSaplingSpend, CompactTx, +}; use zcash_encoding::CompactSize; /// Txin format as described in https://en.bitcoin.it/wiki/Transaction @@ -20,7 +22,10 @@ pub struct TxIn { } impl ParseFromSlice for TxIn { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for TxIn::parse_from_slice".to_string(), @@ -55,7 +60,10 @@ pub struct TxOut { } impl ParseFromSlice for TxOut { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for TxOut::parse_from_slice".to_string(), @@ -114,7 +122,10 @@ pub struct Spend { } impl ParseFromSlice for Spend { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Spend::parse_from_slice".to_string(), @@ -156,7 +167,10 @@ pub struct Output { } impl ParseFromSlice for Output { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Output::parse_from_slice".to_string(), @@ -202,7 +216,10 @@ pub struct JoinSplit { } impl ParseFromSlice for JoinSplit { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for JoinSplit::parse_from_slice".to_string(), @@ -254,7 +271,10 @@ pub struct Action { } impl ParseFromSlice for Action { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Action::parse_from_slice".to_string(), @@ -604,7 +624,10 @@ pub struct FullTransaction { } impl ParseFromSlice for FullTransaction { - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> { + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> { let txid = txid.ok_or_else(|| { ParseError::InvalidData( "txid must be used for FullTransaction::parse_from_slice".to_string(), @@ -648,7 +671,7 @@ impl ParseFromSlice for FullTransaction { let full_transaction = FullTransaction { raw_transaction: transaction_data, raw_bytes: data[..(data.len() - remaining_data.len())].to_vec(), - tx_id: txid, + tx_id: txid[0].clone(), }; Ok((remaining_data, full_transaction)) @@ -657,7 +680,58 @@ impl ParseFromSlice for FullTransaction { impl FullTransaction { /// Converts a zcash full transaction into a compact transaction. - pub fn to_compact(self) -> Result { - todo!() + pub fn to_compact(self, index: u64) -> Result { + let hash = self.tx_id; + + // NOTE: LightWalletD currently does not return a fee and is not currently priority here. Please open an Issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy) if you require this functionality. + let fee = 0; + + let spends = self + .raw_transaction + .shielded_spends + .iter() + .map(|spend| CompactSaplingSpend { + nf: spend.nullifier.clone(), + }) + .collect(); + + let outputs = self + .raw_transaction + .shielded_outputs + .iter() + .map(|output| CompactSaplingOutput { + cmu: output.cmu.clone(), + ephemeral_key: output.ephemeral_key.clone(), + ciphertext: output.enc_ciphertext[..52].to_vec(), + }) + .collect(); + + let actions = self + .raw_transaction + .orchard_actions + .iter() + .map(|action| CompactOrchardAction { + nullifier: action.nullifier.clone(), + cmx: action.cmx.clone(), + ephemeral_key: action.ephemeral_key.clone(), + ciphertext: action.enc_ciphertext[..52].to_vec(), + }) + .collect(); + + Ok(CompactTx { + index, + hash, + fee, + spends, + outputs, + actions, + }) + } + + /// Returns true if the transaction contains either sapling spends or outputs. + pub fn has_shielded_elements(&self) -> bool { + !self.raw_transaction.shielded_spends.is_empty() + || !self.raw_transaction.shielded_outputs.is_empty() + || !self.raw_transaction.orchard_actions.is_empty() } } diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index eb9e49b..bf9b693 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -3,12 +3,14 @@ use byteorder::{LittleEndian, ReadBytesExt}; use std::io::{Cursor, Read}; +use crate::jsonrpc::connector::JsonRpcConnectorError; + /// Parser Error Type. #[derive(Debug)] pub enum ParseError { /// Io Error Io(std::io::Error), - /// Parse Error. + /// Invalid Data Error. InvalidData(String), } @@ -18,12 +20,39 @@ impl From for ParseError { } } +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::Io(err) => write!(f, "IO Error: {}", err), + ParseError::InvalidData(msg) => write!(f, "Invalid Data Error: {}", msg), + } + } +} + +impl std::error::Error for ParseError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + ParseError::Io(err) => Some(err), + ParseError::InvalidData(_) => None, + } + } +} + +impl From for ParseError { + fn from(err: JsonRpcConnectorError) -> ParseError { + ParseError::InvalidData(err.to_string()) + } +} + /// Used for decoding zcash blocks from a bytestring. pub trait ParseFromSlice { /// Reads data from a bytestring, consuming data read, and returns an instance of self along with the remaining data in the bytestring given. /// /// Txid is givin as an input as this is taken from a get_block verbose=1 call. - fn parse_from_slice(data: &[u8], txid: Option>) -> Result<(&[u8], Self), ParseError> + fn parse_from_slice( + data: &[u8], + txid: Option>>, + ) -> Result<(&[u8], Self), ParseError> where Self: Sized; } diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 881924b..c3f7281 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -11,7 +11,7 @@ use zebra_chain::{ transaction::{self}, transparent, }; -use zebra_rpc::methods::{GetBlockHash, GetBlockTrees}; +use zebra_rpc::methods::GetBlockHash; /// Response to a `getinfo` RPC request. /// @@ -177,6 +177,15 @@ impl AsRef<[u8]> for ProxySerializedBlock { } } +/// Information about the note commitment trees. +#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxyBlockTrees { + /// Sapling commitment tree size. + pub sapling: u64, + /// Orchard commitment tree size. + pub orchard: u64, +} + /// Contains the hex-encoded hash of the sent transaction. /// /// This is used for the output parameter of [`JsonRpcConnector::get_block`]. @@ -206,7 +215,7 @@ pub enum GetBlockResponse { tx: Vec, /// Information about the note commitment trees. - trees: GetBlockTrees, + trees: zebra_rpc::methods::GetBlockTrees, }, } diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 92c971e..3b87fac 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -12,6 +12,7 @@ use zcash_client_backend::proto::{ use zebra_chain::block::Height; use crate::{ + blockcache::block::get_block_from_node, define_grpc_passthrough, jsonrpc::{ connector::JsonRpcConnector, @@ -202,159 +203,82 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - // type GetBlockRangeStream = tonic::Streaming; - type GetBlockRangeStream = std::pin::Pin>; + type GetBlockRangeStream = tonic::Streaming; + // type GetBlockRangeStream = std::pin::Pin>; // /// Return a list of consecutive compact blocks. // /// // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - // /// - Add get_block_from_node function that fetches block from full node using JsonRpcConnector and updates the block cache with this block. // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - fn get_block_range<'life0, 'async_trait>( - &'life0 self, - request: tonic::Request, - ) -> core::pin::Pin< - Box< - dyn core::future::Future< - Output = std::result::Result< - tonic::Response, - tonic::Status, - >, - > + core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { - println!("@zingoproxyd: Received call of get_block_range."); - let zebrad_uri = self.zebrad_uri.clone(); - Box::pin(async move { - let blockrange = request.into_inner(); - let mut start = blockrange - .start - .map(|s| s.height as u32) - .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - let mut end = blockrange - .end - .map(|e| e.height as u32) - .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - if start > end { - (start, end) = (end, start); - } - - let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - tokio::spawn(async move { - let zebrad_client = JsonRpcConnector::new( - zebrad_uri, - Some("xxxxxx".to_string()), - Some("xxxxxx".to_string()), - ) - .await; - - for height in start..end { - let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; - match block_1 { - Ok(GetBlockResponse::Object { - hash, - confirmations: _, - height, - time, - tx, - trees, - }) => { - let block_0 = - zebrad_client.get_block(hash.0.to_string(), Some(2)).await; - match block_0 { - Ok(GetBlockResponse::Object { - hash: _, - confirmations: _, - height: _, - time: _, - tx: _, - trees: _, - }) => { - if channel_tx - .send(Err(tonic::Status::internal("Received object block type, this should not be possible here.", - ))) - .await - .is_err() - { - break; - } - } - Ok(GetBlockResponse::Raw(block_hex)) => { - let block_hash: Vec = todo!(); //block_hash_0; - let block_height: u64 = height.unwrap().0 as u64; - let block_time: u32 = time.unwrap() as u32; - let block_tx: Vec = todo!(); //tx; - let block_metadata: Option = Some(ChainMetadata { - sapling_commitment_tree_size: todo!(), //trees.sapling.size, - orchard_commitment_tree_size: todo!(), //trees.orchard.size, - }); - if channel_tx - .send(Ok(CompactBlock { - proto_version: todo!(), - height: block_height, - hash: block_hash, - prev_hash: todo!(), - time: block_time, - header: todo!(), - vtx: block_tx, - chain_metadata: block_metadata, - })) - .await - .is_err() - { - break; - } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } - } - } - Ok(GetBlockResponse::Raw(_)) => { - if channel_tx - .send(Err(tonic::Status::internal( - "Received raw block type, this should not be possible here.", - ))) - .await - .is_err() - { - break; - } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } - } - } - }); - let output_stream = CompactBlockStream::new(channel_rx); - let stream_boxed = Box::pin(output_stream); - Ok(tonic::Response::new(stream_boxed)) - }) - } - // define_grpc_passthrough!( - // fn get_block_range( - // &self, - // request: tonic::Request, - // ) -> Self::GetBlockRangeStream - // ); + // fn get_block_range<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_block_range."); + // let zebrad_uri = self.zebrad_uri.clone(); + // Box::pin(async move { + // let blockrange = request.into_inner(); + // let mut start = blockrange + // .start + // .map(|s| s.height as u32) + // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + // let mut end = blockrange + // .end + // .map(|e| e.height as u32) + // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + // if start > end { + // (start, end) = (end, start); + // } + + // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + // tokio::spawn(async move { + // for height in start..end { + // let compact_block = get_block_from_node(&zebrad_uri, &height).await; + // match compact_block { + // Ok(block) => { + // println!("\nCompact Block:\n{:?}\n", block); + + // if channel_tx.send(Ok(block)).await.is_err() { + // break; + // } + // } + // Err(e) => { + // if channel_tx + // .send(Err(tonic::Status::internal(e.to_string()))) + // .await + // .is_err() + // { + // break; + // } + // } + // } + // } + // }); + // let output_stream = CompactBlockStream::new(channel_rx); + // let stream_boxed = Box::pin(output_stream); + // Ok(tonic::Response::new(stream_boxed)) + // }) + // } + define_grpc_passthrough!( + fn get_block_range( + &self, + request: tonic::Request, + ) -> Self::GetBlockRangeStream + ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] diff --git a/zingoproxy-testutils/Cargo.toml b/zingoproxy-testutils/Cargo.toml index 78c7f3b..01e0038 100644 --- a/zingoproxy-testutils/Cargo.toml +++ b/zingoproxy-testutils/Cargo.toml @@ -24,6 +24,9 @@ tonic = { workspace = true } zingo-testutils = { git = "https://github.com/zingolabs/zingolib.git", branch = "nym_integration" } zingoconfig = { git = "https://github.com/zingolabs/zingolib.git", branch = "nym_integration" } zingolib = { git = "https://github.com/zingolabs/zingolib.git", branch = "nym_integration" } +# zingo-testutils = { path = "../../zingolib/zingo-testutils" } +# zingoconfig = { path = "../../zingolib/zingoconfig" } +# zingolib = { path = "../../zingolib/zingolib" } ctrlc = "3.2.1" tempfile = "3.2.0" From b72ecc60a8bd776e83d2642f73a15b4e95d52f55 Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 13 Jun 2024 13:40:08 +0100 Subject: [PATCH 28/40] builds, compact block returned inccorrect --- zingo-rpc/src/blockcache/block.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index de3c3f8..ddbacc9 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -330,7 +330,8 @@ impl FullBlock { }) .collect::, _>>()?; - let header = self.hdr.raw_block_header.to_binary()?; + // let header = self.hdr.raw_block_header.to_binary()?; + let header = Vec::new(); let compact_block = CompactBlock { proto_version: 1, // TODO: check this is correct! From f78ac424a17fe524b1df8db79f009d8bcef9094c Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 13 Jun 2024 17:18:45 +0100 Subject: [PATCH 29/40] fixed txid conversion, get_block_range no works --- zingo-rpc/src/blockcache/block.rs | 31 +++--- zingo-rpc/src/blockcache/utils.rs | 17 ++++ zingo-rpc/src/jsonrpc/connector.rs | 2 - zingo-rpc/src/jsonrpc/primitives.rs | 19 +++- zingo-rpc/src/rpc/service.rs | 152 ++++++++++++++-------------- 5 files changed, 126 insertions(+), 95 deletions(-) diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index ddbacc9..6afa6a1 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -4,7 +4,8 @@ use crate::{ blockcache::{ transaction::FullTransaction, utils::{ - read_bytes, read_i32, read_u32, read_zcash_script_i64, ParseError, ParseFromSlice, + display_txids_to_server, read_bytes, read_i32, read_u32, read_zcash_script_i64, + ParseError, ParseFromSlice, }, }, jsonrpc::{connector::JsonRpcConnector, primitives::GetBlockResponse}, @@ -227,9 +228,6 @@ impl ParseFromSlice for FullBlock { let (remaining_data, block_header_data) = BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None)?; cursor.set_position(data.len() as u64 - remaining_data.len() as u64); - - println!("\nBlockHeaderData: {:?}\n", block_header_data); - let tx_count = CompactSize::read(&mut cursor)?; if txid.len() != tx_count as usize { return Err(ParseError::InvalidData(format!( @@ -253,8 +251,6 @@ impl ParseFromSlice for FullBlock { transactions.push(tx); remaining_data = new_remaining_data; } - println!("\nTransactions: {:?}\n", transactions); - let block_height = Self::get_block_height(&transactions)?; let block_hash = block_header_data.get_hash()?; @@ -330,6 +326,7 @@ impl FullBlock { }) .collect::, _>>()?; + // NOTE: LightWalletD doesnt return a compact block header, however this could be used to return data if required. // let header = self.hdr.raw_block_header.to_binary()?; let header = Vec::new(); @@ -378,6 +375,14 @@ pub async fn get_block_from_node( Some("xxxxxx".to_string()), ) .await; + println!( + "\ntest get_block_response: {:?}\n", + zebrad_client + .get_block(height.to_string(), Some(1)) + .await + .unwrap() + ); + let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; match block_1 { Ok(GetBlockResponse::Object { @@ -402,14 +407,12 @@ pub async fn get_block_from_node( "Received object block type, this should not be possible here.".to_string(), )); } - Ok(GetBlockResponse::Raw(block_hex)) => { - Ok(FullBlock::parse_to_compact( - block_hex.as_ref(), - Some(tx.into_iter().map(|s| s.into_bytes()).collect()), - 0, //trees.sapling as u32, - 2, //trees.orchard as u32, - )?) - } + Ok(GetBlockResponse::Raw(block_hex)) => Ok(FullBlock::parse_to_compact( + block_hex.as_ref(), + Some(display_txids_to_server(tx)), + trees.sapling.size as u32, + trees.orchard.size as u32, + )?), Err(e) => { return Err(e.into()); } diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index bf9b693..9d5d3b0 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -141,3 +141,20 @@ pub fn read_zcash_script_i64(cursor: &mut Cursor<&[u8]>) -> Result) -> Vec> { + txids + .iter() + .map(|txid| { + txid.as_bytes() + .chunks(2) + .map(|chunk| { + let hex_pair = std::str::from_utf8(chunk).unwrap(); + u8::from_str_radix(hex_pair, 16).unwrap() + }) + .rev() + .collect() + }) + .collect() +} diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 8ca8c40..e6dff58 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -301,8 +301,6 @@ impl JsonRpcConnector { /// /// - `hash_or_height`: (string, required, example="1") The hash or height for the block to be returned. /// - `verbosity`: (number, optional, default=1, example=1) 0 for hex encoded data, 1 for a json object, and 2 for json object with transaction data. - /// - /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_block( &self, hash_or_height: String, diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index c3f7281..e2ffd38 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -177,13 +177,26 @@ impl AsRef<[u8]> for ProxySerializedBlock { } } +/// Sapling note commitment tree information. +#[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct ProxyTrees { + /// Commitment tree size. + pub size: u64, +} + +// /// Orchard note commitment tree information. +// #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +// pub struct ProxyOrchardTrees { +// pub size: u64, +// } + /// Information about the note commitment trees. #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ProxyBlockTrees { /// Sapling commitment tree size. - pub sapling: u64, + pub sapling: ProxyTrees, /// Orchard commitment tree size. - pub orchard: u64, + pub orchard: ProxyTrees, } /// Contains the hex-encoded hash of the sent transaction. @@ -215,7 +228,7 @@ pub enum GetBlockResponse { tx: Vec, /// Information about the note commitment trees. - trees: zebra_rpc::methods::GetBlockTrees, + trees: ProxyBlockTrees, //zebra_rpc::methods::GetBlockTrees, }, } diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 3b87fac..40acc30 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -203,82 +203,82 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - type GetBlockRangeStream = tonic::Streaming; - // type GetBlockRangeStream = std::pin::Pin>; - - // /// Return a list of consecutive compact blocks. - // /// - // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - // fn get_block_range<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_block_range."); - // let zebrad_uri = self.zebrad_uri.clone(); - // Box::pin(async move { - // let blockrange = request.into_inner(); - // let mut start = blockrange - // .start - // .map(|s| s.height as u32) - // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - // let mut end = blockrange - // .end - // .map(|e| e.height as u32) - // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - // if start > end { - // (start, end) = (end, start); - // } - - // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - // tokio::spawn(async move { - // for height in start..end { - // let compact_block = get_block_from_node(&zebrad_uri, &height).await; - // match compact_block { - // Ok(block) => { - // println!("\nCompact Block:\n{:?}\n", block); - - // if channel_tx.send(Ok(block)).await.is_err() { - // break; - // } - // } - // Err(e) => { - // if channel_tx - // .send(Err(tonic::Status::internal(e.to_string()))) - // .await - // .is_err() - // { - // break; - // } - // } - // } - // } - // }); - // let output_stream = CompactBlockStream::new(channel_rx); - // let stream_boxed = Box::pin(output_stream); - // Ok(tonic::Response::new(stream_boxed)) - // }) - // } - define_grpc_passthrough!( - fn get_block_range( - &self, - request: tonic::Request, - ) -> Self::GetBlockRangeStream - ); + // type GetBlockRangeStream = tonic::Streaming; + type GetBlockRangeStream = std::pin::Pin>; + + /// Return a list of consecutive compact blocks. + /// + /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. + fn get_block_range<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_range."); + let zebrad_uri = self.zebrad_uri.clone(); + Box::pin(async move { + let blockrange = request.into_inner(); + let mut start = blockrange + .start + .map(|s| s.height as u32) + .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + let mut end = blockrange + .end + .map(|e| e.height as u32) + .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + if start > end { + (start, end) = (end, start); + } + + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + for height in start..end { + let compact_block = get_block_from_node(&zebrad_uri, &height).await; + match compact_block { + Ok(block) => { + println!("\nCompact Block:\n{:?}\n", block); + + if channel_tx.send(Ok(block)).await.is_err() { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + }); + let output_stream = CompactBlockStream::new(channel_rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_block_range( + // &self, + // request: tonic::Request, + // ) -> Self::GetBlockRangeStream + // ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] From 0e612aab968a910d974e2b13a90766d2b4428e1b Mon Sep 17 00:00:00 2001 From: idky137 Date: Thu, 13 Jun 2024 20:52:51 +0100 Subject: [PATCH 30/40] fixed block range inversion bug, possible bug with sapling spend / output return in parse_from_slice, error in parse_full_block --- integration-tests/tests/integrations.rs | 11 +- zingo-rpc/src/blockcache/block.rs | 22 ++-- zingo-rpc/src/blockcache/utils.rs | 2 +- zingo-rpc/src/jsonrpc/primitives.rs | 12 +- zingo-rpc/src/rpc/service.rs | 155 ++++++++++++------------ 5 files changed, 99 insertions(+), 103 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 1d0fef2..81c5667 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -49,11 +49,8 @@ mod wallet { TestManager::launch(online.clone()).await; let zingo_client = test_manager.build_lightclient().await; - test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + test_manager.regtest_manager.generate_n_blocks(2).unwrap(); zingo_client.do_sync(false).await.unwrap(); - - // std::thread::sleep(std::time::Duration::from_secs(10)); - zingo_client .do_send(vec![( &zingolib::get_base_address!(zingo_client, "sapling"), @@ -62,12 +59,16 @@ mod wallet { )]) .await .unwrap(); - test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + test_manager.regtest_manager.generate_n_blocks(2).unwrap(); zingo_client.do_sync(false).await.unwrap(); + // test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + // zingo_client.do_sync(false).await.unwrap(); + let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); assert_eq!(balance.sapling_balance.unwrap(), 250_000); + drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), regtest_handler, diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index 6afa6a1..8240cd2 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -298,11 +298,17 @@ impl FullBlock { /// Decodes a hex encoded zcash full block into a FullBlock struct. pub fn parse_full_block(data: &[u8], txid: Option>>) -> Result { + println!( + "\nIn parse_full_block with inputs:\ndata: {:?}\n\ntxid: {:?}\n", + data, txid + ); let (remaining_data, full_block) = Self::parse_from_slice(data, txid)?; if remaining_data.len() != 0 { - return Err(ParseError::InvalidData( - "Error decoding full block, data ramaining: ({remaining_data})".to_string(), - )); + return Err(ParseError::InvalidData(format!( + "Error decoding full block - Data Remaining: ({:?}) - Compact Block: ({:?})", + remaining_data, + full_block.to_compact(0, 0) + ))); } Ok(full_block) } @@ -326,7 +332,7 @@ impl FullBlock { }) .collect::, _>>()?; - // NOTE: LightWalletD doesnt return a compact block header, however this could be used to return data if required. + // NOTE: LightWalletD doesnt return a compact block header, however this could be used to return data if useful. // let header = self.hdr.raw_block_header.to_binary()?; let header = Vec::new(); @@ -375,14 +381,6 @@ pub async fn get_block_from_node( Some("xxxxxx".to_string()), ) .await; - println!( - "\ntest get_block_response: {:?}\n", - zebrad_client - .get_block(height.to_string(), Some(1)) - .await - .unwrap() - ); - let block_1 = zebrad_client.get_block(height.to_string(), Some(1)).await; match block_1 { Ok(GetBlockResponse::Object { diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index 9d5d3b0..db913cf 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -122,7 +122,7 @@ const OP_1_NEGATE: u8 = 0x4f; const OP_1: u8 = 0x51; const OP_16: u8 = 0x60; -/// Reads and interprets a Zcash (Bitcoin)-custom compact integer encoding used for int64 numbers in scripts. +/// Reads and interprets a Zcash (Bitcoin) custom compact integer encoding used for int64 numbers in scripts. pub fn read_zcash_script_i64(cursor: &mut Cursor<&[u8]>) -> Result { let first_byte = read_bytes(cursor, 1, "Error reading first byte in i64 script hash")?[0]; diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index e2ffd38..bf8d57d 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -179,24 +179,18 @@ impl AsRef<[u8]> for ProxySerializedBlock { /// Sapling note commitment tree information. #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub struct ProxyTrees { +pub struct ProxyTree { /// Commitment tree size. pub size: u64, } -// /// Orchard note commitment tree information. -// #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -// pub struct ProxyOrchardTrees { -// pub size: u64, -// } - /// Information about the note commitment trees. #[derive(Copy, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct ProxyBlockTrees { /// Sapling commitment tree size. - pub sapling: ProxyTrees, + pub sapling: ProxyTree, /// Orchard commitment tree size. - pub orchard: ProxyTrees, + pub orchard: ProxyTree, } /// Contains the hex-encoded hash of the sent transaction. diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 40acc30..995c7de 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -203,82 +203,85 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - // type GetBlockRangeStream = tonic::Streaming; - type GetBlockRangeStream = std::pin::Pin>; - - /// Return a list of consecutive compact blocks. - /// - /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - fn get_block_range<'life0, 'async_trait>( - &'life0 self, - request: tonic::Request, - ) -> core::pin::Pin< - Box< - dyn core::future::Future< - Output = std::result::Result< - tonic::Response, - tonic::Status, - >, - > + core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { - println!("@zingoproxyd: Received call of get_block_range."); - let zebrad_uri = self.zebrad_uri.clone(); - Box::pin(async move { - let blockrange = request.into_inner(); - let mut start = blockrange - .start - .map(|s| s.height as u32) - .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - let mut end = blockrange - .end - .map(|e| e.height as u32) - .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - if start > end { - (start, end) = (end, start); - } - - let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - tokio::spawn(async move { - for height in start..end { - let compact_block = get_block_from_node(&zebrad_uri, &height).await; - match compact_block { - Ok(block) => { - println!("\nCompact Block:\n{:?}\n", block); - - if channel_tx.send(Ok(block)).await.is_err() { - break; - } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } - } - } - }); - let output_stream = CompactBlockStream::new(channel_rx); - let stream_boxed = Box::pin(output_stream); - Ok(tonic::Response::new(stream_boxed)) - }) - } - // define_grpc_passthrough!( - // fn get_block_range( - // &self, - // request: tonic::Request, - // ) -> Self::GetBlockRangeStream - // ); + type GetBlockRangeStream = tonic::Streaming; + // type GetBlockRangeStream = std::pin::Pin>; + + // /// Return a list of consecutive compact blocks. + // /// + // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. + // /// TODO: Add 30s timeout. + // fn get_block_range<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_block_range."); + // let zebrad_uri = self.zebrad_uri.clone(); + // Box::pin(async move { + // let blockrange = request.into_inner(); + // let mut start = blockrange + // .start + // .map(|s| s.height as u32) + // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + // let mut end = blockrange + // .end + // .map(|e| e.height as u32) + // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + // if start > end { + // (start, end) = (end, start); + // } + // println!( + // "\n Fetching Blocks in Range - Start: {}, End: {}\n", + // start, end + // ); + // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + // tokio::spawn(async move { + // for height in (start..=end).rev() { + // let compact_block = get_block_from_node(&zebrad_uri, &height).await; + // match compact_block { + // Ok(block) => { + // println!("\nCompact Block at Height {}: {:?}\n", height, block); + // if channel_tx.send(Ok(block)).await.is_err() { + // break; + // } + // } + // Err(e) => { + // if channel_tx + // .send(Err(tonic::Status::internal(e.to_string()))) + // .await + // .is_err() + // { + // break; + // } + // } + // } + // } + // }); + // let output_stream = CompactBlockStream::new(channel_rx); + // let stream_boxed = Box::pin(output_stream); + // Ok(tonic::Response::new(stream_boxed)) + // }) + // } + define_grpc_passthrough!( + fn get_block_range( + &self, + request: tonic::Request, + ) -> Self::GetBlockRangeStream + ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] From 9498f7233b1f7f9452056dfede0d152fe56e626b Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 14 Jun 2024 15:52:54 +0100 Subject: [PATCH 31/40] get_block_range running, more testing needed.. --- integration-tests/tests/integrations.rs | 33 +++-- zingo-rpc/src/blockcache/block.rs | 42 +++++-- zingo-rpc/src/blockcache/transaction.rs | 86 +++++++++++-- zingo-rpc/src/blockcache/utils.rs | 5 +- zingo-rpc/src/jsonrpc/connector.rs | 10 +- zingo-rpc/src/rpc/service.rs | 158 ++++++++++++------------ 6 files changed, 218 insertions(+), 116 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 81c5667..835a723 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -59,10 +59,21 @@ mod wallet { )]) .await .unwrap(); - test_manager.regtest_manager.generate_n_blocks(2).unwrap(); + // zingo_client + // .do_send(vec![( + // &zingolib::get_base_address!(zingo_client, "sapling"), + // 250_000, + // None, + // )]) + // .await + // .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); + + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; // test_manager.regtest_manager.generate_n_blocks(1).unwrap(); // zingo_client.do_sync(false).await.unwrap(); + // tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); @@ -84,7 +95,7 @@ mod wallet { TestManager::launch(online.clone()).await; let zingo_client = test_manager.build_lightclient().await; - test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + test_manager.regtest_manager.generate_n_blocks(2).unwrap(); zingo_client.do_sync(false).await.unwrap(); zingo_client .do_send(vec![( @@ -94,20 +105,20 @@ mod wallet { )]) .await .unwrap(); - zingo_client - .do_send(vec![( - &zingolib::get_base_address!(zingo_client, "transparent"), - 250_000, - None, - )]) - .await - .unwrap(); + // zingo_client + // .do_send(vec![( + // &zingolib::get_base_address!(zingo_client, "transparent"), + // 250_000, + // None, + // )]) + // .await + // .unwrap(); test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); - assert_eq!(balance.transparent_balance.unwrap(), 500_000); + assert_eq!(balance.transparent_balance.unwrap(), 250_000); drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), regtest_handler, diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index 8240cd2..ba77ae9 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -99,12 +99,18 @@ impl ParseFromSlice for BlockHeaderData { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for BlockHeaderData::parse_from_slice".to_string(), )); } + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for BlockHeaderData::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); let version = read_i32(&mut cursor, "Error reading BlockHeaderData::version")?; @@ -219,15 +225,24 @@ impl ParseFromSlice for FullBlock { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { let txid = txid.ok_or_else(|| { ParseError::InvalidData("txid must be used for FullBlock::parse_from_slice".to_string()) })?; + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for FullBlock::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); let (remaining_data, block_header_data) = - BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None)?; + BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None, None)?; cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + + println!("Block Header decoded: {:?}", block_header_data); + let tx_count = CompactSize::read(&mut cursor)?; if txid.len() != tx_count as usize { return Err(ParseError::InvalidData(format!( @@ -244,12 +259,24 @@ impl ParseFromSlice for FullBlock { "parsing block transactions: not enough data for transaction.", ))); } + println!( + "\nremaining data before tx: {} bytes.\n", + remaining_data.len() + ); + let (new_remaining_data, tx) = FullTransaction::parse_from_slice( &data[cursor.position() as usize..], Some(vec![txid_item.clone()]), + None, )?; transactions.push(tx); remaining_data = new_remaining_data; + cursor.set_position(data.len() as u64 - remaining_data.len() as u64); + + println!( + "\nremaining data after tx: {} bytes.\n", + remaining_data.len() + ); } let block_height = Self::get_block_height(&transactions)?; let block_hash = block_header_data.get_hash()?; @@ -298,15 +325,12 @@ impl FullBlock { /// Decodes a hex encoded zcash full block into a FullBlock struct. pub fn parse_full_block(data: &[u8], txid: Option>>) -> Result { - println!( - "\nIn parse_full_block with inputs:\ndata: {:?}\n\ntxid: {:?}\n", - data, txid - ); - let (remaining_data, full_block) = Self::parse_from_slice(data, txid)?; + println!("Starting Parse Full Block"); + let (remaining_data, full_block) = Self::parse_from_slice(data, txid, None)?; if remaining_data.len() != 0 { return Err(ParseError::InvalidData(format!( - "Error decoding full block - Data Remaining: ({:?}) - Compact Block: ({:?})", - remaining_data, + "Error decoding full block - {} bytes of Remaining data. Compact Block Created: ({:?})", + remaining_data.len(), full_block.to_compact(0, 0) ))); } @@ -360,6 +384,7 @@ impl FullBlock { sapling_commitment_tree_size: u32, orchard_commitment_tree_size: u32, ) -> Result { + let test_block = Self::parse_full_block(data, txid.clone()).unwrap(); // TEST CODE REMOVE BEFORE MERGING! Ok(Self::parse_full_block(data, txid)? .to_compact(sapling_commitment_tree_size, orchard_commitment_tree_size)?) } @@ -391,6 +416,7 @@ pub async fn get_block_from_node( tx, trees, }) => { + println!("\nTxids in Block: {:?}", tx); let block_0 = zebrad_client.get_block(hash.0.to_string(), Some(0)).await; match block_0 { Ok(GetBlockResponse::Object { diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index d9c88f1..9a8bfe9 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -25,12 +25,18 @@ impl ParseFromSlice for TxIn { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for TxIn::parse_from_slice".to_string(), )); } + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for TxIn::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); skip_bytes(&mut cursor, 32, "Error skipping TxIn::PrevTxHash")?; @@ -63,12 +69,18 @@ impl ParseFromSlice for TxOut { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for TxOut::parse_from_slice".to_string(), )); } + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for TxOut::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); let value = read_u64(&mut cursor, "Error TxOut::reading Value")?; @@ -87,18 +99,22 @@ fn parse_transparent(data: &[u8]) -> Result<(&[u8], Vec, Vec), Pars let mut cursor = Cursor::new(data); let tx_in_count = CompactSize::read(&mut cursor)?; + println!("tx_in_count: {}", tx_in_count); + let mut tx_ins = Vec::with_capacity(tx_in_count as usize); for _ in 0..tx_in_count { let (remaining_data, tx_in) = - TxIn::parse_from_slice(&data[cursor.position() as usize..], None)?; + TxIn::parse_from_slice(&data[cursor.position() as usize..], None, None)?; tx_ins.push(tx_in); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } let tx_out_count = CompactSize::read(&mut cursor)?; + println!("tx_out_count: {}", tx_out_count); + let mut tx_outs = Vec::with_capacity(tx_out_count as usize); for _ in 0..tx_out_count { let (remaining_data, tx_out) = - TxOut::parse_from_slice(&data[cursor.position() as usize..], None)?; + TxOut::parse_from_slice(&data[cursor.position() as usize..], None, None)?; tx_outs.push(tx_out); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -125,20 +141,30 @@ impl ParseFromSlice for Spend { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Spend::parse_from_slice".to_string(), )); } + let tx_version = tx_version.ok_or_else(|| { + ParseError::InvalidData( + "tx_version must be used for Spend::parse_from_slice".to_string(), + ) + })?; let mut cursor = Cursor::new(data); skip_bytes(&mut cursor, 32, "Error skipping Spend::Cv")?; - skip_bytes(&mut cursor, 32, "Error skipping Spend::Anchor")?; + if tx_version <= 4 { + skip_bytes(&mut cursor, 32, "Error skipping Spend::Anchor")?; + } let nullifier = read_bytes(&mut cursor, 32, "Error reading Spend::nullifier")?; skip_bytes(&mut cursor, 32, "Error skipping Spend::Rk")?; - skip_bytes(&mut cursor, 32, "Error skipping Spend::Zkproof")?; - skip_bytes(&mut cursor, 32, "Error skipping Spend::SpendAuthSig")?; + if tx_version <= 4 { + skip_bytes(&mut cursor, 192, "Error skipping Spend::Zkproof")?; + skip_bytes(&mut cursor, 64, "Error skipping Spend::SpendAuthSig")?; + } Ok((&data[cursor.position() as usize..], Spend { nullifier })) } @@ -170,12 +196,18 @@ impl ParseFromSlice for Output { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Output::parse_from_slice".to_string(), )); } + let tx_version = tx_version.ok_or_else(|| { + ParseError::InvalidData( + "tx_version must be used for Output::parse_from_slice".to_string(), + ) + })?; let mut cursor = Cursor::new(data); skip_bytes(&mut cursor, 32, "Error skipping Output::Cv")?; @@ -183,7 +215,9 @@ impl ParseFromSlice for Output { let ephemeral_key = read_bytes(&mut cursor, 32, "Error reading Output::ephemeral_key")?; let enc_ciphertext = read_bytes(&mut cursor, 580, "Error reading Output::enc_ciphertext")?; skip_bytes(&mut cursor, 80, "Error skipping Output::OutCiphertext")?; - skip_bytes(&mut cursor, 192, "Error skipping Output::Zkproof")?; + if tx_version <= 4 { + skip_bytes(&mut cursor, 192, "Error skipping Output::Zkproof")?; + } Ok(( &data[cursor.position() as usize..], @@ -219,12 +253,18 @@ impl ParseFromSlice for JoinSplit { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for JoinSplit::parse_from_slice".to_string(), )); } + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for JoinSplit::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); skip_bytes(&mut cursor, 8, "Error skipping JoinSplit::vpubOld")?; @@ -274,12 +314,18 @@ impl ParseFromSlice for Action { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { if txid != None { return Err(ParseError::InvalidData( "txid must be None for Action::parse_from_slice".to_string(), )); } + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for Action::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); skip_bytes(&mut cursor, 32, "Error skipping Action::Cv")?; @@ -387,7 +433,7 @@ impl TransactionData { let mut shielded_spends = Vec::with_capacity(spend_count as usize); for _ in 0..spend_count { let (remaining_data, spend) = - Spend::parse_from_slice(&data[cursor.position() as usize..], None)?; + Spend::parse_from_slice(&data[cursor.position() as usize..], None, Some(4))?; shielded_spends.push(spend); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -395,7 +441,7 @@ impl TransactionData { let mut shielded_outputs = Vec::with_capacity(output_count as usize); for _ in 0..output_count { let (remaining_data, output) = - Output::parse_from_slice(&data[cursor.position() as usize..], None)?; + Output::parse_from_slice(&data[cursor.position() as usize..], None, Some(4))?; shielded_outputs.push(output); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -403,7 +449,7 @@ impl TransactionData { let mut join_splits = Vec::with_capacity(join_split_count as usize); for _ in 0..join_split_count { let (remaining_data, join_split) = - JoinSplit::parse_from_slice(&data[cursor.position() as usize..], None)?; + JoinSplit::parse_from_slice(&data[cursor.position() as usize..], None, None)?; join_splits.push(join_split); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -450,6 +496,7 @@ impl TransactionData { version: u32, n_version_group_id: u32, ) -> Result<(&[u8], Self), ParseError> { + println!("In parse_v5, remaining data: {}", data.len()); if n_version_group_id != 0x26A7270A { return Err(ParseError::InvalidData(format!( "version group ID {:x} must be 0x892F2085 for v5 transactions", @@ -481,10 +528,11 @@ impl TransactionData { spend_count ))); } + println!("spend_count: {}", spend_count); let mut shielded_spends = Vec::with_capacity(spend_count as usize); for _ in 0..spend_count { let (remaining_data, spend) = - Spend::parse_from_slice(&data[cursor.position() as usize..], None)?; + Spend::parse_from_slice(&data[cursor.position() as usize..], None, Some(5))?; shielded_spends.push(spend); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -495,10 +543,11 @@ impl TransactionData { output_count ))); } + println!("output_count: {}", output_count); let mut shielded_outputs = Vec::with_capacity(output_count as usize); for _ in 0..output_count { let (remaining_data, output) = - Output::parse_from_slice(&data[cursor.position() as usize..], None)?; + Output::parse_from_slice(&data[cursor.position() as usize..], None, Some(5))?; shielded_outputs.push(output); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -549,10 +598,11 @@ impl TransactionData { actions_count ))); } + println!("action_count: {}", actions_count); let mut orchard_actions = Vec::with_capacity(actions_count as usize); for _ in 0..actions_count { let (remaining_data, action) = - Action::parse_from_slice(&data[cursor.position() as usize..], None)?; + Action::parse_from_slice(&data[cursor.position() as usize..], None, None)?; orchard_actions.push(action); cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } @@ -627,12 +677,24 @@ impl ParseFromSlice for FullTransaction { fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { + println!( + "in FullTransaction::parse_from_slice with txid: {:?}, remaining data: {}", + txid, + data.len() + ); + let txid = txid.ok_or_else(|| { ParseError::InvalidData( "txid must be used for FullTransaction::parse_from_slice".to_string(), ) })?; + if tx_version != None { + return Err(ParseError::InvalidData( + "tx_version must be None for FullTransaction::parse_from_slice".to_string(), + )); + } let mut cursor = Cursor::new(data); let header = read_u32(&mut cursor, "Error reading FullTransaction::header")?; diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index db913cf..b731961 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -48,10 +48,13 @@ impl From for ParseError { pub trait ParseFromSlice { /// Reads data from a bytestring, consuming data read, and returns an instance of self along with the remaining data in the bytestring given. /// - /// Txid is givin as an input as this is taken from a get_block verbose=1 call. + /// txid is givin as an input as this is taken from a get_block verbose=1 call. + /// + /// tx_version is used for deserializing sapling spends and outputs. fn parse_from_slice( data: &[u8], txid: Option>>, + tx_version: Option, ) -> Result<(&[u8], Self), ParseError> where Self: Sized; diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index e6dff58..750a60c 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -211,11 +211,11 @@ impl JsonRpcConnector { })?; // NOTE: This is useful for development but is not clear to users and should be simplified or completely removed before production. - println!( - "@zingoproxyd: Received response from {} call to node: {:#?}", - method.to_string(), - body_bytes - ); + // println!( + // "@zingoproxyd: Received response from {} call to node: {:#?}", + // method.to_string(), + // body_bytes + // ); let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 995c7de..429ea77 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -203,85 +203,85 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - type GetBlockRangeStream = tonic::Streaming; - // type GetBlockRangeStream = std::pin::Pin>; - - // /// Return a list of consecutive compact blocks. - // /// - // /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. - // /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - // /// TODO: Add 30s timeout. - // fn get_block_range<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_block_range."); - // let zebrad_uri = self.zebrad_uri.clone(); - // Box::pin(async move { - // let blockrange = request.into_inner(); - // let mut start = blockrange - // .start - // .map(|s| s.height as u32) - // .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; - // let mut end = blockrange - // .end - // .map(|e| e.height as u32) - // .ok_or(tonic::Status::invalid_argument("End block not specified"))?; - // if start > end { - // (start, end) = (end, start); - // } - // println!( - // "\n Fetching Blocks in Range - Start: {}, End: {}\n", - // start, end - // ); - // let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); - // tokio::spawn(async move { - // for height in (start..=end).rev() { - // let compact_block = get_block_from_node(&zebrad_uri, &height).await; - // match compact_block { - // Ok(block) => { - // println!("\nCompact Block at Height {}: {:?}\n", height, block); - // if channel_tx.send(Ok(block)).await.is_err() { - // break; - // } - // } - // Err(e) => { - // if channel_tx - // .send(Err(tonic::Status::internal(e.to_string()))) - // .await - // .is_err() - // { - // break; - // } - // } - // } - // } - // }); - // let output_stream = CompactBlockStream::new(channel_rx); - // let stream_boxed = Box::pin(output_stream); - // Ok(tonic::Response::new(stream_boxed)) - // }) - // } - define_grpc_passthrough!( - fn get_block_range( - &self, - request: tonic::Request, - ) -> Self::GetBlockRangeStream - ); + // type GetBlockRangeStream = tonic::Streaming; + type GetBlockRangeStream = std::pin::Pin>; + + /// Return a list of consecutive compact blocks. + /// + /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. + /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. + /// TODO: Add 30s timeout. + fn get_block_range<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_block_range."); + let zebrad_uri = self.zebrad_uri.clone(); + Box::pin(async move { + let blockrange = request.into_inner(); + let mut start = blockrange + .start + .map(|s| s.height as u32) + .ok_or(tonic::Status::invalid_argument("Start block not specified"))?; + let mut end = blockrange + .end + .map(|e| e.height as u32) + .ok_or(tonic::Status::invalid_argument("End block not specified"))?; + if start > end { + (start, end) = (end, start); + } + println!( + "\n Fetching Blocks in Range - Start: {}, End: {}\n", + start, end + ); + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + for height in (start..=end).rev() { + let compact_block = get_block_from_node(&zebrad_uri, &height).await; + match compact_block { + Ok(block) => { + println!("\nCompact Block at Height {}: {:?}\n", height, block); + if channel_tx.send(Ok(block)).await.is_err() { + break; + } + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + }); + let output_stream = CompactBlockStream::new(channel_rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_block_range( + // &self, + // request: tonic::Request, + // ) -> Self::GetBlockRangeStream + // ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] From f2d523469947247df188cbc09cf098faad9a23de Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 14 Jun 2024 17:08:08 +0100 Subject: [PATCH 32/40] added more tests, get_tree_state fails on sync_full_batch --- integration-tests/tests/integrations.rs | 327 ++++++++++++++++++++++-- zingo-rpc/src/blockcache/block.rs | 16 -- zingo-rpc/src/blockcache/transaction.rs | 14 - zingo-rpc/src/rpc/service.rs | 137 +++++----- 4 files changed, 367 insertions(+), 127 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 835a723..81e653a 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -8,7 +8,7 @@ use zingoproxy_testutils::{drop_test_manager, TestManager}; use zingo_netutils::GrpcConnector; -mod wallet { +mod wallet_basic { use super::*; #[tokio::test] @@ -43,13 +43,45 @@ mod wallet { } #[tokio::test] - async fn send_and_sync_shielded() { + async fn send_to_orchard() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; let zingo_client = test_manager.build_lightclient().await; - test_manager.regtest_manager.generate_n_blocks(2).unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "unified"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.orchard_balance.unwrap(), 1_875_000_000); + + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn send_to_sapling() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); zingo_client .do_send(vec![( @@ -59,26 +91,94 @@ mod wallet { )]) .await .unwrap(); - // zingo_client - // .do_send(vec![( - // &zingolib::get_base_address!(zingo_client, "sapling"), - // 250_000, - // None, - // )]) - // .await - // .unwrap(); test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); - tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; - // test_manager.regtest_manager.generate_n_blocks(1).unwrap(); - // zingo_client.do_sync(false).await.unwrap(); - // tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.sapling_balance.unwrap(), 250_000); + + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn send_to_transparent() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.transparent_balance.unwrap(), 250_000); + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn send_to_multiple() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(2).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "unified"), + 250_000, + None, + )]) + .await + .unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.orchard_balance.unwrap(), 2_499_500_000); assert_eq!(balance.sapling_balance.unwrap(), 250_000); + assert_eq!(balance.transparent_balance.unwrap(), 250_000); drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), @@ -89,13 +189,63 @@ mod wallet { } #[tokio::test] - async fn send_and_sync_transparent() { + async fn shield_from_sapling() { let online = Arc::new(AtomicBool::new(true)); let (test_manager, regtest_handler, _proxy_handler) = TestManager::launch(online.clone()).await; let zingo_client = test_manager.build_lightclient().await; - test_manager.regtest_manager.generate_n_blocks(2).unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.sapling_balance.unwrap(), 250_000); + + zingo_client + .do_shield( + &[ + zingolib::wallet::Pool::Sapling, + // zingolib::wallet::Pool::Transparent, + ], + None, + ) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.sapling_balance.unwrap(), 0); + assert_eq!(balance.orchard_balance.unwrap(), 2_500_000_000); + + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn shield_from_transparent() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); zingo_client .do_send(vec![( @@ -105,20 +255,145 @@ mod wallet { )]) .await .unwrap(); - // zingo_client - // .do_send(vec![( - // &zingolib::get_base_address!(zingo_client, "transparent"), - // 250_000, - // None, - // )]) - // .await - // .unwrap(); test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.transparent_balance.unwrap(), 250_000); + + zingo_client + .do_shield( + &[ + // zingolib::wallet::Pool::Sapling, + zingolib::wallet::Pool::Transparent, + ], + None, + ) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.transparent_balance.unwrap(), 0); + assert_eq!(balance.orchard_balance.unwrap(), 2_500_000_000); + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn shield_from_multiple() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.sapling_balance.unwrap(), 250_000); + assert_eq!(balance.transparent_balance.unwrap(), 250_000); + + zingo_client + .do_shield( + &[ + zingolib::wallet::Pool::Sapling, + zingolib::wallet::Pool::Transparent, + ], + None, + ) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.sapling_balance.unwrap(), 0); + assert_eq!(balance.transparent_balance.unwrap(), 0); + assert_eq!(balance.orchard_balance.unwrap(), 2_500_000_000); + + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } + + #[tokio::test] + async fn sync_full_batch() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(2).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + test_manager.regtest_manager.generate_n_blocks(30).unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "unified"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(30).unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(30).unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "transparent"), + 250_000, + None, + )]) + .await + .unwrap(); + test_manager.regtest_manager.generate_n_blocks(30).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.orchard_balance.unwrap(), 76_874_500_000); + assert_eq!(balance.sapling_balance.unwrap(), 250_000); assert_eq!(balance.transparent_balance.unwrap(), 250_000); + drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), regtest_handler, @@ -129,7 +404,7 @@ mod wallet { // TODO: Add test for get_mempool_stream: lightclient::start_mempool_monitor. // #[tokio::test] - // async fn mempool_monitor() {} + // async fn monitor_mempool() {} } mod nym { diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index ba77ae9..8bdf8f6 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -240,9 +240,6 @@ impl ParseFromSlice for FullBlock { let (remaining_data, block_header_data) = BlockHeaderData::parse_from_slice(&data[cursor.position() as usize..], None, None)?; cursor.set_position(data.len() as u64 - remaining_data.len() as u64); - - println!("Block Header decoded: {:?}", block_header_data); - let tx_count = CompactSize::read(&mut cursor)?; if txid.len() != tx_count as usize { return Err(ParseError::InvalidData(format!( @@ -259,11 +256,6 @@ impl ParseFromSlice for FullBlock { "parsing block transactions: not enough data for transaction.", ))); } - println!( - "\nremaining data before tx: {} bytes.\n", - remaining_data.len() - ); - let (new_remaining_data, tx) = FullTransaction::parse_from_slice( &data[cursor.position() as usize..], Some(vec![txid_item.clone()]), @@ -272,11 +264,6 @@ impl ParseFromSlice for FullBlock { transactions.push(tx); remaining_data = new_remaining_data; cursor.set_position(data.len() as u64 - remaining_data.len() as u64); - - println!( - "\nremaining data after tx: {} bytes.\n", - remaining_data.len() - ); } let block_height = Self::get_block_height(&transactions)?; let block_hash = block_header_data.get_hash()?; @@ -325,7 +312,6 @@ impl FullBlock { /// Decodes a hex encoded zcash full block into a FullBlock struct. pub fn parse_full_block(data: &[u8], txid: Option>>) -> Result { - println!("Starting Parse Full Block"); let (remaining_data, full_block) = Self::parse_from_slice(data, txid, None)?; if remaining_data.len() != 0 { return Err(ParseError::InvalidData(format!( @@ -384,7 +370,6 @@ impl FullBlock { sapling_commitment_tree_size: u32, orchard_commitment_tree_size: u32, ) -> Result { - let test_block = Self::parse_full_block(data, txid.clone()).unwrap(); // TEST CODE REMOVE BEFORE MERGING! Ok(Self::parse_full_block(data, txid)? .to_compact(sapling_commitment_tree_size, orchard_commitment_tree_size)?) } @@ -416,7 +401,6 @@ pub async fn get_block_from_node( tx, trees, }) => { - println!("\nTxids in Block: {:?}", tx); let block_0 = zebrad_client.get_block(hash.0.to_string(), Some(0)).await; match block_0 { Ok(GetBlockResponse::Object { diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index 9a8bfe9..780167b 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -99,8 +99,6 @@ fn parse_transparent(data: &[u8]) -> Result<(&[u8], Vec, Vec), Pars let mut cursor = Cursor::new(data); let tx_in_count = CompactSize::read(&mut cursor)?; - println!("tx_in_count: {}", tx_in_count); - let mut tx_ins = Vec::with_capacity(tx_in_count as usize); for _ in 0..tx_in_count { let (remaining_data, tx_in) = @@ -109,8 +107,6 @@ fn parse_transparent(data: &[u8]) -> Result<(&[u8], Vec, Vec), Pars cursor.set_position(data.len() as u64 - remaining_data.len() as u64); } let tx_out_count = CompactSize::read(&mut cursor)?; - println!("tx_out_count: {}", tx_out_count); - let mut tx_outs = Vec::with_capacity(tx_out_count as usize); for _ in 0..tx_out_count { let (remaining_data, tx_out) = @@ -496,7 +492,6 @@ impl TransactionData { version: u32, n_version_group_id: u32, ) -> Result<(&[u8], Self), ParseError> { - println!("In parse_v5, remaining data: {}", data.len()); if n_version_group_id != 0x26A7270A { return Err(ParseError::InvalidData(format!( "version group ID {:x} must be 0x892F2085 for v5 transactions", @@ -528,7 +523,6 @@ impl TransactionData { spend_count ))); } - println!("spend_count: {}", spend_count); let mut shielded_spends = Vec::with_capacity(spend_count as usize); for _ in 0..spend_count { let (remaining_data, spend) = @@ -543,7 +537,6 @@ impl TransactionData { output_count ))); } - println!("output_count: {}", output_count); let mut shielded_outputs = Vec::with_capacity(output_count as usize); for _ in 0..output_count { let (remaining_data, output) = @@ -598,7 +591,6 @@ impl TransactionData { actions_count ))); } - println!("action_count: {}", actions_count); let mut orchard_actions = Vec::with_capacity(actions_count as usize); for _ in 0..actions_count { let (remaining_data, action) = @@ -679,12 +671,6 @@ impl ParseFromSlice for FullTransaction { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - println!( - "in FullTransaction::parse_from_slice with txid: {:?}, remaining data: {}", - txid, - data.len() - ); - let txid = txid.ok_or_else(|| { ParseError::InvalidData( "txid must be used for FullTransaction::parse_from_slice".to_string(), diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 429ea77..79b93ca 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -244,17 +244,12 @@ impl CompactTxStreamer for ProxyClient { if start > end { (start, end) = (end, start); } - println!( - "\n Fetching Blocks in Range - Start: {}, End: {}\n", - start, end - ); let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { for height in (start..=end).rev() { let compact_block = get_block_from_node(&zebrad_uri, &height).await; match compact_block { Ok(block) => { - println!("\nCompact Block at Height {}: {:?}\n", height, block); if channel_tx.send(Ok(block)).await.is_err() { break; } @@ -673,72 +668,72 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetMempoolStreamStream ); - /// GetTreeState returns the note commitment tree state corresponding to the given block. - /// See section 3.7 of the Zcash protocol specification. It returns several other useful - /// values also (even though they can be obtained using GetBlock). - /// The block can be specified by either height or hash. - fn get_tree_state<'life0, 'async_trait>( - &'life0 self, - request: tonic::Request, - ) -> core::pin::Pin< - Box< - dyn core::future::Future< - Output = std::result::Result< - tonic::Response, - tonic::Status, - >, - > + core::marker::Send - + 'async_trait, - >, - > - where - 'life0: 'async_trait, - Self: 'async_trait, - { - println!("@zingoproxyd: Received call of get_tree_state."); - Box::pin(async { - let block_id = request.into_inner(); - let hash_or_height = if block_id.height != 0 { - block_id.height.to_string() - } else { - hex::encode(block_id.hash) - }; - - let zebrad_client = JsonRpcConnector::new( - self.zebrad_uri.clone(), - Some("xxxxxx".to_string()), - Some("xxxxxx".to_string()), - ) - .await; - - // TODO: This is slow. Chain, along with other blockchain info should be saved on startup and used here [blockcache?]. - let chain = zebrad_client - .get_blockchain_info() - .await - .map_err(|e| e.to_grpc_status())? - .chain; - let treestate = zebrad_client - .get_treestate(hash_or_height) - .await - .map_err(|e| e.to_grpc_status())?; - Ok(tonic::Response::new( - zcash_client_backend::proto::service::TreeState { - network: chain, - height: treestate.height as u64, - hash: treestate.hash.to_string(), - time: treestate.time, - sapling_tree: treestate.sapling.commitments.final_state.to_string(), - orchard_tree: treestate.orchard.commitments.final_state.to_string(), - }, - )) - }) - } - // define_grpc_passthrough!( - // fn get_tree_state( - // &self, - // request: tonic::Request, - // ) -> TreeState - // ); + // /// GetTreeState returns the note commitment tree state corresponding to the given block. + // /// See section 3.7 of the Zcash protocol specification. It returns several other useful + // /// values also (even though they can be obtained using GetBlock). + // /// The block can be specified by either height or hash. + // fn get_tree_state<'life0, 'async_trait>( + // &'life0 self, + // request: tonic::Request, + // ) -> core::pin::Pin< + // Box< + // dyn core::future::Future< + // Output = std::result::Result< + // tonic::Response, + // tonic::Status, + // >, + // > + core::marker::Send + // + 'async_trait, + // >, + // > + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // println!("@zingoproxyd: Received call of get_tree_state."); + // Box::pin(async { + // let block_id = request.into_inner(); + // let hash_or_height = if block_id.height != 0 { + // block_id.height.to_string() + // } else { + // hex::encode(block_id.hash) + // }; + + // let zebrad_client = JsonRpcConnector::new( + // self.zebrad_uri.clone(), + // Some("xxxxxx".to_string()), + // Some("xxxxxx".to_string()), + // ) + // .await; + + // // TODO: This is slow. Chain, along with other blockchain info should be saved on startup and used here [blockcache?]. + // let chain = zebrad_client + // .get_blockchain_info() + // .await + // .map_err(|e| e.to_grpc_status())? + // .chain; + // let treestate = zebrad_client + // .get_treestate(hash_or_height) + // .await + // .map_err(|e| e.to_grpc_status())?; + // Ok(tonic::Response::new( + // zcash_client_backend::proto::service::TreeState { + // network: chain, + // height: treestate.height as u64, + // hash: treestate.hash.to_string(), + // time: treestate.time, + // sapling_tree: treestate.sapling.commitments.final_state.to_string(), + // orchard_tree: treestate.orchard.commitments.final_state.to_string(), + // }, + // )) + // }) + // } + define_grpc_passthrough!( + fn get_tree_state( + &self, + request: tonic::Request, + ) -> zcash_client_backend::proto::service::TreeState + ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). From 84ef3359fbce92240ed4df59a1655e9fde364d52 Mon Sep 17 00:00:00 2001 From: idky137 Date: Fri, 14 Jun 2024 18:22:28 +0100 Subject: [PATCH 33/40] all tests pass --- zingo-rpc/src/jsonrpc/connector.rs | 110 ++++++++++++++--------- zingo-rpc/src/rpc/service.rs | 140 ++++++++++++++--------------- 2 files changed, 138 insertions(+), 112 deletions(-) diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 750a60c..48ff6f0 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -169,63 +169,89 @@ impl JsonRpcConnector { &self.uri } - /// Sends a jsonRPC request and returns the response.`` + /// Sends a jsonRPC request and returns the response. + /// + /// NOTE/TODO: This function currently resends the call up to 5 times on a server response of "Work queue depth exceeded". + /// This is because the node's queue can become overloaded and stop servicing RPCs. + /// This functionality is weak and should be incorporated in Zingo-Proxy's queue mechanism [WIP] that handles various errors appropriately. pub async fn send_request Deserialize<'de>>( &self, method: &str, params: T, ) -> Result { let id = self.id_counter.fetch_add(1, Ordering::SeqCst); - let client = Client::builder().build(HttpsConnector::new()); - let mut request_builder = Request::builder() - .method("POST") - .uri(self.uri.clone()) - .header("Content-Type", "application/json"); - if let (Some(user), Some(password)) = (&self.user, &self.password) { - let auth = base64::encode(format!("{}:{}", user, password)); - request_builder = request_builder.header("Authorization", format!("Basic {}", auth)); - } let req = RpcRequest { jsonrpc: "2.0".to_string(), method: method.to_string(), params, id, }; - let request_body = serde_json::to_string(&req).map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) - })?; - // println!("request body`: {:?}", request_body); - let request = request_builder - .body(Body::from(request_body)) - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + let max_attempts = 5; + let mut attempts = 0; + loop { + attempts += 1; + let client = Client::builder().build(HttpsConnector::new()); + let mut request_builder = Request::builder() + .method("POST") + .uri(self.uri.clone()) + .header("Content-Type", "application/json"); + if let (Some(user), Some(password)) = (&self.user, &self.password) { + let auth = base64::encode(format!("{}:{}", user, password)); + request_builder = + request_builder.header("Authorization", format!("Basic {}", auth)); + } + let request_body = serde_json::to_string(&req).map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) })?; - // println!("request: {:?}", request); - let response = client.request(request).await.map_err(|e| { - JsonRpcConnectorError::new_with_source("HTTP request failed", Box::new(e)) - })?; - let body_bytes = hyper::body::to_bytes(response.into_body()) - .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) + let request = request_builder + .body(Body::from(request_body)) + .map_err(|e| { + JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) + })?; + let response = client.request(request).await.map_err(|e| { + JsonRpcConnectorError::new_with_source("HTTP request failed", Box::new(e)) })?; + let body_bytes = hyper::body::to_bytes(response.into_body()) + .await + .map_err(|e| { + JsonRpcConnectorError::new_with_source( + "Failed to read response body", + Box::new(e), + ) + })?; + + // let test_response: RpcResponse = + // serde_json::from_slice(&body_bytes).unwrap_or_else(|e| { + // panic!( + // "Failed to deserialize response: {}\nBody bytes: {:?}", + // e, + // String::from_utf8_lossy(&body_bytes) + // ) + // }); + let body_str = String::from_utf8_lossy(&body_bytes); + if body_str.contains("Work queue depth exceeded") { + if attempts >= max_attempts { + return Err(JsonRpcConnectorError::new( + "Work queue depth exceeded after multiple attempts", + )); + } + tokio::time::sleep(std::time::Duration::from_millis(500)).await; + continue; + } - // NOTE: This is useful for development but is not clear to users and should be simplified or completely removed before production. - // println!( - // "@zingoproxyd: Received response from {} call to node: {:#?}", - // method.to_string(), - // body_bytes - // ); - - let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) - })?; - match response.error { - Some(error) => Err(JsonRpcConnectorError::new(format!( - "RPC Error {}: {}", - error.code, error.message - ))), - None => Ok(response.result), + let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { + JsonRpcConnectorError::new_with_source( + "Failed to deserialize response", + Box::new(e), + ) + })?; + return match response.error { + Some(error) => Err(JsonRpcConnectorError::new(format!( + "RPC Error {}: {}", + error.code, error.message + ))), + None => Ok(response.result), + }; } } diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 79b93ca..056aa56 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -3,10 +3,10 @@ use hex::FromHex; use tokio_stream::wrappers::ReceiverStream; use zcash_client_backend::proto::{ - compact_formats::{ChainMetadata, CompactBlock, CompactTx}, + compact_formats::{CompactBlock, CompactTx}, service::{ - compact_tx_streamer_server::CompactTxStreamer, Address, Balance, BlockId, BlockRange, - Empty, GetAddressUtxosReply, LightdInfo, RawTransaction, SubtreeRoot, + compact_tx_streamer_server::CompactTxStreamer, Address, Balance, BlockId, Empty, + GetAddressUtxosReply, LightdInfo, RawTransaction, SubtreeRoot, }, }; use zebra_chain::block::Height; @@ -16,7 +16,7 @@ use crate::{ define_grpc_passthrough, jsonrpc::{ connector::JsonRpcConnector, - primitives::{GetBlockResponse, GetTransactionResponse, ProxyConsensusBranchIdHex}, + primitives::{GetTransactionResponse, ProxyConsensusBranchIdHex}, }, primitives::ProxyClient, utils::get_build_info, @@ -668,72 +668,72 @@ impl CompactTxStreamer for ProxyClient { ) -> Self::GetMempoolStreamStream ); - // /// GetTreeState returns the note commitment tree state corresponding to the given block. - // /// See section 3.7 of the Zcash protocol specification. It returns several other useful - // /// values also (even though they can be obtained using GetBlock). - // /// The block can be specified by either height or hash. - // fn get_tree_state<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_tree_state."); - // Box::pin(async { - // let block_id = request.into_inner(); - // let hash_or_height = if block_id.height != 0 { - // block_id.height.to_string() - // } else { - // hex::encode(block_id.hash) - // }; - - // let zebrad_client = JsonRpcConnector::new( - // self.zebrad_uri.clone(), - // Some("xxxxxx".to_string()), - // Some("xxxxxx".to_string()), - // ) - // .await; - - // // TODO: This is slow. Chain, along with other blockchain info should be saved on startup and used here [blockcache?]. - // let chain = zebrad_client - // .get_blockchain_info() - // .await - // .map_err(|e| e.to_grpc_status())? - // .chain; - // let treestate = zebrad_client - // .get_treestate(hash_or_height) - // .await - // .map_err(|e| e.to_grpc_status())?; - // Ok(tonic::Response::new( - // zcash_client_backend::proto::service::TreeState { - // network: chain, - // height: treestate.height as u64, - // hash: treestate.hash.to_string(), - // time: treestate.time, - // sapling_tree: treestate.sapling.commitments.final_state.to_string(), - // orchard_tree: treestate.orchard.commitments.final_state.to_string(), - // }, - // )) - // }) - // } - define_grpc_passthrough!( - fn get_tree_state( - &self, - request: tonic::Request, - ) -> zcash_client_backend::proto::service::TreeState - ); + /// GetTreeState returns the note commitment tree state corresponding to the given block. + /// See section 3.7 of the Zcash protocol specification. It returns several other useful + /// values also (even though they can be obtained using GetBlock). + /// The block can be specified by either height or hash. + fn get_tree_state<'life0, 'async_trait>( + &'life0 self, + request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_tree_state."); + Box::pin(async { + let block_id = request.into_inner(); + let hash_or_height = if block_id.height != 0 { + block_id.height.to_string() + } else { + hex::encode(block_id.hash) + }; + + let zebrad_client = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + + // TODO: This is slow. Chain, along with other blockchain info should be saved on startup and used here [blockcache?]. + let chain = zebrad_client + .get_blockchain_info() + .await + .map_err(|e| e.to_grpc_status())? + .chain; + let treestate = zebrad_client + .get_treestate(hash_or_height) + .await + .map_err(|e| e.to_grpc_status())?; + Ok(tonic::Response::new( + zcash_client_backend::proto::service::TreeState { + network: chain, + height: treestate.height as u64, + hash: treestate.hash.to_string(), + time: treestate.time, + sapling_tree: treestate.sapling.commitments.final_state.to_string(), + orchard_tree: treestate.orchard.commitments.final_state.to_string(), + }, + )) + }) + } + // define_grpc_passthrough!( + // fn get_tree_state( + // &self, + // request: tonic::Request, + // ) -> zcash_client_backend::proto::service::TreeState + // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). From 6c35b78415b9ed7f89ffbd715ec30075f477de0d Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 17 Jun 2024 15:10:02 +0100 Subject: [PATCH 34/40] added mempool_manager test --- integration-tests/tests/integrations.rs | 57 ++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 81e653a..33c34dd 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -4,9 +4,9 @@ #![forbid(unsafe_code)] use std::sync::{atomic::AtomicBool, Arc}; -use zingoproxy_testutils::{drop_test_manager, TestManager}; - use zingo_netutils::GrpcConnector; +use zingolib::lightclient::LightClient; +use zingoproxy_testutils::{drop_test_manager, TestManager}; mod wallet_basic { use super::*; @@ -402,9 +402,56 @@ mod wallet_basic { .await; } - // TODO: Add test for get_mempool_stream: lightclient::start_mempool_monitor. - // #[tokio::test] - // async fn monitor_mempool() {} + #[tokio::test] + async fn monitor_unverified_mempool() { + let online = Arc::new(AtomicBool::new(true)); + let (test_manager, regtest_handler, _proxy_handler) = + TestManager::launch(online.clone()).await; + let zingo_client = test_manager.build_lightclient().await; + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); + + let zingo_client_saved = zingo_client.export_save_buffer_async().await.unwrap(); + let zingo_client_loaded = std::sync::Arc::new( + LightClient::read_wallet_from_buffer_async( + zingo_client.config(), + &zingo_client_saved[..], + ) + .await + .unwrap(), + ); + LightClient::start_mempool_monitor(zingo_client_loaded.clone()); + // This seems to be long enough for the mempool monitor to kick in. + // One second is insufficient. Even if this fails, this can only ever be + // a false negative, giving us a balance of 100_000. Still, could be improved. + tokio::time::sleep(std::time::Duration::from_secs(5)).await; + + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.unverified_sapling_balance.unwrap(), 250_000); + + test_manager.regtest_manager.generate_n_blocks(1).unwrap(); + zingo_client.do_sync(false).await.unwrap(); + let balance = zingo_client.do_balance().await; + println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); + assert_eq!(balance.verified_sapling_balance.unwrap(), 250_000); + + drop_test_manager( + Some(test_manager.temp_conf_dir.path().to_path_buf()), + regtest_handler, + online, + ) + .await; + } } mod nym { From 5c1d8b5af1cef3439c0184beb8b6682d55602aea Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 17 Jun 2024 21:38:55 +0100 Subject: [PATCH 35/40] get_mempool_stream implemented --- Cargo.lock | 1 + integration-tests/tests/integrations.rs | 14 ++- zingo-proxyd/src/proxy.rs | 2 +- zingo-rpc/Cargo.toml | 4 +- zingo-rpc/src/blockcache.rs | 1 + zingo-rpc/src/blockcache/mempool.rs | 132 +++++++++++++++++++++++ zingo-rpc/src/jsonrpc/connector.rs | 14 +-- zingo-rpc/src/jsonrpc/primitives.rs | 7 ++ zingo-rpc/src/rpc/service.rs | 134 ++++++++++++++++++------ 9 files changed, 255 insertions(+), 54 deletions(-) create mode 100644 zingo-rpc/src/blockcache/mempool.rs diff --git a/Cargo.lock b/Cargo.lock index 8822c79..ec1d685 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8564,6 +8564,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.8", + "thiserror", "tokio", "tokio-rustls 0.23.4", "tokio-stream", diff --git a/integration-tests/tests/integrations.rs b/integration-tests/tests/integrations.rs index 33c34dd..3a16758 100644 --- a/integration-tests/tests/integrations.rs +++ b/integration-tests/tests/integrations.rs @@ -419,6 +419,14 @@ mod wallet_basic { )]) .await .unwrap(); + zingo_client + .do_send(vec![( + &zingolib::get_base_address!(zingo_client, "sapling"), + 250_000, + None, + )]) + .await + .unwrap(); let zingo_client_saved = zingo_client.export_save_buffer_async().await.unwrap(); let zingo_client_loaded = std::sync::Arc::new( @@ -431,19 +439,17 @@ mod wallet_basic { ); LightClient::start_mempool_monitor(zingo_client_loaded.clone()); // This seems to be long enough for the mempool monitor to kick in. - // One second is insufficient. Even if this fails, this can only ever be - // a false negative, giving us a balance of 100_000. Still, could be improved. tokio::time::sleep(std::time::Duration::from_secs(5)).await; let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); - assert_eq!(balance.unverified_sapling_balance.unwrap(), 250_000); + assert_eq!(balance.unverified_sapling_balance.unwrap(), 500_000); test_manager.regtest_manager.generate_n_blocks(1).unwrap(); zingo_client.do_sync(false).await.unwrap(); let balance = zingo_client.do_balance().await; println!("@zingoproxytest: zingo_client balance: \n{:#?}.", balance); - assert_eq!(balance.verified_sapling_balance.unwrap(), 250_000); + assert_eq!(balance.verified_sapling_balance.unwrap(), 500_000); drop_test_manager( Some(test_manager.temp_conf_dir.path().to_path_buf()), diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 8e06bde..4ae9a52 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -25,7 +25,7 @@ pub async fn spawn_proxy( let mut handles = vec![]; let nym_addr_out: Option; - // startup_message(); + startup_message(); println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; handles.push(proxy_handle); diff --git a/zingo-rpc/Cargo.toml b/zingo-rpc/Cargo.toml index 645c39d..d39dc86 100644 --- a/zingo-rpc/Cargo.toml +++ b/zingo-rpc/Cargo.toml @@ -47,12 +47,10 @@ serde_json = { version = "1.0.117", features = ["preserve_order"] } jsonrpc-core = "18.0.0" indexmap = { version = "2.2.6", features = ["serde"] } - base64 = "0.13.0" tokio-stream = "0.1" futures = "0.3.30" - byteorder = "1" sha2 = "0.10" - +thiserror = "1.0.59" diff --git a/zingo-rpc/src/blockcache.rs b/zingo-rpc/src/blockcache.rs index 0525271..a68f1da 100644 --- a/zingo-rpc/src/blockcache.rs +++ b/zingo-rpc/src/blockcache.rs @@ -1,5 +1,6 @@ //! Zingo-Proxy Block Cache and Mempool State Engine. pub mod block; +pub mod mempool; pub mod transaction; pub mod utils; diff --git a/zingo-rpc/src/blockcache/mempool.rs b/zingo-rpc/src/blockcache/mempool.rs new file mode 100644 index 0000000..09e2b3a --- /dev/null +++ b/zingo-rpc/src/blockcache/mempool.rs @@ -0,0 +1,132 @@ +//! Zingo-Proxy mempool state functionality. + +use std::{collections::HashSet, time::SystemTime}; +use thiserror::Error; +use tokio::sync::{Mutex, RwLock}; + +use crate::jsonrpc::connector::{JsonRpcConnector, JsonRpcConnectorError}; + +/// Mempool state information. +pub struct Mempool { + /// Txids currently in the mempool. + txids: RwLock>, + /// Txids that have already been added to Zingo-Proxy's mempool. + txids_seen: Mutex>, + /// System time when the mempool was last updated. + last_sync_time: Mutex, + /// Blockchain data, used to check when a new block has been mined. + best_block_hash: RwLock>, +} + +/// Mempool Error struct. +#[derive(Error, Debug)] +pub enum MempoolError { + /// Errors from the JsonRPC client. + #[error("JsonRpcConnectorError: {0}")] + JsonRpcError(#[from] JsonRpcConnectorError), +} + +impl Mempool { + /// Returns an empty mempool. + pub fn new() -> Self { + Mempool { + txids: RwLock::new(Vec::new()), + txids_seen: Mutex::new(HashSet::new()), + last_sync_time: Mutex::new(SystemTime::now()), + best_block_hash: RwLock::new(None), + } + } + + /// Updates the mempool, returns true if the current block in the mempool has been mined. + pub async fn update(&self, zebrad_uri: &http::Uri) -> Result { + self.update_last_sync_time().await?; + let mined = self.check_and_update_best_block_hash(zebrad_uri).await?; + if mined { + self.reset_txids().await?; + self.update_txids(zebrad_uri).await?; + Ok(true) + } else { + self.update_txids(zebrad_uri).await?; + Ok(false) + } + } + + /// Updates the txids in the mempool. + async fn update_txids(&self, zebrad_uri: &http::Uri) -> Result<(), MempoolError> { + let node_txids = JsonRpcConnector::new( + zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .get_raw_mempool() + .await? + .transactions; + let mut txids_seen = self.txids_seen.lock().await; + let mut txids = self.txids.write().await; + for txid in node_txids { + if !txids_seen.contains(&txid) { + txids.push(txid.clone()); + } + txids_seen.insert(txid); + } + Ok(()) + } + + /// Updates the system last sync time. + async fn update_last_sync_time(&self) -> Result<(), MempoolError> { + let mut last_sync_time = self.last_sync_time.lock().await; + *last_sync_time = SystemTime::now(); + Ok(()) + } + + /// Updates the mempool blockchain info, returns true if the current block in the mempool has been mined. + async fn check_and_update_best_block_hash( + &self, + zebrad_uri: &http::Uri, + ) -> Result { + let node_best_block_hash = JsonRpcConnector::new( + zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await + .get_blockchain_info() + .await? + .best_block_hash; + + let mut last_best_block_hash = self.best_block_hash.write().await; + + if let Some(ref last_hash) = *last_best_block_hash { + if node_best_block_hash == *last_hash { + return Ok(false); + } + } + + *last_best_block_hash = Some(node_best_block_hash); + Ok(true) + } + + /// Clears the txids currently held in the mempool. + async fn reset_txids(&self) -> Result<(), MempoolError> { + let mut txids = self.txids.write().await; + txids.clear(); + let mut txids_seen = self.txids_seen.lock().await; + txids_seen.clear(); + Ok(()) + } + + /// Returns the txids currently in the mempool. + pub async fn get_mempool_txids(&self) -> Result, MempoolError> { + let txids = self.txids.read().await; + Ok(txids.clone()) + } + + /// Returns the hash of the block currently in the mempool. + pub async fn get_best_block_hash( + &self, + ) -> Result, MempoolError> { + let best_block_hash = self.best_block_hash.read().await; + Ok(best_block_hash.clone()) + } +} diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 48ff6f0..90cba95 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -171,7 +171,7 @@ impl JsonRpcConnector { /// Sends a jsonRPC request and returns the response. /// - /// NOTE/TODO: This function currently resends the call up to 5 times on a server response of "Work queue depth exceeded". + /// TODO: This function currently resends the call up to 5 times on a server response of "Work queue depth exceeded". /// This is because the node's queue can become overloaded and stop servicing RPCs. /// This functionality is weak and should be incorporated in Zingo-Proxy's queue mechanism [WIP] that handles various errors appropriately. pub async fn send_request Deserialize<'de>>( @@ -219,15 +219,6 @@ impl JsonRpcConnector { Box::new(e), ) })?; - - // let test_response: RpcResponse = - // serde_json::from_slice(&body_bytes).unwrap_or_else(|e| { - // panic!( - // "Failed to deserialize response: {}\nBody bytes: {:?}", - // e, - // String::from_utf8_lossy(&body_bytes) - // ) - // }); let body_str = String::from_utf8_lossy(&body_bytes); if body_str.contains("Work queue depth exceeded") { if attempts >= max_attempts { @@ -238,7 +229,6 @@ impl JsonRpcConnector { tokio::time::sleep(std::time::Duration::from_millis(500)).await; continue; } - let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { JsonRpcConnectorError::new_with_source( "Failed to deserialize response", @@ -364,8 +354,6 @@ impl JsonRpcConnector { /// zcashd reference: [`getrawmempool`](https://zcash.github.io/rpc/getrawmempool.html) /// method: post /// tags: blockchain - /// - /// NOTE: Currently unused by Zingo-Proxy and untested! pub async fn get_raw_mempool(&self) -> Result { self.send_request::<(), TxidsResponse>("getrawmempool", ()) .await diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index bf8d57d..ee11669 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -451,6 +451,13 @@ impl<'de> Deserialize<'de> for GetTransactionResponse { confirmations: v["confirmations"].as_u64().unwrap() as u32, }; Ok(obj) + } else if v.get("hex").is_some() && v.get("txid").is_some() { + let obj = GetTransactionResponse::Object { + hex: serde_json::from_value(v["hex"].clone()).unwrap(), + height: -1 as i32, + confirmations: 0 as u32, + }; + Ok(obj) } else { let raw = GetTransactionResponse::Raw(serde_json::from_value(v.clone()).unwrap()); Ok(raw) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 056aa56..36799e6 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -12,8 +12,8 @@ use zcash_client_backend::proto::{ use zebra_chain::block::Height; use crate::{ - blockcache::block::get_block_from_node, - define_grpc_passthrough, + blockcache::{block::get_block_from_node, mempool::Mempool}, + // define_grpc_passthrough, jsonrpc::{ connector::JsonRpcConnector, primitives::{GetTransactionResponse, ProxyConsensusBranchIdHex}, @@ -636,37 +636,105 @@ impl CompactTxStreamer for ProxyClient { /// Server streaming response type for the GetMempoolStream method. #[doc = "Server streaming response type for the GetMempoolStream method."] - type GetMempoolStreamStream = tonic::Streaming; - - // /// Return a stream of current Mempool transactions. This will keep the output stream open while - // /// there are mempool transactions. It will close the returned stream when a new block is mined. - // fn get_mempool_stream<'life0, 'async_trait>( - // &'life0 self, - // request: tonic::Request, - // ) -> core::pin::Pin< - // Box< - // dyn core::future::Future< - // Output = std::result::Result< - // tonic::Response, - // tonic::Status, - // >, - // > + core::marker::Send - // + 'async_trait, - // >, - // > - // where - // 'life0: 'async_trait, - // Self: 'async_trait, - // { - // println!("@zingoproxyd: Received call of get_mempool_stream."); - // Box::pin(async { todo!("get_mempool_stream not yet implemented") }) - // } - define_grpc_passthrough!( - fn get_mempool_stream( - &self, - request: tonic::Request, - ) -> Self::GetMempoolStreamStream - ); + // type GetMempoolStreamStream = tonic::Streaming; + type GetMempoolStreamStream = std::pin::Pin>; + + /// Return a stream of current Mempool transactions. This will keep the output stream open while + /// there are mempool transactions. It will close the returned stream when a new block is mined. + /// + /// TODO: This implementation is slow. Zingo-Proxy's blockcache state engine should keep its own intyernal mempool state. + /// - This RPC should query Zingo-Proxy's internal mempool state rather than creating its owm mempool and directly querying zebrad. + fn get_mempool_stream<'life0, 'async_trait>( + &'life0 self, + _request: tonic::Request, + ) -> core::pin::Pin< + Box< + dyn core::future::Future< + Output = std::result::Result< + tonic::Response, + tonic::Status, + >, + > + core::marker::Send + + 'async_trait, + >, + > + where + 'life0: 'async_trait, + Self: 'async_trait, + { + println!("@zingoproxyd: Received call of get_mempool_stream."); + Box::pin(async { + let zebrad_client = JsonRpcConnector::new( + self.zebrad_uri.clone(), + Some("xxxxxx".to_string()), + Some("xxxxxx".to_string()), + ) + .await; + + let zebrad_uri = self.zebrad_uri.clone(); + let (tx, rx) = tokio::sync::mpsc::channel(32); + tokio::spawn(async move { + let mempool = Mempool::new(); + mempool.update(&zebrad_uri).await.unwrap(); + let mut mined = false; + let mut txid_index: usize = 0; + while !mined { + let mempool_txids = mempool.get_mempool_txids().await.unwrap(); + for txid in &mempool_txids[txid_index..] { + let transaction = zebrad_client + .get_raw_transaction(txid.clone(), Some(1)) + .await; + match transaction { + Ok(GetTransactionResponse::Object { hex, height, .. }) => { + txid_index += 1; + if tx + .send(Ok(RawTransaction { + data: hex.bytes, + height: height as u64, + })) + .await + .is_err() + { + break; + } + } + Ok(GetTransactionResponse::Raw(_)) => { + if tx + .send(Err(tonic::Status::internal( + "Received raw transaction type, this should not be impossible.", + ))) + .await + .is_err() + { + break; + } + } + Err(e) => { + if tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } + } + } + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + mined = mempool.update(&zebrad_uri).await.unwrap(); + } + }); + let output_stream = RawTransactionStream::new(rx); + let stream_boxed = Box::pin(output_stream); + Ok(tonic::Response::new(stream_boxed)) + }) + } + // define_grpc_passthrough!( + // fn get_mempool_stream( + // &self, + // request: tonic::Request, + // ) -> Self::GetMempoolStreamStream + // ); /// GetTreeState returns the note commitment tree state corresponding to the given block. /// See section 3.7 of the Zcash protocol specification. It returns several other useful From 4e91ffff7888bb77fb9be75bdcf8c83408d644b6 Mon Sep 17 00:00:00 2001 From: idky137 Date: Mon, 17 Jun 2024 22:54:20 +0100 Subject: [PATCH 36/40] converted errors to thiserror --- zingo-rpc/src/blockcache/mempool.rs | 5 +- zingo-rpc/src/blockcache/utils.rs | 43 ++----- zingo-rpc/src/jsonrpc/connector.rs | 184 +++++++++------------------- 3 files changed, 69 insertions(+), 163 deletions(-) diff --git a/zingo-rpc/src/blockcache/mempool.rs b/zingo-rpc/src/blockcache/mempool.rs index 09e2b3a..6bbb4ad 100644 --- a/zingo-rpc/src/blockcache/mempool.rs +++ b/zingo-rpc/src/blockcache/mempool.rs @@ -1,7 +1,6 @@ //! Zingo-Proxy mempool state functionality. use std::{collections::HashSet, time::SystemTime}; -use thiserror::Error; use tokio::sync::{Mutex, RwLock}; use crate::jsonrpc::connector::{JsonRpcConnector, JsonRpcConnectorError}; @@ -19,10 +18,10 @@ pub struct Mempool { } /// Mempool Error struct. -#[derive(Error, Debug)] +#[derive(thiserror::Error, Debug)] pub enum MempoolError { /// Errors from the JsonRPC client. - #[error("JsonRpcConnectorError: {0}")] + #[error("JsonRPC Connector Error: {0}")] JsonRpcError(#[from] JsonRpcConnectorError), } diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index b731961..de620d1 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -6,42 +6,17 @@ use std::io::{Cursor, Read}; use crate::jsonrpc::connector::JsonRpcConnectorError; /// Parser Error Type. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum ParseError { - /// Io Error - Io(std::io::Error), - /// Invalid Data Error. + /// Io Error. + #[error("IO Error: {0}")] + Io(#[from] std::io::Error), + /// Invalid Data Error + #[error("Invalid Data Error: {0}")] InvalidData(String), -} - -impl From for ParseError { - fn from(err: std::io::Error) -> ParseError { - ParseError::Io(err) - } -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParseError::Io(err) => write!(f, "IO Error: {}", err), - ParseError::InvalidData(msg) => write!(f, "Invalid Data Error: {}", msg), - } - } -} - -impl std::error::Error for ParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ParseError::Io(err) => Some(err), - ParseError::InvalidData(_) => None, - } - } -} - -impl From for ParseError { - fn from(err: JsonRpcConnectorError) -> ParseError { - ParseError::InvalidData(err.to_string()) - } + /// Errors from the JsonRPC client. + #[error("JsonRPC Connector Error: {0}")] + JsonRpcError(#[from] JsonRpcConnectorError), } /// Used for decoding zcash blocks from a bytestring. diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 90cba95..1025613 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -39,109 +39,61 @@ struct RpcError { } /// General error type for handling JsonRpcConnector errors. -#[derive(Debug)] -pub struct JsonRpcConnectorError { - details: String, - source: Option>, +#[derive(Debug, thiserror::Error)] +pub enum JsonRpcConnectorError { + /// Uncatogorized Errors. + #[error("{0}")] + CustomError(String), + + /// Serialization/Deserialization Errors. + #[error("Serialization/Deserialization Error: {0}")] + SerdeJsonError(#[from] serde_json::Error), + + /// HTTP Request Errors. + #[error("HTTP Request Error: {0}")] + HyperError(#[from] hyper::Error), + + ///HTTP Errors. + #[error("HTTP Error: {0}")] + HttpError(#[from] http::Error), + + /// Invalid URI Errors. + #[error("Invalid URI: {0}")] + InvalidUriError(#[from] http::uri::InvalidUri), + + /// UTF-8 Conversion Errors. + #[error("UTF-8 Conversion Error")] + Utf8Error(#[from] std::string::FromUtf8Error), + + /// Request Timeout Errors. + #[error("Request Timeout Error")] + TimeoutError(#[from] tokio::time::error::Elapsed), } impl JsonRpcConnectorError { /// Constructor for errors without an underlying source pub fn new(msg: impl Into) -> Self { - Self { - details: msg.into(), - source: None, - } - } - - /// Constructor for errors with an underlying source - pub fn new_with_source( - msg: impl Into, - source: Box, - ) -> Self { - Self { - details: msg.into(), - source: Some(source), - } + JsonRpcConnectorError::CustomError(msg.into()) } /// Maps JsonRpcConnectorError to tonic::Status pub fn to_grpc_status(&self) -> tonic::Status { eprintln!("@zingoproxyd: Error occurred: {}.", self); - if let Some(source) = &self.source { - if source.is::() { - return tonic::Status::invalid_argument(self.to_string()); - } else if source.is::() { - return tonic::Status::unavailable(self.to_string()); - } else if source.is::() { - return tonic::Status::internal(self.to_string()); + match self { + JsonRpcConnectorError::SerdeJsonError(_) => { + tonic::Status::invalid_argument(self.to_string()) } + JsonRpcConnectorError::HyperError(_) => tonic::Status::unavailable(self.to_string()), + JsonRpcConnectorError::HttpError(_) => tonic::Status::internal(self.to_string()), + _ => tonic::Status::internal(self.to_string()), } - - tonic::Status::internal(self.to_string()) - } -} - -impl std::error::Error for JsonRpcConnectorError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source - .as_deref() - .map(|e| e as &(dyn std::error::Error + 'static)) - } -} - -impl std::fmt::Display for JsonRpcConnectorError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.details) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: serde_json::Error) -> Self { - JsonRpcConnectorError::new_with_source( - format!("Serialization/Deserialization Error: {}", err), - Box::new(err), - ) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: hyper::Error) -> Self { - JsonRpcConnectorError::new_with_source( - format!("HTTP Request Error: {}", err), - Box::new(err), - ) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: http::Error) -> Self { - JsonRpcConnectorError::new_with_source(format!("HTTP Error: {}", err), Box::new(err)) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: String) -> Self { - JsonRpcConnectorError::new(err) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: std::string::FromUtf8Error) -> Self { - JsonRpcConnectorError::new_with_source("UTF-8 Conversion Error", Box::new(err)) - } -} - -impl From for JsonRpcConnectorError { - fn from(err: tokio::time::error::Elapsed) -> Self { - JsonRpcConnectorError::new_with_source("Request Timeout Error", Box::new(err)) } } impl From for tonic::Status { fn from(err: JsonRpcConnectorError) -> Self { - tonic::Status::internal(err.to_string()) + err.to_grpc_status() } } @@ -200,25 +152,19 @@ impl JsonRpcConnector { request_builder = request_builder.header("Authorization", format!("Basic {}", auth)); } - let request_body = serde_json::to_string(&req).map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to serialize request", Box::new(e)) - })?; + let request_body = serde_json::to_string(&req) + .map_err(|e| JsonRpcConnectorError::SerdeJsonError(e.into()))?; let request = request_builder .body(Body::from(request_body)) - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) - })?; - let response = client.request(request).await.map_err(|e| { - JsonRpcConnectorError::new_with_source("HTTP request failed", Box::new(e)) - })?; + .map_err(|e| JsonRpcConnectorError::HttpError(e.into()))?; + let response = client + .request(request) + .await + .map_err(|e| JsonRpcConnectorError::HyperError(e.into()))?; let body_bytes = hyper::body::to_bytes(response.into_body()) .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source( - "Failed to read response body", - Box::new(e), - ) - })?; + .map_err(|e| JsonRpcConnectorError::HyperError(e.into()))?; + let body_str = String::from_utf8_lossy(&body_bytes); if body_str.contains("Work queue depth exceeded") { if attempts >= max_attempts { @@ -229,12 +175,8 @@ impl JsonRpcConnector { tokio::time::sleep(std::time::Duration::from_millis(500)).await; continue; } - let response: RpcResponse = serde_json::from_slice(&body_bytes).map_err(|e| { - JsonRpcConnectorError::new_with_source( - "Failed to deserialize response", - Box::new(e), - ) - })?; + let response: RpcResponse = serde_json::from_slice(&body_bytes) + .map_err(|e| JsonRpcConnectorError::SerdeJsonError(e.into()))?; return match response.error { Some(error) => Err(JsonRpcConnectorError::new(format!( "RPC Error {}: {}", @@ -499,24 +441,16 @@ pub async fn test_node_connection( .body(Body::from( r#"{"jsonrpc":"2.0","method":"getinfo","params":[],"id":1}"#, )) - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to build request", Box::new(e)) - })?; + .map_err(JsonRpcConnectorError::HttpError)?; let response = tokio::time::timeout(tokio::time::Duration::from_secs(3), client.request(request)) .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Request timed out", Box::new(e)) - })??; + .map_err(JsonRpcConnectorError::TimeoutError)??; let body_bytes = hyper::body::to_bytes(response.into_body()) .await - .map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to read response body", Box::new(e)) - })?; + .map_err(JsonRpcConnectorError::HyperError)?; let _response: RpcResponse = - serde_json::from_slice(&body_bytes).map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to deserialize response", Box::new(e)) - })?; + serde_json::from_slice(&body_bytes).map_err(JsonRpcConnectorError::SerdeJsonError)?; Ok(()) } @@ -526,14 +460,13 @@ pub async fn test_node_and_return_uri( user: Option, password: Option, ) -> Result { - let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port).parse().map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to parse IPv4 URI", Box::new(e)) - })?; - let ipv6_uri: Uri = format!("http://[::1]:{}", port).parse().map_err(|e| { - JsonRpcConnectorError::new_with_source("Failed to parse IPv6 URI", Box::new(e)) - })?; + let ipv4_uri: Uri = format!("http://127.0.0.1:{}", port) + .parse() + .map_err(JsonRpcConnectorError::InvalidUriError)?; + let ipv6_uri: Uri = format!("http://[::1]:{}", port) + .parse() + .map_err(JsonRpcConnectorError::InvalidUriError)?; let mut interval = tokio::time::interval(tokio::time::Duration::from_millis(500)); - for _ in 0..3 { println!("@zingoproxyd: Trying connection on IPv4."); match test_node_connection(ipv4_uri.clone(), user.clone(), password.clone()).await { @@ -563,7 +496,6 @@ pub async fn test_node_and_return_uri( } interval.tick().await; } - eprintln!("@zingoproxyd: Could not establish connection with node. \n@zingoproxyd: Please check config and confirm node is listening at the correct address and the correct authorisation details have been entered. \n@zingoproxyd: Exiting.."); std::process::exit(1); } From 917072e1212e679bc8cfe22c7d7badd69d2a3a05 Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 18 Jun 2024 16:13:46 +0100 Subject: [PATCH 37/40] removed unwraps in PR production code --- zingo-rpc/src/blockcache/block.rs | 2 +- zingo-rpc/src/blockcache/utils.rs | 16 +- zingo-rpc/src/jsonrpc/primitives.rs | 26 +++- zingo-rpc/src/rpc/service.rs | 223 +++++++++------------------- 4 files changed, 104 insertions(+), 163 deletions(-) diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index 8bdf8f6..a00fadc 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -417,7 +417,7 @@ pub async fn get_block_from_node( } Ok(GetBlockResponse::Raw(block_hex)) => Ok(FullBlock::parse_to_compact( block_hex.as_ref(), - Some(display_txids_to_server(tx)), + Some(display_txids_to_server(tx)?), trees.sapling.size as u32, trees.orchard.size as u32, )?), diff --git a/zingo-rpc/src/blockcache/utils.rs b/zingo-rpc/src/blockcache/utils.rs index de620d1..d8169a3 100644 --- a/zingo-rpc/src/blockcache/utils.rs +++ b/zingo-rpc/src/blockcache/utils.rs @@ -17,6 +17,12 @@ pub enum ParseError { /// Errors from the JsonRPC client. #[error("JsonRPC Connector Error: {0}")] JsonRpcError(#[from] JsonRpcConnectorError), + /// UTF-8 conversion error. + #[error("UTF-8 Error: {0}")] + Utf8Error(#[from] std::str::Utf8Error), + /// Hexadecimal parsing error. + #[error("Hex Parse Error: {0}")] + ParseIntError(#[from] std::num::ParseIntError), } /// Used for decoding zcash blocks from a bytestring. @@ -121,18 +127,18 @@ pub fn read_zcash_script_i64(cursor: &mut Cursor<&[u8]>) -> Result) -> Vec> { +pub fn display_txids_to_server(txids: Vec) -> Result>, ParseError> { txids .iter() .map(|txid| { txid.as_bytes() .chunks(2) .map(|chunk| { - let hex_pair = std::str::from_utf8(chunk).unwrap(); - u8::from_str_radix(hex_pair, 16).unwrap() + let hex_pair = std::str::from_utf8(chunk).map_err(ParseError::from)?; + u8::from_str_radix(hex_pair, 16).map_err(ParseError::from) }) .rev() - .collect() + .collect::, _>>() }) - .collect() + .collect::>, _>>() } diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index ee11669..10a6371 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -445,21 +445,33 @@ impl<'de> Deserialize<'de> for GetTransactionResponse { { let v = serde_json::Value::deserialize(deserializer)?; if v.get("height").is_some() && v.get("confirmations").is_some() { + let hex = serde_json::from_value(v["hex"].clone()).map_err(serde::de::Error::custom)?; + let height = v["height"] + .as_i64() + .ok_or_else(|| serde::de::Error::custom("Missing or invalid height"))? + as i32; + let confirmations = v["confirmations"] + .as_u64() + .ok_or_else(|| serde::de::Error::custom("Missing or invalid confirmations"))? + as u32; let obj = GetTransactionResponse::Object { - hex: serde_json::from_value(v["hex"].clone()).unwrap(), - height: v["height"].as_i64().unwrap() as i32, - confirmations: v["confirmations"].as_u64().unwrap() as u32, + hex, + height, + confirmations, }; Ok(obj) } else if v.get("hex").is_some() && v.get("txid").is_some() { + let hex = serde_json::from_value(v["hex"].clone()).map_err(serde::de::Error::custom)?; let obj = GetTransactionResponse::Object { - hex: serde_json::from_value(v["hex"].clone()).unwrap(), - height: -1 as i32, - confirmations: 0 as u32, + hex, + height: -1, + confirmations: 0, }; Ok(obj) } else { - let raw = GetTransactionResponse::Raw(serde_json::from_value(v.clone()).unwrap()); + let raw = GetTransactionResponse::Raw( + serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?, + ); Ok(raw) } } diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 36799e6..6a502ee 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -13,7 +13,6 @@ use zebra_chain::block::Height; use crate::{ blockcache::{block::get_block_from_node, mempool::Mempool}, - // define_grpc_passthrough, jsonrpc::{ connector::JsonRpcConnector, primitives::{GetTransactionResponse, ProxyConsensusBranchIdHex}, @@ -124,12 +123,6 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(block_id)) }) } - // define_grpc_passthrough!( - // fn get_latest_block( - // &self, - // request: tonic::Request, - // ) -> BlockId - // ); /// Return the compact block corresponding to the given block identifier. /// @@ -160,12 +153,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_block not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_block( - // &self, - // request: tonic::Request, - // ) -> CompactBlock - // ); /// Same as GetBlock except actions contain only nullifiers. /// @@ -194,16 +181,9 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_block_nullifiers not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_block_nullifiers( - // &self, - // request: tonic::Request, - // ) -> CompactBlock - // ); /// Server streaming response type for the GetBlockRange method. #[doc = "Server streaming response type for the GetBlockRange method."] - // type GetBlockRangeStream = tonic::Streaming; type GetBlockRangeStream = std::pin::Pin>; /// Return a list of consecutive compact blocks. @@ -271,12 +251,6 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(stream_boxed)) }) } - // define_grpc_passthrough!( - // fn get_block_range( - // &self, - // request: tonic::Request, - // ) -> Self::GetBlockRangeStream - // ); /// Server streaming response type for the GetBlockRangeNullifiers method. #[doc = " Server streaming response type for the GetBlockRangeNullifiers method."] @@ -309,12 +283,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_block_range_nullifiers not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_block_range_nullifiers( - // &self, - // request: tonic::request, - // ) -> self::getblockrangenullifiersstream - // ); /// Return the requested full (not compact) transaction (as from zcashd). fn get_transaction<'life0, 'async_trait>( @@ -356,12 +324,16 @@ impl CompactTxStreamer for ProxyClient { } else { return Err(tonic::Status::not_found("Transaction not received")); }; + let height: u64 = height.try_into().map_err(|_e| { + tonic::Status::internal( + "Invalid response from server - Height conversion failed", + ) + })?; - // TODO: Remove unwrap on height and handle error. Ok(tonic::Response::new( zcash_client_backend::proto::service::RawTransaction { data: hex.bytes, - height: height.try_into().unwrap(), + height, }, )) } else { @@ -371,12 +343,6 @@ impl CompactTxStreamer for ProxyClient { } }) } - // define_grpc_passthrough!( - // fn get_transaction( - // &self, - // request: tonic::Request, - // ) -> RawTransaction - // ); /// Submit the given transaction to the Zcash network. fn send_transaction<'life0, 'async_trait>( @@ -418,16 +384,9 @@ impl CompactTxStreamer for ProxyClient { )) }) } - // define_grpc_passthrough!( - // fn send_transaction( - // &self, - // request: tonic::Request, - // ) -> SendResponse - // ); /// Server streaming response type for the GetTaddressTxids method. #[doc = "Server streaming response type for the GetTaddressTxids method."] - // type GetTaddressTxidsStream = tonic::Streaming; type GetTaddressTxidsStream = std::pin::Pin>; /// This name is misleading, returns the full transactions that have either inputs or outputs connected to the given transparent address. @@ -525,12 +484,6 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(stream_boxed)) }) } - // define_grpc_passthrough!( - // fn get_taddress_txids( - // &self, - // request: tonic::Request, - // ) -> Self::GetTaddressTxidsStream - // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). @@ -557,12 +510,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_taddress_balance not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_taddress_balance( - // &self, - // request: tonic::Request, - // ) -> Balance - // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). @@ -627,23 +574,17 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_mempool_tx not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_mempool_tx( - // &self, - // request: tonic::Request, - // ) -> Self::GetMempoolTxStream - // ); /// Server streaming response type for the GetMempoolStream method. #[doc = "Server streaming response type for the GetMempoolStream method."] - // type GetMempoolStreamStream = tonic::Streaming; type GetMempoolStreamStream = std::pin::Pin>; /// Return a stream of current Mempool transactions. This will keep the output stream open while /// there are mempool transactions. It will close the returned stream when a new block is mined. /// - /// TODO: This implementation is slow. Zingo-Proxy's blockcache state engine should keep its own intyernal mempool state. - /// - This RPC should query Zingo-Proxy's internal mempool state rather than creating its owm mempool and directly querying zebrad. + /// TODO: This implementation is slow. Zingo-Proxy's blockcache state engine should keep its own internal mempool state. + /// - This RPC should query Zingo-Proxy's internal mempool state rather than creating its own mempool and directly querying zebrad. + /// TODO: Add 30s timeout. fn get_mempool_stream<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -675,53 +616,77 @@ impl CompactTxStreamer for ProxyClient { let (tx, rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { let mempool = Mempool::new(); - mempool.update(&zebrad_uri).await.unwrap(); + if let Err(e) = mempool.update(&zebrad_uri).await { + tx.send(Err(tonic::Status::internal(e.to_string()))) + .await + .ok(); + return; + } let mut mined = false; let mut txid_index: usize = 0; while !mined { - let mempool_txids = mempool.get_mempool_txids().await.unwrap(); - for txid in &mempool_txids[txid_index..] { - let transaction = zebrad_client - .get_raw_transaction(txid.clone(), Some(1)) - .await; - match transaction { - Ok(GetTransactionResponse::Object { hex, height, .. }) => { - txid_index += 1; - if tx - .send(Ok(RawTransaction { - data: hex.bytes, - height: height as u64, - })) - .await - .is_err() - { + match mempool.get_mempool_txids().await { + Ok(mempool_txids) => { + for txid in &mempool_txids[txid_index..] { + match zebrad_client + .get_raw_transaction(txid.clone(), Some(1)) + .await { + Ok(GetTransactionResponse::Object { hex, height, .. }) => { + txid_index += 1; + if tx + .send(Ok(RawTransaction { + data: hex.bytes, + height: height as u64, + })) + .await + .is_err() + { + break; + } + } + Ok(GetTransactionResponse::Raw(_)) => { + if tx + .send(Err(tonic::Status::internal( + "Received raw transaction type, this should not be impossible.", + ))) + .await + .is_err() + { + break; + } + } + Err(e) => { + if tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { break; + } + } } } - Ok(GetTransactionResponse::Raw(_)) => { - if tx - .send(Err(tonic::Status::internal( - "Received raw transaction type, this should not be impossible.", - ))) + } + Err(e) => { + if tx + .send(Err(tonic::Status::internal(e.to_string()))) .await .is_err() { break; } - } - Err(e) => { - if tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; - } - } } } tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - mined = mempool.update(&zebrad_uri).await.unwrap(); + mined = match mempool.update(&zebrad_uri).await { + Ok(mined) => mined, + Err(e) => { + tx.send(Err(tonic::Status::internal(e.to_string()))) + .await + .ok(); + break; + } + }; } }); let output_stream = RawTransactionStream::new(rx); @@ -729,12 +694,6 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(stream_boxed)) }) } - // define_grpc_passthrough!( - // fn get_mempool_stream( - // &self, - // request: tonic::Request, - // ) -> Self::GetMempoolStreamStream - // ); /// GetTreeState returns the note commitment tree state corresponding to the given block. /// See section 3.7 of the Zcash protocol specification. It returns several other useful @@ -796,12 +755,6 @@ impl CompactTxStreamer for ProxyClient { )) }) } - // define_grpc_passthrough!( - // fn get_tree_state( - // &self, - // request: tonic::Request, - // ) -> zcash_client_backend::proto::service::TreeState - // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). @@ -828,12 +781,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_latest_tree_state not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_latest_tree_state( - // &self, - // request: tonic::Request, - // ) -> TreeState - // ); /// Server streaming response type for the GetSubtreeRoots method. #[doc = " Server streaming response type for the GetSubtreeRoots method."] @@ -867,12 +814,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_subtree_roots not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_subtree_roots( - // &self, - // request: tonic::Request, - // ) -> Self::GetSubtreeRootsStream - // ); /// This RPC has not been implemented as it is not currently used by zingolib. /// If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy). @@ -901,12 +842,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_address_utxos not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_address_utxos( - // &self, - // request: tonic::Request, - // ) -> GetAddressUtxosReplyList - // ); /// Server streaming response type for the GetAddressUtxosStream method. #[doc = "Server streaming response type for the GetAddressUtxosStream method."] @@ -937,12 +872,6 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("get_address_utxos_stream not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn get_address_utxos_stream( - // &self, - // request: tonic::Request, - // ) -> tonic::Streaming - // ); /// Return information about this lightwalletd instance and the blockchain fn get_lightd_info<'life0, 'async_trait>( @@ -985,7 +914,13 @@ impl CompactTxStreamer for ProxyClient { let sapling_id_str = "76b809bb"; let sapling_id = ProxyConsensusBranchIdHex( - zebra_chain::parameters::ConsensusBranchId::from_hex(sapling_id_str).unwrap(), + zebra_chain::parameters::ConsensusBranchId::from_hex(sapling_id_str).map_err( + |_e| { + tonic::Status::internal( + "Internal Error - Consesnsus Branch ID hex conversion failed", + ) + }, + )?, ); let sapling_height = blockchain_info .upgrades @@ -1014,12 +949,6 @@ impl CompactTxStreamer for ProxyClient { Ok(tonic::Response::new(lightd_info)) }) } - // define_grpc_passthrough!( - // fn get_lightd_info( - // &self, - // request: tonic::Request, - // ) -> LightdInfo - // ); // /// Testing-only, requires lightwalletd --ping-very-insecure (do not enable in production) [from zebrad] /// This RPC has not been implemented as it is not currently used by zingolib. @@ -1047,10 +976,4 @@ impl CompactTxStreamer for ProxyClient { Err(tonic::Status::unimplemented("ping not yet implemented. If you require this RPC please open an issue or PR at the Zingo-Proxy github (https://github.com/zingolabs/zingo-proxy).")) }) } - // define_grpc_passthrough!( - // fn ping( - // &self, - // request: tonic::Request, - // ) -> PingResponse - // ); } From 54608088bb1bc2c3d767df8d06a4f59829783f64 Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 18 Jun 2024 16:46:08 +0100 Subject: [PATCH 38/40] added 30s timeout to streaming rpcs --- zingo-rpc/src/rpc/service.rs | 241 ++++++++++++++++++++--------------- 1 file changed, 137 insertions(+), 104 deletions(-) diff --git a/zingo-rpc/src/rpc/service.rs b/zingo-rpc/src/rpc/service.rs index 6a502ee..ae99841 100644 --- a/zingo-rpc/src/rpc/service.rs +++ b/zingo-rpc/src/rpc/service.rs @@ -1,6 +1,7 @@ //! Lightwallet service RPC implementations. use hex::FromHex; +use tokio::time::timeout; use tokio_stream::wrappers::ReceiverStream; use zcash_client_backend::proto::{ compact_formats::{CompactBlock, CompactTx}, @@ -190,7 +191,6 @@ impl CompactTxStreamer for ProxyClient { /// /// TODO: This implementation is slow. An internal block cache should be implemented that this rpc, along with the get_block rpc, can rely on. /// - add get_block function that queries the block cache for block and calls get_block_from_node to fetch block if not present. - /// TODO: Add 30s timeout. fn get_block_range<'life0, 'async_trait>( &'life0 self, request: tonic::Request, @@ -226,24 +226,36 @@ impl CompactTxStreamer for ProxyClient { } let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { - for height in (start..=end).rev() { - let compact_block = get_block_from_node(&zebrad_uri, &height).await; - match compact_block { - Ok(block) => { - if channel_tx.send(Ok(block)).await.is_err() { - break; + let timeout = timeout(std::time::Duration::from_secs(30), async { + for height in (start..=end).rev() { + let compact_block = get_block_from_node(&zebrad_uri, &height).await; + match compact_block { + Ok(block) => { + if channel_tx.send(Ok(block)).await.is_err() { + break; + } } - } - Err(e) => { - if channel_tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } } } } + }) + .await; + match timeout { + Ok(_) => {} + Err(_) => { + channel_tx + .send(Err(tonic::Status::internal("Request timed out"))) + .await + .ok(); + } } }); let output_stream = CompactBlockStream::new(channel_rx); @@ -390,8 +402,6 @@ impl CompactTxStreamer for ProxyClient { type GetTaddressTxidsStream = std::pin::Pin>; /// This name is misleading, returns the full transactions that have either inputs or outputs connected to the given transparent address. - /// - /// TODO: Add 30 second timout. fn get_taddress_txids<'life0, 'async_trait>( &'life0 self, request: tonic::Request< @@ -439,25 +449,26 @@ impl CompactTxStreamer for ProxyClient { .await .map_err(|e| e.to_grpc_status())?; - let (tx, rx) = tokio::sync::mpsc::channel(32); + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { - for txid in txids.transactions { - let transaction = zebrad_client.get_raw_transaction(txid, Some(1)).await; - match transaction { - Ok(GetTransactionResponse::Object { hex, height, .. }) => { - if tx - .send(Ok(RawTransaction { - data: hex.bytes, - height: height as u64, - })) - .await - .is_err() - { - break; + let timeout = timeout(std::time::Duration::from_secs(30), async { + for txid in txids.transactions { + let transaction = zebrad_client.get_raw_transaction(txid, Some(1)).await; + match transaction { + Ok(GetTransactionResponse::Object { hex, height, .. }) => { + if channel_tx + .send(Ok(RawTransaction { + data: hex.bytes, + height: height as u64, + })) + .await + .is_err() + { + break; + } } - } - Ok(GetTransactionResponse::Raw(_)) => { - if tx + Ok(GetTransactionResponse::Raw(_)) => { + if channel_tx .send(Err(tonic::Status::internal( "Received raw transaction type, this should not be impossible.", ))) @@ -466,20 +477,31 @@ impl CompactTxStreamer for ProxyClient { { break; } - } - Err(e) => { - if tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } } } } + }) + .await; + match timeout { + Ok(_) => {} + Err(_) => { + channel_tx + .send(Err(tonic::Status::internal("Request timed out"))) + .await + .ok(); + } } }); - let output_stream = RawTransactionStream::new(rx); + let output_stream = RawTransactionStream::new(channel_rx); let stream_boxed = Box::pin(output_stream); Ok(tonic::Response::new(stream_boxed)) }) @@ -584,7 +606,6 @@ impl CompactTxStreamer for ProxyClient { /// /// TODO: This implementation is slow. Zingo-Proxy's blockcache state engine should keep its own internal mempool state. /// - This RPC should query Zingo-Proxy's internal mempool state rather than creating its own mempool and directly querying zebrad. - /// TODO: Add 30s timeout. fn get_mempool_stream<'life0, 'async_trait>( &'life0 self, _request: tonic::Request, @@ -613,83 +634,95 @@ impl CompactTxStreamer for ProxyClient { .await; let zebrad_uri = self.zebrad_uri.clone(); - let (tx, rx) = tokio::sync::mpsc::channel(32); + let (channel_tx, channel_rx) = tokio::sync::mpsc::channel(32); tokio::spawn(async move { - let mempool = Mempool::new(); - if let Err(e) = mempool.update(&zebrad_uri).await { - tx.send(Err(tonic::Status::internal(e.to_string()))) - .await - .ok(); - return; - } - let mut mined = false; - let mut txid_index: usize = 0; - while !mined { - match mempool.get_mempool_txids().await { - Ok(mempool_txids) => { - for txid in &mempool_txids[txid_index..] { - match zebrad_client - .get_raw_transaction(txid.clone(), Some(1)) - .await { - Ok(GetTransactionResponse::Object { hex, height, .. }) => { - txid_index += 1; - if tx - .send(Ok(RawTransaction { - data: hex.bytes, - height: height as u64, - })) + let timeout = timeout(std::time::Duration::from_secs(30), async { + let mempool = Mempool::new(); + if let Err(e) = mempool.update(&zebrad_uri).await { + channel_tx.send(Err(tonic::Status::internal(e.to_string()))) + .await + .ok(); + return; + } + let mut mined = false; + let mut txid_index: usize = 0; + while !mined { + match mempool.get_mempool_txids().await { + Ok(mempool_txids) => { + for txid in &mempool_txids[txid_index..] { + match zebrad_client + .get_raw_transaction(txid.clone(), Some(1)) + .await { + Ok(GetTransactionResponse::Object { hex, height, .. }) => { + txid_index += 1; + if channel_tx + .send(Ok(RawTransaction { + data: hex.bytes, + height: height as u64, + })) + .await + .is_err() + { + break; + } + } + Ok(GetTransactionResponse::Raw(_)) => { + if channel_tx + .send(Err(tonic::Status::internal( + "Received raw transaction type, this should not be impossible.", + ))) .await .is_err() { break; } - } - Ok(GetTransactionResponse::Raw(_)) => { - if tx - .send(Err(tonic::Status::internal( - "Received raw transaction type, this should not be impossible.", - ))) - .await - .is_err() - { + } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { break; - } - } - Err(e) => { - if tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { - break; + } } } } } + Err(e) => { + if channel_tx + .send(Err(tonic::Status::internal(e.to_string()))) + .await + .is_err() + { + break; + } + } } - Err(e) => { - if tx - .send(Err(tonic::Status::internal(e.to_string()))) - .await - .is_err() - { + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + mined = match mempool.update(&zebrad_uri).await { + Ok(mined) => mined, + Err(e) => { + channel_tx.send(Err(tonic::Status::internal(e.to_string()))) + .await + .ok(); break; } - } + }; + } + }) + .await; + match timeout { + Ok(_) => {} + Err(_) => { + channel_tx + .send(Err(tonic::Status::internal("Request timed out"))) + .await + .ok(); } - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - mined = match mempool.update(&zebrad_uri).await { - Ok(mined) => mined, - Err(e) => { - tx.send(Err(tonic::Status::internal(e.to_string()))) - .await - .ok(); - break; - } - }; } }); - let output_stream = RawTransactionStream::new(rx); + let output_stream = RawTransactionStream::new(channel_rx); let stream_boxed = Box::pin(output_stream); Ok(tonic::Response::new(stream_boxed)) }) From 420d32d5dc82f858d432ffac42a5c04d75d1c829 Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 18 Jun 2024 17:14:18 +0100 Subject: [PATCH 39/40] applied clippy changes --- zingo-proxyd/src/server.rs | 2 +- zingo-rpc/build.rs | 4 +-- zingo-rpc/src/blockcache/block.rs | 46 ++++++++++--------------- zingo-rpc/src/blockcache/mempool.rs | 8 ++++- zingo-rpc/src/blockcache/transaction.rs | 23 +++++++------ zingo-rpc/src/jsonrpc/connector.rs | 10 +++--- zingo-rpc/src/jsonrpc/primitives.rs | 3 +- zingoproxy-testutils/src/lib.rs | 12 +++---- 8 files changed, 53 insertions(+), 55 deletions(-) diff --git a/zingo-proxyd/src/server.rs b/zingo-proxyd/src/server.rs index 362b721..a52ae80 100644 --- a/zingo-proxyd/src/server.rs +++ b/zingo-proxyd/src/server.rs @@ -96,5 +96,5 @@ pub async fn spawn_server( .unwrap(); let server = ProxyServer::new(lwd_uri, zebra_uri); - server.serve(proxy_port.clone(), online) + server.serve(*proxy_port, online) } diff --git a/zingo-rpc/build.rs b/zingo-rpc/build.rs index 4dd60db..bc2cb1f 100644 --- a/zingo-rpc/build.rs +++ b/zingo-rpc/build.rs @@ -4,7 +4,7 @@ use std::process::Command; fn main() { // Fetch the commit hash let commit_hash = Command::new("git") - .args(&["rev-parse", "HEAD"]) + .args(["rev-parse", "HEAD"]) .output() .expect("Failed to get commit hash") .stdout; @@ -13,7 +13,7 @@ fn main() { // Fetch the current branch let branch = Command::new("git") - .args(&["rev-parse", "--abbrev-ref", "HEAD"]) + .args(["rev-parse", "--abbrev-ref", "HEAD"]) .output() .expect("Failed to get branch") .stdout; diff --git a/zingo-rpc/src/blockcache/block.rs b/zingo-rpc/src/blockcache/block.rs index a00fadc..024b4b7 100644 --- a/zingo-rpc/src/blockcache/block.rs +++ b/zingo-rpc/src/blockcache/block.rs @@ -101,12 +101,12 @@ impl ParseFromSlice for BlockHeaderData { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for BlockHeaderData::parse_from_slice".to_string(), )); } - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for BlockHeaderData::parse_from_slice".to_string(), )); @@ -189,7 +189,7 @@ impl BlockHeaderData { let mut hasher = Sha256::new(); hasher.update(&serialized_header); let digest = hasher.finalize_reset(); - hasher.update(&digest); + hasher.update(digest); let final_digest = hasher.finalize(); Ok(final_digest.to_vec()) @@ -230,7 +230,7 @@ impl ParseFromSlice for FullBlock { let txid = txid.ok_or_else(|| { ParseError::InvalidData("txid must be used for FullBlock::parse_from_slice".to_string()) })?; - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for FullBlock::parse_from_slice".to_string(), )); @@ -252,9 +252,9 @@ impl ParseFromSlice for FullBlock { let mut remaining_data = &data[cursor.position() as usize..]; for txid_item in txid.iter() { if remaining_data.is_empty() { - return Err(ParseError::InvalidData(format!( - "parsing block transactions: not enough data for transaction.", - ))); + return Err(ParseError::InvalidData( + "parsing block transactions: not enough data for transaction.".to_string(), + )); } let (new_remaining_data, tx) = FullTransaction::parse_from_slice( &data[cursor.position() as usize..], @@ -290,7 +290,7 @@ const GENESIS_TARGET_DIFFICULTY: u32 = 520617983; impl FullBlock { /// Extracts the block height from the coinbase transaction. - pub fn get_block_height(transactions: &Vec) -> Result { + pub fn get_block_height(transactions: &[FullTransaction]) -> Result { let coinbase_script = transactions[0].raw_transaction.transparent_inputs[0] .script_sig .as_slice(); @@ -313,7 +313,7 @@ impl FullBlock { /// Decodes a hex encoded zcash full block into a FullBlock struct. pub fn parse_full_block(data: &[u8], txid: Option>>) -> Result { let (remaining_data, full_block) = Self::parse_from_slice(data, txid, None)?; - if remaining_data.len() != 0 { + if !remaining_data.is_empty() { return Err(ParseError::InvalidData(format!( "Error decoding full block - {} bytes of Remaining data. Compact Block Created: ({:?})", remaining_data.len(), @@ -370,8 +370,8 @@ impl FullBlock { sapling_commitment_tree_size: u32, orchard_commitment_tree_size: u32, ) -> Result { - Ok(Self::parse_full_block(data, txid)? - .to_compact(sapling_commitment_tree_size, orchard_commitment_tree_size)?) + Self::parse_full_block(data, txid)? + .to_compact(sapling_commitment_tree_size, orchard_commitment_tree_size) } } @@ -410,29 +410,21 @@ pub async fn get_block_from_node( time: _, tx: _, trees: _, - }) => { - return Err(ParseError::InvalidData( - "Received object block type, this should not be possible here.".to_string(), - )); - } + }) => Err(ParseError::InvalidData( + "Received object block type, this should not be possible here.".to_string(), + )), Ok(GetBlockResponse::Raw(block_hex)) => Ok(FullBlock::parse_to_compact( block_hex.as_ref(), Some(display_txids_to_server(tx)?), trees.sapling.size as u32, trees.orchard.size as u32, )?), - Err(e) => { - return Err(e.into()); - } + Err(e) => Err(e.into()), } } - Ok(GetBlockResponse::Raw(_)) => { - return Err(ParseError::InvalidData( - "Received raw block type, this should not be possible here.".to_string(), - )); - } - Err(e) => { - return Err(e.into()); - } + Ok(GetBlockResponse::Raw(_)) => Err(ParseError::InvalidData( + "Received raw block type, this should not be possible here.".to_string(), + )), + Err(e) => Err(e.into()), } } diff --git a/zingo-rpc/src/blockcache/mempool.rs b/zingo-rpc/src/blockcache/mempool.rs index 6bbb4ad..62f373d 100644 --- a/zingo-rpc/src/blockcache/mempool.rs +++ b/zingo-rpc/src/blockcache/mempool.rs @@ -25,6 +25,12 @@ pub enum MempoolError { JsonRpcError(#[from] JsonRpcConnectorError), } +impl Default for Mempool { + fn default() -> Self { + Self::new() + } +} + impl Mempool { /// Returns an empty mempool. pub fn new() -> Self { @@ -126,6 +132,6 @@ impl Mempool { &self, ) -> Result, MempoolError> { let best_block_hash = self.best_block_hash.read().await; - Ok(best_block_hash.clone()) + Ok(*best_block_hash) } } diff --git a/zingo-rpc/src/blockcache/transaction.rs b/zingo-rpc/src/blockcache/transaction.rs index 780167b..22a98cd 100644 --- a/zingo-rpc/src/blockcache/transaction.rs +++ b/zingo-rpc/src/blockcache/transaction.rs @@ -27,12 +27,12 @@ impl ParseFromSlice for TxIn { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for TxIn::parse_from_slice".to_string(), )); } - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for TxIn::parse_from_slice".to_string(), )); @@ -71,12 +71,12 @@ impl ParseFromSlice for TxOut { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for TxOut::parse_from_slice".to_string(), )); } - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for TxOut::parse_from_slice".to_string(), )); @@ -95,6 +95,7 @@ impl ParseFromSlice for TxOut { } } +#[allow(clippy::type_complexity)] fn parse_transparent(data: &[u8]) -> Result<(&[u8], Vec, Vec), ParseError> { let mut cursor = Cursor::new(data); @@ -139,7 +140,7 @@ impl ParseFromSlice for Spend { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for Spend::parse_from_slice".to_string(), )); @@ -194,7 +195,7 @@ impl ParseFromSlice for Output { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for Output::parse_from_slice".to_string(), )); @@ -251,12 +252,12 @@ impl ParseFromSlice for JoinSplit { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for JoinSplit::parse_from_slice".to_string(), )); } - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for JoinSplit::parse_from_slice".to_string(), )); @@ -312,12 +313,12 @@ impl ParseFromSlice for Action { txid: Option>>, tx_version: Option, ) -> Result<(&[u8], Self), ParseError> { - if txid != None { + if txid.is_some() { return Err(ParseError::InvalidData( "txid must be None for Action::parse_from_slice".to_string(), )); } - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for Action::parse_from_slice".to_string(), )); @@ -676,7 +677,7 @@ impl ParseFromSlice for FullTransaction { "txid must be used for FullTransaction::parse_from_slice".to_string(), ) })?; - if tx_version != None { + if tx_version.is_some() { return Err(ParseError::InvalidData( "tx_version must be None for FullTransaction::parse_from_slice".to_string(), )); diff --git a/zingo-rpc/src/jsonrpc/connector.rs b/zingo-rpc/src/jsonrpc/connector.rs index 1025613..19a6fef 100644 --- a/zingo-rpc/src/jsonrpc/connector.rs +++ b/zingo-rpc/src/jsonrpc/connector.rs @@ -153,17 +153,17 @@ impl JsonRpcConnector { request_builder.header("Authorization", format!("Basic {}", auth)); } let request_body = serde_json::to_string(&req) - .map_err(|e| JsonRpcConnectorError::SerdeJsonError(e.into()))?; + .map_err(JsonRpcConnectorError::SerdeJsonError)?; let request = request_builder .body(Body::from(request_body)) - .map_err(|e| JsonRpcConnectorError::HttpError(e.into()))?; + .map_err(JsonRpcConnectorError::HttpError)?; let response = client .request(request) .await - .map_err(|e| JsonRpcConnectorError::HyperError(e.into()))?; + .map_err(JsonRpcConnectorError::HyperError)?; let body_bytes = hyper::body::to_bytes(response.into_body()) .await - .map_err(|e| JsonRpcConnectorError::HyperError(e.into()))?; + .map_err(JsonRpcConnectorError::HyperError)?; let body_str = String::from_utf8_lossy(&body_bytes); if body_str.contains("Work queue depth exceeded") { @@ -176,7 +176,7 @@ impl JsonRpcConnector { continue; } let response: RpcResponse = serde_json::from_slice(&body_bytes) - .map_err(|e| JsonRpcConnectorError::SerdeJsonError(e.into()))?; + .map_err(JsonRpcConnectorError::SerdeJsonError)?; return match response.error { Some(error) => Err(JsonRpcConnectorError::new(format!( "RPC Error {}: {}", diff --git a/zingo-rpc/src/jsonrpc/primitives.rs b/zingo-rpc/src/jsonrpc/primitives.rs index 10a6371..e26abe3 100644 --- a/zingo-rpc/src/jsonrpc/primitives.rs +++ b/zingo-rpc/src/jsonrpc/primitives.rs @@ -167,13 +167,12 @@ impl FromHex for ProxySerializedBlock { fn from_hex>(hex: T) -> Result { hex::decode(hex) .map(|bytes| ProxySerializedBlock(SerializedBlock::from(bytes))) - .map_err(|e| e.into()) } } impl AsRef<[u8]> for ProxySerializedBlock { fn as_ref(&self) -> &[u8] { - &self.0.as_ref() + self.0.as_ref() } } diff --git a/zingoproxy-testutils/src/lib.rs b/zingoproxy-testutils/src/lib.rs index bfb0325..41767a9 100644 --- a/zingoproxy-testutils/src/lib.rs +++ b/zingoproxy-testutils/src/lib.rs @@ -125,7 +125,7 @@ pub async fn drop_test_manager( } if let Some(ref path) = temp_conf_path { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary regtest configuration directory: {:?}.", e @@ -133,7 +133,7 @@ pub async fn drop_test_manager( } } if let Some(ref path) = Some(temp_wallet_path) { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary directory: {:?}.", e @@ -165,7 +165,7 @@ fn set_custom_drops( default_panic_hook(panic_info); online_panic.store(false, std::sync::atomic::Ordering::SeqCst); if let Some(ref path) = temp_conf_path_panic { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary regtest config directory: {:?}.", e @@ -173,7 +173,7 @@ fn set_custom_drops( } } if let Some(ref path) = temp_wallet_path_panic { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary wallet directory: {:?}.", e @@ -188,7 +188,7 @@ fn set_custom_drops( println!("@zingoproxyd: Received Ctrl+C, exiting."); online_ctrlc.store(false, std::sync::atomic::Ordering::SeqCst); if let Some(ref path) = temp_conf_path_ctrlc { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary regtest config directory: {:?}.", e @@ -196,7 +196,7 @@ fn set_custom_drops( } } if let Some(ref path) = temp_wallet_path_ctrlc { - if let Err(e) = std::fs::remove_dir_all(&path) { + if let Err(e) = std::fs::remove_dir_all(path) { eprintln!( "@zingoproxyd: Failed to delete temporary wallet directory: {:?}.", e From cba31b9641de7827d3d64e506dbf98c79db07eef Mon Sep 17 00:00:00 2001 From: idky137 Date: Tue, 18 Jun 2024 18:05:11 +0100 Subject: [PATCH 40/40] nym poc fixes and readme updated --- README.md | 16 +++++----------- zingo-proxyd/src/bin/zingoproxyd.rs | 9 ++++++++- zingo-proxyd/src/proxy.rs | 10 +++++++++- zingo-rpc/src/rpc/nymservice.rs | 4 ++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 578b1c0..2225b1b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,8 @@ # Zingo-Proxy -A(n eventual) replacement for lightwalletd, written in Rust. - -Currently connects to a lightwalletd, and acts as a man-in-the-middle proxy that does nothing. -Each RPC we wish to support will be added individually, by connecting to zebrad and doing any nessisary processing. -Eventually, we'll no longer have any calls that need to use the lightwalletd, and it can be removed from the network stack entirely. +A rust implemented, nym enhanced, lightwalletd for Zcash. A note to developers/consumers/contributers: The end goal is not an exact one-to-one port of all existing lwd functionaliy. -We currently plan to hold the Service and Darkside RPC implementations, along with a Nym counterpart to the service RPCs for sending and recieving currency over the Nym Mixnet. And a Lightweight gRPC server for testing and development (this may be fleshed out to be a mainnet LightWalletD alternative in the future but is currently not a priority and will depend on zebrad). +We currently plan to hold the Service and Darkside RPC implementations, along with a Nym counterpart to the service RPCs for sending and recieving currency over the Nym Mixnet. # Security Vulnerability Disclosure If you believe you have discovered a security issue, please contact us at: @@ -14,10 +10,10 @@ If you believe you have discovered a security issue, please contact us at: zingodisclosure@proton.me # Zingo-RPC -will eventually hold the rust implementations of the LightWallet Service and Darkside RPCs, along with the wallet-side and server-side Nym Service implementations. +Will eventually hold the rust implementations of the LightWallet Service and Darkside RPCs, along with the wallet-side and server-side Nym Service implementations. # Zingo-ProxyD -A lightweight gRPC server for testing and development. Zingo-ProxyD also has a basic nym server, currently only receives send_transaction commands send over the mixnet. +Currently a lightweight gRPC server for testing and development. Zingo-ProxyD also has a basic nym server, currently only receives send_transaction commands send over the mixnet. This should not be used to run mainnet nodes in its current form as it lacks the queueing and error checking logic necessary. Under the "nym_poc" feature flag Zingo-ProxyD can also act as a Nym powered proxy between zcash wallets and Zingo-ProxyD, capable of sending zcash transactions over the Nym Mixnet. @@ -32,7 +28,7 @@ Our plan is to first enable wallets to send and recieve transactions via a nym p # Dependencies 1) zebrad -2) lightwalletd +2) lightwalletd [require for testing] 3) zingolib [if running zingo-cli] 4) zcashd, zcash-cli [required for integration tests until zebrad has working regtest mode] @@ -45,7 +41,6 @@ Our plan is to first enable wallets to send and recieve transactions via a nym p # zingoproxyd - To run zingo-cli through zingo-proxy, connecting to lightwalletd/zebrad locally: 1) Run `$ zebrad --config #PATH_TO_ZINGO_PROXY/zebrad.toml start` -2) Run `$ ./lightwalletd --no-tls-very-insecure --zcash-conf-path $PATH_TO_ZINGO_PROXY/zcash.conf --data-dir . --log-file /dev/stdout` 3) Run `$ cargo run` From zingolib: @@ -55,7 +50,6 @@ From zingolib: The walletside Nym implementations are moving to ease wallet integration but the POC walletside nym server is still available under the "nym_poc" feature flag. - To run the POC: 1) Run `$ zebrad --config #PATH_TO_ZINGO_PROXY/zebrad.toml start` -2) Run `$ ./lightwalletd --no-tls-very-insecure --zcash-conf-path $PATH_TO_ZINGO_PROXY/zcash.conf --data-dir . --log-file /dev/stdout` 3) Run `$ cargo run` 4) Copy nym address displayed 5) Run `$ cargo run --features "nym_poc" -- ` diff --git a/zingo-proxyd/src/bin/zingoproxyd.rs b/zingo-proxyd/src/bin/zingoproxyd.rs index 78f3cdd..0de3374 100644 --- a/zingo-proxyd/src/bin/zingoproxyd.rs +++ b/zingo-proxyd/src/bin/zingoproxyd.rs @@ -28,7 +28,14 @@ async fn main() { { proxy_port = 8088; } - let lwd_port: u16 = 9067; + + #[allow(unused_mut)] + let mut lwd_port: u16 = 9067; + #[cfg(feature = "nym_poc")] + { + lwd_port = 8080; + } + let zcashd_port: u16 = 18232; let (_handles, _nym_address) = spawn_proxy( diff --git a/zingo-proxyd/src/proxy.rs b/zingo-proxyd/src/proxy.rs index 4ae9a52..3c5bc25 100644 --- a/zingo-proxyd/src/proxy.rs +++ b/zingo-proxyd/src/proxy.rs @@ -29,7 +29,15 @@ pub async fn spawn_proxy( println!("@zingoproxyd: Launching Zingo-Proxy..\n@zingoproxyd: Launching gRPC Server.."); let proxy_handle = spawn_server(proxy_port, lwd_port, zebrad_port, online.clone()).await; handles.push(proxy_handle); - wait_on_grpc_startup(proxy_port, online.clone()).await; + + #[cfg(not(feature = "nym_poc"))] + { + wait_on_grpc_startup(proxy_port, online.clone()).await; + } + #[cfg(feature = "nym_poc")] + { + wait_on_grpc_startup(lwd_port, online.clone()).await; + } #[cfg(not(feature = "nym_poc"))] { diff --git a/zingo-rpc/src/rpc/nymservice.rs b/zingo-rpc/src/rpc/nymservice.rs index e012988..1aece94 100644 --- a/zingo-rpc/src/rpc/nymservice.rs +++ b/zingo-rpc/src/rpc/nymservice.rs @@ -11,11 +11,11 @@ use crate::primitives::NymClient; impl NymClient { /// Forwards the recieved send_transaction request on to a Lightwalletd and returns the response. - /// TODO: Forward to zingo-Proxy instead of lwd. pub async fn nym_send_transaction( request: &RawTransaction, ) -> Result> { - let zproxy_port = 9067; + // TODO: Expose zproxy_port to point to actual zproxy listen port. + let zproxy_port = 8080; let zproxy_uri = Uri::builder() .scheme("http") .authority(format!("localhost:{zproxy_port}"))