feat(metering): replace state root time with state root gas in priority fee estimation#1934
feat(metering): replace state root time with state root gas in priority fee estimation#1934
Conversation
…ty fee estimation The builder already uses state root gas (a synthetic resource derived from gas_used and SR time) instead of raw state root time for block building budgets. This aligns the metering/estimation side to use the same metric. Adds a shared `compute_state_root_gas` function and `StateRootGasConfig` to `base-bundles` so both builder and metering use one implementation.
🟡 Heimdall Review Status
|
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
| let excess_us = state_root_time_us.saturating_sub(config.anchor_us); | ||
| let excess_ms = excess_us as f64 / 1000.0; | ||
| let multiplier = 1.0 + config.coefficient * excess_ms; | ||
| (gas_used as f64 * multiplier) as u64 |
There was a problem hiding this comment.
The coefficient field is user-configurable via --metering.state-root-gas-coefficient with no validation. If a negative value is passed, multiplier can go below zero (e.g. with large excess_ms), and the as u64 cast would silently clamp the result to 0. Consider either:
- Validating
coefficient >= 0.0and!coefficient.is_nan()at CLI parse / config construction time, or - Clamping
multiplierto>= 1.0here defensively (matching the formula's intent that penalty can only inflate gas).
This also applies to anchor_us — a u128 can be set to absurdly large values that would zero out the penalty for all transactions.
| /// Computes state root gas from gas used and state root time. | ||
| /// | ||
| /// `sr_gas = gas_used × (1 + K × max(0, SR_ms - anchor_ms))` | ||
| pub fn compute_state_root_gas( |
There was a problem hiding this comment.
Per the project conventions (CLAUDE.md): "Prefer placing functions as methods on a type rather than as bare functions, so the public API exports types, not loose functions."
This should be a method on StateRootGasConfig:
| pub fn compute_state_root_gas( | |
| impl StateRootGasConfig { | |
| /// Computes state root gas from gas used and state root time. | |
| /// | |
| /// `sr_gas = gas_used × (1 + K × max(0, SR_ms - anchor_ms))` | |
| pub fn compute(&self, gas_used: u64, state_root_time_us: u128) -> u64 { | |
| let excess_us = state_root_time_us.saturating_sub(self.anchor_us); | |
| let excess_ms = excess_us as f64 / 1000.0; | |
| let multiplier = 1.0 + self.coefficient * excess_ms; | |
| (gas_used as f64 * multiplier) as u64 | |
| } | |
| } |
This would change call sites from compute_state_root_gas(gas, time, &config) to config.compute(gas, time), and removes a bare function from the public API.
| /// State root gas coefficient (K) for priority fee estimation. | ||
| #[arg(long = "metering.state-root-gas-coefficient", default_value = "0.02")] | ||
| pub metering_state_root_gas_coefficient: f64, | ||
|
|
||
| /// State root gas anchor in microseconds for priority fee estimation. | ||
| #[arg(long = "metering.state-root-gas-anchor-us", default_value = "5000")] | ||
| pub metering_state_root_gas_anchor_us: u128, |
There was a problem hiding this comment.
These two CLI args have default_value but no requires = "enable_metering", unlike every other metering.* arg. They'll be silently accepted even without --enable-metering. Since they're only meaningful when metering is active, they should either:
- Add
requires = "enable_metering"(but that conflicts with having a default — clap would require the flag to be explicitly set), or - Be
Option<f64>/Option<u128>withrequires = "enable_metering", falling back to theStateRootGasConfig::default()values whenNone.
As-is, passing --metering.state-root-gas-coefficient 0.05 without --enable-metering is silently accepted, which could confuse operators into thinking they've configured something.
Review SummaryThe refactor cleanly extracts the state-root-gas formula into a shared Findings
No correctness issues found in the core logic — the formula extraction preserves the original behavior from |
Summary
meteredPriorityFeePerGaswith the builder's state root gas model (sr_gas = gas_used × (1 + K × max(0, SR_ms - anchor_ms))) instead of using raw state root time as the resource dimensioncompute_state_root_gasandStateRootGasConfigtobase-bundlesso both builder and metering use one implementationResourceKind::StateRootTime→StateRootGas, updates cache, collector, estimator, extension, and node CLI accordingly--metering.state-root-gas-limit,--metering.state-root-gas-coefficient,--metering.state-root-gas-anchor-usTest plan
base-meteringlib tests passbase-builder-corelib tests passbase-bundlestests passtype=routine
risk=low
impact=sev5