Skip to content

feat: ensure p2p DAHint within limits#3128

Merged
julienrbrt merged 3 commits intomainfrom
alex/dahints_limit
Mar 3, 2026
Merged

feat: ensure p2p DAHint within limits#3128
julienrbrt merged 3 commits intomainfrom
alex/dahints_limit

Conversation

@alpe
Copy link
Contributor

@alpe alpe commented Mar 3, 2026

Overview

Prevent malicious da hints

Summary by CodeRabbit

  • Bug Fixes
    • Strengthened validation of network DA height hints to ignore unreasonable values, skipping hints that are already past known DA height and enforcing a 200-block drift limit with graceful fallback if current DA height cannot be fetched.
  • Tests
    • Added tests covering rejection of malicious hints, acceptance of valid hints, and skipping of already-fetched hints; integrated a mock/real DA retriever for more realistic validation scenarios.
  • Documentation
    • Added changelog entry describing the new DA hint validation behavior.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 3, 2026

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedMar 3, 2026, 10:31 AM

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 3, 2026

📝 Walkthrough

Walkthrough

This change adds conditional validation of P2P DA height hints: if any hint exceeds the current DA height by more than 200, the syncer fetches the latest DA height (with a 500ms timeout) to filter out unreasonable hints, logging and falling back if the fetch fails.

Changes

Cohort / File(s) Summary
DA Height Hint Validation
block/internal/syncing/syncer.go
Adds early pass computing currentDAHeight, determines when validation is needed (>200 drift), fetches latest DA height with 500ms timeout, skips hints already past currentDAHeight, and filters/ignores hints that exceed latestDAHeight; logs and disables strict validation on fetch failure.
Validation Test Coverage
block/internal/syncing/syncer_test.go
Rewires tests to inject a mock DA client and real DARetriever where needed; adds tests for rejecting unreasonable hints (e.g., math.MaxUint64), accepting valid hints, and skipping hints already fetched; adjusts store height setup for relevant scenarios.
Changelog
CHANGELOG.md
Adds Unreleased entry documenting the new DA height hint validation rule to prevent malicious peers from triggering excessive catchup.

Sequence Diagram

sequenceDiagram
    participant P2P as P2P Network
    participant Syncer
    participant DAClient as DA Client
    participant DARetriever

    P2P->>Syncer: Emit event with DA height hints
    Syncer->>Syncer: compute currentDAHeight (atomic load)
    Note over Syncer: If any hint > currentDAHeight + 200 -> validation needed
    alt Validation needed
        Syncer->>DAClient: GetLatestDAHeight() (500ms timeout)
        alt Fetch successful
            DAClient-->>Syncer: latestDAHeight
            Syncer->>Syncer: filter out hints > latestDAHeight
        else Fetch failed
            Syncer-->>Syncer: log warning, disable strict validation
        end
    end
    loop For each remaining hint
        Syncer->>Syncer: skip if hint <= daRetrieverHeight
        Syncer->>DARetriever: queue hint for priority DA retrieval
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hints hop in from near and far,
I check their heights before they spar.
If they leap too far, I take a peek,
And only queue the hints that speak. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description is minimal, providing only 'Prevent malicious da hints' without detailed context, background, or issue links as specified in the template. Expand the description with more details about the problem being solved, why this validation is necessary, and link to any related issue (e.g., Closes #).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: ensure p2p DAHint within limits' directly reflects the main change: adding validation to prevent malicious DA height hints from exceeding reasonable limits.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch alex/dahints_limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Contributor

claude bot commented Mar 3, 2026

Claude finished @alpe's task in 2m 17s —— View job


Code Review

  • Gather context and read changed files
  • Analyze code quality & architecture
  • Review error handling & resilience
  • Review security considerations
  • Review performance & resource efficiency
  • Review testing & reliability

Good targeted fix. The approach — lazy fetch of latest DA height only when a hint is suspiciously far ahead — is the right trade-off between security and RPC load. A few items to address:


Bug: Missing test for the validation bypass on DA client error

block/internal/syncing/syncer.go:648 — When GetLatestDAHeight fails, needsValidation = false, so all hints are accepted regardless of size (the MaxUint64 case would pass through). This is intentional with the comment "height is checked in the daRetriever", but the security boundary of this PR is: a malicious peer cannot trigger runaway catchup. The fallback breaks that guarantee if the DA client is temporarily unavailable.

There's no test covering the error path. Consider adding one that asserts the fallback behavior explicitly, so the contract is documented and regressions are caught. Fix this →


Missing test: hint exceeds drift but is actually valid

block/internal/syncing/syncer_test.go:774TestProcessHeightEvent_AcceptsValidDAHint uses hint=50 with currentDAHeight=0, so 50 > 0+200 is false — needsValidation is never set to true and GetLatestDAHeight is never called (hence .Maybe() on the mock). This test exercises only the "within drift threshold" code path, not the "validation triggered and hint passes" path.

A complete test matrix needs a case where hint exceeds the threshold but hint <= latestDAHeight. Example: daRetrieverHeight=0, hint=201, latestDAHeight=300 → validation fires, hint passes. Fix this →


Warn log noise during initial sync

block/internal/syncing/syncer.go:659 — Seconding @julienrbrt's comment. During initial sync, peers legitimately ahead of us will send hints that exceed currentDAHeight + 200, and if those hints also exceed the actual latest DA height (e.g., because our GetLatestDAHeight call races with tip updates, or we're just catching up), we'll spam warnings.

