Skip to content

Conversation

@tim-barry
Copy link
Contributor

@tim-barry tim-barry commented Jan 12, 2026

Instead of just containing a height, SyncResponse now contains a header and QC certifying the header, proving that >2/3rds of nodes on the corresponding chain have reached that height. This PR only updates the SyncResponse struct and makes minor updates to the sync engine (removing the implementation of deprecated network.Engine and replacing with network.MessageProcessor), and does not add validation for the Header or QC (split into a separate PR).

The Height field and code path using that field are retained for backwards compatibility (d14aaee):

  • Non-updated nodes receiving a SyncResponse from updated nodes will discard the message as it has unknown data attached
  • Updated nodes receiving a SyncResponse from non-updated nodes will receive a message with an empty Header field, and use the Height field as before
    • Height=0 is treated as the Header is not present, since height 0 should never be synced: Sending a SyncResponse with the real header at Height=0 would imply the sender of the SyncRequest was at height below 0
  • Updated nodes receiving a SyncResponse from other updated nodes will follow the new path and exclusively use the Header+QC

Issues: #8173 #8174

Summary by CodeRabbit

  • New Features

    • Sync responses now include the finalized block header and a certifying quorum certificate.
  • Bug Fixes

    • Incompatible input types are logged as non-fatal warnings instead of causing failures.
    • Height handling in sync flows now derives from the returned header when available (fallback to height retained).
  • Refactor

    • Removed redundant local submission wrappers; request handlers now carry finalized state and produce header+QC responses.
  • Tests

    • Tests updated to assert on header and quorum-certificate–based responses and updated mocks.

✏️ Tip: You can customize this high-level summary in your review settings.

Used in the cluster sync engine to certify our latest finalized height
in a syncResponse.
Implementation may still change.
Also, the correct QC is now included in both consensus/cluster `SyncResponse`s,
and tests now check that SyncResponses contain a QC.
@tim-barry tim-barry requested a review from a team as a code owner January 12, 2026 17:41
@github-actions
Copy link
Contributor

github-actions bot commented Jan 12, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

📝 Walkthrough

Walkthrough

Replaces height-only sync responses with finalized block Header plus its CertifyingQC, threads protocol.State into request handlers to obtain finalized headers/QCs, adds Snapshot.QuorumCertificate(), and removes local convenience submission methods from collection synchronization engines; tests and mocks updated accordingly.

Changes

Cohort / File(s) Summary
Collection Synchronization Engine
engine/collection/synchronization/engine.go, engine/collection/synchronization/request_handler.go
Removed public methods SubmitLocal, Submit, ProcessLocal; adjusted Process to delegate to internal processing and treat IncompatibleInputTypeError as non-fatal; use res.Header.Height where applicable.
Collection Synchronization Tests
engine/collection/synchronization/engine_test.go
Tests updated to include qc field in SyncSuite; mocks and assertions switched to Header + CertifyingQC; engine consumer typed as []netint.MessageProcessor.
Common Synchronization Engine
engine/common/synchronization/engine.go, engine/common/synchronization/request_handler_engine.go
NewRequestHandler call/site updated to pass an additional protocol.State argument.
Common Request Handler
engine/common/synchronization/request_handler.go
RequestHandler adds state protocol.State; onSyncRequest obtains finalized state and its QuorumCertificate(); responses now include Header and CertifyingQC (Height retained for compatibility).
Common Sync Tests
engine/common/synchronization/engine_suite_test.go, engine/common/synchronization/engine_test.go
Added qc to SyncSuite; tests construct/assert SyncResponse.Header and CertifyingQC; include backward-compatible Height path; new invalid-response stub.
Sync Data Model
model/flow/synchronization.go
SyncResponse now contains Nonce, Height, Header, and CertifyingQC (Header+QC are authoritative; Height kept for compatibility).
Cluster Snapshot Interface
state/cluster/snapshot.go
Added QuorumCertificate() (*flow.QuorumCertificate, error) to the Snapshot interface.
Snapshot Implementations & Mocks
state/cluster/badger/snapshot.go, state/cluster/invalid/snapshot.go, state/cluster/mock/snapshot.go
Implemented QuorumCertificate() across implementations: badger derives QC from first unfinalized child header; invalid snapshot returns stored error; mock method added.
Cluster State Tests
state/cluster/badger/mutator_test.go
Adjusted finalized block view creation to avoid view collision with orphaned certified block.
Docs / Minor Fix
storage/operation/qcs.go
Typo fixed in comment ("samn" → "same").

Sequence Diagram(s)

sequenceDiagram
    participant Node
    participant RequestHandler
    participant ProtocolState as State
    participant FinalizedState
    participant Network

    Node->>RequestHandler: SyncRequest
    RequestHandler->>State: Final()
    State-->>RequestHandler: FinalizedState
    RequestHandler->>FinalizedState: Head()
    FinalizedState-->>RequestHandler: Header
    RequestHandler->>FinalizedState: QuorumCertificate()
    FinalizedState-->>RequestHandler: CertifyingQC
    RequestHandler->>Network: SendResponse { Nonce, Header, CertifyingQC } to Node
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

Improvement, Preserve

Suggested reviewers

  • zhangchiqing
  • peterargue

Poem

🐰 Hop — a header sealed and bright,
QC snug beneath the moonlight.
State nudged final, snapshots hum,
Syncs arrive — certs on the drum. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and accurately reflects the main objective of the PR: updating SyncResponse to include verifiable data (Header and Quorum Certificate) that proves finality.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
engine/collection/synchronization/engine.go (1)

261-268: Add defensive nil check for res.Header before dereferencing.

The Header field in SyncResponse is defined as a pointer (*Header) and can be nil. The code directly accesses res.Header.Height without validation. While tests create valid responses, production inputs must be treated as potentially byzantine—a malformed message could reach this point (validation is incomplete per the PR description), causing a panic.

Add an explicit nil check:

if res.Header == nil {
    e.log.Error().Msg("sync response has nil header")
    return
}
🤖 Fix all issues with AI agents
In @engine/common/synchronization/engine.go:
- Around line 296-300: In onSyncResponse, avoid a nil pointer dereference by
checking that res and res.Header are non-nil before accessing res.Header.Height;
if res or res.Header is nil, log a warning including originID (via
e.log.Warn/Error().Str("origin_id", originID.String())) and return early instead
of calling e.core.HandleHeight(final, res.Header.Height); this keeps the
existing use of finalizedHeaderCache.Get() and e.core.HandleHeight intact but
prevents crashes from malformed/malicious SyncResponse messages.

In @model/flow/synchronization.go:
- Around line 14-18: Update the SyncResponse docstring to accurately describe
its current fields: explain that SyncResponse contains a Nonce for request
matching, a pointer to the latest Header returned by the responding node
(Header) and the accompanying CertifyingQC (QuorumCertificate) used to verify
the header; mention that field validation is deferred to
validateSyncResponseForALSP. Locate the comment above the SyncResponse type and
replace the outdated "latest finalized block height" description with this
updated summary referencing SyncResponse, Header, CertifyingQC, and
validateSyncResponseForALSP.

