use std::{path::PathBuf, time::Duration};
use tempfile::TempDir;
use zebra_chain::{block::Height, parameters::Network};
use zebrad::{components::sync, config::ZebradConfig};
use zebra_test::{args, prelude::*};
use super::{
config::{persistent_test_config, testdir},
launch::ZebradTestDirExt,
};
pub const TINY_CHECKPOINT_TEST_HEIGHT: Height = Height(0);
pub const MEDIUM_CHECKPOINT_TEST_HEIGHT: Height =
Height(zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP as u32);
pub const LARGE_CHECKPOINT_TEST_HEIGHT: Height =
Height((zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP * 2) as u32);
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\.";
#[cfg(feature = "lightwalletd-grpc-tests")]
pub const SYNC_PROGRESS_REGEX: &str = r"sync_percent";
#[cfg(feature = "zebra-checkpoints")]
pub const CHECKPOINT_VERIFIER_REGEX: &str =
r"initializing block verifier router.*max_checkpoint_height.*=.*Height";
pub const STOP_ON_LOAD_TIMEOUT: Duration = Duration::from_secs(10);
pub const TINY_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(240);
pub const LARGE_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(240);
pub const FINISH_PARTIAL_SYNC_TIMEOUT: Duration = Duration::from_secs(11 * 60 * 60);
pub const FINISH_FULL_SYNC_TIMEOUT: Duration = Duration::from_secs(72 * 60 * 60);
pub const MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD: Height =
Height(3 * sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT as u32);
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MempoolBehavior {
ForceActivationAt(Height),
#[allow(dead_code)]
ShouldAutomaticallyActivate,
ShouldNotActivate,
}
impl MempoolBehavior {
pub fn enable_at_height(&self) -> Option<u32> {
match self {
MempoolBehavior::ForceActivationAt(height) => Some(height.0),
MempoolBehavior::ShouldAutomaticallyActivate | MempoolBehavior::ShouldNotActivate => {
None
}
}
}
pub fn require_activation(&self) -> bool {
self.require_forced_activation() || self.require_automatic_activation()
}
pub fn require_forced_activation(&self) -> bool {
matches!(self, MempoolBehavior::ForceActivationAt(_))
}
pub fn require_automatic_activation(&self) -> bool {
matches!(self, MempoolBehavior::ShouldAutomaticallyActivate)
}
#[allow(dead_code)]
pub fn require_no_activation(&self) -> bool {
matches!(self, MempoolBehavior::ShouldNotActivate)
}
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip(reuse_tempdir))]
pub fn sync_until(
height: Height,
network: &Network,
stop_regex: &str,
timeout: Duration,
reuse_tempdir: impl Into<Option<TempDir>>,
mempool_behavior: MempoolBehavior,
checkpoint_sync: bool,
check_legacy_chain: bool,
) -> Result<TempDir> {
let _init_guard = zebra_test::init();
if zebra_test::net::zebra_skip_network_tests() {
return testdir();
}
let reuse_tempdir = reuse_tempdir.into();
let mut config = persistent_test_config(network)?;
config.state.debug_stop_at_height = Some(height.0);
config.mempool.debug_enable_at_height = mempool_behavior.enable_at_height();
config.consensus.checkpoint_sync = checkpoint_sync;
if height > MIN_HEIGHT_FOR_DEFAULT_LOOKAHEAD {
config.sync.checkpoint_verify_concurrency_limit =
sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT;
}
let tempdir = if let Some(reuse_tempdir) = reuse_tempdir {
reuse_tempdir.replace_config(&mut config)?
} else {
testdir()?.with_config(&mut config)?
};
let child = tempdir.spawn_child(args!["start"])?.with_timeout(timeout);
let network_log = format!("network: {network},");
if mempool_behavior.require_activation() {
let mut child = check_sync_logs_until(
child,
network,
stop_regex,
mempool_behavior,
check_legacy_chain,
)?;
child.kill(true)?;
let dir = child.dir.take().expect("dir was not already taken");
child.wait_with_output()?;
std::thread::sleep(std::time::Duration::from_secs(3));
Ok(dir)
} else {
assert!(
height.0 < 2_000_000,
"zebrad must exit by itself, so we can collect all the output",
);
let output = child.wait_with_output()?;
output.stdout_line_contains(&network_log)?;
if check_legacy_chain {
output.stdout_line_contains("starting legacy chain check")?;
output.stdout_line_contains("no legacy chain found")?;
}
assert!(output.stdout_line_contains("activating mempool").is_err());
assert!(output
.stdout_line_contains("sending mempool transaction broadcast")
.is_err());
output.stdout_line_matches(stop_regex)?;
output.assert_was_not_killed()?;
let output = output.assert_success()?;
Ok(output.dir.expect("wait_with_output sets dir"))
}
}
#[tracing::instrument(skip(zebrad))]
pub fn check_sync_logs_until(
mut zebrad: TestChild<TempDir>,
network: &Network,
stop_regex: &str,
mempool_behavior: MempoolBehavior,
check_legacy_chain: bool,
) -> Result<TestChild<TempDir>> {
zebrad.expect_stdout_line_matches(format!("network: {network},"))?;
if check_legacy_chain {
zebrad.expect_stdout_line_matches("starting legacy chain check")?;
zebrad.expect_stdout_line_matches("no legacy chain found")?;
zebrad.expect_stdout_line_matches("starting state checkpoint validation")?;
zebrad.expect_stdout_line_matches("finished state checkpoint validation")?;
}
if mempool_behavior.require_forced_activation() {
zebrad.expect_stdout_line_matches("enabling mempool for debugging")?;
}
zebrad.expect_stdout_line_matches("activating mempool")?;
zebrad.expect_stdout_line_matches(stop_regex)?;
Ok(zebrad)
}
fn get_zebra_cached_state_dir() -> PathBuf {
ZebradConfig::load(None)
.map(|c| c.state.cache_dir)
.unwrap_or_else(|_| "/zebrad-cache".into())
}
pub fn cached_mandatory_checkpoint_test_config(network: &Network) -> Result<ZebradConfig> {
let mut config = persistent_test_config(network)?;
config.state.cache_dir = get_zebra_cached_state_dir();
config.sync.checkpoint_verify_concurrency_limit = sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT;
Ok(config)
}
#[allow(clippy::print_stderr)]
#[tracing::instrument]
pub fn create_cached_database_height(
network: &Network,
height: Height,
checkpoint_sync: bool,
stop_regex: &str,
) -> Result<()> {
eprintln!("creating cached database");
let mut config = cached_mandatory_checkpoint_test_config(network)?;
config.state.debug_stop_at_height = Some(height.0);
config.consensus.checkpoint_sync = checkpoint_sync;
let dir = get_zebra_cached_state_dir();
let mut child = dir
.with_exact_config(&config)?
.spawn_child(args!["start"])?
.with_timeout(FINISH_FULL_SYNC_TIMEOUT)
.bypass_test_capture(true);
let network = format!("network: {network},");
child.expect_stdout_line_matches(network)?;
child.expect_stdout_line_matches("starting legacy chain check")?;
child.expect_stdout_line_matches("no legacy chain found")?;
child.expect_stdout_line_matches(stop_regex)?;
child.kill(true)?;
Ok(())
}