Skip to content

Persist the EVM-only execution cursor with each block's FlatKV state - #4190

Merged
masih merged 5 commits into
giga-1from
masih/1789484452-evmonly-durable-cursor
Sep 16, 2026
Merged

masih merged 5 commits into
giga-1from
masih/1789484452-evmonly-durable-cursor

Conversation

@masih

@masih masih commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Fixes #4169. The EVM-only Autobahn app made FlatKV state durable inside FinalizeBlock but kept its height, app hash and parent hash only in memory, advanced by Commit. After a restart Info() reported height 0, the Giga router took the fresh-genesis branch, and InitChain plus block 1 were replayed against state that already held those nonces, so the node crash-looped. The initialHeight == 1 early return in the InitChain guard meant a non-empty store was never refused at the default initial height.

The executor gains an optional BlockChangeSetEncoder whose named changesets are appended to the block's state changesets in the same CommitStateChanges call. The app uses it to write an evmonly/cursor record (height ‖ appHash ‖ parentHash ‖ gasLimit) atomically with each block, and NewEVMOnlyApplication reads it back on construction so Info() reports the durable height and the router takes its restart branch. A crash between FinalizeBlock(N) and Commit(N) therefore restarts at N. InitChain is refused once an executor exists or the store is at any version other than 0 or initialHeight-1. The chained SHA-256 app hash is unchanged.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 16, 2026, 8:20 AM

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.74074% with 26 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.54%. Comparing base (7e3ca24) to head (50c4f0f).
⚠️ Report is 5 commits behind head on giga-1.

Files with missing lines Patch % Lines
sei-tendermint/internal/evmonlyapp/app.go 76.13% 21 Missing ⚠️
sei-tendermint/internal/evmonlyapp/cursor.go 91.89% 3 Missing ⚠️
giga/evmonly/giga_store.go 80.00% 1 Missing ⚠️
sei-tendermint/node/public.go 66.66% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           giga-1    #4190      +/-   ##
==========================================
- Coverage   65.55%   65.54%   -0.02%     
==========================================
  Files        2081     2077       -4     
  Lines      157460   157117     -343     
==========================================
- Hits       103222   102975     -247     
+ Misses      54097    54001      -96     
  Partials      141      141              
Flag Coverage Δ
sei-chain-pr 83.46% <80.74%> (?)
sei-db ?

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

Files with missing lines Coverage Δ
giga/evmonly/executor.go 91.28% <100.00%> (+0.07%) ⬆️
sei-tendermint/internal/p2p/giga_router_common.go 78.35% <ø> (+0.15%) ⬆️
giga/evmonly/giga_store.go 92.30% <80.00%> (-0.80%) ⬇️
sei-tendermint/node/public.go 81.81% <66.66%> (-0.66%) ⬇️
sei-tendermint/internal/evmonlyapp/cursor.go 91.89% <91.89%> (ø)
sei-tendermint/internal/evmonlyapp/app.go 80.08% <76.13%> (-1.69%) ⬇️

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@masih
masih changed the base branch from main to giga-1 September 15, 2026 15:12
FlatKV state became durable in FinalizeBlock while the app's height, app hash
and parent hash lived only in memory, so a restarted evmonly node reported
height 0, re-ran InitChain and block 1, and crash-looped on existing nonces.
The cursor is now written as an evmonly named changeset in the same
CommitStateChanges call as the block's state, restored on construction, and a
non-empty store refuses InitChain at any initial height.
@devin-ai-integration
devin-ai-integration Bot force-pushed the masih/1789484452-evmonly-durable-cursor branch from 665d830 to 273ea8b Compare September 15, 2026 15:17
@masih
masih marked this pull request as ready for review September 16, 2026 07:59
@masih
masih requested review from codchen and wen-coding September 16, 2026 07:59
@cursor

cursor Bot commented Sep 16, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Changes consensus restart behavior, InitChain guards, and how ABCI height/app hash are sourced relative to durable executor commits.

Overview
The EVM-only Autobahn app previously kept height, app hash, and parent hash in memory and only advanced them on ABCI Commit, while ExecuteBlock already made FlatKV state durable in FinalizeBlock. After restart, Info() reported height 0, the Giga router replayed InitChain and block 1, and the node crash-looped on existing state.

The executor now accepts an optional WithBlockChangeSetEncoder hook that appends app-specific named changesets in the same CommitStateChanges call as the block’s EVM changes. The EVM-only app uses it to persist an evmonly/cursor record (height, app hash, parent hash, gas limit) atomically with each block, and NewEVMOnlyApplication loads that cursor on startup so Info() reflects durable storage. Commit only promotes the in-memory pending cursor to committed; restart resumes at the last finalized block even if Commit never ran. InitChain is refused when storage already has blocks or an executor exists, and initial-version seeding tolerates a crash after seed-only.

Tests cover restart resume, finalize-without-commit, and repeat InitChain; node wiring handles the new (Application, error) constructor.

Reviewed by Cursor Bugbot for commit 50c4f0f. Bugbot is set up for automated code reviews on this repo. Configure here.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Persisting the execution cursor in the same CommitStateChanges batch as the block's EVM state is a sound fix for the restart crash-loop, and the restart/finalized-but-uncommitted paths are covered by new end-to-end tests. Two non-blocking cleanups: a duplicated seedInitialStateVersion call in InitChain, and an implicit assumption behind abandonPending.

Findings: 0 blocking | 3 non-blocking | 2 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • [suggestion] evmOnlyCursor.encode/decodeEVMOnlyCursor introduce a new hand-rolled 80-byte on-disk format, but its error branches (wrong length, height > int64) have no direct test — the restart tests only exercise the happy round trip. A small table test in cursor.go's package would pin the layout so a future field addition can't silently change the byte order or size.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.

Comment thread sei-tendermint/internal/evmonlyapp/app.go
Comment thread sei-tendermint/internal/evmonlyapp/app.go Outdated
Comment thread giga/evmonly/README.md Outdated
block can be retried. A state failure can leave receipts behind, but retrying
the block overwrites them. `ResultSink` runs only after both stores succeed.

Because state becomes durable inside `ExecuteBlock`, before any ABCI `Commit`,

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.

Not entirely accurate. The crash durability point is when we flush the block to BlockDB. The storage layer is intentionally asynchronous, and so at any point in time we may have dozens of executed blocks that are not crash durable at the DB layer. This is ok though, since we can re-execute blocks from the BlockDB.

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.

In theory we could make ExecuteBlock crash durable as this comment suggests. Cost of doing this is likely on the order of 50% to 80% reduction in max DB throughput.

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.

Thanks, reworded in 50c4f0f. It no longer claims crash durability at ExecuteBlock; it now says the store's version advances independently of ABCI Commit and that what survives a restart is decided by the storage layer (async flush, re-execution from BlockDB), which is why Info() has to come from storage rather than memory. Left unresolved in case you want to tweak the wording further.

@masih
masih added this pull request to the merge queue Sep 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 16, 2026
@masih
masih added this pull request to the merge queue Sep 16, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 16, 2026
@masih
masih added this pull request to the merge queue Sep 16, 2026
Merged via the queue into giga-1 with commit 02dcb38 Sep 16, 2026
80 checks passed
@masih
masih deleted the masih/1789484452-evmonly-durable-cursor branch September 16, 2026 10:22
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.

evmonly: the app keeps its committed height in memory, so any restart after block 1 crashloops forever

3 participants