In @state/cluster/badger/snapshot.go:
- Around line 71-91: Fix Snapshot.QuorumCertificate by removing the premature
return that makes the storage.ErrNotFound handling unreachable: after calling
operation.RetrieveBlockChildren, if err != nil check if errors.Is(err,
storage.ErrNotFound) and treat that as “no children” (initialize pendingIDs
empty) otherwise return the wrapped error; then before indexing pendingIDs[0]
verify len(pendingIDs) > 0 and return (nil, nil) or an appropriate no-QC result
when empty to avoid panic; refer to Snapshot.QuorumCertificate,
operation.RetrieveBlockChildren, storage.ErrNotFound and mimic the
error-handling pattern used in pending().
🧹 Nitpick comments (2)
engine/common/synchronization/request_handler.go (2)

158-162: Add error wrapping for debugging context.

The error from Head() is returned without context, which makes debugging harder. The collection counterpart (engine/collection/synchronization/request_handler.go line 154) wraps this error properly. As per coding guidelines, use comprehensive error wrapping.

♻️ Suggested fix
 	finalizedState := r.state.Final()
 	finalizedHeader, err := finalizedState.Head()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get last finalized header: %w", err)
 	}

183-191: Good snapshot consistency; consider error wrapping.

Using the same finalizedState snapshot for both Head() and QuorumCertificate() ensures the QC corresponds to the returned header. The SyncResponse construction correctly populates the new fields.

However, the error from QuorumCertificate() should also be wrapped for consistency with the collection implementation.

♻️ Optional: wrap QC error
 	qcForFinalizedHeader, err := finalizedState.QuorumCertificate()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get QC for last finalized header: %w", err)
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d163b2a and 05e6fa9.

📒 Files selected for processing (14)
  • engine/collection/synchronization/engine.go
  • engine/collection/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • engine/common/synchronization/request_handler_engine.go
  • model/flow/synchronization.go
  • state/cluster/badger/mutator_test.go
  • state/cluster/badger/snapshot.go
  • state/cluster/invalid/snapshot.go
  • state/cluster/mock/snapshot.go
  • state/cluster/snapshot.go
🧰 Additional context used
📓 Path-based instructions (5)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/coding_conventions.mdc)

Follow Go coding conventions as documented in @docs/agents/CodingConventions.md

Follow Go coding standards and conventions as documented in @docs/agents/GoDocs.md

**/*.go: Follow the existing module structure in /module/, /engine/, /model/ and use dependency injection patterns for component composition
Implement proper interfaces before concrete types
Follow Go naming conventions and the project's coding style defined in /docs/CodingConventions.md
Use mock generators: run make generate-mocks after interface changes
All inputs must be considered potentially byzantine; error classification is context-dependent and no code path is safe unless explicitly proven and documented
Use comprehensive error wrapping for debugging; avoid fmt.Errorf, use irrecoverable package for exceptions
NEVER log and continue on best effort basis; ALWAYS explicitly handle errors
Uses golangci-lint with custom configurations (.golangci.yml) and custom linters for Flow-specific conventions (struct write checking)

Files:

  • model/flow/synchronization.go
  • engine/common/synchronization/request_handler.go
  • state/cluster/snapshot.go
  • state/cluster/badger/snapshot.go
  • engine/common/synchronization/request_handler_engine.go
  • state/cluster/invalid/snapshot.go
  • engine/common/synchronization/engine.go
  • engine/collection/synchronization/engine.go
  • state/cluster/badger/mutator_test.go
  • engine/collection/synchronization/engine_test.go
  • state/cluster/mock/snapshot.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
{module,engine,cmd}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

All major processing components must implement the Component interface from /module/component/component.go to ensure consistent lifecycle management and graceful shutdown patterns

Files:

  • engine/common/synchronization/request_handler.go
  • engine/common/synchronization/request_handler_engine.go
  • engine/common/synchronization/engine.go
  • engine/collection/synchronization/engine.go
  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
{crypto,fvm,ledger,access,engine}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Cryptographic operations require careful handling; refer to crypto library documentation for proper implementation

Files:

  • engine/common/synchronization/request_handler.go
  • engine/common/synchronization/request_handler_engine.go
  • engine/common/synchronization/engine.go
  • engine/collection/synchronization/engine.go
  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
{network,engine,consensus}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Network messages must be authenticated and validated

Files:

  • engine/common/synchronization/request_handler.go
  • engine/common/synchronization/request_handler_engine.go
  • engine/common/synchronization/engine.go
  • engine/collection/synchronization/engine.go
  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Unit tests should be co-located with the code they test
Follow the existing pattern of *_test.go files for test naming
Use fixtures for realistic test data as defined in /utils/unittest/

Files:

  • state/cluster/badger/mutator_test.go
  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_suite_test.go
  • engine/common/synchronization/engine_test.go
🧠 Learnings (2)
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to {storage,ledger,execution,fvm}/**/*.go : State consistency is paramount; use proper synchronization primitives

Applied to files:

  • engine/common/synchronization/request_handler.go
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to **/*_test.go : Use fixtures for realistic test data as defined in `/utils/unittest/`

Applied to files:

  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_test.go
🧬 Code graph analysis (10)
model/flow/synchronization.go (2)
utils/unittest/fixtures/header.go (1)
  • Header (10-10)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
engine/common/synchronization/request_handler.go (1)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
state/cluster/snapshot.go (1)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
state/cluster/invalid/snapshot.go (4)
state/cluster/badger/snapshot.go (1)
  • Snapshot (20-23)
state/cluster/mock/snapshot.go (1)
  • Snapshot (11-13)
state/cluster/snapshot.go (1)
  • Snapshot (10-42)
state/protocol/badger/snapshot.go (1)
  • Snapshot (29-32)
engine/common/synchronization/engine.go (2)
engine/common/synchronization/request_handler.go (1)
  • NewRequestHandler (67-93)
engine/common/synchronization/request_handler_engine.go (1)
  • NewResponseSender (47-51)
state/cluster/badger/mutator_test.go (1)
fvm/evm/offchain/query/view.go (1)
  • View (23-31)
engine/collection/synchronization/engine_test.go (5)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
utils/unittest/fixtures.go (3)
  • CertifyBlock (2088-2094)
  • BlockHeaderFixture (580-600)
  • WithHeaderHeight (549-553)
utils/unittest/fixtures/header.go (1)
  • Header (10-10)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
state/cluster/mock/snapshot.go (2)
state/cluster/snapshot.go (1)
  • Snapshot (10-42)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
engine/common/synchronization/engine_suite_test.go (2)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
utils/unittest/fixtures.go (1)
  • CertifyBlock (2088-2094)
engine/collection/synchronization/request_handler.go (3)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: Lint (./insecure/)
  • GitHub Check: Lint (./)
  • GitHub Check: Lint (./integration/)
  • GitHub Check: Integration Tests Others (integration)
  • GitHub Check: Unit Tests (network/alsp)
  • GitHub Check: Unit Tests (module)
  • GitHub Check: Unit Tests (others)
  • GitHub Check: Unit Tests (network/test/cohort2)
  • GitHub Check: Unit Tests (network/p2p/scoring)
  • GitHub Check: Unit Tests (network/p2p)
  • GitHub Check: Unit Tests (network/test/cohort1)
  • GitHub Check: Unit Tests (network/p2p/node)
  • GitHub Check: Unit Tests (engine/execution)
  • GitHub Check: Unit Tests (network)
  • GitHub Check: Unit Tests (engine/collection)
  • GitHub Check: Unit Tests (network/p2p/connection)
  • GitHub Check: Unit Tests (engine/execution/computation)
  • GitHub Check: Unit Tests (module/dkg)
  • GitHub Check: Unit Tests (utils)
  • GitHub Check: Unit Tests (engine/execution/ingestion)
  • GitHub Check: Unit Tests (engine/common)
  • GitHub Check: Unit Tests (engine/verification)
  • GitHub Check: Unit Tests (engine)
  • GitHub Check: Unit Tests (engine/access)
  • GitHub Check: Unit Tests (engine/consensus)
  • GitHub Check: Unit Tests (storage)
  • GitHub Check: Unit Tests (admin)
  • GitHub Check: Unit Tests (ledger)
  • GitHub Check: Unit Tests (state)
  • GitHub Check: Unit Tests (cmd)
  • GitHub Check: Unit Tests (consensus)
  • GitHub Check: Unit Tests (fvm)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/rpc_inspector)
  • GitHub Check: Unit Tests Insecure (insecure)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/scoring)
  • GitHub Check: analyze-code (go)
  • GitHub Check: Docker Build
🔇 Additional comments (21)
state/cluster/badger/mutator_test.go (1)

500-503: LGTM - Test fix correctly prevents view collision.

The fix ensures that the finalized block doesn't share the same view as the orphaned certified block, which would violate the consensus invariant that only one block can be certified per view. This prevents flaky test failures.

engine/common/synchronization/engine_suite_test.go (3)

39-39: LGTM - QC field addition aligns with new SyncResponse structure.

The new qc field properly supports testing the Header+CertifyingQC-based sync responses.


67-68: LGTM - QC correctly initialized from the head header.

Using unittest.CertifyBlock(ss.head) ensures the QC's View and BlockID are correctly linked to the header being certified, which is essential for valid sync responses.


119-124: LGTM - Snapshot mock correctly implements QuorumCertificate().

The mock returns the pre-created QC that certifies the head, consistent with how the real snapshot would behave.

engine/common/synchronization/engine.go (1)

120-120: LGTM - RequestHandler correctly initialized with state dependency.

The state parameter is now passed to NewRequestHandler, enabling it to retrieve finalized header and QC information for constructing verifiable sync responses.

state/cluster/snapshot.go (1)

26-30: LGTM - QuorumCertificate() method follows established interface patterns.

All implementations of the Snapshot interface have been properly updated: state/cluster/badger/snapshot.go, state/cluster/invalid/snapshot.go, and state/cluster/mock/snapshot.go all include the QuorumCertificate() method. This addition enables the sync engine to construct verifiable SyncResponses with the certifying QC.

engine/common/synchronization/request_handler_engine.go (1)

90-100: LGTM!

The state parameter is correctly threaded through to NewRequestHandler, enabling access to the finalized state needed for populating the new Header and CertifyingQC fields in SyncResponse.

state/cluster/invalid/snapshot.go (1)

45-48: LGTM!

The QuorumCertificate() method correctly follows the established pattern in this file, returning nil and the stored error, consistent with Head(), Collection(), and Pending() methods.

state/cluster/mock/snapshot.go (1)

105-133: LGTM!

Auto-generated mock code correctly implements the QuorumCertificate() method following the established mockery pattern.

engine/common/synchronization/request_handler.go (2)

54-54: LGTM on state field addition.

The protocol.State is properly wired through the constructor and stored for use in request handling.

Also applies to: 72-72, 82-82


170-174: TODO appropriately tracked.

The TODO(8174) comment correctly references the related issue for removing this step once certified sync responses are fully relied upon.

engine/collection/synchronization/engine_test.go (4)

67-68: Good test setup for QC-based flow.

The test setup properly creates a QC using unittest.CertifyBlock and configures the snapshot mock to return it. This aligns with the fixture patterns in utils/unittest/. Based on learnings, using fixtures for realistic test data is the correct approach.

Also applies to: 120-125


198-201: Comprehensive assertions for SyncResponse validation.

The test properly validates the new structure: Header content, QC content, and critically, that the CertifyingQC.BlockID matches the Header's ID. This ensures the QC actually certifies the returned header.


217-227: Test correctly updated for Header-based SyncResponse.

The test constructs a valid SyncResponse with Header and CertifyingQC, and properly references res.Header.Height for the mock expectation.


610-614: Type update reflects interface changes.

Using netint.MessageProcessor instead of netint.Engine correctly reflects the removal of public API surface (SubmitLocal, Submit, ProcessLocal) as noted in the AI summary.

engine/common/synchronization/engine_test.go (4)

87-90: Good assertions for SyncResponse validation.

Properly validates Header, CertifyingQC, and their relationship (BlockID match).


103-123: Test updated correctly for Header-based response.

The test properly constructs a SyncResponse with the new fields and validates that HandleHeight receives the height from res.Header.Height.


125-144: Skipped test appropriately deferred.

The placeholder test for invalid SyncResponse (with nil CertifyingQC) is correctly skipped with a TODO referencing issue #8174, aligning with the PR description that validation is intentionally deferred to a separate PR.


494-500: Per-iteration header/QC creation is correct.

Creating distinct headers and QCs for each iteration ensures the test properly validates processing of multiple items with the new SyncResponse structure.

engine/collection/synchronization/request_handler.go (2)

151-177: Well-structured state access with proper error handling.

The implementation correctly:

  1. Uses a single finalizedState snapshot for both Head() and QuorumCertificate(), ensuring consistency
  2. Wraps errors with descriptive context for debugging
  3. Constructs the SyncResponse with the new Header and CertifyingQC fields

This is cleaner than the common implementation due to proper error wrapping.


157-159: TODO appropriately tracked.

The TODO(8173) correctly references the related issue for this cleanup. Note that the common implementation uses TODO(8174) - both issues are mentioned as related in the PR description.

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

🤖 Fix all issues with AI agents
In @state/cluster/badger/snapshot.go:
- Around line 71-97: The QuorumCertificate method should preserve
storage.ErrNotFound so callers using errors.Is can detect it: after calling
operation.RetrieveBlockChildren in Snapshot.QuorumCertificate, check
errors.Is(err, storage.ErrNotFound) and return that sentinel (or return err
directly) without wrapping it in a new fmt.Errorf; also change the error text to
reference the known block (s.blockID) rather than "finalized head". Do the same
for the RetrieveHeader call (check errors.Is(err, storage.ErrNotFound) and
return the original sentinel/error instead of wrapping) so storage.ErrNotFound
can be detected by callers.
🧹 Nitpick comments (1)
engine/common/synchronization/request_handler.go (1)

158-162: Consider wrapping errors with context for better debugging.

The error from finalizedState.Head() is returned directly without additional context. Per the project's coding guidelines, comprehensive error wrapping aids debugging.

Suggested improvement
 	finalizedState := r.state.Final()
 	finalizedHeader, err := finalizedState.Head()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get finalized header: %w", err)
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05e6fa9 and 3d6b0c5.

📒 Files selected for processing (8)
  • engine/collection/synchronization/engine_test.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • model/flow/synchronization.go
  • state/cluster/badger/snapshot.go
  • state/cluster/snapshot.go
  • storage/operation/qcs.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • engine/collection/synchronization/engine_test.go
  • model/flow/synchronization.go
🧰 Additional context used
📓 Path-based instructions (7)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/coding_conventions.mdc)

Follow Go coding conventions as documented in @docs/agents/CodingConventions.md

Follow Go coding standards and conventions as documented in @docs/agents/GoDocs.md

**/*.go: Follow the existing module structure in /module/, /engine/, /model/ and use dependency injection patterns for component composition
Implement proper interfaces before concrete types
Follow Go naming conventions and the project's coding style defined in /docs/CodingConventions.md
Use mock generators: run make generate-mocks after interface changes
All inputs must be considered potentially byzantine; error classification is context-dependent and no code path is safe unless explicitly proven and documented
Use comprehensive error wrapping for debugging; avoid fmt.Errorf, use irrecoverable package for exceptions
NEVER log and continue on best effort basis; ALWAYS explicitly handle errors
Uses golangci-lint with custom configurations (.golangci.yml) and custom linters for Flow-specific conventions (struct write checking)

Files:

  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine_test.go
  • state/cluster/badger/snapshot.go
  • storage/operation/qcs.go
  • state/cluster/snapshot.go
{module,engine,cmd}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

All major processing components must implement the Component interface from /module/component/component.go to ensure consistent lifecycle management and graceful shutdown patterns

Files:

  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine_test.go
{crypto,fvm,ledger,access,engine}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Cryptographic operations require careful handling; refer to crypto library documentation for proper implementation

Files:

  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine_test.go
{network,engine,consensus}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Network messages must be authenticated and validated

Files:

  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Unit tests should be co-located with the code they test
Follow the existing pattern of *_test.go files for test naming
Use fixtures for realistic test data as defined in /utils/unittest/

Files:

  • engine/common/synchronization/engine_test.go
{crypto,fvm,ledger,storage}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Security checks for cryptographic misuse must be enforced using gosec

Files:

  • storage/operation/qcs.go
{storage,ledger,execution,fvm}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

State consistency is paramount; use proper synchronization primitives

Files:

  • storage/operation/qcs.go
🧠 Learnings (3)
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to {storage,ledger,execution,fvm}/**/*.go : State consistency is paramount; use proper synchronization primitives

Applied to files:

  • engine/common/synchronization/request_handler.go
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to **/*_test.go : Use fixtures for realistic test data as defined in `/utils/unittest/`

Applied to files:

  • engine/common/synchronization/engine_test.go
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to {crypto,fvm,ledger,access,engine}/**/*.go : Cryptographic operations require careful handling; refer to crypto library documentation for proper implementation

Applied to files:

  • storage/operation/qcs.go
🧬 Code graph analysis (5)
engine/common/synchronization/request_handler.go (2)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
engine/collection/synchronization/request_handler.go (4)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
utils/unittest/fixtures/header.go (1)
  • Header (10-10)
engine/common/synchronization/engine_test.go (2)
utils/unittest/fixtures.go (4)
  • BlockHeaderFixture (580-600)
  • WithHeaderHeight (549-553)
  • IdentifierFixture (1151-1155)
  • CertifyBlock (2088-2094)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
state/cluster/badger/snapshot.go (3)
state/cluster/snapshot.go (1)
  • Snapshot (10-43)
storage/operation/children.go (1)
  • RetrieveBlockChildren (90-108)
storage/operation/headers.go (1)
  • RetrieveHeader (47-49)
state/cluster/snapshot.go (1)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: Lint (./insecure/)
  • GitHub Check: Lint (./integration/)
  • GitHub Check: Lint (./)
  • GitHub Check: Unit Tests (consensus)
  • GitHub Check: Unit Tests (others)
  • GitHub Check: Unit Tests (engine/execution)
  • GitHub Check: Unit Tests (network)
  • GitHub Check: Unit Tests (network/p2p/node)
  • GitHub Check: Unit Tests (network/test/cohort2)
  • GitHub Check: Unit Tests (network/test/cohort1)
  • GitHub Check: Unit Tests (ledger)
  • GitHub Check: Unit Tests (network/p2p)
  • GitHub Check: Unit Tests (utils)
  • GitHub Check: Unit Tests (engine)
  • GitHub Check: Unit Tests (engine/consensus)
  • GitHub Check: Unit Tests (storage)
  • GitHub Check: Unit Tests (engine/execution/computation)
  • GitHub Check: Unit Tests (engine/collection)
  • GitHub Check: Unit Tests (network/p2p/connection)
  • GitHub Check: Unit Tests (network/p2p/scoring)
  • GitHub Check: Unit Tests (engine/verification)
  • GitHub Check: Unit Tests (engine/execution/ingestion)
  • GitHub Check: Unit Tests (cmd)
  • GitHub Check: Unit Tests (engine/access)
  • GitHub Check: Unit Tests (engine/common)
  • GitHub Check: Unit Tests (module)
  • GitHub Check: Unit Tests (network/alsp)
  • GitHub Check: Unit Tests (module/dkg)
  • GitHub Check: Unit Tests (admin)
  • GitHub Check: Unit Tests (fvm)
  • GitHub Check: Unit Tests (state)
  • GitHub Check: Integration Tests Others (integration)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/scoring)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/rpc_inspector)
  • GitHub Check: Unit Tests Insecure (insecure)
  • GitHub Check: Docker Build
  • GitHub Check: analyze-code (go)
🔇 Additional comments (9)
storage/operation/qcs.go (1)

26-26: LGTM!

Documentation typo fix ("samn" → "same") improves clarity.

state/cluster/snapshot.go (1)

26-31: LGTM!

The new QuorumCertificate() method is well-documented with clear error semantics, following the same pattern as other methods in the interface.

engine/common/synchronization/request_handler.go (1)

183-191: LGTM on the response construction with Header and CertifyingQC.

The response correctly includes both the finalized header and its certifying QC, enabling receivers to verify the sync response. Same suggestion applies for error wrapping on line 185.

Optional: wrap error with context
 	qcForFinalizedHeader, err := finalizedState.QuorumCertificate()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get QC for finalized header: %w", err)
 	}
engine/common/synchronization/engine_test.go (4)

