zebra/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs

244 lines
7.7 KiB
Rust

//! 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`: Covered.
//! - `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, TransparentAddressBlockFilter,
TxFilter,
},
zebra_skip_lightwalletd_tests,
LightwalletdTestType::UpdateCachedState,
},
};
/// 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("wallet_grpc_test".to_string());
if zebrad_state_path.is_none() {
return Ok(());
}
// Require to have a `LIGHTWALLETD_DATA_DIR` in place
let lightwalletd_state_path = test_type.lightwalletd_state_path("wallet_grpc_test".to_string());
if lightwalletd_state_path.is_none() {
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(), test_type)?;
// Launch lightwalletd
let (_lightwalletd, lightwalletd_rpc_port) = spawn_lightwalletd_with_rpc_server(
zebra_rpc_address,
lightwalletd_state_path,
test_type,
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::<Block>()
.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);
// Call `GetTaddressTxids` with a founders reward address that we know exists and have transactions in the first
// few blocks of the mainnet
let mut 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();
let mut counter = 0;
while let Some(_transaction) = transactions.message().await? {
counter += 1;
}
// For the provided address in the first 10 blocks there are 10 transactions in the mainnet
assert_eq!(10, counter);
// 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(())
}