Skip to content

Commit

Permalink
Merge branch 'brent/pos-inflation-rewards' (#714)
Browse files Browse the repository at this point in the history
* brent/pos-inflation-rewards:
  pos: fix token conversion in rewards products
  pos: add debug log for rewards
  pos: fix token conversion in rewards calculation
  pos: improve inflation rewards error type
  pos: fix debug assertion checking validator voting power
  core/token: add unscaled decimal conversions
  test/e2e: set PoS param tm_votes_per_token = 0.1
  • Loading branch information
tzemanovic committed Apr 5, 2023
2 parents 685c757 + 7faef3d commit e399494
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 31 deletions.
17 changes: 17 additions & 0 deletions core/src/types/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,23 @@ impl Amount {
micro: change as u64,
}
}

/// Convert the amount to [`Decimal`] ignoring its scale (i.e. as an integer
/// in micro units).
pub fn as_dec_unscaled(&self) -> Decimal {
Into::<Decimal>::into(self.micro)
}

/// Convert from a [`Decimal`] that's not scaled (i.e. an integer
/// in micro units).
///
/// # Panics
///
/// Panics if the given decimal is not an integer that fits `u64`.
pub fn from_dec_unscaled(micro: Decimal) -> Self {
let res = micro.to_u64().unwrap();
Self { micro: res }
}
}

impl serde::Serialize for Amount {
Expand Down
2 changes: 1 addition & 1 deletion genesis/e2e-tests-single-node.toml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ pipeline_len = 2
# for a fault in epoch 'n' up through epoch 'n + unbonding_len'.
unbonding_len = 3
# Votes per fundamental staking token (namnam)
tm_votes_per_token = 1
tm_votes_per_token = 0.1
# Reward for proposing a block.
block_proposer_reward = 0.125
# Reward for voting on a block.
Expand Down
59 changes: 32 additions & 27 deletions proof_of_stake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use namada_core::ledger::storage_api::collections::lazy_map::{
use namada_core::ledger::storage_api::collections::{LazyCollection, LazySet};
use namada_core::ledger::storage_api::token::credit_tokens;
use namada_core::ledger::storage_api::{
self, OptionExt, StorageRead, StorageWrite,
self, OptionExt, ResultExt, StorageRead, StorageWrite,
};
use namada_core::types::address::{Address, InternalAddress};
use namada_core::types::key::{
Expand Down Expand Up @@ -93,8 +93,8 @@ pub enum GenesisError {
#[allow(missing_docs)]
#[derive(Error, Debug)]
pub enum InflationError {
#[error("Error")]
Error,
#[error("Error in calculating rewards: {0}")]
Rewards(rewards::RewardsError),
}

#[allow(missing_docs)]
Expand Down Expand Up @@ -2570,7 +2570,7 @@ where
let consensus_validators = consensus_validator_set_handle().at(&epoch);

// Get total stake of the consensus validator set
let mut total_consensus_stake = 0_u64;
let mut total_consensus_stake = token::Amount::default();
for validator in consensus_validators.iter(storage)? {
let (
NestedSubKey::Data {
Expand All @@ -2579,13 +2579,13 @@ where
},
_address,
) = validator?;
total_consensus_stake += u64::from(amount);
total_consensus_stake += amount;
}

// Get set of signing validator addresses and the combined stake of
// these signers
let mut signer_set: HashSet<Address> = HashSet::new();
let mut total_signing_stake: u64 = 0;
let mut total_signing_stake = token::Amount::default();
for VoteInfo {
validator_address,
validator_vp,
Expand All @@ -2595,39 +2595,40 @@ where
continue;
}

let stake_from_deltas =
read_validator_stake(storage, &params, &validator_address, epoch)?
.unwrap_or_default();

// Ensure TM stake updates properly with a debug_assert
if cfg!(debug_assertions) {
let stake_from_deltas = read_validator_stake(
storage,
&params,
&validator_address,
epoch,
)?
.unwrap_or_default();
debug_assert_eq!(
stake_from_deltas,
token::Amount::from(validator_vp)
into_tm_voting_power(
params.tm_votes_per_token,
stake_from_deltas
),
i64::try_from(validator_vp).unwrap_or_default(),
);
}

signer_set.insert(validator_address);
total_signing_stake += validator_vp;
total_signing_stake += stake_from_deltas;
}

// Get the block rewards coefficients (proposing, signing/voting,
// consensus set status)
let consensus_stake: Decimal = total_consensus_stake.into();
let signing_stake: Decimal = total_signing_stake.into();
let rewards_calculator = PosRewardsCalculator {
proposer_reward: params.block_proposer_reward,
signer_reward: params.block_vote_reward,
signing_stake: total_signing_stake,
total_stake: total_consensus_stake,
};
let coeffs = match rewards_calculator.get_reward_coeffs() {
Ok(coeffs) => coeffs,
Err(_) => return Err(InflationError::Error.into()),
signing_stake: u64::from(total_signing_stake),
total_stake: u64::from(total_consensus_stake),
};
let coeffs = rewards_calculator
.get_reward_coeffs()
.map_err(InflationError::Rewards)
.into_storage_result()?;
tracing::debug!(
"PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}."
);

// println!(
// "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}",
Expand All @@ -2636,6 +2637,9 @@ where

// Compute the fractional block rewards for each consensus validator and
// update the reward accumulators
let consensus_stake_unscaled: Decimal =
total_consensus_stake.as_dec_unscaled();
let signing_stake_unscaled: Decimal = total_signing_stake.as_dec_unscaled();
let mut values: HashMap<Address, Decimal> = HashMap::new();
for validator in consensus_validators.iter(storage)? {
let (
Expand All @@ -2655,7 +2659,7 @@ where
}

let mut rewards_frac = Decimal::default();
let stake: Decimal = u64::from(stake).into();
let stake_unscaled: Decimal = stake.as_dec_unscaled();
// println!(
// "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} =
// {}", epoch, stake
Expand All @@ -2667,11 +2671,12 @@ where
}
// Signer reward
if signer_set.contains(&address) {
let signing_frac = stake / signing_stake;
let signing_frac = stake_unscaled / signing_stake_unscaled;
rewards_frac += coeffs.signer_coeff * signing_frac;
}
// Consensus validator reward
rewards_frac += coeffs.active_val_coeff * (stake / consensus_stake);
rewards_frac += coeffs.active_val_coeff
* (stake_unscaled / consensus_stake_unscaled);

// Update the rewards accumulator
let prev = rewards_accumulator_handle()
Expand Down
14 changes: 11 additions & 3 deletions proof_of_stake/src/rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ const MIN_PROPOSER_REWARD: Decimal = dec!(0.01);

/// Errors during rewards calculation
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum RewardsError {
/// number of votes is less than the threshold of 2/3
#[error(
"Insufficient votes, needed at least 2/3 of the total bonded stake"
"Insufficient votes. Got {signing_stake}, needed {votes_needed} (at \
least 2/3 of the total bonded stake)."
)]
InsufficentVotes,
InsufficientVotes {
votes_needed: u64,
signing_stake: u64,
},
/// rewards coefficients are not set
#[error("Rewards coefficients are not properly set.")]
CoeffsNotSet,
Expand Down Expand Up @@ -58,7 +63,10 @@ impl PosRewardsCalculator {
} = *self;

if signing_stake < votes_needed {
return Err(RewardsError::InsufficentVotes);
return Err(RewardsError::InsufficientVotes {
votes_needed,
signing_stake,
});
}

// Logic for determining the coefficients.
Expand Down

0 comments on commit e399494

Please sign in to comment.