Skip to content

blockchain, node/cn: bound the hash-based header ancestor lookup - #1010

Open
hyunsooda wants to merge 2 commits into
kaiachain:devfrom
hyunsooda:fix/bounded-header-ancestor-lookup
Open

blockchain, node/cn: bound the hash-based header ancestor lookup#1010
hyunsooda wants to merge 2 commits into
kaiachain:devfrom
hyunsooda:fix/bounded-header-ancestor-lookup

Conversation

@hyunsooda

@hyunsooda hyunsooda commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Proposed changes

The hash-mode advance in the header request handler verified descent by walking Skip+1 parents and pre-allocating a Skip-sized hash slice, so a request whose answer is bounded by Amount could cost work proportional to the chain height. This ports go-ethereum's GetAncestor (#16946, released in v1.8.11, after Kaia's fork point): a canonical ancestor is resolved by number, and the non-canonical walk is bounded by a per-request budget. GetBlockHashesFromHash had no other caller and is removed, as upstream did.

Types of changes

  • 🐛 Bug fix
  • ✨ Non-hardfork changes (node upgrade not required)
  • 💥 Hardfork / consensus-breaking changes
  • 🧪 Test improvements
  • 🧰 CI / build tool
  • ♻️ Chore / Refactor / Non-functional changes

Checklist

  • 📖 I have read the CONTRIBUTING GUIDELINES doc
  • 📝 I have signed in the PR comment I have read the CLA Document and I hereby sign the CLA in first time contribute after having read CLA
  • 🟢 Lint and unit tests pass locally with my changes ($ make test)

Related issues

Further comments

The hash-mode advance in the header request handler verified descent by walking
Skip+1 parents and pre-allocating a Skip-sized hash slice, so a request whose
answer is bounded by Amount could cost work proportional to the chain height.
Ported go-ethereum's GetAncestor (#16946, released in v1.8.11, after Kaia's fork
point): a canonical ancestor is resolved by number, and the non-canonical walk
is bounded by a per-request budget. GetBlockHashesFromHash had no other caller
and is removed, as upstream did.

Constraint: the response shapes verified by TestGetBlockHeaders must not change
Rejected: cap query.Skip instead | the cost no longer depends on Skip, and an arbitrary cap risks rejecting legitimate skeleton sync requests
Directive: GetAncestor is a verbatim port; keep it diffable against upstream
Confidence: high
Scope-risk: narrow
Not-tested: upstream's companion change that fetches later origins with GetHeader(hash, number) instead of GetHeaderByHash is deliberately not ported

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hyunsooda hyunsooda self-assigned this Aug 4, 2026
ian0371
ian0371 previously approved these changes Aug 7, 2026
@ian0371
ian0371 requested a review from 2dvorak August 7, 2026 08:41

@2dvorak 2dvorak 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.

The new GetAncestor logic looks correct, but TestGetAncestor never actually exercises the budget running out. The 0xdead case stops after one hop (GetHeader returns nil), so if *maxNonCanonical == 0 { return ... } in headerchain.go:276 is never reached by any test — and that line is the whole point of the change.

Could we add a case for it?

TestGetAncestor's non-canonical case uses a hash with no header, so the walk
gives up on its first hop and never reaches the budget guard. Add a case with a
branch that outlasts the budget, and assert the walk ends with the budget spent
rather than on a missing header.

Confidence: high
Scope-risk: narrow

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hyunsooda

Copy link
Copy Markdown
Contributor Author

@2dvorak Thanks, added.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants