Skip to content

Conversation

@scarmuega
Copy link
Member

@scarmuega scarmuega commented Nov 30, 2025

Summary by CodeRabbit

  • Refactor
    • Redesigned epoch incentive and reward handling to produce and carry explicit per-epoch incentives through transitions, improving consistency of pot and incentive updates.
  • New Features
    • Exposes a simple available_rewards metric for workflows that consume epoch rewards.
  • Bug Fixes
    • Added sanity checks and logging to catch pot/incentive inconsistencies during epoch transitions.

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

@coderabbitai
Copy link

coderabbitai bot commented Nov 30, 2025

Walkthrough

Epoch incentives are integrated into the epoch lifecycle: EpochState gains an incentives field, EpochTransition carries new_incentives, boundary flushing computes and returns incentives alongside pots, rewards APIs switch to exposing only numeric available_rewards, and several helpers/types (Eta, EpochIncentives, neutral()) are added or updated.

Changes

Cohort / File(s) Summary
Epoch transition & boundary
crates/cardano/src/estart/reset.rs
Added new_incentives: EpochIncentives to EpochTransition; apply now writes entity.incentives; BoundaryVisitor.flush now computes and returns both new_pots and new_incentives; introduced define_eta and define_new_incentives helpers; pot delta calculation now reads from epoch.incentives.
Model & Genesis
crates/cardano/src/model.rs, crates/cardano/src/genesis/mod.rs
EpochState gets a new public field incentives: EpochIncentives (initialized in bootstrap); EndStats.epoch_incentives renamed to __epoch_incentives (deprecation note).
Pots & types
crates/cardano/src/pots.rs
Added EpochIncentives::neutral() helper; derived Default for PotDelta; imports/exports updated to expose EpochIncentives, Eta, pallas_ratio, etc.; pot creation now uses new incentives path.
Rewards core & trait
crates/cardano/src/rewards/mod.rs
Removed stored incentives: EpochIncentives from RewardMap; RewardMap::new() no longer takes incentives; RewardsContext API changed from incentives(&self) -> &EpochIncentives to available_rewards(&self) -> u64; internal callers updated to use numeric available rewards.
Mocking (tests/dev) changes
crates/cardano/src/rewards/mocking.rs
MockContext now stores available_rewards: Option<u64> (serde skip); replaced incentives() impl with available_rewards() returning the numeric amount; JSON-loading maps computed incentives -> available_rewards.
RUPD / Rewards processing
crates/cardano/src/rupd/mod.rs, crates/cardano/src/rupd/loading.rs
RupdWork replaced incentives: EpochIncentives with available_rewards: u64; removed earlier local incentive helpers; RupdWork::load sets available_rewards from epoch.incentives.available_rewards; RewardsContext impl updated accordingly.
End wrap-up
crates/cardano/src/ewrap/wrapup.rs
Removed direct incentives computation from define_end_stats; EndStats now uses a default/deprecated __epoch_incentives placeholder.
Imports & misc
multiple files (...)
Minor import and formatting updates across files to accommodate new helpers/types and renamed fields (Eta, EpochIncentives, pallas_ratio, PotDelta Default, etc.).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25–30 minutes

  • Pay attention to: crates/cardano/src/estart/reset.rs (incentives propagation and pot consistency checks).
  • Check crates/cardano/src/rewards/mod.rs and all RewardsContext implementations for complete API updates.
  • Validate RupdWork and MockContext changes to ensure tests and mocks align with numeric available_rewards semantics.

Possibly related PRs

Poem

🐰 In epochs where the ledgers sing,
New carrots of incentives spring,
Pots and numbers, tidy and bright,
Rewards counted in plain daylight,
—Hops the refactor rabbit, light!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately reflects the main refactoring objective: moving incentives storage from various contexts (MockContext, RupdWork, RewardMap) to be stored as a field on EpochState.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/incentives-store

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

@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: 0

🧹 Nitpick comments (3)
crates/cardano/src/pots.rs (2)

