Skip to content

Initialize app from last header when a frozen node restarts - #4447

Merged
masih merged 1 commit into
mainfrom
masih/1791207120-freeze-init-last-header
Oct 5, 2026
Merged

masih merged 1 commit into
mainfrom
masih/1791207120-freeze-init-last-header

Conversation

@masih

@masih masih commented Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator

A node in freeze mode halts one block below its freeze height and never commits again. When its process restarts, no Commit runs, so BaseApp.Initialized() never fires and the EVM HTTP and WS servers, which wait on it in app/app.go, never start. The node keeps its data and its Tendermint RPC, but EVM RPC stays down from the first restart on. This is PLT-1370.

In nodeImpl.OnStart, right after the handshake, a frozen node with a non-empty chain now loads the block meta at state.LastBlockHeight and passes its header to Application.InitLastHeader. BaseApp sets checkState from that header and signals initialization, which is what Commit does after a block. Sending only the start signal was rejected because checkState would keep the empty header from LoadLatestVersion, and the RPC "latest" context reads it.

Nodes not in freeze mode are unchanged. A frozen node that is still catching up now opens EVM RPC at startup instead of after its first block; later commits reset checkState as before. TestFreezeModeRestartInitializesAppFromLastHeader covers the node wiring. TestFrozenNodeServesEVMRPCAfterRestart restarts seid on the height-10 frozen node in the frozen RPC router cluster. Locally it timed out on main and passed with this change.

Part of PLT-1370.

A frozen node commits no blocks after a restart, so nothing called
Commit and app.Initialized() never fired. The EVM HTTP and WS servers
wait on that signal and never started. In freeze mode, pass the last
stored block header to InitLastHeader after the handshake.
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access that start with 'Devin'.

  • Disable automatic comment, CI, and merge conflict monitoring

@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedOct 8, 2026, 9:09 AM

@codecov

codecov Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 56.77%. Comparing base (82c0cda) to head (c31603a).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
sei-tendermint/node/node.go 80.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4447      +/-   ##
==========================================
+ Coverage   56.75%   56.77%   +0.01%     
==========================================
  Files        2126     2126              
  Lines      166836   166868      +32     
==========================================
+ Hits        94691    94734      +43     
+ Misses      72140    72129      -11     
  Partials        5        5              
Flag Coverage Δ
sei-chain 54.94% <80.00%> (+0.01%) ⬆️
sei-db 75.17% <ø> (+0.36%) ⬆️
sei-db-state-db 78.84% <ø> (+0.01%) ⬆️

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

Files with missing lines Coverage Δ
sei-tendermint/node/node.go 74.63% <80.00%> (+1.12%) ⬆️

... and 29 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 commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator Author

@alexander-sei backport on this one was too hairy so I did it manually -- removing the lable

@masih masih removed the backport release/v6.6 Backport to release v6.6 label Oct 5, 2026
@masih
masih marked this pull request as ready for review October 5, 2026 15:01
@cursor

cursor Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches node startup and app initialization for freeze-mode restarts; wrong header or timing could affect EVM RPC “latest” context, but scope is limited to freezeHeight > 0 with existing chain data.

Overview
Frozen nodes that never commit again after restart no longer leave the app uninitialized, so EVM RPC can come up from persisted chain state instead of staying down until a Commit that never happens.

In nodeImpl.OnStart, after the handshake, a node with freeze mode and existing chain height loads the block meta at state.LastBlockHeight and calls Application.InitLastHeader with that header (fails startup if the block is missing). That mirrors post-Commit initialization: checkState reflects the real latest header and the app signals readiness for query/EVM servers. Non-frozen behavior is unchanged.

Tests: unit coverage that restart passes the stored last header into the app; Docker integration test that restarts seid on a height-10 frozen node and asserts eth_getBlockByNumber still works.

Reviewed by Cursor Bugbot for commit c31603a. 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.

When a node in freeze mode restarts, it now passes the last stored block header to InitLastHeader right after the handshake, so BaseApp sets checkState and signals initialization, and EVM RPC comes up without needing another Commit. I'm approving because the change is correct: freeze mode turns off state sync, so the handshake always runs first and app height matches state.LastBlockHeight; autobahn can't be combined with freeze mode; setCheckState holds its lock; and non-frozen nodes are unaffected. The one other reader (codex) reported nothing, which matches my reading apart from the two test-harness nits below.

2 nits, not posted on the code
  • integration_test/frozen_rpc_router/router_test.go:147 — Running start_sei.sh again appends another Done to the shared build/generated/launch.complete. That file then has more lines than there are nodes, which can break any later step that checks for an exact count, such as release-upgrade-test.sh's -eq "$NODE_COUNT".
  • integration_test/frozen_rpc_router/router_test.go:107 — This test restarts node1, which TestFrozenRPCRouterReachesEveryNode also uses, so both tests only pass if they run in file order. Running with -shuffle or a -run subset could send the router test to a node that is still restarting.

seidroid review · decision approve · session 964dcb912c8346c384c53fb7c6ccd57d · turn resp_claude_0baa7798f04ec4be38fc1e35be444238 · item 6774be652ad35355844485248f75d26e

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

Comment on lines +580 to +586
if n.freezeHeight > 0 && state.LastBlockHeight > 0 {
meta := n.blockStore.LoadBlockMeta(state.LastBlockHeight)
if meta == nil {
return fmt.Errorf("freeze mode: block %d is missing from the block store", state.LastBlockHeight)
}
n.rpcEnv.App.InitLastHeader(meta.Header.ToProto())
}

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.

nit: should this not just be in the freeze mode if-statement above or move that check down?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's here because higher up it won't work: It has to run after the handshake and after the state is reloaded from the store. Until then the app hasn't been brought up to state.LastBlockHeight, so state.LastBlockHeight isn't the number we want.

WE can moving further down, but the catch is that if handshake fails then we swallow the freezemode error which is argubly a more important log line from an operators perspective.

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 sure I follow. I'm suggesting removing the if-statement checking for freeze mode to write a log into this if-statement. Anyways, not a blocker

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No worries, let me be clearer: it can go in one block, but it has to be nested because a freeze node that hasn't synced any blocks yet should still log that freeze mode is on. The only cost is the log moving after the handshake. Happy to make that change.

@masih
masih added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit d4853e5 Oct 5, 2026
107 of 110 checks passed
@masih
masih deleted the masih/1791207120-freeze-init-last-header branch October 5, 2026 15:36
masih added a commit that referenced this pull request Oct 5, 2026
This is the v6.6 version of #4447. A node in freeze mode halts one block
below its freeze height and never processes another block. On v6.6 the
EVM HTTP and WS servers wait for start signals that only `ProcessBlock`
sends, so once a frozen node restarts they never start. The node keeps
its data and its Tendermint RPC, but EVM RPC stays down. `frozen-6-7-0`
runs v6.6.3 and hits this on every restart.

v6.6 has no `InitLastHeader` in the ABCI `Application` interface, so
#4447 cannot be cherry-picked as is. `Proxy.InitLastHeader` forwards the
call only to apps that implement it, which leaves the interface
unchanged. `BaseApp.InitLastHeader` sets checkState from the header, as
`Commit` does. `App.InitLastHeader` calls it and then sends the start
signals through `signalEVMServersStart`, which `ProcessBlock` now also
uses, so each signal is still sent once. `nodeImpl.OnStart` makes the
same freeze-only call as on main, with the block meta at
`state.LastBlockHeight`.

`TestFrozenNodeServesEVMRPCAfterRestart` restarts seid on the height-10
frozen node in the frozen RPC router cluster. Locally it timed out with
the v6.6.3 binary and passed with this change.

Part of PLT-1370.
@masih masih added the backport release/v6.7 Backport to release v6.7 label Oct 8, 2026
@masih

masih commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator Author

/backport

@seidroid

seidroid Bot commented Oct 8, 2026

Copy link
Copy Markdown

Created backport PR for release/v6.7:

Please cherry-pick the changes locally and resolve any conflicts.

git fetch origin backport-4447-to-release/v6.7
git worktree add --checkout .worktree/backport-4447-to-release/v6.7 backport-4447-to-release/v6.7
cd .worktree/backport-4447-to-release/v6.7
git reset --hard HEAD^
git cherry-pick -x d4853e532b878fe34033ff63495521c782baf0fb
git push --force-with-lease

alexander-sei added a commit that referenced this pull request Oct 12, 2026
…n node restarts (#4507)

Backport of #4447 to `release/v6.7`.

---------

Co-authored-by: Masih H. Derkani <m@derkani.org>
Co-authored-by: alexander-sei <alexanderh@seinetwork.io>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants