Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions sei-cosmos/server/reserve_policy_default.go
Original file line number Diff line number Diff line change
@@ -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.


package server

import sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types"

// assertReserveNodeAllowed reports whether a node may start with the given
// Tendermint mode and effective state-commit write mode. Production builds
// accept every combination the configuration itself accepts.
func assertReserveNodeAllowed(string, sctypes.WriteMode) error { return nil }
41 changes: 41 additions & 0 deletions sei-cosmos/server/reserve_policy_default_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//go:build !mock_chain_validation

package server

import (
"testing"

"github.com/stretchr/testify/require"

sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types"
tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config"
)

// A production build must not refuse any mode/write-mode combination; the
// reserve guard exists only in the mock_chain_validation build.
func TestAssertReserveNodeAllowed_Default_AcceptsEveryCombination(t *testing.T) {
for _, nodeMode := range allNodeModes() {
for _, writeMode := range allSCWriteModes() {
require.NoError(t, assertReserveNodeAllowed(nodeMode, writeMode),
"mode %q with write mode %q must be accepted by a production build", nodeMode, writeMode)
}
}
}

func allNodeModes() []string {
return []string{tmcfg.ModeFull, tmcfg.ModeValidator, tmcfg.ModeSeed}
}

func allSCWriteModes() []sctypes.WriteMode {
return []sctypes.WriteMode{
sctypes.MemiavlOnly,
sctypes.MigrateEVM,
sctypes.EVMMigrated,
sctypes.MigrateAllButBank,
sctypes.AllMigratedButBank,
sctypes.MigrateBank,
sctypes.FlatKVOnly,
sctypes.TestOnlyDualWrite,
sctypes.Auto,
}
}
46 changes: 46 additions & 0 deletions sei-cosmos/server/reserve_policy_mock_chain_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//go:build mock_chain_validation

package server

import (
"fmt"

sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types"
tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config"
)

// assertReserveNodeAllowed reports whether a node may start with the given
// 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.

// The consensus policy compiled in here swallows ErrAppHash and the
// validator-set sentinels, so a validator running it would prevote and
// precommit blocks whose app hash contradicts its own state instead of
// prevoting nil. And a node that is not pinned joins the migration on the
// first block after governance raises the batch size, which spends the reserve
// with nothing to signal that it happened.
//
// Refusing here rather than trusting app.toml costs a restart to discover and
// saves finding out at the first divergent block.
func assertReserveNodeAllowed(nodeMode string, writeMode sctypes.WriteMode) error {
if nodeMode == tmcfg.ModeValidator {
return fmt.Errorf(
"mock_chain_validation builds must not run as a validator: this build "+
"swallows app-hash and validator-set validation failures, so it cannot "+
"safely vote; set mode = %q in config.toml",
tmcfg.ModeFull,
)
}
if writeMode != sctypes.MemiavlOnly {
return fmt.Errorf(
"mock_chain_validation builds must run %[1]q, got %[2]q: set "+
"state-commit.sc-write-mode = %[1]q and "+
"state-commit.sc-write-mode-enable-auto = false in app.toml (the latter "+
"is absent from a generated app.toml and defaults to true, which "+
"discards the former)",
sctypes.MemiavlOnly, writeMode,
)
}
return nil
}
62 changes: 62 additions & 0 deletions sei-cosmos/server/reserve_policy_mock_chain_validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//go:build mock_chain_validation

package server

import (
"testing"

"github.com/stretchr/testify/require"

sctypes "github.com/sei-protocol/sei-chain/sei-db/state_db/sc/types"
tmcfg "github.com/sei-protocol/sei-chain/sei-tendermint/config"
)

// Exactly one combination may start: a non-validator pinned to memiavl_only.
func TestAssertReserveNodeAllowed_MockChainValidation_Matrix(t *testing.T) {
for _, nodeMode := range allNodeModes() {
for _, writeMode := range allSCWriteModes() {
allowed := nodeMode != tmcfg.ModeValidator && writeMode == sctypes.MemiavlOnly
err := assertReserveNodeAllowed(nodeMode, writeMode)
if allowed {
require.NoError(t, err, "mode %q with write mode %q must be accepted", nodeMode, writeMode)
continue
}
require.Error(t, err, "mode %q with write mode %q must be refused", nodeMode, writeMode)
}
}
}

// Validator mode is refused whatever the write mode, so a correctly pinned
// validator does not slip through.
func TestAssertReserveNodeAllowed_MockChainValidation_RefusesPinnedValidator(t *testing.T) {
err := assertReserveNodeAllowed(tmcfg.ModeValidator, sctypes.MemiavlOnly)
require.Error(t, err)
require.Contains(t, err.Error(), tmcfg.ModeFull)
}

// Auto is what a forgotten sc-write-mode-enable-auto resolves to, and it is the
// failure the write-mode half of this guard exists for, so the message must
// name that key.
func TestAssertReserveNodeAllowed_MockChainValidation_ErrorNamesTheAutoKey(t *testing.T) {
err := assertReserveNodeAllowed(tmcfg.ModeFull, sctypes.Auto)
require.Error(t, err)
require.Contains(t, err.Error(), "sc-write-mode-enable-auto")
}

func allNodeModes() []string {
return []string{tmcfg.ModeFull, tmcfg.ModeValidator, tmcfg.ModeSeed}
}

func allSCWriteModes() []sctypes.WriteMode {
return []sctypes.WriteMode{
sctypes.MemiavlOnly,
sctypes.MigrateEVM,
sctypes.EVMMigrated,
sctypes.MigrateAllButBank,
sctypes.AllMigratedButBank,
sctypes.MigrateBank,
sctypes.FlatKVOnly,
sctypes.TestOnlyDualWrite,
sctypes.Auto,
}
}
3 changes: 3 additions & 0 deletions sei-cosmos/server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ func startInProcess(
if err := config.ValidateFreeze(); err != nil {
return err
}
if err := assertReserveNodeAllowed(cfg.Mode, config.StateCommit.WriteMode); err != nil {
return err
}
gRPCOnly := ctx.Viper.GetBool(flagGRPCOnly)
if gRPCOnly && config.FreezeHeight > 0 {
return errors.New("freeze-height cannot be used with grpc-only mode")
Expand Down
Loading