Expand description
Hybrid is a prunable archive of finalized blocks fronting reth.
Reth is the source of truth. The prunable archive is a hot cache of
the most-recently finalized blocks that lets the marshal serve
gap-repair without round-tripping to reth on every read. The marshal
only sees the [Blocks] interface and is unaware which side served
a given read.
§Eviction
Eviction is reth-driven, not marshal-driven. Every [Blocks::put]
reads reth’s finalized watermark and prunes the cache below
reth.finalized − retention_blocks + 1. [Blocks::prune] from the
marshal is a no-op.
As a consequences, the cache never drops a block reth doesn’t yet have.
If reth is lagging the marshal actor, the cache temporarily holds more than
retention_blocks items.
§Section-rounding
The prunable archive prunes in whole sections of
items_per_section items (4096 in production); prune(min) rounds
min down to a section boundary.
Consequences:
- Retention is approximate: the cache holds between
retention_blocksandretention_blocks + items_per_section − 1items, evicted in one-section batches. - The cache/reth boundary is the section-aligned
oldest_allowed, nottip − retention. Reads in[oldest_allowed, tip − retention)still hit the cache. Same answer, slightly wasteful path. - A re-
putbelow the requested floor may still land in the cache (live tail section) or be silently absorbed (see “Stale puts” below).
Pick retention_blocks as a few multiples of items_per_section so
section overshoot is a small fraction of the working set; see
super::DEFAULT_FINALIZED_BLOCKS_RETENTION.
§Stale puts
Hybrid::put absorbs the prunable archive’s
[archive::Error::AlreadyPrunedTo] as a silent success. The
eviction invariant
oldest_allowed ≤ section_aligned(reth.finalized − retention + 1) ≤ reth.finalized guarantees that a put at H < oldest_allowed
also has H ≤ reth.finalized, so the block is durable in reth and
a subsequent [Blocks::get] will hit the reth fallback. Surfacing
the error would crash the node on a recoverable condition (e.g.
follow-mode catching up while reth has synced past the cache
window).
§Why reth pruning is not a concern
Reth may be configured to retain only a window of recent history,
creating a reth.pruned_below ≤ reth.finalized watermark. The
marshal can never ask for a block panic-on-miss below
reth.pruned_below:
Blocks::put(H): marshal’slast_processed_heightis floored tomax(stored_height, reth.finalized)at startup (alias::marshal::init), so every put hasH > reth.finalized > reth.pruned_below.Blocks::get(Index(H)): only ever asks for the next contiguous height or agap_endalready in the cache.Blocks::get(Key(digest)): gap-repair parent walks may ask for a digest belowreth.pruned_below; on miss we returnOk(None)and the marshal falls back to peer resolution.
Configuring reth with a smaller retention window than
retention_blocks is a perf concern (more peer fetches), not a
correctness one.
Structs§
- Config 🔒
- Configuration for
Hybrid. - Hybrid 🔒
- Finalized blocks store backed by a prunable archive (a hot cache of the most recently finalized blocks) and reth (the source of truth for finalized blocks).
Enums§
Traits§
- Finalized
Blocks 🔒Provider - Narrow view of reth that
Hybridneeds: a finalized watermark and canonical-by-height / canonical-by-hash block reads.
Type Aliases§
- Prunable 🔒
- Backing prunable archive type.