From 7f4939b4136a65107df7644d61da015bf1df1560 Mon Sep 17 00:00:00 2001 From: teor Date: Fri, 25 Aug 2023 12:14:02 +1000 Subject: [PATCH 1/2] If there's an existing database with no version file, give it version 25.0.0 --- zebra-state/src/config.rs | 43 +++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/zebra-state/src/config.rs b/zebra-state/src/config.rs index 69020061ff3..bc3592b1fd4 100644 --- a/zebra-state/src/config.rs +++ b/zebra-state/src/config.rs @@ -318,25 +318,42 @@ pub fn database_format_version_on_disk( ) -> Result, BoxError> { let version_path = config.version_file_path(network); - let version = match fs::read_to_string(version_path) { - Ok(version) => version, + let disk_version_file = match fs::read_to_string(version_path) { + Ok(version) => Some(version), Err(e) if e.kind() == ErrorKind::NotFound => { - // If the version file doesn't exist, don't guess the version. - // (It will end up being the version in code, once the database is created.) - return Ok(None); + // If the version file doesn't exist, don't guess the version yet. + None } Err(e) => Err(e)?, }; - let (minor, patch) = version - .split_once('.') - .ok_or("invalid database format version file")?; + // The database has a version file on disk + if let Some(version) = disk_version_file { + let (minor, patch) = version + .split_once('.') + .ok_or("invalid database format version file")?; + + return Ok(Some(Version::new( + DATABASE_FORMAT_VERSION, + minor.parse()?, + patch.parse()?, + ))); + } - Ok(Some(Version::new( - DATABASE_FORMAT_VERSION, - minor.parse()?, - patch.parse()?, - ))) + let db_path = config.db_path(network); + + // There's no version file on disk, so we need to guess the version + // based on the database content + match fs::metadata(db_path) { + // But there is a database on disk, so it has the current major version with no upgrades + Ok(_metadata) => Ok(Some(Version::new(DATABASE_FORMAT_VERSION, 0, 0))), + Err(e) if e.kind() == ErrorKind::NotFound => { + // There's no version file and no database on disk, so it's a new database. + // It will be created with the current version. + Ok(None) + } + Err(e) => Err(e)?, + } } /// Writes `changed_version` to the on-disk database after the format is changed. From c442321764d5d90a2af0d4ccd4df20ecc9ae5f30 Mon Sep 17 00:00:00 2001 From: teor Date: Mon, 28 Aug 2023 08:38:33 +1000 Subject: [PATCH 2/2] Creating the RocksDB database makes a temporary change to the default database version --- zebra-state/src/config.rs | 20 +++++++++++++------ .../finalized_state/disk_format/upgrade.rs | 7 ++++++- .../src/service/finalized_state/zebra_db.rs | 4 ++++ 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/zebra-state/src/config.rs b/zebra-state/src/config.rs index bc3592b1fd4..3e305dc2670 100644 --- a/zebra-state/src/config.rs +++ b/zebra-state/src/config.rs @@ -308,6 +308,12 @@ pub fn database_format_version_in_code() -> Version { } /// Returns the full semantic version of the on-disk database. +/// +/// 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))`. +/// (This happens even if the database directory was just newly created.) +/// /// If there is no existing on-disk database, returns `Ok(None)`. /// /// This is the format of the data on disk, the minor and patch versions @@ -345,13 +351,15 @@ pub fn database_format_version_on_disk( // There's no version file on disk, so we need to guess the version // based on the database content match fs::metadata(db_path) { - // But there is a database on disk, so it has the current major version with no upgrades + // 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))), - Err(e) if e.kind() == ErrorKind::NotFound => { - // There's no version file and no database on disk, so it's a new database. - // It will be created with the current version. - Ok(None) - } + + // There's no version file and no database on disk, so it's a new database. + // It will be created with the current version, + // but temporarily return the default version above until the version file is written. + Err(e) if e.kind() == ErrorKind::NotFound => Ok(None), + Err(e) => Err(e)?, } } 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 6a813a5e7b7..929b0299221 100644 --- a/zebra-state/src/service/finalized_state/disk_format/upgrade.rs +++ b/zebra-state/src/service/finalized_state/disk_format/upgrade.rs @@ -22,6 +22,7 @@ use DbFormatChange::*; use crate::{ config::write_database_format_version_to_disk, + constants::DATABASE_FORMAT_VERSION, database_format_version_in_code, database_format_version_on_disk, service::finalized_state::{DiskWriteBatch, ZebraDb}, Config, @@ -478,8 +479,12 @@ impl DbFormatChange { .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)); + + // 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.) assert_eq!( - disk_version, None, + disk_version, default_new_version, "can't overwrite the format version in an existing database:\n\ disk: {disk_version:?}\n\ running: {running_version}" diff --git a/zebra-state/src/service/finalized_state/zebra_db.rs b/zebra-state/src/service/finalized_state/zebra_db.rs index 63decfe10db..859ab80a654 100644 --- a/zebra-state/src/service/finalized_state/zebra_db.rs +++ b/zebra-state/src/service/finalized_state/zebra_db.rs @@ -71,6 +71,10 @@ impl ZebraDb { // Open the database and do initial checks. let mut db = ZebraDb { format_change_handle: None, + // After the database directory is created, a newly created database temporarily + // 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), };