Skip to content

Implement the RPCBlockHeaderSubscriber for indexing finalized results#728

Open
m-Peter wants to merge 131 commits into
mainfrom
mpeter/poc-index-finalized-block-results
Open

Implement the RPCBlockHeaderSubscriber for indexing finalized results#728
m-Peter wants to merge 131 commits into
mainfrom
mpeter/poc-index-finalized-block-results

Conversation

@m-Peter

@m-Peter m-Peter commented Jan 21, 2025

Copy link
Copy Markdown
Collaborator

Closes: #727

Description


For contributor use:

  • Targeted PR against master branch
  • Linked to Github issue with discussion and accepted design OR link to spec that describes this work.
  • Code follows the standards mentioned here.
  • Updated relevant documentation
  • Re-reviewed Files changed in the Github PR explorer
  • Added appropriate labels

Summary by CodeRabbit

  • New Features
    • Experimental soft-finality and sealing-verification runtime flags; optional sealing verifier and a block-tracking subscriber for validated event streaming.
  • Improvements
    • Event-hash storage and DB batch operations to support atomic event processing and recovery.
    • Cross-spork client expanded to fetch events and subscribe block headers across spork boundaries.
  • Behavior Changes
    • Transaction pool now always batches per-account transactions (no fast-path single-tx submit).
  • Tests
    • Updated batching expectations and added regression test for out-of-order nonce bursts.

@m-Peter m-Peter self-assigned this Jan 21, 2025
@coderabbitai

coderabbitai Bot commented Jan 21, 2025

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds experimental soft-finality ingestion (flags/config) and event-hash storage; introduces batching-only tx pool behavior (always enqueue), updates tests for batching semantics, and small storage/db batch helpers and keys.

Changes

Soft Finality

Layer / File(s) Summary
Feature flags & config
cmd/run/cmd.go, config/config.go
Adds ExperimentalSoftFinalityEnabled and ExperimentalSealingVerificationEnabled flags and config fields; flags are wired into startup config.

Batching & Tests

Layer / File(s) Summary
BatchTxPool behavior change
services/requester/batch_tx_pool.go
Removes EOA activity LRU and immediate single-tx submit path; Add always enqueues transactions for periodic batched submission.
Tests, go.mod, tidy
tests/tx_batching_test.go, go.mod, Makefile
Updates E2E expectations to require batching semantics, adds Test_OutOfOrderNonceBurstTransactions regression test, and makes go mod tidy -v changes in Makefile.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Feature, EVM

Suggested reviewers

  • zhangchiqing
  • peterargue
  • janezpodhostnik

Poem

🐰 I hopped through flags and batching lanes,
Enqueued the hops, removed the pains.
Soft blocks tracked, sealed hashes checked,
Tests now prove bursts never wrecked.
A rabbit cheers: code stitched and blessed!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title mentions 'RPCBlockHeaderSubscriber' but the actual implementation is named 'RPCBlockTrackingSubscriber' with different functionality. Update the PR title to accurately reflect the implemented component: 'Implement the RPCBlockTrackingSubscriber for soft finality block indexing' or similar.
Out of Scope Changes check ⚠️ Warning Several changes appear out of scope: modifications to BatchTxPool removing EOA activity cache, EOAActivityCacheTTL deprecation, removal of submitSingleTransaction, and updates to transaction batching tests are unrelated to soft finality indexing. Move EOA activity cache removal and transaction batching changes to a separate PR focused on that feature, keeping this PR focused on soft finality block tracking and indexing.
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR successfully implements all primary objectives from issue #727: subscribes to finalized blocks, retrieves EVM events for each block, integrates with existing indexing, handles soft finality, and maintains rollback compatibility.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mpeter/poc-index-finalized-block-results

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.

@peterargue peterargue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

added a couple small comments, but otherwise this looks good. let's get it running against one of the live networks.

Comment thread services/ingestion/block_header_subscriber.go Outdated
Comment thread services/ingestion/block_header_subscriber.go Outdated
Comment thread services/ingestion/block_header_subscriber.go Outdated
Comment thread services/ingestion/block_header_subscriber.go Outdated
Comment thread services/ingestion/block_header_subscriber.go Outdated
@m-Peter

m-Peter commented Jan 23, 2025

Copy link
Copy Markdown
Collaborator Author

@peterargue Thanks for the review 🙌 I've addressed all the comments.
Did you perhaps have the chance to take a look at the behavior described in #727 (comment) ? This is puzzling to me 🤔