Consider Debug level here and only escalate to Warn for hints that exceed latestDAHeight by a significant margin (e.g., a multiple, not just by 1). A peer sending hint at latestDAHeight + 1 is probably fine; one sending MaxUint64 is clearly malicious.


Integer overflow edge case (minor)

block/internal/syncing/syncer.go:634:

if h > currentDAHeight+daHintMaxDrift {

If currentDAHeight is near math.MaxUint64 (not realistic, but worth noting), this addition wraps to a small value and the condition misfires. A safe alternative:

if currentDAHeight+daHintMaxDrift < currentDAHeight || h > currentDAHeight+daHintMaxDrift {

Or use a subtraction-based check:

if h > daHintMaxDrift && h-daHintMaxDrift > currentDAHeight {

Timeout already applied (no action needed)

The coderabbitai suggestion about bounding the GetLatestDAHeight call with a context timeout is already implemented at block/internal/syncing/syncer.go:643 with a 500ms deadline. No change needed.


Changelog

The entry at CHANGELOG.md:29 belongs under Changes. Given this is also a security fix, consider moving it to a Security or Bug Fixes section for better discoverability. Minor suggestion.


Summary: The core logic is correct and the staged validation approach avoids excessive RPC traffic. The two missing test paths (error fallback + hint-exceeds-drift-but-valid) are the main asks before merging. The warn log noise is a usability issue worth addressing.

currentDAHeight := s.daRetrieverHeight.Load()

// Only fetch the latest DA height if any hint is suspiciously far ahead.
const daHintMaxDrift = uint64(200)
Copy link
Contributor Author

@alpe alpe Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is an arbitrary value to ensure that we do not hit DA height request on every message

}

if needsValidation && daHeightHint > latestDAHeight {
s.logger.Warn().Uint64("da_height_hint", daHeightHint).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may be really noisy during syncing, when we are receiving p2p blocks very far ahead.
I think the 200 is too reasonable, or the log line should be a debug log instead of warning.

@codecov
Copy link

codecov bot commented Mar 3, 2026

Codecov Report

❌ Patch coverage is 86.36364% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.76%. Comparing base (e877782) to head (ba1e3cf).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
block/internal/syncing/syncer.go 86.36% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3128   +/-   ##
=======================================
  Coverage   60.76%   60.76%           
=======================================
  Files         113      113           
  Lines       11672    11693   +21     
=======================================
+ Hits         7092     7105   +13     
- Misses       3777     3785    +8     
  Partials      803      803           
Flag Coverage Δ
combined 60.76% <86.36%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
block/internal/syncing/syncer_test.go (1)

717-772: Add one explicit test for latest-height fetch failure fallback.

Please add a case where GetLatestDAHeight returns an error and assert the intended fallback behavior (current code disables strict validation in that branch). This prevents regressions in the failure-mode contract.

Also applies to: 787-829

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@block/internal/syncing/syncer_test.go` around lines 717 - 772, Add a new test
variant of TestProcessHeightEvent_RejectsUnreasonableDAHint where the mock DA
client’s GetLatestDAHeight returns an error (e.g.,
mockDAClient.EXPECT().GetLatestDAHeight(...).Return(uint64(0),
errors.New("fail")).Maybe()), then call s.processHeightEvent(...) and assert the
fallback behavior: because strict validation is disabled on DA fetch error, the
hint should be accepted (verify s.daRetriever.PopPriorityHeight() returns the
hinted height rather than 0). Use the same setup flow (NewSyncer,
initializeState, set store height) and reference the existing test function
name, mockDAClient.GetLatestDAHeight, s.processHeightEvent, and
s.daRetriever.PopPriorityHeight to locate where to add the case.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@block/internal/syncing/syncer.go`:
- Around line 641-647: The call to s.daClient.GetLatestDAHeight(ctx) in the hot
path (inside processHeightEvent handling where needsValidation is true) must be
bounded by a short timeout to avoid stalling; replace the long-lived ctx with a
short-lived context (use context.WithTimeout, e.g., a few hundred milliseconds
or a configured short duration), defer the cancel, and pass the derived ctx to
s.daClient.GetLatestDAHeight; keep the existing error handling (s.logger.Warn...
and needsValidation = false) unchanged so behavior remains the same on timeout
or error.

---

Nitpick comments:
In `@block/internal/syncing/syncer_test.go`:
- Around line 717-772: Add a new test variant of
TestProcessHeightEvent_RejectsUnreasonableDAHint where the mock DA client’s
GetLatestDAHeight returns an error (e.g.,
mockDAClient.EXPECT().GetLatestDAHeight(...).Return(uint64(0),
errors.New("fail")).Maybe()), then call s.processHeightEvent(...) and assert the
fallback behavior: because strict validation is disabled on DA fetch error, the
hint should be accepted (verify s.daRetriever.PopPriorityHeight() returns the
hinted height rather than 0). Use the same setup flow (NewSyncer,
initializeState, set store height) and reference the existing test function
name, mockDAClient.GetLatestDAHeight, s.processHeightEvent, and
s.daRetriever.PopPriorityHeight to locate where to add the case.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e877782 and 7dd74a2.

📒 Files selected for processing (2)
  • block/internal/syncing/syncer.go
  • block/internal/syncing/syncer_test.go

@julienrbrt
Copy link
Member

Can we add a changelog as well please :)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 29: The CHANGELOG entry contains an extra space before the period in the
sentence "Validate P2P DA height hints against the latest known DA height to
prevent malicious peers from triggering runaway catchup ."; remove the stray
space so the sentence ends "runaway catchup." (Update the exact line text in
CHANGELOG.md to eliminate the space before the period.)

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dd74a2 and ba1e3cf.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • block/internal/syncing/syncer.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • block/internal/syncing/syncer.go

- Store pending blocks separately from executed blocks key. [#3073](https://github.com/evstack/ev-node/pull/3073)
- Fixes issues with force inclusion verification on sync nodes. [#3057](https://github.com/evstack/ev-node/pull/3057)
- Add flag to `local-da` to produce empty DA blocks (closer to the real system). [#3057](https://github.com/evstack/ev-node/pull/3057)
- Validate P2P DA height hints against the latest known DA height to prevent malicious peers from triggering runaway catchup . [#3128](https://github.com/evstack/ev-node/pull/3128)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix spacing before period.

There's an extra space before the period in "catchup ." — it should be "catchup."

✏️ Proposed fix
-- Validate P2P DA height hints against the latest known DA height to prevent malicious peers from triggering runaway catchup . [`#3128`](https://github.com/evstack/ev-node/pull/3128)
+- Validate P2P DA height hints against the latest known DA height to prevent malicious peers from triggering runaway catchup. [`#3128`](https://github.com/evstack/ev-node/pull/3128)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Validate P2P DA height hints against the latest known DA height to prevent malicious peers from triggering runaway catchup . [#3128](https://github.com/evstack/ev-node/pull/3128)
- Validate P2P DA height hints against the latest known DA height to prevent malicious peers from triggering runaway catchup. [`#3128`](https://github.com/evstack/ev-node/pull/3128)
🧰 Tools
🪛 LanguageTool

[grammar] ~29-~29: Ensure spelling is correct
Context: ...malicious peers from triggering runaway catchup . [#3128](https://github.com/evstack/ev...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 29, The CHANGELOG entry contains an extra space before
the period in the sentence "Validate P2P DA height hints against the latest
known DA height to prevent malicious peers from triggering runaway catchup .";
remove the stray space so the sentence ends "runaway catchup." (Update the exact
line text in CHANGELOG.md to eliminate the space before the period.)

@alpe alpe requested a review from julienrbrt March 3, 2026 10:41
@julienrbrt julienrbrt merged commit 042b75a into main Mar 3, 2026
32 of 33 checks passed
@julienrbrt julienrbrt deleted the alex/dahints_limit branch March 3, 2026 11:25
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.

2 participants