//! `zebra-scanner` binary tests. use std::path::Path; #[cfg(not(target_os = "windows"))] use std::io::Write; use tempfile::TempDir; use zebra_test::{ args, command::{Arguments, TestDirExt}, prelude::*, }; #[cfg(not(target_os = "windows"))] use zebra_grpc::scanner::{scanner_client::ScannerClient, Empty}; mod scan_task_commands; /// The extended Sapling viewing key of [ZECpages](https://zecpages.com/boardinfo) #[cfg(not(target_os = "windows"))] const ZECPAGES_SAPLING_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz"; /// Test the scanner binary with the `--help` flag. #[test] fn scanner_help() -> eyre::Result<()> { let _init_guard = zebra_test::init(); let testdir = testdir()?; let child = testdir.spawn_scanner_child(args!["--help"])?; let output = child.wait_with_output()?; let output = output.assert_success()?; output.stdout_line_contains("USAGE:")?; Ok(()) } /// Test that the scanner binary starts the scan task. /// /// This test creates a new zebrad cache dir by using a `zebrad` binary specified in the `CARGO_BIN_EXE_zebrad` env variable. /// /// To run it locally, one way is: /// /// ``` /// cargo test scan_binary_starts -- --nocapture /// ``` #[tokio::test] #[cfg(not(target_os = "windows"))] async fn scan_binary_starts() -> Result<()> { use std::time::Duration; let _init_guard = zebra_test::init(); // Create a directory to dump test data into it. let test_dir = testdir()?; // Create a config for zebrad with a cache directory inside the test directory. let mut config = zebrad::config::ZebradConfig::default(); config.state.cache_dir = test_dir.path().join("zebra").to_path_buf(); let config_file = test_dir.path().join("zebrad.toml"); std::fs::File::create(config_file.clone())?.write_all(toml::to_string(&config)?.as_bytes())?; // Start the node just to make sure a cache is created. let mut zebrad = test_dir.spawn_zebrad_child(args!["-c", config_file.clone().to_str().unwrap(), "start"])?; zebrad.expect_stdout_line_matches("Opened Zebra state cache at .*")?; // Kill the node now that we have a valid zebra node cache. zebrad.kill(false)?; let output = zebrad.wait_with_output()?; // Make sure the command was killed output.assert_was_killed()?; // Create a new directory for the scanner let test_dir = testdir()?; // Create arguments for the scanner let key = serde_json::json!({"key": ZECPAGES_SAPLING_VIEWING_KEY}).to_string(); let listen_addr = "127.0.0.1:8231"; let rpc_listen_addr = "127.0.0.1:8232"; let zebrad_cache_dir = config.state.cache_dir.clone(); let scanning_cache_dir = test_dir.path().join("scanner").to_path_buf(); let args = args![ "--zebrad-cache-dir", zebrad_cache_dir.to_str().unwrap(), "--scanning-cache-dir", scanning_cache_dir.to_str().unwrap(), "--sapling-keys-to-scan", key, "--listen-addr", listen_addr, "--zebra-rpc-listen-addr", rpc_listen_addr ]; // Start the scaner using another test directory. let mut zebra_scanner = test_dir.spawn_scanner_child(args)?; // Check scanner was started. zebra_scanner.expect_stdout_line_matches("loaded Zebra scanner cache")?; // Wait for the scanner's gRPC server to start up tokio::time::sleep(Duration::from_secs(1)).await; // Check if the grpc server is up let mut client = ScannerClient::connect(format!("http://{listen_addr}")).await?; let request = tonic::Request::new(Empty {}); client.get_info(request).await?; // Look for 2 scanner notices indicating we are below sapling activation. zebra_scanner.expect_stdout_line_matches("scanner is waiting for Sapling activation. Current tip: [0-9]{1,4}, Sapling activation: 419200")?; zebra_scanner.expect_stdout_line_matches("scanner is waiting for Sapling activation. Current tip: [0-9]{1,4}, Sapling activation: 419200")?; // Kill the scanner. zebra_scanner.kill(true)?; // Check that scan task started and the scanning is done. let output = zebra_scanner.wait_with_output()?; // Make sure the command was killed output.assert_was_killed()?; output.assert_failure()?; Ok(()) } /// Test that the scanner can continue scanning where it was left when zebrad restarts. /// /// Needs a cache state close to the tip. A possible way to run it locally is: /// /// export ZEBRA_CACHED_STATE_DIR="/path/to/zebra/state" /// cargo test scan_start_where_left -- --ignored --nocapture /// /// The test will run zebrad with a key to scan, scan the first few blocks after sapling and then stops. /// Then it will restart zebrad and check that it resumes scanning where it was left. #[ignore] #[tokio::test] #[cfg(not(target_os = "windows"))] async fn scan_start_where_left() -> Result<()> { use ZECPAGES_SAPLING_VIEWING_KEY; let _init_guard = zebra_test::init(); let Ok(zebrad_cachedir) = std::env::var("ZEBRA_CACHED_STATE_DIR") else { tracing::info!("skipping scan_start_where_left test due to missing cached state, \ please set a ZEBRA_CACHED_STATE_DIR env var with a populated and valid path to run this test"); return Ok(()); }; // 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. let network = zebra_chain::parameters::Network::Mainnet; tracing::info!("Zcash network: {network}"); let scanning_cache_dir = testdir()?.path().join("scanner").to_path_buf(); // Create arguments for the scanner let key = serde_json::json!({"key": ZECPAGES_SAPLING_VIEWING_KEY}).to_string(); let listen_addr = "127.0.0.1:18231"; let rpc_listen_addr = "127.0.0.1:18232"; let args = args![ "--zebrad-cache-dir", zebrad_cachedir, "--scanning-cache-dir", scanning_cache_dir.to_str().unwrap(), "--sapling-keys-to-scan", key, "--listen-addr", listen_addr, "--zebra-rpc-listen-addr", rpc_listen_addr ]; // Start the scaner using another test directory. let mut zebra_scanner: TestChild = testdir()?.spawn_scanner_child(args.clone())?; // Check scanner was started. zebra_scanner.expect_stdout_line_matches("loaded Zebra scanner cache")?; // The first time zebra_scanner.expect_stdout_line_matches( r"Scanning the blockchain for key 0, started at block 419200, now at block 420000", )?; // Make sure scanner scans a few blocks. zebra_scanner.expect_stdout_line_matches( r"Scanning the blockchain for key 0, started at block 419200, now at block 430000", )?; zebra_scanner.expect_stdout_line_matches( r"Scanning the blockchain for key 0, started at block 419200, now at block 440000", )?; // Kill the scanner. zebra_scanner.kill(true)?; // Check that scan task started and the first scanning is done. let output = zebra_scanner.wait_with_output()?; // Make sure the command was killed output.assert_was_killed()?; output.assert_failure()?; // Start the node again with the same arguments. let mut zebra_scanner: TestChild = testdir()?.spawn_scanner_child(args)?; // Resuming message. zebra_scanner.expect_stdout_line_matches( "Last scanned height for key number 0 is 439000, resuming at 439001", )?; zebra_scanner.expect_stdout_line_matches("loaded Zebra scanner cache")?; // Start scanning where it was left. zebra_scanner.expect_stdout_line_matches( r"Scanning the blockchain for key 0, started at block 439001, now at block 440000", )?; zebra_scanner.expect_stdout_line_matches( r"Scanning the blockchain for key 0, started at block 439001, now at block 450000", )?; // Kill the scanner. zebra_scanner.kill(true)?; // Check that scan task started and the second scanning is done. let output = zebra_scanner.wait_with_output()?; // Make sure the command was killed output.assert_was_killed()?; output.assert_failure()?; Ok(()) } /// Tests successful: /// - Registration of a new key, /// - Subscription to scan results of new key, and /// - Deletion of keys /// in the scan task /// See [`common::shielded_scan::scan_task_commands`] for more information. /// /// Example of how to run the scan_task_commands test locally: /// /// ```console /// RUST_LOG=info ZEBRA_CACHED_STATE_DIR=/path/to/zebra/state cargo test scan_task_commands -- --include-ignored --nocapture /// ``` #[tokio::test] #[ignore] async fn scan_task_commands() -> Result<()> { scan_task_commands::run().await } /// Create a temporary directory for testing `zebra-scanner`. pub fn testdir() -> eyre::Result { tempfile::Builder::new() .prefix("zebra_scanner_tests") .tempdir() .map_err(Into::into) } /// Extension trait for methods on `tempfile::TempDir` for using it as a test /// directory for `zebra-scanner`. pub trait ScannerTestDirExt where Self: AsRef + Sized, { /// Spawn `zebra-scanner` with `args` as a child process in this test directory. fn spawn_scanner_child(self, args: Arguments) -> Result>; /// Spawn `zebrad` with `args` as a child process in this test directory. fn spawn_zebrad_child(self, args: Arguments) -> Result>; } impl ScannerTestDirExt for T where Self: TestDirExt + AsRef + Sized, { fn spawn_scanner_child(self, extra_args: Arguments) -> Result> { let mut args = Arguments::new(); args.merge_with(extra_args); self.spawn_child_with_command(env!("CARGO_BIN_EXE_zebra-scanner"), args) } fn spawn_zebrad_child(self, extra_args: Arguments) -> Result> { let mut args = Arguments::new(); args.merge_with(extra_args); self.spawn_child_with_command(env!("CARGO_BIN_EXE_zebrad-for-scanner"), args) } }