87-90: Verify pointer-to-value comparison in assertions.

Line 87 compares ss.head (a *flow.Header) with res.Header (a flow.Header value). This comparison will work correctly with testify's assert.Equal since it dereferences pointers. Same applies to line 89 comparing ss.qc with res.CertifyingQC.

The assertions correctly validate the new response structure including QC linkage verification on line 90.


109-123: LGTM!

Test correctly constructs a SyncResponse with the new Header and CertifyingQC fields using the unittest.CertifyBlock fixture helper to generate a properly linked QC.


125-144: Good placeholder for future validation test.

The skipped test with TODO reference to issue 8174 is a good reminder to implement SyncResponse validation. Consider adding a brief comment about what behavior should be tested (e.g., response rejection, slashing report).


494-500: LGTM!

Per-iteration header creation with properly linked CertifyingQC ensures each test message has a valid, distinct header/QC pair. The assertion on line 500 correctly references msg.Header.Height.

engine/collection/synchronization/request_handler.go (2)

151-155: LGTM!

The finalized state retrieval and error handling follows proper patterns with descriptive error wrapping.


167-177: LGTM!

The QC retrieval and response construction correctly implements the new verifiable SyncResponse structure with Header and CertifyingQC. Error handling is properly wrapped with context.

Comment on lines +71 to +97
// QuorumCertificate returns a valid quorum certificate for the header at this snapshot, if one exists.
//
// Expected error returns during normal operations:
// - [storage.ErrNotFound] is returned if the QC is unknown.
func (s *Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) {
// Implementation detail: QuorumCertificates storage / operation.RetrieveQuorumCertificate only indexes QCs
// for main consensus blocks, not cluster blocks, so we directly check for any children that would have a
// QC for the cluster header.
var pendingIDs flow.IdentifierList
err := operation.RetrieveBlockChildren(s.state.db.Reader(), s.blockID, &pendingIDs)
if err != nil {
// The low-level storage returns `storage.ErrNotFound` in two cases:
// 1. the block/collection is unknown
// 2. the block/collection is known but no children have been indexed yet
// By contract of the constructor, the blockID must correspond to a known collection in the database.
// A snapshot with s.err == nil is only created for known blocks. Hence, only case 2 is
// possible here (no children).
return nil, fmt.Errorf("could not get QC for finalized head %v: %w", s.blockID, err)
}
// at least one child exists (pendingIDs[0]) - guaranteed by RetrieveBlockChildren
var child flow.Header
err = operation.RetrieveHeader(s.state.db.Reader(), pendingIDs[0], &child)
if err != nil {
return nil, fmt.Errorf("could not retrieve header for unfinalized child %v: %w", pendingIDs[0], err)
}
return child.ParentQC(), nil
}
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

Error wrapping may not preserve storage.ErrNotFound as documented in the interface.

The interface documentation states that [storage.ErrNotFound] is returned if the QC is unknown. However, the error wrapping on line 88 uses fmt.Errorf which may cause callers using errors.Is(err, storage.ErrNotFound) to fail depending on how they check for this condition.

Additionally, the error message "could not get QC for finalized head" is slightly misleading since this snapshot could reference any known block, not necessarily the finalized head.

Suggested improvements
-		return nil, fmt.Errorf("could not get QC for finalized head %v: %w", s.blockID, err)
+		return nil, fmt.Errorf("could not get QC for block %v: %w", s.blockID, err)

The %w verb should preserve the wrapped error for errors.Is() checks, but verify that callers can properly detect storage.ErrNotFound through this wrapping.

📝 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
// QuorumCertificate returns a valid quorum certificate for the header at this snapshot, if one exists.
//
// Expected error returns during normal operations:
// - [storage.ErrNotFound] is returned if the QC is unknown.
func (s *Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) {
// Implementation detail: QuorumCertificates storage / operation.RetrieveQuorumCertificate only indexes QCs
// for main consensus blocks, not cluster blocks, so we directly check for any children that would have a
// QC for the cluster header.
var pendingIDs flow.IdentifierList
err := operation.RetrieveBlockChildren(s.state.db.Reader(), s.blockID, &pendingIDs)
if err != nil {
// The low-level storage returns `storage.ErrNotFound` in two cases:
// 1. the block/collection is unknown
// 2. the block/collection is known but no children have been indexed yet
// By contract of the constructor, the blockID must correspond to a known collection in the database.
// A snapshot with s.err == nil is only created for known blocks. Hence, only case 2 is
// possible here (no children).
return nil, fmt.Errorf("could not get QC for finalized head %v: %w", s.blockID, err)
}
// at least one child exists (pendingIDs[0]) - guaranteed by RetrieveBlockChildren
var child flow.Header
err = operation.RetrieveHeader(s.state.db.Reader(), pendingIDs[0], &child)
if err != nil {
return nil, fmt.Errorf("could not retrieve header for unfinalized child %v: %w", pendingIDs[0], err)
}
return child.ParentQC(), nil
}
// QuorumCertificate returns a valid quorum certificate for the header at this snapshot, if one exists.
//
// Expected error returns during normal operations:
// - [storage.ErrNotFound] is returned if the QC is unknown.
func (s *Snapshot) QuorumCertificate() (*flow.QuorumCertificate, error) {
// Implementation detail: QuorumCertificates storage / operation.RetrieveQuorumCertificate only indexes QCs
// for main consensus blocks, not cluster blocks, so we directly check for any children that would have a
// QC for the cluster header.
var pendingIDs flow.IdentifierList
err := operation.RetrieveBlockChildren(s.state.db.Reader(), s.blockID, &pendingIDs)
if err != nil {
// The low-level storage returns `storage.ErrNotFound` in two cases:
// 1. the block/collection is unknown
// 2. the block/collection is known but no children have been indexed yet
// By contract of the constructor, the blockID must correspond to a known collection in the database.
// A snapshot with s.err == nil is only created for known blocks. Hence, only case 2 is
// possible here (no children).
return nil, fmt.Errorf("could not get QC for block %v: %w", s.blockID, err)
}
// at least one child exists (pendingIDs[0]) - guaranteed by RetrieveBlockChildren
var child flow.Header
err = operation.RetrieveHeader(s.state.db.Reader(), pendingIDs[0], &child)
if err != nil {
return nil, fmt.Errorf("could not retrieve header for unfinalized child %v: %w", pendingIDs[0], err)
}
return child.ParentQC(), nil
}
🤖 Prompt for AI Agents
In @state/cluster/badger/snapshot.go around lines 71 - 97, The QuorumCertificate
method should preserve storage.ErrNotFound so callers using errors.Is can detect
it: after calling operation.RetrieveBlockChildren in Snapshot.QuorumCertificate,
check errors.Is(err, storage.ErrNotFound) and return that sentinel (or return
err directly) without wrapping it in a new fmt.Errorf; also change the error
text to reference the known block (s.blockID) rather than "finalized head". Do
the same for the RetrieveHeader call (check errors.Is(err, storage.ErrNotFound)
and return the original sentinel/error instead of wrapping) so
storage.ErrNotFound can be detected by callers.


