fix(cardano): Handle pool re-registrations#822
Conversation
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds delegator-retirement handling: a new public Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
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 |
There was a problem hiding this comment.
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 ifprev_poolis notSome(PoolDelegation::Pool(_)). While this should be guaranteed by the check invisit_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
📒 Files selected for processing (5)
crates/cardano/src/ewrap/drops.rscrates/cardano/src/genesis/staking.rscrates/cardano/src/model.rscrates/cardano/src/roll/accounts.rscrates/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_poolwhendelegated_pool_at(current_epoch)returnsNonecorrectly 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
StakeDelegationdelta correctly:
- Saves
retired_poolbefore applying (line 203)- Clears
retired_poolwhen applying a new delegation (line 209) - this is correct because a new delegation supersedes the retired state- Restores
retired_poolon 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: Nonefor 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_poolsset and emits aPoolDelegatorRetiredelta accordingly.crates/cardano/src/model.rs (3)
536-539: LGTM! Appropriate field addition with correct CBOR tagging.The
retired_poolfield is properly tagged with#[n(7)]following the existing sequential CBOR encoding pattern, and theOption<PoolHash>type correctly represents the optional retired pool state.
543-555: LGTM! Correct initialization in constructor.The
AccountState::newproperly initializesretired_pool: None, consistent with the genesis initialization instaking.rs.
1823-1823: LGTM! Complete delta integration.The
PoolDelegatorRetiredelta is fully wired into theCardanoDeltaenum 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
| fn undo(&self, _entity: &mut Option<AccountState>) { | ||
| // todo!() | ||
| // Placeholder undo logic. Ensure this does not panic. | ||
| } |
There was a problem hiding this comment.
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>, |
There was a problem hiding this comment.
let's put a cbor default attribute here so that previous snapshots still work regardless of the existence of the new value.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 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
PoolDelegatorRetirefollows the established pattern and is consistent with other delta types from theewrap::dropsmodule.
537-539: LGTM!The
retired_poolfield 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_poolfield is correctly initialized toNonein the constructor, following the established pattern for optional fields.
1824-1824: LGTM!The
PoolDelegatorRetirevariant is correctly added to theCardanoDeltaenum, following the established pattern for delta types.
1892-1892: LGTM!The
delta_from!macro invocation correctly implements theFromtrait forPoolDelegatorRetire, enabling conversion toCardanoDelta.
1929-1929: LGTM!The
key()match arm forPoolDelegatorRetirecorrectly delegates to the inner delta'skey()method, following the established pattern.
1967-1967: LGTM!The
apply()match arm forPoolDelegatorRetirecorrectly uses thedowncast_applyhelper, 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), |
There was a problem hiding this comment.
🧩 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 10Repository: 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 fRepository: 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 rustRepository: 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.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.