Skip to content

fix(major-upgrade): bump SND template + settle before binary swap#390

Merged
bdchatham merged 3 commits into
mainfrom
fix/major-upgrade-bump-snd-template
Jun 8, 2026
Merged

fix(major-upgrade): bump SND template + settle before binary swap#390
bdchatham merged 3 commits into
mainfrom
fix/major-upgrade-bump-snd-template

Conversation

@bdchatham

Copy link
Copy Markdown
Collaborator

Problem

The nightly major-upgrade scenario fails before completing the upgrade, for two independent reasons:

  1. Per-child image churn. The scenario patched each child SeiNode.spec.image directly (per-child UpdateNodeImage). Those SeiNodes are owned by the SeiNodeDeployment, whose controller re-asserts the child image from the template on every reconcile (ensureSeiNode). The per-child patch and the SND controller fight → child spec.image flip-flops → StatefulSet churn → observe-image never settles → deadlock.
  2. Premature binary swap. The entry is Serial; the image swap followed wait-for-proposal-to-pass with nothing between, so the post-upgrade binary landed while the chain was still ~100 blocks below UPGRADE_HEIGHT. The new binary then processed a pre-upgrade block and panicked BINARY UPDATED BEFORE TRIGGER (sei-cosmos/x/upgrade/abci.go).

Fix

  • bump-snd-image replaces the per-child fan-out with a single patch of the SND template image. The controller is the sole source of truth for child image, so there's no fight and the rollout settles. (runner/rbac.yaml gains seinodedeployments: patch,update; an envtest asserts child generation stays stable.)
  • settle-into-halt (fixed 150s wait) sits between proposal-pass and the bump. The upgrade height can't be polled — all validators halt together at UPGRADE_HEIGHT and stop serving RPC exactly when the predicate would be true — so the gate is time-based. Over-waiting is free (the chain sits halted until the swap); only too-short fails, so it errs generous (~2.5× the ~60s remaining after the proposal passes).

Validation

Ran end-to-end on harbor against this branch (SCENARIO_REF, unchanged SEITASK_IMAGE): genesis → proposal → votes → settle-into-halt (chain halted on the old binary, observed) → bump-snd-image → all 4 validators rolled onto v6.5.0, applied the upgrade at the height, and advanced past POST_UPGRADE_HEIGHT (await-post-upgrade-progress ✓ for all four). No BINARY UPDATED BEFORE TRIGGER, no observe-image deadlock.

Out of scope (deliberate)

  • No write-mode override. major-upgrade pins a fixed v6.4.4→v6.5.0 image pair, both of which default to cosmos_only. The memiavl_only override the release/load scenarios use is only valid on the newer $SEID_IMAGE they run; it panics on v6.4.4.
  • Genesis gentx-collection race (a rare seictl ceremony issue seen once during testing) is intermittent and tracked separately.

Platform follow-up (separate PR)

clusters/harbor/nightly/major-upgrade/: bump SCENARIO_REF to this merge commit, and mirror the seinodedeployments: patch,update grant into rbac.yaml.

🤖 Generated with Claude Code

bdchatham and others added 2 commits June 7, 2026 21:59
…teNodeImage

Per-child UpdateNodeImage patches each child SeiNode spec.image while the
parent SND template stays at the old image. The SND controller re-asserts
the child image from its template every reconcile (ensureSeiNode), so the
child image flip-flops, the StatefulSet churns, updateRevision never settles,
observe-image never completes, mark-ready never fires, and the seid sidecar
gate keeps the node from signing -> quorum collapse -> await fails.

Replace early-upgrade-node-0 + wait-for-target-height + per-child
upgrade-nodes-1-2-3 with one bump-snd-image step that
`kubectl patch seinodedeployment spec.template.spec.image`. The SND template
is the single source of child image; the controller rolls all validators
onto the new binary. await-post-upgrade-progress fans out over all four
nodes' POST_UPGRADE_HEIGHT predicate.

- runner Role: add patch/update on seinodedeployments for bump-snd-image.
- inplace_rollout envtest: assert child generations hold steady post-rollout
  (the no-flip-flop guarantee the scenario now depends on).

Does not preserve staggered per-node rollout (deliberate -- see README
limitation #3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…inary swap

The major-upgrade entry is Serial; bump-snd-image followed wait-for-proposal-to-pass
with nothing between them, so the SND template image was swapped to the post-upgrade
build while the chain was still ~100 blocks below UPGRADE_HEIGHT. The new binary then
processed a pre-upgrade block and panicked "BINARY UPDATED BEFORE TRIGGER" (sei-cosmos
x/upgrade abci.go) -- node-0's observed failure.

Insert a settle-into-halt step (fixed 150s sleep) between proposal-pass and the bump.
UPGRADE_HEIGHT is current + 200 blocks at compute-target-height, but the proposal flow
(~60s voting + tally) burns most of that budget first, leaving ~100 blocks/~60s once
the proposal passes; 150s is ~2.5x that remainder. The upgrade height can't be polled:
all validators halt together at UPGRADE_HEIGHT and stop serving RPC exactly when the
predicate would be satisfiable, so the gate is time-based. Over-waiting is free (the
chain sits halted until the swap); only too-short fails, so the wait errs generous.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jun 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes destructive nightly upgrade orchestration and runner RBAC on SeiNodeDeployments; timing-based settle step can be fragile if block times exceed assumptions.

Overview
Fixes the major-upgrade acceptance workflow so upgrades complete without image oscillation or premature binary swaps.

Workflow redesign: Per-child UpdateNodeImage steps (early upgrade, staggered nodes 1–3, downgrade node-0, etc.) are removed. After governance passes, settle-into-halt sleeps 150s so validators can reach UPGRADE_HEIGHT and halt on the old binary before any swap (avoids BINARY UPDATED BEFORE TRIGGER). bump-snd-image merges a single patch to spec.template.spec.image on the SeiNodeDeployment; the SND controller rolls all validators together. await-post-upgrade-progress runs in parallel for nodes 0–3 past POST_UPGRADE_HEIGHT.

RBAC: seitask-runner gains patch on seinodedeployments for the bump step.

Tests: Envtest TestInPlaceRollout_EndToEnd adds a post-rollout Consistently check that every child spec.image stays on the template image (guards against the flip-flop that broke per-child patches).

Docs: scenarios/README.md and workflow comments describe the SND-template-as-source-of-truth model and note that staggered per-node upgrade is intentionally out of scope until SND-level rollout primitives exist.

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

- envtest: assert child spec.image stays pinned to the template image after
  rollout (the real no-flip-flop invariant) instead of metadata.generation,
  which takes one benign bump when the revision podLabel resyncs post-rollout.
- runner/rbac.yaml: drop unused `update` on seinodedeployments; bump-snd-image
  only `kubectl patch`es, so `patch` is sufficient.
- major-upgrade.yaml: make the settle-wait comment's block-rate assumption
  explicit (~1s/block breaks the 150s wait) instead of the misleading "2.5x".
- README: drop stale PANIC_BOUNDARY from compute-target-height outputs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@bdchatham bdchatham merged commit 75a6c8b into main Jun 8, 2026
5 checks passed
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