// if we're sufficiently ahead of the requester, send a response
res := &messages.SyncResponse{
Height: final.Height,
Copy link
Member

Choose a reason for hiding this comment

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

This would be a breaking change, and require all the peers to upgrade, right?

I wonder if we can keep the Height field to be backward compatible, so that this change can be rolled out.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it would be breaking; I think we could keep the Height field for now (with the Height field and current code path being used if and only if the Header/QC is empty)

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: 0

🧹 Nitpick comments (1)
engine/common/synchronization/engine_test.go (1)

125-144: Consider enhancing the skipped test stub with clearer expectations.

The placeholder test for invalid SyncResponse is appropriately skipped with a TODO reference to issue #8174. However, the test currently calls ss.e.onSyncResponse but has no mock expectations set up for the invalid case, which would cause unexpected calls if the test were enabled.

When implementing this test, ensure:

  1. The empty CertifyingQC (QC with zero-values) is properly detected as invalid
  2. Appropriate misbehavior reporting or logging is verified
  3. HandleHeight should NOT be called for invalid responses

Would you like me to help sketch out the expected mock setup for when this test is implemented?

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6b0c5 and 6d9ca64.

📒 Files selected for processing (2)
  • engine/collection/synchronization/engine_test.go
  • engine/common/synchronization/engine_test.go
🧰 Additional context used
📓 Path-based instructions (5)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/coding_conventions.mdc)

Follow Go coding conventions as documented in @docs/agents/CodingConventions.md

Follow Go coding standards and conventions as documented in @docs/agents/GoDocs.md

**/*.go: Follow the existing module structure in /module/, /engine/, /model/ and use dependency injection patterns for component composition
Implement proper interfaces before concrete types
Follow Go naming conventions and the project's coding style defined in /docs/CodingConventions.md
Use mock generators: run make generate-mocks after interface changes
All inputs must be considered potentially byzantine; error classification is context-dependent and no code path is safe unless explicitly proven and documented
Use comprehensive error wrapping for debugging; avoid fmt.Errorf, use irrecoverable package for exceptions
NEVER log and continue on best effort basis; ALWAYS explicitly handle errors
Uses golangci-lint with custom configurations (.golangci.yml) and custom linters for Flow-specific conventions (struct write checking)

Files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Unit tests should be co-located with the code they test
Follow the existing pattern of *_test.go files for test naming
Use fixtures for realistic test data as defined in /utils/unittest/

Files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
{module,engine,cmd}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

All major processing components must implement the Component interface from /module/component/component.go to ensure consistent lifecycle management and graceful shutdown patterns

Files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
{crypto,fvm,ledger,access,engine}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Cryptographic operations require careful handling; refer to crypto library documentation for proper implementation

Files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
{network,engine,consensus}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Network messages must be authenticated and validated

Files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
🧠 Learnings (1)
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to **/*_test.go : Use fixtures for realistic test data as defined in `/utils/unittest/`

Applied to files:

  • engine/common/synchronization/engine_test.go
  • engine/collection/synchronization/engine_test.go
🧬 Code graph analysis (2)
engine/common/synchronization/engine_test.go (2)
utils/unittest/fixtures.go (4)
  • BlockHeaderFixture (580-600)
  • WithHeaderHeight (549-553)
  • IdentifierFixture (1151-1155)
  • CertifyBlock (2088-2094)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
engine/collection/synchronization/engine_test.go (2)
utils/unittest/fixtures.go (3)
  • CertifyBlock (2088-2094)
  • BlockHeaderFixture (580-600)
  • WithHeaderHeight (549-553)
model/flow/synchronization.go (1)
  • SyncResponse (14-18)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: Lint (./)
  • GitHub Check: Lint (./insecure/)
  • GitHub Check: Lint (./integration/)
  • GitHub Check: Unit Tests (network/p2p/node)
  • GitHub Check: Unit Tests (network/p2p)
  • GitHub Check: Unit Tests (network/alsp)
  • GitHub Check: Unit Tests (network/test/cohort1)
  • GitHub Check: Unit Tests (network/test/cohort2)
  • GitHub Check: Unit Tests (network)
  • GitHub Check: Unit Tests (others)
  • GitHub Check: Unit Tests (module/dkg)
  • GitHub Check: Unit Tests (engine/execution/ingestion)
  • GitHub Check: Unit Tests (consensus)
  • GitHub Check: Unit Tests (module)
  • GitHub Check: Unit Tests (engine/verification)
  • GitHub Check: Unit Tests (network/p2p/scoring)
  • GitHub Check: Unit Tests (network/p2p/connection)
  • GitHub Check: Unit Tests (engine)
  • GitHub Check: Unit Tests (engine/execution)
  • GitHub Check: Unit Tests (utils)
  • GitHub Check: Unit Tests (storage)
  • GitHub Check: Unit Tests (fvm)
  • GitHub Check: Unit Tests (engine/access)
  • GitHub Check: Unit Tests (engine/consensus)
  • GitHub Check: Unit Tests (engine/collection)
  • GitHub Check: Unit Tests (ledger)
  • GitHub Check: Unit Tests (admin)
  • GitHub Check: Unit Tests (engine/common)
  • GitHub Check: Unit Tests (engine/execution/computation)
  • GitHub Check: Unit Tests (cmd)
  • GitHub Check: Unit Tests (state)
  • GitHub Check: Integration Tests Others (integration)
  • GitHub Check: Unit Tests Insecure (insecure)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/scoring)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/rpc_inspector)
  • GitHub Check: Docker Build
  • GitHub Check: analyze-code (go)
🔇 Additional comments (9)
engine/collection/synchronization/engine_test.go (6)

43-43: LGTM! Good use of the CertifyBlock fixture.

The test setup properly creates a QC that certifies the head header using the unittest.CertifyBlock helper, which correctly sets qc.View = header.View and qc.BlockID = header.ID(). This ensures the test data accurately reflects the real relationship between headers and their certifying QCs. Based on learnings, this follows the pattern of using fixtures from /utils/unittest/.

Also applies to: 67-68


120-125: LGTM! Snapshot mock correctly extended.

The mock properly returns the stored QC and a nil error, matching the expected interface contract for QuorumCertificate().


195-211: LGTM! Good comprehensive assertions for SyncResponse.

The test properly validates:

  1. Response contains the expected header
  2. Response contains the expected QC
  3. QC's BlockID matches the Header's ID (critical for verifiability)
  4. Nonce and recipient are correct

This provides good coverage for the new verifiable SyncResponse structure.


214-228: LGTM! TestOnSyncResponse correctly updated.

The test creates a valid Header+QC pair using the fixture helpers and verifies that HandleHeight is called with the correct height from the response header.


569-585: LGTM! TestProcessingMultipleItems properly creates distinct Header+QC pairs per iteration.

Each iteration correctly creates a unique header with a specific height and a corresponding QC that certifies that header. The mock expectation for HandleHeight correctly references msg.Header.Height.


