From dc6aa708d0ea6857102e79647f8db8c0d9b88aa9 Mon Sep 17 00:00:00 2001 From: Marek Date: Wed, 13 Sep 2023 01:59:56 +0200 Subject: [PATCH] Test `z_getsubtreesbyindex` using a lightwalletd gRPC request (#7521) * Add lightwalletd's protobuf types * Don't explicitly derive `Eq` for enums I got bitten by this bug: https://github.com/tokio-rs/prost/issues/332 when I added the enum `ShieldedProtocol` to the file `service.proto`. The problem is that `prost` implicitly derives `Eq` for enums, so deriving it explicitly via `type_attribute` causes a conflict. Lukily, there is another method `message_attribute` that operates only on messages and not enums. * Test the `z_getsubtreesbyindex` RPC * Fix a typo * Add test vectors --- Cargo.lock | 7 ++ zebrad/Cargo.toml | 1 + zebrad/build.rs | 2 +- .../common/lightwalletd/proto/service.proto | 25 ++++++ .../common/lightwalletd/wallet_grpc_test.rs | 76 ++++++++++++++++++- 5 files changed, 109 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a951d6a05c..234f52d1ff1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1795,6 +1795,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + [[package]] name = "hmac" version = "0.12.1" @@ -5814,6 +5820,7 @@ dependencies = [ "dirs", "futures", "hex", + "hex-literal", "howudoin", "humantime-serde", "hyper", diff --git a/zebrad/Cargo.toml b/zebrad/Cargo.toml index 08c02752995..a989afbe348 100644 --- a/zebrad/Cargo.toml +++ b/zebrad/Cargo.toml @@ -231,6 +231,7 @@ tonic-build = { version = "0.10.0", optional = true } [dev-dependencies] abscissa_core = { version = "0.7.0", features = ["testing"] } hex = "0.4.3" +hex-literal = "0.4.1" jsonrpc-core = "18.0.0" once_cell = "1.18.0" regex = "1.9.5" diff --git a/zebrad/build.rs b/zebrad/build.rs index 07b66ed8fdb..b16a5dda330 100644 --- a/zebrad/build.rs +++ b/zebrad/build.rs @@ -46,7 +46,7 @@ fn main() { // The lightwalletd gRPC types don't use floats or complex collections, // so we can derive `Eq` as well as the default generated `PartialEq` derive. // This fixes `clippy::derive_partial_eq_without_eq` warnings. - .type_attribute(".", "#[derive(Eq)]") + .message_attribute(".", "#[derive(Eq)]") .compile( &["tests/common/lightwalletd/proto/service.proto"], &["tests/common/lightwalletd/proto"], diff --git a/zebrad/tests/common/lightwalletd/proto/service.proto b/zebrad/tests/common/lightwalletd/proto/service.proto index 0ccf47e90a7..db379790775 100644 --- a/zebrad/tests/common/lightwalletd/proto/service.proto +++ b/zebrad/tests/common/lightwalletd/proto/service.proto @@ -117,6 +117,27 @@ message TreeState { string tree = 5; // sapling commitment tree state } +enum ShieldedProtocol { + sapling = 0; + orchard = 1; +} + +// Request type for `GetSubtreeRoots` +message GetSubtreeRootsArg { + uint32 startIndex = 1; // Index identifying where to start returning subtree roots. + ShieldedProtocol shieldedProtocol = 2; // Shielded protocol to return subtree roots for. + uint32 maxEntries = 3; // Maximum number of entries to return, or 0 for all entries. +} + + +// Response type for `GetSubtreeRoots`. +// The actual response contains a stream of `SubtreeRoot`s. +message SubtreeRoot { + bytes rootHash = 2; // The 32-byte Merkle root of the subtree. + bytes completingBlockHash = 3; // The hash of the block that completed this subtree. + uint64 completingBlockHeight = 4; // The height of the block that completed this subtree in the main chain. +} + // Results are sorted by height, which makes it easy to issue another // request that picks up from where the previous left off. message GetAddressUtxosArg { @@ -175,6 +196,10 @@ service CompactTxStreamer { // The block can be specified by either height or hash. rpc GetTreeState(BlockID) returns (TreeState) {} + // Returns a stream of information about roots of subtrees of the Sapling + // and Orchard note commitment trees. + rpc GetSubtreeRoots(GetSubtreeRootsArg) returns (stream SubtreeRoot) {} + rpc GetAddressUtxos(GetAddressUtxosArg) returns (GetAddressUtxosReplyList) {} rpc GetAddressUtxosStream(GetAddressUtxosArg) returns (stream GetAddressUtxosReply) {} diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs index 2001f94f8f1..351c14843a8 100644 --- a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs +++ b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs @@ -35,6 +35,7 @@ //! purposes. use color_eyre::eyre::Result; +use hex_literal::hex; use zebra_chain::{ block::Block, @@ -50,7 +51,8 @@ use crate::common::{ sync::wait_for_zebrad_and_lightwalletd_sync, wallet_grpc::{ connect_to_lightwalletd, Address, AddressList, BlockId, BlockRange, ChainSpec, Empty, - GetAddressUtxosArg, TransparentAddressBlockFilter, TxFilter, + GetAddressUtxosArg, GetSubtreeRootsArg, ShieldedProtocol, + TransparentAddressBlockFilter, TxFilter, }, }, test_type::TestType::UpdateCachedState, @@ -337,6 +339,78 @@ pub async fn run() -> Result<()> { *zebra_test::vectors::SAPLING_TREESTATE_MAINNET_419201_STRING ); + // Call `z_getsubtreesbyindex` separately for + + // ... Sapling. + let mut subtrees = rpc_client + .get_subtree_roots(GetSubtreeRootsArg { + start_index: 0u32, + shielded_protocol: ShieldedProtocol::Sapling.into(), + max_entries: 2u32, + }) + .await? + .into_inner(); + + let mut counter = 0; + while let Some(subtree) = subtrees.message().await? { + match counter { + 0 => { + assert_eq!( + subtree.root_hash, + hex!("754bb593ea42d231a7ddf367640f09bbf59dc00f2c1d2003cc340e0c016b5b13") + ); + assert_eq!(subtree.completing_block_height, 558822u64); + } + 1 => { + assert_eq!( + subtree.root_hash, + hex!("03654c3eacbb9b93e122cf6d77b606eae29610f4f38a477985368197fd68e02d") + ); + assert_eq!(subtree.completing_block_height, 670209u64); + } + _ => { + panic!("The response from the `z_getsubtreesbyindex` RPC contains a wrong number of Sapling subtrees.") + } + } + counter += 1; + } + assert_eq!(counter, 2); + + // ... Orchard. + let mut subtrees = rpc_client + .get_subtree_roots(GetSubtreeRootsArg { + start_index: 0u32, + shielded_protocol: ShieldedProtocol::Orchard.into(), + max_entries: 2u32, + }) + .await? + .into_inner(); + + let mut counter = 0; + while let Some(subtree) = subtrees.message().await? { + match counter { + 0 => { + assert_eq!( + subtree.root_hash, + hex!("d4e323b3ae0cabfb6be4087fec8c66d9a9bbfc354bf1d9588b6620448182063b") + ); + assert_eq!(subtree.completing_block_height, 1707429u64); + } + 1 => { + assert_eq!( + subtree.root_hash, + hex!("8c47d0ca43f323ac573ee57c90af4ced484682827248ca5f3eead95eb6415a14") + ); + assert_eq!(subtree.completing_block_height, 1708132u64); + } + _ => { + panic!("The response from the `z_getsubtreesbyindex` RPC contains a wrong number of Orchard subtrees.") + } + } + counter += 1; + } + assert_eq!(counter, 2); + // Call `GetAddressUtxos` with the ZF funding stream address that will always have utxos let utxos = rpc_client .get_address_utxos(GetAddressUtxosArg {