zebra/zebrad/tests/common/shielded_scan/scan_task_commands.rs

162 lines
5.5 KiB
Rust

//! Test registering keys, subscribing to their results, and deleting keys in the scan task while zebrad is running.
//!
//! This test requires a cached chain state that is partially synchronized past the
//! Sapling activation height and [`REQUIRED_MIN_TIP_HEIGHT`]
//!
//! export ZEBRA_CACHED_STATE_DIR="/path/to/zebra/state"
//! cargo test scan_task_commands --features="shielded-scan" -- --ignored --nocapture
use std::{fs, time::Duration};
use color_eyre::{eyre::eyre, Result};
use tokio::sync::mpsc::error::TryRecvError;
use tower::ServiceBuilder;
use zebra_chain::{
block::Height,
chain_tip::ChainTip,
parameters::{Network, NetworkUpgrade},
};
use zebra_scan::{
service::ScanTask,
storage::{db::SCANNER_DATABASE_KIND, Storage},
tests::ZECPAGES_SAPLING_VIEWING_KEY,
};
use crate::common::{
cached_state::start_state_service_with_cache_dir, launch::can_spawn_zebrad_for_test_type,
test_type::TestType,
};
/// The minimum required tip height for the cached state in this test.
const REQUIRED_MIN_TIP_HEIGHT: Height = Height(1_000_000);
/// How long this test waits for a result before failing.
/// Should be long enough for ScanTask to start and scan ~500 blocks
const WAIT_FOR_RESULTS_DURATION: Duration = Duration::from_secs(60);
/// A block height where a scan result can be found with the [`ZECPAGES_SAPLING_VIEWING_KEY`]
const EXPECTED_RESULT_HEIGHT: Height = Height(780_532);
/// Initialize Zebra's state service with a cached state, then:
/// - Start the scan task,
/// - Add a new key,
/// - Subscribe to results for that key,
/// - Check that the scanner sends an expected result,
/// - Remove the key and,
/// - Check that the results channel is disconnected
pub(crate) async fn run() -> Result<()> {
let _init_guard = zebra_test::init();
let test_type = TestType::UpdateZebraCachedStateNoRpc;
let test_name = "scan_task_commands";
let network = Network::Mainnet;
// Skip the test unless the user specifically asked for it and there is a zebrad_state_path
if !can_spawn_zebrad_for_test_type(test_name, test_type, true) {
return Ok(());
}
tracing::info!(
?network,
?test_type,
"running scan_subscribe_results test using zebra state service",
);
let zebrad_state_path = test_type
.zebrad_state_path(test_name)
.expect("already checked that there is a cached state path");
let mut scan_config = zebra_scan::Config::default();
scan_config.db_config_mut().cache_dir = zebrad_state_path.clone();
// Logs the network as zebrad would as part of the metadata when starting up.
// This is currently needed for the 'Check startup logs' step in CI to pass.
tracing::info!("Zcash network: {network}");
// Remove the scan directory before starting.
let scan_db_path = zebrad_state_path.join(SCANNER_DATABASE_KIND);
fs::remove_dir_all(std::path::Path::new(&scan_db_path)).ok();
let (state_service, _read_state_service, latest_chain_tip, chain_tip_change) =
start_state_service_with_cache_dir(network, zebrad_state_path.clone()).await?;
let chain_tip_height = latest_chain_tip
.best_tip_height()
.ok_or_else(|| eyre!("State directory doesn't have a chain tip block"))?;
let sapling_activation_height = NetworkUpgrade::Sapling
.activation_height(network)
.expect("there should be an activation height for Mainnet");
assert!(
sapling_activation_height < REQUIRED_MIN_TIP_HEIGHT,
"minimum tip height should be above sapling activation height"
);
assert!(
REQUIRED_MIN_TIP_HEIGHT < chain_tip_height,
"chain tip height must be above required minimum tip height"
);
tracing::info!("opened state service with valid chain tip height, starting scan task",);
let state = ServiceBuilder::new().buffer(10).service(state_service);
// Create an ephemeral `Storage` instance
let storage = Storage::new(&scan_config, network, false);
let mut scan_task = ScanTask::spawn(storage, state, chain_tip_change);
tracing::info!("started scan task, sending register/subscribe keys messages with zecpages key to start scanning for a new key",);
let keys = [ZECPAGES_SAPLING_VIEWING_KEY.to_string()];
scan_task.register_keys(
keys.iter()
.cloned()
.map(|key| (key, Some(EXPECTED_RESULT_HEIGHT.0)))
.collect(),
)?;
let mut result_receiver = scan_task
.subscribe(keys.iter().cloned().collect())
.await
.expect("should send and receive message successfully");
// Wait for the scanner to send a result in the channel
let result = tokio::time::timeout(WAIT_FOR_RESULTS_DURATION, result_receiver.recv()).await?;
tracing::info!(?result, "received a result from the channel");
let result = result.expect("there should be some scan result");
assert_eq!(
EXPECTED_RESULT_HEIGHT, result.height,
"result height should match expected height for hard-coded key"
);
scan_task.remove_keys(keys.to_vec())?;
// Wait for scan task to drop results sender
tokio::time::sleep(WAIT_FOR_RESULTS_DURATION).await;
loop {
match result_receiver.try_recv() {
// Empty any messages in the buffer
Ok(_) => continue,
Err(recv_error) => {
assert_eq!(
recv_error,
TryRecvError::Disconnected,
"any result senders should have been dropped"
);
break;
}
}
}
Ok(())
}