610-619: LGTM! Type update aligns with interface change.

The change from netint.Engine to netint.MessageProcessor reflects the removal of the deprecated network.Engine implementation as noted in the PR description.

engine/common/synchronization/engine_test.go (3)

84-101: LGTM! Assertions properly verify QC-Header relationship.

The test correctly validates that:

  1. res.Header matches the expected finalized header
  2. res.CertifyingQC contains the expected QC
  3. res.CertifyingQC.BlockID equals ss.head.ID() (verifying the QC certifies the header)

This provides strong coverage for the new verifiable SyncResponse structure.


103-123: LGTM! TestOnSyncResponse correctly updated for new structure.

The test creates a valid Header+QC pair and verifies that HandleHeight is called with res.Header.Height, correctly reflecting the new data model where height is accessed through the Header.


493-506: LGTM! TestProcessingMultipleItems properly creates per-iteration Header+QC pairs.

Each iteration creates a unique header with a deterministic height (1000 + i) and a corresponding certifying QC. The mock expectations correctly reference msg.Header.Height for the HandleHeight call.

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: 0

🧹 Nitpick comments (2)
engine/common/synchronization/engine.go (1)

299-304: Consider a more robust backwards compatibility check.

The current check res.Header.Height == 0 assumes legacy messages will have a zero-initialized Header. While this works in practice, checking Header.Height == 0 could theoretically match a valid header at height 0 (genesis).

A more explicit approach would be to check if the header's ParentID is zero (since only genesis has zero parent, and genesis wouldn't be the finalized head in normal sync scenarios) or check if CertifyingQC.BlockID is the zero identifier:

💡 Suggested alternative check
 // backwards compatibility - ignore the Header/QC if they are not present, and use Height field instead
-if res.Header.Height == 0 {
+if res.CertifyingQC.BlockID == flow.ZeroID {
 	e.core.HandleHeight(final, res.Height)
 } else {
 	e.core.HandleHeight(final, res.Header.Height)
 }

This is a minor robustness improvement since genesis sync is an edge case.

engine/common/synchronization/request_handler.go (1)

158-186: Add error wrapping for better debugging.

Per coding guidelines, errors should be wrapped for debugging context. The errors at lines 161 and 185 are returned without wrapping, which makes it harder to trace failures in onSyncRequest.

🔧 Suggested fix
 func (r *RequestHandler) onSyncRequest(originID flow.Identifier, req *flow.SyncRequest) error {
 	finalizedState := r.state.Final()
 	finalizedHeader, err := finalizedState.Head()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get finalized header: %w", err)
 	}

 	// ... existing code ...

 	// if we're sufficiently ahead of the requester, send a response
 	qcForFinalizedHeader, err := finalizedState.QuorumCertificate()
 	if err != nil {
-		return err
+		return fmt.Errorf("could not get QC for finalized header: %w", err)
 	}

Based on coding guidelines: "Use comprehensive error wrapping for debugging."

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d9ca64 and d14aaee.

📒 Files selected for processing (6)
  • engine/collection/synchronization/engine.go
  • engine/collection/synchronization/request_handler.go
  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • model/flow/synchronization.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • engine/collection/synchronization/engine.go
🧰 Additional context used
📓 Path-based instructions (5)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/coding_conventions.mdc)

Follow Go coding conventions as documented in @docs/agents/CodingConventions.md

Follow Go coding standards and conventions as documented in @docs/agents/GoDocs.md

**/*.go: Follow the existing module structure in /module/, /engine/, /model/ and use dependency injection patterns for component composition
Implement proper interfaces before concrete types
Follow Go naming conventions and the project's coding style defined in /docs/CodingConventions.md
Use mock generators: run make generate-mocks after interface changes
All inputs must be considered potentially byzantine; error classification is context-dependent and no code path is safe unless explicitly proven and documented
Use comprehensive error wrapping for debugging; avoid fmt.Errorf, use irrecoverable package for exceptions
NEVER log and continue on best effort basis; ALWAYS explicitly handle errors
Uses golangci-lint with custom configurations (.golangci.yml) and custom linters for Flow-specific conventions (struct write checking)

Files:

  • model/flow/synchronization.go
  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
{module,engine,cmd}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

All major processing components must implement the Component interface from /module/component/component.go to ensure consistent lifecycle management and graceful shutdown patterns

Files:

  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
{crypto,fvm,ledger,access,engine}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Cryptographic operations require careful handling; refer to crypto library documentation for proper implementation

Files:

  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
{network,engine,consensus}/**/*.go

📄 CodeRabbit inference engine (AGENTS.md)

Network messages must be authenticated and validated

Files:

  • engine/common/synchronization/engine.go
  • engine/common/synchronization/engine_test.go
  • engine/common/synchronization/request_handler.go
  • engine/collection/synchronization/request_handler.go
**/*_test.go

📄 CodeRabbit inference engine (AGENTS.md)

**/*_test.go: Unit tests should be co-located with the code they test
Follow the existing pattern of *_test.go files for test naming
Use fixtures for realistic test data as defined in /utils/unittest/

Files:

  • engine/common/synchronization/engine_test.go
🧠 Learnings (2)
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to **/*_test.go : Use fixtures for realistic test data as defined in `/utils/unittest/`

Applied to files:

  • engine/common/synchronization/engine_test.go
📚 Learning: 2025-12-23T00:28:41.005Z
Learnt from: CR
Repo: onflow/flow-go PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T00:28:41.005Z
Learning: Applies to {storage,ledger,execution,fvm}/**/*.go : State consistency is paramount; use proper synchronization primitives

Applied to files:

  • engine/common/synchronization/request_handler.go
🧬 Code graph analysis (5)
model/flow/synchronization.go (2)
utils/unittest/fixtures/header.go (1)
  • Header (10-10)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
engine/common/synchronization/engine.go (2)
engine/common/synchronization/request_handler.go (1)
  • NewRequestHandler (67-93)
engine/common/synchronization/request_handler_engine.go (1)
  • NewResponseSender (47-51)
engine/common/synchronization/engine_test.go (5)
utils/unittest/fixtures/header.go (1)
  • Header (10-10)
utils/unittest/fixtures.go (4)
  • BlockHeaderFixture (580-600)
  • WithHeaderHeight (549-553)
  • IdentifierFixture (1151-1155)
  • CertifyBlock (2088-2094)
model/flow/synchronization.go (1)
  • SyncResponse (14-19)
utils/unittest/fixtures/util.go (1)
  • NoError (6-10)
utils/unittest/fixtures/quorum_certificate.go (1)
  • QuorumCertificate (9-9)
engine/common/synchronization/request_handler.go (2)
model/flow/synchronization.go (1)
  • SyncResponse (14-19)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
engine/collection/synchronization/request_handler.go (2)
model/flow/synchronization.go (1)
  • SyncResponse (14-19)
