Skip to content

fix(cardano): Handle pool re-registrations#822

Merged
scarmuega merged 3 commits intomainfrom
fix/handle-pool-reregistrations
Jan 5, 2026
Merged

fix(cardano): Handle pool re-registrations#822
scarmuega merged 3 commits intomainfrom
fix/handle-pool-reregistrations

Conversation

@gonzalezzfelipe
Copy link
Copy Markdown
Contributor

@gonzalezzfelipe gonzalezzfelipe commented Dec 22, 2025

Summary by CodeRabbit

  • New Features
    • Automatic tracking and queuing of pool-delegator retirement events so retiring delegations are processed at epoch boundaries.
    • Account state now preserves a retired-pool value for fallback display and logic when no active delegation exists.
    • Account view/build logic falls back to the preserved retired pool when determining a delegator's pool.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 22, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds delegator-retirement handling: a new public PoolDelegatorRetire delta, an AccountState.retired_pool field, wiring for the delta in Cardano delta dispatch and boundary visitation, delegation apply/undo tracking, genesis init, and a minibf model fallback to retired_pool. (50 words)

Changes

Cohort / File(s) Summary
New delta & boundary wiring
crates/cardano/src/ewrap/drops.rs
Adds PoolDelegatorRetire (delegator, epoch, prev_pool), implements dolos_core::EntityDelta for AccountState, and enqueues this delta from BoundaryVisitor::visit_account when a delegator points to a retiring pool.
Model & delta dispatch
crates/cardano/src/model.rs
Adds pub retired_pool: Option<PoolHash> (CBOR n(7)) to AccountState, initializes it to None, adds CardanoDelta::PoolDelegatorRetire(...), registers conversion, and routes key/apply/undo to the new delta variant.
Genesis initialization
crates/cardano/src/genesis/staking.rs
Initialize retired_pool: None in AccountState created by parse_delegation.
Delegation apply/undo tracking
crates/cardano/src/roll/accounts.rs
Adds prev_retired_pool: Option<PoolHash> to StakeDelegation; apply() saves current retired_pool to prev_retired_pool and clears retired_pool; undo() restores it.
Model builder fallback
crates/minibf/src/routes/accounts.rs
AccountModelBuilder::into_model now falls back to account_state.retired_pool when computing pool_id if there is no active delegation for the current epoch.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor BoundaryVisitor
  participant DeltaQueue
  participant DeltaEngine
  participant AccountState
  participant ModelBuilder

  BoundaryVisitor->>DeltaQueue: detect account delegates to retiring pool\nqueue PoolDelegatorRetire(delegator, epoch)
  DeltaQueue->>DeltaEngine: dispatch PoolDelegatorRetire
  DeltaEngine->>AccountState: apply delta\nset delegation -> NotDelegated\nset retired_pool = prev_pool
  Note right of AccountState: retired_pool records previous pool
  ModelBuilder->>AccountState: read delegated_pool_at(current_epoch)
  alt no active delegation
    ModelBuilder->>AccountState: fallback to retired_pool for display
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

"I hopped through code with whiskers keen,
Queued a delta neat and tidy, unseen.
Delegator rests, its pool remembered fine,
retired_pool tucked in a cosy line.
🐰✨"

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'fix(cardano): Handle pool re-registrations' is vague and does not accurately reflect the main changes in the pull request. The PR introduces pool delegator retirement tracking with a new PoolDelegatorRetire delta and retired_pool field, but the title suggests handling pool re-registrations rather than delegator retirements. Revise the title to be more specific about the core change, such as 'fix(cardano): Track retiring pool delegators' or 'feat(cardano): Add pool delegator retirement tracking' to better reflect that this implements retirement state tracking.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9b758c and 136e901.

📒 Files selected for processing (1)
  • crates/cardano/src/ewrap/drops.rs

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
crates/cardano/src/ewrap/drops.rs (1)

88-91: Potential panic if invariant is violated.

The unreachable! macro will panic if prev_pool is not Some(PoolDelegation::Pool(_)). While this should be guaranteed by the check in visit_account (line 161-162), defensive coding might be preferable to avoid panics in edge cases.

Consider using a warning log instead:

