Page MenuHomePhabricator

D13337.id38581.diff
No OneTemporary

D13337.id38581.diff

diff --git a/chronik/chronik-db/src/db.rs b/chronik/chronik-db/src/db.rs
--- a/chronik/chronik-db/src/db.rs
+++ b/chronik/chronik-db/src/db.rs
@@ -13,10 +13,11 @@
use rocksdb::{ColumnFamilyDescriptor, IteratorMode};
use thiserror::Error;
-use crate::io::BlockWriter;
+use crate::io::{BlockWriter, MetadataWriter};
// All column family names used by Chronik should be defined here
pub(crate) const CF_BLK: &str = "blk";
+pub(crate) const CF_META: &str = "meta";
pub(crate) type CF = rocksdb::ColumnFamily;
@@ -47,6 +48,7 @@
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let mut cfs = Vec::new();
BlockWriter::add_cfs(&mut cfs);
+ MetadataWriter::add_cfs(&mut cfs);
Self::open_with_cfs(path, cfs)
}
diff --git a/chronik/chronik-db/src/io/metadata.rs b/chronik/chronik-db/src/io/metadata.rs
new file mode 100644
--- /dev/null
+++ b/chronik/chronik-db/src/io/metadata.rs
@@ -0,0 +1,86 @@
+// Copyright (c) 2023 The Bitcoin developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+use abc_rust_error::Result;
+use rocksdb::ColumnFamilyDescriptor;
+
+use crate::{
+ db::{Db, CF, CF_META},
+ ser::{db_deserialize, db_serialize},
+};
+
+/// Type for the version of the database, to let us know when we're outdated.
+pub type SchemaVersion = u64;
+
+const FIELD_SCHEMA_VERSION: &[u8] = b"SCHEMA_VERSION";
+
+/// Write database metadata
+pub struct MetadataWriter<'a> {
+ cf: &'a CF,
+}
+
+/// Read database metadata
+pub struct MetadataReader<'a> {
+ db: &'a Db,
+ cf: &'a CF,
+}
+
+impl<'a> MetadataWriter<'a> {
+ /// Create a writer to the database for metadata
+ pub fn new(db: &'a Db) -> Result<Self> {
+ let cf = db.cf(CF_META)?;
+ Ok(MetadataWriter { cf })
+ }
+
+ /// Update the schema version of the database
+ pub fn update_schema_version(
+ &self,
+ batch: &mut rocksdb::WriteBatch,
+ schema_version: SchemaVersion,
+ ) -> Result<()> {
+ batch.put_cf(
+ self.cf,
+ FIELD_SCHEMA_VERSION,
+ db_serialize(&schema_version)?,
+ );
+ Ok(())
+ }
+
+ pub(crate) fn add_cfs(columns: &mut Vec<ColumnFamilyDescriptor>) {
+ columns.push(ColumnFamilyDescriptor::new(
+ CF_META,
+ rocksdb::Options::default(),
+ ));
+ }
+}
+
+impl std::fmt::Debug for MetadataWriter<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "MetadataWriter {{ ... }}")
+ }
+}
+
+impl<'a> MetadataReader<'a> {
+ /// Create a reader from the database for metadata
+ pub fn new(db: &'a Db) -> Result<Self> {
+ let cf = db.cf(CF_META)?;
+ Ok(MetadataReader { db, cf })
+ }
+
+ /// Read the schema version of the database
+ pub fn schema_version(&self) -> Result<Option<SchemaVersion>> {
+ match self.db.get(self.cf, FIELD_SCHEMA_VERSION)? {
+ Some(ser_schema_version) => {
+ Ok(Some(db_deserialize(&ser_schema_version)?))
+ }
+ None => Ok(None),
+ }
+ }
+}
+
+impl std::fmt::Debug for MetadataReader<'_> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "MetadataReader {{ ... }}")
+ }
+}
diff --git a/chronik/chronik-db/src/io/mod.rs b/chronik/chronik-db/src/io/mod.rs
--- a/chronik/chronik-db/src/io/mod.rs
+++ b/chronik/chronik-db/src/io/mod.rs
@@ -5,5 +5,7 @@
//! Module containing readers and writers for the database used by Chronik.
mod blocks;
+mod metadata;
pub use self::blocks::*;
+pub use self::metadata::*;
diff --git a/chronik/chronik-indexer/src/indexer.rs b/chronik/chronik-indexer/src/indexer.rs
--- a/chronik/chronik-indexer/src/indexer.rs
+++ b/chronik/chronik-indexer/src/indexer.rs
@@ -11,11 +11,16 @@
use chronik_bridge::{ffi, util::expect_unique_ptr};
use chronik_db::{
db::{Db, WriteBatch},
- io::{BlockHeight, BlockReader, BlockWriter, DbBlock},
+ io::{
+ BlockHeight, BlockReader, BlockWriter, DbBlock, MetadataReader,
+ MetadataWriter, SchemaVersion,
+ },
};
use chronik_util::{log, log_chronik};
use thiserror::Error;
+const CURRENT_INDEXER_VERSION: SchemaVersion = 1;
+
/// Params for setting up a [`ChronikIndexer`] instance.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ChronikIndexerParams {
@@ -63,6 +68,23 @@
/// Higher height that exists
exists: BlockHeight,
},
+
+ /// This Chronik instance is outdated
+ #[error(
+ "Chronik outdated: Chronik has version {}, but the database has \
+ version {0}. Upgrade your node to the appropriate version.",
+ CURRENT_INDEXER_VERSION
+ )]
+ ChronikOutdated(SchemaVersion),
+
+ /// Database is outdated
+ #[error(
+ "DB outdated: Chronik has version {}, but the database has version \
+ {0}. -reindex/-chronikreindex to reindex the database to the new \
+ version.",
+ CURRENT_INDEXER_VERSION
+ )]
+ DatabaseOutdated(SchemaVersion),
}
use self::ChronikIndexerError::*;
@@ -83,6 +105,7 @@
}
log_chronik!("Opening Chronik at {}\n", db_path.to_string_lossy());
let db = Db::open(&db_path)?;
+ verify_schema_version(&db)?;
Ok(ChronikIndexer { db })
}
@@ -235,15 +258,42 @@
ChronikBlock { db_block }
}
+fn verify_schema_version(db: &Db) -> Result<()> {
+ let metadata_reader = MetadataReader::new(db)?;
+ let metadata_writer = MetadataWriter::new(db)?;
+ match metadata_reader.schema_version()? {
+ Some(schema_version) => {
+ if schema_version > CURRENT_INDEXER_VERSION {
+ return Err(ChronikOutdated(schema_version).into());
+ }
+ if schema_version < CURRENT_INDEXER_VERSION {
+ return Err(DatabaseOutdated(schema_version).into());
+ }
+ }
+ None => {
+ let mut batch = WriteBatch::default();
+ metadata_writer
+ .update_schema_version(&mut batch, CURRENT_INDEXER_VERSION)?;
+ db.write_batch(batch)?;
+ }
+ }
+ log!("Chronik has version {CURRENT_INDEXER_VERSION}\n");
+ Ok(())
+}
+
#[cfg(test)]
mod tests {
use abc_rust_error::Result;
use bitcoinsuite_core::block::BlockHash;
- use chronik_db::io::{BlockReader, DbBlock};
+ use chronik_db::{
+ db::{Db, WriteBatch},
+ io::{BlockReader, DbBlock, MetadataReader, MetadataWriter},
+ };
use pretty_assertions::assert_eq;
use crate::indexer::{
- ChronikBlock, ChronikIndexer, ChronikIndexerError, ChronikIndexerParams,
+ ChronikBlock, ChronikIndexer, ChronikIndexerError,
+ ChronikIndexerParams, CURRENT_INDEXER_VERSION,
};
#[test]
@@ -307,4 +357,61 @@
Ok(())
}
+
+ #[test]
+ fn test_schema_version() -> Result<()> {
+ let dir = tempdir::TempDir::new("chronik-indexer--schema_version")?;
+ let chronik_path = dir.path().join("indexes").join("chronik");
+ let params = ChronikIndexerParams {
+ datadir_net: dir.path().to_path_buf(),
+ wipe_db: false,
+ };
+
+ // Setting up DB first time sets the schema version
+ ChronikIndexer::setup(params.clone())?;
+ {
+ let db = Db::open(&chronik_path)?;
+ assert_eq!(
+ MetadataReader::new(&db)?.schema_version()?,
+ Some(CURRENT_INDEXER_VERSION)
+ );
+ }
+ // Opening DB again works fine
+ ChronikIndexer::setup(params.clone())?;
+
+ // Override DB schema version to 0
+ {
+ let db = Db::open(&chronik_path)?;
+ let mut batch = WriteBatch::default();
+ MetadataWriter::new(&db)?.update_schema_version(&mut batch, 0)?;
+ db.write_batch(batch)?;
+ }
+ // -> DB too old
+ assert_eq!(
+ ChronikIndexer::setup(params.clone())
+ .unwrap_err()
+ .downcast::<ChronikIndexerError>()?,
+ ChronikIndexerError::DatabaseOutdated(0),
+ );
+
+ // Override DB schema version to CURRENT_INDEXER_VERSION + 1
+ {
+ let db = Db::open(&chronik_path)?;
+ let mut batch = WriteBatch::default();
+ MetadataWriter::new(&db)?.update_schema_version(
+ &mut batch,
+ CURRENT_INDEXER_VERSION + 1,
+ )?;
+ db.write_batch(batch)?;
+ }
+ // -> Chronik too old
+ assert_eq!(
+ ChronikIndexer::setup(params)
+ .unwrap_err()
+ .downcast::<ChronikIndexerError>()?,
+ ChronikIndexerError::ChronikOutdated(CURRENT_INDEXER_VERSION + 1),
+ );
+
+ Ok(())
+ }
}

File Metadata

Mime Type
text/plain
Expires
Tue, May 20, 21:37 (17 h, 51 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5865982
Default Alt Text
D13337.id38581.diff (8 KB)

Event Timeline