Call SetFinality when processing a milestone#2226
Conversation
|
|
@claude review once |
|
Claude finished @pratikspatil024's task in 1m 33s —— View job Review: Call SetFinalized when processing a milestone
Overall: This is a well-motivated, clean fix. The PR description accurately identifies the gap — Bor's milestone finality was never written to CorrectnessLooks good. The change is placed after Thread safety is fine: Persistence across restarts
Suggestions (non-blocking)1. Consider also calling In the Engine API path ( For Bor, milestones represent the strongest finality signal, so the finalized block is by definition also "safe." Adding if header := eth.blockchain.GetHeaderByHash(hash); header != nil {
eth.blockchain.SetFinalized(header)
eth.blockchain.SetSafe(header)
}2. Re-processing on the 1-second ticker
3. No monotonicity check There's no check that the new finalized block number is ≥ the current finalized block number. In practice this should always hold because milestones are monotonically increasing, but a defensive check would make the invariant explicit: if header := eth.blockchain.GetHeaderByHash(hash); header != nil {
if current := eth.blockchain.CurrentFinalBlock(); current == nil || header.Number.Uint64() > current.Number.Uint64() {
eth.blockchain.SetFinalized(header)
}
}Again, non-blocking — the milestone protocol already guarantees monotonicity. No issues found
LGTM with the optional |
There was a problem hiding this comment.
LGTM — small, defensive 5-line change that mirrors the existing ProcessMilestone call.
Extended reasoning...
Overview
Single-file, 5-line addition in eth/handler_bor.go. After the existing h.downloader.ProcessMilestone(num, hash) call in handleMilestone, the PR fetches the corresponding header via GetHeaderByHash and, if non-nil, calls eth.blockchain.SetFinalized(header). This brings bor's finality signal into the canonical geth BlockChain API (CurrentFinalBlock()), which was previously always nil on bor since the only upstream writer is the (unused) Engine API.
Security risks
None identified. SetFinalized is a simple setter that stores the header pointer, writes the finalized hash to rawdb, and updates a metrics gauge — no consensus or state-transition impact. GetHeaderByHash is a safe read and returns nil for unknown hashes; the unknown-hash case is handled as a defensive no-op. No new external inputs, auth, or crypto surface area.
Level of scrutiny
Low. The change is purely additive metadata propagation: it makes existing geth machinery (metrics, the SetHead safety guard, tracer-visible finality) reflect a value bor already computes elsewhere (getFinalizedBlockNumber via the downloader whitelist). The two observable effects called out in the description (gauge advancing, SetHead invalidated finalized block log becoming reachable on rewinds below a milestone) are intended and benign.
Other factors
SonarQube quality gate passed. Maintainer (pratikspatil024) explicitly requested review on this PR. The change has been manually tested on mainnet per the PR description. No bugs flagged by the bug hunting system.



Description
bc.CurrentFinalBlock()is currently alwaysnilon bor nodes, because the only writers ofblockchain.SetFinalizedin upstream geth are the Engine API (eth/catalyst), which bor does not use. As a result:headFinalizedBlockGauge/headSafeBlockGaugemetrics never advance.SetHeadsafety guard atcore/blockchain.gothat refuses to rewind past a finalized block is dead code on bor — it's gated onbc.CurrentFinalBlock() != nil.BlockChainAPI to read finality — live tracers (core.BlockchainLogger), external indexers, downstream tooling — sees no finality on bor, even though Heimdall milestones are by definition the chain's strongest finality signal.Bor already knows what's finalized:
eth/api.go:getFinalizedBlockNumberderives it fromDownloader().GetWhitelistedMilestone()to answer theeth_getBlockByNumber("finalized")RPC. This PR closes the gap by also writing that information into the canonical place (bc.SetFinalized) the moment a milestone has been successfully processed inethHandler.handleMilestone, right after the existingProcessMilestonecall. The header is fetched defensively viaGetHeaderByHash; an unknown hash is a no-op.Changes
Breaking changes
None at the protocol or RPC level. Two observable effects:
headFinalizedBlockGaugeandheadSafeBlockGaugewill now advance on bor nodes (they were stuck at 0). Dashboards may need to acknowledge this.SetHead invalidated finalized blocklog path incore/blockchain.gobecomes reachable. By construction it only triggers on a rewind below a confirmed Heimdall milestone, which is already a pathological situation — making it visible is the desired behavior.he JSON-RPC
eth_getBlockByNumber("finalized")response is unchanged: it still flows throughgetFinalizedBlockNumberand the downloader whitelist.Testing
eth_getBlockByNumber("finalized")continues to return the latest milestoned block (unchanged).headFinalizedBlockGaugeadvances after the first milestone is processed (new behavior).BlockChain.CurrentFinalBlock()returns the milestone header (new behavior, previouslynil).