From 25712f19355dc47465d75a333b8666593cf4a096 Mon Sep 17 00:00:00 2001 From: Alfredo Garcia Date: Wed, 4 May 2022 01:00:46 -0300 Subject: [PATCH] tests(rpc): Add wallet grpc tests (#4253) * add a test that call all the lightwalletd grpc methods * fix some docs * use ZF address in tests for balance and utxos Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- zebrad/tests/acceptance.rs | 10 +- zebrad/tests/common/lightwalletd.rs | 1 + .../lightwalletd/send_transaction_test.rs | 2 +- .../tests/common/lightwalletd/wallet_grpc.rs | 6 +- .../common/lightwalletd/wallet_grpc_test.rs | 249 ++++++++++++++++++ 5 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 zebrad/tests/common/lightwalletd/wallet_grpc_test.rs diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 2bc8be23b..9128b9681 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -1006,7 +1006,7 @@ fn lightwalletd_integration() -> Result<()> { /// Make sure `lightwalletd` can sync from Zebra, in update sync mode. /// -/// If is set, runs a quick sync, then a full sync. +/// If `LIGHTWALLETD_DATA_DIR` is set, runs a quick sync, then a full sync. /// If `LIGHTWALLETD_DATA_DIR` is not set, just runs a full sync. /// /// This test only runs when the `ZEBRA_TEST_LIGHTWALLETD`, @@ -1531,3 +1531,11 @@ async fn fully_synced_rpc_test() -> Result<()> { async fn sending_transactions_using_lightwalletd() -> Result<()> { common::lightwalletd::send_transaction_test::run().await } + +/// Test all the rpc methods a wallet connected to lightwalletd can call. +/// +/// See [`common::lightwalletd::wallet_grpc_test`] for more information. +#[tokio::test] +async fn lightwalletd_wallet_grpc_tests() -> Result<()> { + common::lightwalletd::wallet_grpc_test::run().await +} diff --git a/zebrad/tests/common/lightwalletd.rs b/zebrad/tests/common/lightwalletd.rs index 4f50cda95..6b57bd000 100644 --- a/zebrad/tests/common/lightwalletd.rs +++ b/zebrad/tests/common/lightwalletd.rs @@ -36,6 +36,7 @@ use LightwalletdTestType::*; pub mod send_transaction_test; pub mod wallet_grpc; +pub mod wallet_grpc_test; /// The name of the env var that enables Zebra lightwalletd integration tests. /// These tests need a `lightwalletd` binary in the test machine's path. diff --git a/zebrad/tests/common/lightwalletd/send_transaction_test.rs b/zebrad/tests/common/lightwalletd/send_transaction_test.rs index 715469185..b409a53d6 100644 --- a/zebrad/tests/common/lightwalletd/send_transaction_test.rs +++ b/zebrad/tests/common/lightwalletd/send_transaction_test.rs @@ -73,7 +73,7 @@ pub async fn run() -> Result<()> { )?; let (_lightwalletd, lightwalletd_rpc_port) = - spawn_lightwalletd_with_rpc_server(zebra_rpc_address)?; + spawn_lightwalletd_with_rpc_server(zebra_rpc_address, true)?; let mut rpc_client = connect_to_lightwalletd(lightwalletd_rpc_port).await?; diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc.rs b/zebrad/tests/common/lightwalletd/wallet_grpc.rs index d316cac59..d8d1eda2e 100644 --- a/zebrad/tests/common/lightwalletd/wallet_grpc.rs +++ b/zebrad/tests/common/lightwalletd/wallet_grpc.rs @@ -21,6 +21,7 @@ pub type LightwalletdRpcClient = /// Returns the lightwalletd instance and the port number that it is listening for RPC connections. pub fn spawn_lightwalletd_with_rpc_server( zebrad_rpc_address: SocketAddr, + wait_for_blocks: bool, ) -> Result<(TestChild, u16)> { // We're using cached Zebra state here, so this test type is the most similar let test_type = LightwalletdTestType::UpdateCachedState; @@ -41,8 +42,9 @@ pub fn spawn_lightwalletd_with_rpc_server( .with_failure_regex_iter(lightwalletd_failure_messages, lightwalletd_ignore_messages); lightwalletd.expect_stdout_line_matches("Starting gRPC server")?; - lightwalletd.expect_stdout_line_matches("Waiting for block")?; - + if wait_for_blocks { + lightwalletd.expect_stdout_line_matches("Waiting for block")?; + } Ok((lightwalletd, lightwalletd_rpc_port)) } diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs new file mode 100644 index 000000000..311c7a5aa --- /dev/null +++ b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs @@ -0,0 +1,249 @@ +//! Test all grpc calls a wallet connected to a lightwalletd instance backed by zebra can do. +//! +//! This test requires a cached chain state that is partially synchronized, i.e., it should be a +//! few blocks below the network chain tip height. It also requires a lightwalletd data dir in sync +//! with the cached chain state. +//! +//! Current coverage of all available rpc methods according to `CompactTxStreamer`: +//! +//! - `GetLatestBlock`: Covered. +//! - `GetBlock`: Covered. +//! - `GetBlockRange`: Covered. +//! +//! - `GetTransaction`: Covered. +//! - `SendTransaction`: Not covered and it will never will, it has its own test. +//! +//! - `GetTaddressTxids`: Not covered, need #4216 to be fixed first. +//! - `GetTaddressBalance`: Covered. +//! - `GetTaddressBalanceStream`: Not covered. +//! +//! - `GetMempoolTx`: Not covered. +//! - `GetMempoolStream`: Not covered. +//! +//! - `GetTreeState`: Not covered, Need #3990 +//! +//! - `GetAddressUtxos` -= Covered. +//! - `GetAddressUtxosStream`: Not covered. +//! +//! - `GetLightdInfo`: Covered. +//! - `Ping`: Not covered and it will never will, ping is only used for testing purposes. + +use color_eyre::eyre::Result; + +use zebra_chain::{ + block::Block, parameters::Network, parameters::NetworkUpgrade::Canopy, + serialization::ZcashDeserializeInto, +}; + +use zebra_network::constants::USER_AGENT; + +use crate::common::{ + launch::spawn_zebrad_for_rpc_without_initial_peers, + lightwalletd::{ + wallet_grpc::{ + connect_to_lightwalletd, spawn_lightwalletd_with_rpc_server, AddressList, BlockId, + BlockRange, ChainSpec, Empty, GetAddressUtxosArg, TxFilter, + }, + zebra_skip_lightwalletd_tests, + LightwalletdTestType::UpdateCachedState, + LIGHTWALLETD_DATA_DIR_VAR, LIGHTWALLETD_TEST_TIMEOUT, ZEBRA_CACHED_STATE_DIR_VAR, + }, +}; + +/// The test entry point. +pub async fn run() -> Result<()> { + zebra_test::init(); + + // Skip the test unless the user specifically asked for it + if zebra_skip_lightwalletd_tests() { + return Ok(()); + } + + // We want a zebra state dir and a lightwalletd data dir in place, + // so `UpdateCachedState` can be used as our test type + let test_type = UpdateCachedState; + + // Require to have a `ZEBRA_CACHED_STATE_DIR` in place + let zebrad_state_path = test_type.zebrad_state_path(); + if zebrad_state_path.is_none() { + tracing::info!( + "skipped {test_type:?} lightwalletd test, \ + set the {ZEBRA_CACHED_STATE_DIR_VAR:?} environment variable to run the test", + ); + + return Ok(()); + } + + // Require to have a `LIGHTWALLETD_DATA_DIR` in place + let lightwalletd_state_path = test_type.lightwalletd_state_path(); + if lightwalletd_state_path.is_none() { + tracing::info!( + "skipped {test_type:?} lightwalletd test, \ + set the {LIGHTWALLETD_DATA_DIR_VAR:?} environment variable to run the test", + ); + + return Ok(()); + } + + // This test is only for the mainnet + let network = Network::Mainnet; + + // Launch zebra using a predefined zebrad state path + let (_zebrad, zebra_rpc_address) = spawn_zebrad_for_rpc_without_initial_peers( + network, + zebrad_state_path.unwrap(), + LIGHTWALLETD_TEST_TIMEOUT, + )?; + + // Launch lightwalletd + let (_lightwalletd, lightwalletd_rpc_port) = + spawn_lightwalletd_with_rpc_server(zebra_rpc_address, false)?; + + // Give lightwalletd a few seconds to open its grpc port before connecting to it + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + // Connect to the lightwalletd instance + let mut rpc_client = connect_to_lightwalletd(lightwalletd_rpc_port).await?; + + // End of the setup and start the tests + + // Call `GetLatestBlock` + let block_tip = rpc_client + .get_latest_block(ChainSpec {}) + .await? + .into_inner(); + + // As we are using a pretty much synchronized blockchain, we can assume the tip is above the Canopy network upgrade + assert!(block_tip.height > Canopy.activation_height(network).unwrap().0 as u64); + + // Call `GetBlock` with block 1 height + let block_one = rpc_client + .get_block(BlockId { + height: 1, + hash: vec![], + }) + .await? + .into_inner(); + + // Make sure we got block 1 back + assert_eq!(block_one.height, 1); + + // Call `GetBlockRange` with the range starting at block 1 up to block 10 + let mut block_range = rpc_client + .get_block_range(BlockRange { + start: Some(BlockId { + height: 1, + hash: vec![], + }), + end: Some(BlockId { + height: 10, + hash: vec![], + }), + }) + .await? + .into_inner(); + + // Make sure the returned Stream of blocks is what we expect + let mut counter = 0; + while let Some(block) = block_range.message().await? { + counter += 1; + assert_eq!(block.height, counter); + } + + // Get the first transction of the first block in the mainnet + let hash = zebra_test::vectors::BLOCK_MAINNET_1_BYTES + .zcash_deserialize_into::() + .expect("block should deserialize") + .transactions[0] + .hash() + .0 + .to_vec(); + + // Call `GetTransaction` with the transaction hash + let transaction = rpc_client + .get_transaction(TxFilter { + block: None, + index: 0, + hash, + }) + .await? + .into_inner(); + + // Check the height of transactions is 1 as expected + assert_eq!(transaction.height, 1); + + // TODO: Add after #4216 + // Currently, this call fails with: + // 0: status: Unknown, message: "-32602: Invalid params: invalid length 1, expected a tuple of size 3.", details: [], metadata: MetadataMap { headers: {"content-type": "application/grpc"} } + + /* + // Call `GetTaddressTxids` with a founders reward address that we know exists and have transactions in the first + // few blocks of the mainnet + let transactions = rpc_client.get_taddress_txids(TransparentAddressBlockFilter { + address: "t3Vz22vK5z2LcKEdg16Yv4FFneEL1zg9ojd".to_string(), + range: Some(BlockRange { + start: Some(BlockId { + height: 1, + hash: vec![], + }), + end: Some(BlockId { + height: 10, + hash: vec![], + }), + }) + }).await?.into_inner(); + + dbg!(transactions); + */ + + // Call `GetTaddressBalance` with the ZF funding stream address + let balance = rpc_client + .get_taddress_balance(AddressList { + addresses: vec!["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1".to_string()], + }) + .await? + .into_inner(); + + // With ZFND or Major Grants funding stream address, the balance will always be greater than zero, + // because new coins are created in each block + assert!(balance.value_zat > 0); + + // TODO: Create call and check for `GetTaddressBalanceStream` + + // TODO: Create call and checks for `GetMempoolTx` and `GetMempoolTxStream`? + + // TODO: Activate after #3990 is merged + // Currently, this call fails as the method is not available + /* + // Call `GetTreeState` for block 1. + let tree_state = rpc_client.get_tree_state(BlockId { + height: 1, + hash: vec![] + }).await?.into_inner(); + + dbg!(tree_state); + */ + + // Call `GetAddressUtxos` with the ZF funding stream address that will always have utxos + let utxos = rpc_client + .get_address_utxos(GetAddressUtxosArg { + addresses: vec!["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1".to_string()], + start_height: 1, + max_entries: 1, + }) + .await? + .into_inner(); + + // As we requested one entry we should get a response of length 1 + assert_eq!(utxos.address_utxos.len(), 1); + + // TODO: Create call and check for `GetAddressUtxosStream` + + // Call `GetLightdInfo` + let lightd_info = rpc_client.get_lightd_info(Empty {}).await?.into_inner(); + + // Make sure the subversion field is zebra the user agent + assert_eq!(lightd_info.zcashd_subversion, USER_AGENT); + + Ok(()) +}