fix: guard against empty baseFeePerGas in FeeHistoryEstimator.RefreshDynamicPrice#498
Open
snowkide wants to merge 1 commit into
Open
Conversation
…DynamicPrice RefreshDynamicPrice indexed into feeHistory.BaseFee with feeHistory.BaseFee[len(feeHistory.BaseFee)-1] without checking the slice was non-empty. If an eth_feeHistory RPC call returns err == nil with an empty baseFeePerGas array (some providers do this instead of returning an error, e.g. on a deep/archive range they can't serve, or on chains without full EIP-1559 support), this indexes with -1 and panics with "runtime error: index out of range [-1]". Because this runs in an unrecovered background goroutine, the panic crashes the entire node process, not just the affected chain's estimator. We hit this in production across three separate CCIP node deployments running different chainlink-evm builds (Feb, Mar, and Jun 2026 pseudo-versions), recurring over several weeks. Reproduced deterministically with a unit test using the package's own mocks, feeding an empty BaseFee. This adds a bounds check that returns a normal error instead, matching the existing handling for the Reward slice a few lines below (which already guards against short/empty per-block reward arrays).
cdd66e8 to
15d2257
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FeeHistoryEstimator.RefreshDynamicPriceindexes intofeeHistory.BaseFeewithfeeHistory.BaseFee[len(feeHistory.BaseFee)-1]without checking the slice isnon-empty. If an
eth_feeHistoryRPC call returnserr == nilwith an emptybaseFeePerGasarray — which we've observed real providers do instead ofreturning an error, e.g. on a deep/archive block range they can't serve, or on
chains without full EIP-1559 support — this indexes with
-1and panics:Because
RefreshDynamicPriceruns in an unrecovered background goroutine(
FeeHistoryEstimator.run), this panic is fatal to the entire node process —not just the affected chain's gas estimator. Every chain and job the node
services goes down until the process is restarted.
Note the
Rewardslice a few lines below this is already defended(
if len(reward) < 2 { continue });BaseFeewas not.Impact observed
We hit this in production across three separate CCIP node deployments running
different chainlink-evm builds (pseudo-versions from Feb, Mar, and Jun 2026),
on different physical hosts, recurring in bursts over several weeks before we
root-caused it. Log history shows it's been reachable since at least the
February 2026 build.
Fix
Added a bounds check right after the existing RPC-error check, returning a
descriptive error instead of indexing into the empty slice — the same
treatment already given to a malformed
Rewardresponse a few lines down.Testing
Added
TestRefreshDynamicPrice_EmptyBaseFee, which reproduces the panicagainst the real package using its own mocks (feeding an empty
BaseFeewitherr == nil) and confirms the fix returns a graceful error instead. Fullexisting
pkg/gastest suite passes unmodified;go vetis clean.