Skip to content

feat(sidecar): gov-vote handler (PR-B of #163)#175

Merged
bdchatham merged 3 commits into
mainfrom
feat/gov-vote-handler
May 12, 2026
Merged

feat(sidecar): gov-vote handler (PR-B of #163)#175
bdchatham merged 3 commits into
mainfrom
feat/gov-vote-handler

Conversation

@bdchatham
Copy link
Copy Markdown
Contributor

@bdchatham bdchatham commented May 12, 2026

First user-facing gov sign-tx handler. Composes on the sign-tx foundation (#170) and hardening (#173).

Summary

Test plan

  • `TestParseVoteOption` table — case-insensitivity, kebab alias, empty/unknown rejection
  • `TestBuildVoteMsg` subtests — happy path (incl. `ValidateBasic` assertion), zero proposalID, invalid option, nil keyring, empty keyName, missing key — all Terminal
  • `TestSignedTxHasNoFeePayerOrGranter` — proto-level fee-payer/granter empty assertion
  • `go build ./...` clean
  • `go test ./... -count=1` clean (all packages)
  • `go test -race ./sidecar/tasks/` clean
  • `golangci-lint run --new-from-rev=origin/main ./...` reports 0 issues
  • Coral trio cross-review (platform / kubernetes / security) — synthesized findings applied; remaining deferred to Per-handler idempotency review + TaskResult.Output engine extension #174

🤖 Generated with Claude Code


Note

High Risk
Adds a new sign-and-broadcast task that can submit on-chain governance votes using the validator’s keyring, which is security-sensitive (especially given the sidecar API exposure). Also introduces new invariants around tx fee payer/granter enforced by tests, but mistakes could still result in unintended transactions/fees.

Overview
Adds a new gov-vote task end-to-end: registers engine.TaskGovVote in serve.go, defines the task type in engine/types.go, and implements sidecar/tasks/gov_vote.go to build a gov v1beta1 MsgVote and delegate signing/broadcasting to SignAndBroadcast.

Extends the Go client SDK (sidecar/client/tasks.go) with a typed GovVoteTask (validation + wire params) and shares vote-option parsing via exported tasks.ParseVoteOption.

Hardens tx signing tests by capturing broadcast tx bytes and asserting, at the protobuf level, that AuthInfo.Fee.Payer and AuthInfo.Fee.Granter are empty, preventing accidental fee delegation regressions.

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

First user-facing gov sign-tx handler. Composes on the sign-tx
foundation (#170) and hardening (#173). Closes #163 for the
gov-vote slice; gov-submit-proposal (#163-C) ships separately.

The handler builds a Cosmos gov v1beta1 MsgVote and delegates to
SignAndBroadcast. Idempotency is intentionally NOT handled in the
sidecar — the chain overwrites on duplicate (last-write-wins on the
(proposalID, voter) storage key), so the caller's request is
authoritative. Engine UUID dedupe covers the task-submission case.

Crash-rehydration semantics are documented inline: safe for MsgVote
specifically because of chain idempotency. Future non-idempotent
sign-tx handlers (MsgSend, MsgWithdraw, etc.) need a pre-broadcast
SQLite marker — tracked in #174.

Also closes #172 item 3 (deferred from #173): the new
TestSignedTxHasNoFeePayerOrGranter decodes the signed proto Tx and
asserts AuthInfo.Fee.Payer/Granter are empty strings — directly
locking the no-fee-grant invariant rather than relying on the
FeePayer() interface method (which falls back to signer when the
field is unset, masking explicit WithFeePayer(signer) plumbing).

Shared ParseVoteOption is exported so client and server validation
stay in lockstep — drift between the two enum lists is otherwise
inevitable.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bdchatham and others added 2 commits May 12, 2026 14:14
Trio cross-review focused on comment bloat. Synthesis: drop paraphrase,
keep WHY. Net 23 lines removed.

Dropped:
- GovVoteRequest "is the wire-format param shape" + "caller's request
  is authoritative" — both said by the type name and the kept WHY line.
- buildVoteMsg docstring — function name + tests file already say it.
- "Tracked in #174" forward-pointer — the issue back-pointers this PR;
  forward refs from source rot faster.
- GovVoteTask client-side idempotency note — duplicates the server-side
  GovVoteRequest comment; one canonical location.
- "(closes #172 item 3)" parenthetical on the fee-payer test — belongs
  in commit/PR, not source.

Tightened:
- GovVoter docstring — from 4 lines to 2; kept the value-copy WHY.
- Handler() rehydration block — collapsed redundancies, kept the
  "future Msgs must evaluate per-Msg idempotency" template warning
  (load-bearing for the next sign-tx contributor).
- ParseVoteOption — kept the client/server drift WHY; dropped the
  mapping description (the body is the mapping).

Kept (load-bearing WHY):
- SECURITY POSTURE NOTE banner — established pattern in sign_and_broadcast.go
  and trust-boundary risk during the pre-authn phase.
- Stale-proposal no-pre-check + TOCTOU reasoning.
- Test docstring + inline FeeTx.FeePayer() fallback warning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fakeTxClient field block had extra whitespace from the lastTxBytes
addition; gofmt -s realigns it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bdchatham bdchatham merged commit 54bbbcf into main May 12, 2026
3 checks passed
@bdchatham bdchatham deleted the feat/gov-vote-handler branch May 12, 2026 21:22
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b1061c3. Configure here.

Comment thread sidecar/client/tasks.go
p["memo"] = t.Memo
}
return TaskRequest{Type: t.TaskType(), Params: &p}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missing SubmitGovVoteTask client convenience method

Low Severity

Every other task type in the client SDK (SnapshotRestoreTask, AwaitConditionTask, ConfigApplyTask, etc.) has a corresponding Submit*Task convenience method on SidecarClient in client.go that calls Validate() before delegating to SubmitTask. The new GovVoteTask defines Validate(), TaskType(), and ToTaskRequest() but has no matching SubmitGovVoteTask method, breaking the established SDK pattern. Callers using the generic SubmitTask path can accidentally skip client-side validation.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1061c3. Configure here.

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.

sign-tx hardening: typed RPCError discriminator, ctx-plumbing audit, fee_payer/granter assertion

1 participant