Skip to content

feat(builder): record build-loop state-read latency - #5025

Merged
wbj-cb merged 1 commit into
mainfrom
willjohnston/base-165-build-loop-io-metrics
Sep 15, 2026
Merged

wbj-cb merged 1 commit into
mainfrom
willjohnston/base-165-build-loop-io-metrics

Conversation

@wbj-cb

@wbj-cb wbj-cb commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

A sequencer both builds blocks and validates them, and reth's state-provider instrumentation is wired only into the validation path. Enabling --engine.state-provider-metrics there reports the cost of checking a finished block, not the cost of assembling one — so time spent blocked on state reads during building was never measurable.

What changed

  • Both payload builders (flashblocks and native) wrap their state provider in reth's InstrumentedStateProvider, reporting under sync.state_provider with a builder source label to distinguish it from the engine's engine label.
  • Gated on reth's existing --engine.state-provider-metrics rather than a new flag, so the builder and the validation path are instrumented by one switch. Off by default.
  • The wrapper is applied outside CachedStateProvider, so the recorded latency is the build loop's total per-read cost including cache hits. Inside, a working cache would show no improvement.
  • Adds state_provider_metrics to BuilderConfig and BaseBuilderConfig, resolved from the node config when the payload service is built.

Notes for reviewers

No production effect as merged. --engine.state-provider-metrics is currently off across the fleet, so this emits nothing until it is enabled. The gate is evaluated once per payload build, not per read.

@cb-heimdall

cb-heimdall commented Sep 11, 2026

Copy link
Copy Markdown
Collaborator

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@linear

linear Bot commented Sep 11, 2026

Copy link
Copy Markdown

BASE-165

@depot-code-access

depot-code-access Bot commented Sep 11, 2026

Copy link
Copy Markdown

✅ All benchmarks green — 14 within ±2% (deterministic instruction counts). View run

Benchmark details (14)
Benchmark Base (target) Head (this PR) Δ instructions
batch_queue/drain/drain_cached_span_batches 242,027 242,027 +0.0%
batch_transaction/encode_in_place/encode_in_place 4,199,759 4,199,759 +0.0%
batch_transaction/temporary_frame_buffers/temporary_frame_buffers 8,408,350 8,408,350 +0.0%
flashblock_decode/decode/brotli 3,296,484 3,296,484 +0.0%
flashblock_decode/decode/plain_json 2,280,194 2,280,194 +0.0%
flz/compress_len/real_contract_call 43,148 43,148 +0.0%
flz/compress_len/synthetic_0 38,205 38,205 +0.0%
flz/compress_len/synthetic_1 54,682 54,682 +0.0%
flz/compress_len/synthetic_2 147,976 147,976 +0.0%
flz/data_gas 43,059 43,059 +0.0%
flz/tx_estimated_size 43,056 43,056 +0.0%
frame_parse/decode/single_4kib 1,031 1,031 +0.0%
frame_parse/parse_frames/few_large 1,053,062 1,053,062 +0.0%
frame_parse/parse_frames/many_small 154,763 154,763 +0.0%

Comment thread crates/execution/node/src/node.rs Outdated
Comment thread Cargo.toml Outdated
@depot-code-access

depot-code-access Bot commented Sep 11, 2026

Copy link
Copy Markdown

Base Std historical fork tests

Fork Result Passed Failed Skipped base/base base-anvil base-std
Beryl pass 616 0 13 a6f76a62 98e7839c 520d069b
Cobalt pass 725 0 14 a6f76a62 98e7839c 4571b325

View run

Comment thread crates/execution/payload/Cargo.toml Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary — feat(builder): record build-loop state-read latency

Clean, well-scoped observability addition. The wrapping order, config gating, and threading through the multiplex path are all correct. No block-production risk: the instrumentation is gated on --engine.state-provider-metrics (off by default), and InstrumentedStateProvider is already battle-tested on reth's engine validation path.

Findings

# Severity File Issue
1 Low crates/execution/node/src/node.rs:1182 state_provider_metrics hardcoded to false in the standalone build_payload_builder path. The multiplex path reads from ctx.config().engine.state_provider_metrics, but this path doesn't — so the flag has no effect when BasePayloadBuilder is used without the multiplex layer.
2 Nit Cargo.toml, crates/builder/core/Cargo.toml, crates/execution/payload/Cargo.toml reth-engine-tree is a heavyweight dependency (~40 transitive crates including reth-db, reth-stages, rayon) pulled in for a single type. Worth tracking upstream to re-export InstrumentedStateProvider from a lighter crate.
3 Nit crates/execution/payload/Cargo.toml:23, crates/builder/core/Cargo.toml:31, Cargo.toml:395 reth-engine-tree breaks the waterfall (sort-by-line-length) ordering convention in all three manifests.

