Expand description
§State-history journal
Per-block reverse-diff entries persisted to disk so reorgs deeper than the
in-memory TrieLayerCache become possible up to the finalized boundary.
Each entry captures the previous on-disk values (or absence markers) for every account-trie node, storage-trie node, account flat-key-value, and storage flat-key-value path that a single layer commit overwrites. Codes are content-addressed and not journaled.
Entries are keyed by block_number.to_be_bytes() in the
STATE_HISTORY column family. Big-endian
ensures lexicographic order matches numeric order, which lets finality
pruning use a single delete_range.
§Pruning model
When a forkchoice_update advances the finalized block, forkchoice_update_inner
calls delete_range(STATE_HISTORY, lowest, min(finalized_number, highest) + 1),
where lowest and highest are the lowest and highest entries present, removing
all journal entries at or below the new finality boundary. No range is written when
lowest is above finality. Ending at the highest existing entry rather than at
finality matters because entries are written DB_COMMIT_THRESHOLD blocks behind
head and finality can run ahead of them: a range delete only hides keys written
before it, so a range reaching past the newest entry would leave later-written
entries inside it alive and the next range would overlap it. The ranges therefore
never overlap, which keeps RocksDB’s unflushed range tombstones cheap to fragment
(overlapping ones grow as N²). The surviving entries cover
[finalized_number+1, cache_edge_D], which is exactly the window a future
deep reorg could need. After pruning, Store::lowest_state_history_block_number
reflects the new floor.
§Final layers are not journaled
Layers commit ~DB_COMMIT_THRESHOLD blocks behind the head. On a finalizing chain
that block is usually final already: an L2 finalizes its head every block, and L1
finality trails the head by two or three epochs. Its entry would be pruned on the
next finality advance, and until then it could only serve a reorg across finality,
which ethrex does allow (finality is not a reorg limit, see compute_reorg_ceiling
in the blockchain crate) but rejects as too deep when the journal has no entry for
it. The commit path therefore skips the entry, giving up only that window: up to an
epoch on L1, one block on L2. It does so only while the journal is empty and the
block is at or below the head as well, which leaves the journal in a state pruning
already produces: the newest entry, when there is one, still names the block whose
state is on disk. In steady state the journal stays empty and pruning writes
nothing; entries appear when finality falls behind the commit edge, which is when a
deep reorg can need them. See journal_skip_ceiling in store.rs.
§Batch mode (full sync)
When batch_mode == true (full sync), the commit path skips journaling
entirely. A full-sync import writes one layer per ~1024 blocks and does
not produce the per-block reverse-diffs needed for deep reorgs. Reorg
support is only active after the node transitions to normal block-by-block
execution.
§Codec
Entries use a hand-rolled compact format: a version byte at offset 0, then
block_hash (32 bytes), parent_state_root (32 bytes), then four
varint-prefixed reverse-diff sections in order: account-trie, storage-trie,
account flat-KV, storage flat-KV. RLP/bincode/postcard are skipped — the
access pattern (write-once, read-on-reorg, large volume) makes encode/decode
cost matter.
§Version strategy
JOURNAL_VERSION is a single byte at offset 0 of every entry. The decoder
rejects any version other than the current one with
JournalDecodeError::VersionMismatch. On a codec bump, the journal should
be drained at a finality boundary so the new binary starts with an empty
journal and never encounters old-format entries. A future bump that needs to
keep history across the upgrade should introduce per-version decode_vN arms
rather than re-encoding existing entries.
Structs§
- Journal
Entry - A single reverse-diff entry covering one block’s commit.
Enums§
- Journal
Decode Error - Errors that can occur when decoding a journal entry from disk.
Constants§
- JOURNAL_
VERSION - Current version of the journal entry codec.
Type Aliases§
- Flat
Diff - A flat list of reverse-diff entries.
- Reverse
Diff Entry - A single reverse-diff entry:
(on_disk_key, previous_value_or_none).