🔎 Proposed defensive handling
-        let Some(PoolDelegation::Pool(pool)) = self.prev_pool else {
-            unreachable!("account delegated to pool")
-        };
-        entity.retired_pool = Some(pool);
+        if let Some(PoolDelegation::Pool(pool)) = self.prev_pool {
+            entity.retired_pool = Some(pool);
+        } else {
+            warn!(delegator=%self.delegator, "expected pool delegation but found {:?}", self.prev_pool);
+        }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a342df and 1923263.

📒 Files selected for processing (5)
  • crates/cardano/src/ewrap/drops.rs
  • crates/cardano/src/genesis/staking.rs
  • crates/cardano/src/model.rs
  • crates/cardano/src/roll/accounts.rs
  • crates/minibf/src/routes/accounts.rs
🧰 Additional context used
🧬 Code graph analysis (2)
crates/cardano/src/roll/accounts.rs (1)
crates/cardano/src/model.rs (1)
  • undo (1974-2009)
crates/cardano/src/ewrap/drops.rs (1)
crates/cardano/src/roll/accounts.rs (24)
  • new (28-34)
  • new (119-129)
  • new (177-185)
  • new (233-247)
  • new (293-304)
  • new (381-388)
  • apply (53-64)
  • apply (86-96)
  • apply (140-155)
  • apply (196-210)
  • apply (258-268)
  • apply (315-337)
  • apply (359-363)
  • apply (399-412)
  • apply (437-448)
  • undo (66-68)
  • undo (98-102)
  • undo (157-162)
  • undo (212-217)
  • undo (270-275)
  • undo (339-342)
  • undo (365-369)
  • undo (414-419)
  • undo (450-452)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Build
🔇 Additional comments (7)
crates/minibf/src/routes/accounts.rs (1)

96-101: LGTM! Appropriate fallback for retired pool delegation.

The fallback to retired_pool when delegated_pool_at(current_epoch) returns None correctly preserves pool information for accounts whose delegated pool has retired. This ensures API consumers can still identify the pool the account was delegated to.

crates/cardano/src/roll/accounts.rs (1)

165-218: LGTM! Proper undo/redo semantics for retired pool state.

The StakeDelegation delta correctly:

  1. Saves retired_pool before applying (line 203)
  2. Clears retired_pool when applying a new delegation (line 209) - this is correct because a new delegation supersedes the retired state
  3. Restores retired_pool on undo (line 216)

This ensures that chain rollbacks properly restore the retired pool tracking state.

crates/cardano/src/genesis/staking.rs (1)

101-111: LGTM! Correct initialization for genesis accounts.

Initializing retired_pool: None for genesis delegations is correct since these accounts are bootstrapped with active pools, not retired ones.

crates/cardano/src/ewrap/drops.rs (1)

161-165: LGTM! Boundary processing correctly detects retiring pools.

The logic properly checks if the account's delegated pool is in the retiring_pools set and emits a PoolDelegatorRetire delta accordingly.

crates/cardano/src/model.rs (3)

536-539: LGTM! Appropriate field addition with correct CBOR tagging.

The retired_pool field is properly tagged with #[n(7)] following the existing sequential CBOR encoding pattern, and the Option<PoolHash> type correctly represents the optional retired pool state.


543-555: LGTM! Correct initialization in constructor.

The AccountState::new properly initializes retired_pool: None, consistent with the genesis initialization in staking.rs.


1823-1823: LGTM! Complete delta integration.

The PoolDelegatorRetire delta is fully wired into the CardanoDelta enum with:

  • Enum variant (line 1823)
  • delta_from! macro (line 1891)
  • key() dispatch (line 1928)
  • apply() dispatch (line 1966)
  • undo() dispatch (line 2003)

Also applies to: 1891-1891, 1928-1928, 1966-1966, 2003-2003

Comment on lines +94 to +97
fn undo(&self, _entity: &mut Option<AccountState>) {
// todo!()
// Placeholder undo logic. Ensure this does not panic.
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Placeholder undo logic may cause issues during chain rollbacks.

The undo method is not implemented, which could lead to incorrect state after rollbacks. Unlike some other deltas where undo is less critical, PoolDelegatorRetire::apply modifies both entity.pool (via schedule) and entity.retired_pool. If a rollback occurs, these changes won't be reversed.

🔎 Proposed undo implementation
     fn undo(&self, _entity: &mut Option<AccountState>) {
-        // todo!()
-        // Placeholder undo logic. Ensure this does not panic.
+        let Some(entity) = _entity.as_mut() else {
+            warn!(delegator=%self.delegator, "missing account during undo");
+            return;
+        };
+
+        // Restore previous pool delegation
+        entity.pool.reset(self.prev_pool.clone());
+        // Clear the retired_pool since we're undoing the retirement
+        entity.retired_pool = None;
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In crates/cardano/src/ewrap/drops.rs around lines 94 to 97, implement undo to
reverse the changes made by PoolDelegatorRetire::apply: remove the scheduled
retirement entry from entity.pool.schedule that matches this delta (epoch and
pool id) and restore entity.retired_pool to its previous state (clear it or set
it back if it equals the pool id and epoch scheduled by this delta). Ensure you
safely handle None entity, only mutate when the pool and retired_pool match the
values set by apply, and avoid panics.

pub credential: StakeCredential,

#[n(7)]
pub retired_pool: Option<PoolHash>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

let's put a cbor default attribute here so that previous snapshots still work regardless of the existence of the new value.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

resolved at d9b758c

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1923263 and d9b758c.

📒 Files selected for processing (1)
  • crates/cardano/src/model.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Check Build
🔇 Additional comments (7)
crates/cardano/src/model.rs (7)

30-30: LGTM!

The import of PoolDelegatorRetire follows the established pattern and is consistent with other delta types from the ewrap::drops module.


537-539: LGTM!

The retired_pool field is correctly added with:

  • Sequential CBOR tag n(7) following the existing field order
  • #[cbor(default)] attribute ensuring backwards compatibility with existing snapshots (addressing the past review comment)
  • Appropriate type Option<PoolHash> for optional retirement tracking

554-554: LGTM!

The retired_pool field is correctly initialized to None in the constructor, following the established pattern for optional fields.


1824-1824: LGTM!

The PoolDelegatorRetire variant is correctly added to the CardanoDelta enum, following the established pattern for delta types.


1892-1892: LGTM!

The delta_from! macro invocation correctly implements the From trait for PoolDelegatorRetire, enabling conversion to CardanoDelta.


1929-1929: LGTM!

The key() match arm for PoolDelegatorRetire correctly delegates to the inner delta's key() method, following the established pattern.


1967-1967: LGTM!

The apply() match arm for PoolDelegatorRetire correctly uses the downcast_apply helper, following the established pattern for delta application.

Self::EpochTransition(x) => Self::downcast_undo(x, entity),
Self::EpochWrapUp(x) => Self::downcast_undo(x, entity),
Self::DRepDelegatorDrop(x) => Self::downcast_undo(x, entity),
Self::PoolDelegatorRetire(x) => Self::downcast_undo(x, entity),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, find where PoolDelegatorRetire is defined
fd -t f -n "*.rs" | head -20
echo "---"
# Search for PoolDelegatorRetire definition
rg "struct PoolDelegatorRetire" --type rust -A 5
echo "---"
# Search for EntityDelta implementation for PoolDelegatorRetire
rg "impl.*EntityDelta.*PoolDelegatorRetire" --type rust -A 10

Repository: txpipe/dolos

Length of output: 1407


🏁 Script executed:

#!/bin/bash
# Check the context around line 2004 in model.rs
cat -n crates/cardano/src/model.rs | sed -n '1995,2015p'

Repository: txpipe/dolos

Length of output: 1204


🏁 Script executed:

#!/bin/bash
# Find the exact file structure for drops.rs
fd "drops.rs" --type f

Repository: txpipe/dolos

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Get the full PoolDelegatorRetire implementation in drops.rs
rg "impl.*EntityDelta.*PoolDelegatorRetire" -A 50 --type rust

Repository: txpipe/dolos

Length of output: 3080


Implement the missing undo() method for PoolDelegatorRetire.

The undo() method in crates/cardano/src/ewrap/drops.rs is a placeholder (line contains // todo!()) and does not reverse the state changes made by apply(). The struct correctly captures undo information in prev_pool, but the undo() method is empty and will not restore the previous pool delegation state, breaking rollback functionality for this delta.

@scarmuega scarmuega merged commit 619115e into main Jan 5, 2026
6 of 8 checks passed
@scarmuega scarmuega deleted the fix/handle-pool-reregistrations branch January 5, 2026 20:28
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.

2 participants