diff --git a/crates/cardano/src/estart/reset.rs b/crates/cardano/src/estart/reset.rs index 53ac8509..82270426 100644 --- a/crates/cardano/src/estart/reset.rs +++ b/crates/cardano/src/estart/reset.rs @@ -3,11 +3,13 @@ use std::sync::Arc; use dolos_core::{ChainError, Genesis, NsKey}; use pallas::ledger::primitives::Epoch; use serde::{Deserialize, Serialize}; +use tracing::debug; use crate::{ estart::{AccountId, PoolId, WorkContext}, - pots::{apply_delta, PotDelta, Pots}, - AccountState, CardanoDelta, EpochState, EraTransition, FixedNamespace as _, PoolState, + pallas_ratio, + pots::{self, apply_delta, EpochIncentives, Eta, PotDelta, Pots}, + ratio, AccountState, CardanoDelta, EpochState, EraTransition, FixedNamespace as _, PoolState, CURRENT_EPOCH_KEY, }; @@ -84,6 +86,7 @@ impl dolos_core::EntityDelta for PoolTransition { pub struct EpochTransition { new_epoch: Epoch, new_pots: Pots, + new_incentives: EpochIncentives, era_transition: Option, #[serde(skip)] @@ -113,6 +116,7 @@ impl dolos_core::EntityDelta for EpochTransition { entity.number = self.new_epoch; entity.initial_pots = self.new_pots.clone(); + entity.incentives = self.new_incentives.clone(); entity.rolling.default_transition(self.new_epoch); entity.pparams.default_transition(self.new_epoch); @@ -135,6 +139,79 @@ impl dolos_core::EntityDelta for EpochTransition { } } +fn define_eta(genesis: &Genesis, epoch: &EpochState) -> Result { + if epoch.pparams.mark().is_none_or(|x| x.is_byron()) { + return Ok(ratio!(1)); + } + + let blocks_minted = epoch.rolling.mark().map(|x| x.blocks_minted); + + let Some(blocks_minted) = blocks_minted else { + // TODO: check if returning eta = 1 on epoch 0 is what the specs says. + return Ok(ratio!(1)); + }; + + let f_param = genesis + .shelley + .active_slots_coeff + .ok_or(ChainError::GenesisFieldMissing( + "active_slots_coeff".to_string(), + ))?; + + let d_param = epoch.pparams.mark().unwrap().ensure_d()?; + let epoch_length = epoch.pparams.mark().unwrap().ensure_epoch_length()?; + + let eta = pots::calculate_eta( + blocks_minted as u64, + pallas_ratio!(d_param), + f_param, + epoch_length, + ); + + Ok(eta) +} + +fn define_new_incentives( + ctx: &WorkContext, + new_pots: &Pots, +) -> Result { + let state = ctx.ended_state(); + + let pparams = state.pparams.unwrap_live(); + + if pparams.is_byron() { + debug!("no pot changes during Byron epoch"); + return Ok(EpochIncentives::neutral()); + } + + let rho_param = pparams.ensure_rho()?; + let tau_param = pparams.ensure_tau()?; + + let fee_ss = match state.rolling.mark() { + Some(rolling) => rolling.gathered_fees, + None => 0, + }; + + let eta = define_eta(&ctx.genesis, state)?; + + let incentives = pots::epoch_incentives( + new_pots.reserves, + fee_ss, + pallas_ratio!(rho_param), + pallas_ratio!(tau_param), + eta, + ); + + debug!( + %incentives.total, + %incentives.treasury_tax, + %incentives.available_rewards, + "defined new incentives" + ); + + Ok(incentives) +} + pub fn define_new_pots(ctx: &super::WorkContext) -> Pots { let epoch = ctx.ended_state(); @@ -171,7 +248,7 @@ pub fn define_new_pots(ctx: &super::WorkContext) -> Pots { .unwrap_or(0), }; - let pots = apply_delta(epoch.initial_pots.clone(), &end.epoch_incentives, &delta); + let pots = apply_delta(epoch.initial_pots.clone(), &epoch.incentives, &delta); tracing::debug!( rewards = pots.rewards, @@ -179,7 +256,7 @@ pub fn define_new_pots(ctx: &super::WorkContext) -> Pots { treasury = pots.treasury, fees = pots.fees, utxos = pots.utxos, - "pots after reset" + "defined new pots" ); if !pots.is_consistent(epoch.initial_pots.max_supply()) { @@ -233,9 +310,13 @@ impl super::BoundaryVisitor for BoundaryVisitor { ctx.add_delta(delta); } + let new_pots = define_new_pots(ctx); + let new_incentives = define_new_incentives(ctx, &new_pots)?; + ctx.deltas.add_for_entity(EpochTransition { new_epoch: ctx.starting_epoch_no(), - new_pots: define_new_pots(ctx), + new_pots, + new_incentives, era_transition: ctx.ended_state().pparams.era_transition(), genesis: Some(ctx.genesis.clone()), }); diff --git a/crates/cardano/src/ewrap/wrapup.rs b/crates/cardano/src/ewrap/wrapup.rs index e0e520aa..b70a109a 100644 --- a/crates/cardano/src/ewrap/wrapup.rs +++ b/crates/cardano/src/ewrap/wrapup.rs @@ -1,6 +1,6 @@ use crate::{ - AccountState, CardanoDelta, EndStats, EpochState, FixedNamespace as _, PoolHash, PoolState, - CURRENT_EPOCH_KEY, + pots::EpochIncentives, AccountState, CardanoDelta, EndStats, EpochState, FixedNamespace as _, + PoolHash, PoolState, CURRENT_EPOCH_KEY, }; use dolos_core::{ChainError, NsKey}; use serde::{Deserialize, Serialize}; @@ -122,13 +122,10 @@ fn define_end_stats(ctx: &super::BoundaryWork) -> EndStats { let proposal_valid_refunds = define_proposal_valid_refunds(ctx); let proposal_invalid_refunds = proposal_total_refunds - proposal_valid_refunds; - let incentives = ctx.rewards.incentives(); - EndStats { pool_deposit_count: ctx.new_pools.len() as u64, pool_refund_count: pool_refund_count as u64, pool_invalid_refund_count: pool_invalid_refund_count as u64, - epoch_incentives: incentives.clone(), effective_rewards: ctx.rewards.applied_effective(), unspendable_rewards: ctx.rewards.applied_unspendable(), proposal_refunds: proposal_valid_refunds, @@ -136,6 +133,7 @@ fn define_end_stats(ctx: &super::BoundaryWork) -> EndStats { // TODO: deprecate __drep_deposits: 0, __drep_refunds: 0, + __epoch_incentives: EpochIncentives::default(), } } diff --git a/crates/cardano/src/genesis/mod.rs b/crates/cardano/src/genesis/mod.rs index 8da74557..6e0bd9ef 100644 --- a/crates/cardano/src/genesis/mod.rs +++ b/crates/cardano/src/genesis/mod.rs @@ -4,8 +4,10 @@ use dolos_core::{ }; use crate::{ - pots::Pots, utils::nonce_stability_window, EpochState, EpochValue, EraBoundary, EraSummary, - Lovelace, Nonces, PParamsSet, RollingStats, CURRENT_EPOCH_KEY, + pots::{EpochIncentives, Pots}, + utils::nonce_stability_window, + EpochState, EpochValue, EraBoundary, EraSummary, Lovelace, Nonces, PParamsSet, RollingStats, + CURRENT_EPOCH_KEY, }; mod staking; @@ -71,6 +73,7 @@ pub fn bootstrap_epoch( previous_nonce_tail: None, number: 0, rolling: EpochValue::with_live(0, RollingStats::default()), + incentives: EpochIncentives::default(), end: None, }; diff --git a/crates/cardano/src/model.rs b/crates/cardano/src/model.rs index 6013ac33..2a718211 100644 --- a/crates/cardano/src/model.rs +++ b/crates/cardano/src/model.rs @@ -1460,8 +1460,9 @@ pub struct EndStats { #[n(2)] pub pool_invalid_refund_count: u64, + // TODO: deprecate #[n(3)] - pub epoch_incentives: EpochIncentives, + pub __epoch_incentives: EpochIncentives, #[n(4)] pub effective_rewards: u64, @@ -1495,6 +1496,9 @@ pub struct EpochState { #[n(2)] pub rolling: EpochValue, + #[n(3)] + pub incentives: EpochIncentives, + #[n(9)] pub pparams: EpochValue, diff --git a/crates/cardano/src/pots.rs b/crates/cardano/src/pots.rs index 16f8276f..7c1ec098 100644 --- a/crates/cardano/src/pots.rs +++ b/crates/cardano/src/pots.rs @@ -89,7 +89,19 @@ pub struct EpochIncentives { pub used_fees: u64, } -#[derive(Debug, Clone, Encode, Decode, Serialize, Deserialize)] +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, + } + } +} + +#[derive(Debug, Clone, Encode, Decode, Serialize, Deserialize, Default)] pub struct PotDelta { #[n(0)] pub produced_utxos: Lovelace, diff --git a/crates/cardano/src/rewards/mocking.rs b/crates/cardano/src/rewards/mocking.rs index 480aff87..2f5d0003 100644 --- a/crates/cardano/src/rewards/mocking.rs +++ b/crates/cardano/src/rewards/mocking.rs @@ -134,9 +134,9 @@ pub struct MockContext { /// Converted pool params for efficient lookup #[serde(skip)] pool_params_converted: HashMap, - /// Computed pot delta for the rewards calculation + /// Computed available rewards for the epoch #[serde(skip)] - incentives: Option, + available_rewards: Option, } impl MockContext { @@ -156,13 +156,15 @@ impl MockContext { context.pool_params_converted = converted; - context.incentives = Some(crate::pots::epoch_incentives( + let incentives = crate::pots::epoch_incentives( context.pots.reserves, context.epoch_fee_ss, pallas_ratio!(context.pparams.ensure_rho()?), pallas_ratio!(context.pparams.ensure_tau()?), pallas_ratio!(context.epoch_eta), - )); + ); + + context.available_rewards = Some(incentives.available_rewards); Ok(context) } @@ -196,10 +198,9 @@ impl MockContext { } impl super::RewardsContext for MockContext { - fn incentives(&self) -> &EpochIncentives { - self.incentives - .as_ref() - .expect("epoch incentives not computed") + fn available_rewards(&self) -> u64 { + self.available_rewards + .expect("available rewards not computed") } fn pots(&self) -> &Pots { diff --git a/crates/cardano/src/rewards/mod.rs b/crates/cardano/src/rewards/mod.rs index f4207d28..fcafc5b0 100644 --- a/crates/cardano/src/rewards/mod.rs +++ b/crates/cardano/src/rewards/mod.rs @@ -4,11 +4,7 @@ use dolos_core::ChainError; use pallas::ledger::primitives::StakeCredential; use tracing::debug; -use crate::{ - pallas_extras, pallas_ratio, - pots::{EpochIncentives, Pots}, - Lovelace, PParamsSet, PoolHash, PoolParams, -}; +use crate::{pallas_extras, pallas_ratio, pots::Pots, Lovelace, PParamsSet, PoolHash, PoolParams}; mod formulas; @@ -145,7 +141,6 @@ impl Reward { #[derive(Debug)] pub struct RewardMap { - incentives: EpochIncentives, pending: HashMap, applied_effective: u64, applied_unspendable: u64, @@ -179,7 +174,6 @@ impl std::fmt::Display for RewardMap { impl Default for RewardMap { fn default() -> Self { Self { - incentives: EpochIncentives::default(), pending: HashMap::new(), applied_effective: 0, applied_unspendable: 0, @@ -191,7 +185,6 @@ impl Default for RewardMap { impl Clone for RewardMap { fn clone(&self) -> Self { Self { - incentives: self.incentives.clone(), pending: self.pending.clone(), applied_effective: self.applied_effective, applied_unspendable: self.applied_unspendable, @@ -201,9 +194,8 @@ impl Clone for RewardMap { } impl RewardMap { - fn new(incentives: EpochIncentives) -> Self { + fn new() -> Self { Self { - incentives, pending: HashMap::new(), applied_effective: 0, applied_unspendable: 0, @@ -285,10 +277,6 @@ impl RewardMap { } } - pub fn incentives(&self) -> &EpochIncentives { - &self.incentives - } - pub fn applied_effective(&self) -> u64 { assert!(self.pending.is_empty()); self.applied_effective @@ -301,7 +289,7 @@ impl RewardMap { } pub trait RewardsContext { - fn incentives(&self) -> &EpochIncentives; + fn available_rewards(&self) -> u64; fn pots(&self) -> &Pots; fn pre_allegra(&self) -> bool; @@ -330,7 +318,7 @@ pub trait RewardsContext { } pub fn define_rewards(ctx: &C) -> Result, ChainError> { - let mut map = RewardMap::::new(ctx.incentives().clone()); + let mut map = RewardMap::::new(); for pool in ctx.iter_all_pools() { let pool_params = ctx.pool_params(pool); @@ -347,7 +335,7 @@ pub fn define_rewards(ctx: &C) -> Result, ChainE let live_pledge = ctx.live_pledge(pool, &owners); let circulating_supply = ctx.pots().circulating(); let pool_stake = ctx.pool_stake(pool); - let epoch_rewards = ctx.incentives().available_rewards; + let epoch_rewards = ctx.available_rewards(); let total_active_stake = ctx.active_stake(); let epoch_blocks = ctx.epoch_blocks(); let pool_blocks = ctx.pool_blocks(pool); diff --git a/crates/cardano/src/rupd/loading.rs b/crates/cardano/src/rupd/loading.rs index 572b70fc..aaef455e 100644 --- a/crates/cardano/src/rupd/loading.rs +++ b/crates/cardano/src/rupd/loading.rs @@ -1,90 +1,13 @@ use dolos_core::{ChainError, Domain, Genesis, StateStore}; use pallas::ledger::primitives::StakeCredential; -use tracing::{debug, trace}; +use tracing::trace; use crate::{ - pallas_ratio, - pots::{self, EpochIncentives, Eta, Pots}, - ratio, + pots::Pots, rupd::{RupdWork, StakeSnapshot}, - AccountState, EpochState, EraProtocol, FixedNamespace as _, PParamsSet, PoolHash, PoolParams, - PoolState, + AccountState, EraProtocol, FixedNamespace as _, PParamsSet, PoolHash, PoolParams, PoolState, }; -fn define_eta(genesis: &Genesis, epoch: &EpochState) -> Result { - if epoch.pparams.mark().is_none_or(|x| x.is_byron()) { - return Ok(ratio!(1)); - } - - let blocks_minted = epoch.rolling.mark().map(|x| x.blocks_minted); - - let Some(blocks_minted) = blocks_minted else { - // TODO: check if returning eta = 1 on epoch 0 is what the specs says. - return Ok(ratio!(1)); - }; - - let f_param = genesis - .shelley - .active_slots_coeff - .ok_or(ChainError::GenesisFieldMissing( - "active_slots_coeff".to_string(), - ))?; - - let d_param = epoch.pparams.mark().unwrap().ensure_d()?; - let epoch_length = epoch.pparams.mark().unwrap().ensure_epoch_length()?; - - let eta = pots::calculate_eta( - blocks_minted as u64, - pallas_ratio!(d_param), - f_param, - epoch_length, - ); - - Ok(eta) -} - -fn neutral_incentives() -> EpochIncentives { - EpochIncentives { - total: 0, - treasury_tax: 0, - available_rewards: 0, - used_fees: 0, - } -} - -fn define_epoch_incentives( - genesis: &Genesis, - state: &EpochState, - reserves: u64, -) -> Result { - let pparams = state.pparams.unwrap_live(); - - if pparams.is_byron() { - debug!("no pot changes during Byron epoch"); - return Ok(neutral_incentives()); - } - - let rho_param = pparams.ensure_rho()?; - let tau_param = pparams.ensure_tau()?; - - let fee_ss = match state.rolling.mark() { - Some(rolling) => rolling.gathered_fees, - None => 0, - }; - - let eta = define_eta(genesis, state)?; - - let incentives = pots::epoch_incentives( - reserves, - fee_ss, - pallas_ratio!(rho_param), - pallas_ratio!(tau_param), - eta, - ); - - Ok(incentives) -} - impl StakeSnapshot { fn track_stake( &mut self, @@ -223,24 +146,15 @@ impl RupdWork { let pots = epoch.initial_pots.clone(); - let incentives = define_epoch_incentives(genesis, &epoch, pots.reserves)?; - let chain = crate::load_era_summary::(state)?; - debug!( - %incentives.total, - %incentives.treasury_tax, - %incentives.available_rewards, - "defined pot delta" - ); - let mut work = RupdWork { chain, current_epoch, max_supply, pparams: epoch.pparams.mark().cloned(), pots, - incentives, + available_rewards: epoch.incentives.available_rewards, snapshot: StakeSnapshot::default(), }; @@ -255,8 +169,8 @@ impl RupdWork { } impl crate::rewards::RewardsContext for RupdWork { - fn incentives(&self) -> &EpochIncentives { - &self.incentives + fn available_rewards(&self) -> u64 { + self.available_rewards } fn pots(&self) -> &Pots { diff --git a/crates/cardano/src/rupd/mod.rs b/crates/cardano/src/rupd/mod.rs index 75054ef1..4854181c 100644 --- a/crates/cardano/src/rupd/mod.rs +++ b/crates/cardano/src/rupd/mod.rs @@ -8,10 +8,8 @@ use pallas::ledger::primitives::StakeCredential; use tracing::{info, instrument}; use crate::{ - pots::{EpochIncentives, Pots}, - rewards::RewardMap, - AccountState, ChainSummary, EpochValue, PParamsSet, PoolHash, PoolSnapshot, PoolState, - StakeLog, + pots::Pots, rewards::RewardMap, AccountState, ChainSummary, EpochValue, PParamsSet, PoolHash, + PoolSnapshot, PoolState, StakeLog, }; pub mod loading; @@ -100,7 +98,7 @@ pub struct RupdWork { pub current_epoch: u64, pub snapshot: StakeSnapshot, pub pots: Pots, - pub incentives: EpochIncentives, + pub available_rewards: u64, pub max_supply: u64, pub chain: ChainSummary, pub pparams: Option,