-
Notifications
You must be signed in to change notification settings - Fork 42
fix(cardano): use correct timing for pool params activation #764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThis PR restructures Cardano's epoch transition and entity delta system. It refactors EpochValue to track live and scheduled next values, introduces a TransitionDefault trait for default behavior, reorganizes protocol parameter access patterns throughout the codebase, restructures AccountState with a new Stake struct, updates Pots to use a Lovelace type alias, consolidates delta types (removing snapshot.rs), and changes pool iteration APIs to separate pool enumeration from parameter retrieval. Changes
Sequence DiagramsequenceDiagram
participant OldFlow as Old: Direct Mutation
participant NewFlow as New: Delta-Based
participant Epoch as Epoch State
participant Stake as Stake/Account
participant Pool as Pool/Snapshot
Note over OldFlow,Epoch: Previous Approach
OldFlow->>Epoch: mutate epoch.pparams directly
OldFlow->>Stake: set rewards_sum field directly
OldFlow->>Pool: mutate snapshot.live_mut() in-place
Note over NewFlow,Epoch: New Approach
NewFlow->>NewFlow: create AccountTransition delta
NewFlow->>NewFlow: create PoolTransition delta
NewFlow->>NewFlow: create EpochTransition delta
NewFlow->>Epoch: apply all deltas atomically
NewFlow->>Stake: schedule via stake.schedule()
NewFlow->>Pool: schedule via snapshot.schedule()
Note over Epoch: Transition happens via transition_unchecked()
Epoch->>Epoch: move next→live, clear mark/set/go
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Rationale: This is a foundational refactoring affecting the core data model (EpochValue with new multi-state tracking), entity delta system (new types: AccountTransition, PoolTransition, EpochTransition, PoolWrapUp), and pervasive protocol parameter access patterns across 20+ files. Changes are heterogeneous—spanning data structure redesigns, API signature changes, state transition semantics, and interconnected boundary/reward logic updates. High logic density in model.rs and delta implementations, coupled with wide-ranging cascading updates throughout the codebase, demands comprehensive cross-file reasoning. Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
crates/cardano/src/ewrap/commit.rs (1)
40-47: Avoid panic on missing live pparams during era switchunwrap_live() will abort the process if live pparams are absent; return a typed error instead to preserve chain progress.
Apply:
- let pparams = self.ending_state().pparams.unwrap_live(); + let pparams = self + .ending_state() + .pparams + .live() + .ok_or_else(|| ChainError::from(BrokenInvariant::BadBootstrap))?;No other logic changes needed; ensure ensure_epoch_length()/ensure_slot_length() remain fallible as-is.
crates/cardano/src/roll/pools.rs (1)
120-122: Undo path is unimplemented (panic). Implement reversible logic.todo!() in EntityDelta::undo will panic during reorgs. Capture and restore prior state.
Suggested approach:
- Store previous scheduled “next” snapshot before apply.
- On undo, restore that value; if this delta created the entity, delete it.
Example diff (fields + logic):
@@ pub struct PoolRegistration { cert: MultiEraPoolRegistration, slot: BlockSlot, epoch: Epoch, pool_deposit: u64, // undo is_new: Option<bool>, + prev_next: Option<PoolSnapshot>, } @@ fn apply(&mut self, entity: &mut Option<PoolState>) { if let Some(entity) = entity { @@ - entity.snapshot.schedule( + // save undo + self.prev_next = entity.snapshot.next().cloned(); + entity.snapshot.schedule( self.epoch, Some(PoolSnapshot { is_retired: false, blocks_minted: 0, params: self.cert.clone().into(), }), ); } else { @@ *entity = Some(state); } } @@ - fn undo(&self, _entity: &mut Option<PoolState>) { - todo!() + fn undo(&self, entity: &mut Option<PoolState>) { + if self.is_new == Some(true) { + // registration created a new pool; undo by removing it + *entity = None; + return; + } + if let Some(entity) = entity { + // restore previously scheduled snapshot + entity + .snapshot + .schedule(self.epoch, self.prev_next.clone()); + } }
🧹 Nitpick comments (28)
crates/minibf/src/routes/epochs/mod.rs (2)
35-38: Avoid silently defaulting protocol params; prefer explicit error or a deliberate fallback.Using unwrap_or_default() can emit a zeroed/default parameter set, which may misrepresent the network. Consider returning 404/500 when no live params exist, or use a documented fallback (e.g., current effective params) with an explicit comment.
Also, params use live() while nonce still reads active; please confirm this mismatch is intentional for epoch timing.
60-66: Same defaulting concern for historical parameters.by_number_parameters now also defaults to an empty param set when live() is None. Prefer explicit error or a documented fallback to avoid serving incorrect values. Verify if nonce should also be based on live() here for consistency.
crates/minibf/src/lib.rs (1)
123-126: Semantics check: is prior_epoch.live() the correct source for “effective in epoch E”?If logs capture both “live” and “scheduled next,” retrieving effective params for epoch E via prior_epoch.live() may be off by one depending on model semantics. Please confirm whether prior.next (or equivalent) is the intended source. Also, avoid unwrap_or_default() here to prevent silently returning bogus params; prefer an explicit error or well-documented fallback.
crates/minibf/src/routes/pools.rs (2)
69-94: Default numeric strings to "0" instead of empty string.declared_pledge and fixed_cost currently default to "". Prefer "0" to match typical Blockfrost numeric-as-string conventions and avoid surprising clients.
Apply this diff:
- declared_pledge: params - .as_ref() - .map(|x| x.pledge.to_string()) - .unwrap_or_default(), + declared_pledge: params + .as_ref() + .map(|x| x.pledge.to_string()) + .unwrap_or_else(|| "0".to_string()), - fixed_cost: params - .as_ref() - .map(|x| x.cost.to_string()) - .unwrap_or_default(), + fixed_cost: params + .as_ref() + .map(|x| x.cost.to_string()) + .unwrap_or_else(|| "0".to_string()),
120-136: Retired filter may include pools with missing snapshots.state.snapshot.live().map(|x| x.is_retired).unwrap_or(false) treats “no snapshot” as “not retired,” potentially surfacing pools lacking a live snapshot. If that state is possible, consider excluding entries without a snapshot or making the default explicit in a comment.
crates/cardano/src/roll/accounts.rs (1)
100-100: Remove unused field.The
prev_depositfield is declared but never assigned in theapplymethod and is not used inundo. This appears to be dead code left over from the refactoring.Apply this diff to remove the unused field:
- prev_deposit: Option<u64>,Also remove it from the constructor at line 112:
prev_registered_at: None, prev_deregistered_at: None, - prev_deposit: None,crates/cardano/src/model.rs (1)
456-469: Defensive arithmetic to avoid underflow in Stake.total() and withdrawable() subtract on u64; if invariants are violated (e.g., withdrawals_sum > rewards_sum), values wrap. Add saturating math or debug assertions.
impl Stake { - pub fn total(&self) -> u64 { - let mut out = self.utxo_sum; - out += self.rewards_sum; - out -= self.withdrawals_sum; - out - } + pub fn total(&self) -> u64 { + self.utxo_sum + .saturating_add(self.rewards_sum) + .saturating_sub(self.withdrawals_sum) + } - pub fn withdrawable(&self) -> u64 { - let mut out = self.rewards_sum; - out -= self.withdrawals_sum; - out - } + pub fn withdrawable(&self) -> u64 { + self.rewards_sum.saturating_sub(self.withdrawals_sum) + } }crates/cardano/src/genesis/mod.rs (1)
47-48: Guard against underflow when computing reserves.Plain subtraction can wrap if utxos > max_supply in malformed genesis. Prefer checked or saturating subtraction.
- let reserves = max_supply - utxos; + let reserves = max_supply.saturating_sub(utxos); + // Alternatively, use checked_sub to error out: + // let reserves = max_supply + // .checked_sub(utxos) + // .ok_or_else(|| ChainError::GenesisFieldMissing("inconsistent supply: utxos > max".into()))?;Please confirm desired behavior (clamp vs. error) for invalid genesis.
crates/cardano/src/ewrap/commit.rs (1)
33-47: Small invariant check for new EraSummaryOptionally assert previous.end is set after define_end to catch regressions early.
- let new = EraSummary { + debug_assert!(previous.end.is_some(), "EraSummary end not defined"); + let new = EraSummary { start: previous.end.clone().unwrap(),crates/cardano/src/rupd/mod.rs (1)
177-178: Commented debug print—fine to keepNo functional impact. Consider a trace! with feature-gate if you plan to re-enable.
crates/cardano/src/ewrap/loading.rs (1)
32-35: Improve retire log contextLog which pool/operator is being retired to aid debugging.
- info!("retiring pool"); + info!(operator = %pool.operator, "retiring pool");crates/cardano/src/rewards/mocking.rs (2)
320-324: Make missing params error explicit (avoid bare unwrap)unwrap() will panic without context if a pool hash is absent. Prefer expect with key context, or return Option and let callers decide.
- fn pool_params(&self, pool: PoolHash) -> &PoolParams { - self.pool_params_converted - .get(&hex::encode(pool.as_ref())) - .unwrap() - } + fn pool_params(&self, pool: PoolHash) -> &PoolParams { + self.pool_params_converted + .get(&hex::encode(pool.as_ref())) + .expect("missing mock pool_params for given PoolHash") + }Alternatively, change the trait to return Option<&PoolParams> if broader callers can handle absence.
65-113: Mock reward account uses mainnet stake tag (0xe1)Just noting: hardcoded to mainnet stake address semantics; fine for tests. If test vectors intend preview/preprod, consider parameterizing.
crates/cardano/src/estart/rewards.rs (1)
44-46: Undo path should be safe from underflowUse saturating_sub (or checked_sub with debug_assert) to avoid accidental underflow if deltas are replayed out of order.
- let stake = entity.stake.unwrap_live_mut(); - stake.rewards_sum -= self.reward; + if let Some(stake) = entity.stake.live_mut() { + stake.rewards_sum = stake.rewards_sum.saturating_sub(self.reward); + } else { + warn!("missing live stake when undoing rewards"); + }crates/cardano/src/rewards/mod.rs (3)
155-164: Display for RewardMap is minimal—consider adding account contextCurrently prints only amounts; adding the stake credential (or pool split) would improve diagnostics, but fine if intentionally terse.
327-415: Avoid O(n) owner lookups per delegatorowners.contains(&delegator) is linear; convert owners to a HashSet once to reduce complexity in large pools.
- let owners = pool_params - .pool_owners - .iter() - .map(|owner| pallas_extras::keyhash_to_stake_cred(*owner)) - .collect::<Vec<_>>(); + let owners: std::collections::HashSet<_> = pool_params + .pool_owners + .iter() + .map(|owner| pallas_extras::keyhash_to_stake_cred(*owner)) + .collect(); ... - for delegator in ctx.pool_delegators(pool) { - if owners.contains(&delegator) { + for delegator in ctx.pool_delegators(pool) { + if owners.contains(&delegator) { continue; }
350-366: Parameter fetches: consolidate to avoid repeated pparams() callsMinor: cache pparams once per loop to avoid repeated virtual calls.
- let k = ctx.pparams().ensure_k()?; - let a0 = ctx.pparams().ensure_a0()?; - let d = ctx.pparams().ensure_d()?; + let pparams = ctx.pparams(); + let k = pparams.ensure_k()?; + let a0 = pparams.ensure_a0()?; + let d = pparams.ensure_d()?;crates/cardano/src/roll/pools.rs (1)
107-111: Fix comment typo and grammar.“udpate its live snapshot” → “update its live snapshot”.
-// please note that new pools will udpate its live snapshot directly. This differs +// please note that new pools will update their live snapshot directly. This differscrates/cardano/src/pots.rs (1)
46-52: Guard against u64 overflow in obligations().Multiplying deposit_per_* by *_count and summing as u64 can overflow on large inputs.
Use u128 intermediates and clamp on conversion:
- pub fn obligations(&self) -> Lovelace { - let pool_deposits = self.deposit_per_pool * self.pool_count; - let account_deposits = self.deposit_per_account * self.account_count; - - Lovelace::from(self.nominal_deposits + pool_deposits + account_deposits) - } + pub fn obligations(&self) -> Lovelace { + let pool_deposits = (self.deposit_per_pool as u128) * (self.pool_count as u128); + let account_deposits = (self.deposit_per_account as u128) * (self.account_count as u128); + let nominal = self.nominal_deposits as u128; + let sum = nominal + .saturating_add(pool_deposits) + .saturating_add(account_deposits); + Lovelace::from(sum as u64) + }crates/cardano/src/ewrap/wrapup.rs (2)
1-5: Remove unused imports to satisfy warnings.Strip unused AccountId, AccountState, PoolSnapshot, and Epoch.
-use crate::{ - ewrap::{AccountId, PoolId}, - AccountState, CardanoDelta, EndStats, EpochState, FixedNamespace as _, PParamsSet, - PoolSnapshot, PoolState, CURRENT_EPOCH_KEY, -}; +use crate::{ + ewrap::PoolId, CardanoDelta, EndStats, EpochState, FixedNamespace as _, PParamsSet, PoolState, + CURRENT_EPOCH_KEY, +}; -use pallas::ledger::primitives::Epoch;
28-35: Be defensive on missing pool state (prefer warn-and-return).Using expect("existing pool") will panic; consider logging and returning to keep boundary processing resilient.
- let entity = entity.as_mut().expect("existing pool"); + let Some(entity) = entity.as_mut() else { + tracing::warn!("missing pool during wrap-up; skipping"); + return; + };crates/cardano/src/ewrap/retires.rs (2)
40-47: Fix log message in ProposalExpiration::undo.Warn says “missing pool” but this delta targets proposals.
- warn!("missing pool"); + warn!("missing proposal");
115-122: Message nit: replace “existing account” with “existing drep”.Clarity improvement for DRepExpiration.
- let entity = entity.as_mut().expect("existing account"); + let entity = entity.as_mut().expect("existing drep");crates/cardano/src/rupd/loading.rs (2)
59-76: Avoid potential panic: prefer non-panicking pparams access or guard Byron/None before unwrap.Using epoch.pparams.unwrap_live() can panic if live params are absent. Guard with mark() and bail out early (neutral pot) or Byron check before using ensure_*.
- let pparams = epoch.pparams.unwrap_live(); + let Some(pparams) = epoch.pparams.mark() else { + info!("no pparams available for epoch; neutral incentives"); + return Ok(neutral_incentives()); + }; + if pparams.is_byron() { + info!("no pot changes during Byron epoch"); + return Ok(neutral_incentives()); + } - if pparams.is_byron() { - info!("no pot changes during Byron epoch"); - return Ok(neutral_incentives()); - }Please confirm unwrap_live cannot occur in Byron/edge epochs, otherwise this is required.
195-208: Display should not unwrap pool_params; handle missing gracefully.Even with the insertion fix, defensive formatting avoids panics during debug output.
- for (pool, blocks) in self.pool_blocks.iter() { - let pparams = self.pool_params.get(pool).unwrap(); - writeln!(f, "| {pool} | {blocks} | {} |", pparams.pledge)?; - } + for (pool, blocks) in self.pool_blocks.iter() { + if let Some(pparams) = self.pool_params.get(pool) { + writeln!(f, "| {pool} | {blocks} | {} |", pparams.pledge)?; + } else { + writeln!(f, "| {pool} | {blocks} | <missing params> |")?; + } + }crates/cardano/src/estart/reset.rs (3)
30-42: Undo paths are todo; confirm not required for current flows.AccountTransition::undo left as todo!(). If rollbacks/rewinds can reach these deltas, this will crash.
Add a tracked issue or implement minimal reversible state if rewinds are possible in estart.
62-73: PoolTransition undo missing; verify no rewind usage.Same concern as accounts; please confirm or stub with a safe no-op if acceptable.
102-129: Prefer non-panicking accessors for rolling live values.Using unwrap_live() for rolling assumes presence at boundary. If absence is possible on early epochs, use mark() or expect(...) with context.
- let rolling = epoch.rolling.unwrap_live(); + let rolling = epoch + .rolling + .mark() + .expect("rolling stats should be live at epoch boundary");Also confirm epoch.end is always Some at this stage; otherwise expect(...) with a clearer message.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (26)
crates/cardano/src/estart/nonces.rs(1 hunks)crates/cardano/src/estart/reset.rs(2 hunks)crates/cardano/src/estart/rewards.rs(2 hunks)crates/cardano/src/ewrap/commit.rs(2 hunks)crates/cardano/src/ewrap/loading.rs(3 hunks)crates/cardano/src/ewrap/mod.rs(0 hunks)crates/cardano/src/ewrap/retires.rs(10 hunks)crates/cardano/src/ewrap/snapshot.rs(0 hunks)crates/cardano/src/ewrap/wrapup.rs(4 hunks)crates/cardano/src/genesis/mod.rs(5 hunks)crates/cardano/src/lib.rs(1 hunks)crates/cardano/src/model.rs(19 hunks)crates/cardano/src/pots.rs(9 hunks)crates/cardano/src/rewards/mocking.rs(1 hunks)crates/cardano/src/rewards/mod.rs(4 hunks)crates/cardano/src/roll/accounts.rs(9 hunks)crates/cardano/src/roll/epochs.rs(7 hunks)crates/cardano/src/roll/pools.rs(3 hunks)crates/cardano/src/rupd/loading.rs(6 hunks)crates/cardano/src/rupd/mod.rs(1 hunks)crates/minibf/src/lib.rs(1 hunks)crates/minibf/src/routes/accounts.rs(1 hunks)crates/minibf/src/routes/epochs/mod.rs(2 hunks)crates/minibf/src/routes/pools.rs(4 hunks)src/bin/dolos/data/dump_logs.rs(1 hunks)src/bin/dolos/data/dump_state.rs(5 hunks)
💤 Files with no reviewable changes (2)
- crates/cardano/src/ewrap/mod.rs
- crates/cardano/src/ewrap/snapshot.rs
🧰 Additional context used
🧬 Code graph analysis (18)
crates/cardano/src/estart/nonces.rs (1)
crates/cardano/src/model.rs (1)
era_transition(1007-1020)
crates/cardano/src/genesis/mod.rs (2)
crates/cardano/src/pots.rs (1)
max_supply(54-56)crates/cardano/src/model.rs (4)
with_genesis(183-192)with_live(161-170)epoch(195-200)protocol_major(1113-1115)
crates/cardano/src/rewards/mod.rs (2)
crates/cardano/src/rupd/loading.rs (3)
fmt(196-207)iter_all_pools(302-304)pool_params(306-308)crates/cardano/src/rewards/mocking.rs (2)
iter_all_pools(314-318)pool_params(320-324)
crates/cardano/src/ewrap/commit.rs (3)
crates/cardano/src/rewards/mocking.rs (1)
pparams(340-342)crates/cardano/src/rewards/mod.rs (1)
pparams(309-309)crates/cardano/src/rupd/loading.rs (1)
pparams(317-321)
crates/cardano/src/ewrap/loading.rs (3)
crates/cardano/src/rewards/mocking.rs (1)
pparams(340-342)crates/cardano/src/rewards/mod.rs (1)
pparams(309-309)crates/cardano/src/rupd/loading.rs (1)
pparams(317-321)
src/bin/dolos/data/dump_state.rs (2)
crates/cardano/src/model.rs (13)
total(456-462)go(77-82)go(215-217)set(71-76)set(219-221)set(1081-1089)mark(65-70)mark(223-225)live(203-205)epoch(195-200)protocol_major(1113-1115)len(1065-1067)next(227-229)crates/cardano/src/pots.rs (1)
obligations(47-52)
crates/minibf/src/routes/pools.rs (1)
crates/cardano/src/model.rs (1)
live_stake(525-527)
src/bin/dolos/data/dump_logs.rs (2)
crates/cardano/src/model.rs (2)
protocol_major(1113-1115)len(1065-1067)crates/cardano/src/pots.rs (1)
obligations(47-52)
crates/cardano/src/rupd/loading.rs (2)
crates/cardano/src/rewards/mocking.rs (3)
pparams(340-342)iter_all_pools(314-318)pool_params(320-324)crates/cardano/src/rewards/mod.rs (4)
pparams(309-309)iter_all_pools(306-306)pool_params(307-307)pool_params(333-337)
crates/cardano/src/pots.rs (3)
crates/cardano/src/rewards/mocking.rs (1)
pots(246-248)crates/cardano/src/rewards/mod.rs (1)
pots(296-296)crates/cardano/src/rupd/loading.rs (1)
pots(274-276)
crates/cardano/src/rewards/mocking.rs (2)
crates/cardano/src/rewards/mod.rs (3)
iter_all_pools(306-306)pool_params(307-307)pool_params(333-337)crates/cardano/src/rupd/loading.rs (2)
iter_all_pools(302-304)pool_params(306-308)
crates/cardano/src/roll/accounts.rs (3)
crates/cardano/src/model.rs (1)
undo(1774-1808)crates/cardano/src/roll/pools.rs (3)
undo(120-122)undo(145-150)undo(199-204)crates/cardano/src/roll/dreps.rs (3)
undo(70-76)undo(123-129)undo(166-170)
crates/cardano/src/roll/epochs.rs (3)
crates/cardano/src/model.rs (2)
next(227-229)undo(1774-1808)crates/core/src/state.rs (2)
next(224-234)undo(163-163)crates/cardano/src/roll/accounts.rs (7)
undo(56-60)undo(83-87)undo(142-147)undo(189-193)undo(239-244)undo(303-305)undo(328-332)
crates/cardano/src/ewrap/retires.rs (3)
crates/cardano/src/estart/rewards.rs (3)
undo(36-46)key(20-22)apply(24-34)crates/cardano/src/roll/accounts.rs (18)
undo(56-60)undo(83-87)undo(142-147)undo(189-193)undo(239-244)new(26-32)new(104-114)new(160-166)new(208-216)new(261-271)key(45-48)key(72-75)key(120-123)key(172-175)apply(50-54)apply(77-81)apply(125-140)apply(177-187)crates/cardano/src/pallas_extras.rs (1)
pool_reward_account(334-337)
crates/cardano/src/ewrap/wrapup.rs (3)
crates/cardano/src/model.rs (9)
key(700-702)key(1702-1736)from(1429-1431)from(1435-1437)from(1441-1443)from(1447-1450)apply(1738-1772)undo(1774-1808)transition(299-302)crates/cardano/src/ewrap/mod.rs (2)
BoundaryWork(118-118)visit_pool(24-31)crates/cardano/src/forks.rs (1)
migrate_pparams_version(212-246)
crates/cardano/src/roll/pools.rs (3)
crates/cardano/src/model.rs (2)
with_live(161-170)undo(1774-1808)crates/cardano/src/roll/accounts.rs (7)
undo(56-60)undo(83-87)undo(142-147)undo(189-193)undo(239-244)undo(303-305)undo(328-332)crates/cardano/src/roll/epochs.rs (3)
undo(67-94)undo(125-128)undo(168-170)
crates/cardano/src/estart/reset.rs (4)
crates/cardano/src/estart/mod.rs (4)
WorkContext(109-109)visit_account(31-38)visit_pool(21-28)flush(61-63)crates/cardano/src/rewards/mod.rs (5)
pots(296-296)new(87-114)new(191-199)incentives(274-276)incentives(295-295)crates/cardano/src/pots.rs (1)
apply_delta(223-262)crates/cardano/src/model.rs (7)
new(150-159)new(511-523)new(682-698)new(1392-1402)key(700-702)key(1702-1736)apply(1738-1772)
crates/cardano/src/model.rs (3)
crates/cardano/src/roll/accounts.rs (5)
new(26-32)new(104-114)new(160-166)new(208-216)new(261-271)crates/cardano/src/roll/epochs.rs (1)
new(140-145)crates/cardano/src/roll/pools.rs (3)
new(45-58)new(164-171)from(30-41)
🪛 GitHub Actions: CI
crates/cardano/src/estart/reset.rs
[warning] 8-9: unused imports: PParamsSet and RollingStats
🪛 GitHub Check: Check Build
crates/cardano/src/ewrap/wrapup.rs
[warning] 7-7:
unused import: pallas::ledger::primitives::Epoch
[warning] 2-2:
unused imports: AccountId, AccountState, and PoolSnapshot
🔇 Additional comments (23)
crates/minibf/src/routes/accounts.rs (1)
112-124: Good alignment with Stake-centric live values.Using stake.live() for totals, rewards, withdrawals, and withdrawable improves consistency and avoids NPEs. Looks correct.
Please confirm these stringified zeros match Blockfrost responses for accounts without live stake.
crates/minibf/src/routes/pools.rs (2)
60-60: OK to surface params as Option; keep clone localized.Mapping snapshot.live() to an Option of params is fine and keeps cloning scoped.
155-160: Delegator live stake path looks good.Using AccountState::live_stake() centralizes the live() handling for stake totals and cleanly defaults to 0.
crates/cardano/src/roll/epochs.rs (1)
227-234: Fee selection for invalid txs looks good.Using total_collateral when is_valid() is false matches the semantics; falls back to fee otherwise. LGTM.
Please confirm that this aligns with how downstream consumers expect “gathered_fees” to report invalid tx collateral vs. fees in epoch stats.
crates/cardano/src/model.rs (3)
1006-1021: Era transition detection via PParamsSet is clear.Comparing live protocol_major to scheduled-next captures upgrades cleanly. LGTM.
1295-1299: RollingStats reset policy is appropriate.TransitionDefault::next_value returns default, ensuring epoch-scoped stats reset at transitions. LGTM.
525-527: Minor: live_stake() can reuse withdrawable().Not required, but consider using stake.live().map(Stake::total).unwrap_or_default() as you have; it’s fine. No change needed.
crates/cardano/src/genesis/mod.rs (1)
76-86: Genesis bootstrap changes look consistent.
- EpochValue::with_genesis for pparams and with_live for rolling are aligned with new EpochValue flow.
- bootstrap_eras now sources params via unwrap_live() and ensure_* accessors. LGTM.
Also applies to: 96-103
crates/cardano/src/lib.rs (1)
328-332: Effective pparams source updated to unwrap_live(): LGTM.This matches the EpochValue semantics and avoids mixing in scheduled-next.
crates/cardano/src/estart/nonces.rs (1)
44-48: Era transition source switched to pparams: LGTM.Sourcing via ended_state.pparams.era_transition() matches the new scheduling model.
Double-check that all BoundaryVisitors use the same pparams-based era transition source for consistency.
crates/cardano/src/ewrap/commit.rs (1)
20-22: Good: transition read is scoped under pparamsUsing ending_state.pparams.era_transition() tightens the coupling to the source of truth for era changes. LGTM.
crates/cardano/src/ewrap/loading.rs (2)
26-29: Correct: exclude retired pools from “existing” setFiltering on snapshot.is_retired prevents stale operators from being marked existing. LGTM.
73-121: Snapshot visitor removal verified — no lingering dependenciesVerification confirms complete removal of the snapshot visitor from the codebase:
- No
visitor_snapshotreferences exist- No
SnapshotVisitorstruct/impl definitions found- No snapshot visitor patterns in any modules
- Only the three expected visitors (retires, govactions, wrapup) remain active in
compute_deltasThe consolidation is sound and the new delta flow is clean.
src/bin/dolos/data/dump_logs.rs (1)
66-99: Safer optional chaining in EpochState rowCaching pparams/rolling and using as_ref()/unwrap_or_default() avoids panics and duplicate lookups. LGTM.
crates/cardano/src/rewards/mocking.rs (1)
314-318: iter_all_pools now by PoolHash—good alignmentIterating keys and converting to PoolHash matches the updated RewardsContext and reduces coupling. LGTM.
crates/cardano/src/rewards/mod.rs (1)
306-308: Trait API change verified: all implementors properly updatedVerification confirms:
- Both implementors (
RupdWorkin loading.rs andMockContextin mocking.rs) implement all three trait methods:iter_all_pools,pool_params, andpool_delegatorsiter_all_poolscorrectly yieldsPoolHashas separate methodpool_paramsis properly separated as independent method- Call sites in mod.rs correctly use the new split API
- No evidence of broken code or old paired signature dependencies
crates/cardano/src/roll/pools.rs (1)
77-87: No off-by-one error; scheduling is correct.The
schedule()function documentation states it "Schedules the next value to be applied on the next epoch transition." The function signatureschedule(&mut self, current_epoch: Epoch, next: Option<T>)accepts the current epoch and schedules the value for activation at the transition TO the next epoch. The code correctly passesself.epoch(the current epoch), so the pool snapshot will be applied on the next epoch transition, exactly as the comment states. No fix needed here.crates/cardano/src/ewrap/retires.rs (1)
86-90: ****The review comment incorrectly assumes a double-wrapped
Option. TheEpochValue::next()method returnsOption<&T>, notOption<&Option<T>>. ForEpochValue<PoolHash>, calling.next().cloned()correctly producesOption<PoolHash>, and no.flatten()is needed. The original code is correct as written.Likely an incorrect or invalid review comment.
crates/cardano/src/rupd/loading.rs (2)
166-172: LGTM on using stake snapshot for delegation and stake amount.Pulling pool and stake from stake_epoch aligns with “params at snapshot epoch, blocks at performance epoch.”
Please confirm that registration status (added elsewhere) is also evaluated at stake_epoch if that’s a protocol requirement.
255-256: pparams source change: confirm semantic intent.Switch to epoch.pparams.mark().cloned() looks correct with unified pparams handling. Verify it always reflects the set expected by rewards at load time (not next-epoch scheduled).
crates/cardano/src/estart/reset.rs (3)
81-96: EpochTransition.apply order looks correct; confirm pparams transition epoch number.You set number, then transition rolling and pparams with self.new_epoch. Validate that transition() expects the epoch you’re transitioning into (not from), and that this aligns with “params active at start of new_epoch.”
144-165: LGTM on per-entity delta collection.visit_account and visit_pool enqueue fine-grained transitions; keeps ordering explicit and reduces contention.
166-174: No off-by-one risk. The code is correct.The
starting_epoch_no()function returnsself.ended_state.number + 1, which represents the epoch being transitioned to, not the one that ended. SettingEpochTransition.new_epoch = ctx.starting_epoch_no()is semantically correct and aligns with consistent usage patterns throughout the codebase. No fix required.
| estart::{AccountId, PoolId, WorkContext}, | ||
| pots::{apply_delta, PotDelta, Pots}, | ||
| EpochState, FixedNamespace as _, PParamsSet, RollingStats, CURRENT_EPOCH_KEY, | ||
| AccountState, CardanoDelta, EpochState, FixedNamespace as _, PParamsSet, PoolState, | ||
| RollingStats, CURRENT_EPOCH_KEY, | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CI warning: remove unused imports.
PParamsSet and RollingStats are unused. Clean them up to fix CI warnings.
- AccountState, CardanoDelta, EpochState, FixedNamespace as _, PParamsSet, PoolState,
- RollingStats, CURRENT_EPOCH_KEY,
+ AccountState, CardanoDelta, EpochState, FixedNamespace as _, PoolState, CURRENT_EPOCH_KEY,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| estart::{AccountId, PoolId, WorkContext}, | |
| pots::{apply_delta, PotDelta, Pots}, | |
| EpochState, FixedNamespace as _, PParamsSet, RollingStats, CURRENT_EPOCH_KEY, | |
| AccountState, CardanoDelta, EpochState, FixedNamespace as _, PParamsSet, PoolState, | |
| RollingStats, CURRENT_EPOCH_KEY, | |
| }; | |
| estart::{AccountId, PoolId, WorkContext}, | |
| pots::{apply_delta, PotDelta, Pots}, | |
| AccountState, CardanoDelta, EpochState, FixedNamespace as _, PoolState, CURRENT_EPOCH_KEY, | |
| }; |
🧰 Tools
🪛 GitHub Actions: CI
[warning] 8-9: unused imports: PParamsSet and RollingStats
🤖 Prompt for AI Agents
In crates/cardano/src/estart/reset.rs around lines 6 to 10, the imports include
PParamsSet and RollingStats which are unused and triggering CI warnings; remove
PParamsSet and RollingStats from the import list (and tidy commas/whitespace if
needed) so the file only imports actually used symbols.
| let stake = entity.stake.unwrap_live_mut(); | ||
| stake.rewards_sum += self.reward; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against missing live stake instead of panicking
unwrap_live_mut() will panic if stake is absent for a valid account (e.g., edge cases during transitions). Fail soft with a warning like the missing account path.
- let stake = entity.stake.unwrap_live_mut();
- stake.rewards_sum += self.reward;
+ if let Some(stake) = entity.stake.live_mut() {
+ stake.rewards_sum = stake.rewards_sum.saturating_add(self.reward);
+ } else {
+ warn!("missing live stake when assigning rewards");
+ }If live_mut() isn’t available, add a helper or map unwrap_live_mut() error into ChainError.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In crates/cardano/src/estart/rewards.rs around lines 32–34, calling
unwrap_live_mut() on entity.stake will panic if the live stake is missing;
change this to handle the None case gracefully by matching or using
map_ok/map_err to convert the missing-stake into a ChainError and emit a warning
(or log) instead of panicking. Specifically, replace the direct
unwrap_live_mut() with a match or helper that returns an error variant (with a
short warning message) when there is no live stake, or implement a small helper
that maps unwrap_live_mut() failure into ChainError and then update the call to
use that helper so stake.rewards_sum += self.reward only runs when the stake is
present.
| let pparams = self.ending_state().pparams.unwrap_live(); | ||
|
|
||
| let expiring_epoch = last_activity_epoch + pparams.ensure_drep_inactivity_period()?; | ||
|
|
||
| Ok(expiring_epoch <= self.starting_epoch_no()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid panic in DRep expiry calc
unwrap_live() can panic if pparams are not yet live at boundary; prefer a fallible path.
- let pparams = self.ending_state().pparams.unwrap_live();
+ let pparams = self
+ .ending_state()
+ .pparams
+ .live()
+ .ok_or_else(|| ChainError::from(BrokenInvariant::BadBootstrap))?;
- let expiring_epoch = last_activity_epoch + pparams.ensure_drep_inactivity_period()?;
+ let expiring_epoch = last_activity_epoch + pparams.ensure_drep_inactivity_period()?;🤖 Prompt for AI Agents
In crates/cardano/src/ewrap/loading.rs around lines 52 to 56, replace the
panicking call to pparams.unwrap_live() with a fallible retrieval and handle the
non-live case instead of panicking: obtain the live protocol parameters via the
API's fallible method (or match on the Option/Result), propagate an error if
appropriate or return an early Ok(false)/appropriate result when params are not
yet live, then continue using the safely obtained live params for the
expiring_epoch calculation and return.
| fn undo(&self, entity: &mut Option<AccountState>) { | ||
| let Some(entity) = entity else { | ||
| warn!("missing delegator"); | ||
| return; | ||
| }; | ||
|
|
||
| debug!(delegator=%self.delegator, "restoring pool delegator"); | ||
|
|
||
| entity.pool = self.prev_pool.clone().expect("called with undo data"); | ||
| todo!() | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement undo for PoolDelegatorDrop.
Leaving todo!() will panic on reorg; restore prior scheduled value (or clear if none).
fn undo(&self, entity: &mut Option<AccountState>) {
- todo!()
+ if let Some(entity) = entity {
+ entity.pool.schedule_unchecked(self.prev_pool.clone());
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fn undo(&self, entity: &mut Option<AccountState>) { | |
| let Some(entity) = entity else { | |
| warn!("missing delegator"); | |
| return; | |
| }; | |
| debug!(delegator=%self.delegator, "restoring pool delegator"); | |
| entity.pool = self.prev_pool.clone().expect("called with undo data"); | |
| todo!() | |
| } | |
| } | |
| fn undo(&self, entity: &mut Option<AccountState>) { | |
| if let Some(entity) = entity { | |
| entity.pool.schedule_unchecked(self.prev_pool.clone()); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In crates/cardano/src/ewrap/retires.rs around lines 92–95, replace the todo!()
with logic that restores the prior scheduled delegation value when undoing a
PoolDelegatorDrop: if entity is Some(ref mut state) set
state.scheduled_delegation (or the field name used for the scheduled value) to
self.prior.clone() (or None if self.prior is None); if entity is None and
self.prior.is_some(), create a new AccountState with scheduled_delegation set to
self.prior.clone() and assign it to *entity; ensure you handle cloning and avoid
unwraps so no panic occurs on reorg.
| fn apply(&mut self, entity: &mut Option<AccountState>) { | ||
| let Some(entity) = entity else { | ||
| warn!("missing delegator"); | ||
| return; | ||
| }; | ||
| let entity = entity.as_mut().expect("existing account"); | ||
|
|
||
| debug!(delegator=%self.delegator, "dropping drep delegator"); | ||
|
|
||
| // save undo info | ||
| self.prev_drep = Some(entity.drep.clone()); | ||
|
|
||
| // apply changes | ||
| entity.drep.replace_unchecked(None); | ||
| entity.drep.schedule_unchecked(None); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DRepDelegatorDrop doesn’t save previous value; undo can’t restore.
Capture prev_next before scheduling None and implement undo accordingly.
pub struct DRepDelegatorDrop {
delegator: AccountId,
// undo
- prev_drep: Option<DRep>,
+ prev_drep: Option<DRep>,
}
@@
fn apply(&mut self, entity: &mut Option<AccountState>) {
- let entity = entity.as_mut().expect("existing account");
+ let entity = entity.as_mut().expect("existing account");
debug!(delegator=%self.delegator, "dropping drep delegator");
- // apply changes
+ // capture undo
+ self.prev_drep = entity.drep.next().cloned();
+ // apply changes
entity.drep.schedule_unchecked(None);
}
@@
- fn undo(&self, entity: &mut Option<AccountState>) {
- todo!()
+ fn undo(&self, entity: &mut Option<AccountState>) {
+ if let Some(entity) = entity {
+ entity.drep.schedule_unchecked(self.prev_drep.clone());
+ }
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In crates/cardano/src/ewrap/retires.rs around lines 159–167, the apply() call
clears the drep delegator but does not record the previous value so undo cannot
restore it; capture the existing next value from entity.drep (clone or take into
a new field on DRepDelegatorDrop, e.g. prev_next: Option<_>) before calling
schedule_unchecked(None), store that prev_next on the DRepDelegatorDrop
instance, and update undo() to call entity.drep.schedule_unchecked(prev_next) to
restore the original value (handle cloning/ownership to avoid borrow issues).
| // we need to use a delta approach instead of simple increments because the total size of moved | ||
| // lovelace can be higher than u64, causing overflows | ||
| utxo_delta: i64, | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UTXO accounting is incorrect: net delta loses produced/consumed totals.
Using a single utxo_delta and updating produced_utxos or consumed_utxos based only on its sign records net movement per block, not the actual amounts produced and consumed. Example: inputs=990, outputs=1000 → utxo_delta=+10, but produced=1000 and consumed=990 should both be counted. Current logic would add only 10 to produced. This breaks epoch stats.
Fix by tracking per-side totals in the update and applying both:
@@
- // we need to use a delta approach instead of simple increments because the total size of moved
- // lovelace can be higher than u64, causing overflows
- utxo_delta: i64,
+ // track per-side totals to avoid netting out consumption vs production
+ utxo_consumed: u64,
+ utxo_produced: u64,
@@
- if self.utxo_delta > 0 {
- stats.produced_utxos += self.utxo_delta.abs() as u64;
- } else {
- stats.consumed_utxos += self.utxo_delta.abs() as u64;
- }
+ stats.produced_utxos += self.utxo_produced;
+ stats.consumed_utxos += self.utxo_consumed;
@@
- if self.utxo_delta > 0 {
- stats.produced_utxos -= self.utxo_delta.abs() as u64;
- } else {
- stats.consumed_utxos -= self.utxo_delta.abs() as u64;
- }
+ stats.produced_utxos -= self.utxo_produced;
+ stats.consumed_utxos -= self.utxo_consumed;
@@
- let amount = resolved.value().coin();
- self.stats_delta.as_mut().unwrap().utxo_delta -= amount as i64;
+ let amount = resolved.value().coin();
+ self.stats_delta.as_mut().unwrap().utxo_consumed =
+ self.stats_delta.as_ref().unwrap().utxo_consumed.saturating_add(amount);
@@
- let amount = output.value().coin();
- self.stats_delta.as_mut().unwrap().utxo_delta += amount as i64;
+ let amount = output.value().coin();
+ self.stats_delta.as_mut().unwrap().utxo_produced =
+ self.stats_delta.as_ref().unwrap().utxo_produced.saturating_add(amount);Also applies to: 49-54, 78-83, 246-247, 260-261
🤖 Prompt for AI Agents
In crates/cardano/src/roll/epochs.rs around lines 25-28 (and also adjust update
sites at 49-54, 78-83, 246-247, 260-261), replace the single utxo_delta approach
with explicit per-side totals: add fields for produced_utxo_amount and
consumed_utxo_amount (types large enough to avoid overflow), and in each update
compute and add both the produced and consumed amounts separately instead of
only applying the signed net delta to one side; update all places that currently
set or accumulate utxo_delta to compute inputs and outputs independently and
increment both produced and consumed totals accordingly, ensuring aggregation
uses the new fields.
| fn undo(&self, entity: &mut Option<EpochState>) { | ||
| if let Some(entity) = entity { | ||
| if let Some(prev_value) = &self.prev_value { | ||
| entity.pparams.live_mut_unchecked().set(prev_value.clone()); | ||
| } else { | ||
| entity | ||
| .pparams | ||
| .live_mut_unchecked() | ||
| .clear(self.to_update.kind()); | ||
| } | ||
| } | ||
| todo!() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement PParamsUpdate.undo to support rollbacks.
Undo is todo!(), which will panic on rollback. Restore the scheduled-next value previously captured in prev_value:
@@
impl dolos_core::EntityDelta for PParamsUpdate {
@@
fn apply(&mut self, entity: &mut Option<EpochState>) {
let entity = entity.as_mut().expect("epoch state missing");
debug!(value = ?self.to_update, "applying pparam update");
- let next = entity.pparams.scheduled_or_default();
- // undo data
- self.prev_value = next.get(self.to_update.kind()).cloned();
- next.set(self.to_update.clone());
+ let next = entity.pparams.scheduled_or_default();
+ // capture previous scheduled value to allow precise undo
+ self.prev_value = next.get(self.to_update.kind()).cloned();
+ next.set(self.to_update.clone());
}
@@
- fn undo(&self, entity: &mut Option<EpochState>) {
- todo!()
- }
+ fn undo(&self, entity: &mut Option<EpochState>) {
+ let entity = entity.as_mut().expect("epoch state missing");
+ let next = entity
+ .pparams
+ .next_mut()
+ .expect("scheduled pparams missing on undo");
+
+ match &self.prev_value {
+ Some(prev) => next.set(prev.clone()),
+ None => next.clear(self.to_update.kind()),
+ }
+ }Optional: also track whether “next” existed before apply to restore emptiness precisely (e.g., a boolean flag), but the above is sufficient functionally for rollbacks now.
Also applies to: 155-166, 160-166
🤖 Prompt for AI Agents
In crates/cardano/src/roll/epochs.rs around lines 168-170, implement
PParamsUpdate::undo so it restores the epoch state's scheduled-next value from
self.prev_value instead of todo!(); specifically, if the provided entity is
Some(epoch_state) then set epoch_state.scheduled_next = self.prev_value.clone();
if entity is None simply return (don't panic). Optionally you can add a boolean
to track existence for an exact empty-vs-some restore, but restoring
prev_value.clone() is sufficient for rollbacks.
| // if pool.snapshot.snapshot_at(stake_epoch).is_none() { | ||
| // continue; | ||
| // }; | ||
|
|
||
| let Some(pool_snapshot) = pool.snapshot.snapshot_at(performance_epoch) else { | ||
| continue; | ||
| }; | ||
|
|
||
| if pool_snapshot.is_retired { | ||
| warn!(operator = %pool.operator, "skipping retired or pending pool are stake epoch"); | ||
| if pool_snapshot.blocks_minted == 0 { | ||
| continue; | ||
| } | ||
|
|
||
| snapshot | ||
| .pool_params | ||
| .insert(pool.operator, pool_snapshot.params.clone()); | ||
|
|
||
| // for tracking blocks we switch to the performance epoch (previous epoch, the | ||
| // one we're computing rewards for) | ||
| .pool_blocks | ||
| .insert(pool.operator, pool_snapshot.blocks_minted as u64); | ||
|
|
||
| let Some(pool_snapshot) = pool.snapshot.snapshot_at(performance_epoch) else { | ||
| let Some(pool_snapshot) = pool.snapshot.snapshot_at(stake_epoch) else { | ||
| continue; | ||
| }; | ||
|
|
||
| snapshot | ||
| .pool_blocks | ||
| .insert(pool.operator, pool_snapshot.blocks_minted as u64); | ||
| .pool_params | ||
| .insert(pool.operator, pool_snapshot.params.clone()); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Key-set mismatch between pool_blocks and pool_params can cause panics and missed rewards.
You insert pool_blocks before verifying stake_epoch params exist. For pools that minted in performance_epoch but lack a stake_epoch snapshot (e.g., newly registered after snapshot), pool_params will be missing. This breaks:
- Display impl (unwrap)
- RewardsContext::pool_params (unwrap) when iterating pools
Fix by inserting blocks only after confirming stake_epoch params are present, or by collecting both first and inserting atomically.
- let Some(pool_snapshot) = pool.snapshot.snapshot_at(performance_epoch) else {
+ let Some(perf_s) = pool.snapshot.snapshot_at(performance_epoch) else {
continue;
};
- if pool_snapshot.blocks_minted == 0 {
+ if perf_s.blocks_minted == 0 {
continue;
}
-
- snapshot
- .pool_blocks
- .insert(pool.operator, pool_snapshot.blocks_minted as u64);
-
- let Some(pool_snapshot) = pool.snapshot.snapshot_at(stake_epoch) else {
+ // Only proceed if stake-epoch params exist; keep maps in sync
+ let Some(stake_s) = pool.snapshot.snapshot_at(stake_epoch) else {
continue;
};
-
- snapshot
- .pool_params
- .insert(pool.operator, pool_snapshot.params.clone());
+ snapshot
+ .pool_blocks
+ .insert(pool.operator, perf_s.blocks_minted as u64);
+ snapshot
+ .pool_params
+ .insert(pool.operator, stake_s.params.clone());🤖 Prompt for AI Agents
In crates/cardano/src/rupd/loading.rs around lines 128 to 151, you currently
insert pool_blocks as soon as a performance_epoch snapshot exists which can
leave pool_params missing if the stake_epoch snapshot is absent; change the
logic to first attempt to obtain both snapshots (performance_epoch and
stake_epoch) for a pool, then only if the stake_epoch snapshot exists and
blocks_minted > 0 insert both pool_blocks and pool_params (or collect both
values and insert them together), ensuring the two maps are updated atomically
and preserving key-set parity.
| fn iter_all_pools(&self) -> impl Iterator<Item = PoolHash> { | ||
| self.snapshot.pool_blocks.keys().cloned() | ||
| } | ||
|
|
||
| fn pool_params(&self, pool: PoolHash) -> &PoolParams { | ||
| self.snapshot.pool_params.get(&pool).unwrap() | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Prevent invariant violations: iterate only pools that have both blocks and params.
iter_all_pools currently yields keys from pool_blocks, but pool_params unwraps and can panic if any key is missing. Either enforce the map-population invariant (see earlier diff) or add a defensive intersection here.
- fn iter_all_pools(&self) -> impl Iterator<Item = PoolHash> {
- self.snapshot.pool_blocks.keys().cloned()
- }
+ fn iter_all_pools(&self) -> impl Iterator<Item = PoolHash> {
+ self.snapshot
+ .pool_blocks
+ .keys()
+ .filter(|k| self.snapshot.pool_params.contains_key(*k))
+ .cloned()
+ }Additionally, consider replacing unwrap in pool_params with expect carrying a clear message to aid debugging.
- fn pool_params(&self, pool: PoolHash) -> &PoolParams {
- self.snapshot.pool_params.get(&pool).unwrap()
- }
+ fn pool_params(&self, pool: PoolHash) -> &PoolParams {
+ self.snapshot
+ .pool_params
+ .get(&pool)
+ .expect("pool_params missing for iterated pool")
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fn iter_all_pools(&self) -> impl Iterator<Item = PoolHash> { | |
| self.snapshot.pool_blocks.keys().cloned() | |
| } | |
| fn pool_params(&self, pool: PoolHash) -> &PoolParams { | |
| self.snapshot.pool_params.get(&pool).unwrap() | |
| } | |
| fn iter_all_pools(&self) -> impl Iterator<Item = PoolHash> { | |
| self.snapshot | |
| .pool_blocks | |
| .keys() | |
| .filter(|k| self.snapshot.pool_params.contains_key(*k)) | |
| .cloned() | |
| } | |
| fn pool_params(&self, pool: PoolHash) -> &PoolParams { | |
| self.snapshot | |
| .pool_params | |
| .get(&pool) | |
| .expect("pool_params missing for iterated pool") | |
| } |
🤖 Prompt for AI Agents
In crates/cardano/src/rupd/loading.rs around lines 302 to 308, iter_all_pools
currently yields keys from snapshot.pool_blocks only which can lead to
pool_params calling unwrap and panicking if a pool key is missing; change
iter_all_pools to return an iterator over the intersection of
snapshot.pool_blocks.keys() and snapshot.pool_params.keys() (so only pools that
have both blocks and params are produced), and replace the unwrap in pool_params
with expect carrying a clear message like "missing pool params for {pool}" to
make any remaining invariant violations explicit for debugging.
| "{} ({})", | ||
| self.snapshot.go().map(|x| x.is_pending).unwrap_or_default(), | ||
| self.snapshot | ||
| .go() | ||
| .map(|x| x.params.pledge) | ||
| .unwrap_or_default(), | ||
| self.snapshot.epoch().unwrap_or_default() - 3, | ||
| ), | ||
| format!( | ||
| "{} ({})", | ||
| self.snapshot | ||
| .set() | ||
| .map(|x| x.is_pending) | ||
| .map(|x| x.params.pledge) | ||
| .unwrap_or_default(), | ||
| self.snapshot.epoch().unwrap_or_default() - 2 | ||
| ), | ||
| format!( | ||
| "{} ({})", | ||
| self.snapshot | ||
| .mark() | ||
| .map(|x| x.is_pending) | ||
| .map(|x| x.params.pledge) | ||
| .unwrap_or_default(), | ||
| self.snapshot.epoch().unwrap_or_default() - 1 | ||
| ), | ||
| format!( | ||
| "{} ({})", | ||
| self.snapshot.live().is_pending, | ||
| self.snapshot | ||
| .live() | ||
| .map(|x| x.params.pledge) | ||
| .unwrap_or_default(), | ||
| self.snapshot.epoch().unwrap_or_default() | ||
| ), | ||
| format!( | ||
| "{} ({})", | ||
| self.snapshot | ||
| .next() | ||
| .map(|x| x.params.pledge) | ||
| .unwrap_or_default(), | ||
| self.snapshot.epoch().unwrap_or_default() | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Epoch arithmetic underflow in display.
Subtracting 3/2/1 from epoch without checks will wrap for early epochs (u64 underflow).
Use saturating_sub:
- self.snapshot.epoch().unwrap_or_default() - 3,
+ self.snapshot.epoch().unwrap_or(0).saturating_sub(3),
@@
- self.snapshot.epoch().unwrap_or_default() - 2
+ self.snapshot.epoch().unwrap_or(0).saturating_sub(2)
@@
- self.snapshot.epoch().unwrap_or_default() - 1
+ self.snapshot.epoch().unwrap_or(0).saturating_sub(1)
@@
- self.snapshot.epoch().unwrap_or_default()
+ self.snapshot.epoch().unwrap_or(0)
@@
- self.snapshot.epoch().unwrap_or_default()
+ self.snapshot.epoch().unwrap_or(0)Alternatively, surface epoch numbers from mark()/set()/go() helpers to avoid manual math. [Based on learnings]
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "{} ({})", | |
| self.snapshot.go().map(|x| x.is_pending).unwrap_or_default(), | |
| self.snapshot | |
| .go() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or_default() - 3, | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .set() | |
| .map(|x| x.is_pending) | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or_default() - 2 | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .mark() | |
| .map(|x| x.is_pending) | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or_default() - 1 | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot.live().is_pending, | |
| self.snapshot | |
| .live() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or_default() | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .next() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or_default() | |
| ), | |
| "{} ({})", | |
| self.snapshot | |
| .go() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or(0).saturating_sub(3), | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .set() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or(0).saturating_sub(2) | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .mark() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or(0).saturating_sub(1) | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .live() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or(0) | |
| ), | |
| format!( | |
| "{} ({})", | |
| self.snapshot | |
| .next() | |
| .map(|x| x.params.pledge) | |
| .unwrap_or_default(), | |
| self.snapshot.epoch().unwrap_or(0) | |
| ), |
🤖 Prompt for AI Agents
In src/bin/dolos/data/dump_state.rs around lines 207 to 245, the code subtracts
constants (3,2,1) from snapshot.epoch() which can underflow for early epochs
(u64 wrap); replace these subtractions with saturating_sub calls (e.g.,
epoch.saturating_sub(3/2/1)) or, preferably, use the epoch values returned by
go()/set()/mark()/live()/next() helpers when available instead of manual
arithmetic so no underflow occurs and displayed epochs are correct.
Summary by CodeRabbit
New Features
Lovelacetype for monetary amounts, improving type safety.AccountTransition,PoolTransition,EpochTransition,PoolWrapUp) for granular state transitions.Bug Fixes & Improvements