Skip to content

fix: resolve eth_feeHistory base fee per query instead of pinning a container clone - #1295

Merged
MavenRain merged 8 commits into
mainfrom
fix/1282-rpc-base-fee-lookup
Sep 4, 2026
Merged

fix: resolve eth_feeHistory base fee per query instead of pinning a container clone#1295
MavenRain merged 8 commits into
mainfrom
fix/1282-rpc-base-fee-lookup

Conversation

@MavenRain

Copy link
Copy Markdown
Contributor

Closes #1282.

Problem

The RPC server received a one-time BaseFeeContainer clone at worker startup and held it for its whole life. GasAccumulator::set_num_workers truncates the per-worker slots on a shrink and creates fresh default containers on a regrow, so for worker ids >= 1 the held clone detached from the slot the accumulator owns. From then on the epoch schedule (EpochBaseFees::apply) wrote the new container while eth_feeHistory and eth_blobBaseFee kept quoting the orphaned one: a permanently stale base fee for the rest of the node's life. No attacker is required; a routine committee-driven worker-count shrink and regrow is enough.

Fix

Hand the RPC server a lookup, not a clone (option 1 from the issue). A new WorkerBaseFee handle wraps the GasAccumulator plus a WorkerId and resolves the worker's slot on every read, so a long-lived holder always sees the container the accumulator currently owns. The handle is total: a read while the slot is truncated answers MIN_PROTOCOL_BASE_FEE, which is exactly the value the regrown slot is reborn with, so a fee quote never panics a running node (in contrast to inc_block, whose out-of-range panic is by design on the consensus path).

initialize_worker_components and get_rpc_server now take the handle instead of a pinned container clone; the read site in FeeHistoryWithEpochBaseFee is unchanged (self.base_fee.base_fee() at query time).

Changes

  • crates/types/src/gas_accumulator.rs: add WorkerBaseFee handle and GasAccumulator::worker_base_fee; document the staleness hazard on GasAccumulator::base_fee
  • crates/tn-reth/src/rpc_fee_history.rs: FeeHistoryWithEpochBaseFee stores a WorkerBaseFee instead of a BaseFeeContainer clone
  • crates/tn-reth/src/env/rpc.rs: get_rpc_server takes the handle; add a shrink-and-regrow RPC regression test
  • crates/node/src/engine/inner.rs, crates/node/src/engine/mod.rs: initialize_worker_components takes the handle
  • crates/node/src/manager/node/start_epoch.rs: build the handle from the accumulator and pass it through
  • crates/node/tests/it/main.rs: source the test's base fee through the accumulator and pass a handle

Testing

  • Three new tn-types unit tests: held_container_clone_goes_stale_after_shrink_and_regrow (reproduces the bug and shows the handle survives it), worker_base_fee_answers_rebirth_default_while_truncated, and worker_base_fee_and_clone_agree_across_a_pure_grow.
  • New tn-reth regression test test_fee_history_quote_tracks_accumulator_across_shrink_and_regrow: builds the production RPC server for worker 1 of 2, shrinks to 1 and regrows to 2, writes a fresh epoch fee, and asserts eth_feeHistory quotes the live fee.
  • Each new test was confirmed by in-diff mutation (reverting the per-read resolution made the stale-clone and RPC tests fail; changing the truncated-slot default made the rebirth test fail), then the fix was restored.
  • Local static ladder under the house gates: nightly cargo fmt -- --check, cargo +1.94 check --workspace --all-targets, scoped clippy --no-deps on the touched crates.

…ontainer clone

Closes #1282. The RPC server held a one-time BaseFeeContainer clone for
its worker. GasAccumulator::set_num_workers truncates slots on shrink and
creates fresh default containers on regrow, so for worker ids >= 1 the
held clone detached from the slot the epoch schedule writes, and
eth_feeHistory / eth_blobBaseFee quoted a permanently stale fee.

Add WorkerBaseFee, a per-query handle that resolves the worker's slot
through the accumulator on every read, and pass it (instead of a clone)
through initialize_worker_components into get_rpc_server and
FeeHistoryWithEpochBaseFee. A read while the slot is truncated answers
MIN_PROTOCOL_BASE_FEE, the value the regrown slot is reborn with, so a
fee quote never panics a running node.

Tests: three tn-types unit tests (stale-clone repro, truncated-slot
default, pure-grow agreement) and a tn-reth RPC regression test that
shrinks and regrows the worker set and asserts eth_feeHistory quotes the
live fee. Each new test confirmed by in-diff mutation.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
The merge of main brought in the eth_syncing test fix (#1285), which
passes BaseFeeContainer::default() to get_rpc_server. On this branch
get_rpc_server takes a WorkerBaseFee handle (#1282) and the test module
no longer imports BaseFeeContainer, so the workspace test build failed
with E0433. Hand the test a one-worker accumulator handle, matching the
other fee-history tests in the module.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
@MavenRain MavenRain self-assigned this Aug 27, 2026
@MavenRain
MavenRain deployed to merge-into-main August 27, 2026 15:08 — with GitHub Actions Active
sstanfield
sstanfield previously approved these changes Aug 27, 2026
…-lookup

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

# Conflicts:
#	crates/node/src/engine/mod.rs
#	crates/node/src/manager/node/start_epoch.rs
#	crates/tn-reth/src/env/rpc.rs
@MavenRain MavenRain changed the title fix: resolve eth_feeHistory base fee per query instead of pinning a contianer clone fix: resolve eth_feeHistory base fee per query instead of pinning a container clone Sep 4, 2026
…-lookup

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
…-lookup

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>

# Conflicts:
#	crates/node/src/engine/inner.rs
#	crates/node/src/engine/mod.rs

@grantkee grantkee left a comment

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.

LGTM!

@MavenRain
MavenRain added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit d6c758c Sep 4, 2026
18 of 20 checks passed
@MavenRain
MavenRain deleted the fix/1282-rpc-base-fee-lookup branch September 4, 2026 23:41
MavenRain added a commit that referenced this pull request Sep 5, 2026
…c-endpoints

Round 6: main 66e0d14, 8 commits past the round-5 target 87364b2 (#1270,
#1295, #1291, #1288, #1280, #1268, #1294 and one fork merge; 38 files).

Two files overlap the PR. engine/inner.rs auto-merged: main's WorkerBaseFee
handle at get_rpc_server, the PR's worker_id at start_rpc. env/rpc.rs needed
two hand reconciliations because #1295 changed get_rpc_server to take a
WorkerBaseFee handle instead of a BaseFeeContainer clone:

- The tn_types import: keep the PR's WorkerId and take main's WorkerBaseFee.
- The PR's start_worker_rpc test helper: take a &GasAccumulator and pass
  worker_id's container to init_txn_pool and its worker_base_fee handle to
  get_rpc_server. The two-worker IPC test builds one GasAccumulator::new(2)
  so each worker resolves its own slot, matching main's
  fee_history_methods_for_worker helper.

Every other file is byte-identical to origin/main.

Signed-off-by: Onyeka Obi <softwareengineerasaservant@isurvivable.cv>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants