Skip to content

Conversation

@julienrbrt
Copy link
Member

@julienrbrt julienrbrt commented Dec 12, 2025

Overview

Temporary fix until #2891.
After #2891 the verification for p2p blocks will be done in the background.

ref: #2906

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @julienrbrt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a temporary fix to optimize block processing by conditionally skipping forced transaction checks. Specifically, these checks will now only be applied to blocks sourced from the Data Availability layer, while blocks received via the peer-to-peer network will bypass this verification step. This adjustment is a stopgap measure designed to improve syncing behavior until a more robust background verification mechanism for P2P blocks is implemented in a future update.

Highlights

  • Conditional Forced Transaction Checks: The verifyForcedInclusionTxs function is now only executed for blocks originating from the Data Availability (DA) layer (common.SourceDA).
  • P2P Block Optimization: Forced transaction checks are skipped for blocks received via the P2P network, addressing a temporary performance or correctness issue.
  • Temporary Solution: This change is explicitly marked as a temporary fix, pending the merge of PR feat: DA hints in p2p #2891, which will introduce background verification for P2P blocks.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 12, 2025

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedDec 15, 2025, 11:42 AM

@claude
Copy link
Contributor

claude bot commented Dec 12, 2025

Claude finished @julienrbrt's task in 2m 6s —— View job


Comprehensive Code Review

I've analyzed PR #2922 which introduces a temporary fix to skip forced transaction checks for P2P blocks. Here's my detailed review across multiple dimensions:


Summary of Changes

The PR modifies block/internal/syncing/syncer.go to conditionally execute verifyForcedInclusionTxs only for blocks sourced from the Data Availability (DA) layer, skipping verification for P2P-sourced blocks. This is explicitly marked as a temporary solution pending PR #2891.

Key changes:

  • Lines 594-602: Wrapped verifyForcedInclusionTxs call in a conditional check for event.Source == common.SourceDA
  • Minor refactors: GetLastState()getLastState() (unexported), removed defensive cloning in getter, fixed DA height check for zero value

1. Code Quality & Architecture

✅ Strengths:

  • Clear separation of concerns with the EventSource type distinguishing DA vs P2P blocks
  • Explicit TODO comment with PR reference makes the temporary nature obvious
  • Minimal change scope reduces risk

⚠️ Concerns:

a) Inconsistent Error Handling Pattern (High Priority)

At lines 595-601, there's a problematic error handling pattern flagged by Gemini Code Assist:

if err := s.verifyForcedInclusionTxs(currentState, data); err != nil {
    s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("forced inclusion verification failed")
    if errors.Is(err, errMaliciousProposer) {
        s.cache.RemoveHeaderDAIncluded(headerHash)
        return err
    }
}

Issue: Non-errMaliciousProposer errors are logged but swallowed, allowing block processing to continue despite verification failure. This could occur if:

  • DA layer is temporarily unreachable
  • Transient network failures
  • Timeout errors from fiRetriever.RetrieveForcedIncludedTxs

Impact: A node could process a block without properly verifying forced inclusion requirements, potentially accepting blocks that should be rejected.

Recommendation: Return all errors from verifyForcedInclusionTxs. The caller (processHeightEvent) already has retry logic via s.cache.SetPendingEvent(height, event) in the default error case (lines 533-534), which will reschedule verification attempts.

if event.Source == common.SourceDA {
    if err := s.verifyForcedInclusionTxs(currentState, data); err != nil {
        s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("forced inclusion verification failed")
        s.cache.RemoveHeaderDAIncluded(headerHash)
        return err
    }
}

b) Architectural Concern: Dual Code Paths

The PR creates divergent verification paths for DA vs P2P blocks:

  • DA blocks: Full validation including forced inclusion checks
  • P2P blocks: Skip forced inclusion checks

While this is marked as temporary, it introduces a trust model inconsistency where P2P blocks are assumed valid without verification. This is acceptable for a temporary fix but should be closely tracked.

c) State Mutation Removed (Minor)

The change from GetLastState() (with defensive cloning) to getLastState() (direct pointer dereference) removes memory safety guarantees. While this may be intentional for performance, ensure that callers don't mutate the returned state, as it now shares the underlying data.


2. Error Handling & Resilience

⚠️ Critical Issue: As detailed above, transient errors in forced inclusion verification are silently ignored, reducing system resilience.

Additional Observations:

  • Lines 523-534: Good error classification with specific handling for errInvalidBlock, errMaliciousProposer, and errInvalidState
  • Line 527: Appropriate escalation of malicious proposer to critical error channel
  • Missing: No test coverage for the new conditional behavior (see Testing section)

3. Security Implications

⚠️ Medium Risk:

Temporary Security Degradation:

  • P2P blocks bypass forced inclusion verification entirely
  • Nodes syncing via P2P during this period could accept blocks that violate forced inclusion requirements
  • This weakens the censorship-resistance guarantee of the forced inclusion mechanism

Mitigation Context:

  • This is explicitly temporary pending background verification in PR feat: DA hints in p2p #2891
  • DA-sourced blocks (canonical source of truth) still receive full verification
  • The risk is bounded to the sync window between P2P receipt and DA confirmation

Recommendation:

  • Ensure PR feat: DA hints in p2p #2891 is prioritized and merged promptly
  • Consider adding monitoring/metrics to track P2P vs DA block sources
  • Document this security tradeoff in release notes if shipping

4. Performance & Resource Efficiency

✅ Positive Impact:

  • Skipping forced inclusion checks for P2P blocks reduces DA retrieval overhead during fast sync
  • The getLastState() change (removing cloning) reduces memory allocations in hot path

⚠️ Potential Issue:

  • No benchmarking provided to quantify performance improvement
  • The removed defensive cloning in getLastState() could lead to subtle bugs if any caller mutates the state

5. Testing & Reliability

❌ Critical Gap:

Zero Test Coverage for New Behavior:

  • Codecov reports 0% patch coverage (7 lines missing coverage)
  • No tests verify that P2P blocks skip verifyForcedInclusionTxs
  • No tests verify that DA blocks still call verifyForcedInclusionTxs
  • Existing tests in syncer_forced_inclusion_test.go may not cover the source differentiation

Recommended Test Cases:

// Test: P2P blocks skip forced inclusion verification
func TestTrySyncNextBlock_P2PSkipsForcedInclusion(t *testing.T) {
    // Setup syncer with forced inclusion configured
    // Create event with Source = common.SourceP2P
    // Verify verifyForcedInclusionTxs is not called (use mock)
}

// Test: DA blocks still verify forced inclusion
func TestTrySyncNextBlock_DAVerifiesForcedInclusion(t *testing.T) {
    // Setup syncer with forced inclusion configured
    // Create event with Source = common.SourceDA
    // Verify verifyForcedInclusionTxs is called (use mock)
}

// Test: Transient errors in forced inclusion are properly handled
func TestTrySyncNextBlock_ForcedInclusionTransientErrorRetries(t *testing.T) {
    // Setup syncer with DA source event
    // Mock verifyForcedInclusionTxs to return transient error
    // Verify event is rescheduled via cache.SetPendingEvent
}

6. Documentation & Developer Experience

✅ Good:

  • Clear TODO comment with PR reference
  • Explicit temporary nature documented

⚠️ Improvements Needed:

  • No docstring update for verifyForcedInclusionTxs explaining when it's called vs skipped
  • No documentation of the security implications in comments
  • The issue reference (Force inclusion follow ups #2906) should be linked in code comment for traceability

Suggested Comment:

// Verify forced inclusion transactions if configured.
// TEMPORARY: Skip for P2P blocks until background verification is implemented (see #2891).
// This is safe because:
// 1. DA blocks (source of truth) are still fully verified
// 2. P2P blocks will be re-verified in the background once #2891 merges
// 3. Any malicious P2P blocks will be caught when DA confirmation arrives
// Related: https://github.com/evstack/ev-node/issues/2906
if event.Source == common.SourceDA {
    ...
}

Additional Observations

Minor Code Quality Issues:

  1. Line 539: Comment formatting inconsistency

    // TODO(@julienrbrt): To be reverted once DA Hints are merged (https://github.com/evstack/ev-node/pull/2891)

    This TODO should be moved to a separate line for better readability.

  2. DA Height Check (Line 270):

    if state.DAHeight != 0 && state.DAHeight < s.genesis.DAStartHeight {

    The added != 0 check is correct for fresh nodes, but this change isn't mentioned in the PR description. Consider documenting this fix separately.


Final Verdict

Conditional Approval with Required Changes

The PR achieves its stated goal of temporarily skipping forced inclusion checks for P2P blocks to improve sync performance. However, it has critical issues that must be addressed:

Must Fix Before Merge:

  1. Fix error handling: Return all errors from verifyForcedInclusionTxs, not just errMaliciousProposer
  2. Add test coverage: Minimum 3 tests covering DA source, P2P source, and error handling paths

Should Fix (High Priority):
3. Improve documentation with detailed comments explaining security tradeoff
4. Add metrics tracking P2P vs DA block processing for observability

Post-Merge Actions:
5. Prioritize PR #2891 to minimize the window of degraded verification
6. Document this temporary security tradeoff in release notes

The core logic is sound for a temporary fix, but the error handling bug and lack of test coverage make this risky to merge as-is.


@julienrbrt julienrbrt mentioned this pull request Dec 12, 2025
13 tasks
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request correctly implements the intended fix of skipping forced transaction checks for P2P blocks as a temporary measure. The changes are straightforward and align with the PR's objective.

However, I've identified a pre-existing issue in the error handling for verifyForcedInclusionTxs. Currently, errors other than errMaliciousProposer are logged but otherwise ignored, allowing block processing to continue even if verification fails due to a transient error. This could lead to incorrect state. I've suggested a change to ensure any verification failure causes the block sync to be retried, which improves the robustness of the syncer.

Comment on lines +599 to 605
if err := s.verifyForcedInclusionTxs(currentState, data); err != nil {
s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("forced inclusion verification failed")
if errors.Is(err, errMaliciousProposer) {
s.cache.RemoveHeaderDAIncluded(headerHash)
return err
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The current error handling for verifyForcedInclusionTxs only returns an error if it's errMaliciousProposer. Other errors, such as a transient failure to retrieve data from the DA layer, are logged but then ignored. This allows block processing to continue without proper verification, which could be problematic.

Any error from verifyForcedInclusionTxs should cause trySyncNextBlock to fail. This will allow the caller (processHeightEvent) to handle the error appropriately, such as rescheduling the event for a retry, which is the default behavior for non-critical errors. This makes the syncer more robust against transient issues.

I suggest simplifying the error handling to always return the error, which is also more consistent with how errors from validateBlock are handled just above.

		if err := s.verifyForcedInclusionTxs(currentState, data); err != nil {
			s.logger.Error().Err(err).Uint64("height", nextHeight).Msg("forced inclusion verification failed")
			s.cache.RemoveHeaderDAIncluded(headerHash)
			return err
		}

@codecov
Copy link

codecov bot commented Dec 12, 2025

Codecov Report

❌ Patch coverage is 0% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.62%. Comparing base (c3775f8) to head (6463cec).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
block/internal/syncing/syncer.go 0.00% 5 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2922      +/-   ##
==========================================
- Coverage   63.72%   63.62%   -0.11%     
==========================================
  Files          90       90              
  Lines        8571     8572       +1     
==========================================
- Hits         5462     5454       -8     
- Misses       2529     2536       +7     
- Partials      580      582       +2     
Flag Coverage Δ
combined 63.62% <0.00%> (-0.11%) ⬇️

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.

@julienrbrt julienrbrt enabled auto-merge December 12, 2025 14:22
@julienrbrt julienrbrt added this pull request to the merge queue Dec 15, 2025
Merged via the queue into main with commit 7808c5a Dec 15, 2025
26 of 27 checks passed
@julienrbrt julienrbrt deleted the julien/fix-verify branch December 15, 2025 11:57

// only save to p2p stores if the event came from DA
if event.Source == common.SourceDA {
if event.Source == common.SourceDA { // TODO(@julienrbrt): To be reverted once DA Hints are merged (https://github.com/evstack/ev-node/pull/2891)
Copy link
Member Author

Choose a reason for hiding this comment

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

turns out the todo is on the wrong condition, but we'll get it :D (should have been L594)

alpe added a commit that referenced this pull request Dec 15, 2025
* main:
  fix(syncing): skip forced txs checks for p2p blocks (#2922)
  build(deps): Bump the all-go group across 5 directories with 5 updates (#2919)
  chore: loosen syncer state check (#2927)
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