fix(tests): add submitblock test to CI, and avoid copying the cached state directory in other tests (#5589)
* updates mod docs for tests that use future blocks * updates submitblock test to use TestType methods * prunes redundant code * adds check_sync_logs_until * adds assertion for needs cached state & rpc server * updates get_raw_future_blocks fn with rpc calls * updates to get_raw_future_blocks fn and submit_block test * Rename LightwalletdTestType to TestType * moves TestType and random_known_rpc_port_config to test_type.rs and config.rs * moves get_raw_future_blocks to cached_state.rs * updates ci workflows to include submit block test * adds get_future_blocks fn and uses it in load_transactions_from_future_blocks * updates CI docker * Apply suggestions from code review Co-authored-by: teor <teor@riseup.net> * Applies suggestions from code review * Updates misnamed closure param * updates mod docs for test_type.rs Co-authored-by: teor <teor@riseup.net>
This commit is contained in:
parent
be24a364da
commit
c447b03223
|
@ -90,6 +90,12 @@ jobs:
|
|||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
|
||||
submit-block-test:
|
||||
name: submit block / Run submit-block test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: 'echo "No build required"'
|
||||
|
||||
lightwalletd-full-sync:
|
||||
name: lightwalletd tip / Run lwd-full-sync test
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
@ -578,3 +578,28 @@ jobs:
|
|||
root_state_path: '/var/cache'
|
||||
zebra_state_dir: 'zebrad-cache'
|
||||
lwd_state_dir: 'lwd-cache'
|
||||
|
||||
# Test that Zebra can handle a submit block RPC call, using a cached Zebra tip state
|
||||
#
|
||||
# Runs:
|
||||
# - after every PR is merged to `main`
|
||||
# - on every PR update
|
||||
#
|
||||
# If the state version has changed, waits for the new cached states to be created.
|
||||
# Otherwise, if the state rebuild was skipped, runs immediately after the build job.
|
||||
submit-block-test:
|
||||
name: submit block
|
||||
needs: test-full-sync
|
||||
uses: ./.github/workflows/deploy-gcp-tests.yml
|
||||
if: ${{ !cancelled() && !failure() && github.event.inputs.regenerate-disks != 'true' && github.event.inputs.run-full-sync != 'true' && github.event.inputs.run-lwd-sync != 'true' && github.event.inputs.run-lwd-send-tx != 'true' }}
|
||||
with:
|
||||
app_name: zebrad
|
||||
test_id: submit-block
|
||||
test_description: Test submitting blocks via Zebra's rpc server
|
||||
test_variables: '-e TEST_SUBMIT_BLOCK=1 -e ZEBRA_FORCE_USE_COLOR=1 -e ZEBRA_CACHED_STATE_DIR=/var/cache/zebrad-cache'
|
||||
needs_zebra_state: true
|
||||
needs_lwd_state: false
|
||||
saves_to_disk: false
|
||||
disk_suffix: tip
|
||||
root_state_path: '/var/cache'
|
||||
zebra_state_dir: 'zebrad-cache'
|
||||
|
|
|
@ -78,6 +78,10 @@ case "$1" in
|
|||
ls -lh "$ZEBRA_CACHED_STATE_DIR"/*/* || (echo "No $ZEBRA_CACHED_STATE_DIR/*/*"; ls -lhR "$ZEBRA_CACHED_STATE_DIR" | head -50 || echo "No $ZEBRA_CACHED_STATE_DIR directory")
|
||||
ls -lhR "$LIGHTWALLETD_DATA_DIR/db" || (echo "No $LIGHTWALLETD_DATA_DIR/db"; ls -lhR "$LIGHTWALLETD_DATA_DIR" | head -50 || echo "No $LIGHTWALLETD_DATA_DIR directory")
|
||||
cargo test --locked --release --features lightwalletd-grpc-tests --package zebrad --test acceptance -- --nocapture --include-ignored sending_transactions_using_lightwalletd
|
||||
elif [[ "$TEST_SUBMIT_BLOCK" -eq "1" ]]; then
|
||||
# Starting with a cached Zebra tip, test sending a block to Zebra's RPC port.
|
||||
ls -lh "$ZEBRA_CACHED_STATE_DIR"/*/* || (echo "No $ZEBRA_CACHED_STATE_DIR/*/*"; ls -lhR "$ZEBRA_CACHED_STATE_DIR" | head -50 || echo "No $ZEBRA_CACHED_STATE_DIR directory")
|
||||
cargo test --locked --release --features getblocktemplate-rpcs --package zebrad --test acceptance -- --nocapture --include-ignored submit_block
|
||||
|
||||
# These command-lines are provided by the caller.
|
||||
#
|
||||
|
|
|
@ -146,20 +146,19 @@ mod common;
|
|||
|
||||
use common::{
|
||||
check::{is_zebrad_version, EphemeralCheck, EphemeralConfig},
|
||||
config::random_known_rpc_port_config,
|
||||
config::{
|
||||
config_file_full_path, configs_dir, default_test_config, persistent_test_config, testdir,
|
||||
},
|
||||
launch::{spawn_zebrad_for_rpc, ZebradTestDirExt, BETWEEN_NODES_DELAY, LAUNCH_DELAY},
|
||||
lightwalletd::{
|
||||
can_spawn_lightwalletd_for_rpc, random_known_rpc_port_config, spawn_lightwalletd_for_rpc,
|
||||
LightwalletdTestType::{self, *},
|
||||
},
|
||||
lightwalletd::{can_spawn_lightwalletd_for_rpc, spawn_lightwalletd_for_rpc},
|
||||
sync::{
|
||||
create_cached_database_height, sync_until, MempoolBehavior, LARGE_CHECKPOINT_TEST_HEIGHT,
|
||||
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,
|
||||
},
|
||||
test_type::TestType::{self, *},
|
||||
};
|
||||
|
||||
/// The maximum amount of time that we allow the creation of a future to block the `tokio` executor.
|
||||
|
@ -1585,7 +1584,7 @@ async fn lightwalletd_test_suite() -> Result<()> {
|
|||
/// 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<()> {
|
||||
fn lightwalletd_integration_test(test_type: TestType) -> Result<()> {
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
// We run these sync tests with a network connection, for better test coverage.
|
||||
|
@ -2029,7 +2028,7 @@ 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::UpdateCachedState;
|
||||
let test_type = TestType::UpdateCachedState;
|
||||
let network = Network::Mainnet;
|
||||
|
||||
let (mut zebrad, zebra_rpc_address) = if let Some(zebrad_and_address) =
|
||||
|
|
|
@ -7,11 +7,17 @@
|
|||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use reqwest::Client;
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use tempfile::TempDir;
|
||||
use tokio::fs;
|
||||
use tower::{util::BoxService, Service};
|
||||
|
||||
use zebra_chain::block::Block;
|
||||
use zebra_chain::serialization::ZcashDeserializeInto;
|
||||
use zebra_chain::{
|
||||
block::{self, Height},
|
||||
chain_tip::ChainTip,
|
||||
|
@ -21,6 +27,14 @@ use zebra_state::{ChainTipChange, LatestChainTip};
|
|||
|
||||
use crate::common::config::testdir;
|
||||
|
||||
use zebra_state::MAX_BLOCK_REORG_HEIGHT;
|
||||
|
||||
use crate::common::{
|
||||
launch::spawn_zebrad_for_rpc,
|
||||
sync::{check_sync_logs_until, MempoolBehavior, SYNC_FINISHED_REGEX},
|
||||
test_type::TestType,
|
||||
};
|
||||
|
||||
/// Path to a directory containing a cached Zebra state.
|
||||
pub const ZEBRA_CACHED_STATE_DIR: &str = "ZEBRA_CACHED_STATE_DIR";
|
||||
|
||||
|
@ -144,3 +158,152 @@ async fn copy_directory(
|
|||
|
||||
Ok(sub_directories)
|
||||
}
|
||||
|
||||
/// Accepts a network, test_type, test_name, and num_blocks (how many blocks past the finalized tip to try getting)
|
||||
///
|
||||
/// Syncs zebra until the tip, gets some blocks near the tip, via getblock rpc calls,
|
||||
/// shuts down zebra, and gets the finalized tip height of the updated cached state.
|
||||
///
|
||||
/// Returns retrieved and deserialized blocks that are above the finalized tip height of the cached state.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// If the provided `test_type` doesn't need an rpc server and cached state, or if `max_num_blocks` is 0
|
||||
pub async fn get_future_blocks(
|
||||
network: Network,
|
||||
test_type: TestType,
|
||||
test_name: &str,
|
||||
max_num_blocks: u32,
|
||||
) -> Result<Vec<Block>> {
|
||||
let blocks: Vec<Block> = get_raw_future_blocks(network, test_type, test_name, max_num_blocks)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(hex::decode)
|
||||
.map(|block_bytes| {
|
||||
block_bytes
|
||||
.expect("getblock rpc calls in get_raw_future_blocks should return valid hexdata")
|
||||
.zcash_deserialize_into()
|
||||
.expect("decoded hex data from getblock rpc calls should deserialize into blocks")
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(blocks)
|
||||
}
|
||||
|
||||
/// Accepts a network, test_type, test_name, and num_blocks (how many blocks past the finalized tip to try getting)
|
||||
///
|
||||
/// Syncs zebra until the tip, gets some blocks near the tip, via getblock rpc calls,
|
||||
/// shuts down zebra, and gets the finalized tip height of the updated cached state.
|
||||
///
|
||||
/// Returns hexdata of retrieved blocks that are above the finalized tip height of the cached state.
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// If the provided `test_type` doesn't need an rpc server and cached state, or if `max_num_blocks` is 0
|
||||
pub async fn get_raw_future_blocks(
|
||||
network: Network,
|
||||
test_type: TestType,
|
||||
test_name: &str,
|
||||
max_num_blocks: u32,
|
||||
) -> Result<Vec<String>> {
|
||||
assert!(max_num_blocks > 0);
|
||||
|
||||
let max_num_blocks = max_num_blocks.min(MAX_BLOCK_REORG_HEIGHT);
|
||||
let mut raw_blocks = Vec::with_capacity(max_num_blocks as usize);
|
||||
|
||||
assert!(
|
||||
test_type.needs_zebra_cached_state() && test_type.needs_zebra_rpc_server(),
|
||||
"get_raw_future_blocks needs zebra cached state and rpc server"
|
||||
);
|
||||
|
||||
let should_sync = true;
|
||||
let (zebrad, zebra_rpc_address) =
|
||||
spawn_zebrad_for_rpc(network, test_name, test_type, should_sync)?
|
||||
.ok_or_else(|| eyre!("get_raw_future_blocks requires a cached state"))?;
|
||||
let rpc_address = zebra_rpc_address.expect("test type must have RPC port");
|
||||
|
||||
let mut zebrad = check_sync_logs_until(
|
||||
zebrad,
|
||||
network,
|
||||
SYNC_FINISHED_REGEX,
|
||||
MempoolBehavior::ShouldAutomaticallyActivate,
|
||||
true,
|
||||
)?;
|
||||
|
||||
// Create an http client
|
||||
let client = Client::new();
|
||||
|
||||
let send_rpc_request = |method, params| {
|
||||
client
|
||||
.post(format!("http://{}", &rpc_address))
|
||||
.body(format!(
|
||||
r#"{{"jsonrpc": "2.0", "method": "{method}", "params": {params}, "id":123 }}"#
|
||||
))
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
};
|
||||
|
||||
let blockchain_info: serde_json::Value = serde_json::from_str(
|
||||
&send_rpc_request("getblockchaininfo", "[]".to_string())
|
||||
.await?
|
||||
.text()
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
let tip_height: u32 = blockchain_info["result"]["blocks"]
|
||||
.as_u64()
|
||||
.expect("unexpected block height: doesn't fit in u64")
|
||||
.try_into()
|
||||
.expect("unexpected block height: doesn't fit in u32");
|
||||
|
||||
let estimated_finalized_tip_height = tip_height - MAX_BLOCK_REORG_HEIGHT;
|
||||
|
||||
tracing::info!(
|
||||
?tip_height,
|
||||
?estimated_finalized_tip_height,
|
||||
"got tip height from blockchaininfo",
|
||||
);
|
||||
|
||||
for block_height in (0..max_num_blocks).map(|idx| idx + estimated_finalized_tip_height) {
|
||||
let raw_block: serde_json::Value = serde_json::from_str(
|
||||
&send_rpc_request("getblock", format!(r#"["{block_height}", 0]"#))
|
||||
.await?
|
||||
.text()
|
||||
.await?,
|
||||
)?;
|
||||
|
||||
raw_blocks.push((
|
||||
block_height,
|
||||
raw_block["result"]
|
||||
.as_str()
|
||||
.expect("unexpected getblock result: not a string")
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
zebrad.kill(true)?;
|
||||
|
||||
// Sleep for a few seconds to make sure zebrad releases lock on cached state directory
|
||||
std::thread::sleep(Duration::from_secs(3));
|
||||
|
||||
let zebrad_state_path = test_type
|
||||
.zebrad_state_path(test_name)
|
||||
.expect("already checked that there is a cached state path");
|
||||
|
||||
let Height(finalized_tip_height) =
|
||||
load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?;
|
||||
|
||||
tracing::info!(
|
||||
?finalized_tip_height,
|
||||
non_finalized_tip_height = ?tip_height,
|
||||
?estimated_finalized_tip_height,
|
||||
"got finalized tip height from state directory"
|
||||
);
|
||||
|
||||
let raw_future_blocks = raw_blocks
|
||||
.into_iter()
|
||||
.filter_map(|(height, raw_block)| height.gt(&finalized_tip_height).then_some(raw_block))
|
||||
.collect();
|
||||
|
||||
Ok(raw_future_blocks)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
use std::{
|
||||
env,
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
@ -14,6 +15,7 @@ use std::{
|
|||
use color_eyre::eyre::Result;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use zebra_test::net::random_known_port;
|
||||
use zebrad::{
|
||||
components::{mempool, sync, tracing},
|
||||
config::ZebradConfig,
|
||||
|
@ -95,3 +97,27 @@ pub fn config_file_full_path(config_file: PathBuf) -> PathBuf {
|
|||
let path = configs_dir().join(config_file);
|
||||
Path::new(&path).into()
|
||||
}
|
||||
|
||||
/// Returns a `zebrad` config with a random known RPC port.
|
||||
///
|
||||
/// Set `parallel_cpu_threads` to true to auto-configure based on the number of CPU cores.
|
||||
pub fn random_known_rpc_port_config(parallel_cpu_threads: bool) -> Result<ZebradConfig> {
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let listen_port = random_known_port();
|
||||
let listen_ip = "127.0.0.1".parse().expect("hard-coded IP is valid");
|
||||
let zebra_rpc_listener = SocketAddr::new(listen_ip, listen_port);
|
||||
|
||||
// Write a configuration that has the rpc listen_addr option set
|
||||
// TODO: split this config into another function?
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(zebra_rpc_listener);
|
||||
if parallel_cpu_threads {
|
||||
// Auto-configure to the number of CPU cores: most users configre this
|
||||
config.rpc.parallel_cpu_threads = 0;
|
||||
} else {
|
||||
// Default config, users who want to detect port conflicts configure this
|
||||
config.rpc.parallel_cpu_threads = 1;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
//! Acceptance tests for getblocktemplate RPC methods in Zebra.
|
||||
|
||||
use super::*;
|
||||
|
||||
pub(crate) mod submit_block;
|
||||
|
|
|
@ -1,170 +1,92 @@
|
|||
//! Test submitblock RPC method.
|
||||
//!
|
||||
//! This test requires a cached chain state that is synchronized past the max checkpoint height,
|
||||
//! and will sync to the next block without updating the cached chain state.
|
||||
//! This test requires a cached chain state that is partially synchronized close to the
|
||||
//! network chain tip height. It will finish the sync and update the cached chain state.
|
||||
//!
|
||||
//! After finishing the sync, it will get the first few blocks in the non-finalized state
|
||||
//! (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height
|
||||
//! of the updated cached state, restart zebra without peers, and submit blocks above the
|
||||
//! finalized tip height.
|
||||
|
||||
// TODO: Update this test and the doc to:
|
||||
//
|
||||
// This test requires a cached chain state that is partially synchronized close to the
|
||||
// network chain tip height, and will finish the sync and update the cached chain state.
|
||||
//
|
||||
// After finishing the sync, it will get the first 20 blocks in the non-finalized state
|
||||
// (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, get the finalized tip height
|
||||
// of the updated cached state, restart zebra without peers, and submit blocks above the
|
||||
// finalized tip height.
|
||||
use color_eyre::eyre::{Context, Result};
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use color_eyre::eyre::{eyre, Context, Result};
|
||||
|
||||
use futures::TryFutureExt;
|
||||
use indexmap::IndexSet;
|
||||
use reqwest::Client;
|
||||
use tower::{Service, ServiceExt};
|
||||
use zebra_chain::{block::Height, parameters::Network, serialization::ZcashSerialize};
|
||||
use zebra_state::HashOrHeight;
|
||||
use zebra_test::args;
|
||||
use zebra_chain::parameters::Network;
|
||||
|
||||
use crate::common::{
|
||||
cached_state::{copy_state_directory, start_state_service_with_cache_dir},
|
||||
config::{persistent_test_config, testdir},
|
||||
launch::ZebradTestDirExt,
|
||||
lightwalletd::random_known_rpc_port_config,
|
||||
cached_state::get_raw_future_blocks,
|
||||
launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc},
|
||||
test_type::TestType,
|
||||
};
|
||||
|
||||
use super::cached_state::{load_tip_height_from_state_directory, ZEBRA_CACHED_STATE_DIR};
|
||||
|
||||
async fn get_future_block_hex_data(
|
||||
network: Network,
|
||||
zebrad_state_path: &PathBuf,
|
||||
) -> Result<Option<String>> {
|
||||
tracing::info!(
|
||||
?zebrad_state_path,
|
||||
"getting cached sync height from ZEBRA_CACHED_STATE_DIR path"
|
||||
);
|
||||
|
||||
let cached_sync_height =
|
||||
load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?;
|
||||
|
||||
let future_block_height = Height(cached_sync_height.0 + 1);
|
||||
|
||||
tracing::info!(
|
||||
?cached_sync_height,
|
||||
?future_block_height,
|
||||
"got cached sync height, copying state dir to tempdir"
|
||||
);
|
||||
|
||||
let copied_state_path = copy_state_directory(network, &zebrad_state_path).await?;
|
||||
|
||||
let mut config = persistent_test_config()?;
|
||||
config.state.debug_stop_at_height = Some(future_block_height.0);
|
||||
|
||||
let mut child = copied_state_path
|
||||
.with_config(&mut config)?
|
||||
.spawn_child(args!["start"])?
|
||||
.bypass_test_capture(true);
|
||||
|
||||
while child.is_running() {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
let _ = child.kill(true);
|
||||
let copied_state_path = child.dir.take().unwrap();
|
||||
|
||||
let (_read_write_state_service, mut state, _latest_chain_tip, _chain_tip_change) =
|
||||
start_state_service_with_cache_dir(network, copied_state_path.as_ref()).await?;
|
||||
let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(future_block_height));
|
||||
|
||||
let response = state
|
||||
.ready()
|
||||
.and_then(|ready_service| ready_service.call(request))
|
||||
.map_err(|error| eyre!(error))
|
||||
.await?;
|
||||
|
||||
let block_hex_data = match response {
|
||||
zebra_state::ReadResponse::Block(Some(block)) => {
|
||||
hex::encode(block.zcash_serialize_to_vec()?)
|
||||
}
|
||||
zebra_state::ReadResponse::Block(None) => {
|
||||
tracing::info!(
|
||||
"Reached the end of the finalized chain, state is missing block at {future_block_height:?}",
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
_ => unreachable!("Incorrect response from state service: {response:?}"),
|
||||
};
|
||||
|
||||
Ok(Some(block_hex_data))
|
||||
}
|
||||
/// Number of blocks past the finalized to retrieve and submit.
|
||||
const MAX_NUM_FUTURE_BLOCKS: u32 = 3;
|
||||
|
||||
#[allow(clippy::print_stderr)]
|
||||
pub(crate) async fn run() -> Result<(), color_eyre::Report> {
|
||||
pub(crate) async fn run() -> Result<()> {
|
||||
let _init_guard = zebra_test::init();
|
||||
|
||||
let mut config = random_known_rpc_port_config(true)?;
|
||||
let network = config.network.network;
|
||||
let rpc_address = config.rpc.listen_addr.unwrap();
|
||||
// We want a zebra state dir in place,
|
||||
let test_type = TestType::UpdateZebraCachedStateWithRpc;
|
||||
let test_name = "submit_block_test";
|
||||
let network = Network::Mainnet;
|
||||
|
||||
config.state.cache_dir = match std::env::var_os(ZEBRA_CACHED_STATE_DIR) {
|
||||
Some(path) => path.into(),
|
||||
None => {
|
||||
eprintln!(
|
||||
"skipped submitblock test, \
|
||||
set the {ZEBRA_CACHED_STATE_DIR:?} environment variable to run the test",
|
||||
);
|
||||
// Skip the test unless the user specifically asked for it and there is a zebrad_state_path
|
||||
if !can_spawn_zebrad_for_rpc(test_name, test_type) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
tracing::info!(
|
||||
?network,
|
||||
?test_type,
|
||||
"running submitblock test using zebrad",
|
||||
);
|
||||
|
||||
// TODO: As part of or as a pre-cursor to issue #5015,
|
||||
// - Use only original cached state,
|
||||
// - sync until the tip
|
||||
// - get first 3 blocks in non-finalized state via getblock rpc calls
|
||||
// - restart zebra without peers
|
||||
// - submit block(s) above the finalized tip height
|
||||
let block_hex_data = get_future_block_hex_data(network, &config.state.cache_dir)
|
||||
.await?
|
||||
.expect(
|
||||
"spawned zebrad in get_future_block_hex_data should live until it gets the next block",
|
||||
);
|
||||
let raw_blocks: Vec<String> =
|
||||
get_raw_future_blocks(network, test_type, test_name, MAX_NUM_FUTURE_BLOCKS).await?;
|
||||
|
||||
// Runs the rest of this test without an internet connection
|
||||
config.network.initial_mainnet_peers = IndexSet::new();
|
||||
config.network.initial_testnet_peers = IndexSet::new();
|
||||
config.mempool.debug_enable_at_height = Some(0);
|
||||
tracing::info!("got raw future blocks, spawning isolated zebrad...",);
|
||||
|
||||
// We're using the cached state
|
||||
config.state.ephemeral = false;
|
||||
// Start zebrad with no peers, we run the rest of the submitblock test without syncing.
|
||||
let should_sync = false;
|
||||
let (mut zebrad, zebra_rpc_address) =
|
||||
spawn_zebrad_for_rpc(network, test_name, test_type, should_sync)?
|
||||
.expect("Already checked zebra state path with can_spawn_zebrad_for_rpc");
|
||||
|
||||
let mut child = testdir()?
|
||||
.with_exact_config(&config)?
|
||||
.spawn_child(args!["start"])?
|
||||
.bypass_test_capture(true);
|
||||
let rpc_address = zebra_rpc_address.expect("submitblock test must have RPC port");
|
||||
|
||||
child.expect_stdout_line_matches(&format!("Opened RPC endpoint at {rpc_address}"))?;
|
||||
tracing::info!(
|
||||
?test_type,
|
||||
?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 {rpc_address}"))?;
|
||||
|
||||
tracing::info!(?rpc_address, "zebrad opened its RPC port",);
|
||||
|
||||
// Create an http client
|
||||
let client = Client::new();
|
||||
|
||||
let res = client
|
||||
.post(format!("http://{}", &rpc_address))
|
||||
.body(format!(
|
||||
r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{block_hex_data}"], "id":123 }}"#
|
||||
))
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?;
|
||||
for raw_block in raw_blocks {
|
||||
let res = client
|
||||
.post(format!("http://{}", &rpc_address))
|
||||
.body(format!(
|
||||
r#"{{"jsonrpc": "2.0", "method": "submitblock", "params": ["{raw_block}"], "id":123 }}"#
|
||||
))
|
||||
.header("Content-Type", "application/json")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert!(res.status().is_success());
|
||||
let res_text = res.text().await?;
|
||||
assert!(res.status().is_success());
|
||||
let res_text = res.text().await?;
|
||||
|
||||
// Test rpc endpoint response
|
||||
assert!(res_text.contains(r#""result":"null""#));
|
||||
// Test rpc endpoint response
|
||||
assert!(res_text.contains(r#""result":null"#));
|
||||
}
|
||||
|
||||
child.kill(false)?;
|
||||
zebrad.kill(false)?;
|
||||
|
||||
let output = child.wait_with_output()?;
|
||||
let output = zebrad.wait_with_output()?;
|
||||
let output = output.assert_failure()?;
|
||||
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
|
|
|
@ -25,9 +25,8 @@ use zebra_test::{
|
|||
use zebrad::config::ZebradConfig;
|
||||
|
||||
use crate::common::{
|
||||
config::testdir,
|
||||
lightwalletd::{zebra_skip_lightwalletd_tests, LightwalletdTestType},
|
||||
sync::FINISH_PARTIAL_SYNC_TIMEOUT,
|
||||
config::testdir, lightwalletd::zebra_skip_lightwalletd_tests,
|
||||
sync::FINISH_PARTIAL_SYNC_TIMEOUT, test_type::TestType,
|
||||
};
|
||||
|
||||
/// After we launch `zebrad`, wait this long for the command to start up,
|
||||
|
@ -213,7 +212,7 @@ where
|
|||
pub fn spawn_zebrad_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
network: Network,
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
test_type: TestType,
|
||||
use_internet_connection: bool,
|
||||
) -> Result<Option<(TestChild<TempDir>, Option<SocketAddr>)>> {
|
||||
let test_name = test_name.as_ref();
|
||||
|
@ -255,7 +254,7 @@ pub fn spawn_zebrad_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
|||
#[tracing::instrument]
|
||||
pub fn can_spawn_zebrad_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
test_type: TestType,
|
||||
) -> bool {
|
||||
if zebra_test::net::zebra_skip_network_tests() {
|
||||
return false;
|
||||
|
|
|
@ -9,7 +9,6 @@ use std::{
|
|||
env,
|
||||
net::SocketAddr,
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use tempfile::TempDir;
|
||||
|
@ -17,26 +16,12 @@ use tempfile::TempDir;
|
|||
use zebra_chain::parameters::Network::{self, *};
|
||||
use zebra_test::{
|
||||
args,
|
||||
command::{Arguments, TestChild, TestDirExt, NO_MATCHES_REGEX_ITER},
|
||||
command::{Arguments, TestChild, TestDirExt},
|
||||
net::random_known_port,
|
||||
prelude::*,
|
||||
};
|
||||
use zebrad::config::ZebradConfig;
|
||||
|
||||
use super::{
|
||||
cached_state::ZEBRA_CACHED_STATE_DIR,
|
||||
config::{default_test_config, testdir},
|
||||
failure_messages::{
|
||||
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES, LIGHTWALLETD_FAILURE_MESSAGES,
|
||||
PROCESS_FAILURE_MESSAGES, ZEBRA_FAILURE_MESSAGES,
|
||||
},
|
||||
launch::{
|
||||
ZebradTestDirExt, LIGHTWALLETD_DELAY, LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
||||
LIGHTWALLETD_UPDATE_TIP_DELAY,
|
||||
},
|
||||
};
|
||||
|
||||
use LightwalletdTestType::*;
|
||||
use super::{config::testdir, launch::ZebradTestDirExt, test_type::TestType};
|
||||
|
||||
#[cfg(feature = "lightwalletd-grpc-tests")]
|
||||
pub mod send_transaction_test;
|
||||
|
@ -60,7 +45,7 @@ pub const ZEBRA_TEST_LIGHTWALLETD: &str = "ZEBRA_TEST_LIGHTWALLETD";
|
|||
|
||||
/// Optional environment variable with the cached state for lightwalletd.
|
||||
///
|
||||
/// Required for [`LightwalletdTestType::UpdateCachedState`],
|
||||
/// Required for [`TestType::UpdateCachedState`],
|
||||
/// so we can test lightwalletd RPC integration with a populated state.
|
||||
///
|
||||
/// Can also be used to speed up the [`sending_transactions_using_lightwalletd`] test,
|
||||
|
@ -88,30 +73,6 @@ pub fn zebra_skip_lightwalletd_tests() -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
/// Returns a `zebrad` config with a random known RPC port.
|
||||
///
|
||||
/// Set `parallel_cpu_threads` to true to auto-configure based on the number of CPU cores.
|
||||
pub fn random_known_rpc_port_config(parallel_cpu_threads: bool) -> Result<ZebradConfig> {
|
||||
// [Note on port conflict](#Note on port conflict)
|
||||
let listen_port = random_known_port();
|
||||
let listen_ip = "127.0.0.1".parse().expect("hard-coded IP is valid");
|
||||
let zebra_rpc_listener = SocketAddr::new(listen_ip, listen_port);
|
||||
|
||||
// Write a configuration that has the rpc listen_addr option set
|
||||
// TODO: split this config into another function?
|
||||
let mut config = default_test_config()?;
|
||||
config.rpc.listen_addr = Some(zebra_rpc_listener);
|
||||
if parallel_cpu_threads {
|
||||
// Auto-configure to the number of CPU cores: most users configre this
|
||||
config.rpc.parallel_cpu_threads = 0;
|
||||
} else {
|
||||
// Default config, users who want to detect port conflicts configure this
|
||||
config.rpc.parallel_cpu_threads = 1;
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// Spawns a lightwalletd instance on `network`, connected to `zebrad_rpc_address`,
|
||||
/// with its gRPC server functionality enabled.
|
||||
///
|
||||
|
@ -126,7 +87,7 @@ pub fn random_known_rpc_port_config(parallel_cpu_threads: bool) -> Result<Zebrad
|
|||
pub fn spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
network: Network,
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
test_type: TestType,
|
||||
zebrad_rpc_address: SocketAddr,
|
||||
) -> Result<Option<(TestChild<TempDir>, u16)>> {
|
||||
assert_eq!(network, Mainnet, "this test only supports Mainnet for now");
|
||||
|
@ -165,7 +126,7 @@ pub fn spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
|||
#[tracing::instrument]
|
||||
pub fn can_spawn_lightwalletd_for_rpc<S: AsRef<str> + std::fmt::Debug>(
|
||||
test_name: S,
|
||||
test_type: LightwalletdTestType,
|
||||
test_type: TestType,
|
||||
) -> bool {
|
||||
if zebra_test::net::zebra_skip_network_tests() {
|
||||
return false;
|
||||
|
@ -304,278 +265,3 @@ where
|
|||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of lightwalletd integration test that we're running.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum LightwalletdTestType {
|
||||
/// Launch with an empty Zebra and lightwalletd state.
|
||||
LaunchWithEmptyState,
|
||||
|
||||
/// 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?
|
||||
///
|
||||
/// If `false`, the test fails if the lightwalletd state is populated.
|
||||
allow_lightwalletd_cached_state: bool,
|
||||
},
|
||||
|
||||
/// Sync to tip from a lightwalletd cached state.
|
||||
///
|
||||
/// This test requires a cached Zebra and lightwalletd state.
|
||||
UpdateCachedState,
|
||||
|
||||
/// Launch `zebrad` and sync it to the tip, but don't launch `lightwalletd`.
|
||||
///
|
||||
/// If this test fails, the failure is in `zebrad` without RPCs or `lightwalletd`.
|
||||
/// If it succeeds, but the RPC tests fail, the problem is caused by RPCs or `lightwalletd`.
|
||||
///
|
||||
/// This test requires a cached Zebra state.
|
||||
UpdateZebraCachedStateNoRpc,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this test launch `lightwalletd`?
|
||||
pub fn launches_lightwalletd(&self) -> bool {
|
||||
match self {
|
||||
UpdateZebraCachedStateNoRpc => false,
|
||||
LaunchWithEmptyState | FullSyncFromGenesis { .. } | UpdateCachedState => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
UpdateCachedState => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this test allow a `lightwalletd` cached state, even if it is not required?
|
||||
pub fn allow_lightwalletd_cached_state(&self) -> bool {
|
||||
match self {
|
||||
LaunchWithEmptyState => false,
|
||||
FullSyncFromGenesis {
|
||||
allow_lightwalletd_cached_state,
|
||||
} => *allow_lightwalletd_cached_state,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<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",
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Zebra config for this test.
|
||||
///
|
||||
/// Returns `None` if the test should be skipped,
|
||||
/// and `Some(Err(_))` if the config could not be created.
|
||||
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)
|
||||
} else {
|
||||
default_test_config()
|
||||
};
|
||||
|
||||
let mut config = match config {
|
||||
Ok(config) => config,
|
||||
Err(error) => return Some(Err(error)),
|
||||
};
|
||||
|
||||
// We want to preload the consensus parameters,
|
||||
// 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));
|
||||
}
|
||||
|
||||
let zebra_state_path = self.zebrad_state_path(test_name)?;
|
||||
|
||||
config.sync.checkpoint_verify_concurrency_limit =
|
||||
zebrad::components::sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT;
|
||||
|
||||
config.state.ephemeral = false;
|
||||
config.state.cache_dir = zebra_state_path;
|
||||
|
||||
Some(Ok(config))
|
||||
}
|
||||
|
||||
/// Returns the `lightwalletd` state path for this test, if set, and if allowed for this test.
|
||||
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",
|
||||
);
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
match env::var_os(LIGHTWALLETD_DATA_DIR) {
|
||||
Some(path) => Some(path.into()),
|
||||
None => {
|
||||
if self.needs_lightwalletd_cached_state() {
|
||||
tracing::info!(
|
||||
"skipped {test_name:?} {self:?} lightwalletd test, \
|
||||
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run the test",
|
||||
);
|
||||
} else if self.allow_lightwalletd_cached_state() {
|
||||
tracing::info!(
|
||||
"running {test_name:?} {self:?} lightwalletd test without cached state, \
|
||||
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run with cached state",
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `zebrad` timeout for this test type.
|
||||
pub fn zebrad_timeout(&self) -> Duration {
|
||||
match self {
|
||||
LaunchWithEmptyState => LIGHTWALLETD_DELAY,
|
||||
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc => LIGHTWALLETD_UPDATE_TIP_DELAY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `lightwalletd` timeout for this test type.
|
||||
#[track_caller]
|
||||
pub fn lightwalletd_timeout(&self) -> Duration {
|
||||
if !self.launches_lightwalletd() {
|
||||
panic!("lightwalletd must not be launched in the {self:?} test");
|
||||
}
|
||||
|
||||
// We use the same timeouts for zebrad and lightwalletd,
|
||||
// because the tests check zebrad and lightwalletd concurrently.
|
||||
match self {
|
||||
LaunchWithEmptyState => LIGHTWALLETD_DELAY,
|
||||
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc => LIGHTWALLETD_UPDATE_TIP_DELAY,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Zebra log regexes that indicate the tests have failed,
|
||||
/// and regexes of any failures that should be ignored.
|
||||
pub fn zebrad_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
||||
let mut zebrad_failure_messages: Vec<String> = ZEBRA_FAILURE_MESSAGES
|
||||
.iter()
|
||||
.chain(PROCESS_FAILURE_MESSAGES)
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
if self.needs_zebra_cached_state() {
|
||||
// Fail if we need a cached Zebra state, but it's empty
|
||||
zebrad_failure_messages.push("loaded Zebra state cache .*tip.*=.*None".to_string());
|
||||
}
|
||||
if *self == LaunchWithEmptyState {
|
||||
// Fail if we need an empty Zebra state, but it has blocks
|
||||
zebrad_failure_messages
|
||||
.push(r"loaded Zebra state cache .*tip.*=.*Height\([1-9][0-9]*\)".to_string());
|
||||
}
|
||||
|
||||
let zebrad_ignore_messages = Vec::new();
|
||||
|
||||
(zebrad_failure_messages, zebrad_ignore_messages)
|
||||
}
|
||||
|
||||
/// Returns `lightwalletd` log regexes that indicate the tests have failed,
|
||||
/// and regexes of any failures that should be ignored.
|
||||
#[track_caller]
|
||||
pub fn lightwalletd_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
||||
if !self.launches_lightwalletd() {
|
||||
panic!("lightwalletd must not be launched in the {self:?} test");
|
||||
}
|
||||
|
||||
let mut lightwalletd_failure_messages: Vec<String> = LIGHTWALLETD_FAILURE_MESSAGES
|
||||
.iter()
|
||||
.chain(PROCESS_FAILURE_MESSAGES)
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
// Zebra state failures
|
||||
if self.needs_zebra_cached_state() {
|
||||
// Fail if we need a cached Zebra state, but it's empty
|
||||
lightwalletd_failure_messages.push("No Chain tip available yet".to_string());
|
||||
}
|
||||
|
||||
// lightwalletd state failures
|
||||
if self.needs_lightwalletd_cached_state() {
|
||||
// Fail if we need a cached lightwalletd state, but it isn't near the tip
|
||||
lightwalletd_failure_messages.push("Found [0-9]{1,6} blocks in cache".to_string());
|
||||
}
|
||||
if !self.allow_lightwalletd_cached_state() {
|
||||
// Fail if we need an empty lightwalletd state, but it has blocks
|
||||
lightwalletd_failure_messages.push("Found [1-9][0-9]* blocks in cache".to_string());
|
||||
}
|
||||
|
||||
let lightwalletd_ignore_messages = if *self == LaunchWithEmptyState {
|
||||
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES.iter()
|
||||
} else {
|
||||
NO_MATCHES_REGEX_ITER.iter()
|
||||
}
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
(lightwalletd_failure_messages, lightwalletd_ignore_messages)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,49 +1,42 @@
|
|||
//! Test sending transactions using a lightwalletd instance connected to a zebrad instance.
|
||||
//!
|
||||
//! This test requires a cached chain state that is partially synchronized, i.e., it should be a
|
||||
//! few blocks below the network chain tip height. We open this state during the test, but we don't
|
||||
//! add any blocks to it.
|
||||
//! This test requires a cached chain state that is partially synchronized close to the
|
||||
//! network chain tip height. It will finish the sync and update the cached chain state.
|
||||
//!
|
||||
//! The transactions to use to send are obtained from the blocks synchronized by a temporary zebrad
|
||||
//! instance that are higher than the chain tip of the cached state. This instance uses a copy of
|
||||
//! the state.
|
||||
//! After finishing the sync, it will get the first 20 blocks in the non-finalized state
|
||||
//! (past the MAX_BLOCK_REORG_HEIGHT) via getblock rpc calls, shuts down the zebrad instance
|
||||
//! so that the retrieved blocks aren't finalized into the cached state, and get the finalized
|
||||
//! tip height of the updated cached state.
|
||||
//!
|
||||
//! The transactions to use to send are obtained from those blocks that are above the finalized
|
||||
//! tip height of the updated cached state.
|
||||
//!
|
||||
//! The zebrad instance connected to lightwalletd uses the cached state and does not connect to any
|
||||
//! external peers, which prevents it from downloading the blocks from where the test transactions
|
||||
//! were obtained. This is to ensure that zebra does not reject the transactions because they have
|
||||
//! already been seen in a block.
|
||||
|
||||
use std::{
|
||||
cmp::min,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use std::{cmp::min, sync::Arc};
|
||||
|
||||
use color_eyre::eyre::{eyre, Result};
|
||||
use futures::TryFutureExt;
|
||||
use tower::{Service, ServiceExt};
|
||||
use color_eyre::eyre::Result;
|
||||
|
||||
use zebra_chain::{
|
||||
block,
|
||||
chain_tip::ChainTip,
|
||||
parameters::Network::{self, *},
|
||||
serialization::ZcashSerialize,
|
||||
transaction::{self, Transaction},
|
||||
};
|
||||
use zebra_rpc::queue::CHANNEL_AND_QUEUE_CAPACITY;
|
||||
use zebra_state::HashOrHeight;
|
||||
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},
|
||||
cached_state::get_future_blocks,
|
||||
launch::{can_spawn_zebrad_for_rpc, spawn_zebrad_for_rpc},
|
||||
lightwalletd::{
|
||||
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,
|
||||
test_type::TestType::{self, *},
|
||||
};
|
||||
|
||||
/// The maximum number of transactions we want to send in the test.
|
||||
|
@ -55,6 +48,9 @@ fn max_sent_transactions() -> usize {
|
|||
min(CHANNEL_AND_QUEUE_CAPACITY, MAX_INBOUND_CONCURRENCY) - 1
|
||||
}
|
||||
|
||||
/// Number of blocks past the finalized to load transactions from.
|
||||
const MAX_NUM_FUTURE_BLOCKS: u32 = 50;
|
||||
|
||||
/// The test entry point.
|
||||
//
|
||||
// TODO:
|
||||
|
@ -94,7 +90,7 @@ pub async fn run() -> Result<()> {
|
|||
);
|
||||
|
||||
let mut transactions =
|
||||
load_transactions_from_future_blocks(network, zebrad_state_path.clone()).await?;
|
||||
load_transactions_from_future_blocks(network, test_type, test_name).await?;
|
||||
|
||||
tracing::info!(
|
||||
transaction_count = ?transactions.len(),
|
||||
|
@ -252,136 +248,31 @@ pub async fn run() -> Result<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Loads transactions from a block that's after the chain tip of the cached state.
|
||||
/// Loads transactions from a few block(s) after the chain tip of the cached state.
|
||||
///
|
||||
/// We copy the cached state to avoid modifying `zebrad_state_path`.
|
||||
/// This copy is used to launch a `zebrad` instance connected to the network,
|
||||
/// which finishes synchronizing the chain.
|
||||
/// Then we load transactions from this updated state.
|
||||
/// Returns a list of non-coinbase transactions from blocks that have not been finalized to disk
|
||||
/// in the `ZEBRA_CACHED_STATE_DIR`.
|
||||
///
|
||||
/// Returns a list of valid transactions that are not in any of the blocks present in the
|
||||
/// original `zebrad_state_path`.
|
||||
/// ## Panics
|
||||
///
|
||||
/// If the provided `test_type` doesn't need an rpc server and cached state
|
||||
#[tracing::instrument]
|
||||
async fn load_transactions_from_future_blocks(
|
||||
network: Network,
|
||||
zebrad_state_path: PathBuf,
|
||||
test_type: TestType,
|
||||
test_name: &str,
|
||||
) -> Result<Vec<Arc<Transaction>>> {
|
||||
let partial_sync_height =
|
||||
load_tip_height_from_state_directory(network, zebrad_state_path.as_ref()).await?;
|
||||
|
||||
tracing::info!(
|
||||
?partial_sync_height,
|
||||
partial_sync_path = ?zebrad_state_path,
|
||||
"performing full sync...",
|
||||
);
|
||||
|
||||
let full_sync_path =
|
||||
copy_state_and_perform_full_sync(network, zebrad_state_path.as_ref()).await?;
|
||||
|
||||
tracing::info!(?full_sync_path, "loading transactions...");
|
||||
|
||||
let transactions =
|
||||
load_transactions_from_block_after(partial_sync_height, network, full_sync_path.as_ref())
|
||||
.await?;
|
||||
let transactions = get_future_blocks(network, test_type, test_name, MAX_NUM_FUTURE_BLOCKS)
|
||||
.await?
|
||||
.into_iter()
|
||||
.flat_map(|block| block.transactions)
|
||||
.filter(|transaction| !transaction.is_coinbase())
|
||||
.take(max_sent_transactions())
|
||||
.collect();
|
||||
|
||||
Ok(transactions)
|
||||
}
|
||||
|
||||
/// Loads transactions from a block that's after the specified `height`.
|
||||
///
|
||||
/// Starts at the block after the block at the specified `height`, and stops when it finds a block
|
||||
/// from where it can load at least one non-coinbase transaction.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the specified `zebrad_state_path` contains a chain state that's not synchronized to a tip that's
|
||||
/// after `height`.
|
||||
#[tracing::instrument]
|
||||
async fn load_transactions_from_block_after(
|
||||
height: block::Height,
|
||||
network: Network,
|
||||
zebrad_state_path: &Path,
|
||||
) -> Result<Vec<Arc<Transaction>>> {
|
||||
let (_read_write_state_service, mut state, latest_chain_tip, _chain_tip_change) =
|
||||
start_state_service_with_cache_dir(network, zebrad_state_path).await?;
|
||||
|
||||
let tip_height = latest_chain_tip
|
||||
.best_tip_height()
|
||||
.ok_or_else(|| eyre!("State directory doesn't have a chain tip block"))?;
|
||||
|
||||
assert!(
|
||||
tip_height > height,
|
||||
"Chain not synchronized to a block after the specified height",
|
||||
);
|
||||
|
||||
let mut target_height = height.0;
|
||||
let mut transactions = Vec::new();
|
||||
|
||||
while transactions.len() < max_sent_transactions() {
|
||||
let new_transactions =
|
||||
load_transactions_from_block(block::Height(target_height), &mut state).await?;
|
||||
|
||||
if let Some(mut new_transactions) = new_transactions {
|
||||
new_transactions.retain(|transaction| !transaction.is_coinbase());
|
||||
transactions.append(&mut new_transactions);
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Reached the end of the finalized chain\n\
|
||||
collected {} transactions from {} blocks before {target_height:?}",
|
||||
transactions.len(),
|
||||
target_height - height.0 - 1,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
target_height += 1;
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
"Collected {} transactions from {} blocks before {target_height:?}",
|
||||
transactions.len(),
|
||||
target_height - height.0 - 1,
|
||||
);
|
||||
|
||||
Ok(transactions)
|
||||
}
|
||||
|
||||
/// Performs a request to the provided read-only `state` service to fetch all transactions from a
|
||||
/// block at the specified `height`.
|
||||
#[tracing::instrument(skip(state))]
|
||||
async fn load_transactions_from_block<ReadStateService>(
|
||||
height: block::Height,
|
||||
state: &mut ReadStateService,
|
||||
) -> Result<Option<Vec<Arc<Transaction>>>>
|
||||
where
|
||||
ReadStateService: Service<
|
||||
zebra_state::ReadRequest,
|
||||
Response = zebra_state::ReadResponse,
|
||||
Error = zebra_state::BoxError,
|
||||
>,
|
||||
{
|
||||
let request = zebra_state::ReadRequest::Block(HashOrHeight::Height(height));
|
||||
|
||||
let response = state
|
||||
.ready()
|
||||
.and_then(|ready_service| ready_service.call(request))
|
||||
.map_err(|error| eyre!(error))
|
||||
.await?;
|
||||
|
||||
let block = match response {
|
||||
zebra_state::ReadResponse::Block(Some(block)) => block,
|
||||
zebra_state::ReadResponse::Block(None) => {
|
||||
tracing::info!(
|
||||
"Reached the end of the finalized chain, state is missing block at {height:?}",
|
||||
);
|
||||
return Ok(None);
|
||||
}
|
||||
_ => unreachable!("Incorrect response from state service: {response:?}"),
|
||||
};
|
||||
|
||||
Ok(Some(block.transactions.to_vec()))
|
||||
}
|
||||
|
||||
/// Prepare a request to send to lightwalletd that contains a transaction to be sent.
|
||||
fn prepare_send_transaction_request(transaction: Arc<Transaction>) -> wallet_grpc::RawTransaction {
|
||||
let transaction_bytes = transaction.zcash_serialize_to_vec().unwrap();
|
||||
|
|
|
@ -12,10 +12,8 @@ use zebra_test::prelude::*;
|
|||
|
||||
use crate::common::{
|
||||
launch::ZebradTestDirExt,
|
||||
lightwalletd::{
|
||||
wallet_grpc::{connect_to_lightwalletd, ChainSpec},
|
||||
LightwalletdTestType,
|
||||
},
|
||||
lightwalletd::wallet_grpc::{connect_to_lightwalletd, ChainSpec},
|
||||
test_type::TestType,
|
||||
};
|
||||
|
||||
/// The amount of time we wait between each tip check.
|
||||
|
@ -33,7 +31,7 @@ pub fn wait_for_zebrad_and_lightwalletd_sync<
|
|||
lightwalletd_rpc_port: u16,
|
||||
mut zebrad: TestChild<P>,
|
||||
zebra_rpc_address: SocketAddr,
|
||||
test_type: LightwalletdTestType,
|
||||
test_type: TestType,
|
||||
wait_for_zebrad_mempool: bool,
|
||||
wait_for_zebrad_tip: bool,
|
||||
) -> Result<(TestChild<TempDir>, TestChild<P>)> {
|
||||
|
|
|
@ -54,8 +54,8 @@ use crate::common::{
|
|||
connect_to_lightwalletd, Address, AddressList, BlockId, BlockRange, ChainSpec, Empty,
|
||||
GetAddressUtxosArg, TransparentAddressBlockFilter, TxFilter,
|
||||
},
|
||||
LightwalletdTestType::UpdateCachedState,
|
||||
},
|
||||
test_type::TestType::UpdateCachedState,
|
||||
};
|
||||
|
||||
/// The test entry point.
|
||||
|
|
|
@ -18,3 +18,4 @@ pub mod get_block_template_rpcs;
|
|||
pub mod launch;
|
||||
pub mod lightwalletd;
|
||||
pub mod sync;
|
||||
pub mod test_type;
|
||||
|
|
|
@ -225,29 +225,21 @@ pub fn sync_until(
|
|||
testdir()?.with_config(&mut config)?
|
||||
};
|
||||
|
||||
let mut child = tempdir.spawn_child(args!["start"])?.with_timeout(timeout);
|
||||
let child = tempdir.spawn_child(args!["start"])?.with_timeout(timeout);
|
||||
|
||||
let network = format!("network: {network},");
|
||||
let network_log = format!("network: {network},");
|
||||
|
||||
if mempool_behavior.require_activation() {
|
||||
// require that the mempool activated,
|
||||
// checking logs as they arrive
|
||||
|
||||
child.expect_stdout_line_matches(&network)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
child.expect_stdout_line_matches("starting legacy chain check")?;
|
||||
child.expect_stdout_line_matches("no legacy chain found")?;
|
||||
}
|
||||
|
||||
// before the stop regex, expect mempool activation
|
||||
if mempool_behavior.require_forced_activation() {
|
||||
child.expect_stdout_line_matches("enabling mempool for debugging")?;
|
||||
}
|
||||
child.expect_stdout_line_matches("activating mempool")?;
|
||||
|
||||
// then wait for the stop log, which must happen after the mempool becomes active
|
||||
child.expect_stdout_line_matches(stop_regex)?;
|
||||
let mut child = check_sync_logs_until(
|
||||
child,
|
||||
network,
|
||||
stop_regex,
|
||||
mempool_behavior,
|
||||
check_legacy_chain,
|
||||
)?;
|
||||
|
||||
// make sure the child process is dead
|
||||
// if it has already exited, ignore that error
|
||||
|
@ -271,7 +263,7 @@ pub fn sync_until(
|
|||
);
|
||||
let output = child.wait_with_output()?;
|
||||
|
||||
output.stdout_line_contains(&network)?;
|
||||
output.stdout_line_contains(&network_log)?;
|
||||
|
||||
if check_legacy_chain {
|
||||
output.stdout_line_contains("starting legacy chain check")?;
|
||||
|
@ -295,6 +287,47 @@ pub fn sync_until(
|
|||
}
|
||||
}
|
||||
|
||||
/// Check sync logs on `network` until `zebrad` logs `stop_regex`.
|
||||
///
|
||||
/// ## Test Settings
|
||||
///
|
||||
/// Checks the logs for the expected `mempool_behavior`.
|
||||
///
|
||||
/// If `check_legacy_chain` is true, make sure the logs contain the legacy chain check.
|
||||
///
|
||||
/// ## Test Status
|
||||
///
|
||||
/// Returns the provided `zebrad` [`TestChild`] when `stop_regex` is encountered.
|
||||
///
|
||||
/// Returns an error if the child exits or `timeout` elapses before `stop_regex` is found.
|
||||
#[tracing::instrument(skip(zebrad))]
|
||||
pub fn check_sync_logs_until(
|
||||
mut zebrad: TestChild<TempDir>,
|
||||
network: Network,
|
||||
stop_regex: &str,
|
||||
// Test Settings
|
||||
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")?;
|
||||
}
|
||||
|
||||
// before the stop regex, expect mempool activation
|
||||
if mempool_behavior.require_forced_activation() {
|
||||
zebrad.expect_stdout_line_matches("enabling mempool for debugging")?;
|
||||
}
|
||||
zebrad.expect_stdout_line_matches("activating mempool")?;
|
||||
|
||||
// then wait for the stop log, which must happen after the mempool becomes active
|
||||
zebrad.expect_stdout_line_matches(stop_regex)?;
|
||||
|
||||
Ok(zebrad)
|
||||
}
|
||||
|
||||
/// Runs a zebrad instance to synchronize the chain to the network tip.
|
||||
///
|
||||
/// The zebrad instance is executed on a copy of the partially synchronized chain state. This copy
|
||||
|
|
|
@ -0,0 +1,319 @@
|
|||
//! Provides TestType enum with shared code for acceptance tests
|
||||
|
||||
use std::{env, path::PathBuf, time::Duration};
|
||||
|
||||
use zebra_test::{command::NO_MATCHES_REGEX_ITER, prelude::*};
|
||||
use zebrad::config::ZebradConfig;
|
||||
|
||||
use super::{
|
||||
cached_state::ZEBRA_CACHED_STATE_DIR,
|
||||
config::{default_test_config, random_known_rpc_port_config},
|
||||
failure_messages::{
|
||||
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES, LIGHTWALLETD_FAILURE_MESSAGES,
|
||||
PROCESS_FAILURE_MESSAGES, ZEBRA_FAILURE_MESSAGES,
|
||||
},
|
||||
launch::{LIGHTWALLETD_DELAY, LIGHTWALLETD_FULL_SYNC_TIP_DELAY, LIGHTWALLETD_UPDATE_TIP_DELAY},
|
||||
lightwalletd::LIGHTWALLETD_DATA_DIR,
|
||||
sync::FINISH_PARTIAL_SYNC_TIMEOUT,
|
||||
};
|
||||
|
||||
use TestType::*;
|
||||
|
||||
/// The type of integration test that we're running.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TestType {
|
||||
/// Launch with an empty Zebra and lightwalletd state.
|
||||
LaunchWithEmptyState,
|
||||
|
||||
/// 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?
|
||||
///
|
||||
/// If `false`, the test fails if the lightwalletd state is populated.
|
||||
allow_lightwalletd_cached_state: bool,
|
||||
},
|
||||
|
||||
/// Sync to tip from a lightwalletd cached state.
|
||||
///
|
||||
/// This test requires a cached Zebra and lightwalletd state.
|
||||
UpdateCachedState,
|
||||
|
||||
/// Launch `zebrad` and sync it to the tip, but don't launch `lightwalletd`.
|
||||
///
|
||||
/// If this test fails, the failure is in `zebrad` without RPCs or `lightwalletd`.
|
||||
/// If it succeeds, but the RPC tests fail, the problem is caused by RPCs or `lightwalletd`.
|
||||
///
|
||||
/// This test requires a cached Zebra state.
|
||||
UpdateZebraCachedStateNoRpc,
|
||||
|
||||
/// Launch `zebrad` and sync it to the tip, but don't launch `lightwalletd`.
|
||||
///
|
||||
/// This test requires a cached Zebra state.
|
||||
#[allow(dead_code)]
|
||||
UpdateZebraCachedStateWithRpc,
|
||||
}
|
||||
|
||||
impl TestType {
|
||||
/// 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
|
||||
| UpdateZebraCachedStateWithRpc => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this test need a Zebra rpc server?
|
||||
pub fn needs_zebra_rpc_server(&self) -> bool {
|
||||
match self {
|
||||
UpdateZebraCachedStateWithRpc => true,
|
||||
UpdateZebraCachedStateNoRpc
|
||||
| LaunchWithEmptyState
|
||||
| FullSyncFromGenesis { .. }
|
||||
| UpdateCachedState => self.launches_lightwalletd(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this test launch `lightwalletd`?
|
||||
pub fn launches_lightwalletd(&self) -> bool {
|
||||
match self {
|
||||
UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => false,
|
||||
LaunchWithEmptyState | FullSyncFromGenesis { .. } | UpdateCachedState => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
| UpdateZebraCachedStateWithRpc => false,
|
||||
UpdateCachedState => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Does this test allow a `lightwalletd` cached state, even if it is not required?
|
||||
pub fn allow_lightwalletd_cached_state(&self) -> bool {
|
||||
match self {
|
||||
LaunchWithEmptyState => false,
|
||||
FullSyncFromGenesis {
|
||||
allow_lightwalletd_cached_state,
|
||||
} => *allow_lightwalletd_cached_state,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 | UpdateZebraCachedStateWithRpc => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the Zebra state path for this test, if set.
|
||||
#[allow(clippy::print_stderr)]
|
||||
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",
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a Zebra config for this test.
|
||||
///
|
||||
/// Returns `None` if the test should be skipped,
|
||||
/// and `Some(Err(_))` if the config could not be created.
|
||||
pub fn zebrad_config<S: AsRef<str>>(&self, test_name: S) -> Option<Result<ZebradConfig>> {
|
||||
let config = if self.needs_zebra_rpc_server() {
|
||||
// This is what we recommend our users configure.
|
||||
random_known_rpc_port_config(true)
|
||||
} else {
|
||||
default_test_config()
|
||||
};
|
||||
|
||||
let mut config = match config {
|
||||
Ok(config) => config,
|
||||
Err(error) => return Some(Err(error)),
|
||||
};
|
||||
|
||||
// We want to preload the consensus parameters,
|
||||
// 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));
|
||||
}
|
||||
|
||||
let zebra_state_path = self.zebrad_state_path(test_name)?;
|
||||
|
||||
config.sync.checkpoint_verify_concurrency_limit =
|
||||
zebrad::components::sync::DEFAULT_CHECKPOINT_CONCURRENCY_LIMIT;
|
||||
|
||||
config.state.ephemeral = false;
|
||||
config.state.cache_dir = zebra_state_path;
|
||||
|
||||
Some(Ok(config))
|
||||
}
|
||||
|
||||
/// Returns the `lightwalletd` state path for this test, if set, and if allowed for this test.
|
||||
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",
|
||||
);
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
match env::var_os(LIGHTWALLETD_DATA_DIR) {
|
||||
Some(path) => Some(path.into()),
|
||||
None => {
|
||||
if self.needs_lightwalletd_cached_state() {
|
||||
tracing::info!(
|
||||
"skipped {test_name:?} {self:?} lightwalletd test, \
|
||||
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run the test",
|
||||
);
|
||||
} else if self.allow_lightwalletd_cached_state() {
|
||||
tracing::info!(
|
||||
"running {test_name:?} {self:?} lightwalletd test without cached state, \
|
||||
set the {LIGHTWALLETD_DATA_DIR:?} environment variable to run with cached state",
|
||||
);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `zebrad` timeout for this test type.
|
||||
pub fn zebrad_timeout(&self) -> Duration {
|
||||
match self {
|
||||
LaunchWithEmptyState => LIGHTWALLETD_DELAY,
|
||||
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc => LIGHTWALLETD_UPDATE_TIP_DELAY,
|
||||
UpdateZebraCachedStateWithRpc => FINISH_PARTIAL_SYNC_TIMEOUT,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `lightwalletd` timeout for this test type.
|
||||
#[track_caller]
|
||||
pub fn lightwalletd_timeout(&self) -> Duration {
|
||||
if !self.launches_lightwalletd() {
|
||||
panic!("lightwalletd must not be launched in the {self:?} test");
|
||||
}
|
||||
|
||||
// We use the same timeouts for zebrad and lightwalletd,
|
||||
// because the tests check zebrad and lightwalletd concurrently.
|
||||
match self {
|
||||
LaunchWithEmptyState => LIGHTWALLETD_DELAY,
|
||||
FullSyncFromGenesis { .. } => LIGHTWALLETD_FULL_SYNC_TIP_DELAY,
|
||||
UpdateCachedState | UpdateZebraCachedStateNoRpc | UpdateZebraCachedStateWithRpc => {
|
||||
LIGHTWALLETD_UPDATE_TIP_DELAY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Zebra log regexes that indicate the tests have failed,
|
||||
/// and regexes of any failures that should be ignored.
|
||||
pub fn zebrad_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
||||
let mut zebrad_failure_messages: Vec<String> = ZEBRA_FAILURE_MESSAGES
|
||||
.iter()
|
||||
.chain(PROCESS_FAILURE_MESSAGES)
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
if self.needs_zebra_cached_state() {
|
||||
// Fail if we need a cached Zebra state, but it's empty
|
||||
zebrad_failure_messages.push("loaded Zebra state cache .*tip.*=.*None".to_string());
|
||||
}
|
||||
if *self == LaunchWithEmptyState {
|
||||
// Fail if we need an empty Zebra state, but it has blocks
|
||||
zebrad_failure_messages
|
||||
.push(r"loaded Zebra state cache .*tip.*=.*Height\([1-9][0-9]*\)".to_string());
|
||||
}
|
||||
|
||||
let zebrad_ignore_messages = Vec::new();
|
||||
|
||||
(zebrad_failure_messages, zebrad_ignore_messages)
|
||||
}
|
||||
|
||||
/// Returns `lightwalletd` log regexes that indicate the tests have failed,
|
||||
/// and regexes of any failures that should be ignored.
|
||||
#[track_caller]
|
||||
pub fn lightwalletd_failure_messages(&self) -> (Vec<String>, Vec<String>) {
|
||||
if !self.launches_lightwalletd() {
|
||||
panic!("lightwalletd must not be launched in the {self:?} test");
|
||||
}
|
||||
|
||||
let mut lightwalletd_failure_messages: Vec<String> = LIGHTWALLETD_FAILURE_MESSAGES
|
||||
.iter()
|
||||
.chain(PROCESS_FAILURE_MESSAGES)
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
// Zebra state failures
|
||||
if self.needs_zebra_cached_state() {
|
||||
// Fail if we need a cached Zebra state, but it's empty
|
||||
lightwalletd_failure_messages.push("No Chain tip available yet".to_string());
|
||||
}
|
||||
|
||||
// lightwalletd state failures
|
||||
if self.needs_lightwalletd_cached_state() {
|
||||
// Fail if we need a cached lightwalletd state, but it isn't near the tip
|
||||
lightwalletd_failure_messages.push("Found [0-9]{1,6} blocks in cache".to_string());
|
||||
}
|
||||
if !self.allow_lightwalletd_cached_state() {
|
||||
// Fail if we need an empty lightwalletd state, but it has blocks
|
||||
lightwalletd_failure_messages.push("Found [1-9][0-9]* blocks in cache".to_string());
|
||||
}
|
||||
|
||||
let lightwalletd_ignore_messages = if *self == LaunchWithEmptyState {
|
||||
LIGHTWALLETD_EMPTY_ZEBRA_STATE_IGNORE_MESSAGES.iter()
|
||||
} else {
|
||||
NO_MATCHES_REGEX_ITER.iter()
|
||||
}
|
||||
.map(ToString::to_string)
|
||||
.collect();
|
||||
|
||||
(lightwalletd_failure_messages, lightwalletd_ignore_messages)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue