change(state): Expose ZebraDb methods that can create different kinds of databases (#8002)

* Provide access to DiskDb and DiskWriteBatch outside the state using a feature

* Actually let's export ZebraDb for the format upgrade code

* Pass column families to ZebraDb as an argument

* Allow the database kind to be changed in config.rs

* Use the state kind in finalized_state.rs

* Allow different database kinds in ZebraDb, but don't move the upgrade code yet

* Allow different database kinds in DiskDb

* Allow different database kinds in upgrade.rs, but don't split the upgrade code out yet

* Add new arguments to raw database tests

* Fix doc links

* Fix internal imports

* Fix unused code

* Update zebrad version metadata

* Create a specific state database delete function

* Fix state exports

* Fix zebrad tests

* Fix zebrad state write tests

* Make CI run again

* Fix dead code warnings for test methods

* Remove unnecessary async on some tests

* Fix logging required by tests

* Fix logging required in test itself

* Fix variable names

* Try to copy the message and add regexes
This commit is contained in:
teor 2023-11-28 23:49:11 +10:00 committed by GitHub
parent 35a7764b01
commit 1d241afbaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 515 additions and 300 deletions

View File

@ -35,6 +35,9 @@ proptest-impl = [
"zebra-chain/proptest-impl"
]
# Experimental shielded blockchain scanning
shielded-scan = []
# Experimental elasticsearch support
elasticsearch = [
"dep:elasticsearch",

View File

@ -15,11 +15,8 @@ use tracing::Span;
use zebra_chain::parameters::Network;
use crate::{
constants::{
DATABASE_FORMAT_MINOR_VERSION, DATABASE_FORMAT_PATCH_VERSION, DATABASE_FORMAT_VERSION,
DATABASE_FORMAT_VERSION_FILE_NAME,
},
BoxError,
constants::{DATABASE_FORMAT_VERSION_FILE_NAME, STATE_DATABASE_KIND},
state_database_format_version_in_code, BoxError,
};
/// Configuration for the state service.
@ -128,27 +125,37 @@ fn gen_temp_path(prefix: &str) -> PathBuf {
}
impl Config {
/// Returns the path for the finalized state database
pub fn db_path(&self, network: Network) -> PathBuf {
/// Returns the path for the database, based on the kind, major version and network.
/// Each incompatible database format or network gets its own unique path.
pub fn db_path(
&self,
db_kind: impl AsRef<str>,
major_version: u64,
network: Network,
) -> PathBuf {
let db_kind = db_kind.as_ref();
let major_version = format!("v{}", major_version);
let net_dir = network.lowercase_name();
if self.ephemeral {
gen_temp_path(&format!(
"zebra-state-v{}-{}-",
crate::constants::DATABASE_FORMAT_VERSION,
net_dir
))
gen_temp_path(&format!("zebra-{db_kind}-{major_version}-{net_dir}-"))
} else {
self.cache_dir
.join("state")
.join(format!("v{}", crate::constants::DATABASE_FORMAT_VERSION))
.join(db_kind)
.join(major_version)
.join(net_dir)
}
}
/// Returns the path of the database format version file.
pub fn version_file_path(&self, network: Network) -> PathBuf {
let mut version_path = self.db_path(network);
/// Returns the path for the database format minor/patch version file,
/// based on the kind, major version and network.
pub fn version_file_path(
&self,
db_kind: impl AsRef<str>,
major_version: u64,
network: Network,
) -> PathBuf {
let mut version_path = self.db_path(db_kind, major_version, network);
version_path.push(DATABASE_FORMAT_VERSION_FILE_NAME);
@ -189,21 +196,48 @@ impl Default for Config {
// Cleaning up old database versions
// TODO: put this in a different module?
/// Spawns a task that checks if there are old state database folders,
/// and deletes them from the filesystem.
///
/// See `check_and_delete_old_databases()` for details.
pub fn check_and_delete_old_state_databases(config: &Config, network: Network) -> JoinHandle<()> {
check_and_delete_old_databases(
config,
STATE_DATABASE_KIND,
state_database_format_version_in_code().major,
network,
)
}
/// Spawns a task that checks if there are old database folders,
/// and deletes them from the filesystem.
///
/// Iterate over the files and directories in the databases folder and delete if:
/// - The state directory exists.
/// - The entry is a directory.
/// - The `db_kind` directory exists.
/// - The entry in `db_kind` is a directory.
/// - The directory name has a prefix `v`.
/// - The directory name without the prefix can be parsed as an unsigned number.
/// - The parsed number is lower than the hardcoded `DATABASE_FORMAT_VERSION`.
pub fn check_and_delete_old_databases(config: Config) -> JoinHandle<()> {
/// - The parsed number is lower than the `major_version`.
///
/// The network is used to generate the path, then ignored.
/// If `config` is an ephemeral database, no databases are deleted.
///
/// # Panics
///
/// If the path doesn't match the expected `db_kind/major_version/network` format.
pub fn check_and_delete_old_databases(
config: &Config,
db_kind: impl AsRef<str>,
major_version: u64,
network: Network,
) -> JoinHandle<()> {
let current_span = Span::current();
let config = config.clone();
let db_kind = db_kind.as_ref().to_string();
spawn_blocking(move || {
current_span.in_scope(|| {
delete_old_databases(config);
delete_old_databases(config, db_kind, major_version, network);
info!("finished old database version cleanup task");
})
})
@ -212,20 +246,43 @@ pub fn check_and_delete_old_databases(config: Config) -> JoinHandle<()> {
/// Check if there are old database folders and delete them from the filesystem.
///
/// See [`check_and_delete_old_databases`] for details.
fn delete_old_databases(config: Config) {
fn delete_old_databases(config: Config, db_kind: String, major_version: u64, network: Network) {
if config.ephemeral || !config.delete_old_database {
return;
}
info!("checking for old database versions");
info!(db_kind, "checking for old database versions");
let state_dir = config.cache_dir.join("state");
if let Some(state_dir) = read_dir(&state_dir) {
for entry in state_dir.flatten() {
let deleted_state = check_and_delete_database(&config, &entry);
let mut db_path = config.db_path(&db_kind, major_version, network);
// Check and remove the network path.
assert_eq!(
db_path.file_name(),
Some(network.lowercase_name().as_ref()),
"unexpected database network path structure"
);
assert!(db_path.pop());
if let Some(deleted_state) = deleted_state {
info!(?deleted_state, "deleted outdated state directory");
// Check and remove the major version path, we'll iterate over them all below.
assert_eq!(
db_path.file_name(),
Some(format!("v{major_version}").as_ref()),
"unexpected database version path structure"
);
assert!(db_path.pop());
// Check for the correct database kind to iterate within.
assert_eq!(
db_path.file_name(),
Some(db_kind.as_ref()),
"unexpected database kind path structure"
);
if let Some(db_kind_dir) = read_dir(&db_path) {
for entry in db_kind_dir.flatten() {
let deleted_db = check_and_delete_database(&config, major_version, &entry);
if let Some(deleted_db) = deleted_db {
info!(?deleted_db, "deleted outdated {db_kind} database directory");
}
}
}
@ -247,11 +304,15 @@ fn read_dir(dir: &Path) -> Option<ReadDir> {
/// See [`check_and_delete_old_databases`] for details.
///
/// If the directory was deleted, returns its path.
fn check_and_delete_database(config: &Config, entry: &DirEntry) -> Option<PathBuf> {
fn check_and_delete_database(
config: &Config,
major_version: u64,
entry: &DirEntry,
) -> Option<PathBuf> {
let dir_name = parse_dir_name(entry)?;
let version_number = parse_version_number(&dir_name)?;
let dir_major_version = parse_major_version(&dir_name)?;
if version_number >= crate::constants::DATABASE_FORMAT_VERSION {
if dir_major_version >= major_version {
return None;
}
@ -296,10 +357,10 @@ fn parse_dir_name(entry: &DirEntry) -> Option<String> {
None
}
/// Parse the state version number from `dir_name`.
/// Parse the database major version number from `dir_name`.
///
/// Returns `None` if parsing fails, or the directory name is not in the expected format.
fn parse_version_number(dir_name: &str) -> Option<u64> {
fn parse_major_version(dir_name: &str) -> Option<u64> {
dir_name
.strip_prefix('v')
.and_then(|version| version.parse().ok())
@ -307,23 +368,26 @@ fn parse_version_number(dir_name: &str) -> Option<u64> {
// TODO: move these to the format upgrade module
/// Returns the full semantic version of the currently running database format code.
///
/// This is the version implemented by the Zebra code that's currently running,
/// the minor and patch versions on disk can be different.
pub fn database_format_version_in_code() -> Version {
Version::new(
DATABASE_FORMAT_VERSION,
DATABASE_FORMAT_MINOR_VERSION,
DATABASE_FORMAT_PATCH_VERSION,
/// Returns the full semantic version of the on-disk state database, based on its config and network.
pub fn state_database_format_version_on_disk(
config: &Config,
network: Network,
) -> Result<Option<Version>, BoxError> {
database_format_version_on_disk(
config,
STATE_DATABASE_KIND,
state_database_format_version_in_code().major,
network,
)
}
/// Returns the full semantic version of the on-disk database.
/// Returns the full semantic version of the on-disk database, based on its config, kind, major version,
/// and network.
///
/// Typically, the version is read from a version text file.
///
/// If there is an existing on-disk database, but no version file, returns `Ok(Some(major.0.0))`.
/// If there is an existing on-disk database, but no version file,
/// returns `Ok(Some(major_version.0.0))`.
/// (This happens even if the database directory was just newly created.)
///
/// If there is no existing on-disk database, returns `Ok(None)`.
@ -332,12 +396,14 @@ pub fn database_format_version_in_code() -> Version {
/// implemented by the running Zebra code can be different.
pub fn database_format_version_on_disk(
config: &Config,
db_kind: impl AsRef<str>,
major_version: u64,
network: Network,
) -> Result<Option<Version>, BoxError> {
let version_path = config.version_file_path(network);
let db_path = config.db_path(network);
let version_path = config.version_file_path(&db_kind, major_version, network);
let db_path = config.db_path(db_kind, major_version, network);
database_format_version_at_path(&version_path, &db_path)
database_format_version_at_path(&version_path, &db_path, major_version)
}
/// Returns the full semantic version of the on-disk database at `version_path`.
@ -346,6 +412,7 @@ pub fn database_format_version_on_disk(
pub(crate) fn database_format_version_at_path(
version_path: &Path,
db_path: &Path,
major_version: u64,
) -> Result<Option<Version>, BoxError> {
let disk_version_file = match fs::read_to_string(version_path) {
Ok(version) => Some(version),
@ -363,7 +430,7 @@ pub(crate) fn database_format_version_at_path(
.ok_or("invalid database format version file")?;
return Ok(Some(Version::new(
DATABASE_FORMAT_VERSION,
major_version,
minor.parse()?,
patch.parse()?,
)));
@ -374,7 +441,7 @@ pub(crate) fn database_format_version_at_path(
match fs::metadata(db_path) {
// But there is a database on disk, so it has the current major version with no upgrades.
// If the database directory was just newly created, we also return this version.
Ok(_metadata) => Ok(Some(Version::new(DATABASE_FORMAT_VERSION, 0, 0))),
Ok(_metadata) => Ok(Some(Version::new(major_version, 0, 0))),
// There's no version file and no database on disk, so it's a new database.
// It will be created with the current version,
@ -386,15 +453,33 @@ pub(crate) fn database_format_version_at_path(
}
// Hide this destructive method from the public API, except in tests.
pub(crate) use hidden::write_database_format_version_to_disk;
#[allow(unused_imports)]
pub(crate) use hidden::{
write_database_format_version_to_disk, write_state_database_format_version_to_disk,
};
pub(crate) mod hidden {
#![allow(dead_code)]
use super::*;
/// Writes `changed_version` to the on-disk state database after the format is changed.
/// (Or a new database is created.)
///
/// See `write_database_format_version_to_disk()` for details.
pub fn write_state_database_format_version_to_disk(
config: &Config,
changed_version: &Version,
network: Network,
) -> Result<(), BoxError> {
write_database_format_version_to_disk(config, STATE_DATABASE_KIND, changed_version, network)
}
/// Writes `changed_version` to the on-disk database after the format is changed.
/// (Or a new database is created.)
///
/// The database path is based on its kind, `changed_version.major`, and network.
///
/// # Correctness
///
/// This should only be called:
@ -407,22 +492,13 @@ pub(crate) mod hidden {
/// This must only be called while RocksDB has an open database for `config`.
/// Otherwise, multiple Zebra processes could write the version at the same time,
/// corrupting the file.
///
/// # Panics
///
/// If the major versions do not match. (The format is incompatible.)
pub fn write_database_format_version_to_disk(
changed_version: &Version,
config: &Config,
db_kind: impl AsRef<str>,
changed_version: &Version,
network: Network,
) -> Result<(), BoxError> {
let version_path = config.version_file_path(network);
// The major version is already in the directory path.
assert_eq!(
changed_version.major, DATABASE_FORMAT_VERSION,
"tried to do in-place database format change to an incompatible version"
);
let version_path = config.version_file_path(db_kind, changed_version.major, network);
let version = format!("{}.{}", changed_version.minor, changed_version.patch);

View File

@ -6,7 +6,10 @@ use semver::Version;
// For doc comment links
#[allow(unused_imports)]
use crate::config::{self, Config};
use crate::{
config::{self, Config},
constants,
};
pub use zebra_chain::transparent::MIN_TRANSPARENT_COINBASE_MATURITY;
@ -27,6 +30,9 @@ pub use zebra_chain::transparent::MIN_TRANSPARENT_COINBASE_MATURITY;
// TODO: change to HeightDiff
pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
/// The directory name used to distinguish the state database from Zebra's other databases or flat files.
pub const STATE_DATABASE_KIND: &str = "state";
/// The database format major version, incremented each time the on-disk database format has a
/// breaking data format change.
///
@ -38,9 +44,9 @@ pub const MAX_BLOCK_REORG_HEIGHT: u32 = MIN_TRANSPARENT_COINBASE_MATURITY - 1;
/// - we previously added compatibility code, and
/// - it's available in all supported Zebra versions.
///
/// Use [`config::database_format_version_in_code()`] or
/// [`config::database_format_version_on_disk()`] to get the full semantic format version.
pub(crate) const DATABASE_FORMAT_VERSION: u64 = 25;
/// Instead of using this constant directly, use [`constants::state_database_format_version_in_code()`]
/// or [`config::database_format_version_on_disk()`] to get the full semantic format version.
const DATABASE_FORMAT_VERSION: u64 = 25;
/// The database format minor version, incremented each time the on-disk database format has a
/// significant data format change.
@ -49,11 +55,23 @@ pub(crate) const DATABASE_FORMAT_VERSION: u64 = 25;
/// - adding new column families,
/// - changing the format of a column family in a compatible way, or
/// - breaking changes with compatibility code in all supported Zebra versions.
pub(crate) const DATABASE_FORMAT_MINOR_VERSION: u64 = 3;
const DATABASE_FORMAT_MINOR_VERSION: u64 = 3;
/// The database format patch version, incremented each time the on-disk database format has a
/// significant format compatibility fix.
pub(crate) const DATABASE_FORMAT_PATCH_VERSION: u64 = 0;
const DATABASE_FORMAT_PATCH_VERSION: u64 = 0;
/// Returns the full semantic version of the currently running state database format code.
///
/// This is the version implemented by the Zebra code that's currently running,
/// the minor and patch versions on disk can be different.
pub fn state_database_format_version_in_code() -> Version {
Version::new(
DATABASE_FORMAT_VERSION,
DATABASE_FORMAT_MINOR_VERSION,
DATABASE_FORMAT_PATCH_VERSION,
)
}
/// Returns the highest database version that modifies the subtree index format.
///

View File

@ -40,10 +40,10 @@ mod service;
mod tests;
pub use config::{
check_and_delete_old_databases, database_format_version_in_code,
database_format_version_on_disk, Config,
check_and_delete_old_databases, check_and_delete_old_state_databases,
database_format_version_on_disk, state_database_format_version_on_disk, Config,
};
pub use constants::MAX_BLOCK_REORG_HEIGHT;
pub use constants::{state_database_format_version_in_code, MAX_BLOCK_REORG_HEIGHT};
pub use error::{
BoxError, CloneError, CommitSemanticallyVerifiedError, DuplicateNullifierError,
ValidateContextError,
@ -59,6 +59,12 @@ pub use service::{
OutputIndex, OutputLocation, TransactionLocation,
};
#[cfg(feature = "shielded-scan")]
pub use service::finalized_state::{ReadDisk, ZebraDb};
#[cfg(any(test, feature = "proptest-impl", feature = "shielded-scan"))]
pub use service::finalized_state::{DiskWriteBatch, WriteDisk};
#[cfg(feature = "getblocktemplate-rpcs")]
pub use response::GetBlockTemplateChainInfo;
@ -66,12 +72,20 @@ pub use response::GetBlockTemplateChainInfo;
pub use service::{
arbitrary::{populated_state, CHAIN_TIP_UPDATE_WAIT_LIMIT},
chain_tip::{ChainTipBlock, ChainTipSender},
finalized_state::{DiskWriteBatch, MAX_ON_DISK_HEIGHT},
finalized_state::MAX_ON_DISK_HEIGHT,
init_test, init_test_services, ReadStateService,
};
#[cfg(not(any(test, feature = "proptest-impl")))]
#[allow(unused_imports)]
pub(crate) use config::hidden::{
write_database_format_version_to_disk, write_state_database_format_version_to_disk,
};
#[cfg(any(test, feature = "proptest-impl"))]
pub use config::hidden::write_database_format_version_to_disk;
pub use config::hidden::{
write_database_format_version_to_disk, write_state_database_format_version_to_disk,
};
#[cfg(any(test, feature = "proptest-impl"))]
pub use constants::latest_version_for_adding_subtrees;

View File

@ -2,9 +2,8 @@
//!
//! Zebra's database is implemented in 4 layers:
//! - [`FinalizedState`]: queues, validates, and commits blocks, using...
//! - [`ZebraDb`]: reads and writes [`zebra_chain`] types to the database, using...
//! - [`DiskDb`](disk_db::DiskDb): reads and writes format-specific types
//! to the database, using...
//! - [`ZebraDb`]: reads and writes [`zebra_chain`] types to the state database, using...
//! - [`DiskDb`]: reads and writes generic types to any column family in the database, using...
//! - [`disk_format`]: converts types to raw database bytes.
//!
//! These layers allow us to split [`zebra_chain`] types for efficient database storage.
@ -12,8 +11,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
io::{stderr, stdout, Write},
@ -23,6 +22,7 @@ use std::{
use zebra_chain::{block, parallel::tree::NoteCommitmentTrees, parameters::Network};
use crate::{
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
request::{FinalizableBlock, FinalizedBlock, Treestate},
service::{check, QueuedCheckpointVerified},
BoxError, CheckpointVerifiedBlock, CloneError, Config,
@ -38,17 +38,52 @@ mod arbitrary;
#[cfg(test)]
mod tests;
#[allow(unused_imports)]
pub use disk_db::{DiskDb, DiskWriteBatch, WriteDisk};
pub use disk_format::{OutputIndex, OutputLocation, TransactionLocation};
pub use zebra_db::ZebraDb;
#[cfg(feature = "shielded-scan")]
pub use disk_db::ReadDisk;
#[cfg(any(test, feature = "proptest-impl"))]
pub use disk_format::MAX_ON_DISK_HEIGHT;
pub(super) use zebra_db::ZebraDb;
#[cfg(any(test, feature = "proptest-impl"))]
pub use disk_db::DiskWriteBatch;
#[cfg(not(any(test, feature = "proptest-impl")))]
use disk_db::DiskWriteBatch;
/// The column families supported by the running `zebra-state` database code.
///
/// Existing column families that aren't listed here are preserved when the database is opened.
pub const STATE_COLUMN_FAMILIES_IN_CODE: &[&str] = &[
// Blocks
"hash_by_height",
"height_by_hash",
"block_header_by_height",
// Transactions
"tx_by_loc",
"hash_by_tx_loc",
"tx_loc_by_hash",
// Transparent
"balance_by_transparent_addr",
"tx_loc_by_transparent_addr_loc",
"utxo_by_out_loc",
"utxo_loc_by_transparent_addr_loc",
// Sprout
"sprout_nullifiers",
"sprout_anchors",
"sprout_note_commitment_tree",
// Sapling
"sapling_nullifiers",
"sapling_anchors",
"sapling_note_commitment_tree",
"sapling_note_commitment_subtree",
// Orchard
"orchard_nullifiers",
"orchard_anchors",
"orchard_note_commitment_tree",
"orchard_note_commitment_subtree",
// Chain
"history_tree",
"tip_chain_value_pool",
];
/// The finalized part of the chain state, stored in the db.
///
@ -65,9 +100,6 @@ pub struct FinalizedState {
// This configuration cannot be modified after the database is initialized,
// because some clones would have different values.
//
/// The configured network.
network: Network,
/// The configured stop height.
///
/// Commit blocks to the finalized state up to this height, then exit Zebra.
@ -120,11 +152,19 @@ impl FinalizedState {
debug_skip_format_upgrades: bool,
#[cfg(feature = "elasticsearch")] elastic_db: Option<elasticsearch::Elasticsearch>,
) -> Self {
let db = ZebraDb::new(config, network, debug_skip_format_upgrades);
let db = ZebraDb::new(
config,
STATE_DATABASE_KIND,
&state_database_format_version_in_code(),
network,
debug_skip_format_upgrades,
STATE_COLUMN_FAMILIES_IN_CODE
.iter()
.map(ToString::to_string),
);
#[cfg(feature = "elasticsearch")]
let new_state = Self {
network,
debug_stop_at_height: config.debug_stop_at_height.map(block::Height),
db,
elastic_db,
@ -133,7 +173,6 @@ impl FinalizedState {
#[cfg(not(feature = "elasticsearch"))]
let new_state = Self {
network,
debug_stop_at_height: config.debug_stop_at_height.map(block::Height),
db,
};
@ -182,7 +221,7 @@ impl FinalizedState {
/// Returns the configured network for this database.
pub fn network(&self) -> Network {
self.network
self.db.network()
}
/// Commit a checkpoint-verified block to the state.
@ -293,7 +332,7 @@ impl FinalizedState {
// thread, if it shows up in profiles
check::block_commitment_is_valid_for_chain_history(
block.clone(),
self.network,
self.network(),
&history_tree,
)?;
@ -359,9 +398,12 @@ impl FinalizedState {
let finalized_inner_block = finalized.block.clone();
let note_commitment_trees = finalized.treestate.note_commitment_trees.clone();
let result =
self.db
.write_block(finalized, prev_note_commitment_trees, self.network, source);
let result = self.db.write_block(
finalized,
prev_note_commitment_trees,
self.network(),
source,
);
if result.is_ok() {
// Save blocks to elasticsearch if the feature is enabled.
@ -421,7 +463,7 @@ impl FinalizedState {
// less than this number of seconds.
const CLOSE_TO_TIP_SECONDS: i64 = 14400; // 4 hours
let mut blocks_size_to_dump = match self.network {
let mut blocks_size_to_dump = match self.network() {
Network::Mainnet => MAINNET_AWAY_FROM_TIP_BULK_SIZE,
Network::Testnet => TESTNET_AWAY_FROM_TIP_BULK_SIZE,
};
@ -451,7 +493,7 @@ impl FinalizedState {
let rt = tokio::runtime::Runtime::new()
.expect("runtime creation for elasticsearch should not fail.");
let blocks = self.elastic_blocks.clone();
let network = self.network;
let network = self.network();
rt.block_on(async move {
let response = client

View File

@ -7,8 +7,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constants must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
collections::{BTreeMap, HashMap},
@ -22,6 +22,7 @@ use itertools::Itertools;
use rlimit::increase_nofile_limit;
use rocksdb::ReadOptions;
use semver::Version;
use zebra_chain::parameters::Network;
use crate::{
@ -65,6 +66,12 @@ pub struct DiskDb {
// This configuration cannot be modified after the database is initialized,
// because some clones would have different values.
//
/// The configured database kind for this database.
db_kind: String,
/// The format version of the running Zebra code.
format_version_in_code: Version,
/// The configured network for this database.
network: Network,
@ -93,10 +100,6 @@ pub struct DiskDb {
///
/// [`rocksdb::WriteBatch`] is a batched set of database updates,
/// which must be written to the database using `DiskDb::write(batch)`.
//
// TODO: move DiskDb, FinalizedBlock, and the source String into this struct,
// (DiskDb can be cloned),
// and make them accessible via read-only methods
#[must_use = "batches must be written to the database"]
#[derive(Default)]
pub struct DiskWriteBatch {
@ -617,44 +620,18 @@ impl DiskDb {
/// <https://github.com/facebook/rocksdb/wiki/RocksDB-FAQ#configuration-and-tuning>
const MEMTABLE_RAM_CACHE_MEGABYTES: usize = 128;
/// The column families supported by the running database code.
const COLUMN_FAMILIES_IN_CODE: &'static [&'static str] = &[
// Blocks
"hash_by_height",
"height_by_hash",
"block_header_by_height",
// Transactions
"tx_by_loc",
"hash_by_tx_loc",
"tx_loc_by_hash",
// Transparent
"balance_by_transparent_addr",
"tx_loc_by_transparent_addr_loc",
"utxo_by_out_loc",
"utxo_loc_by_transparent_addr_loc",
// Sprout
"sprout_nullifiers",
"sprout_anchors",
"sprout_note_commitment_tree",
// Sapling
"sapling_nullifiers",
"sapling_anchors",
"sapling_note_commitment_tree",
"sapling_note_commitment_subtree",
// Orchard
"orchard_nullifiers",
"orchard_anchors",
"orchard_note_commitment_tree",
"orchard_note_commitment_subtree",
// Chain
"history_tree",
"tip_chain_value_pool",
];
/// Opens or creates the database at `config.path` for `network`,
/// Opens or creates the database at a path based on the kind, major version and network,
/// with the supplied column families, preserving any existing column families,
/// and returns a shared low-level database wrapper.
pub fn new(config: &Config, network: Network) -> DiskDb {
let path = config.db_path(network);
pub fn new(
config: &Config,
db_kind: impl AsRef<str>,
format_version_in_code: &Version,
network: Network,
column_families_in_code: impl IntoIterator<Item = String>,
) -> DiskDb {
let db_kind = db_kind.as_ref();
let path = config.db_path(db_kind, format_version_in_code.major, network);
let db_options = DiskDb::options();
@ -666,9 +643,7 @@ impl DiskDb {
//
// <https://github.com/facebook/rocksdb/wiki/Column-Families#reference>
let column_families_on_disk = DB::list_cf(&db_options, &path).unwrap_or_default();
let column_families_in_code = Self::COLUMN_FAMILIES_IN_CODE
.iter()
.map(ToString::to_string);
let column_families_in_code = column_families_in_code.into_iter();
let column_families = column_families_on_disk
.into_iter()
@ -683,6 +658,8 @@ impl DiskDb {
info!("Opened Zebra state cache at {}", path.display());
let db = DiskDb {
db_kind: db_kind.to_string(),
format_version_in_code: format_version_in_code.clone(),
network,
ephemeral: config.ephemeral,
db: Arc::new(db),
@ -704,6 +681,21 @@ impl DiskDb {
// Accessor methods
/// Returns the configured database kind for this database.
pub fn db_kind(&self) -> String {
self.db_kind.clone()
}
/// Returns the format version of the running code that created this `DiskDb` instance in memory.
pub fn format_version_in_code(&self) -> Version {
self.format_version_in_code.clone()
}
/// Returns the fixed major version for this database.
pub fn major_version(&self) -> u64 {
self.format_version_in_code().major
}
/// Returns the configured network for this database.
pub fn network(&self) -> Network {
self.network

View File

@ -2,8 +2,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::sync::Arc;

View File

@ -2,8 +2,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use zebra_chain::{
block::{self, Height},

View File

@ -2,8 +2,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::collections::BTreeMap;

View File

@ -2,8 +2,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use bincode::Options;

View File

@ -2,8 +2,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{cmp::max, fmt::Debug};

View File

@ -15,17 +15,13 @@ use zebra_chain::{
task::{CheckForPanics, WaitForPanics},
CodeTimer,
},
parameters::Network,
};
use DbFormatChange::*;
use crate::{
config::write_database_format_version_to_disk,
constants::{latest_version_for_adding_subtrees, DATABASE_FORMAT_VERSION},
database_format_version_in_code, database_format_version_on_disk,
constants::latest_version_for_adding_subtrees,
service::finalized_state::{DiskWriteBatch, ZebraDb},
Config,
};
pub(crate) mod add_subtrees;
@ -113,7 +109,9 @@ impl DbFormatChange {
/// Also logs that change at info level.
///
/// If `disk_version` is `None`, Zebra is creating a new database.
pub fn open_database(running_version: Version, disk_version: Option<Version>) -> Self {
pub fn open_database(running_version: &Version, disk_version: Option<Version>) -> Self {
let running_version = running_version.clone();
let Some(disk_version) = disk_version else {
info!(
%running_version,
@ -160,8 +158,8 @@ impl DbFormatChange {
/// This check makes sure the upgrade and new block code produce the same data.
///
/// Also logs the check at info level.
pub fn check_new_blocks() -> Self {
let running_version = database_format_version_in_code();
pub fn check_new_blocks(db: &ZebraDb) -> Self {
let running_version = db.format_version_in_code();
info!(%running_version, "checking new blocks were written in current database format");
CheckNewBlocksCurrent { running_version }
@ -219,14 +217,12 @@ impl DbFormatChange {
/// Launch a `std::thread` that applies this format change to the database,
/// then continues running to perform periodic format checks.
///
/// `initial_tip_height` is the database height when it was opened, and `upgrade_db` is the
/// `initial_tip_height` is the database height when it was opened, and `db` is the
/// database instance to upgrade or check.
pub fn spawn_format_change(
self,
config: Config,
network: Network,
db: ZebraDb,
initial_tip_height: Option<Height>,
upgrade_db: ZebraDb,
) -> DbFormatChangeThreadHandle {
// # Correctness
//
@ -237,13 +233,7 @@ impl DbFormatChange {
let span = Span::current();
let update_task = thread::spawn(move || {
span.in_scope(move || {
self.format_change_run_loop(
config,
network,
initial_tip_height,
upgrade_db,
cancel_receiver,
)
self.format_change_run_loop(db, initial_tip_height, cancel_receiver)
})
});
@ -264,21 +254,13 @@ impl DbFormatChange {
/// newly added blocks matches the current format. It will run until it is cancelled or panics.
fn format_change_run_loop(
self,
config: Config,
network: Network,
db: ZebraDb,
initial_tip_height: Option<Height>,
upgrade_db: ZebraDb,
cancel_receiver: mpsc::Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
self.run_format_change_or_check(
&config,
network,
initial_tip_height,
&upgrade_db,
&cancel_receiver,
)?;
self.run_format_change_or_check(&db, initial_tip_height, &cancel_receiver)?;
let Some(debug_validity_check_interval) = config.debug_validity_check_interval else {
let Some(debug_validity_check_interval) = db.config().debug_validity_check_interval else {
return Ok(());
};
@ -292,11 +274,9 @@ impl DbFormatChange {
return Err(CancelFormatChange);
}
Self::check_new_blocks().run_format_change_or_check(
&config,
network,
Self::check_new_blocks(&db).run_format_change_or_check(
&db,
initial_tip_height,
&upgrade_db,
&cancel_receiver,
)?;
}
@ -306,24 +286,16 @@ impl DbFormatChange {
#[allow(clippy::unwrap_in_result)]
pub(crate) fn run_format_change_or_check(
&self,
config: &Config,
network: Network,
db: &ZebraDb,
initial_tip_height: Option<Height>,
upgrade_db: &ZebraDb,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
match self {
// Perform any required upgrades, then mark the state as upgraded.
Upgrade { .. } => self.apply_format_upgrade(
config,
network,
initial_tip_height,
upgrade_db,
cancel_receiver,
)?,
Upgrade { .. } => self.apply_format_upgrade(db, initial_tip_height, cancel_receiver)?,
NewlyCreated { .. } => {
Self::mark_as_newly_created(config, network);
Self::mark_as_newly_created(db);
}
Downgrade { .. } => {
@ -332,7 +304,7 @@ impl DbFormatChange {
// At the start of a format downgrade, the database must be marked as partially or
// fully downgraded. This lets newer Zebra versions know that some blocks with older
// formats have been added to the database.
Self::mark_as_downgraded(config, network);
Self::mark_as_downgraded(db);
// Older supported versions just assume they can read newer formats,
// because they can't predict all changes a newer Zebra version could make.
@ -373,10 +345,10 @@ impl DbFormatChange {
// (unless a future upgrade breaks these format checks)
// - re-opening the current version should be valid, regardless of whether the upgrade
// or new block code created the format (or any combination).
Self::format_validity_checks_detailed(upgrade_db, cancel_receiver)?.unwrap_or_else(|_| {
Self::format_validity_checks_detailed(db, cancel_receiver)?.unwrap_or_else(|_| {
panic!(
"unexpected invalid database format: delete and re-sync the database at '{:?}'",
upgrade_db.path()
db.path()
)
});
@ -392,6 +364,8 @@ impl DbFormatChange {
Ok(())
}
// TODO: Move state-specific upgrade code to a finalized_state/* module.
/// Apply any required format updates to the database.
/// Format changes should be launched in an independent `std::thread`.
///
@ -405,10 +379,8 @@ impl DbFormatChange {
#[allow(clippy::unwrap_in_result)]
fn apply_format_upgrade(
&self,
config: &Config,
network: Network,
initial_tip_height: Option<Height>,
db: &ZebraDb,
initial_tip_height: Option<Height>,
cancel_receiver: &mpsc::Receiver<CancelFormatChange>,
) -> Result<(), CancelFormatChange> {
let Upgrade {
@ -430,7 +402,7 @@ impl DbFormatChange {
"marking empty database as upgraded"
);
Self::mark_as_upgraded_to(&database_format_version_in_code(), config, network);
Self::mark_as_upgraded_to(db, newer_running_version);
info!(
%newer_running_version,
@ -510,7 +482,7 @@ impl DbFormatChange {
// Mark the database as upgraded. Zebra won't repeat the upgrade anymore once the
// database is marked, so the upgrade MUST be complete at this point.
Self::mark_as_upgraded_to(&version_for_pruning_trees, config, network);
Self::mark_as_upgraded_to(db, &version_for_pruning_trees);
timer.finish(module_path!(), line!(), "deduplicate trees upgrade");
}
@ -538,7 +510,7 @@ impl DbFormatChange {
// Mark the database as upgraded. Zebra won't repeat the upgrade anymore once the
// database is marked, so the upgrade MUST be complete at this point.
Self::mark_as_upgraded_to(&latest_version_for_adding_subtrees, config, network);
Self::mark_as_upgraded_to(db, &latest_version_for_adding_subtrees);
timer.finish(module_path!(), line!(), "add subtrees upgrade");
}
@ -564,7 +536,7 @@ impl DbFormatChange {
// Mark the database as upgraded. Zebra won't repeat the upgrade anymore once the
// database is marked, so the upgrade MUST be complete at this point.
Self::mark_as_upgraded_to(&version_for_tree_keys_and_caches, config, network);
Self::mark_as_upgraded_to(db, &version_for_tree_keys_and_caches);
timer.finish(module_path!(), line!(), "tree keys and caches upgrade");
}
@ -721,12 +693,13 @@ impl DbFormatChange {
/// # Panics
///
/// If the format should not have been upgraded, because the database is not newly created.
fn mark_as_newly_created(config: &Config, network: Network) {
let disk_version = database_format_version_on_disk(config, network)
fn mark_as_newly_created(db: &ZebraDb) {
let running_version = db.format_version_in_code();
let disk_version = db
.format_version_on_disk()
.expect("unable to read database format version file path");
let running_version = database_format_version_in_code();
let default_new_version = Some(Version::new(DATABASE_FORMAT_VERSION, 0, 0));
let default_new_version = Some(Version::new(running_version.major, 0, 0));
// The database version isn't empty any more, because we've created the RocksDB database
// and acquired its lock. (If it is empty, we have a database locking bug.)
@ -737,7 +710,7 @@ impl DbFormatChange {
running: {running_version}"
);
write_database_format_version_to_disk(&running_version, config, network)
db.update_format_version_on_disk(&running_version)
.expect("unable to write database format version file to disk");
info!(
@ -768,11 +741,12 @@ impl DbFormatChange {
/// - older or the same as the disk version
/// (multiple upgrades to the same version are not allowed)
/// - greater than the running version (that's a logic bug)
fn mark_as_upgraded_to(format_upgrade_version: &Version, config: &Config, network: Network) {
let disk_version = database_format_version_on_disk(config, network)
fn mark_as_upgraded_to(db: &ZebraDb, format_upgrade_version: &Version) {
let running_version = db.format_version_in_code();
let disk_version = db
.format_version_on_disk()
.expect("unable to read database format version file")
.expect("tried to upgrade a newly created database");
let running_version = database_format_version_in_code();
assert!(
running_version > disk_version,
@ -798,7 +772,7 @@ impl DbFormatChange {
running: {running_version}"
);
write_database_format_version_to_disk(format_upgrade_version, config, network)
db.update_format_version_on_disk(format_upgrade_version)
.expect("unable to write database format version file to disk");
info!(
@ -826,11 +800,12 @@ impl DbFormatChange {
/// If the state is newly created, because the running version should be the same.
///
/// Multiple downgrades are allowed, because they all downgrade to the same running version.
fn mark_as_downgraded(config: &Config, network: Network) {
let disk_version = database_format_version_on_disk(config, network)
fn mark_as_downgraded(db: &ZebraDb) {
let running_version = db.format_version_in_code();
let disk_version = db
.format_version_on_disk()
.expect("unable to read database format version file")
.expect("can't downgrade a newly created database");
let running_version = database_format_version_in_code();
assert!(
disk_version >= running_version,
@ -839,7 +814,7 @@ impl DbFormatChange {
running: {running_version}"
);
write_database_format_version_to_disk(&running_version, config, network)
db.update_format_version_on_disk(&running_version)
.expect("unable to write database format version file to disk");
info!(

View File

@ -6,18 +6,19 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
path::Path,
sync::{mpsc, Arc},
};
use semver::Version;
use zebra_chain::parameters::Network;
use crate::{
config::{database_format_version_in_code, database_format_version_on_disk},
config::database_format_version_on_disk,
service::finalized_state::{
disk_db::DiskDb,
disk_format::{
@ -25,7 +26,7 @@ use crate::{
upgrade::{DbFormatChange, DbFormatChangeThreadHandle},
},
},
Config,
write_database_format_version_to_disk, BoxError, Config,
};
pub mod block;
@ -37,7 +38,7 @@ pub mod transparent;
#[cfg(any(test, feature = "proptest-impl"))]
pub mod arbitrary;
/// Wrapper struct to ensure high-level typed database access goes through the correct API.
/// Wrapper struct to ensure high-level `zebra-state` database access goes through the correct API.
///
/// `rocksdb` allows concurrent writes through a shared reference,
/// so database instances are cloneable. When the final clone is dropped,
@ -51,11 +52,13 @@ pub struct ZebraDb {
//
/// The configuration for the database.
//
// TODO: use the database and version paths instead, and refactor the upgrade code to use paths
// TODO: move the config to DiskDb
config: Arc<Config>,
/// Should format upgrades and format checks be skipped for this instance?
/// Only used in test code.
//
// TODO: move this to DiskDb
debug_skip_format_upgrades: bool,
// Owned State
@ -68,6 +71,8 @@ pub struct ZebraDb {
///
/// This field should be dropped before the database field, so the format upgrade task is
/// cancelled before the database is dropped. This helps avoid some kinds of deadlocks.
//
// TODO: move the generic upgrade code and fields to DiskDb
format_change_handle: Option<DbFormatChangeThreadHandle>,
/// The inner low-level database wrapper for the RocksDB database.
@ -75,18 +80,32 @@ pub struct ZebraDb {
}
impl ZebraDb {
/// Opens or creates the database at `config.path` for `network`,
/// Opens or creates the database at a path based on the kind, major version and network,
/// with the supplied column families, preserving any existing column families,
/// and returns a shared high-level typed database wrapper.
///
/// If `debug_skip_format_upgrades` is true, don't do any format upgrades or format checks.
/// This argument is only used when running tests, it is ignored in production code.
pub fn new(config: &Config, network: Network, debug_skip_format_upgrades: bool) -> ZebraDb {
let running_version = database_format_version_in_code();
let disk_version = database_format_version_on_disk(config, network)
.expect("unable to read database format version file");
//
// TODO: rename to StateDb and remove the db_kind and column_families_in_code arguments
pub fn new(
config: &Config,
db_kind: impl AsRef<str>,
format_version_in_code: &Version,
network: Network,
debug_skip_format_upgrades: bool,
column_families_in_code: impl IntoIterator<Item = String>,
) -> ZebraDb {
let disk_version = database_format_version_on_disk(
config,
&db_kind,
format_version_in_code.major,
network,
)
.expect("unable to read database format version file");
// Log any format changes before opening the database, in case opening fails.
let format_change = DbFormatChange::open_database(running_version, disk_version);
let format_change = DbFormatChange::open_database(format_version_in_code, disk_version);
// Open the database and do initial checks.
let mut db = ZebraDb {
@ -97,21 +116,22 @@ impl ZebraDb {
// changes to the default database version. Then we set the correct version in the
// upgrade thread. We need to do the version change in this order, because the version
// file can only be changed while we hold the RocksDB database lock.
db: DiskDb::new(config, network),
db: DiskDb::new(
config,
db_kind,
format_version_in_code,
network,
column_families_in_code,
),
};
db.spawn_format_change(config, network, format_change);
db.spawn_format_change(format_change);
db
}
/// Launch any required format changes or format checks, and store their thread handle.
pub fn spawn_format_change(
&mut self,
config: &Config,
network: Network,
format_change: DbFormatChange,
) {
pub fn spawn_format_change(&mut self, format_change: DbFormatChange) {
// Always do format upgrades & checks in production code.
if cfg!(test) && self.debug_skip_format_upgrades {
return;
@ -128,18 +148,59 @@ impl ZebraDb {
// TODO:
// - should debug_stop_at_height wait for the upgrade task to finish?
// - if needed, make upgrade_db into a FinalizedState,
// or move the FinalizedState methods needed for upgrades to ZebraDb.
let format_change_handle = format_change.spawn_format_change(
config.clone(),
network,
initial_tip_height,
upgrade_db,
);
let format_change_handle =
format_change.spawn_format_change(upgrade_db, initial_tip_height);
self.format_change_handle = Some(format_change_handle);
}
/// Returns config for this database.
pub fn config(&self) -> &Config {
&self.config
}
/// Returns the configured database kind for this database.
pub fn db_kind(&self) -> String {
self.db.db_kind()
}
/// Returns the format version of the running code that created this `ZebraDb` instance in memory.
pub fn format_version_in_code(&self) -> Version {
self.db.format_version_in_code()
}
/// Returns the fixed major version for this database.
pub fn major_version(&self) -> u64 {
self.db.major_version()
}
/// Returns the format version of this database on disk.
///
/// See `database_format_version_on_disk()` for details.
pub fn format_version_on_disk(&self) -> Result<Option<Version>, BoxError> {
database_format_version_on_disk(
self.config(),
self.db_kind(),
self.major_version(),
self.network(),
)
}
/// Updates the format of this database on disk to the suppled version.
///
/// See `write_database_format_version_to_disk()` for details.
pub(crate) fn update_format_version_on_disk(
&self,
new_version: &Version,
) -> Result<(), BoxError> {
write_database_format_version_to_disk(
self.config(),
self.db_kind(),
new_version,
self.network(),
)
}
/// Returns the configured network for this database.
pub fn network(&self) -> Network {
self.db.network()
@ -191,8 +252,13 @@ impl ZebraDb {
// which would then make unrelated PRs fail when Zebra starts up.
// If the upgrade has completed, or we've done a downgrade, check the state is valid.
let disk_version = database_format_version_on_disk(&self.config, self.network())
.expect("unexpected invalid or unreadable database version file");
let disk_version = database_format_version_on_disk(
&self.config,
self.db_kind(),
self.major_version(),
self.network(),
)
.expect("unexpected invalid or unreadable database version file");
if let Some(disk_version) = disk_version {
// We need to keep the cancel handle until the format check has finished,
@ -201,14 +267,12 @@ impl ZebraDb {
// We block here because the checks are quick and database validity is
// consensus-critical.
if disk_version >= database_format_version_in_code() {
DbFormatChange::check_new_blocks()
if disk_version >= self.db.format_version_in_code() {
DbFormatChange::check_new_blocks(self)
.run_format_change_or_check(
&self.config,
self.network(),
// This argument is not used by the format check.
None,
self,
// The initial tip height is not used by the new blocks format check.
None,
&never_cancel_receiver,
)
.expect("cancel handle is never used");

View File

@ -6,8 +6,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
collections::{BTreeMap, HashMap, HashSet},

View File

@ -26,8 +26,9 @@ use zebra_chain::{
use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS};
use crate::{
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
request::{FinalizedBlock, Treestate},
service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb},
service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE},
CheckpointVerifiedBlock, Config,
};
@ -80,9 +81,14 @@ fn test_block_db_round_trip_with(
let state = ZebraDb::new(
&Config::ephemeral(),
STATE_DATABASE_KIND,
&state_database_format_version_in_code(),
network,
// The raw database accesses in this test create invalid database formats.
true,
STATE_COLUMN_FAMILIES_IN_CODE
.iter()
.map(ToString::to_string),
);
// Check that each block round-trips to the database

View File

@ -8,8 +8,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{borrow::Borrow, collections::HashMap, sync::Arc};

View File

@ -9,8 +9,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
collections::{BTreeMap, HashMap},

View File

@ -8,8 +8,8 @@
//!
//! # Correctness
//!
//! The [`crate::constants::DATABASE_FORMAT_VERSION`] constant must
//! be incremented each time the database format (column, serialization, etc) changes.
//! [`crate::constants::state_database_format_version_in_code()`] must be incremented
//! each time the database format (column, serialization, etc) changes.
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},

View File

@ -18,10 +18,11 @@ use zebra_test::{
};
use crate::{
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
init_test_services, populated_state,
response::MinedTx,
service::{
finalized_state::{DiskWriteBatch, ZebraDb},
finalized_state::{DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE},
non_finalized_state::Chain,
read::{orchard_subtrees, sapling_subtrees},
},
@ -122,7 +123,8 @@ async fn test_read_subtrees() -> Result<()> {
// Prepare the finalized state.
let db = {
let db = ZebraDb::new(&Config::ephemeral(), Mainnet, true);
let db = new_ephemeral_db();
let db_subtrees = db_height_range.enumerate().map(dummy_subtree);
for db_subtree in db_subtrees {
let mut db_batch = DiskWriteBatch::new();
@ -206,7 +208,8 @@ async fn test_sapling_subtrees() -> Result<()> {
// Prepare the finalized state.
let db_subtree = NoteCommitmentSubtree::new(0, Height(1), dummy_subtree_root);
let db = ZebraDb::new(&Config::ephemeral(), Mainnet, true);
let db = new_ephemeral_db();
let mut db_batch = DiskWriteBatch::new();
db_batch.insert_sapling_subtree(&db, &db_subtree);
db.write(db_batch)
@ -271,7 +274,8 @@ async fn test_orchard_subtrees() -> Result<()> {
// Prepare the finalized state.
let db_subtree = NoteCommitmentSubtree::new(0, Height(1), dummy_subtree_root);
let db = ZebraDb::new(&Config::ephemeral(), Mainnet, true);
let db = new_ephemeral_db();
let mut db_batch = DiskWriteBatch::new();
db_batch.insert_orchard_subtree(&db, &db_subtree);
db.write(db_batch)
@ -361,3 +365,17 @@ where
{
index == &subtree.index && subtree_data == &subtree.into_data()
}
/// Returns a new ephemeral database with no consistency checks.
fn new_ephemeral_db() -> ZebraDb {
ZebraDb::new(
&Config::ephemeral(),
STATE_DATABASE_KIND,
&state_database_format_version_in_code(),
Mainnet,
true,
STATE_COLUMN_FAMILIES_IN_CODE
.iter()
.map(ToString::to_string),
)
}

View File

@ -16,7 +16,8 @@ use semver::{BuildMetadata, Version};
use zebra_network::constants::PORT_IN_USE_ERROR;
use zebra_state::{
constants::LOCK_FILE_ERROR, database_format_version_in_code, database_format_version_on_disk,
constants::LOCK_FILE_ERROR, state_database_format_version_in_code,
state_database_format_version_on_disk,
};
use crate::{
@ -267,7 +268,7 @@ impl Application for ZebradApp {
// reads state disk version file, doesn't open RocksDB database
let disk_db_version =
match database_format_version_on_disk(&config.state, config.network.network) {
match state_database_format_version_on_disk(&config.state, config.network.network) {
Ok(Some(version)) => version.to_string(),
// This "version" is specially formatted to match a relaxed version regex in CI
Ok(None) => "creating.new.database".to_string(),
@ -286,7 +287,7 @@ impl Application for ZebradApp {
// code constant
(
"running state version",
database_format_version_in_code().to_string(),
state_database_format_version_in_code().to_string(),
),
// state disk file, doesn't open database
("initial disk state version", disk_db_version),

View File

@ -249,8 +249,10 @@ impl StartCmd {
);
info!("spawning delete old databases task");
let mut old_databases_task_handle =
zebra_state::check_and_delete_old_databases(config.state.clone());
let mut old_databases_task_handle = zebra_state::check_and_delete_old_state_databases(
&config.state,
config.network.network,
);
info!("spawning progress logging task");
let progress_task_handle = tokio::spawn(

View File

@ -159,7 +159,7 @@ use zebra_chain::{
};
use zebra_network::constants::PORT_IN_USE_ERROR;
use zebra_node_services::rpc_client::RpcRequestClient;
use zebra_state::{constants::LOCK_FILE_ERROR, database_format_version_in_code};
use zebra_state::{constants::LOCK_FILE_ERROR, state_database_format_version_in_code};
use zebra_test::{
args,
@ -1856,7 +1856,7 @@ fn lightwalletd_integration_test(test_type: TestType) -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
[format!(
"Opened RPC endpoint at {}",
zebra_rpc_address.expect("lightwalletd test must have RPC port")
@ -1866,7 +1866,7 @@ fn lightwalletd_integration_test(test_type: TestType) -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
}
@ -1978,7 +1978,7 @@ fn lightwalletd_integration_test(test_type: TestType) -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
}
@ -2004,7 +2004,7 @@ fn lightwalletd_integration_test(test_type: TestType) -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
}
@ -2192,7 +2192,7 @@ fn zebra_state_conflict() -> Result<()> {
dir_conflict_full.push("state");
dir_conflict_full.push(format!(
"v{}",
zebra_state::database_format_version_in_code().major,
zebra_state::state_database_format_version_in_code().major,
));
dir_conflict_full.push(config.network.network.to_string().to_lowercase());
format!(
@ -2331,8 +2331,8 @@ async fn fully_synced_rpc_test() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn delete_old_databases() -> Result<()> {
#[test]
fn delete_old_databases() -> Result<()> {
use std::fs::{canonicalize, create_dir};
let _init_guard = zebra_test::init();
@ -2379,7 +2379,7 @@ async fn delete_old_databases() -> Result<()> {
// inside dir was deleted
child.expect_stdout_line_matches(format!(
"deleted outdated state directory deleted_state={canonicalized_inside_dir:?}"
"deleted outdated state database directory.*deleted_db.*=.*{canonicalized_inside_dir:?}"
))?;
assert!(!inside_dir.as_path().exists());
@ -2526,7 +2526,7 @@ async fn new_state_format() -> Result<()> {
/// (or just add a delay during tests)
#[tokio::test]
async fn update_state_format() -> Result<()> {
let mut fake_version = database_format_version_in_code();
let mut fake_version = state_database_format_version_in_code();
fake_version.minor = 0;
fake_version.patch = 0;
@ -2543,7 +2543,7 @@ async fn update_state_format() -> Result<()> {
/// Future version compatibility is a best-effort attempt, this test can be disabled if it fails.
#[tokio::test]
async fn downgrade_state_format() -> Result<()> {
let mut fake_version = database_format_version_in_code();
let mut fake_version = state_database_format_version_in_code();
fake_version.minor = u16::MAX.into();
fake_version.patch = 0;
@ -2625,13 +2625,17 @@ async fn state_format_test(
.zebrad_config(test_name, false, Some(dir.path()), network)
.expect("already checked config")?;
zebra_state::write_database_format_version_to_disk(fake_version, &config.state, network)
.expect("can't write fake database version to disk");
zebra_state::write_state_database_format_version_to_disk(
&config.state,
fake_version,
network,
)
.expect("can't write fake database version to disk");
// Give zebra_state enough time to actually write the database version to disk.
tokio::time::sleep(Duration::from_secs(1)).await;
let running_version = database_format_version_in_code();
let running_version = state_database_format_version_in_code();
match fake_version.cmp(&running_version) {
Ordering::Less => expect_older_version = true,
@ -2737,7 +2741,7 @@ async fn fully_synced_rpc_z_getsubtreesbyindex_snapshot_test() -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
@ -2800,10 +2804,10 @@ async fn fully_synced_rpc_z_getsubtreesbyindex_snapshot_test() -> Result<()> {
Ok(())
}
#[cfg(feature = "zebra-scan")]
/// Test that the scanner gets started when the node starts.
#[tokio::test]
async fn scan_task_starts() -> Result<()> {
#[cfg(feature = "zebra-scan")]
#[test]
fn scan_task_starts() -> Result<()> {
use indexmap::IndexMap;
const ZECPAGES_VIEWING_KEY: &str = "zxviews1q0duytgcqqqqpqre26wkl45gvwwwd706xw608hucmvfalr759ejwf7qshjf5r9aa7323zulvz6plhttp5mltqcgs9t039cx2d09mgq05ts63n8u35hyv6h9nc9ctqqtue2u7cer2mqegunuulq2luhq3ywjcz35yyljewa4mgkgjzyfwh6fr6jd0dzd44ghk0nxdv2hnv4j5nxfwv24rwdmgllhe0p8568sgqt9ckt02v2kxf5ahtql6s0ltjpkckw8gtymxtxuu9gcr0swvz";

View File

@ -20,7 +20,7 @@ use zebra_chain::{
};
use zebra_consensus::MAX_CHECKPOINT_HEIGHT_GAP;
use zebra_node_services::rpc_client::RpcRequestClient;
use zebra_state::database_format_version_in_code;
use zebra_state::state_database_format_version_in_code;
use zebra_test::{
args,
command::{Arguments, TestDirExt, NO_MATCHES_REGEX_ITER},
@ -98,7 +98,7 @@ pub async fn run(network: Network) -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
}

View File

@ -43,7 +43,7 @@ use zebra_chain::{
parameters::NetworkUpgrade::{Nu5, Sapling},
serialization::ZcashDeserializeInto,
};
use zebra_state::database_format_version_in_code;
use zebra_state::state_database_format_version_in_code;
use crate::common::{
cached_state::{
@ -122,7 +122,7 @@ pub async fn run() -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
[format!("Opened RPC endpoint at {zebra_rpc_address}")],
)?;
}
@ -159,7 +159,7 @@ pub async fn run() -> Result<()> {
wait_for_state_version_upgrade(
&mut zebrad,
&state_version_message,
database_format_version_in_code(),
state_database_format_version_in_code(),
None,
)?;
}