Skip to content

fix(sidecar): confirm a GovVote from the gov module when the tx index is off - #604

Merged
bdchatham merged 2 commits into
mainfrom
brandon2/plt-1401-govvote-confirm-by-state
Oct 7, 2026
Merged

bdchatham merged 2 commits into
mainfrom
brandon2/plt-1401-govvote-confirm-by-state

Conversation

@bdchatham

Copy link
Copy Markdown
Collaborator

Summary

GovVote targets validators, and most validators run with the tx index off. The sidecar then cannot look up the vote tx, and the task ends Failed with inclusion unverifiable, even when the vote landed. #603 fixed the same case for Unjail; this PR applies the pattern to GovVote (PLT-1401).

Change

  • When the broadcast result is Unverifiable, the handler polls the gov module's vote query (proposal ID plus voter) for up to 30 seconds, once a second, under a context deadline.
  • A recorded vote whose one full-weight choice is the requested option completes the task. The result keeps the tx hash and inclusionStatus: unverifiable.
  • A missing vote, or a vote for a different option, keeps today's terminal error.
  • The vote read runs off-goroutine, as chainJailState does, because the SDK query path does not honor the context. Vote holds no Any, so the fix(sidecar): unpack the validator consensus key before the unjail jail-state read #602 unpack issue cannot occur.
  • The wire docs, classifyGovResult, and the GovVote kind comment now name the second state-confirmed task.
  • Proposal-submission kinds are unchanged: their proposal ID comes from the tx result.

Verification

  • Three new handler tests: confirmed by the vote query (Complete), a different option (terminal unverifiable), and a committed tx (no vote query). The first fails without the change.
  • The sidecar module passes gofmt, go vet, golangci-lint --new-from-merge-base, and go test ./.... make manifests generate leaves no diff.
  • Next: a harbor e2e after merge, voting on a proposal across validators without a tx index.

Refs: PLT-1401

🤖 Generated with Claude Code

… is off

GovVote targets validators, and most run with the tx index off. The
sidecar could not look up the vote tx, so the task ended Failed with
'inclusion unverifiable' even when the vote landed. #603 fixed the same
case for Unjail.

When the tx is unverifiable, the handler now polls the gov vote query
(proposal, voter) for up to 30 seconds. A recorded vote with the requested
option as its one full-weight choice completes the task; the result keeps
the tx hash and the unverifiable inclusion status. A different or missing
vote keeps the terminal error. The wire docs and the GovVote kind comment
name the new exception.

Refs: PLT-1401

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bdchatham

Copy link
Copy Markdown
Collaborator Author

@seidroid review

@cursor

cursor Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes when GovVote tasks succeed on validators without tx indexing; mitigated by option matching, sync checks, and bounded polling similar to Unjail.

Overview
GovVote no longer fails with terminal inclusion unverifiable when the vote likely succeeded but the validator cannot look up the tx (tx index off). After broadcast, if inclusion is unverifiable, the handler polls the gov module vote query (proposal + voter) for up to 30s; when on-chain state records the requested option with full weight, the task completes while still returning inclusionStatus: unverifiable and the tx hash.

Vote reads use the same off-goroutine + context cancel pattern as Unjail, refuse reads while the node is catching up, and expose test seams on GovVoter. Docs in wire, classifyGovResult, and the GovVote CRD kind comment now list GovVote alongside Unjail as tasks that can succeed via state confirmation. New tests cover state-confirmed success, mismatched option, committed tx (no query), and catching-up behavior.

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

@bdchatham

Copy link
Copy Markdown
Collaborator Author

@seidroid review

@seidroid seidroid Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

When the node's tx index is off and the broadcast comes back Unverifiable, GovVote now polls the gov module's vote query for up to 30s and completes the task once the recorded choice is the requested full-weight option, following the pattern #603 used for Unjail. Nothing blocks: the confirmation logic, the deadline handling and the new tests are sound, and one suggestion remains about not guarding against a catching-up node; tests were not run because the sandbox has no Go toolchain.

Non-blocking

1 finding on the changed lines, as inline comments.

  • Reconciling the other readings: I dropped codex's medium finding at gov_vote.go:190 about chainVote leaking a goroutine and RPC past the deadline. The abandoned goroutine writes to a buffered channel, so it never blocks. Its RPC runs over the rpchttp.NewWithTimeout(..., tmRPCTimeout) client (transactions.go:37), so it ends within 30s instead of piling up, the same bounded behavior as the existing chainJailState this code copies.

seidroid review · decision approve · session e41e4e2f5f33432793674451d12470bf · turn resp_claude_b9b039f39e8ac0f0fe0a8df147613359 · item 7197519a76f65a9fae65186e11feb4fd

Findings: 0 blocking | 2 non-blocking | 1 posted inline

Comment thread sidecar/tasks/gov_vote.go
Review on #604: a lagging node can still show an older vote with the
requested option after a newer vote replaced it. readVoteState checks
/status first and returns an error while the node catches up, so the
confirmation keeps polling, as the Unjail jail-state read does.

Refs: PLT-1401

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@bdchatham

Copy link
Copy Markdown
Collaborator Author

@seidroid review

@seidroid seidroid Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since the last review, chainVote reads through a new readVoteState. That function checks /status first and returns an error while the node is catching up, so votedAfter keeps polling instead of trusting a stale vote; TestReadVoteState tests both the catching-up and caught-up cases. That fixes my catching-up finding, nothing else blocks, and I again dropped codex's repeated goroutine-leak claim at gov_vote.go:191: the abandoned goroutine writes to a buffered channel and its RPC runs over the client built with the 30s tmRPCTimeout, so it exits on its own and leaves nothing behind, as in the existing chainJailState.

seidroid review · decision approve · session e41e4e2f5f33432793674451d12470bf · turn resp_claude_3ecd340b69c7ebe0d23ece986cc487cc · item c8af85538227597fabcbaf2e820b74dd

Findings: 0 blocking | 0 non-blocking | 0 posted inline

@bdchatham
bdchatham merged commit 14b1747 into main Oct 7, 2026
15 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