model/messages/synchronization.go (1)
  • SyncResponse (27-27)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (37)
  • GitHub Check: Lint (./insecure/)
  • GitHub Check: Lint (./integration/)
  • GitHub Check: Lint (./)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/scoring)
  • GitHub Check: Unit Tests Insecure (insecure)
  • GitHub Check: Unit Tests Insecure (insecure/integration/functional/test/gossipsub/rpc_inspector)
  • GitHub Check: Unit Tests (engine/execution/ingestion)
  • GitHub Check: Unit Tests (others)
  • GitHub Check: Unit Tests (network/alsp)
  • GitHub Check: Unit Tests (module)
  • GitHub Check: Unit Tests (network/p2p/node)
  • GitHub Check: Unit Tests (network/p2p/connection)
  • GitHub Check: Unit Tests (network/p2p)
  • GitHub Check: Unit Tests (network)
  • GitHub Check: Unit Tests (network/p2p/scoring)
  • GitHub Check: Unit Tests (module/dkg)
  • GitHub Check: Unit Tests (engine/collection)
  • GitHub Check: Unit Tests (engine/consensus)
  • GitHub Check: Unit Tests (network/test/cohort2)
  • GitHub Check: Unit Tests (engine)
  • GitHub Check: Unit Tests (engine/execution)
  • GitHub Check: Unit Tests (storage)
  • GitHub Check: Unit Tests (engine/verification)
  • GitHub Check: Unit Tests (engine/execution/computation)
  • GitHub Check: Unit Tests (network/test/cohort1)
  • GitHub Check: Unit Tests (engine/common)
  • GitHub Check: Unit Tests (engine/access)
  • GitHub Check: Unit Tests (utils)
  • GitHub Check: Unit Tests (admin)
  • GitHub Check: Unit Tests (ledger)
  • GitHub Check: Unit Tests (state)
  • GitHub Check: Unit Tests (cmd)
  • GitHub Check: Unit Tests (fvm)
  • GitHub Check: Unit Tests (consensus)
  • GitHub Check: Integration Tests Others (integration)
  • GitHub Check: analyze-code (go)
  • GitHub Check: Docker Build
🔇 Additional comments (10)
model/flow/synchronization.go (1)

11-19: LGTM! Struct updated correctly for verifiable sync responses.

The addition of Header and CertifyingQC fields aligns with the PR objective to make SyncResponse verifiable. Retaining Height for backwards compatibility is appropriate.

One consideration: both Header and QuorumCertificate are value types (not pointers), which means they will always be initialized with zero values. The engine code at engine/common/synchronization/engine.go (lines 300-304) uses res.Header.Height == 0 to detect legacy messages. This works but relies on the assumption that a valid header will never have Height == 0 (genesis block edge case). Since genesis is typically height 0, consider whether this could cause issues for nodes syncing from genesis.

engine/common/synchronization/engine.go (1)

120-120: LGTM! Constructor updated to pass state dependency.

The state parameter is correctly passed to NewRequestHandler, enabling finalized header and QC retrieval for response construction.

engine/common/synchronization/request_handler.go (2)

54-54: LGTM! State dependency correctly integrated.

The protocol.State dependency is properly added to the struct, constructor signature, and initialization. This enables finalized header and QC retrieval for constructing verifiable sync responses.

Also applies to: 72-72, 82-82


187-192: LGTM! Response construction is correct.

The SyncResponse is properly constructed with all required fields. The Header and CertifyingQC are correctly dereferenced from pointers to value types matching the struct definition. Setting Height alongside Header.Height maintains backwards compatibility.

engine/common/synchronization/engine_test.go (4)

87-90: LGTM! Assertions correctly validate new response fields.

The assertions properly verify:

  1. res.Header matches the expected header
  2. res.CertifyingQC matches the expected QC
  3. res.CertifyingQC.BlockID corresponds to res.Header.ID() (integrity check)

This ensures the response contains a valid header-QC pair. Based on learnings, the test correctly uses fixtures from utils/unittest/.


103-131: LGTM! Test covers both new and legacy response formats.

The test properly validates:

  1. New format: SyncResponse with Header and CertifyingQC fields (lines 113-121)
  2. Legacy format: SyncResponse with only Height field (lines 123-129)

Both paths correctly assert HandleHeight is called with the appropriate height value.


133-152: Appropriate placeholder for deferred validation test.

The skipped test is correctly structured to validate invalid sync responses once QC/Header verification is implemented in the follow-up PR (#8174). The test scenario (valid header with empty QC) correctly represents an invalid state that should be rejected.


501-514: LGTM! Test correctly constructs responses with per-iteration headers.

Each iteration creates a unique header and corresponding QC using fixtures. The HandleHeight expectation correctly references msg.Header.Height, matching the engine's processing logic for new-format messages.

engine/collection/synchronization/request_handler.go (2)

150-187: LGTM! Collection handler correctly updated for verifiable sync responses.

The implementation properly:

  1. Retrieves finalized state via r.state.Final() (cluster state)
  2. Gets the QC via finalizedState.QuorumCertificate() with error wrapping
  3. Constructs SyncResponse with all new fields (Header, CertifyingQC)

Error handling includes proper context wrapping (lines 154, 169-170), which is better than the common handler implementation.


167-178: Good error wrapping here - consider applying same pattern to common handler.

This implementation correctly wraps the QC retrieval error with context (line 169-170). The parallel implementation in engine/common/synchronization/request_handler.go (lines 184-186) returns the error without wrapping. Consider aligning both implementations for consistency.

final := e.finalizedHeaderCache.Get()
e.core.HandleHeight(final, res.Height)
// backwards compatibility - ignore the Header/QC if they are not present, and use Height field instead
if res.Header.Height == 0 {
Copy link
Member

@zhangchiqing zhangchiqing Jan 13, 2026

Choose a reason for hiding this comment

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

Can you verify two test cases:

  1. if an old flow.SyncResponse message is set with a height H, after it's been encoded and decoded to the new flow.SyncResponse message, the res.Height is H, and the res.Header.Height is 0
  2. if a new flow.SyncResponse message is set with a height H, after it's been encoded and decoded as the old flow.SyncResponse message, the res.Height is H.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately:

  1. passes - old response is decoded to the new response successfully (with all the values as expected)
  2. fails - the new response fails to decode as the old response, because the Header and CertifyingQC it receives are unknown fields, and we use a config to enable that error for network messages:
    // DefaultDecMode is the DecMode used for decoding messages over the network.
    // It returns an error if the message contains any extra field not present in the
    // target (struct we are unmarshalling into), which prevents some classes of resource exhaustion attacks.
    var DefaultDecMode, _ = cbor.DecOptions{ExtraReturnErrors: cbor.ExtraDecErrorUnknownField}.DecMode()

Copy link
Member

Choose a reason for hiding this comment

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

So it means, new node can fetch blocks from old node, but old node can not fetch blocks from new node.

I think this is probably ok, and we can still roll it out. Because when rolling out, most of the old nodes are all up to date, they don't need to sync blocks from other peers, once an old node is restarted, it becomes new node, and can still fetch blocks from old nodes or other new nodes.

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.

4 participants