Skip to content

feat(cardano): capture governance votes and full proposal state#1129

Merged
scarmuega merged 1 commit into
mainfrom
feat/governance-vote-capture
Jul 25, 2026
Merged

feat(cardano): capture governance votes and full proposal state#1129
scarmuega merged 1 commit into
mainfrom
feat/governance-vote-capture

Conversation

@scarmuega

@scarmuega scarmuega commented Jul 25, 2026

Copy link
Copy Markdown
Member

Plan

Executes phase 2 (Vote & proposal capture) of Brain/txpipe plans/dolos-governance-gaps.md — requirement 2 of the Cardano ledger governance-gaps initiative. Semantics source: the domain's haskell-governance-research.md; shapes per dolos-implementation-design.md §3.1, §4, §5.1 (phase 1 landed in #1128).

Done criterion: ProposalState extended with the full 7-variant governance action, lineage parent, proposed_in, and slot-stamped per-voter vote histories; VoteCast deltas emitted for all three voter classes (CC members, DReps, SPOs). Met.

What changed

  • crates/cardano/src/model/proposals.rs
    • ProposalAction: appended NoConfidence, UpdateCommittee, NewConstitution, Info variants (CBOR indexes 4..=7; Other kept at 3 for old rows). All 7 Conway actions now map to a concrete variant.
    • ProposalState: backward-compatible additions at frozen-index-safe positions 9..=15 — proposed_in, parent: Option<GovActionId>, purpose: Option<GovPurpose> (new 4-purpose enum), anchor, and cc_votes / drep_votes / spo_votes maps of voter → Vec<(BlockSlot, Vote)> (append-only history; last entry is the effective vote, enabling as-of reads at epoch boundaries). Old rows decode with the new fields empty (#[cbor(default)] / Option); no data migration.
    • VoteCast delta: appends (slot, vote) to the voter's history; undo pops exactly the appended entry (tracks entry creation for exact rollback).
    • NewProposalV2 delta: records action + parent + purpose + anchor + proposed_in. The legacy NewProposal stays for WAL replay only — its bincode field layout is frozen and can't grow (same discipline as fix(cardano): stop dropping StakeVoteDeleg drep leg and drep anchors #1128's DRepAnchorUpdate). Legacy replay now also populates proposed_in, derived from the current_epoch the old delta already carries.
  • crates/cardano/src/model/mod.rs: NewProposalV2 and VoteCast appended at the end of CardanoDelta (bincode variant order frozen, append-only).
  • crates/cardano/src/roll/proposals.rs: ProposalVisitor maps all 7 GovActions with lineage/anchor capture and now walks voting_procedures in visit_tx, emitting VoteCast for CC/DRep/SPO voters. Votes are buffered and emitted at block flush so a vote on a proposal submitted in the same block (legal in Conway, even same-tx) lands after that proposal's creation delta in the per-entity ordering. Phase-2-invalid txs are skipped (research §8). The existing DRepActivity bump in DRepStateVisitor is untouched.
  • src/bin/dolos/data/dump_state.rs: display arms for the new action variants.

Interpretation notes (for review)

  • Hack table retained on purpose. The design doc's delta table says the extended proposal delta "no longer consults hacks::proposals", but that describes the end-state: outcome stamping still feeds the boundary enact/drop classification, and the plan keeps the real RATIFY engine for phase 5 (shadow mode) and hack deletion for phase 6. Dropping the lookup here would break ratification tracking for three phases. NewProposalV2 documents this.
  • DRep expiry-refresh on vote and DormancyFold (design §5.1 voting bullet) are deferred to phase 3, which owns epoch-based DRep expiry + dormancy bookkeeping.
  • Delta shape: VoteCast carries the pallas Voter enum, which is the voter class and credential in one value (the design lists voter_class + voter separately; the enum encodes the same information without invalid combinations).

Verification

  • cargo +nightly fmt --all -- --check — clean
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo test --workspace --all-targets — all suites pass (19 suites, 0 failures; includes new prop tests: apply/undo and WAL serde roundtrips for NewProposalV2/VoteCast, vote append/replace unit test)
  • Backward-compat regression test: encodes a replica of the pre-phase-2 ProposalState shape (indexes 0..=8, 4-variant action) and decodes it as the new entity, asserting the new fields come back empty — the design §6 guarantee, now checked mechanically.

Finding (not addressed here)

Existing visitors don't gate on tx.is_valid(): an invalid tx's certs/proposals currently mutate entity state (e.g. DRepActivity bumps, NewProposal emission), contrary to research §8. The new vote path gates explicitly; fixing the pre-existing paths is left as a follow-up for the plan owner.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for expanded governance proposal actions, including committee updates, no-confidence motions, constitutional changes, and informational actions.
    • Added proposal lineage, purpose, metadata anchors, and voter-specific vote histories.
    • Added vote tracking with append-only histories and undo support.
    • Updated governance processing to record proposals and votes in the correct order.
  • Bug Fixes

    • Preserved compatibility with legacy proposal data while populating new fields.
    • Improved state output for newly supported proposal actions.

