indexer/beacon: fix finalize-epoch panic when canonicalBlocks is empty#710
Merged
Merged
Conversation
The second argument was passed by all four callers but never read inside the function (it uses epochStats.dependentRoot exclusively). finalization.go called it with canonicalBlocks[0].Root, which panics when the epoch has no canonical blocks - the empty-canonical-blocks branch above sets dependentRoot via parent walk-back and falls through to the call site without populating canonicalBlocks. Removing the dead parameter fixes the panic and incidentally removes latent similar indexing at the other call sites.
pk910
approved these changes
May 21, 2026
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.
The crash
Observed in a local devnet run:
How I tracked it down
The stack pointed at
finalization.go:284, insidefinalizeEpoch. The offending line was:canonicalBlocks[0]on an empty slice is exactly the[0] with length 0panic.Earlier in
finalizeEpoch(lines 200-271), there's explicit handling for the case where the epoch has no canonical blocks: it walks backward through parent roots to find adependentRootandbreaks out of the loop. Execution then continues into the block at line 279+ withcanonicalBlocksstill empty - which is the bug.Before adding a
len(canonicalBlocks) > 0guard or substitutingdependentRoot, I checked whatensureEpochDependentStateactually does with that root:firstBlockRootis never referenced - the function usesepochStats.dependentRootexclusively. The parameter is dead.grepshows four call sites, all just computing a block root inline that the callee ignores:finalization.go:284-canonicalBlocks[0].Root(the one that panicked)pruning.go:120-blocks[0].Rootindexer.go:467-genesisBlock[0].Rootclient.go:307-currentBlock.RootThe fix
Drop the unused parameter from
ensureEpochDependentStateand remove the second argument from all four callers. This:canonicalBlocks[0].Rootexpression is gone.pruning.go:120(whereblockscomes from a map iteration and could theoretically be empty under future refactors).len(canonicalBlocks) > 0 ? ... : dependentRootguard - papers over the issue while still passing a value the callee discards.5 files changed, 5 insertions, 5 deletions.
Test plan
go build ./...compiles.