No critical or block-production findings.

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from 3175a9b to bb4b743 Compare September 11, 2026 19:46
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary — feat(builder): record build-loop state-read latency

Overall assessment: This PR is well-scoped and safe. The changes add optional InstrumentedStateProvider wrapping to both payload builders (flashblocks and native), gated behind the existing --engine.state-provider-metrics flag (off by default). No new error paths, panics, or fallible operations are introduced in block-production code.

Block production safety

This PR is block-production-sensitive (touches both payload builders). After tracing the changes against the block production review guide:

  • No new failure modes: The InstrumentedStateProvider wrapper is purely additive timing instrumentation. It delegates all state provider trait methods to the inner provider, adding only histogram recording. No ?, unwrap, or panic paths are introduced.
  • Gated and off by default: The wrapping is conditional on state_provider_metrics, which defaults to false. The flag is evaluated once per payload build, not per read.
  • No I/O boundary changes: No payload/data size limits or transport paths are affected.

Existing inline findings (from prior review run)

Three inline comments were previously posted and remain valid:

  1. crates/execution/node/src/node.rsstate_provider_metrics hardcoded to false on the standalone BasePayloadBuilder path, meaning --engine.state-provider-metrics has no effect when the multiplex layer is not used.
  2. Cargo.tomlreth-engine-tree is a heavyweight dependency pulled in for a single type. Worth tracking upstream for a lighter re-export.
  3. crates/execution/payload/Cargo.tomlreth-engine-tree breaks the waterfall sort convention.

No new findings

The remaining changes (config plumbing, builder method signatures, Default impls, Debug impls, test updates) are mechanically correct and consistent across both builders.

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from bb4b743 to 89a00c6 Compare September 11, 2026 19:50
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

This PR adds InstrumentedStateProvider wrapping to both payload builders (flashblocks and native) to measure per-read state fetch latency during block building. The feature is gated behind reth's existing --engine.state-provider-metrics flag and is off by default.

Block-production sensitivity

This PR touches both payload builder paths (flashblocks and native), which are block-production-sensitive. However, the instrumentation is:

  • Off by default (state_provider_metrics: false)
  • A transparent timing wrapper that delegates all calls to the underlying provider without altering error semantics or control flow
  • Evaluated once per payload build (the config check), not per read

No block-production halt/stall risk when disabled. When enabled, the wrapper adds timing overhead but does not introduce new error paths, panics, or failure modes beyond what the underlying state provider already produces.

Findings

No new findings beyond what has already been raised in existing inline comments. The prior comments on node.rs (hardcoded false instead of reading from engine config), reth-engine-tree dependency weight, and Cargo.toml sort order are the substantive items.

Wrapping order note

The InstrumentedStateProvider wraps outside CachedStateProvider (execution cache) but inside the per-job CachedReads. This means it times reads that reach the execution cache layer (including execution-cache hits) but not reads served entirely by the per-job CachedReads. The PR description acknowledges this layering, and the positioning is reasonable for measuring IO-bound cost during block building.

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from 89a00c6 to d400ac7 Compare September 11, 2026 20:01
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

PR: feat(builder): record build-loop state-read latency

This PR adds optional InstrumentedStateProvider wrapping to both the flashblocks and native payload builders, gated by reth's existing --engine.state-provider-metrics flag. The change is well-scoped and low-risk.

Block-production sensitivity

This PR touches payload builder paths, making it block-production-sensitive. However, no Critical findings were identified:

  • No new error paths: The only new code is a conditional Box::new(InstrumentedStateProvider::new(...)) wrapping, which cannot fail or panic.
  • Off by default: state_provider_metrics defaults to false everywhere. The gate is evaluated once per payload build, not per state read. Production is unaffected until operators explicitly enable the flag.
  • No I/O boundary changes: No new data transport, serialization, or size-limit concerns.
  • Proven wrapper: InstrumentedStateProvider is already used by reth's engine validation path.

