Skip to content

feat(chain): add freeze_height to the unified schema - #49

Merged
bdchatham merged 1 commit into
mainfrom
feat/freeze-height
Aug 27, 2026
Merged

feat(chain): add freeze_height to the unified schema#49
bdchatham merged 1 commit into
mainfrom
feat/freeze-height

Conversation

@bdchatham

Copy link
Copy Markdown
Collaborator

What this does

Adds chain.freeze_height to the unified schema, so a caller can set seid's
freeze-height declaratively.

seid reads freeze-height from app.toml to stop executing at a height while
still serving query RPC. The key was missing from this schema, so
ApplyOverrides rejected it as an unknown key (io.go) and no caller could set
it. That closes the only declarative route: sei-k8s-controller builds a fixed
seid start --home <dataDir> argv with no flag escape hatch, so the config file
is the only way in.

Why it is shaped like halt_height

halt_height is the direct precedent — it sits beside freeze-height in the
same sei-cosmos BaseConfig. This change mirrors it exactly:

Site Change
config.go FreezeHeight uint64 on ChainConfig, unified key chain.freeze_height
legacy.go legacy app.toml key freeze-height on legacyAppConfig
legacy.go both mappings — toLegacyApp() and fromLegacy()
enrichments.go description and blocks unit
validate.go the two rules seid enforces
freeze_test.go round-trip, override, and a validation table

Two judgment calls

No baseDefaults() entry. HaltHeight and HaltTime have none either —
zero already means disabled, and the zero value delivers that. The repo guide
says to add a default for a new field; the established pattern for this exact
kind of field says otherwise, so I followed the pattern. Say the word if you
want it stated explicitly instead.

Tests in a new file. config_test.go is at 884 lines, past the ~500-line
threshold in the repo guide, so the new coverage went to freeze_test.go.

Validation

validate.go now mirrors seid's own ValidateFreeze, so an invalid combination
is reported here instead of failing at node start:

  • freeze_height must fit in an int64. seid converts it and rejects an
    over-range value.
  • freeze_height cannot be combined with halt_height or halt_time. Freeze
    keeps serving queries; halt exits the process.

I did not port seid's third rule — freeze-height cannot be used with
grpc-only. That one reads a CLI flag, not config, so it has no representation
here.

Testing

Check Result
make test ok
go vet ./... clean
staticcheck ./... clean
gofmt -l . clean
golangci-lint run 47 issues on this branch, 47 on origin/main, identical breakdown — none introduced

I checked out origin/main to take that lint baseline rather than assume it.
Note the repo does not pass golangci-lint 2.12.2 even on main, so CI is
pinning an older version.

New coverage:

  • TestFreezeHeight_RoundTrip — asserts the rendered app.toml carries the
    freeze-height spelling seid actually reads, then reads it back.
  • TestFreezeHeight_Override — the chain.freeze_height override path the
    controller and seictl use.
  • TestFreezeHeight_Validate — 6 cases: unset, freeze alone, halt alone, freeze
    with halt height, freeze with halt time, and int64 overflow.

Follow-ups, not in this PR

  • A release commit to bump version.json, per the convention here.
  • sei-sidecar bumps this library. That is the load-bearing consumer — it runs
    the override resolution and validation.
  • sei-k8s-controller bumps it for hygiene only. It never validates or writes
    config; it uses this library for types and constants, merges spec.overrides,
    and forwards the map verbatim. No CRD change is needed, because
    spec.overrides is already a free-form map[string]string.

🤖 Generated with Claude Code

seid reads freeze-height from app.toml to stop executing at a height while
still serving query RPC. The key was absent from the unified schema, so
ApplyOverrides rejected it as an unknown key and no caller could set it
declaratively — the controller forwards spec.overrides verbatim and the
sidecar resolves them through this package.

Modelled on halt_height: unified key chain.freeze_height, legacy app.toml
key freeze-height, both legacy mappings, and field metadata. No default
entry, because zero already means disabled, as with halt_height.

Validation mirrors seid's ValidateFreeze so an invalid config is reported
here instead of failing at node start: freeze_height must fit in an int64,
and it cannot be combined with halt_height or halt_time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 27, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Schema and validation-only extension with tests; no runtime node behavior changes in this repo.

Overview
Adds chain.freeze_height to the unified Sei config so operators can set seid’s freeze-height (stop block execution at a height while keeping query RPC) via overrides and generated app.toml, matching how halt_height works.

The field is wired through ChainConfig, legacy app.toml read/write (freeze-height), schema enrichments, and Validate rules aligned with seid: value must fit in int64, and freeze_height cannot be set together with halt_height or halt_time. New tests cover app.toml round-trip, ApplyOverrides, and those validation cases.

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

@bdchatham
bdchatham merged commit fc2d1ef into main Aug 27, 2026
3 checks passed
bdchatham added a commit that referenced this pull request Aug 27, 2026
Carries #49, which threads `freeze_height` through both schema layers, both
legacy mapping directions, field metadata, and validation. The key is
unreachable until it ships in a tagged version: sei-k8s-controller pins
v0.0.22, which predates it, so a SeiNode setting freeze_height today has
ApplyOverrides reject it as an unknown key.

The key matters because seid reads `freeze-height` from app.toml to stop
executing at a height while still serving query RPC, and the config file is the
only route in. sei-k8s-controller builds a fixed `seid start --home <dataDir>`
argv with no flag escape hatch, and carries no CRD field for freeze. So without
this key there is no declarative way to stand up a frozen node at all.

Unlike v0.0.26, the consuming seid build already exists. Freeze mode is on
release/v6.6: the flag is registered in the vendored sei-cosmos
(server/start.go), sei-tendermint refuses to freeze a validator, and
sei-chain#4006 disables mempool traffic in freeze mode. This release closes the
half of the path that was missing, rather than staging a knob ahead of its
build.

Consumed next by sei-sidecar, which resolves and validates overrides during
config-apply and is therefore what makes this real on a running node, and then
by sei-k8s-controller to keep its pin in sync. The controller needs no code or
CRD change: it uses this library for types and constants, merges
spec.overrides, and forwards the map verbatim.

Releasing the key does not by itself change any node's behaviour. freeze_height
defaults to 0, which means disabled, and validation rejects combining it with
halt_height or halt_time.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant