feat(hot)!: add table for journal hashes#70
Conversation
| table = table.name, | ||
| ?expected, | ||
| "FSI metadata entry missing for known table; falling back to compile-time \ | ||
| expected value. Fires once per open per missing table until a RW open \ |
There was a problem hiding this comment.
I think this info belongs in a comment? 🤔
creates the table and persists its FSI; RO-only consumers of a \
pre-upgrade database will see this on every open."```
| let mut known = [("", FixedSizeInfo::None); NUM_TABLES]; | ||
| for (i, &name) in KNOWN_TABLE_NAMES.iter().enumerate() { | ||
| known[i] = (name, tx.read_fsi_from_table(name)?); | ||
| for (index, table) in STANDARD_TABLES.iter().enumerate() { |
There was a problem hiding this comment.
could rewrite this loop as a try_for_each() ?
| /// Removes a table from the environment as if it had never been created: | ||
| /// drops the named sub-database and erases its FSI metadata entry. Used | ||
| /// by tests to simulate a database written by an older binary that | ||
| /// pre-dates the table. Does not invalidate the in-memory `FsiCache`; |
There was a problem hiding this comment.
we still control all significant running copies of the binaries, so we could choose not to implement migration logic/tests here. worth discussing whether we need this complexity yet
| /// Set the journal hash (keccak256 of the wire-encoded `Journal::V1`). | ||
| /// Leave the method uncalled for block-only nodes that do not produce | ||
| /// a journal - the field defaults to `None`. | ||
| pub const fn journal_hash(mut self, hash: B256) -> Self { |
There was a problem hiding this comment.
nit: usually with_journal_hash or some other verb that indicates mutating
There was a problem hiding this comment.
I prefer the with_ prefix too, but all other setters don't have it.
I'd be OK with adding it to all of them. It'd be a breaking change to the API, but this PR already has other breaking changes, so now might be a good time to make that change if we want it?
| /// [`queue_raw_create`](crate::model::HotKvWrite::queue_raw_create) so the | ||
| /// same record drives both table creation and backend-side FSI inference. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| pub struct StandardTable { |
There was a problem hiding this comment.
I don't mind this complexity addition

Summary
Adds a
JournalHashes<BlockNumber => B256>hot table so nodes can re-seed the rolling previous-journal hash across restarts and reorgs (ENG-2017). Hot-only, opt-in per block.Changes
JournalHashes<BlockNumber => B256>plusHotDbRead::get_journal_hashandUnsafeDbWrite::put_journal_hash.ExecutedBlock.journal_hash: Option<B256>+ builder setter.UnifiedStorage::append_blockspersists it in the same hot transaction as headers/bundle.HistoryWrite::unwind_abovedeletes journal hashes alongside change-sets/headers, independent oflast_block_number().STANDARD_TABLESconst insignet-hotas the single source of truth for table creation and MDBX FSI cache seeding.queue_db_initandread_known_fsiboth iterate it.read_known_fsifalls back to compile-time-expected FSI (viaFixedSizeInfo::from_create_args) when an on-disk entry is missing, so RO/RW opens against an older DB succeed.unwind_above(u64::MAX)now short-circuits viachecked_addinstead of overflowingblock + 1(wraps to0in release, panics in debug).From<ExecutedBlock> for BlockDatadestructures explicitly so new fields trip a compile error on the cold side.API breakages
ExecutedBlock::new()removed - useExecutedBlockBuilderinstead.ExecutedBlockgains a new publicjournal_hashfield. Struct-literal construction (ExecutedBlock { header, .. }) and exhaustive destructuring without..will no longer compile.Test plan
cargo t -p signet-hot -p signet-hot-mdbx -p signet-storage --all-featurescargo clippy --workspace --all-targets --all-features -- -D warningscargo clippy --workspace --all-targets --no-default-features -- -D warningscargo +nightly fmt -- --checktest_journal_hash_roundtrip(conformance),open_tolerates_pre_upgrade_db(FSI fallback),append_blocks_persists_journal_hashes/unwind_above_drops_journal_hashes(integration),unwind_above_u64_max_is_noop/unwind_above_below_u64_max_deletes_max_entry(overflow boundary).test_unwind_conformanceupdated to coverJournalHashes.Notes
put_journal_hashhas no consistent-trait wrapper; callers are responsible for pairing writes with a header.unwind_abovecleans both regardless.replay_to_coldsilently discardsjournal_hash(hot-only).PR description drafted by Claude (Opus 4.7, 1M context) via Claude Code.