@m-Peter m-Peter marked this pull request as ready for review January 23, 2025 15:29
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Comment thread services/ingestion/block_tracking_subscriber.go Outdated
Comment thread services/ingestion/block_tracking_subscriber.go Outdated
Comment thread services/ingestion/block_tracking_subscriber.go
Comment thread services/ingestion/block_tracking_subscriber.go Outdated
Comment thread services/ingestion/block_tracking_subscriber.go Outdated
Comment thread services/ingestion/block_tracking_subscriber.go
Comment thread services/ingestion/block_tracking_subscriber.go Outdated
@m-Peter m-Peter force-pushed the mpeter/poc-index-finalized-block-results branch from 314e7cc to bfe6188 Compare January 27, 2025 09:43
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

Co-authored-by: Peter Argue <89119817+peterargue@users.noreply.github.com>
coderabbitai[bot]

This comment was marked as resolved.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
bootstrap/bootstrap.go (1)

630-647: Force cadence height enhancements are well-implemented.

The logic correctly handles resetting event hash state when forcing a start height:

  • Conditionally resets processed sealed height only when it's ahead of the forced height (line 640-644)
  • Always removes event hash entries above the forced height (line 645-647)
  • Proper error handling with descriptive messages and wrapping
  • Batch operations ensure atomicity

Consider clarifying the error message on line 642 to better reflect the operation:

-				return nil, nil, fmt.Errorf("failed to set latest verified sealed height: %w", err)
+				return nil, nil, fmt.Errorf("failed to set processed sealed height to forced start height: %w", err)

The current message says "latest verified sealed height" but we're actually setting it to the forced start height, which may not be the latest.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2862171 and 5baae43.

📒 Files selected for processing (1)
  • bootstrap/bootstrap.go (5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-06T10:14:49.706Z
Learnt from: m-Peter
Repo: onflow/flow-evm-gateway PR: 890
File: services/ingestion/event_subscriber.go:235-239
Timestamp: 2025-10-06T10:14:49.706Z
Learning: In services/ingestion/event_subscriber.go, when reconnecting after disconnect errors (DeadlineExceeded, Internal, Unavailable), the subscription should reconnect at lastReceivedHeight rather than lastReceivedHeight+1. This avoids errors when the next height doesn't exist yet, and duplicate event processing is safe because the ingestion engine is explicitly designed to be idempotent (storage uses batch.Set() which overwrites existing entries).

Applied to files:

  • bootstrap/bootstrap.go
🧬 Code graph analysis (1)
bootstrap/bootstrap.go (5)
storage/pebble/events_hash.go (2)
  • EventsHash (14-16)
  • NewEventsHash (18-22)
services/ingestion/event_subscriber.go (2)
  • EventSubscriber (27-33)
  • NewRPCEventSubscriber (49-65)
services/ingestion/sealing_verifier.go (2)
  • SealingVerifier (30-54)
  • NewSealingVerifier (57-82)
services/ingestion/block_tracking_subscriber.go (1)
  • NewRPCBlockTrackingSubscriber (48-66)
models/errors/errors.go (1)
  • ErrStorageNotInitialized (37-37)
⏰ 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). (2)
  • GitHub Check: Lint
  • GitHub Check: Test
🔇 Additional comments (4)
bootstrap/bootstrap.go (4)

60-60: LGTM! EventsHash storage added to Storages.

The new EventsHash field is properly typed and consistent with the other storage fields in the struct.


618-618: LGTM! EventsHash initialized correctly.

The initialization follows the expected pattern and properly uses the store parameter.


711-711: LGTM! EventsHash properly wired into returned Storages.

The field is correctly added to the return struct, completing the integration of EventsHash into the storage initialization flow.


163-193: Based on the verification results, the original review comment makes valid architectural observations but the execution verification was incomplete. The script output confirms the structure but doesn't show how the verifier field is actually used in methods, which is necessary to fully verify nil-safety.

Code design is sound; verify verifier nil-handling in implementation.

The conditional subscriber creation follows good architectural patterns—the two-level feature flag (outer for soft finality, inner for sealing verification) is clean, and both paths create fresh subscriber instances starting from nextCadenceHeight.

However, confirm that NewRPCBlockTrackingSubscriber properly handles the optional nil verifier parameter in all method calls. The verifier field can be nil when ExperimentalSealingVerificationEnabled is false, and Go requires explicit nil checks before dereferencing. Verify that all methods using the verifier guard against nil or document that the parameter must never be nil.

Additionally, since both subscribers are instantiated fresh from the current height on each bootstrap, toggling ExperimentalSoftFinalityEnabled should not cause state inconsistencies—confirm no persistent subscriber-type mapping exists in storage.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5baae43 and 48b6d0c.

📒 Files selected for processing (2)
  • cmd/run/cmd.go (3 hunks)
  • config/config.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/run/cmd.go

Comment thread config/config.go
Comment on lines +123 to +131
// ExperimentalSoftFinalityEnabled enables the experimental soft finality feature which syncs
// EVM block and transaction data from the upstream Access node before the block is sealed.
// CAUTION: This feature is experimental and may return incorrect data in certain circumstances.
ExperimentalSoftFinalityEnabled bool
// ExperimentalSealingVerificationEnabled enables the experimental sealing verification feature
// which verifies the hash of the EVM events ingested by the requester engine match the hash
// of the events from the sealed block in the Flow network.
// CAUTION: This feature is experimental and will cause the node to halt if the events don't match.
ExperimentalSealingVerificationEnabled bool

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

LGTM! The experimental configuration fields are well-documented.

The addition of these experimental feature flags is clear and includes appropriate CAUTION warnings about potential data issues and node halting behavior. The naming follows Go conventions and the documentation is explicit about the risks.

However, given the PR context noting that integration tests cannot currently run (Emulator limitation) and the known limitations with execution forks requiring manual operator intervention, please ensure comprehensive operator-facing documentation exists.

Run the following script to verify operator documentation exists for these experimental features:


🏁 Script executed:

#!/bin/bash
# Description: Verify that operator documentation exists for the new experimental features

# Search for documentation files that might cover these features
echo "=== Searching for documentation files ==="
fd -e md -e txt -e rst . | head -20

# Search for references to the experimental flags in documentation
echo -e "\n=== Searching for soft finality documentation ==="
rg -n -C3 --type=md "soft.finality|ExperimentalSoftFinalityEnabled" 

echo -e "\n=== Searching for sealing verification documentation ==="
rg -n -C3 --type=md "sealing.verification|ExperimentalSealingVerificationEnabled"

# Search for any operator guides or deployment docs
echo -e "\n=== Searching for operator/deployment guides ==="
rg -n -l --type=md "operator|deployment|configuration|experimental" | head -10

Length of output: 355



🏁 Script executed:

#!/bin/bash
# Search for any references to the experimental features in the entire codebase
echo "=== Searching for ExperimentalSoftFinalityEnabled references ==="
rg -n "ExperimentalSoftFinalityEnabled" . | head -20

echo -e "\n=== Searching for ExperimentalSealingVerificationEnabled references ==="
rg -n "ExperimentalSealingVerificationEnabled" . | head -20

echo -e "\n=== Checking README.md for experimental feature documentation ==="
cat README.md | head -100

echo -e "\n=== Checking if there's any EXPERIMENTAL documentation pattern ==="
rg -n -C2 "EXPERIMENTAL|experimental.*feature" --type=md | head -40

Length of output: 6591



🏁 Script executed:

#!/bin/bash
# Check how the experimental flags are used in bootstrap.go
echo "=== bootstrap.go lines 165-175 (ExperimentalSoftFinalityEnabled usage) ==="
sed -n '160,180p' bootstrap/bootstrap.go

echo -e "\n=== cmd/run/cmd.go lines 225-240 (Flag definition and assignment) ==="
sed -n '225,240p' cmd/run/cmd.go

echo -e "\n=== Check if there's any configuration documentation ==="
find . -name "*config*" -type f | grep -E "\.(md|txt|rst)$"

echo -e "\n=== Search for any mention of flags/configuration in docs ==="
rg -n "flag|--experimental" README.md | head -20

Length of output: 2161



🏁 Script executed:

#!/bin/bash
# Find where the experimental flags are defined as command-line flags
echo "=== Searching for experimentalSoftFinalityEnabled flag definition ==="
rg -n "experimentalSoftFinalityEnabled" . -B2 -A2 | grep -E "(flag|Flag|Define|cmd|command)" -A2 -B2

echo -e "\n=== Searching for experimentalSealingVerificationEnabled flag definition ==="
rg -n "experimentalSealingVerificationEnabled" . -B2 -A2 | grep -E "(flag|Flag|Define|cmd|command)" -A2 -B2

echo -e "\n=== Check cmd/run/cmd.go for flag.Bool definitions ==="
rg -n "flag.Bool|flag.String" cmd/run/cmd.go | tail -20

echo -e "\n=== Extract README.md configuration table (lines 231-280) ==="
sed -n '231,280p' README.md

Length of output: 9751


Add experimental feature flags to README.md configuration table.

The code changes are sound and the experimental flags have appropriate runtime warnings in cmd/run/cmd.go (lines 299-300). However, these flags are missing from the README.md configuration documentation table (lines 231-280), where operators are directed to find all available configuration options.

Add entries for experimental-soft-finality-enabled and experimental-sealing-verification-enabled to the configuration table so operators have complete visibility of all available flags and their purposes.

