Skip to content

Conversation

golddydev
Copy link
Collaborator

@golddydev golddydev commented Aug 20, 2025

Add Pools History Endpoint and SPO Rewards Distribution

Overview

This PR implements a pools history endpoint by adding SPO (Stake Pool Operator) rewards tracking and pub/sub capabilities. The implementation includes new message types, data structures, and parallel processing improvements to support pool history queries.

Key Features

🏊‍♂️ Pool History Data Structure

  • New PoolEpochState type: Tracks epoch-specific pool metrics including:
    • Epoch number
    • Blocks minted
    • Active stake
    • Active size (as rational number)
    • Delegators count
    • Pool rewards and SPO rewards

📊 SPO Rewards Distribution (SPRD)

  • New SPORewardsMessage: Publishes SPO rewards data at epoch boundaries
  • New SPORewards type: Captures total rewards and operator rewards for each pool
  • New SPORewardsPublisher: Dedicated publisher for SPO rewards messages
  • Enhanced rewards calculation: Now tracks and returns SPO rewards alongside SPDD

⚡ Performance Improvements

  • Parallel processing: Replaced sequential processing with Rayon-based parallel iteration in generate_spdd()
  • Optimized stake aggregation: Uses DashMap for thread-safe concurrent updates during parallel processing

🔧 Enhanced Data Structures

  • Extended DelegatedStake: Added active_delegators_count field for pool history tracking
  • Enhanced RewardsResult: Added spors field to capture SPO rewards data
  • Updated PoolHistory query: Now includes actual history data structure

Technical Details

Message Flow

  1. Epoch Activity Processing: In accounts-state, when an epoch ends, handle_epoch_activity() calculates rewards and returns SPO rewards data
  2. Rewards Publishing: SPO rewards are automatically published via the new SPORewardsPublisher
  3. Topic Configuration: New configurable topic cardano.spo.rewards for SPO rewards distribution

Data Collection

  • Delegator Counting: Tracks active delegators per pool during stake aggregation
  • Rewards Tracking: Captures both total pool rewards and operator-specific rewards
  • Epoch Correlation: Links rewards data to the correct epoch (previous epoch when rewards are calculated)

API Integration

  • Pool History Query: GetPoolHistory query now returns structured PoolHistory with epoch data
  • Backward Compatibility: All existing queries and responses remain unchanged

Configuration

New configuration option:

  • store-history: In spo-state, we can enable/disable history by setting up this boolean.

NOTE:

  • rewards and fees (SPO rewards data) are one epoch off from blockfrost's response. And will be fixed in upcoming PRs.

…lock producing by vrf_key_hashes

- Add blocks to keep track of temporary block number and minter's vrf_key_hash, because BlockFees message has no clue of vrf_key_hash

- Include fees to EpochActivityMessage

- Update test cases to check fee

- update vrf_key_hashes state name to blocks_minted for better readability
- add active_stakes to store active_stakes for few epochs by spo key hash
- remove active_stakes_history state
- update get_pools_active_stakes function and handle_spdd function to accept new state and to update history
- Updated state management to conditionally store epoch history based on configuration.
- use  and updated  for performance
- Introduced VrfVkeyHashEpochState to track blocks minted and fees for vrf vkey hashes.
- Updated State struct to conditionally store vrf vkey hashes history based on configuration.
- Added methods to retrieve epoch state for vrf vkey hashes.
- Improved logging for storing history configuration.
- Removed unnecessary Arc wrapping for the spo_stakes variable.
- Simplified the parallel processing of stakes by using for_each instead of for_each_init.
- Improved readability and performance of the stake aggregation logic.
- Introduced SPORewardState and SPORewardStateMessage structs to handle reward data for stake pools.
- Updated generate_spdd function to return both stake distribution and reward state.
- Implemented SPORewardStatePublisher for publishing reward state messages.
- Added PoolEpochHistory struct to store detailed information about each pool's performance per epoch, including blocks minted, active stake, delegators count, rewards, and fees.
- Updated PoolHistory struct to include a vector of PoolEpochHistory.
- Enhanced SPOState to retrieve pool history based on the new structure and handle SPO reward state updates accordingly.
- Implemented query handling for pool history in the Blockfrost API.
…save blocks_minted count by spo

