Skip to main content

Module hybrid

Module hybrid 

Source
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_blocks and retention_blocks + items_per_section − 1 items, evicted in one-section batches.
  • The cache/reth boundary is the section-aligned oldest_allowed, not tip − retention. Reads in [oldest_allowed, tip − retention) still hit the cache. Same answer, slightly wasteful path.
  • A re-put below 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’s last_processed_height is floored to max(stored_height, reth.finalized) at startup (alias::marshal::init), so every put has H > reth.finalized > reth.pruned_below.
  • Blocks::get(Index(H)): only ever asks for the next contiguous height or a gap_end already in the cache.
  • Blocks::get(Key(digest)): gap-repair parent walks may ask for a digest below reth.pruned_below; on miss we return Ok(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§

Error 🔒
Error returned by Hybrid’s [Blocks] impl.

Traits§

FinalizedBlocksProvider 🔒
Narrow view of reth that Hybrid needs: a finalized watermark and canonical-by-height / canonical-by-hash block reads.

Type Aliases§

Prunable 🔒
Backing prunable archive type.