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:
Alfredo Garcia 2022-10-06 01:12:27 -03:00 committed by GitHub
parent 8a5708c217
commit 1937b6cdc0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 632 additions and 329 deletions

View File

@ -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,21 +1492,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(())
@ -1511,71 +1520,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() {
// 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?
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}\)")?;
@ -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)
});
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,
)?;
// `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");
(zebrad, Some(lightwalletd))
}
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))
#[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")

View File

@ -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;
config.network.initial_mainnet_peers = IndexSet::new();
config.network.initial_testnet_peers = IndexSet::new();
// 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<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:

View File

@ -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",

View File

@ -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,

View File

@ -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)
})
}

View File

@ -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> {

View File

@ -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,
"running gRPC query tests using lightwalletd & zebrad...",
);
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!(
?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,

View File

@ -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.
///