Extends ProposalState with backward-compatible additions (new fields at
CBOR indexes 9..=15, existing indexes frozen): full seven-variant
governance action, lineage parent + purpose, submission epoch, metadata
anchor, and slot-stamped append-only vote histories for the three voter
classes (CC hot credential, DRep credential, SPO key hash). Old rows
decode with the new fields empty; no data migration.

Emits a new VoteCast delta for every vote in a tx's voting procedures
(all three voter classes; phase-2-invalid txs excluded), buffered until
block flush so votes on same-block proposals apply after the proposal
exists. NewProposalV2 supersedes NewProposal at emission time, carrying
the lineage capture; the legacy variant stays for WAL replay since its
bincode field layout is frozen. Outcome stamping still consults the
hacks::proposals table until the real ratification engine replaces it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Governance proposal modeling now supports additional actions, lineage metadata, anchors, and voter histories. New proposal and vote deltas are wired through Cardano delta handling, emitted by the roll visitor with ordered vote flushing, tested for compatibility, and rendered in state dumps.

Changes

Governance phase-2 support

Layer / File(s) Summary
Proposal state and governance contracts
crates/cardano/src/model/proposals.rs, crates/cardano/src/model/testing.rs
Proposal actions, lineage fields, anchors, voter-specific vote histories, and supporting property-test strategies are added with CBOR compatibility behavior.
Proposal and vote delta handling
crates/cardano/src/model/proposals.rs, crates/cardano/src/model/mod.rs
NewProposalV2 and VoteCast apply and undo proposal state changes, while CardanoDelta routes, keys, and converts both variants; roundtrip and append/undo tests cover the new behavior.
Governance roll emission and vote ordering
crates/cardano/src/roll/proposals.rs, src/bin/dolos/data/dump_state.rs
Governance actions emit NewProposalV2, Conway votes are buffered and flushed after proposals, and state dumps recognize the additional action variants.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProposalVisitor
  participant GovAction
  participant CardanoDelta
  GovAction->>ProposalVisitor: Parse action and lineage
  ProposalVisitor->>CardanoDelta: Emit NewProposalV2
  ProposalVisitor->>ProposalVisitor: Buffer VoteCast
  ProposalVisitor->>CardanoDelta: Flush votes after proposals
Loading

Possibly related PRs

  • txpipe/dolos#675: Provides the visitor state and flush-hook structure used for buffered vote emission.
  • txpipe/dolos#1128: Extends CardanoDelta with additional variants and corresponding routing methods.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: Cardano governance vote capture and richer proposal state handling.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/governance-vote-capture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
crates/cardano/src/model/proposals.rs (1)

1020-1021: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Test name mentions "replaces" but nothing is replaced.

The assertions only cover append-only history and exact undo. Consider vote_cast_appends_and_undo_pops for accuracy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cardano/src/model/proposals.rs` around lines 1020 - 1021, Rename the
test function vote_cast_appends_and_replaces to vote_cast_appends_and_undo_pops
so its name accurately reflects the append-only behavior and undo assertions it
covers.
crates/cardano/src/roll/proposals.rs (1)

374-389: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a params struct for NewProposalV2::new.

Thirteen positional args at two call sites (with three bare Nones here) is hard to read and will get worse as phases land. A NewProposalV2 { .. } struct literal or small params struct would document each field at the call site.

Also applies to: 411-425

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/cardano/src/roll/proposals.rs` around lines 374 - 389, Refactor
NewProposalV2::new and both call sites to accept a named fields/params struct
instead of thirteen positional arguments. Update the proposal construction in
the shown ParamChange path and the additional call site near the other reported
location, explicitly naming the three lineage/anchor None values so each field’s
purpose is clear while preserving existing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/cardano/src/model/proposals.rs`:
- Around line 1020-1021: Rename the test function vote_cast_appends_and_replaces
to vote_cast_appends_and_undo_pops so its name accurately reflects the
append-only behavior and undo assertions it covers.

In `@crates/cardano/src/roll/proposals.rs`:
- Around line 374-389: Refactor NewProposalV2::new and both call sites to accept
a named fields/params struct instead of thirteen positional arguments. Update
the proposal construction in the shown ParamChange path and the additional call
site near the other reported location, explicitly naming the three
lineage/anchor None values so each field’s purpose is clear while preserving
existing behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f3c53d2d-d752-4f50-8deb-7dfa94f3d728

📥 Commits

Reviewing files that changed from the base of the PR and between 3b165c4 and 6f340bd.

📒 Files selected for processing (5)
  • crates/cardano/src/model/mod.rs
  • crates/cardano/src/model/proposals.rs
  • crates/cardano/src/model/testing.rs
  • crates/cardano/src/roll/proposals.rs
  • src/bin/dolos/data/dump_state.rs

@scarmuega
scarmuega merged commit 11ee34d into main Jul 25, 2026
14 checks passed
@scarmuega
scarmuega deleted the feat/governance-vote-capture branch July 25, 2026 15:06
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