fix(test): Wait for zebrad and lightwalletd to reach the tip in tests, to improve test coverage (#5164)
* Add RPC timing to zcash-rpc-diff * Use transaction hash index for verbose block requests, rather than block data * check if we are at tip for lightwallet wallet tests * move function * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Combine the lightwalletd sync and gRPC test APIs * Rewrite the gRPC and full sync tests for the new APIs * Make zebra_rpc_address optional because only some tests need it * Check for the zebrad RPC port to open in the right place * Do the quick lightwalletd integration tests first in the sequential test function * Ignore the lightwalletd cached state env var in tests that don't want it * Don't replace the state path in RPC tests * Enable IO (and timers) on the tip check tokio runtime * Stop waiting for sync if either waiter thread errors or panics * Try to speed up slow lightwalletd full syncs * Don't wait for the tip in send transaction tests, and try to speed up full lightwalletd syncs * Remove redundant is_lightwalletd_finished store Co-authored-by: Arya <aryasolhi@gmail.com> * Fix unused variable error * Actually create the lightwalletd cached state * Fix lwd cache check logic Co-authored-by: teor <teor@riseup.net> Co-authored-by: Arya <aryasolhi@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
8a5708c217
commit
1937b6cdc0
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1441,25 +1438,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,
|
||||
|
@ -1477,7 +1480,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]
|
||||
|
@ -1489,6 +1492,19 @@ async fn lightwalletd_test_suite() -> Result<()> {
|
|||
// Only runs when ZEBRA_CACHED_STATE_DIR is set.
|
||||
lightwalletd_integration_test(UpdateZebraCachedStateNoRpc)?;
|
||||
|
||||
// These tests need the compile-time gRPC feature
|
||||
#[cfg(feature = "lightwalletd-grpc-tests")]
|
||||
{
|
||||
// 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 {
|
||||
|
@ -1496,14 +1512,7 @@ async fn lightwalletd_test_suite() -> Result<()> {
|
|||
})?;
|
||||
|
||||
// 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.
|
||||
#[cfg(feature = "lightwalletd-grpc-tests")]
|
||||
{
|
||||
common::lightwalletd::send_transaction_test::run().await?;
|
||||
common::lightwalletd::wallet_grpc_test::run().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1511,70 +1520,48 @@ 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() {
|
||||
// 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";
|
||||
|
||||
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(());
|
||||
}
|
||||
|
||||
// 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 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?
|
||||
} else {
|
||||
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",
|
||||
"running lightwalletd & zebrad integration test, launching zebrad...",
|
||||
);
|
||||
|
||||
// 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);
|
||||
zebrad_and_address
|
||||
} else {
|
||||
// Skip the test, we don't have the required cached state
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if test_type.needs_zebra_cached_state() {
|
||||
zebrad
|
||||
|
@ -1585,32 +1572,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())?;
|
||||
tracing::info!(
|
||||
?zebra_rpc_address,
|
||||
"launching lightwalletd connected to zebrad",
|
||||
);
|
||||
|
||||
let (lightwalletd_failure_messages, lightwalletd_ignore_messages) =
|
||||
test_type.lightwalletd_failure_messages();
|
||||
// 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");
|
||||
|
||||
// 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);
|
||||
|
||||
// Wait until `lightwalletd` has launched
|
||||
lightwalletd.expect_stdout_line_matches(regex::escape("Starting gRPC server"))?;
|
||||
tracing::info!(
|
||||
?lightwalletd_rpc_port,
|
||||
"spawned lightwalletd connected to zebrad",
|
||||
);
|
||||
|
||||
// Check that `lightwalletd` is calling the expected Zebra RPCs
|
||||
|
||||
|
@ -1662,49 +1652,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");
|
||||
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;
|
||||
|
||||
lightwalletd.expect_stdout_line_matches(LIGHTWALLETD_SYNC_FINISHED_REGEX)?;
|
||||
tracing::info!(
|
||||
?lightwalletd_rpc_port,
|
||||
"waiting for zebrad and lightwalletd to sync...",
|
||||
);
|
||||
|
||||
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))?;
|
||||
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");
|
||||
|
@ -1713,6 +1697,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)
|
||||
};
|
||||
|
@ -1993,34 +1979,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")
|
||||
|
|
|
@ -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<P: ZebradTestDirExt + std::fmt::Debug>(
|
||||
pub fn spawn_zebrad_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
network: Network,
|
||||
zebra_directory: P,
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
debug_skip_parameter_preload: bool,
|
||||
) -> Result<(TestChild<P>, 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");
|
||||
use_internet_connection: bool,
|
||||
) -> Result<Option<(TestChild<TempDir>, Option<SocketAddr>)>> {
|
||||
let test_name = test_name.as_ref();
|
||||
|
||||
config.state.ephemeral = false;
|
||||
// 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;
|
||||
if !use_internet_connection {
|
||||
config.network.initial_mainnet_peers = IndexSet::new();
|
||||
config.network.initial_testnet_peers = IndexSet::new();
|
||||
config.network.network = network;
|
||||
|
||||
config.mempool.debug_enable_at_height = Some(0);
|
||||
config.consensus.debug_skip_parameter_preload = debug_skip_parameter_preload;
|
||||
}
|
||||
|
||||
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<S: AsRef<str> + std::fmt::Debug>(
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
) -> bool {
|
||||
if zebra_test::net::zebra_skip_network_tests() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ok((zebrad, rpc_address))
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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:
|
||||
|
|
|
@ -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<Zebrad
|
|||
Ok(config)
|
||||
}
|
||||
|
||||
/// Spawns a lightwalletd instance on `network`, connected to `zebrad_rpc_address`,
|
||||
/// with its gRPC server functionality enabled.
|
||||
///
|
||||
/// Expects cached state based on the `test_type`. Use the `LIGHTWALLETD_DATA_DIR`
|
||||
/// environmental variable to provide an initial state to the lightwalletd instance.
|
||||
///
|
||||
/// Returns:
|
||||
/// - `Ok(Some(lightwalletd, lightwalletd_rpc_port))` on success,
|
||||
/// - `Ok(None)` if the test doesn't have the required network or cached state, and
|
||||
/// - `Err(_)` if spawning lightwalletd fails.
|
||||
#[tracing::instrument]
|
||||
pub fn spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
network: Network,
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
zebrad_rpc_address: SocketAddr,
|
||||
) -> Result<Option<(TestChild<TempDir>, 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<S: AsRef<str> + 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<PathBuf> {
|
||||
pub fn zebrad_state_path<S: AsRef<str>>(&self, test_name: S) -> Option<PathBuf> {
|
||||
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<Result<ZebradConfig>> {
|
||||
pub fn zebrad_config<S: AsRef<str>>(&self, test_name: S) -> Option<Result<ZebradConfig>> {
|
||||
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<PathBuf> {
|
||||
if !self.launches_lightwalletd() {
|
||||
pub fn lightwalletd_state_path<S: AsRef<str>>(&self, test_name: S) -> Option<PathBuf> {
|
||||
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",
|
||||
|
|
|
@ -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(());
|
||||
}
|
||||
|
||||
// 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;
|
||||
let test_name = "send_transaction_test";
|
||||
let network = Mainnet;
|
||||
|
||||
let zebrad_state_path = test_type.zebrad_state_path("send_transaction_tests".to_string());
|
||||
// Skip the test unless the user specifically asked for it
|
||||
if !can_spawn_zebrad_for_rpc(test_name, test_type) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
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(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 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))?;
|
||||
|
||||
tracing::info!(
|
||||
?zebra_rpc_address,
|
||||
"spawned disconnected zebrad with shorter chain, waiting for mempool activation...",
|
||||
"zebrad opened its RPC port, spawning lightwalletd...",
|
||||
);
|
||||
|
||||
let (_lightwalletd, lightwalletd_rpc_port) = spawn_lightwalletd_with_rpc_server(
|
||||
zebra_rpc_address,
|
||||
lightwalletd_state_path,
|
||||
test_type,
|
||||
true,
|
||||
)?;
|
||||
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,
|
||||
|
|
|
@ -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<TempDir>,
|
||||
lightwalletd_rpc_port: u16,
|
||||
mut zebrad: TestChild<P>,
|
||||
zebra_rpc_address: SocketAddr,
|
||||
test_type: LightwalletdTestType,
|
||||
wait_for_zebrad_mempool: bool,
|
||||
wait_for_zebrad_tip: bool,
|
||||
) -> Result<(TestChild<TempDir>, TestChild<P>)> {
|
||||
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<TempDir>` 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<bool> {
|
||||
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)
|
||||
})
|
||||
}
|
|
@ -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<tonic::transport::Channel>;
|
||||
|
||||
/// 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<PathBuf>,
|
||||
test_type: LightwalletdTestType,
|
||||
wait_for_sync: bool,
|
||||
) -> Result<(TestChild<TempDir>, 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<LightwalletdRpcClient> {
|
||||
|
|
|
@ -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() {
|
||||
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;
|
||||
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(());
|
||||
}
|
||||
|
||||
// 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,
|
||||
?zebrad_state_path,
|
||||
?lightwalletd_state_path,
|
||||
"running gRPC query tests using lightwalletd & zebrad, \
|
||||
launching disconnected zebrad...",
|
||||
"running gRPC query tests using lightwalletd & zebrad...",
|
||||
);
|
||||
|
||||
// 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_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,
|
||||
"launched zebrad, waiting for zebrad to open its RPC port..."
|
||||
);
|
||||
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,
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue