Skip to content

Refuse to start a mock_chain_validation build outside its reserve role - #4147

Open
blindchaser wants to merge 1 commit into
release/v6.7from
yiren/memiavl-reserve-guard-v67
Open

Refuse to start a mock_chain_validation build outside its reserve role#4147
blindchaser wants to merge 1 commit into
release/v6.7from
yiren/memiavl-reserve-guard-v67

Conversation

@blindchaser

Copy link
Copy Markdown
Contributor

On top of v6.7.0-rc1. That tag is currently the head of release/v6.7
(e59189ea7), so this branch is the tag plus one commit and the cherry-pick was
clean.

What this changes

mock_chain_validation builds now refuse to start unless they are a non-validator
pinned to memiavl_only. Production builds compile a no-op and are unaffected.

The check is one function with two build variants, called once from
startInProcess:

if err := assertReserveNodeAllowed(cfg.Mode, config.StateCommit.WriteMode); err != nil {
    return err
}

Why

A mock_chain_validation binary swallows ErrAppHash and the validator-set
sentinels. That is exactly what makes it useful as a reserve node — it keeps
following a chain it disagrees with — and it is also what makes two configurations
dangerous rather than merely wrong.

As a validator it would prevote and precommit blocks whose app hash contradicts
its own state, instead of prevoting nil. A node that cannot detect divergence must not
vote on it.

Left unpinned it resolves to auto and joins the migration on the first block
after governance raises the batch size, spending the reserve with nothing to signal
that it happened.

The unpinned case is the default state of a fresh node rather than an unlikely slip.
A generated app.toml renders sc-write-mode = "memiavl_only" but does not
render sc-write-mode-enable-auto, and while that unrendered key stays true it
discards the rendered one. So pinning the visible key is not enough, which is why the
error message names the invisible one.

Where the guard lives

startInProcess is the single function every deployed node passes through and no test
reaches, so the guard is an invariant rather than a convention a later call site can
forget. Refusing at startup costs a restart to discover, and saves finding out at the
first divergent block.

Testing

Both build variants are covered, since the interesting behaviour is which variant
compiles:

  • TestAssertReserveNodeAllowed_Default_AcceptsEveryCombination sweeps all 3 node
    modes × 9 write modes and asserts a production build refuses nothing.
  • TestAssertReserveNodeAllowed_MockChainValidation_Matrix sweeps the same grid and
    asserts exactly one cell is allowed.
  • TestAssertReserveNodeAllowed_MockChainValidation_RefusesPinnedValidator covers a
    correctly pinned validator, so the write-mode half cannot mask the mode half.
  • TestAssertReserveNodeAllowed_MockChainValidation_ErrorNamesTheAutoKey pins that
    the message names sc-write-mode-enable-auto, because that is the key the operator
    has to set and the one a generated app.toml never shows them.

go test ./sei-cosmos/server/ passes with and without -tags mock_chain_validation,
and make fmtcheck is clean.

Release note

v6.7.0-rc1 is already cut, so merging this implies an rc2.

A mock_chain_validation binary swallows ErrAppHash and the validator-set
sentinels, which is what lets a reserve node keep following a chain it
disagrees with. That same property makes two configurations dangerous
rather than merely wrong: as a validator it would prevote and precommit
blocks whose app hash contradicts its own state instead of prevoting nil,
and left unpinned it resolves to auto and joins the migration on the first
block after governance raises the batch size, spending the reserve with
nothing to signal that it happened.

Both are now refused in startInProcess, which every deployed node reaches
and no test does. The unpinned case is the default state of a fresh node
rather than an unlikely slip: a generated app.toml renders
sc-write-mode = "memiavl_only" but not sc-write-mode-enable-auto, and
while that unrendered key stays true it discards the rendered one.

Production builds compile the no-op variant and are unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
(cherry picked from commit a161677)
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-11T14:47:58.806250Z 31a9ded PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 11, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Adds a startup check on the universal in-process start path; production is a no-op but misconfigured mock_chain_validation operators are blocked from running as validators or unpinned migrators.

Overview
mock_chain_validation builds now fail fast at node start unless Tendermint mode is non-validator and state-commit write mode is exactly memiavl_only. Production builds compile a no-op assertReserveNodeAllowed and behave as before.

The guard runs in startInProcess right after app config load, using cfg.Mode and config.StateCommit.WriteMode. Validator mode is rejected because that build tolerates app-hash / validator-set mismatches and must not vote. Any write mode other than memiavl_only (including effective auto when sc-write-mode-enable-auto defaults true) is rejected so the reserve node cannot silently join migration.

Tests cover both build tags: production accepts all mode × write-mode pairs; mock_chain_validation allows only one combination and checks error text for validator refusal and the auto-enable key.

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

@github-actions

github-actions Bot commented Sep 11, 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 11, 2026, 5:38 PM

@codecov

codecov Bot commented Sep 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 33.33333% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.30%. Comparing base (e59189e) to head (31a9ded).

Files with missing lines Patch % Lines
sei-cosmos/server/start.go 0.00% 2 Missing ⚠️

❌ Your patch check has failed because the patch coverage (33.33%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@               Coverage Diff                @@
##           release/v6.7    #4147      +/-   ##
================================================
- Coverage         61.34%   60.30%   -1.04%     
================================================
  Files              2163     2065      -98     
  Lines            188785   177105   -11680     
================================================
- Hits             115813   106812    -9001     
+ Misses            62254    60517    -1737     
+ Partials          10718     9776     -942     
Flag Coverage Δ
sei-chain-pr 45.28% <33.33%> (?)
sei-db 69.80% <ø> (ø)
sei-db-state-db ?

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

Files with missing lines Coverage Δ
sei-cosmos/server/reserve_policy_default.go 100.00% <100.00%> (ø)
sei-cosmos/server/start.go 35.89% <0.00%> (-0.27%) ⬇️

... and 99 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.

@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.

A small, well-placed startup guard: startInProcess is genuinely the only production node-start path (the other node.New call sites are test/inprocess-tagged), and config.StateCommit.WriteMode from GetConfig is the post-ApplyWriteModeAuto effective mode, so the pin check is reading the right value. No blockers; two non-blocking gaps — the sibling mock_block_validation image has the same validator hazard and is left unguarded, and the guard's godoc carries rationale the repo guide says belongs inline.

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

Blockers

  • None at the file/PR level.

Non-blocking

  • None at the file/PR level.
  • 2 suggestion(s)/nit(s) flagged inline on specific lines.
  • 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.

Pre-existing issues

  • [suggestion] No CI job runs go test -tags mock_chain_validation (go-test.yml and sei-db-tests.yml set GOFLAGS=-tags=ledger,test_ledger_mock), so the entire mock_chain_validation test surface — the pre-existing sei-tendermint/types/consensus_policy_mock_chain_validation_test.go and friends, and now sei-cosmos/server/reserve_policy_mock_chain_validation_test.go — is neither run nor compile-checked on a PR. The only thing that compiles that build is the on-demand/nightly ECR image job, so a break in tag-gated code surfaces at image-build time rather than at review time.

@@ -0,0 +1,10 @@
//go:build !mock_chain_validation

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The !mock_chain_validation tag means a mock_block_validation binary compiles this no-op, but that build has the same voting hazard the guard exists to close: sei-tendermint/types/consensus_policy_mock_block_validation.go swallows ErrAppHash (plus ErrDataHash, ErrUpgradeBeforeTrigger), so such a validator would also prevote and precommit blocks whose app hash contradicts its own state. It is a shipped image too — .github/workflows/ecr.yml pushes sei-chain:mock_block_validation-<ref> alongside the chain-validation one.

The write-mode pin is specific to the reserve-node role and shouldn't apply there, but the validator half arguably should. Widening the guard file to !mock_block_validation && !mock_chain_validation and giving mock_block_validation a variant that refuses ModeValidator only would make "a build that cannot detect divergence must not vote" the invariant rather than a per-tag convention. If that is deliberately out of scope for this PR, a one-line note saying so would help the next person reading this tag.

// Tendermint mode and effective state-commit write mode. This build starts
// only as a non-validator pinned to memiavl_only.
//
// Both conditions are what make the build a reserve rather than a liability.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] AGENTS.md § Godoc asks for what, not why or how: "Rationale, trade-offs, and mechanism belong in an inline comment at the line that needs them, or nowhere", and "Multi-paragraph godocs are rare." These two paragraphs are rationale (why a validator is unsafe, why an unpinned node spends the reserve) and a trade-off (restart cost vs. divergent block) attached to the doc comment.

The content is worth keeping — it just belongs at the branches it explains. Suggest trimming the godoc to the first paragraph (lines 12-14, which already says exactly what the function does) and moving the validator rationale above the nodeMode == tmcfg.ModeValidator check and the migration/pin rationale above the writeMode != sctypes.MemiavlOnly check.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant