diff --git a/zebrad/tests/acceptance.rs b/zebrad/tests/acceptance.rs index 45c6965f696..03e435ef244 100644 --- a/zebrad/tests/acceptance.rs +++ b/zebrad/tests/acceptance.rs @@ -141,19 +141,16 @@ use common::{ config::{ config_file_full_path, configs_dir, default_test_config, persistent_test_config, testdir, }, - launch::{ - spawn_zebrad_for_rpc_without_initial_peers, ZebradTestDirExt, BETWEEN_NODES_DELAY, - LAUNCH_DELAY, - }, + launch::{spawn_zebrad_for_rpc, ZebradTestDirExt, BETWEEN_NODES_DELAY, LAUNCH_DELAY}, lightwalletd::{ - random_known_rpc_port_config, zebra_skip_lightwalletd_tests, LightWalletdTestDirExt, + can_spawn_lightwalletd_for_rpc, random_known_rpc_port_config, spawn_lightwalletd_for_rpc, LightwalletdTestType::{self, *}, }, sync::{ create_cached_database_height, sync_until, MempoolBehavior, LARGE_CHECKPOINT_TEST_HEIGHT, - LARGE_CHECKPOINT_TIMEOUT, LIGHTWALLETD_SYNC_FINISHED_REGEX, MEDIUM_CHECKPOINT_TEST_HEIGHT, - STOP_AT_HEIGHT_REGEX, STOP_ON_LOAD_TIMEOUT, SYNC_FINISHED_REGEX, - TINY_CHECKPOINT_TEST_HEIGHT, TINY_CHECKPOINT_TIMEOUT, + LARGE_CHECKPOINT_TIMEOUT, MEDIUM_CHECKPOINT_TEST_HEIGHT, STOP_AT_HEIGHT_REGEX, + STOP_ON_LOAD_TIMEOUT, SYNC_FINISHED_REGEX, TINY_CHECKPOINT_TEST_HEIGHT, + TINY_CHECKPOINT_TIMEOUT, }, }; @@ -1438,25 +1435,31 @@ fn zebrad_update_sync() -> Result<()> { /// Make sure `lightwalletd` can sync from Zebra, in update sync mode. /// -/// This test only runs when the `ZEBRA_TEST_LIGHTWALLETD`, -/// `ZEBRA_CACHED_STATE_DIR`, and `LIGHTWALLETD_DATA_DIR` env vars are set. +/// This test only runs when: +/// - the `ZEBRA_TEST_LIGHTWALLETD`, `ZEBRA_CACHED_STATE_DIR`, and +/// `LIGHTWALLETD_DATA_DIR` env vars are set, and +/// - Zebra is compiled with `--features=lightwalletd-grpc-tests`. /// /// This test doesn't work on Windows, so it is always skipped on that platform. #[test] #[cfg(not(target_os = "windows"))] +#[cfg(feature = "lightwalletd-grpc-tests")] fn lightwalletd_update_sync() -> Result<()> { lightwalletd_integration_test(UpdateCachedState) } /// Make sure `lightwalletd` can fully sync from genesis using Zebra. /// -/// This test only runs when the `ZEBRA_TEST_LIGHTWALLETD` and -/// `ZEBRA_CACHED_STATE_DIR` env vars are set. +/// This test only runs when: +/// - the `ZEBRA_TEST_LIGHTWALLETD` and `ZEBRA_CACHED_STATE_DIR` env vars are set, and +/// - Zebra is compiled with `--features=lightwalletd-grpc-tests`. +/// /// /// This test doesn't work on Windows, so it is always skipped on that platform. #[test] #[ignore] #[cfg(not(target_os = "windows"))] +#[cfg(feature = "lightwalletd-grpc-tests")] fn lightwalletd_full_sync() -> Result<()> { lightwalletd_integration_test(FullSyncFromGenesis { allow_lightwalletd_cached_state: false, @@ -1474,7 +1477,7 @@ fn lightwalletd_full_sync() -> Result<()> { /// - run a send transaction gRPC test, /// - run read-only gRPC tests. /// -/// The gRPC tests only run when the `lightwalletd-grpc-tests` is on. +/// The lightwalletd full, update, and gRPC tests only run with `--features=lightwalletd-grpc-tests`. /// /// These tests don't work on Windows, so they are always skipped on that platform. #[tokio::test] @@ -1486,21 +1489,27 @@ async fn lightwalletd_test_suite() -> Result<()> { // Only runs when ZEBRA_CACHED_STATE_DIR is set. lightwalletd_integration_test(UpdateZebraCachedStateNoRpc)?; - // Only runs when ZEBRA_CACHED_STATE_DIR is set. - // When manually running the test suite, allow cached state in the full sync test. - lightwalletd_integration_test(FullSyncFromGenesis { - allow_lightwalletd_cached_state: true, - })?; - - // Only runs when LIGHTWALLETD_DATA_DIR and ZEBRA_CACHED_STATE_DIR are set - lightwalletd_integration_test(UpdateCachedState)?; - - // Only runs when LIGHTWALLETD_DATA_DIR and ZEBRA_CACHED_STATE_DIR are set, - // and the compile-time gRPC feature is on. + // These tests need the compile-time gRPC feature #[cfg(feature = "lightwalletd-grpc-tests")] { - common::lightwalletd::send_transaction_test::run().await?; + // Do the quick tests first + + // Only runs when LIGHTWALLETD_DATA_DIR and ZEBRA_CACHED_STATE_DIR are set + lightwalletd_integration_test(UpdateCachedState)?; + + // Only runs when LIGHTWALLETD_DATA_DIR and ZEBRA_CACHED_STATE_DIR are set common::lightwalletd::wallet_grpc_test::run().await?; + + // Then do the slow tests + + // Only runs when ZEBRA_CACHED_STATE_DIR is set. + // When manually running the test suite, allow cached state in the full sync test. + lightwalletd_integration_test(FullSyncFromGenesis { + allow_lightwalletd_cached_state: true, + })?; + + // Only runs when LIGHTWALLETD_DATA_DIR and ZEBRA_CACHED_STATE_DIR are set + common::lightwalletd::send_transaction_test::run().await?; } Ok(()) @@ -1508,71 +1517,49 @@ async fn lightwalletd_test_suite() -> Result<()> { /// Run a lightwalletd integration test with a configuration for `test_type`. /// +/// Tests that sync `lightwalletd` to the chain tip require the `lightwalletd-grpc-tests` feature`: +/// - [`FullSyncFromGenesis`] +/// - [`UpdateCachedState`] +/// /// Set `FullSyncFromGenesis { allow_lightwalletd_cached_state: true }` to speed up manual full sync tests. /// +/// # Relibility +/// /// The random ports in this test can cause [rare port conflicts.](#Note on port conflict) +/// +/// # Panics +/// +/// If the `test_type` requires `--features=lightwalletd-grpc-tests`, +/// but Zebra was not compiled with that feature. #[tracing::instrument] fn lightwalletd_integration_test(test_type: LightwalletdTestType) -> Result<()> { let _init_guard = zebra_test::init(); - if zebra_test::net::zebra_skip_network_tests() { - return Ok(()); - } + // We run these sync tests with a network connection, for better test coverage. + let use_internet_connection = true; + let network = Mainnet; + let test_name = "lightwalletd_integration_test"; - // Skip the test unless the user specifically asked for it - // - // TODO: pass test_type to zebra_skip_lightwalletd_tests() and check for lightwalletd launch in there - if test_type.launches_lightwalletd() && zebra_skip_lightwalletd_tests() { + if test_type.launches_lightwalletd() && !can_spawn_lightwalletd_for_rpc(test_name, test_type) { + tracing::info!("skipping test due to missing lightwalletd network or cached state"); return Ok(()); } - // TODO: split the zebrad and lightwalletd launches and checks into separate functions? - - // Get the zebrad config - - // Handle the Zebra state directory based on the test type: - // - LaunchWithEmptyState: ignore the state directory - // - FullSyncFromGenesis, UpdateCachedState, UpdateZebraCachedStateNoRpc: - // skip the test if it is not available, timeout if it is not populated - - // Write a configuration that has RPC listen_addr set (if needed). - // If the state path env var is set, use it in the config. - let config = if let Some(config) = - test_type.zebrad_config("lightwalletd_integration_test".to_string()) + // Launch zebra with peers and using a predefined zebrad state path. + let (mut zebrad, zebra_rpc_address) = if let Some(zebrad_and_address) = + spawn_zebrad_for_rpc(network, test_name, test_type, use_internet_connection)? { - config? + tracing::info!( + ?test_type, + "running lightwalletd & zebrad integration test, launching zebrad...", + ); + + zebrad_and_address } else { + // Skip the test, we don't have the required cached state return Ok(()); }; - // Handle the lightwalletd state directory based on the test type: - // - LaunchWithEmptyState, UpdateZebraCachedStateNoRpc: ignore the state directory - // - FullSyncFromGenesis: use it if available, timeout if it is already populated - // - UpdateCachedState: skip the test if it is not available, timeout if it is not populated - let lightwalletd_state_path = - test_type.lightwalletd_state_path("lightwalletd_integration_test".to_string()); - - if test_type.needs_lightwalletd_cached_state() && lightwalletd_state_path.is_none() { - return Ok(()); - } - - tracing::info!( - ?test_type, - ?config, - ?lightwalletd_state_path, - "running lightwalletd & zebrad integration test", - ); - - // Get the lists of process failure logs - let (zebrad_failure_messages, zebrad_ignore_messages) = test_type.zebrad_failure_messages(); - - // Launch zebrad - let zdir = testdir()?.with_exact_config(&config)?; - let mut zebrad = zdir - .spawn_child(args!["start"])? - .with_timeout(test_type.zebrad_timeout()) - .with_failure_regex_iter(zebrad_failure_messages, zebrad_ignore_messages); - if test_type.needs_zebra_cached_state() { zebrad .expect_stdout_line_matches(r"loaded Zebra state cache .*tip.*=.*Height\([0-9]{7}\)")?; @@ -1582,32 +1569,35 @@ fn lightwalletd_integration_test(test_type: LightwalletdTestType) -> Result<()> } // Launch lightwalletd, if needed - let lightwalletd = if test_type.launches_lightwalletd() { - // Wait until `zebrad` has opened the RPC endpoint - zebrad.expect_stdout_line_matches(regex::escape( - format!("Opened RPC endpoint at {}", config.rpc.listen_addr.unwrap()).as_str(), + let lightwalletd_and_port = if test_type.launches_lightwalletd() { + tracing::info!( + ?test_type, + ?zebra_rpc_address, + "waiting for zebrad to open its RPC port..." + ); + zebrad.expect_stdout_line_matches(&format!( + "Opened RPC endpoint at {}", + zebra_rpc_address.expect("lightwalletd test must have RPC port") ))?; - // Write a fake zcashd configuration that has the rpcbind and rpcport options set - let ldir = testdir()?; - let ldir = ldir.with_lightwalletd_config(config.rpc.listen_addr.unwrap())?; - - let (lightwalletd_failure_messages, lightwalletd_ignore_messages) = - test_type.lightwalletd_failure_messages(); - - // Launch the lightwalletd process - let lightwalletd = if test_type == LaunchWithEmptyState { - ldir.spawn_lightwalletd_child(None, args![])? - } else { - ldir.spawn_lightwalletd_child(lightwalletd_state_path, args![])? - }; - - let mut lightwalletd = lightwalletd - .with_timeout(test_type.lightwalletd_timeout()) - .with_failure_regex_iter(lightwalletd_failure_messages, lightwalletd_ignore_messages); + tracing::info!( + ?zebra_rpc_address, + "launching lightwalletd connected to zebrad", + ); - // Wait until `lightwalletd` has launched - lightwalletd.expect_stdout_line_matches(regex::escape("Starting gRPC server"))?; + // Launch lightwalletd + let (mut lightwalletd, lightwalletd_rpc_port) = spawn_lightwalletd_for_rpc( + network, + test_name, + test_type, + zebra_rpc_address.expect("lightwalletd test must have RPC port"), + )? + .expect("already checked for lightwalletd cached state and network"); + + tracing::info!( + ?lightwalletd_rpc_port, + "spawned lightwalletd connected to zebrad", + ); // Check that `lightwalletd` is calling the expected Zebra RPCs @@ -1659,49 +1649,43 @@ fn lightwalletd_integration_test(test_type: LightwalletdTestType) -> Result<()> } } - Some(lightwalletd) + Some((lightwalletd, lightwalletd_rpc_port)) } else { None }; let (mut zebrad, lightwalletd) = if test_type.needs_zebra_cached_state() { - if let Some(mut lightwalletd) = lightwalletd { - // Wait for lightwalletd to sync to Zebra's tip. - // - // "Adding block" and "Waiting for block" logs stop when `lightwalletd` reaches the tip. - // But if the logs just stop, we can't tell the difference between a hang and fully synced. - // So we assume `lightwalletd` will sync and log large groups of blocks, - // and check for logs with heights near the mainnet tip height. - let lightwalletd_thread = std::thread::spawn(move || -> Result<_> { - tracing::info!(?test_type, "waiting for lightwalletd to sync to the tip"); - - lightwalletd.expect_stdout_line_matches(LIGHTWALLETD_SYNC_FINISHED_REGEX)?; - - Ok(lightwalletd) - }); - - // `lightwalletd` syncs can take a long time, - // so we need to check that `zebrad` has synced to the tip in parallel. - let lightwalletd_thread_and_zebrad = std::thread::spawn(move || -> Result<_> { - tracing::info!(?test_type, "waiting for zebrad to sync to the tip"); - - while !lightwalletd_thread.is_finished() { - zebrad.expect_stdout_line_matches(SYNC_FINISHED_REGEX)?; - } - - Ok((lightwalletd_thread, zebrad)) - }); - - // Retrieve the child process handles from the threads - let (lightwalletd_thread, zebrad) = lightwalletd_thread_and_zebrad - .join() - .unwrap_or_else(|panic_object| panic::resume_unwind(panic_object))?; - - let lightwalletd = lightwalletd_thread - .join() - .unwrap_or_else(|panic_object| panic::resume_unwind(panic_object))?; - - (zebrad, Some(lightwalletd)) + if let Some((lightwalletd, lightwalletd_rpc_port)) = lightwalletd_and_port { + #[cfg(feature = "lightwalletd-grpc-tests")] + { + use common::lightwalletd::sync::wait_for_zebrad_and_lightwalletd_sync; + + tracing::info!( + ?lightwalletd_rpc_port, + "waiting for zebrad and lightwalletd to sync...", + ); + + let (lightwalletd, zebrad) = wait_for_zebrad_and_lightwalletd_sync( + lightwalletd, + lightwalletd_rpc_port, + zebrad, + zebra_rpc_address.expect("lightwalletd test must have RPC port"), + test_type, + // We want to wait for the mempool and network for better coverage + true, + use_internet_connection, + )?; + + (zebrad, Some(lightwalletd)) + } + + #[cfg(not(feature = "lightwalletd-grpc-tests"))] + panic!( + "the {test_type:?} test requires `cargo test --feature lightwalletd-grpc-tests`\n\ + zebrad: {zebrad:?}\n\ + lightwalletd: {lightwalletd:?}\n\ + lightwalletd_rpc_port: {lightwalletd_rpc_port:?}" + ); } else { // We're just syncing Zebra, so there's no lightwalletd to check tracing::info!(?test_type, "waiting for zebrad to sync to the tip"); @@ -1710,6 +1694,8 @@ fn lightwalletd_integration_test(test_type: LightwalletdTestType) -> Result<()> (zebrad, None) } } else { + let lightwalletd = lightwalletd_and_port.map(|(lightwalletd, _port)| lightwalletd); + // We don't have a cached state, so we don't do any tip checks for Zebra or lightwalletd (zebrad, lightwalletd) }; @@ -1990,34 +1976,36 @@ async fn fully_synced_rpc_test() -> Result<()> { let _init_guard = zebra_test::init(); // We're only using cached Zebra state here, so this test type is the most similar - let test_type = LightwalletdTestType::FullSyncFromGenesis { - allow_lightwalletd_cached_state: false, - }; + let test_type = LightwalletdTestType::UpdateCachedState; + let network = Network::Mainnet; - // Handle the Zebra state directory - let cached_state_path = test_type.zebrad_state_path("fully_synced_rpc_test".to_string()); + let (mut zebrad, zebra_rpc_address) = if let Some(zebrad_and_address) = + spawn_zebrad_for_rpc(network, "fully_synced_rpc_test", test_type, false)? + { + tracing::info!("running fully synced zebrad RPC test"); - if cached_state_path.is_none() { + zebrad_and_address + } else { + // Skip the test, we don't have the required cached state return Ok(()); }; - tracing::info!("running fully synced zebrad RPC test"); - - let network = Network::Mainnet; - - let (_zebrad, zebra_rpc_address) = spawn_zebrad_for_rpc_without_initial_peers( - network, - cached_state_path.unwrap(), - test_type, - true, - )?; + zebrad.expect_stdout_line_matches(&format!( + "Opened RPC endpoint at {}", + zebra_rpc_address.expect("lightwalletd test must have RPC port"), + ))?; // Make a getblock test that works only on synced node (high block number). // The block is before the mandatory checkpoint, so the checkpoint cached state can be used // if desired. let client = reqwest::Client::new(); let res = client - .post(format!("http://{}", &zebra_rpc_address.to_string())) + .post(format!( + "http://{}", + &zebra_rpc_address + .expect("lightwalletd test must have RPC port") + .to_string() + )) // Manually constructed request to avoid encoding it, for simplicity .body(r#"{"jsonrpc": "2.0", "method": "getblock", "params": ["1180900", 0], "id":123 }"#) .header("Content-Type", "application/json") diff --git a/zebrad/tests/common/launch.rs b/zebrad/tests/common/launch.rs index dcbe2e4df19..3bf9dcd14d5 100644 --- a/zebrad/tests/common/launch.rs +++ b/zebrad/tests/common/launch.rs @@ -14,6 +14,7 @@ use std::{ use color_eyre::eyre::Result; use indexmap::IndexSet; +use tempfile::TempDir; use zebra_chain::parameters::Network; use zebra_test::{ @@ -24,7 +25,8 @@ use zebra_test::{ use zebrad::config::ZebradConfig; use crate::common::{ - lightwalletd::{random_known_rpc_port_config, LightwalletdTestType}, + config::testdir, + lightwalletd::{zebra_skip_lightwalletd_tests, LightwalletdTestType}, sync::FINISH_PARTIAL_SYNC_TIMEOUT, }; @@ -195,43 +197,79 @@ where } } -/// Spawns a zebrad instance to interact with lightwalletd, but without an internet connection. +/// Spawns a zebrad instance on `network` to test lightwalletd with `test_type`. /// -/// This prevents it from downloading blocks. Instead, the `zebra_directory` parameter allows -/// providing an initial state to the zebrad instance. +/// If `use_internet_connection` is `false` then spawn, but without any peers. +/// This prevents it from downloading blocks. Instead, use the `ZEBRA_CACHED_STATE_DIR` +/// environmental variable to provide an initial state to the zebrad instance. +/// +/// Returns: +/// - `Ok(Some(zebrad, zebra_rpc_address))` on success, +/// - `Ok(None)` if the test doesn't have the required network or cached state, and +/// - `Err(_)` if spawning zebrad fails. +/// +/// `zebra_rpc_address` is `None` if the test type doesn't need an RPC port. #[tracing::instrument] -pub fn spawn_zebrad_for_rpc_without_initial_peers( +pub fn spawn_zebrad_for_rpc + std::fmt::Debug>( network: Network, - zebra_directory: P, + test_name: S, test_type: LightwalletdTestType, - debug_skip_parameter_preload: bool, -) -> Result<(TestChild

, SocketAddr)> { - // This is what we recommend our users configure. - let mut config = random_known_rpc_port_config(true) - .expect("Failed to create a config file with a known RPC listener port"); - - config.state.ephemeral = false; - config.network.initial_mainnet_peers = IndexSet::new(); - config.network.initial_testnet_peers = IndexSet::new(); + use_internet_connection: bool, +) -> Result, Option)>> { + let test_name = test_name.as_ref(); + + // Skip the test unless the user specifically asked for it + if !can_spawn_zebrad_for_rpc(test_name, test_type) { + return Ok(None); + } + + // Get the zebrad config + let mut config = test_type + .zebrad_config(test_name) + .expect("already checked config")?; + + // TODO: move this into zebrad_config() config.network.network = network; - config.mempool.debug_enable_at_height = Some(0); - config.consensus.debug_skip_parameter_preload = debug_skip_parameter_preload; + if !use_internet_connection { + config.network.initial_mainnet_peers = IndexSet::new(); + config.network.initial_testnet_peers = IndexSet::new(); + + config.mempool.debug_enable_at_height = Some(0); + } let (zebrad_failure_messages, zebrad_ignore_messages) = test_type.zebrad_failure_messages(); - let mut zebrad = zebra_directory - .with_config(&mut config)? + // Writes a configuration that has RPC listen_addr set (if needed). + // If the state path env var is set, uses it in the config. + let zebrad = testdir()? + .with_exact_config(&config)? .spawn_child(args!["start"])? .bypass_test_capture(true) .with_timeout(test_type.zebrad_timeout()) .with_failure_regex_iter(zebrad_failure_messages, zebrad_ignore_messages); - let rpc_address = config.rpc.listen_addr.unwrap(); + Ok(Some((zebrad, config.rpc.listen_addr))) +} - zebrad.expect_stdout_line_matches("activating mempool")?; - zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {}", rpc_address))?; +/// Returns `true` if a zebrad test for `test_type` has everything it needs to run. +#[tracing::instrument] +pub fn can_spawn_zebrad_for_rpc + std::fmt::Debug>( + test_name: S, + test_type: LightwalletdTestType, +) -> bool { + if zebra_test::net::zebra_skip_network_tests() { + return false; + } + + // Skip the test unless the user specifically asked for it + // + // TODO: pass test_type to zebra_skip_lightwalletd_tests() and check for lightwalletd launch in there + if test_type.launches_lightwalletd() && zebra_skip_lightwalletd_tests() { + return false; + } - Ok((zebrad, rpc_address)) + // Check if we have any necessary cached states for the zebrad config + test_type.zebrad_config(test_name).is_some() } /// Panics if `$pred` is false, with an error report containing: diff --git a/zebrad/tests/common/lightwalletd.rs b/zebrad/tests/common/lightwalletd.rs index 1b71d8d681d..d1301aa9742 100644 --- a/zebrad/tests/common/lightwalletd.rs +++ b/zebrad/tests/common/lightwalletd.rs @@ -12,7 +12,11 @@ use std::{ time::Duration, }; +use tempfile::TempDir; + +use zebra_chain::parameters::Network::{self, *}; use zebra_test::{ + args, command::{Arguments, TestChild, TestDirExt, NO_MATCHES_REGEX_ITER}, net::random_known_port, prelude::*, @@ -21,7 +25,7 @@ use zebrad::config::ZebradConfig; use super::{ cached_state::ZEBRA_CACHED_STATE_DIR, - config::default_test_config, + config::{default_test_config, testdir}, failure_messages::{ LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES, LIGHTWALLETD_FAILURE_MESSAGES, PROCESS_FAILURE_MESSAGES, ZEBRA_FAILURE_MESSAGES, @@ -37,6 +41,8 @@ use LightwalletdTestType::*; #[cfg(feature = "lightwalletd-grpc-tests")] pub mod send_transaction_test; #[cfg(feature = "lightwalletd-grpc-tests")] +pub mod sync; +#[cfg(feature = "lightwalletd-grpc-tests")] pub mod wallet_grpc; #[cfg(feature = "lightwalletd-grpc-tests")] pub mod wallet_grpc_test; @@ -106,6 +112,80 @@ pub fn random_known_rpc_port_config(parallel_cpu_threads: bool) -> Result + std::fmt::Debug>( + network: Network, + test_name: S, + test_type: LightwalletdTestType, + zebrad_rpc_address: SocketAddr, +) -> Result, u16)>> { + assert_eq!(network, Mainnet, "this test only supports Mainnet for now"); + + let test_name = test_name.as_ref(); + + // Skip the test unless the user specifically asked for it + if !can_spawn_lightwalletd_for_rpc(test_name, test_type) { + return Ok(None); + } + + let lightwalletd_state_path = test_type.lightwalletd_state_path(test_name); + let lightwalletd_dir = testdir()?.with_lightwalletd_config(zebrad_rpc_address)?; + + let lightwalletd_rpc_port = random_known_port(); + let lightwalletd_rpc_address = format!("127.0.0.1:{lightwalletd_rpc_port}"); + + let arguments = args!["--grpc-bind-addr": lightwalletd_rpc_address]; + + let (lightwalletd_failure_messages, lightwalletd_ignore_messages) = + test_type.lightwalletd_failure_messages(); + + let mut lightwalletd = lightwalletd_dir + .spawn_lightwalletd_child(lightwalletd_state_path, arguments)? + .with_timeout(test_type.lightwalletd_timeout()) + .with_failure_regex_iter(lightwalletd_failure_messages, lightwalletd_ignore_messages); + + // Wait until `lightwalletd` has launched. + // This log happens very quickly, so it is ok to block for a short while here. + lightwalletd.expect_stdout_line_matches(regex::escape("Starting gRPC server"))?; + + Ok(Some((lightwalletd, lightwalletd_rpc_port))) +} + +/// Returns `true` if a lightwalletd test for `test_type` has everything it needs to run. +#[tracing::instrument] +pub fn can_spawn_lightwalletd_for_rpc + std::fmt::Debug>( + test_name: S, + test_type: LightwalletdTestType, +) -> bool { + if zebra_test::net::zebra_skip_network_tests() { + return false; + } + + // Skip the test unless the user specifically asked for it + // + // TODO: pass test_type to zebra_skip_lightwalletd_tests() and check for lightwalletd launch in there + if test_type.launches_lightwalletd() && zebra_skip_lightwalletd_tests() { + return false; + } + + let lightwalletd_state_path = test_type.lightwalletd_state_path(test_name); + if test_type.needs_lightwalletd_cached_state() && lightwalletd_state_path.is_none() { + return false; + } + + true +} + /// Extension trait for methods on `tempfile::TempDir` for using it as a test /// directory for `zebrad`. pub trait LightWalletdTestDirExt: ZebradTestDirExt @@ -234,6 +314,9 @@ pub enum LightwalletdTestType { /// Do a full sync from an empty lightwalletd state. /// /// This test requires a cached Zebra state. + // + // Only used with `--features=lightwalletd-grpc-tests`. + #[allow(dead_code)] FullSyncFromGenesis { /// Should the test allow a cached lightwalletd state? /// @@ -258,6 +341,10 @@ pub enum LightwalletdTestType { impl LightwalletdTestType { /// Does this test need a Zebra cached state? pub fn needs_zebra_cached_state(&self) -> bool { + // Handle the Zebra state directory based on the test type: + // - LaunchWithEmptyState: ignore the state directory + // - FullSyncFromGenesis, UpdateCachedState, UpdateZebraCachedStateNoRpc: + // skip the test if it is not available match self { LaunchWithEmptyState => false, FullSyncFromGenesis { .. } | UpdateCachedState | UpdateZebraCachedStateNoRpc => true, @@ -274,6 +361,10 @@ impl LightwalletdTestType { /// Does this test need a `lightwalletd` cached state? pub fn needs_lightwalletd_cached_state(&self) -> bool { + // Handle the lightwalletd state directory based on the test type: + // - LaunchWithEmptyState, UpdateZebraCachedStateNoRpc: ignore the state directory + // - FullSyncFromGenesis: use it if available, timeout if it is already populated + // - UpdateCachedState: skip the test if it is not available match self { LaunchWithEmptyState | FullSyncFromGenesis { .. } | UpdateZebraCachedStateNoRpc => { false @@ -293,12 +384,22 @@ impl LightwalletdTestType { } } + /// Can this test create a new `LIGHTWALLETD_DATA_DIR` cached state? + pub fn can_create_lightwalletd_cached_state(&self) -> bool { + match self { + LaunchWithEmptyState => false, + FullSyncFromGenesis { .. } | UpdateCachedState => true, + UpdateZebraCachedStateNoRpc => false, + } + } + /// Returns the Zebra state path for this test, if set. #[allow(clippy::print_stderr)] - pub fn zebrad_state_path(&self, test_name: String) -> Option { + pub fn zebrad_state_path>(&self, test_name: S) -> Option { match env::var_os(ZEBRA_CACHED_STATE_DIR) { Some(path) => Some(path.into()), None => { + let test_name = test_name.as_ref(); eprintln!( "skipped {test_name:?} {self:?} lightwalletd test, \ set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test", @@ -313,7 +414,7 @@ impl LightwalletdTestType { /// /// Returns `None` if the test should be skipped, /// and `Some(Err(_))` if the config could not be created. - pub fn zebrad_config(&self, test_name: String) -> Option> { + pub fn zebrad_config>(&self, test_name: S) -> Option> { let config = if self.launches_lightwalletd() { // This is what we recommend our users configure. random_known_rpc_port_config(true) @@ -330,6 +431,12 @@ impl LightwalletdTestType { // except when we're doing the quick empty state test config.consensus.debug_skip_parameter_preload = !self.needs_zebra_cached_state(); + // We want to run multi-threaded RPCs, if we're using them + if self.launches_lightwalletd() { + // Automatically runs one thread per available CPU core + config.rpc.parallel_cpu_threads = 0; + } + if !self.needs_zebra_cached_state() { return Some(Ok(config)); } @@ -346,8 +453,14 @@ impl LightwalletdTestType { } /// Returns the `lightwalletd` state path for this test, if set, and if allowed for this test. - pub fn lightwalletd_state_path(&self, test_name: String) -> Option { - if !self.launches_lightwalletd() { + pub fn lightwalletd_state_path>(&self, test_name: S) -> Option { + let test_name = test_name.as_ref(); + + // Can this test type use a lwd cached state, or create/update one? + let use_or_create_lwd_cache = + self.allow_lightwalletd_cached_state() || self.can_create_lightwalletd_cached_state(); + + if !self.launches_lightwalletd() || !use_or_create_lwd_cache { tracing::info!( "running {test_name:?} {self:?} lightwalletd test, \ ignoring any cached state in the {LIGHTWALLETD_DATA_DIR:?} environment variable", diff --git a/zebrad/tests/common/lightwalletd/send_transaction_test.rs b/zebrad/tests/common/lightwalletd/send_transaction_test.rs index 655cfd9b5bf..18165a20139 100644 --- a/zebrad/tests/common/lightwalletd/send_transaction_test.rs +++ b/zebrad/tests/common/lightwalletd/send_transaction_test.rs @@ -26,7 +26,7 @@ use tower::{Service, ServiceExt}; use zebra_chain::{ block, chain_tip::ChainTip, - parameters::Network, + parameters::Network::{self, *}, serialization::ZcashSerialize, transaction::{self, Transaction}, }; @@ -36,12 +36,11 @@ use zebrad::components::mempool::downloads::MAX_INBOUND_CONCURRENCY; use crate::common::{ cached_state::{load_tip_height_from_state_directory, start_state_service_with_cache_dir}, - launch::spawn_zebrad_for_rpc_without_initial_peers, + launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc}, lightwalletd::{ - wallet_grpc::{ - self, connect_to_lightwalletd, spawn_lightwalletd_with_rpc_server, Empty, Exclude, - }, - zebra_skip_lightwalletd_tests, + can_spawn_lightwalletd_for_rpc, spawn_lightwalletd_for_rpc, + sync::wait_for_zebrad_and_lightwalletd_sync, + wallet_grpc::{self, connect_to_lightwalletd, Empty, Exclude}, LightwalletdTestType::*, }, sync::copy_state_and_perform_full_sync, @@ -65,38 +64,32 @@ fn max_sent_transactions() -> usize { pub async fn run() -> Result<()> { let _init_guard = zebra_test::init(); - if zebra_test::net::zebra_skip_network_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; + let test_name = "send_transaction_test"; + let network = Mainnet; // Skip the test unless the user specifically asked for it - if zebra_skip_lightwalletd_tests() { + if !can_spawn_zebrad_for_rpc(test_name, test_type) { 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; + if test_type.launches_lightwalletd() && !can_spawn_lightwalletd_for_rpc(test_name, test_type) { + tracing::info!("skipping test due to missing lightwalletd network or cached state"); + return Ok(()); + } - let zebrad_state_path = test_type.zebrad_state_path("send_transaction_tests".to_string()); + let zebrad_state_path = test_type.zebrad_state_path(test_name); let zebrad_state_path = match zebrad_state_path { Some(zebrad_state_path) => zebrad_state_path, None => return Ok(()), }; - let lightwalletd_state_path = - test_type.lightwalletd_state_path("send_transaction_tests".to_string()); - if lightwalletd_state_path.is_none() { - return Ok(()); - } - - let network = Network::Mainnet; - tracing::info!( ?network, ?test_type, ?zebrad_state_path, - ?lightwalletd_state_path, "running gRPC send transaction test using lightwalletd & zebrad", ); @@ -106,39 +99,56 @@ pub async fn run() -> Result<()> { tracing::info!( transaction_count = ?transactions.len(), partial_sync_path = ?zebrad_state_path, - "got transactions to send", + "got transactions to send, spawning isolated zebrad...", ); - // TODO: change debug_skip_parameter_preload to true if we do the mempool test in the wallet gRPC test - let (mut zebrad, zebra_rpc_address) = spawn_zebrad_for_rpc_without_initial_peers( - Network::Mainnet, - zebrad_state_path, - test_type, - false, - )?; + // We run these gRPC tests without a network connection. + let use_internet_connection = false; + + // Start zebrad with no peers, we want to send transactions without blocks coming in. If `wallet_grpc_test` + // runs before this test (as it does in `lightwalletd_test_suite`), then we are the most up to date with tip we can. + let (mut zebrad, zebra_rpc_address) = if let Some(zebrad_and_address) = + spawn_zebrad_for_rpc(network, test_name, test_type, use_internet_connection)? + { + zebrad_and_address + } else { + // Skip the test, we don't have the required cached state + return Ok(()); + }; + + let zebra_rpc_address = zebra_rpc_address.expect("lightwalletd test must have RPC port"); tracing::info!( + ?test_type, ?zebra_rpc_address, - "spawned disconnected zebrad with shorter chain, waiting for mempool activation...", + "spawned isolated zebrad with shorter chain, waiting for zebrad to open its RPC port..." ); + zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {}", zebra_rpc_address))?; - let (_lightwalletd, lightwalletd_rpc_port) = spawn_lightwalletd_with_rpc_server( - zebra_rpc_address, - lightwalletd_state_path, - test_type, - true, - )?; + tracing::info!( + ?zebra_rpc_address, + "zebrad opened its RPC port, spawning lightwalletd...", + ); + + let (lightwalletd, lightwalletd_rpc_port) = + spawn_lightwalletd_for_rpc(network, test_name, test_type, zebra_rpc_address)? + .expect("already checked cached state and network requirements"); tracing::info!( ?lightwalletd_rpc_port, - "spawned lightwalletd connected to zebrad, waiting for zebrad mempool activation...", + "spawned lightwalletd connected to zebrad, waiting for them both to sync...", ); - zebrad.expect_stdout_line_matches("activating mempool")?; - - // TODO: check that lightwalletd is at the tip using gRPC (#4894) - // - // If this takes a long time, we might need to check zebrad logs for failures in a separate thread. + let (_lightwalletd, _zebrad) = wait_for_zebrad_and_lightwalletd_sync( + lightwalletd, + lightwalletd_rpc_port, + zebrad, + zebra_rpc_address, + test_type, + // We want to send transactions to the mempool, but we aren't syncing with the network + true, + use_internet_connection, + )?; tracing::info!( ?lightwalletd_rpc_port, diff --git a/zebrad/tests/common/lightwalletd/sync.rs b/zebrad/tests/common/lightwalletd/sync.rs new file mode 100644 index 00000000000..5808ed2be14 --- /dev/null +++ b/zebrad/tests/common/lightwalletd/sync.rs @@ -0,0 +1,204 @@ +//! Lightwalletd sync functions. + +use std::{ + net::SocketAddr, + sync::atomic::{AtomicBool, Ordering}, + time::{Duration, Instant}, +}; + +use tempfile::TempDir; + +use zebra_test::prelude::*; + +use crate::common::{ + launch::ZebradTestDirExt, + lightwalletd::{ + wallet_grpc::{connect_to_lightwalletd, ChainSpec}, + LightwalletdTestType, + }, +}; + +/// The amount of time we wait between each tip check. +pub const TIP_CHECK_RATE_LIMIT: Duration = Duration::from_secs(60); + +/// Wait for lightwalletd to sync to Zebra's tip. +/// +/// If `wait_for_zebrad_mempool` is `true`, wait for Zebra to activate its mempool. +/// If `wait_for_zebrad_tip` is `true`, also wait for Zebra to sync to the network chain tip. +#[tracing::instrument] +pub fn wait_for_zebrad_and_lightwalletd_sync< + P: ZebradTestDirExt + std::fmt::Debug + std::marker::Send + 'static, +>( + mut lightwalletd: TestChild, + lightwalletd_rpc_port: u16, + mut zebrad: TestChild

, + zebra_rpc_address: SocketAddr, + test_type: LightwalletdTestType, + wait_for_zebrad_mempool: bool, + wait_for_zebrad_tip: bool, +) -> Result<(TestChild, TestChild

)> { + let is_zebrad_finished = AtomicBool::new(false); + let is_lightwalletd_finished = AtomicBool::new(false); + + let is_zebrad_finished = &is_zebrad_finished; + let is_lightwalletd_finished = &is_lightwalletd_finished; + + // TODO: split these closures out into their own functions + + // Check Zebra's logs for errors. + // Optionally waits until Zebra has synced to the tip, based on `wait_for_zebrad_tip`. + // + // `lightwalletd` syncs can take a long time, + // so we need to check that `zebrad` has synced to the tip in parallel. + let zebrad_mut = &mut zebrad; + let zebrad_wait_fn = || -> Result<_> { + // When we are near the tip, the mempool should activate at least once + if wait_for_zebrad_mempool { + tracing::info!( + ?test_type, + "waiting for zebrad to activate the mempool when it gets near the tip..." + ); + zebrad_mut.expect_stdout_line_matches("activating mempool")?; + } + + // When we are near the tip, this message is logged multiple times + if wait_for_zebrad_tip { + tracing::info!(?test_type, "waiting for zebrad to sync to the tip..."); + zebrad_mut.expect_stdout_line_matches(crate::common::sync::SYNC_FINISHED_REGEX)?; + } + + // Tell the other thread that Zebra has finished + is_zebrad_finished.store(true, Ordering::SeqCst); + + tracing::info!( + ?test_type, + "zebrad is waiting for lightwalletd to sync to the tip..." + ); + while !is_lightwalletd_finished.load(Ordering::SeqCst) { + // Just keep checking the Zebra logs for errors... + if wait_for_zebrad_tip { + // Make sure the sync is still finished, this is logged every minute or so. + zebrad_mut.expect_stdout_line_matches(crate::common::sync::SYNC_FINISHED_REGEX)?; + } else { + // Use sync progress logs, which are logged every minute or so. + zebrad_mut.expect_stdout_line_matches(crate::common::sync::SYNC_PROGRESS_REGEX)?; + } + } + + Ok(zebrad_mut) + }; + + // Wait until `lightwalletd` has synced to Zebra's tip. + // Calls `lightwalletd`'s gRPCs and Zebra's JSON-RPCs. + // Also checks `lightwalletd`'s logs for errors. + // + // `zebrad` syncs can take a long time, + // so we need to check that `lightwalletd` has synced to the tip in parallel. + let lightwalletd_mut = &mut lightwalletd; + let lightwalletd_wait_fn = || -> Result<_> { + tracing::info!( + ?test_type, + "lightwalletd is waiting for zebrad to sync to the tip..." + ); + while !is_zebrad_finished.load(Ordering::SeqCst) { + // Just keep checking the `lightwalletd` logs for errors. + // It usually logs something every 30-90 seconds, + // but there's no specific message we need to wait for here. + assert!( + lightwalletd_mut.wait_for_stdout_line(None), + "lightwalletd output unexpectedly finished early", + ); + } + + tracing::info!(?test_type, "waiting for lightwalletd to sync to the tip..."); + while !are_zebrad_and_lightwalletd_tips_synced(lightwalletd_rpc_port, zebra_rpc_address)? { + let previous_check = Instant::now(); + + // To improve performance, only check the tips occasionally + while previous_check.elapsed() < TIP_CHECK_RATE_LIMIT { + assert!( + lightwalletd_mut.wait_for_stdout_line(None), + "lightwalletd output unexpectedly finished early", + ); + } + } + + // Tell the other thread that `lightwalletd` has finished + is_lightwalletd_finished.store(true, Ordering::SeqCst); + + Ok(lightwalletd_mut) + }; + + // Run both threads in parallel, automatically propagating any panics to this thread. + std::thread::scope(|s| { + // Launch the sync-waiting threads + let zebrad_thread = s.spawn(|| { + zebrad_wait_fn().expect("test failed while waiting for zebrad to sync"); + }); + + let lightwalletd_thread = s.spawn(|| { + lightwalletd_wait_fn().expect("test failed while waiting for lightwalletd to sync."); + }); + + // Mark the sync-waiting threads as finished if they fail or panic. + // This tells the other thread that it can exit. + // + // TODO: use `panic::catch_unwind()` instead, + // when `&mut zebra_test::command::TestChild` is unwind-safe + s.spawn(|| { + let zebrad_result = zebrad_thread.join(); + is_zebrad_finished.store(true, Ordering::SeqCst); + + zebrad_result.expect("test panicked or failed while waiting for zebrad to sync"); + }); + s.spawn(|| { + let lightwalletd_result = lightwalletd_thread.join(); + is_lightwalletd_finished.store(true, Ordering::SeqCst); + + lightwalletd_result + .expect("test panicked or failed while waiting for lightwalletd to sync"); + }); + }); + + Ok((lightwalletd, zebrad)) +} + +/// Returns `Ok(true)` if zebrad and lightwalletd are both at the same height. +#[tracing::instrument] +pub fn are_zebrad_and_lightwalletd_tips_synced( + lightwalletd_rpc_port: u16, + zebra_rpc_address: SocketAddr, +) -> Result { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + + rt.block_on(async { + let mut lightwalletd_grpc_client = connect_to_lightwalletd(lightwalletd_rpc_port).await?; + + // Get the block tip from lightwalletd + let lightwalletd_tip_block = lightwalletd_grpc_client + .get_latest_block(ChainSpec {}) + .await? + .into_inner(); + let lightwalletd_tip_height = lightwalletd_tip_block.height; + + // Get the block tip from zebrad + let zebrad_json_rpc_client = reqwest::Client::new(); + let zebrad_blockchain_info = zebrad_json_rpc_client + .post(format!("http://{}", &zebra_rpc_address.to_string())) + .body(r#"{"jsonrpc": "2.0", "method": "getblockchaininfo", "params": [], "id":123 }"#) + .header("Content-Type", "application/json") + .send() + .await? + .text() + .await?; + let zebrad_blockchain_info: serde_json::Value = + serde_json::from_str(&zebrad_blockchain_info)?; + let zebrad_tip_height = zebrad_blockchain_info["result"]["blocks"] + .as_u64() + .expect("unexpected block height: doesn't fit in u64"); + + Ok(lightwalletd_tip_height == zebrad_tip_height) + }) +} diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc.rs b/zebrad/tests/common/lightwalletd/wallet_grpc.rs index edfed7c5b3e..fc80fd6f65f 100644 --- a/zebrad/tests/common/lightwalletd/wallet_grpc.rs +++ b/zebrad/tests/common/lightwalletd/wallet_grpc.rs @@ -1,16 +1,8 @@ //! Lightwalletd gRPC interface and utility functions. -use std::{env, net::SocketAddr, path::PathBuf}; +use std::env; -use tempfile::TempDir; - -use zebra_test::{args, net::random_known_port, prelude::*}; - -use crate::common::{ - config::testdir, lightwalletd::LightWalletdTestDirExt, sync::LIGHTWALLETD_SYNC_FINISHED_REGEX, -}; - -use super::LightwalletdTestType; +use zebra_test::prelude::*; tonic::include_proto!("cash.z.wallet.sdk.rpc"); @@ -18,44 +10,6 @@ tonic::include_proto!("cash.z.wallet.sdk.rpc"); pub type LightwalletdRpcClient = compact_tx_streamer_client::CompactTxStreamerClient; -/// Start a lightwalletd instance connected to `zebrad_rpc_address`, -/// using the `lightwalletd_state_path`, with its gRPC server functionality enabled. -/// -/// Expects cached state based on the `test_type`. -/// Waits for `lightwalletd` to sync to near the tip, if `wait_for_sync` is true. -/// -/// Returns the lightwalletd instance and the port number that it is listening for RPC connections. -#[tracing::instrument] -pub fn spawn_lightwalletd_with_rpc_server( - zebrad_rpc_address: SocketAddr, - lightwalletd_state_path: Option, - test_type: LightwalletdTestType, - wait_for_sync: bool, -) -> Result<(TestChild, u16)> { - let lightwalletd_dir = testdir()?.with_lightwalletd_config(zebrad_rpc_address)?; - - let lightwalletd_rpc_port = random_known_port(); - let lightwalletd_rpc_address = format!("127.0.0.1:{lightwalletd_rpc_port}"); - - let arguments = args!["--grpc-bind-addr": lightwalletd_rpc_address]; - - let (lightwalletd_failure_messages, lightwalletd_ignore_messages) = - test_type.lightwalletd_failure_messages(); - - let mut lightwalletd = lightwalletd_dir - .spawn_lightwalletd_child(lightwalletd_state_path, arguments)? - .with_timeout(test_type.lightwalletd_timeout()) - .with_failure_regex_iter(lightwalletd_failure_messages, lightwalletd_ignore_messages); - - lightwalletd.expect_stdout_line_matches("Starting gRPC server")?; - - if wait_for_sync { - lightwalletd.expect_stdout_line_matches(LIGHTWALLETD_SYNC_FINISHED_REGEX)?; - } - - Ok((lightwalletd, lightwalletd_rpc_port)) -} - /// Connect to a lightwalletd RPC instance. #[tracing::instrument] pub async fn connect_to_lightwalletd(lightwalletd_rpc_port: u16) -> Result { diff --git a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs index 8b4bade02e4..23ec3d776ae 100644 --- a/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs +++ b/zebrad/tests/common/lightwalletd/wallet_grpc_test.rs @@ -46,14 +46,14 @@ use zebra_chain::{ use zebra_network::constants::USER_AGENT; use crate::common::{ - launch::spawn_zebrad_for_rpc_without_initial_peers, + launch::spawn_zebrad_for_rpc, lightwalletd::{ + can_spawn_lightwalletd_for_rpc, spawn_lightwalletd_for_rpc, + sync::wait_for_zebrad_and_lightwalletd_sync, wallet_grpc::{ - connect_to_lightwalletd, spawn_lightwalletd_with_rpc_server, Address, AddressList, - BlockId, BlockRange, ChainSpec, Empty, GetAddressUtxosArg, - TransparentAddressBlockFilter, TxFilter, + connect_to_lightwalletd, Address, AddressList, BlockId, BlockRange, ChainSpec, Empty, + GetAddressUtxosArg, TransparentAddressBlockFilter, TxFilter, }, - zebra_skip_lightwalletd_tests, LightwalletdTestType::UpdateCachedState, }, }; @@ -67,75 +67,73 @@ use crate::common::{ pub async fn run() -> Result<()> { let _init_guard = 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() { + // This test is only for the mainnet + let network = Network::Mainnet; + let test_name = "wallet_grpc_test"; + + // We run these gRPC tests with a network connection, for better test coverage. + let use_internet_connection = true; + + if test_type.launches_lightwalletd() && !can_spawn_lightwalletd_for_rpc(test_name, test_type) { + tracing::info!("skipping test due to missing lightwalletd network or cached state"); 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() { + // Launch zebra with peers and using a predefined zebrad state path. + // As this tests are just queries we can have a live chain where blocks are coming. + let (mut zebrad, zebra_rpc_address) = if let Some(zebrad_and_address) = + spawn_zebrad_for_rpc(network, test_name, test_type, use_internet_connection)? + { + tracing::info!( + ?network, + ?test_type, + "running gRPC query tests using lightwalletd & zebrad...", + ); + + zebrad_and_address + } else { + // Skip the test, we don't have the required cached state return Ok(()); - } + }; - // This test is only for the mainnet - let network = Network::Mainnet; + let zebra_rpc_address = zebra_rpc_address.expect("lightwalletd test must have RPC port"); tracing::info!( - ?network, ?test_type, - ?zebrad_state_path, - ?lightwalletd_state_path, - "running gRPC query tests using lightwalletd & zebrad, \ - launching disconnected zebrad...", + ?zebra_rpc_address, + "launched zebrad, waiting for zebrad to open its RPC port..." ); - - // Launch zebra using a predefined zebrad state path - // - // TODO: change debug_skip_parameter_preload to true if we do the mempool test in the send transaction test - let (mut zebrad, zebra_rpc_address) = spawn_zebrad_for_rpc_without_initial_peers( - network, - zebrad_state_path.unwrap(), - test_type, - false, - )?; + zebrad.expect_stdout_line_matches(&format!("Opened RPC endpoint at {}", zebra_rpc_address))?; tracing::info!( ?zebra_rpc_address, - "launching lightwalletd connected to zebrad, waiting for the mempool to activate...", + "zebrad opened its RPC port, spawning lightwalletd...", ); // Launch lightwalletd - let (_lightwalletd, lightwalletd_rpc_port) = spawn_lightwalletd_with_rpc_server( - zebra_rpc_address, - lightwalletd_state_path, - test_type, - false, - )?; + let (lightwalletd, lightwalletd_rpc_port) = + spawn_lightwalletd_for_rpc(network, test_name, test_type, zebra_rpc_address)? + .expect("already checked cached state and network requirements"); tracing::info!( ?lightwalletd_rpc_port, - "spawned lightwalletd connected to zebrad, waiting for zebrad mempool activation...", + "spawned lightwalletd connected to zebrad, waiting for them both to sync...", ); - zebrad.expect_stdout_line_matches("activating mempool")?; - - // Give lightwalletd a few seconds to sync to the tip before connecting to it - // - // TODO: check that lightwalletd is at the tip using gRPC (#4894) - // - // If this takes a long time, we might need to check zebrad logs for failures in a separate thread. - tokio::time::sleep(std::time::Duration::from_secs(60)).await; + let (_lightwalletd, _zebrad) = wait_for_zebrad_and_lightwalletd_sync( + lightwalletd, + lightwalletd_rpc_port, + zebrad, + zebra_rpc_address, + test_type, + // We want our queries to include the mempool and network for better coverage + true, + use_internet_connection, + )?; tracing::info!( ?lightwalletd_rpc_port, diff --git a/zebrad/tests/common/sync.rs b/zebrad/tests/common/sync.rs index 8bd3ca5d900..0886a3e5c9b 100644 --- a/zebrad/tests/common/sync.rs +++ b/zebrad/tests/common/sync.rs @@ -45,13 +45,11 @@ pub const STOP_AT_HEIGHT_REGEX: &str = "stopping at configured height"; pub const SYNC_FINISHED_REGEX: &str = r"finished initial sync to chain tip, using gossiped blocks .*sync_percent.*=.*100\."; -/// The text that should be logged when `lightwalletd`'s initial sync is near the chain tip. -/// -/// We can't guarantee a "Waiting for block" log, so we just check for a block near the tip height. -/// -/// TODO: update the regex to `1[8-9][0-9]{5}` when mainnet reaches block 1_800_000 -pub const LIGHTWALLETD_SYNC_FINISHED_REGEX: &str = - r"([Aa]dding block to cache 1[7-9][0-9]{5})|([Ww]aiting for block: 1[7-9][0-9]{5})"; +/// The text that should be logged every time Zebra checks the sync progress. +// +// This is only used with `--feature lightwalletd-grpc-tests` +#[allow(dead_code)] +pub const SYNC_PROGRESS_REGEX: &str = r"sync_percent"; /// The maximum amount of time Zebra should take to reload after shutting down. ///