fix(db): use the correct state version for databases without a state version file (#7385)

* If there's an existing database with no version file, give it version 25.0.0

* Creating the RocksDB database makes a temporary change to the default database version
This commit is contained in:
teor 2023-08-29 03:01:01 +10:00 committed by GitHub
parent 798b271279
commit c116cff5f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 14 deletions

View File

@ -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
@ -318,25 +324,44 @@ pub fn database_format_version_on_disk(
) -> Result<Option<Version>, 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)?,
};
// 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")?;
Ok(Some(Version::new(
return 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.
// If the database directory was just newly created, we also return this version.
Ok(_metadata) => Ok(Some(Version::new(DATABASE_FORMAT_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,
// 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)?,
}
}
/// Writes `changed_version` to the on-disk database after the format is changed.

View File

@ -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}"

View File

@ -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),
};