diff --git a/Cargo.lock b/Cargo.lock index 443e54e8..de180665 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5740,6 +5740,7 @@ dependencies = [ "hex", "hex-literal", "howudoin", + "humantime-serde", "indexmap 2.0.0", "insta", "itertools 0.11.0", diff --git a/zebra-state/Cargo.toml b/zebra-state/Cargo.toml index 3d32e4b0..74582444 100644 --- a/zebra-state/Cargo.toml +++ b/zebra-state/Cargo.toml @@ -49,6 +49,7 @@ dirs = "5.0.1" futures = "0.3.28" hex = "0.4.3" hex-literal = "0.4.1" +humantime-serde = "1.1.1" indexmap = "2.0.0" itertools = "0.11.0" lazy_static = "1.4.0" diff --git a/zebra-state/src/config.rs b/zebra-state/src/config.rs index 65461968..fa9f85fa 100644 --- a/zebra-state/src/config.rs +++ b/zebra-state/src/config.rs @@ -4,6 +4,7 @@ use std::{ fs::{self, canonicalize, remove_dir_all, DirEntry, ReadDir}, io::ErrorKind, path::{Path, PathBuf}, + time::Duration, }; use semver::Version; @@ -83,11 +84,6 @@ pub struct Config { /// [`cache_dir`]: struct.Config.html#structfield.cache_dir pub ephemeral: bool, - /// Commit blocks to the finalized state up to this height, then exit Zebra. - /// - /// Set to `None` by default: Zebra continues syncing indefinitely. - pub debug_stop_at_height: Option, - /// Whether to delete the old database directories when present. /// /// Set to `true` by default. If this is set to `false`, @@ -95,6 +91,21 @@ pub struct Config { /// deleted. pub delete_old_database: bool, + // Debug configs + // + /// Commit blocks to the finalized state up to this height, then exit Zebra. + /// + /// Set to `None` by default: Zebra continues syncing indefinitely. + pub debug_stop_at_height: Option, + + /// While Zebra is running, check state validity this often. + /// + /// Set to `None` by default: Zebra only checks state format validity on startup and shutdown. + #[serde(with = "humantime_serde")] + pub debug_validity_check_interval: Option, + + // Elasticsearch configs + // #[cfg(feature = "elasticsearch")] /// The elasticsearch database url. pub elasticsearch_url: String, @@ -162,8 +173,9 @@ impl Default for Config { Self { cache_dir, ephemeral: false, - debug_stop_at_height: None, delete_old_database: true, + debug_stop_at_height: None, + debug_validity_check_interval: None, #[cfg(feature = "elasticsearch")] elasticsearch_url: "https://localhost:9200".to_string(), #[cfg(feature = "elasticsearch")] diff --git a/zebra-state/src/constants.rs b/zebra-state/src/constants.rs index d8ae608c..b5a06851 100644 --- a/zebra-state/src/constants.rs +++ b/zebra-state/src/constants.rs @@ -1,7 +1,5 @@ //! Constants that impact state behaviour. -use std::time::Duration; - use lazy_static::lazy_static; use regex::Regex; use semver::Version; @@ -69,12 +67,6 @@ pub fn latest_version_for_adding_subtrees() -> Version { /// Use [`Config::version_file_path()`] to get the path to this file. pub(crate) const DATABASE_FORMAT_VERSION_FILE_NAME: &str = "version"; -/// The interval between database format checks for newly added blocks. -/// -/// This should be short enough that format bugs cause CI test failures, -/// but long enough that it doesn't impact performance. -pub(crate) const DATABASE_FORMAT_CHECK_INTERVAL: Duration = Duration::from_secs(5 * 60); - /// The maximum number of blocks to check for NU5 transactions, /// before we assume we are on a pre-NU5 legacy chain. /// diff --git a/zebra-state/src/service/finalized_state/disk_format/upgrade.rs b/zebra-state/src/service/finalized_state/disk_format/upgrade.rs index 15f28eb0..ee8c050f 100644 --- a/zebra-state/src/service/finalized_state/disk_format/upgrade.rs +++ b/zebra-state/src/service/finalized_state/disk_format/upgrade.rs @@ -2,7 +2,6 @@ use std::{ cmp::Ordering, - convert::Infallible, sync::{mpsc, Arc}, thread::{self, JoinHandle}, }; @@ -23,9 +22,7 @@ use DbFormatChange::*; use crate::{ config::write_database_format_version_to_disk, - constants::{ - latest_version_for_adding_subtrees, DATABASE_FORMAT_CHECK_INTERVAL, DATABASE_FORMAT_VERSION, - }, + constants::{latest_version_for_adding_subtrees, DATABASE_FORMAT_VERSION}, database_format_version_in_code, database_format_version_on_disk, service::finalized_state::{DiskWriteBatch, ZebraDb}, Config, @@ -94,11 +91,11 @@ pub enum DbFormatChange { #[derive(Clone, Debug)] pub struct DbFormatChangeThreadHandle { /// A handle to the format change/check thread. - /// This thread continues running so it can perform periodic format checks. + /// If configured, this thread continues running so it can perform periodic format checks. /// /// Panics from this thread are propagated into Zebra's state service. /// The task returns an error if the upgrade was cancelled by a shutdown. - update_task: Option>>>, + update_task: Option>>>, /// A channel that tells the running format thread to finish early. cancel_handle: mpsc::SyncSender, @@ -258,10 +255,11 @@ impl DbFormatChange { handle } - /// Run the initial format change or check to the database, then periodically check the format - /// of newly added blocks matches the current format. + /// Run the initial format change or check to the database. Under the default runtime config, + /// this method returns after the format change or check. /// - /// This method runs until it is cancelled or panics. + /// But if runtime validity checks are enabled, this method periodically checks the format of + /// newly added blocks matches the current format. It will run until it is cancelled or panics. fn format_change_run_loop( self, config: Config, @@ -269,7 +267,7 @@ impl DbFormatChange { initial_tip_height: Option, upgrade_db: ZebraDb, cancel_receiver: mpsc::Receiver, - ) -> Result { + ) -> Result<(), CancelFormatChange> { self.run_format_change_or_check( &config, network, @@ -278,11 +276,15 @@ impl DbFormatChange { &cancel_receiver, )?; + let Some(debug_validity_check_interval) = config.debug_validity_check_interval else { + return Ok(()); + }; + loop { // We've just run a format check, so sleep first, then run another one. // But return early if there is a cancel signal. if !matches!( - cancel_receiver.recv_timeout(DATABASE_FORMAT_CHECK_INTERVAL), + cancel_receiver.recv_timeout(debug_validity_check_interval), Err(mpsc::RecvTimeoutError::Timeout) ) { return Err(CancelFormatChange); diff --git a/zebrad/tests/common/cached_state.rs b/zebrad/tests/common/cached_state.rs index 3e293321..588d889b 100644 --- a/zebrad/tests/common/cached_state.rs +++ b/zebrad/tests/common/cached_state.rs @@ -30,9 +30,15 @@ use crate::common::{ test_type::TestType, }; -/// Path to a directory containing a cached Zebra state. +/// The environmental variable that holds the path to a directory containing a cached Zebra state. pub const ZEBRA_CACHED_STATE_DIR: &str = "ZEBRA_CACHED_STATE_DIR"; +/// In integration tests, the interval between database format checks for newly added blocks. +/// +/// This should be short enough that format bugs cause CI test failures, +/// but long enough that it doesn't impact performance. +pub const DATABASE_FORMAT_CHECK_INTERVAL: Duration = Duration::from_secs(5 * 60); + /// Type alias for a boxed state service. pub type BoxStateService = BoxService; diff --git a/zebrad/tests/common/config.rs b/zebrad/tests/common/config.rs index 637a479a..884f205d 100644 --- a/zebrad/tests/common/config.rs +++ b/zebrad/tests/common/config.rs @@ -21,6 +21,8 @@ use zebrad::{ config::ZebradConfig, }; +use crate::common::cached_state::DATABASE_FORMAT_CHECK_INTERVAL; + /// Returns a config with: /// - a Zcash listener on an unused port on IPv4 localhost, and /// - an ephemeral state, @@ -61,9 +63,12 @@ pub fn default_test_config() -> Result { ..tracing::Config::default() }; + let mut state = zebra_state::Config::ephemeral(); + state.debug_validity_check_interval = Some(DATABASE_FORMAT_CHECK_INTERVAL); + let config = ZebradConfig { network, - state: zebra_state::Config::ephemeral(), + state, sync, mempool, consensus,