92-102: Consider removing duplicate functionality.

The TODO comment on line 93 is valid. neutral() and Default::default() produce identical results. Consider either:

  1. Removing neutral() and using Default::default() consistently, or
  2. Removing the Default derive and keeping only neutral() with explicit naming

The semantic naming of neutral() is clearer for the domain, but having both creates API surface redundancy.


393-437: Good test coverage for Byron delta path.

The test validates:

  • Pot consistency before and after delta application
  • Correct reserves/utxos calculations for Byron era
  • Uses realistic mainnet-like values

Minor: Line 428 has a dbg!(&pots) that could be removed or converted to an assertion.

-        dbg!(&pots);
-
         assert!(pots.is_consistent(MAX_SUPPLY));
crates/cardano/src/rewards/mocking.rs (1)

201-204: Consider a more descriptive panic message.

The .expect() will panic if MockContext is created without going through from_json_file(). For test code this is acceptable, but the message could guide users to the solution.

 fn available_rewards(&self) -> u64 {
     self.available_rewards
-        .expect("available rewards not computed")
+        .expect("available rewards not computed - use MockContext::from_json_file() to initialize")
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ac0844 and 06c4257.

📒 Files selected for processing (9)
  • crates/cardano/src/estart/reset.rs (6 hunks)
  • crates/cardano/src/ewrap/wrapup.rs (2 hunks)
  • crates/cardano/src/genesis/mod.rs (2 hunks)
  • crates/cardano/src/model.rs (2 hunks)
  • crates/cardano/src/pots.rs (4 hunks)
  • crates/cardano/src/rewards/mocking.rs (3 hunks)
  • crates/cardano/src/rewards/mod.rs (4 hunks)
  • crates/cardano/src/rupd/loading.rs (3 hunks)
  • crates/cardano/src/rupd/mod.rs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
crates/cardano/src/rewards/mocking.rs (2)
crates/cardano/src/rewards/mod.rs (2)
  • pots (302-302)
  • available_rewards (301-301)
crates/cardano/src/pots.rs (1)
  • epoch_incentives (261-289)
crates/cardano/src/rewards/mod.rs (2)
crates/cardano/src/rewards/mocking.rs (1)
  • available_rewards (201-204)
crates/cardano/src/rupd/loading.rs (1)
  • available_rewards (172-174)
crates/cardano/src/rupd/loading.rs (3)
crates/cardano/src/rewards/mocking.rs (2)
  • pots (206-208)
  • available_rewards (201-204)
crates/cardano/src/rewards/mod.rs (2)
  • pots (302-302)
  • available_rewards (301-301)
crates/cardano/src/rupd/mod.rs (1)
  • RupdWork (173-173)
crates/cardano/src/estart/reset.rs (1)
crates/cardano/src/pots.rs (3)
  • apply_delta (373-378)
  • calculate_eta (231-259)
  • epoch_incentives (261-289)
🔇 Additional comments (18)
crates/cardano/src/rupd/mod.rs (1)

97-107: LGTM!

The simplification from EpochIncentives to a plain u64 available_rewards field is a clean approach that aligns with the broader refactor. The struct now exposes only the data needed downstream, reducing coupling to the internal incentives structure.

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

67-78: LGTM!

Initializing incentives with EpochIncentives::default() at genesis is appropriate since no incentives have been computed for epoch 0.

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

1463-1465: Deprecation approach is consistent.

The __epoch_incentives field follows the same deprecation pattern as other fields in EndStats (e.g., __drep_deposits, __drep_refunds). Consider adding a tracking issue for removing these deprecated fields in a future release.


1488-1516: The field indices were pre-designed with intentional gaps for forward compatibility.

The indices 4-8 in EpochState are reserved slots, not evidence of reindexing. CBOR's explicit field numbering (#[n(...)]) inherently supports forward and backward compatibility—old persisted data with missing fields deserializes correctly, and new data with additional fields can coexist with older readers. The incentives field at #[n(3)] occupies a pre-planned position and does not conflict with existing serialized data.

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

127-139: LGTM!

Setting __epoch_incentives to EpochIncentives::default() is correct here since incentives are now tracked in EpochState.incentives rather than in EndStats. This maintains backward compatibility for the deprecated field while the actual data flows through the new path.

crates/cardano/src/estart/reset.rs (4)

174-213: Incentives computation moved to epoch transition.

The define_new_incentives function correctly:

  • Returns neutral incentives for Byron era
  • Computes incentives using the new pots' reserves (not the old ones)
  • Uses mark() snapshot for fees, which is appropriate for the epoch being transitioned

This is the key refactor: incentives are now computed during epoch transition and stored in EpochState.incentives for use in the next epoch's rewards calculation.


215-272: Pot delta application now uses stored incentives.

Line 251 correctly uses epoch.incentives (the incentives computed during the previous epoch transition) when applying the delta. This ensures consistency between the incentives used for rewards and the pots update.


308-325: Epoch transition now carries incentives for the next epoch.

The ordering is correct:

  1. define_new_pots - compute pots using current epoch's incentives
  2. define_new_incentives - compute incentives for the next epoch using new pots

This ensures the new epoch starts with pre-computed incentives available for rewards distribution.


142-172: Eta calculation logic is correct.

The define_eta function properly handles:

  • Byron era returning eta = 1 (no performance-based rewards)
  • Missing mark data returning eta = 1
  • Shelley+ era computing eta from actual vs expected blocks

The use of is_none_or is consistent with the project's practices—it's already used in multiple locations throughout the codebase (e.g., src/serve/grpc/watch.rs) and the CI pipeline uses the stable toolchain, which supports this method.

crates/cardano/src/pots.rs (2)

291-312: Byron delta application correctly handles era-specific semantics.

The implementation properly:

  • Zeroes out all Shelley+ concepts (deposits, treasury, fees, rewards, etc.)
  • Only tracks utxo changes
  • Derives reserves from utxo changes (consumed utxos go back to reserves)

This matches Byron's simpler economic model where there's no staking/delegation.


373-378: Protocol version dispatch is correct.

The routing logic correctly maps:

  • Protocol 0, 1 → Byron (pre-Shelley)
  • Protocol 2+ → Shelley and later eras

This provides clean separation of era-specific pot accounting logic.

crates/cardano/src/rupd/loading.rs (2)

151-159: Clean simplification of RupdWork loading.

Loading available_rewards directly from epoch.incentives.available_rewards is cleaner than computing it on-demand. The incentives are now pre-computed during epoch transition, making this a simple data access.


171-178: RewardsContext implementation simplified.

The available_rewards() method now simply returns the pre-loaded value, consistent with the trait's simplified interface that exposes only the numeric rewards value rather than the full EpochIncentives structure.

crates/cardano/src/rewards/mocking.rs (2)

137-139: LGTM!

The field simplification from Option<EpochIncentives> to Option<u64> is a clean refactor that stores only the value needed by the RewardsContext trait.


159-167: LGTM!

The incentives computation correctly extracts only the available_rewards value needed for the simplified RewardsContext trait interface.

crates/cardano/src/rewards/mod.rs (3)

200-208: LGTM!

The simplified new() method that takes no arguments is clean. Note that it's now functionally identical to the Default implementation, but keeping both is idiomatic Rust.


300-301: LGTM!

The trait method simplification from returning &EpochIncentives to returning u64 is a clean API improvement that exposes only what callers need. The relevant code snippets confirm consistent implementations in both mocking.rs and loading.rs.


329-347: LGTM!

The call sites are correctly updated to use the simplified API:

  • RewardMap::<C>::new() matches the new parameterless signature
  • ctx.available_rewards() is more direct than the previous ctx.incentives().available_rewards

Copy link

@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: 0

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

92-102: Remove duplication between neutral() and Default::default().

Both neutral() and Default::default() (derived on line 77) produce identical results. The TODO comment acknowledges this duplication. Consider removing neutral() and using Default::default() consistently, or document if there's a semantic distinction intended.

Apply this diff if choosing to remove neutral():

-impl EpochIncentives {
-    // TODO: this and default are same, commit to one
-    pub fn neutral() -> Self {
-        Self {
-            total: 0,
-            treasury_tax: 0,
-            available_rewards: 0,
-            used_fees: 0,
-        }
-    }
-}

Then update call sites (e.g., line 184 in crates/cardano/src/estart/reset.rs) to use EpochIncentives::default() instead.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06c4257 and 95eb6c4.

📒 Files selected for processing (5)
  • crates/cardano/src/estart/reset.rs (6 hunks)
  • crates/cardano/src/ewrap/wrapup.rs (2 hunks)
  • crates/cardano/src/pots.rs (1 hunks)
  • crates/cardano/src/rewards/mod.rs (5 hunks)
  • crates/cardano/src/rupd/mod.rs (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/cardano/src/rupd/mod.rs
  • crates/cardano/src/ewrap/wrapup.rs
🧰 Additional context used
🧬 Code graph analysis (2)
crates/cardano/src/estart/reset.rs (1)
crates/cardano/src/pots.rs (3)
  • apply_delta (403-408)
  • calculate_eta (256-284)
  • epoch_incentives (286-314)
crates/cardano/src/rewards/mod.rs (2)
crates/cardano/src/rupd/loading.rs (1)
  • available_rewards (172-174)
crates/cardano/src/rewards/mocking.rs (1)
  • available_rewards (201-204)
⏰ 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). (4)
  • GitHub Check: Test (windows-latest)
  • GitHub Check: Test (macos-13)
  • GitHub Check: Test (ubuntu-latest)
  • GitHub Check: Test (macos-14)
🔇 Additional comments (10)
crates/cardano/src/pots.rs (1)

104-104: LGTM!

Adding Default for PotDelta is reasonable and aligns with the pattern of providing default initialization. The derived implementation initializes protocol_version to 0, which matches PotDelta::neutral(0).

crates/cardano/src/estart/reset.rs (5)

89-89: LGTM!

The addition of new_incentives field to EpochTransition and its application to entity.incentives correctly implements the refactor objective of storing incentives as an epoch field.

Also applies to: 119-119


174-213: LGTM!

The define_new_incentives function correctly computes epoch incentives based on context and pot state. The Byron special case handling and debug logging are appropriate.


251-251: LGTM!

The change to use &epoch.incentives in apply_delta correctly reflects that incentives are now stored as an epoch field, aligning with the PR objective.


313-319: LGTM!

The flush logic correctly computes both new_pots and new_incentives in sequence and passes them to the EpochTransition constructor, completing the integration of incentives into the epoch lifecycle.


142-172: LGTM. The is_none_or method is available and actively used throughout the codebase.

The define_eta function correctly computes eta based on genesis parameters and epoch state. The early returns for Byron and epoch 0 cases are appropriate defaults. The is_none_or method is already used in multiple places in the codebase (e.g., src/serve/grpc/watch.rs), confirming it's available in the project's Rust version. The TODO on line 150 about verifying epoch 0 behavior against specs is noted for future work.

crates/cardano/src/rewards/mod.rs (4)

142-148: LGTM!

The removal of the incentives field and addition of PhantomData<C> correctly simplifies RewardMap by removing unused data while maintaining the generic parameter.


174-182: LGTM!

The Default and Clone implementations are correctly updated to match the simplified struct definition.

Also applies to: 185-193


197-204: LGTM!

The simplified new() constructor correctly removes the unused incentives parameter.


292-292: LGTM!

The API change from incentives() -> &EpochIncentives to available_rewards() -> u64 is a clean improvement that exposes only the necessary data. The updated usage in define_rewards is consistent with this change.

Also applies to: 321-321, 338-338

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