Skip to main content

Module journal

Module journal 

Source
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§

JournalEntry
A single reverse-diff entry covering one block’s commit.

Enums§

JournalDecodeError
Errors that can occur when decoding a journal entry from disk.

Constants§

JOURNAL_VERSION
Current version of the journal entry codec.

Type Aliases§

FlatDiff
A flat list of reverse-diff entries.
ReverseDiffEntry
A single reverse-diff entry: (on_disk_key, previous_value_or_none).