fix(cardano): move the pots when a treasury withdrawal enacts - #1227
Conversation
An enacted TreasuryWithdrawal credited the recipient account and left both pots untouched, so every Conway boundary that enacted one drifted treasury high and rewards low by the enacted total — 649,764,674 ADA by mainnet epoch 645, the exact misclassification the governance oracle measured against db-sync. Mirror the treasury-MIR pair end to end: the finalize pass records which withdrawal targets are registered at the boundary (the ledger's applyEnactedWithdrawals restricts the enacted map to the rewards UMap and discards the rest, so an undeliverable withdrawal now moves nothing and no longer panics on a missing account row), the enactment visitor accumulates the effective total on the boundary work, EndStats carries it next to effective_treasury_mirs, and apply_shelley_delta debits treasury and credits rewards by it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review. 📝 WalkthroughWalkthroughChangesTreasury withdrawal accounting
Estimated code review effort: 3 (Moderate) | ~25 minutes Merge Risk: ⚪ Minimal · up to The change corrects treasury and rewards pot accounting for enacted withdrawals, with targeted tests and preprod/mainnet verification reported as passing. No actionable merge-blocking risk remains beyond normal checks and review. Sequence Diagram(s)sequenceDiagram
participant BoundaryLoading
participant BoundaryWork
participant Enactment
participant EndStats
participant PotDelta
BoundaryLoading->>BoundaryWork: load registered withdrawal targets
Enactment->>BoundaryWork: classify effective and invalid withdrawals
BoundaryWork->>EndStats: record withdrawal totals
EndStats->>PotDelta: provide effective withdrawals
PotDelta->>PotDelta: transfer treasury value to rewards
Possibly related PRs
Suggested labels: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Status update (plan trail):
|
Plan
plans/dolos-governance-treasury-withdrawal-pots.md— divergence B of the mainnet governance oracle replay (the plan was renamed when divergence A was carved intoplans/dolos-governance-drep-snapshot-timing.md): dolos held intreasurywhat db-sync held inrewards, off by exactly 649,764,674,000,000 lovelace — the sum of everyTreasuryWithdrawalenacted at or before epoch 645.Code read: confirmed
The plan's reading holds on
main:ewrap/enactment.rs— enacting aTreasuryWithdrawalemitted one per-account delta per(credential, amount)and nothing else.model/accounts.rs— that delta'sapplydoes exactly one thing: creditstake.rewards_sum. Per-account value lands, which is why the 17 mainnet recipients reconcile individually.pots.rs—PotDeltahad no field for enacted withdrawals, soapply_shelley_deltanever debitedpots.treasurynor creditedpots.rewards. Later transaction withdrawals subtract frompots.rewards, so the miss shows as rewards short / treasury long by exactly the enacted total.Two additions to the read, found while implementing:
applyEnactedWithdrawalsdomain-restricts the enacted withdrawal map to credentials present in the rewards UMap — i.e. registered at the boundary — and discards the rest: no account credit and no pot movement. Dolos previously credited any existing account row unconditionally (registered or not) and panicked (expect("existing account")) on a credential with no account row at all. Both are fixed by the same gate.scheduled_or_default), while the withdrawal credit lands on the live position — noted here because the harness test pins both.What changed
Mirrors the
treasury_mirspair in the same four places:ewrap/loading.rs— the finalize pass records which withdrawal targets are registered at the boundary (deliverable_withdrawal_targets), read at proposal-classification time like the MIR registration check.ewrap/enactment.rs—enactment_deltasonly emits per-account credits for deliverable targets; the visitor accumulateseffective_treasury_withdrawals/invalid_treasury_withdrawalson the boundary work, warning on each discard.model/epochs.rs—EndStatscarriestreasury_withdrawals(#[n(15)]) andinvalid_treasury_withdrawals(#[n(16)]), both#[cbor(default)]so pre-fix rows decode.pots.rs+estart/reset.rs—PotDelta.treasury_withdrawals(#[n(25)],#[cbor(default)]), wired fromEndStats;apply_shelley_deltasubtracts it frompots.treasuryand adds it topots.rewards. Byron is unaffected (apply_byron_deltazeroes both pots).Tests
pots::tests::treasury_withdrawal_moves_pots_across— unit: the pair moves, supply conserved (uses the mainnet delta figure).ewrap::loading::ratification_tests::enacted_withdrawal_moves_pots_across_the_boundary— harness (ToyDomain, real EWRAP finalize + ESTART shard/finalize commit path): an enacted withdrawal debitspots.treasury, creditspots.rewards, the account is credited, andPots::is_consistent()holds across the transition — done criterion 1.ewrap::enactment::tests::undeliverable_withdrawal_emits_no_account_delta— the discard case.Verification run:
cargo test --all-features(all suites green, 253 indolos-cardano),cargo clippy --all-targets --all-features -- -D warningsclean,cargo +nightly fmt --all -- --checkclean.Consumer-visible surface change
Every Conway-era boundary's
EpochStatetreasury and rewards figures change value on a rebuilt/resynced store —minibf, theepochslog, and Stelae snapshots published pre-fix will disagree with post-fix ones. Flagging here for the v1.7 release notes and the breaking-change sweep.Plan criteria
All met. Divergence A (DRep voting powers, 14 credentials +
Abstainbucket) was carved intoplans/dolos-governance-drep-snapshot-timing.mdby owner ruling; this PR ships B alone per the plan's scope decision, and the mainnet re-read below is that plan's baseline.🤖 Generated with Claude Code
Preprod verification (plan criterion 3) — done
dolos doctor rebuild-state --rewrite-logs --forcein place on the governance preprod instance (17G archive, epoch 307), with this PR's binary (824240e): 3066 s ≈ 51 min wall-clock, exit 0 — in line with the ~1 h datum.dolos data checkon the resulting store: cursors / archive-continuity / account-epochs / epoch-log / totals — 0 issues, "no consistency issues found".EpochStateis content-identical to a pre-fix rebuild of the same archive (diff clean up toHashSetiteration order). Mainnet is the only network where the pot movement is observable on-chain; the harness test carries the mechanism proof.Divergence A — mechanism findings (tracked on the plan, deliberately not fixed here)
Three code-anchored mechanisms, all one class: dolos's distribution accumulation reads live stake in the EWRAP shard passes, before the finalize-pass account mutations, while the ledger's pulser snapshots after all of them (Conway
Epoch.hsordering:applyEnactedWithdrawals→returnProposalDeposits→ pool reap refunds →setFreshDRepPulsingState):Abstain-bucket shortfall class.PoolDepositRefundlands on the scheduled stake position, so a same-boundary pool-deposit refund misses the snapshot the ledger includes — matches the one dolos-low offset of exactly −500,000,000.is_active(B+1)(was_enactedrequirescurrent > ratified + 1), so its deposit stays in the snapshot deposit-set for one extra boundary while its refund already sits in live rewards — a one-epoch double count matching the +100,000,000,000 dolos-high offset exactly.These have their own blast radius (every boundary's DRep voting powers) and are proposed as a carve into their own plan.
Mainnet verification (plan criteria 3–4) — done
The retained
mainnet-govinstance (full genesis replay on shippedv1.7.0-alpha.0,81f7ba7d) had its state re-derived from its own archive with this PR's binary —dolos doctor rebuild-state --target … --stop-epoch 646 --force, no network, no snapshot re-import, no archive rewrite. 26,206 s ≈ 7.28 h, against the ~23.5 h the original genesis replay cost. Cursor landed on the archive tip, slot 193,535,999. The instance's own stores were not written; the read below is from the rebuilt store.Rows 18 and 19 — exact
Both move by exactly 649,764,674,000,000 — the sum of every
TreasuryWithdrawalenacted at or before epoch 645 — in opposite directions, supply conserved to the lovelace.Regression — the fix moves two pots and nothing else
The rebuilt store was diffed against the pre-fix instance store, dump for dump, at the same tip:
epochspot treasuryandpot rewardsdiffer; reserves, utxos, deposits, fees, gathered fees, pparams count, protocol version and epoch nonce are byte-identicalproposalsdrepsgov(committee, constitution, DRep distribution)Rows 7 and 9 — baseline for the carved plan
Unchanged by this fix, as expected:
Abstain9,412,108,368,313,004 andNoConfidence176,451,764,046,729 in the rebuilt store, identical to pre-fix. The oracle's divergences against Koios (14 of 893 DRep powers;Abstainshort by 5,996,004,893,013) therefore carry over untouched as the baseline forplans/dolos-governance-drep-snapshot-timing.md.CI note
cargo deny (advisories, bans)fails on this branch and equally onmain— the advisories check flags RUSTSEC-2026-0258 (h2, unbounded empty DATA frames), which reaches the tree transitively as bothh2 0.3.26andh2 0.4.9. This PR touches no dependencies, so the failure is pre-existing and unrelated; clearing it is a dependency-bump of its own. Every other check is green.(Corrected: an earlier revision of this note attributed the failure to a toolchain-install error, which is a non-fatal line earlier in the same job log.)
Summary by CodeRabbit
New Features
Bug Fixes