🤖 Prompt for AI Agents
In config/config.go around lines 123 to 131, two experimental boolean flags
(ExperimentalSoftFinalityEnabled and ExperimentalSealingVerificationEnabled)
were added but the README.md configuration table (around lines 231-280) is
missing entries for them; update the README.md config table to include rows for
--experimental-soft-finality-enabled and
--experimental-sealing-verification-enabled with concise descriptions matching
the comments/warnings in cmd/run/cmd.go (mentioning that they are experimental
and include the CAUTION text about possible incorrect data for soft finality and
node halt for sealing verification), and ensure flag names, default values, and
descriptions align with existing table formatting and style.

m-Peter and others added 11 commits November 21, 2025 11:16
BatchTxPool previously submitted the first transaction from any EOA burst
immediately, bypassing the pool. If that first transaction carried a future
nonce (common when many goroutines dispatch in parallel), it failed on-chain
and left a nonce gap that caused EVM.batchRun to drop all subsequent nonces.

Confirmed on Flow EVM testnet (2026-06-03, chain 545):
  - 10 txs sent in parallel, shuffled order [8 7 3 9 2 1 6 0 4 5]
  - eth_sendRawTransaction returned success for all 10
  - Only 6/10 landed on-chain; 4 were silently dropped
  - Nonce 6 arrived first → submitted immediately → failed (on-chain nonce=0)
  - Batch [0 1 2 3 4 5 7 8 9] executed 0–5 then stopped at the gap → drop

Fix: Add() now always enqueues every transaction into the per-EOA pool.
The flush timer (TxBatchInterval) is the sole submission path. This ensures
the full burst accumulates before any of it is sorted and submitted, giving
EVM.batchRun a complete, gap-free nonce sequence.

Trade-off: all transactions incur up to TxBatchInterval of additional latency
(default 2.5 s on mainnet). EOAActivityCacheTTL is now a no-op and the flag
is marked deprecated for backward-compatible config parsing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update soft-finality branch with latest changes from main
fix(batch): always pool transactions to eliminate first-tx nonce race (soft-finality)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@services/requester/batch_tx_pool.go`:
- Line 115: The goroutine started with go
batchPool.processPooledTransactions(ctx) lacks panic recovery; wrap the
goroutine invocation or add a top-level defer/recover inside
processPooledTransactions to catch panics, log the error and stack trace, and
continue (so a panic won't crash the whole process). Specifically, add a defer
func() { if r := recover(); r != nil { /* log r and runtime/debug.Stack() via
the package's logger (e.g., batchPool.logger or existing logger) */ } }() at the
start of processPooledTransactions (or wrap the go call in an inline goroutine
that defers recover) so panics are captured and surfaced without terminating the
gateway.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 78abda1c-325a-41d2-a7df-83cb0a0fdf64

📥 Commits

Reviewing files that changed from the base of the PR and between fd4109a and 2738d44.

📒 Files selected for processing (5)
  • cmd/run/cmd.go
  • config/config.go
  • go.mod
  • services/requester/batch_tx_pool.go
  • tests/tx_batching_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • config/config.go
  • cmd/run/cmd.go

eoaActivity: eoaActivity,
}

go batchPool.processPooledTransactions(ctx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for panic recovery in batch_tx_pool.go and verify goroutine resilience
rg -n 'defer.*recover|recover\(\)' services/requester/batch_tx_pool.go

# Also check if processPooledTransactions has internal recovery
rg -A 20 'func.*processPooledTransactions' services/requester/batch_tx_pool.go | rg 'defer|recover'

Repository: onflow/flow-evm-gateway

Length of output: 88


Add recover around the batch processor goroutine to prevent gateway crashes on panic.

In services/requester/batch_tx_pool.go, go batchPool.processPooledTransactions(ctx) is launched without any panic recovery, and processPooledTransactions only defers ticker.Stop()—no recover is present. If anything inside panics, the whole gateway process will terminate (not just the goroutine), breaking batch processing until restart. Wrap the goroutine (or add defer recover() inside processPooledTransactions) to log/alert and keep the service running.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@services/requester/batch_tx_pool.go` at line 115, The goroutine started with
go batchPool.processPooledTransactions(ctx) lacks panic recovery; wrap the
goroutine invocation or add a top-level defer/recover inside
processPooledTransactions to catch panics, log the error and stack trace, and
continue (so a panic won't crash the whole process). Specifically, add a defer
func() { if r := recover(); r != nil { /* log r and runtime/debug.Stack() via
the package's logger (e.g., batchPool.logger or existing logger) */ } }() at the
start of processPooledTransactions (or wrap the go call in an inline goroutine
that defers recover) so panics are captured and surfaced without terminating the
gateway.

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

Labels

None yet

Projects

Status: 👀 In Review

Development

Successfully merging this pull request may close these issues.

PoC to allow indexing unsealed finalized execution results

5 participants