Correctness

  • Both builder paths (flashblocks in crates/builder/core/src/flashblocks/payload.rs and native in crates/execution/payload/src/builder.rs) apply the wrapper consistently, outside CachedStateProvider — so the measured latency includes the execution cache but not reth's per-job CachedReads. This is intentional per the PR description.
  • The flag is correctly resolved from ctx.config().engine.state_provider_metrics in both the multiplex service builder (service_builder.rs:102) and the standalone node path (node.rs:1182).
  • The args.rs hardcode of false is the initial default, appropriately overridden later by the service builder.

No new findings

The existing inline comments from prior review runs cover the relevant observations (heavyweight dependency, sort order). No additional issues identified.

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from d400ac7 to 3ad4a73 Compare September 11, 2026 20:48
Comment thread crates/execution/payload/src/builder.rs Outdated
Comment on lines +1540 to +1541
Arc, Mutex,
atomic::{AtomicUsize, Ordering},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: Unused imports will fail CI.

AtomicUsize and Ordering are imported but never used in the test module. With -D warnings in CI, this will cause a compile error.

Suggested change
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
sync::Arc,

(Keep Mutex on a separate line if it's used elsewhere in the block, or merge as appropriate.)

@github-actions

Copy link
Copy Markdown
Contributor

<!-- CLAUDE_REVIEW_SUMMARY -->

Review Summary

PR: feat(builder): record build-loop state-read latency

This PR adds optional InstrumentedStateProvider wrapping to both the flashblocks and native payload builders, gated by reth's existing --engine.state-provider-metrics flag. The design is clean: the flag is resolved from the node config at service-builder time, the wrapper is applied outside CachedStateProvider so both cache hits and read-throughs are timed, and the test validates the wrapping order.

Findings

Severity File Issue
Low crates/execution/payload/src/builder.rs:1541 Unused imports AtomicUsize and Ordering in the test module will fail CI with -D warnings

Block Production Assessment

This PR touches block-production-sensitive paths (both payload builders' state provider setup). However, the change is low-risk:

  • The InstrumentedStateProvider is a transparent timing wrapper — it delegates all operations without introducing new error types or panics
  • The wrapping is gated by a boolean flag that defaults to false and is off across the fleet
  • The flag is evaluated once per payload build, not per read
  • No new I/O boundaries are introduced

No critical block-production findings.

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from 3ad4a73 to be9102d Compare September 14, 2026 19:23
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

Clean, well-scoped instrumentation change. No correctness, safety, or block-production concerns.

Block-production sensitivity: The PR touches both payload builder paths (flashblocks and native), but the change is gated behind state_provider_metrics (off by default, driven by --engine.state-provider-metrics). The gate is evaluated once per payload build, not per read. InstrumentedStateProvider is a transparent timing wrapper that delegates all trait methods to the inner provider — it introduces no new error paths, panics, or blocking operations. No block-production halt/stall risk.

Wrapping order is correct: InstrumentedStateProvider wraps outside CachedStateProvider in both builder paths, so it times the full per-read cost including cache hits. The new test validates this layering.

Config propagation is consistent: All three entry points (MultiplexingServiceBuilder, node.rs standalone path, and args.rs CLI) correctly resolve the flag from ctx.config().engine.state_provider_metrics before the builder runs.

No new findings beyond what has already been noted (dependency weight of reth-engine-tree).

@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from be9102d to e536a67 Compare September 14, 2026 20:47
reth-rpc-layer.workspace = true
reth-storage-api.workspace = true
reth-chain-state.workspace = true
reth-engine-tree.workspace = true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: reth-engine-tree is unused in base-builder-core.

This crate was previously importing CachedStateProvider etc. from reth-execution-cache directly in flashblocks/payload.rs, but that PR change delegates to base_execution_payload_builder::wrap_builder_state_provider instead. No source file in crates/builder/core/src/ imports reth_engine_tree, so this dependency is unused and will likely be flagged by cargo-udeps in CI.

Suggested change
reth-engine-tree.workspace = true

@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

This PR adds build-loop state-read latency instrumentation by wrapping state providers in InstrumentedStateProvider, gated behind reth's existing --engine.state-provider-metrics flag. The change is well-scoped and off by default.

Findings

Low: Unused reth-engine-tree dependency in base-builder-core (crates/builder/core/Cargo.toml:39)

The PR moves state-provider wrapping into base_execution_payload_builder::wrap_builder_state_provider, so crates/builder/core no longer imports anything from reth_engine_tree directly. The dependency in its Cargo.toml is unused and will likely be flagged by cargo-udeps.

Block-production assessment

This PR is block-production-sensitive (touches payload builder state-provider setup in both the native and flashblocks paths). No critical findings:

  • The instrumentation wrapper is applied outside the build loop's hot path (once per payload build, not per transaction).
  • When instrument=false (the default), the code path is identical to the previous behavior minus an extra Box::new() indirection that is negligible.
  • No new unwrap/expect/panic paths are introduced.
  • No new error propagation paths — InstrumentedStateProvider::new is infallible.
  • No new I/O boundaries introduced.
  • The state_provider_metrics flag is correctly resolved from ctx.config().engine.state_provider_metrics in all three entry points (multiplex service builder, node.rs standalone, flashblocks builder).

Notes on prior bot comments

  • The node.rs comment about hardcoded false appears stale — the current code already resolves from ctx.config().engine.state_provider_metrics.
  • The unused-imports comment (AtomicUsize/Ordering) appears resolved — those imports are not present in the current diff.

Comment thread crates/execution/payload/src/config.rs Outdated
}

/// Wraps a builder's state provider in the decorators the build loop needs.
pub fn wrap_builder_state_provider<S>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice helper fn to make constructing these easier and enforcing instrumentation is after-cache latency.

Wdyt about making this also a StateProvider wrapper like BuilderStateProvider::new(provider, cache, instrument)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it!

Times the payload builder's account, storage, and code reads so time blocked
on state during block building can be measured. Off by default.

Reth's InstrumentedStateProvider is constructed in exactly one place, the
engine's newPayload validation path, hardcoded to a source label of "engine".
Enabling --engine.state-provider-metrics on a sequencer therefore measures
block validation and reports nothing about the loop that builds blocks. Its
constructor already accepts a source label, so both Base payload builders now
wrap their own state provider and report under sync.state_provider with a
"builder" label.

The wrapper is applied outside CachedStateProvider so the recorded latency is
the build loop's total per-read cost, cache hits included. Wrapping it inside
would measure only reads that fall through to the database, and a working
cache would then show no improvement.

Scope, deliberately narrow. The reth per-job read cache (CachedReads) sits
above this wrapper, so what is timed is reads that miss that cache rather than
every state read the build loop issues. Those are the reads that can actually
touch storage, but the measured population shrinks as CachedReads warms, so
means are not directly comparable across configurations without accounting for
it.

The consumer today is the snapshot benchmark harness, which scrapes the
Prometheus endpoint directly and can group on the source label. Datadog
currently applies an empty tag allowlist to the reth metric families for
cardinality control, so builder and engine series would aggregate together
there; separating them in dashboards needs a tag-configuration change that is
not part of this commit.

Driven by reth's existing --engine.state-provider-metrics rather than a new
flag, so the builder and the validation path are instrumented by one switch.

Co-authored-by: OpenCode <opencode-noreply@coinbase.com>
@wbj-cb
wbj-cb force-pushed the willjohnston/base-165-build-loop-io-metrics branch from e536a67 to 4540a1e Compare September 15, 2026 17:54
@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

This PR adds InstrumentedStateProvider wrapping to both payload builders (flashblocks and native), gated behind the existing --engine.state-provider-metrics flag. The BuilderStateProvider abstraction in config.rs centralizes the provider-composition stack so the two builders cannot drift apart. No new error paths, panics, or I/O boundaries are introduced — this is purely observational instrumentation.

Block-production safety: No concerns. The instrumentation is off by default, evaluated once per payload build (not per read), and adds only timing measurement. All existing error paths pass through unchanged.

Findings

All substantive issues were already raised in prior inline comments. No new findings beyond those.

Notes on prior comments

  • The existing inline comment on crates/execution/node/src/node.rs suggesting to resolve from ctx appears stale — the current diff already uses ctx.config().engine.state_provider_metrics on that path.
  • The reth-engine-tree unused-dep flag for base-builder-core and the waterfall sort-order comments remain valid.

@wbj-cb
wbj-cb added this pull request to the merge queue Sep 15, 2026
Merged via the queue into main with commit ccbe98d Sep 15, 2026
27 checks passed
@wbj-cb
wbj-cb deleted the willjohnston/base-165-build-loop-io-metrics branch September 15, 2026 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants