feat(sidecar): gov-software-upgrade handler (PR-C of #163)#177
Merged
Conversation
Software-upgrade proposals only in MVP. Composes on the sign-tx foundation (#170 + #173) and mirrors the gov-vote shape (#175). REHYDRATION RISK SHIPPED KNOWINGLY: MsgSubmitProposal is NOT chain-idempotent. A crash between BroadcastSync and task-result persist re-runs this handler on pod restart, which re-signs at sequence+1 and broadcasts a SECOND proposal with identical content. The operator pays InitialDeposit (~10 SEI) twice and two duplicate proposals appear on chain. For software-upgrade the chain-level damage is bounded — the upgrade module applies once at the named height — but the pattern propagates to non-MVP proposal types (community-pool-spend = double-spend) which MUST NOT reuse this shape without first wiring the pre-broadcast txHash marker from #174. The risk is documented loudly: file-header warning, Handler() docstring, client-side GovSubmitProposalTask doc. Engine UUID dedupe protects same-UUID API resubmission but not the crash-mid-broadcast window. InitialDeposit is denom-whitelisted to usei (symmetric with Fees from #170) — catches a wrong-denom typo at the sidecar layer instead of waiting for the chain CheckTx rejection after sign + broadcast cost. Six follow-ups tracked in #176: nested wire-format shape for typed proposal params, shared client/server validation when a second proposal type lands, UpgradeHeight lower-bound vs current chain height (post-#165), title/description length caps (post-#165), run-value ctx plumbing for forensic correlation, process-start banner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rename gov-submit-proposal to gov-software-upgrade as a single-purpose task. Drop the Type discriminator and the buildProposalContent dispatch switch. Future proposal types (param-change, community-pool-spend, ...) will be separate task types in separate files. Load-bearing reasons: - Forced per-handler idempotency analysis. The meta-shape made adding a non-idempotent proposal type a one-line switch-case PR with no structural review signal. Per-type files force a fresh PR with a fresh REHYDRATION WARNING block — the diff shape itself is the audit signal. - Consistency with gov-vote. Both gov handlers now follow the same Handler/buildXxxMsg single-purpose shape. (Kube-rbac-proxy issues ONE coarse SAR — create seinodetasks.sei.io — independent of task body type, so per-handler files do NOT buy authz granularity at the proxy boundary today. The audit-trail argument is the real benefit.) Renames: - sidecar/tasks/gov_submit_proposal.go → gov_software_upgrade.go - GovSubmitProposalRequest → GovSoftwareUpgradeRequest (no Type field) - GovProposer → GovSoftwareUpgrader - buildSubmitProposalMsg + buildProposalContent → buildSoftwareUpgradeMsg - engine.TaskGovSubmitProposal → engine.TaskGovSoftwareUpgrade - client.GovSubmitProposalTask → client.GovSoftwareUpgradeTask (no Type field) Dropped ProposalTypeSoftwareUpgrade constant (no consumer). #176 items 1 and 2 (nested wire-format shape, shared client/server type dispatch validation) are moot after the refactor — closing those. Item 3+ (UpgradeHeight floor, length caps, run-ctx, banner) still binds future handlers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Software-upgrade proposal handler. Single-purpose task type, mirroring gov-vote's shape — no Type discriminator, no dispatch switch. Future proposal types will be separate task types in separate files.
(Previously shipped as a meta-handler with a Type field; refactored after user pushback on the one-way-door risk and the audit-trail weakness of an extensible switch for non-idempotent broadcasts.)
Summary
`MsgSubmitProposal` is NOT chain-idempotent. A crash between `BroadcastSync` and task-result persist re-runs this handler on restart and broadcasts a SECOND proposal with identical content. The operator pays `InitialDeposit` (~10 SEI) twice. For software-upgrade the chain-level execution damage is bounded (upgrade module applies once at the named height); the pattern MUST NOT be reused for non-MVP proposal types without first wiring the pre-broadcast txHash marker from #174.
Risk is documented at file-header scope. Each future proposal-type handler is a separate file deliberately — that file MUST evaluate its own rehydration impact before shipping. The diff shape itself is the audit signal: adding a new proposal type means a new file + a new test file + a new client task type + a new engine registration, not a one-line switch-case PR.
User-confirmed scope call (better than blocking the workstream on #174's hardening lift).
Why per-type handlers instead of one meta-handler
(Note: kube-rbac-proxy in #165 issues one coarse SAR — `create seinodetasks.sei.io` — independent of task body type, so per-handler files do NOT buy authz granularity at the proxy boundary today. The audit-trail argument is the real benefit.)
Follow-ups tracked in #176
Items 1 and 2 in #176 (nested wire-format shape, shared client/server validation) are moot after the refactor.
Test plan
🤖 Generated with Claude Code