From 0a499357178320b4a913f58423405a3a06b3af40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Duchesneau?= Date: Fri, 2 Oct 2026 16:24:35 -0400 Subject: [PATCH 1/2] Emit Firehose init and genesis without an ExEx The Firehose ExEx only emitted FIRE INIT and the genesis block; its block tracing was disabled. Emit both from a component-initialized hook, which runs before the consensus engine exists, so no block is traced first. --- CHANGELOG.sf.md | 6 +++ bin/node/src/firehose.rs | 19 ++++++--- crates/execution/runner/src/builder.rs | 39 +++++++++++++++++-- .../runner/tests/firehose_live_tracing.rs | 2 +- crates/firehose-flashblocks/README.md | 2 +- crates/firehose-flashblocks/src/tracer.rs | 2 +- .../tests/flashblock_sequence.rs | 8 ++-- .../tests/framework/mod.rs | 4 +- 8 files changed, 65 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.sf.md b/CHANGELOG.sf.md index 2806ed2054..2c64049472 100644 --- a/CHANGELOG.sf.md +++ b/CHANGELOG.sf.md @@ -1,3 +1,9 @@ +## Unreleased + +### Changed + +* The node no longer installs a Firehose execution extension (ExEx). It only emitted `FIRE INIT` and the genesis block; blocks are traced by the engine. Both are now emitted at startup before the consensus engine exists, so no block can be traced ahead of `FIRE INIT`. Unless another ExEx is enabled, the node stops writing the ExEx write-ahead log. `FIRE INIT` now reports the Base node version instead of the `reth-firehose` crate version. + ## v1.4.2-fh3.1-1 ### Changed diff --git a/bin/node/src/firehose.rs b/bin/node/src/firehose.rs index 045055ab16..bdcbf4422d 100644 --- a/bin/node/src/firehose.rs +++ b/bin/node/src/firehose.rs @@ -1,8 +1,8 @@ //! Firehose tracer wiring for `base-reth-node`. //! //! Initializes the process-wide Firehose tracer and installs: -//! * the canonical-block `ExEx` that re-executes committed chains through the -//! global Firehose inspector; +//! * the startup hook that emits `FIRE INIT` and, on an empty chain, the genesis block. Canonical +//! blocks are then traced by the engine as it executes them; //! * (optionally) the [`FirehoseFlashblocksExtension`] that subscribes to a //! flashblock WebSocket feed and emits per-flashblock partial-block FIRE //! events via a separate, dedicated tracer. @@ -20,14 +20,23 @@ use tokio_stream::{StreamExt, wrappers::BroadcastStream}; use tracing::{debug, info, warn}; use url::Url; -/// Runner-level extension that installs the Firehose `ExEx`. +/// Runner-level extension that emits `FIRE INIT` and, on an empty chain, the genesis block. +/// +/// Must run in the component-initialized hook: the tracer panics on a block that starts before +/// `on_blockchain_init`, and the consensus engine can execute blocks before node-started hooks run. #[derive(Debug)] pub struct FirehoseExtension; impl BaseNodeExtension for FirehoseExtension { fn apply(self: Box, hooks: NodeHooks) -> NodeHooks { - hooks.install_exex("firehose", |ctx| async move { - Ok(async move { reth_firehose::run_exex(ctx).await }) + hooks.add_component_initialized_hook(|node| { + let chain_spec = node.provider.chain_spec(); + reth_firehose::tracer().on_blockchain_init( + "reth", + env!("CARGO_PKG_VERSION"), + firehose_tracer::config::ChainConfig::new(chain_spec.chain().id()), + ); + reth_firehose::emit_genesis_block_on_empty_chain(&node.provider, chain_spec.genesis()) }) } } diff --git a/crates/execution/runner/src/builder.rs b/crates/execution/runner/src/builder.rs index aa9f5999d7..cc1cb552c9 100644 --- a/crates/execution/runner/src/builder.rs +++ b/crates/execution/runner/src/builder.rs @@ -1,7 +1,8 @@ //! Hook accumulator for the node builder. //! -//! [`NodeHooks`] collects RPC, node-started, and `ExEx` hooks that extensions install. These hooks -//! are applied to a configured reth builder via [`NodeHooks::apply_to`] just before launch. +//! [`NodeHooks`] collects RPC, component-initialized, node-started, and `ExEx` hooks that +//! extensions install. These hooks are applied to a configured reth builder via +//! [`NodeHooks::apply_to`] just before launch. use std::fmt; @@ -43,6 +44,9 @@ type RpcModuleHook = Box) -> Result<()> + Sen /// Hook type for extending add-ons. type AddOnsHook = Box ConcreteBaseAddOns>; +/// Hook type for component-initialized callbacks. +type ComponentInitializedHook = Box Result<()> + Send + 'static>; + /// Hook type for node-started callbacks. type NodeStartedHook = Box Result<()> + Send + 'static>; @@ -65,6 +69,7 @@ pub type RethNodeBuilder = /// Pure hook accumulator for the Base node builder. /// /// Extensions call [`add_rpc_module`](Self::add_rpc_module), +/// [`add_component_initialized_hook`](Self::add_component_initialized_hook), /// [`add_node_started_hook`](Self::add_node_started_hook), and /// [`install_exex`](Self::install_exex) to register hooks. The runner then calls /// [`apply_to`](Self::apply_to) to drain all hooks onto the concrete configured builder. @@ -72,6 +77,7 @@ pub type RethNodeBuilder = /// After applying hooks, call [`.launch()`](RethNodeBuilder::launch) on the configured builder. pub struct NodeHooks { rpc_hooks: Vec, + component_initialized_hooks: Vec, node_started_hooks: Vec, add_ons_hooks: Vec, exex_hooks: Vec<(String, BoxExExFactory)>, @@ -82,6 +88,7 @@ impl NodeHooks { pub fn new() -> Self { Self { rpc_hooks: Vec::new(), + component_initialized_hooks: Vec::new(), node_started_hooks: Vec::new(), exex_hooks: Vec::new(), add_ons_hooks: Vec::new(), @@ -96,7 +103,13 @@ impl NodeHooks { where CB: NodeComponentsBuilder, { - let Self { rpc_hooks, node_started_hooks, exex_hooks, add_ons_hooks } = self; + let Self { + rpc_hooks, + component_initialized_hooks, + node_started_hooks, + exex_hooks, + add_ons_hooks, + } = self; // Install ExEx hooks for (id, factory) in exex_hooks { @@ -118,6 +131,16 @@ impl NodeHooks { }); } + // Install component-initialized hooks + if !component_initialized_hooks.is_empty() { + builder = builder.on_component_initialized(move |node: BaseNodeAdapter| { + for hook in component_initialized_hooks { + hook(node.clone())?; + } + Ok(()) + }); + } + // Install node-started hooks if !node_started_hooks.is_empty() { builder = builder.on_node_started(move |full_node: BaseFullNode| { @@ -162,6 +185,16 @@ impl NodeHooks { self.add_add_ons_hook(move |add_ons| add_ons.on_engine_events(hook)) } + /// Adds a hook that runs once the node components are built, before the consensus engine + /// and the RPC servers exist. No block is executed before these hooks return. + pub fn add_component_initialized_hook(mut self, hook: F) -> Self + where + F: FnOnce(BaseNodeAdapter) -> Result<()> + Send + 'static, + { + self.component_initialized_hooks.push(Box::new(hook)); + self + } + /// Adds a node-started hook that will run after the node has started. pub fn add_node_started_hook(mut self, hook: F) -> Self where diff --git a/crates/execution/runner/tests/firehose_live_tracing.rs b/crates/execution/runner/tests/firehose_live_tracing.rs index 9c0aceec71..707e85659c 100644 --- a/crates/execution/runner/tests/firehose_live_tracing.rs +++ b/crates/execution/runner/tests/firehose_live_tracing.rs @@ -57,7 +57,7 @@ use reth_provider::ChainSpecProvider; use tokio::time::sleep; /// Number of blocks to advance. All of them (block 1 included — the genesis block itself is -/// emitted separately at node startup by the Firehose ExEx, not through this path) exercise the +/// emitted separately at node startup, not through this path) exercise the /// live `execute_and_trace_block` path on the follower. const PRODUCED_BLOCKS: u64 = 3; diff --git a/crates/firehose-flashblocks/README.md b/crates/firehose-flashblocks/README.md index 6bf44d202e..deba0b9acd 100644 --- a/crates/firehose-flashblocks/README.md +++ b/crates/firehose-flashblocks/README.md @@ -11,4 +11,4 @@ ahead of the canonical engine-API confirmation. The entire subsystem is a no-op unless `--firehose-flashblocks-url` is set on the node binary; in that case the streamer runs alongside (but independently -of) the canonical live-block tracer driven by `reth_firehose::run_exex`. +of) the canonical live-block tracer driven by the engine. diff --git a/crates/firehose-flashblocks/src/tracer.rs b/crates/firehose-flashblocks/src/tracer.rs index 0a86fd2622..a9ca5a038e 100644 --- a/crates/firehose-flashblocks/src/tracer.rs +++ b/crates/firehose-flashblocks/src/tracer.rs @@ -39,7 +39,7 @@ impl FlashblocksTracerHandle { /// Constructs the dedicated tracer with the given config and chain-config, sharing the /// process-wide stdout lock with the global tracer. /// - /// `chain_config` matches what the live tracer received in [`reth_firehose::run_exex`] + /// `chain_config` matches what the live tracer received at node startup /// (chain id of the running node); a fresh `on_blockchain_init` is emitted on this /// instance — required because `Tracer` guards `on_block_start` behind its own per-instance /// `init_sent` flag. diff --git a/crates/firehose-flashblocks/tests/flashblock_sequence.rs b/crates/firehose-flashblocks/tests/flashblock_sequence.rs index aa2528a746..2058e4634d 100644 --- a/crates/firehose-flashblocks/tests/flashblock_sequence.rs +++ b/crates/firehose-flashblocks/tests/flashblock_sequence.rs @@ -496,7 +496,7 @@ fn block_payload_has_correct_block_number() { /// /// This verifies the [`TestEvent::CanonicalBlock`] path: it both marks block 1 available in /// [`GenesisClient`] so that the bootstrap provider call succeeds, and emits a canonical FIRE -/// BLOCK (simulating the global `ExEx` tracer), for a total of three emitted events. +/// BLOCK (simulating the global canonical tracer), for a total of three emitted events. /// /// - `base1` → Flash1 (flashblock tracer, block 1). /// - `canonical_block(1, hash("1a"))` → Canonical1 (canonical tracer, block 1) + makes block 1 available. @@ -557,7 +557,7 @@ fn canonical_block_unblocks_next_base() { /// `accumulated_db` is always `None` and the provider call for the parent block is mandatory. /// /// The `canonical_block(1, hash("1a"))` event also emits a canonical FIRE BLOCK (simulating the global -/// `ExEx` tracer), so two events appear in total: Canonical1 then Flash2. +/// canonical tracer), so two events appear in total: Canonical1 then Flash2. #[test] fn canonical_block_unblocks_non_sequential_gap() { let genesis = test_genesis(); @@ -598,9 +598,9 @@ fn canonical_block_unblocks_non_sequential_gap() { /// Sequence breakdown: /// - `base1` → flashblock for block 1 (`flash_idx` 0) → Flash1 emitted by flashblock tracer. /// - `canonical_block(1, hash("1a"))` → makes block 1 available **and** emits Canonical1 via the canonical -/// tracer, simulating the global `ExEx` tracer finalising block 1. +/// tracer, simulating the global canonical tracer finalising block 1. /// - `canonical_block(2, hash("2a"))` → makes block 2 available **and** emits Canonical2 via the canonical -/// tracer, simulating the global `ExEx` tracer finalising block 2 (even though no flashblock was +/// tracer, simulating the global canonical tracer finalising block 2 (even though no flashblock was /// sent for block 2). /// - `base3` → flashblock for block 3 (`flash_idx` 0). The processor detects a block gap /// (current = 1, incoming = 3) which triggers the bootstrap path. Because block 2 was already diff --git a/crates/firehose-flashblocks/tests/framework/mod.rs b/crates/firehose-flashblocks/tests/framework/mod.rs index 27942c88fe..7989a50300 100644 --- a/crates/firehose-flashblocks/tests/framework/mod.rs +++ b/crates/firehose-flashblocks/tests/framework/mod.rs @@ -1449,7 +1449,7 @@ pub(crate) fn test_genesis() -> Genesis { /// FIRE BLOCK lines are emitted by the **flashblock tracer**. /// - [`TestEvent::CanonicalBlock`] — marks the block available in [`GenesisClient`] *and* /// emits a canonical (non-flash) FIRE BLOCK line through a dedicated **canonical tracer**. -/// This simulates the live-node behaviour where the global `FirehoseExtension` `ExEx` emits +/// This simulates the live-node behaviour where the global canonical tracer emits /// a canonical FIRE BLOCK whenever a block is finalised by the engine, independent of the /// flashblock tracer. /// @@ -1691,7 +1691,7 @@ fn run_flashblock_sequence_internal( // that need to bootstrap from block N can find it. client.mark_canonical_block_available(block_number); - // Emit a canonical FIRE BLOCK to simulate the global ExEx tracer emitting the + // Emit a canonical FIRE BLOCK to simulate the global canonical tracer emitting the // finalised block. A minimal block with the correct number is sufficient; the // test assertions use metadata-only comparisons. let header = client.header_for_block(block_number); From 3fbbc6ee9fa4a507e135fcddf24a6180da7773b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ste=CC=81phane=20Duchesneau?= Date: Fri, 2 Oct 2026 16:30:41 -0400 Subject: [PATCH 2/2] Report the node version as base- in FIRE INIT --- CHANGELOG.sf.md | 2 +- bin/node/src/firehose.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.sf.md b/CHANGELOG.sf.md index 2c64049472..3b821160a2 100644 --- a/CHANGELOG.sf.md +++ b/CHANGELOG.sf.md @@ -2,7 +2,7 @@ ### Changed -* The node no longer installs a Firehose execution extension (ExEx). It only emitted `FIRE INIT` and the genesis block; blocks are traced by the engine. Both are now emitted at startup before the consensus engine exists, so no block can be traced ahead of `FIRE INIT`. Unless another ExEx is enabled, the node stops writing the ExEx write-ahead log. `FIRE INIT` now reports the Base node version instead of the `reth-firehose` crate version. +* The node no longer installs a Firehose execution extension (ExEx). It only emitted `FIRE INIT` and the genesis block; blocks are traced by the engine. Both are now emitted at startup before the consensus engine exists, so no block can be traced ahead of `FIRE INIT`. Unless another ExEx is enabled, the node stops writing the ExEx write-ahead log. `FIRE INIT` now reports the Base node version, as `reth base-1.4.2`, instead of the `reth-firehose` crate version. ## v1.4.2-fh3.1-1 diff --git a/bin/node/src/firehose.rs b/bin/node/src/firehose.rs index bdcbf4422d..9e06a3ce3e 100644 --- a/bin/node/src/firehose.rs +++ b/bin/node/src/firehose.rs @@ -33,7 +33,7 @@ impl BaseNodeExtension for FirehoseExtension { let chain_spec = node.provider.chain_spec(); reth_firehose::tracer().on_blockchain_init( "reth", - env!("CARGO_PKG_VERSION"), + concat!("base-", env!("CARGO_PKG_VERSION")), firehose_tracer::config::ChainConfig::new(chain_spec.chain().id()), ); reth_firehose::emit_genesis_block_on_empty_chain(&node.provider, chain_spec.genesis())