feat(host-reth): replace ExEx backfill with direct DB reads#136
Open
feat(host-reth): replace ExEx backfill with direct DB reads#136
Conversation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces three types for reading host-chain blocks from the reth DB: - `DbBlock`: owned block+receipts pair from the provider - `DbChainSegment`: newtype over `Vec<DbBlock>` implementing `Extractable` using the same `RecoveredBlockShim` transmute pattern as `RethChain` - `DbBackfill<P>`: batch reader that walks from a cursor to the finalized block, recording metrics per batch via `crate::metrics` Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds `HostChain` enum with `Backfill(DbChainSegment)` and `Live(RethChain)` variants, both delegating to the inner `Extractable` impl. Promotes `DbChainSegment` to `pub` and re-exports both new types from the crate root. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace reth's built-in ExEx backfill with DbBackfill for startup catch-up. The notifier now runs a two-phase loop: phase 1 drains DB batches via DbBackfill, then phase 2 switches to live ExEx notifications. set_head initializes backfill instead of resolving a header directly, and set_backfill_thresholds configures DbBackfill batch size. Chain type changes from RethChain to HostChain enum. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fraser999
reviewed
Apr 14, 2026
Comment on lines
+249
to
+254
| if let Some(backfill) = &mut self.backfill | ||
| && let Some(max_blocks) = max_blocks | ||
| { | ||
| debug!(max_blocks, "configured DB backfill batch size"); | ||
| backfill.set_batch_size(max_blocks); | ||
| } |
Contributor
There was a problem hiding this comment.
The trait method's doc comment says "None means use the backend's default.", and the RpcHostNotifier does this, so we should probably reset the batch size to the default here too in the None case.
Evalir
reviewed
Apr 15, 2026
Comment on lines
+43
to
+52
| const { | ||
| assert!( | ||
| size_of::<RecoveredBlockShim>() == size_of::<RethRecovered>(), | ||
| "RecoveredBlockShim layout diverged from RethRecovered" | ||
| ); | ||
| assert!( | ||
| align_of::<RecoveredBlockShim>() == align_of::<RethRecovered>(), | ||
| "RecoveredBlockShim alignment diverged from RethRecovered" | ||
| ); | ||
| } |
| let backfill = self.backfill.take().expect("backfill was Some"); | ||
| let last_backfilled = backfill.cursor().saturating_sub(1); | ||
|
|
||
| let head = self |
Member
There was a problem hiding this comment.
so, this behavior of fetching the last seen block, and if that fails, fall back to genesis, stems from a probably still unresolved bug where Reth starts the exex before its connected to its own db provider (that's roughly what I remember). This ofc complicates the logic quite a bit by adding the genesis fallback path.
Considering that most of the time we might be falling into phase 1 when restarting the node to catch up with a few blocks, maybe we can simplify this?
- `set_backfill_thresholds(None)` now resets the batch size to
`DEFAULT_BATCH_SIZE` via a new `DbBackfill::reset_batch_size`,
matching the trait contract ("`None` means use the backend's
default") and the `RpcHostNotifier` implementation.
- Remove the genesis fallback after backfill completion. The
documented ExEx startup race it was defending against
(reth #19665 / #22168) was fixed upstream, and in any case
`DbBackfill` just read `last_backfilled` from the same provider,
so a missing header at this point indicates DB-level failure.
Now returns `RethHostError::MissingHeader` instead.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
DbBackfill<P>reads blocks+receipts in batches up to finalized, then hands off to the ExEx stream for live blocksHostChainenum unifies backfill and live chain segments behindExtractableset_headdocumented and enforced as once-only across both notifier implementationsDetails
The ExEx backfill mechanism re-executes historical blocks on startup, which is slow and has caused memory issues. The reth DB already contains executed results, making re-execution unnecessary.
New types (
signet-host-reth):DbBlock/DbChainSegment— owned block+receipts from DB, implementsExtractableDbBackfill<P>— batch reader usingspawn_blockingfor MDBX readsHostChain— enum wrappingDbChainSegment(backfill) andRethChain(live)Notifier changes:
RethHostNotifier::next_notificationis now two-phase: drain DB backfill, then switch to ExExset_headcreates aDbBackfillinstead of calling ExExset_with_headreth-stages-typesdependency removedCross-crate:
HostNotifier::set_headtrait doc clarified as once-onlyRpcHostNotifier::set_headguards against repeated callsReview follow-ups
set_backfill_thresholds(None)now resets toDEFAULT_BATCH_SIZEvia a newDbBackfill::reset_batch_size, matching the trait contract andRpcHostNotifier.DbBackfillhas just successfully readlast_backfilledfrom the same provider — so a missing header there now indicates DB-level failure and returnsRethHostError::MissingHeader.Closes ENG-1784
Test plan
cargo clippypasses (both--all-featuresand--no-default-features)RUSTDOCFLAGS="-D warnings" cargo docpassessignet-host-reth,signet-node-types,signet-host-rpc)signet-nodecompiles cleanly with newHostChaintype🤖 Generated with Claude Code