feat(governance): add counted flag to proposal votes - #352
Conversation
The votes endpoint returns every vote-cast transaction ever submitted for a proposal, but the ledger keeps only the latest vote per voter (upsert semantics) and drops a DRep's votes on deregistration, so the response can diverge significantly from the current ledger vote state (queryLedgerState/governanceProposals in Ogmios). Add a boolean "counted" to each vote entry: true only for the voter's latest vote on the proposal, and for DRep voters only when no deregistration follows the vote — re-registering does not restore previously dropped votes, so the check is "no dereg after the vote's tx", not "currently registered". Recency is a window rank per voter within the proposal's votes; the dereg check is an index-only scan on bf_idx_drep_registration_hash_deposit. Validated on mainnet db-sync: on the busiest proposal (453 votes) the flag marks 99 votes uncounted (36 superseded re-votes + 63 latest votes of since-deregistered DReps), no voter retains more than one counted vote, paged/unpaged outputs and ordering are unchanged, and a warm page returns in ~50ms. Requires @blockfrost/openapi with the counted field in proposal_votes (blockfrost/openapi#466); until the dependency is bumped the field is stripped by response serialization. Fixes blockfrost/openapi#466
…e set A DRep deregistration only drops votes from proposals that are still live; once a proposal is ratified/enacted/dropped/expired its tally is frozen, so a later deregistration must not retroactively mark the vote uncounted. Bound the deregistration check by the epoch in which the proposal left the active set (LEAST of the gov_action_proposal epoch columns; NULL = still live, tip semantics unchanged). On mainnet this matters a lot for closed proposals: gap 12 had 65 of 453 votes wrongly flagged uncounted by post-closure deregistrations (now 39 uncounted: 36 superseded + 3 pre-closure deregistrations).
|
Perf comparison vs master on mainnet dev db-sync (9 warm runs each, interleaved, median end-to-end ms from client):
Server-side (EXPLAIN ANALYZE, best of 5) on the busiest proposal: 7.6 ms -> 12.1 ms paged, 9.3 ms -> 11.6 ms unpaged, so the real added cost is a few ms, invisible behind connection overhead. The counted subplan is an index-only scan on bf_idx_drep_registration_hash_deposit (~0.02 ms per returned DRep row). Scaling check: the window function runs over all of a proposal's votes before pagination, so cost scales with proposal size. Running the same shape over the entire voting_procedure table at once (36k votes, ~50x today's biggest proposal) executes in 95 ms, so there is ample headroom. |
Brings the counted field and cc_hot voter schema for
/governance/proposals/{tx_hash}/{cert_index}/votes so response
serialization no longer strips the new field.
|
Cross-checked the counted logic against Koios (koios-artifacts grest.proposal_votes, which silently filters instead of flagging). Three differences, all in our favor, quantified on mainnet dev db-sync across all 36k votes:
Also confirmed both implementations ignore voting_procedure.invalid (0 rows on mainnet) and neither models CC resignation or SPO retirement (Koios has a TODO for it; our schema description scopes it out). CIP-129 cc_hot encoding already verified byte-for-byte against grest.cip129_hex_to_cc_hot output earlier in #351. |
feat(governance): add counted flag to proposal votes (re-land #352)
Summary
Adds a boolean
countedto each entry of/governance/proposals/{tx_hash}/{cert_index}/votesindicating whether the vote is still counted in the ledger's current vote tally (blockfrost/openapi#466). The endpoint returns every vote ever cast, but the ledger keeps only the latest vote per voter and drops a DRep's votes on deregistration, so results could diverge significantly from Ogmios'queryLedgerState/governanceProposals.Semantics
counted = trueiff:tx_id), andLEASTof thegov_action_proposalepoch columns)For live proposals this reconstructs the votes map at the current chain tip; for closed proposals it reflects the state when voting closed. The dereg check is anchored on the existing
bf_idx_drep_registration_hash_depositindex, evaluated only for returned rows.Validation (mainnet db-sync)
9b62b3c6...#0, 453 votes): 39 flagged uncounted — 36 superseded re-votes + 3 DReps deregistered while the proposal was live; the epoch bound prevents 65 votes from being wrongly dropped by post-closure deregistrations7d37220b...#0, 109 votes): tip semantics, 2 uncountedNotes
Fixes blockfrost/openapi#466