use std::{
env,
net::SocketAddr,
path::{Path, PathBuf},
};
use tempfile::TempDir;
use zebra_chain::parameters::Network::{self, *};
use zebra_test::{
args,
command::{Arguments, TestDirExt},
net::random_known_port,
prelude::*,
};
use super::{config::testdir, launch::ZebradTestDirExt, test_type::TestType};
#[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;
pub const TEST_LIGHTWALLETD: &str = "TEST_LIGHTWALLETD";
pub const LWD_CACHE_DIR: &str = "LWD_CACHE_DIR";
#[allow(clippy::print_stderr)]
pub fn zebra_skip_lightwalletd_tests() -> bool {
if env::var_os(TEST_LIGHTWALLETD).is_none() {
eprintln!(
"Skipped lightwalletd integration test, \
set the 'TEST_LIGHTWALLETD' environmental variable to run the test",
);
return true;
}
false
}
#[tracing::instrument]
pub fn spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
network: Network,
test_name: S,
test_type: TestType,
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();
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, test_type, arguments)?
.with_timeout(test_type.lightwalletd_timeout())
.with_failure_regex_iter(lightwalletd_failure_messages, lightwalletd_ignore_messages);
lightwalletd.expect_stdout_line_matches(regex::escape("Starting gRPC server"))?;
Ok(Some((lightwalletd, lightwalletd_rpc_port)))
}
#[tracing::instrument]
pub fn can_spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
test_name: S,
test_type: TestType,
) -> bool {
if zebra_test::net::zebra_skip_network_tests() {
return false;
}
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
}
pub trait LightWalletdTestDirExt: ZebradTestDirExt
where
Self: AsRef<Path> + Sized,
{
fn spawn_lightwalletd_child(
self,
lightwalletd_state_path: impl Into<Option<PathBuf>>,
test_type: TestType,
extra_args: Arguments,
) -> Result<TestChild<Self>>;
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self>;
}
impl<T> LightWalletdTestDirExt for T
where
Self: TestDirExt + AsRef<Path> + Sized,
{
#[allow(clippy::unwrap_in_result)]
fn spawn_lightwalletd_child(
self,
lightwalletd_state_path: impl Into<Option<PathBuf>>,
test_type: TestType,
extra_args: Arguments,
) -> Result<TestChild<Self>> {
let test_dir = self.as_ref().to_owned();
let default_config_path = test_dir.join("lightwalletd-zcash.conf");
assert!(
default_config_path.exists(),
"lightwalletd requires a config"
);
let mut args = Arguments::new();
let zcash_conf_path = default_config_path
.as_path()
.to_str()
.expect("Path is valid Unicode");
args.set_parameter("--zcash-conf-path", zcash_conf_path);
if let Some(lightwalletd_state_path) = lightwalletd_state_path.into() {
tracing::info!(?lightwalletd_state_path, "using lightwalletd state path");
if !matches!(test_type, TestType::FullSyncFromGenesis { .. }) {
let lwd_cache_dir_path = lightwalletd_state_path.join("db/main");
let lwd_cache_entries: Vec<_> = std::fs::read_dir(&lwd_cache_dir_path)
.unwrap_or_else(|error| {
if error.kind() == std::io::ErrorKind::NotFound {
panic!(
"missing cached lightwalletd state at {path:?}.\n\
Populate the directory (for example by running the lwd-sync-full \n\
nextest profile) or set {env_var} to a populated cache.",
path = lwd_cache_dir_path,
env_var = LWD_CACHE_DIR,
);
}
panic!(
"unexpected failure opening lightwalletd cache dir {path:?}: {error:?}",
path = lwd_cache_dir_path,
error = error,
);
})
.collect();
let lwd_cache_dir_size = lwd_cache_entries.iter().fold(0, |acc, entry_result| {
acc + entry_result
.as_ref()
.map(|entry| entry.metadata().map(|meta| meta.len()).unwrap_or(0))
.unwrap_or(0)
});
tracing::info!("{lwd_cache_dir_size} bytes in lightwalletd cache dir");
for entry_result in &lwd_cache_entries {
match entry_result {
Ok(entry) => tracing::info!("{entry:?} entry in lightwalletd cache dir"),
Err(e) => {
tracing::warn!(?e, "error reading entry in lightwalletd cache dir")
}
}
}
}
args.set_parameter(
"--data-dir",
lightwalletd_state_path
.to_str()
.expect("path is valid Unicode"),
);
} else {
tracing::info!("using lightwalletd empty state path");
let empty_state_path = test_dir.join("lightwalletd_state");
std::fs::create_dir(&empty_state_path)
.expect("unexpected failure creating lightwalletd state sub-directory");
args.set_parameter(
"--data-dir",
empty_state_path.to_str().expect("path is valid Unicode"),
);
}
args.set_parameter("--log-file", "/dev/stdout");
args.set_parameter("--grpc-bind-addr", "127.0.0.1:0");
args.set_parameter("--http-bind-addr", "127.0.0.1:0");
args.set_argument("--no-tls-very-insecure");
args.merge_with(extra_args);
self.spawn_child_with_command("lightwalletd", args)
}
fn with_lightwalletd_config(self, zebra_rpc_listener: SocketAddr) -> Result<Self> {
use std::fs;
let lightwalletd_config = format!(
"\
rpcbind={}\n\
rpcport={}\n\
rpcuser=xxxxx
rpcpassword=xxxxx
",
zebra_rpc_listener.ip(),
zebra_rpc_listener.port(),
);
let dir = self.as_ref();
fs::create_dir_all(dir)?;
let config_file = dir.join("lightwalletd-zcash.conf");
fs::write(config_file, lightwalletd_config.as_bytes())?;
Ok(self)
}
}