From d7af2f3930e6531e5d836c853195ed614efc6c73 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 27 Mar 2026 09:17:29 -0400 Subject: [PATCH 1/2] feat(hot): add queue_db_init and auto-create tables on RW open Add HotKvWrite::queue_db_init default method that creates all 9 standard hot storage tables in a single call. Mirror the cold-mdbx pattern by auto-creating tables when DatabaseEnv is opened in read-write mode. Simplify test_utils to rely on automatic table creation instead of manual queue_create calls. Closes ENG-2096 Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.toml | 16 +++++++------- crates/hot-mdbx/src/lib.rs | 16 +++++++++++++- crates/hot-mdbx/src/test_utils.rs | 34 ++++++------------------------ crates/hot/src/model/traits.rs | 35 +++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6b76c0a..8ecb8a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.6.7" +version = "0.6.8" edition = "2024" rust-version = "1.92" authors = ["init4"] @@ -35,13 +35,13 @@ incremental = false [workspace.dependencies] # internal -signet-hot = { version = "0.6.7", path = "./crates/hot" } -signet-hot-mdbx = { version = "0.6.7", path = "./crates/hot-mdbx" } -signet-cold = { version = "0.6.7", path = "./crates/cold" } -signet-cold-mdbx = { version = "0.6.7", path = "./crates/cold-mdbx" } -signet-cold-sql = { version = "0.6.7", path = "./crates/cold-sql" } -signet-storage = { version = "0.6.7", path = "./crates/storage" } -signet-storage-types = { version = "0.6.7", path = "./crates/types" } +signet-hot = { version = "0.6.8", path = "./crates/hot" } +signet-hot-mdbx = { version = "0.6.8", path = "./crates/hot-mdbx" } +signet-cold = { version = "0.6.8", path = "./crates/cold" } +signet-cold-mdbx = { version = "0.6.8", path = "./crates/cold-mdbx" } +signet-cold-sql = { version = "0.6.8", path = "./crates/cold-sql" } +signet-storage = { version = "0.6.8", path = "./crates/storage" } +signet-storage-types = { version = "0.6.8", path = "./crates/types" } # External, in-house signet-libmdbx = { version = "0.8.0" } diff --git a/crates/hot-mdbx/src/lib.rs b/crates/hot-mdbx/src/lib.rs index 3639b02..e792bfd 100644 --- a/crates/hot-mdbx/src/lib.rs +++ b/crates/hot-mdbx/src/lib.rs @@ -78,7 +78,7 @@ pub use tx::Tx; mod utils; -use signet_hot::model::{HotKv, HotKvError}; +use signet_hot::model::{HotKv, HotKvError, HotKvWrite}; /// 1 KB in bytes pub const KILOBYTE: usize = 1024; @@ -369,9 +369,23 @@ impl DatabaseEnv { let fsi_cache = Arc::new(RwLock::new(HashMap::new())); let env = Self { inner: inner_env.open(path)?, fsi_cache, _lock_file }; + if kind.is_rw() { + env.create_tables()?; + } + Ok(env) } + /// Create all standard hot storage tables. + /// + /// Called automatically when opening in read-write mode. + fn create_tables(&self) -> Result<(), MdbxError> { + let tx = self.tx_rw()?; + tx.queue_db_init()?; + tx.raw_commit()?; + Ok(()) + } + /// Start a new read-only transaction. pub fn tx(&self) -> Result, MdbxError> { self.inner diff --git a/crates/hot-mdbx/src/test_utils.rs b/crates/hot-mdbx/src/test_utils.rs index 18fd0ba..91d93c5 100644 --- a/crates/hot-mdbx/src/test_utils.rs +++ b/crates/hot-mdbx/src/test_utils.rs @@ -5,7 +5,7 @@ use alloy::primitives::Bytes; use signet_hot::{ db::UnsafeDbWrite, model::{HotKv, HotKvWrite}, - tables::{self, SingleKey, Table}, + tables::{SingleKey, Table}, }; use tempfile::{TempDir, tempdir}; @@ -29,25 +29,13 @@ pub fn create_test_rw_db() -> (TempDir, DatabaseEnv) { let args = DatabaseArguments::new(); let db = DatabaseEnv::open(dir.path(), DatabaseEnvKind::RW, args).unwrap(); - // Create tables from the `crate::tables::hot` module + // Standard tables are created automatically by open() in RW mode. + // Create test-specific tables. let writer = db.writer().unwrap(); - - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - - // Create DUP_FIXED table for put_multiple tests + // DUP_FIXED table for put_multiple tests // key2_size=8, value_size=8 means total fixed value size is 16 bytes writer.queue_raw_create("put_multiple_test", Some(8), Some(8)).unwrap(); - writer.commit().expect("Failed to commit table creation"); (dir, db) @@ -66,7 +54,6 @@ mod tests { use signet_hot::{ KeySer, MAX_KEY_SIZE, ValSer, conformance::{conformance, test_unwind_conformance}, - db::UnsafeDbWrite, model::{ DualKeyTraverse, DualTableTraverse, HotKv, HotKvRead, HotKvWrite, TableTraverse, TableTraverseMut, @@ -133,9 +120,6 @@ mod tests { { let writer: Tx = db.writer().unwrap(); - // Create tables first - writer.queue_create::().unwrap(); - // Write account data writer.queue_put::(&address, &account).unwrap(); writer.queue_put::(&hash, &bytecode).unwrap(); @@ -494,7 +478,6 @@ mod tests { { let writer: Tx = db.writer().unwrap(); - writer.queue_create::().unwrap(); writer.queue_put::(&hash, &large_bytecode).unwrap(); writer.raw_commit().unwrap(); } @@ -1995,15 +1978,10 @@ mod tests { let dir = tempdir().unwrap(); - // Phase 1: create tables and write data, then drop the DatabaseEnv. + // Phase 1: open RW (auto-creates tables), then drop the DatabaseEnv. { let args = DatabaseArguments::new(); - let db = DatabaseEnv::open(dir.path(), DatabaseEnvKind::RW, args).unwrap(); - - let writer = db.writer().unwrap(); - writer.queue_create::().unwrap(); - writer.queue_create::().unwrap(); - writer.raw_commit().unwrap(); + DatabaseEnv::open(dir.path(), DatabaseEnvKind::RW, args).unwrap(); } // DatabaseEnv (and its in-memory FsiCache) is now dropped. diff --git a/crates/hot/src/model/traits.rs b/crates/hot/src/model/traits.rs index b085c76..ce2afd1 100644 --- a/crates/hot/src/model/traits.rs +++ b/crates/hot/src/model/traits.rs @@ -389,6 +389,41 @@ pub trait HotKvWrite: HotKvRead { self.queue_raw_clear(T::NAME) } + /// Queue creation of all standard hot storage tables. + /// + /// This creates the 9 predefined tables used by the history and state + /// subsystems: [`Headers`], [`HeaderNumbers`], [`Bytecodes`], + /// [`PlainAccountState`], [`PlainStorageState`], [`AccountsHistory`], + /// [`AccountChangeSets`], [`StorageHistory`], and + /// [`StorageChangeSets`]. + /// + /// This does not commit the transaction. The caller must call + /// [`raw_commit`] after this method to persist the tables. + /// + /// [`raw_commit`]: Self::raw_commit + /// [`Headers`]: crate::tables::Headers + /// [`HeaderNumbers`]: crate::tables::HeaderNumbers + /// [`Bytecodes`]: crate::tables::Bytecodes + /// [`PlainAccountState`]: crate::tables::PlainAccountState + /// [`PlainStorageState`]: crate::tables::PlainStorageState + /// [`AccountsHistory`]: crate::tables::AccountsHistory + /// [`AccountChangeSets`]: crate::tables::AccountChangeSets + /// [`StorageHistory`]: crate::tables::StorageHistory + /// [`StorageChangeSets`]: crate::tables::StorageChangeSets + fn queue_db_init(&self) -> Result<(), Self::Error> { + use crate::tables; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + self.queue_create::()?; + Ok(()) + } + /// Clear all K2 entries for a specific K1 in a dual-keyed table. fn clear_k1_for(&self, key1: &T::Key) -> Result<(), Self::Error> { let mut cursor = self.traverse_dual_mut::()?; From 642eeb02e011c3cb44b9c8de63dc4a7882a4b19d Mon Sep 17 00:00:00 2001 From: James Date: Fri, 27 Mar 2026 09:28:22 -0400 Subject: [PATCH 2/2] docs(hot): document idempotency expectation for queue_raw_create and queue_db_init Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/hot/src/model/traits.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/hot/src/model/traits.rs b/crates/hot/src/model/traits.rs index ce2afd1..7d4ee1c 100644 --- a/crates/hot/src/model/traits.rs +++ b/crates/hot/src/model/traits.rs @@ -266,6 +266,9 @@ pub trait HotKvWrite: HotKvRead { /// Queue a raw create operation for a specific table. /// + /// Implementations MUST be idempotent: if the table already exists, + /// this should be a no-op. + /// /// This abstraction supports table specializations: /// 1. `dual_key_size` - whether the table is dual-keyed (i.e., /// `DUPSORT` in LMDB/MDBX). If so, the argument MUST be the @@ -397,9 +400,14 @@ pub trait HotKvWrite: HotKvRead { /// [`AccountChangeSets`], [`StorageHistory`], and /// [`StorageChangeSets`]. /// + /// This is expected to be a no-op if the tables already exist, as + /// [`queue_raw_create`] is required to be idempotent. + /// /// This does not commit the transaction. The caller must call /// [`raw_commit`] after this method to persist the tables. /// + /// [`queue_raw_create`]: Self::queue_raw_create + /// /// [`raw_commit`]: Self::raw_commit /// [`Headers`]: crate::tables::Headers /// [`HeaderNumbers`]: crate::tables::HeaderNumbers