- Added active_delegators_count to DelegatedStake to track active delegators.
- Improved handling of epochs history and added functionality to retrieve SPO from vrf_key_hash.
- Added calculation of total active stake to determine the active size ratio for each epoch.
- Introduced PoolEpochStateRest struct to serialize epoch state data for REST responses.
- Updated the pool history query to convert PoolEpochState to PoolEpochStateRest, enhancing the API's response structure.
- Introduced SPORewardsMessage struct to encapsulate rewards data for pool operators.
- Implemented SPORewardsPublisher for publishing SPO rewards messages in accounts-state.
- Added subscription handling for SPO rewards messages in SPOState.
- Corrected the comment in the publish_spo_rewards method to accurately reflect its purpose of publishing SPO rewards.
- Added a note clarifying that the calculated SPO rewards data is one epoch off compared to blockfrost's response.
- Added functionality to retrieve the latest epoch from the epoch state.
- Updated pool history query to filter out epoch states that are greater than or equal to the latest epoch, ensuring accurate historical data retrieval.
@golddydev golddydev changed the title feat: prepare state for pools history feat: make pools history endpoint Aug 26, 2025
@golddydev golddydev marked this pull request as ready for review August 26, 2025 09:20
Copy link
Collaborator

@whankinsiv whankinsiv left a comment

Choose a reason for hiding this comment

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

Looks good. Please check my comments regarding using more descriptive names, computing active_size in the REST handler, and rollback / pruning logic which should be handled by StateHistory

pub epoch: u64,
pub blocks_minted: u64,
pub active_stake: u64,
pub active_size: RationalNumber,
Copy link
Collaborator

Choose a reason for hiding this comment

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

active_size could be computed in the REST handler if total_active_stake was stored by epoch.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well, that is why I used RationalNumber, decimal is only calculated on demand (rest handler)

Copy link
Collaborator

Choose a reason for hiding this comment

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

The current design is storing total_active_stake for each PoolEpochState as the denominator when this RationalNumber could be constructed in the REST handler.

@@ -14,7 +14,7 @@ pub struct State {
current_epoch: u64,

// Map of counts by VRF key hashes
vrf_vkey_hashes: HashMap<KeyHash, usize>,
blocks_minted: HashMap<KeyHash, usize>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice. I found the previous name confusing as well.

@@ -37,29 +44,35 @@ impl BlockState {
epoch: u64,
spos: HashMap<Vec<u8>, PoolRegistration>,
pending_deregistrations: HashMap<u64, Vec<Vec<u8>>>,
vrf_key_hashes: HashMap<Vec<u8>, Vec<u8>>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's difficult at a quick glance to determine what is actually being stored here. Consider providing a more descriptive name.

Copy link
Collaborator Author

@golddydev golddydev Aug 27, 2025

Choose a reason for hiding this comment

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

Change that to vrf_key_to_pool_id_map

29cb3fd

…rds publisher

- Updated variable names from  to  for better clarity in the context of handling and publishing SPO rewards.
- Adjusted comments to accurately describe the purpose of the variables and their relation to previous epochs.
- Renamed  to  in BlockState to better reflect its functionality.
@golddydev golddydev requested a review from whankinsiv August 27, 2025 08:55
@golddydev
Copy link
Collaborator Author

I am going to use StateHistory for states in spo-states module in the upcoming PR.
(BlockState with block-based StateHistory and epochs_history and active_stakes, total_blocks_minted_history by epoch-based StateHistory)

@@ -19,6 +19,9 @@ pub struct RewardsResult {

/// Rewards to be paid
pub rewards: Vec<(RewardAccount, Lovelace)>,

/// SPO rewards
pub spors: Vec<(KeyHash, SPORewards)>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

+1 for spo_rewards being a better name

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@golddydev golddydev closed this Aug 27, 2025
@golddydev
Copy link
Collaborator Author

Continue with #125

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.

3 participants