Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.sf.md
Original file line number Diff line number Diff line change
@@ -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, as `reth base-1.4.2`, instead of the `reth-firehose` crate version.

## v1.4.2-fh3.1-1

### Changed
Expand Down
19 changes: 14 additions & 5 deletions bin/node/src/firehose.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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<Self>, 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",
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())
})
}
}
Expand Down
39 changes: 36 additions & 3 deletions crates/execution/runner/src/builder.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -43,6 +44,9 @@ type RpcModuleHook = Box<dyn FnOnce(&mut BaseRpcContext<'_>) -> Result<()> + Sen
/// Hook type for extending add-ons.
type AddOnsHook = Box<dyn FnOnce(ConcreteBaseAddOns) -> ConcreteBaseAddOns>;

/// Hook type for component-initialized callbacks.
type ComponentInitializedHook = Box<dyn FnOnce(BaseNodeAdapter) -> Result<()> + Send + 'static>;

/// Hook type for node-started callbacks.
type NodeStartedHook = Box<dyn FnOnce(BaseFullNode) -> Result<()> + Send + 'static>;

Expand All @@ -65,13 +69,15 @@ pub type RethNodeBuilder<CB> =
/// 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.
///
/// After applying hooks, call [`.launch()`](RethNodeBuilder::launch) on the configured builder.
pub struct NodeHooks {
rpc_hooks: Vec<RpcModuleHook>,
component_initialized_hooks: Vec<ComponentInitializedHook>,
node_started_hooks: Vec<NodeStartedHook>,
add_ons_hooks: Vec<AddOnsHook>,
exex_hooks: Vec<(String, BoxExExFactory)>,
Expand All @@ -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(),
Expand All @@ -96,7 +103,13 @@ impl NodeHooks {
where
CB: NodeComponentsBuilder<BaseNodeTypes, Components = BaseComponents>,
{
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 {
Expand All @@ -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| {
Expand Down Expand Up @@ -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<F>(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<F>(mut self, hook: F) -> Self
where
Expand Down
2 changes: 1 addition & 1 deletion crates/execution/runner/tests/firehose_live_tracing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion crates/firehose-flashblocks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 1 addition & 1 deletion crates/firehose-flashblocks/src/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions crates/firehose-flashblocks/tests/flashblock_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions crates/firehose-flashblocks/tests/framework/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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);
Expand Down
Loading