Skip to content

Commit

Permalink
WIP (not compiling yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
brentstone committed Dec 7, 2023
1 parent 9562c16 commit b888ba1
Show file tree
Hide file tree
Showing 8 changed files with 6,013 additions and 5,848 deletions.
6,532 changes: 1,460 additions & 5,072 deletions proof_of_stake/src/lib.rs

Large diffs are not rendered by default.

457 changes: 457 additions & 0 deletions proof_of_stake/src/queries.rs

Large diffs are not rendered by default.

376 changes: 375 additions & 1 deletion proof_of_stake/src/rewards.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
//! PoS rewards distribution.
use std::collections::{HashMap, HashSet};

use namada_core::ledger::storage_api::collections::lazy_map::NestedSubKey;
use namada_core::ledger::storage_api::token::credit_tokens;
use namada_core::ledger::storage_api::{
self, ResultExt, StorageRead, StorageWrite,
};
use namada_core::types::address::{self, Address};
use namada_core::types::dec::Dec;
use namada_core::types::token::Amount;
use namada_core::types::storage::Epoch;
use namada_core::types::token::{self, Amount};
use namada_core::types::uint::{Uint, I256};
use thiserror::Error;

use crate::storage::{
consensus_validator_set_handle, get_last_reward_claim_epoch,
read_pos_params, read_validator_stake, rewards_accumulator_handle,
validator_commission_rate_handle, validator_rewards_products_handle,
validator_state_handle,
};
use crate::types::{into_tm_voting_power, BondId, ValidatorState, VoteInfo};
use crate::{
bond_amounts_for_rewards, get_total_consensus_stake, storage_key,
InflationError, PosParams,
};

/// This is equal to 0.01.
const MIN_PROPOSER_REWARD: Dec =
Dec(I256(Uint([10000000000u64, 0u64, 0u64, 0u64])));
Expand Down Expand Up @@ -99,3 +120,356 @@ impl PosRewardsCalculator {
/ 3u64
}
}

/// Tally a running sum of the fraction of rewards owed to each validator in
/// the consensus set. This is used to keep track of the rewards due to each
/// consensus validator over the lifetime of an epoch.
pub fn log_block_rewards<S>(
storage: &mut S,
epoch: impl Into<Epoch>,
proposer_address: &Address,
votes: Vec<VoteInfo>,
) -> storage_api::Result<()>
where
S: StorageRead + StorageWrite,
{
// The votes correspond to the last committed block (n-1 if we are
// finalizing block n)

let epoch: Epoch = epoch.into();
let params = read_pos_params(storage)?;
let consensus_validators = consensus_validator_set_handle().at(&epoch);

// Get total stake of the consensus validator set
let total_consensus_stake =
get_total_consensus_stake(storage, epoch, &params)?;

// 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 = token::Amount::zero();
for VoteInfo {
validator_address,
validator_vp,
} in votes
{
if validator_vp == 0 {
continue;
}
// Ensure that the validator is not currently jailed or other
let state = validator_state_handle(&validator_address)
.get(storage, epoch, &params)?;
if state != Some(ValidatorState::Consensus) {
return Err(InflationError::ExpectedValidatorInConsensus(
validator_address,
state,
))
.into_storage_result();
}

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

// Ensure TM stake updates properly with a debug_assert
if cfg!(debug_assertions) {
debug_assert_eq!(
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 += stake_from_deltas;
}

// Get the block rewards coefficients (proposing, signing/voting,
// consensus set status)
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 = rewards_calculator
.get_reward_coeffs()
.map_err(InflationError::Rewards)
.into_storage_result()?;
tracing::debug!(
"PoS rewards coefficients {coeffs:?}, inputs: {rewards_calculator:?}."
);

// tracing::debug!(
// "TOTAL SIGNING STAKE (LOGGING BLOCK REWARDS) = {}",
// signing_stake
// );

// Compute the fractional block rewards for each consensus validator and
// update the reward accumulators
let consensus_stake_unscaled: Dec = total_consensus_stake.into();
let signing_stake_unscaled: Dec = total_signing_stake.into();
let mut values: HashMap<Address, Dec> = HashMap::new();
for validator in consensus_validators.iter(storage)? {
let (
NestedSubKey::Data {
key: stake,
nested_sub_key: _,
},
address,
) = validator?;

if stake.is_zero() {
continue;
}

let mut rewards_frac = Dec::zero();
let stake_unscaled: Dec = stake.into();
// tracing::debug!(
// "NAMADA VALIDATOR STAKE (LOGGING BLOCK REWARDS) OF EPOCH {} =
// {}", epoch, stake
// );

// Proposer reward
if address == *proposer_address {
rewards_frac += coeffs.proposer_coeff;
}
// Signer reward
if signer_set.contains(&address) {
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_unscaled / consensus_stake_unscaled);

// To be added to the rewards accumulator
values.insert(address, rewards_frac);
}
for (address, value) in values.into_iter() {
// Update the rewards accumulator
rewards_accumulator_handle().update(storage, address, |prev| {
prev.unwrap_or_default() + value
})?;
}

Ok(())
}

#[derive(Clone, Debug)]
struct Rewards {
product: Dec,
commissions: token::Amount,
}

/// Update validator and delegators rewards products and mint the inflation
/// tokens into the PoS account.
/// Any left-over inflation tokens from rounding error of the sum of the
/// rewards is given to the governance address.
pub fn update_rewards_products_and_mint_inflation<S>(
storage: &mut S,
params: &PosParams,
last_epoch: Epoch,
num_blocks_in_last_epoch: u64,
inflation: token::Amount,
staking_token: &Address,
) -> storage_api::Result<()>
where
S: StorageRead + StorageWrite,
{
// Read the rewards accumulator and calculate the new rewards products
// for the previous epoch
let mut reward_tokens_remaining = inflation;
let mut new_rewards_products: HashMap<Address, Rewards> = HashMap::new();
let mut accumulators_sum = Dec::zero();
for acc in rewards_accumulator_handle().iter(storage)? {
let (validator, value) = acc?;
accumulators_sum += value;

// Get reward token amount for this validator
let fractional_claim = value / num_blocks_in_last_epoch;
let reward_tokens = fractional_claim * inflation;

// Get validator stake at the last epoch
let stake = Dec::from(read_validator_stake(
storage, params, &validator, last_epoch,
)?);

let commission_rate = validator_commission_rate_handle(&validator)
.get(storage, last_epoch, params)?
.expect("Should be able to find validator commission rate");

// Calculate the reward product from the whole validator stake and take
// out the commissions. Because we're using the whole stake to work with
// a single product, we're also taking out commission on validator's
// self-bonds, but it is then included in the rewards claimable by the
// validator so they get it back.
let product =
(Dec::one() - commission_rate) * Dec::from(reward_tokens) / stake;

// Tally the commission tokens earned by the validator.
// TODO: think abt Dec rounding and if `new_product` should be used
// instead of `reward_tokens`
let commissions = commission_rate * reward_tokens;

new_rewards_products.insert(
validator,
Rewards {
product,
commissions,
},
);

reward_tokens_remaining -= reward_tokens;
}
for (
validator,
Rewards {
product,
commissions,
},
) in new_rewards_products
{
validator_rewards_products_handle(&validator)
.insert(storage, last_epoch, product)?;
// The commissions belong to the validator
add_rewards_to_counter(storage, &validator, &validator, commissions)?;
}

// Mint tokens to the PoS account for the last epoch's inflation
let pos_reward_tokens = inflation - reward_tokens_remaining;
tracing::info!(
"Minting tokens for PoS rewards distribution into the PoS account. \
Amount: {}. Total inflation: {}, number of blocks in the last epoch: \
{num_blocks_in_last_epoch}, reward accumulators sum: \
{accumulators_sum}.",
pos_reward_tokens.to_string_native(),
inflation.to_string_native(),
);
credit_tokens(storage, staking_token, &address::POS, pos_reward_tokens)?;

if reward_tokens_remaining > token::Amount::zero() {
tracing::info!(
"Minting tokens remaining from PoS rewards distribution into the \
Governance account. Amount: {}.",
reward_tokens_remaining.to_string_native()
);
credit_tokens(
storage,
staking_token,
&address::GOV,
reward_tokens_remaining,
)?;
}

// Clear validator rewards accumulators
storage.delete_prefix(
// The prefix of `rewards_accumulator_handle`
&storage_key::consensus_validator_rewards_accumulator_key(),
)?;

Ok(())
}

/// Compute the current available rewards amount due only to existing bonds.
/// This does not include pending rewards held in the rewards counter due to
/// unbonds and redelegations.
pub fn compute_current_rewards_from_bonds<S>(
storage: &S,
source: &Address,
validator: &Address,
current_epoch: Epoch,
) -> storage_api::Result<token::Amount>
where
S: StorageRead,
{
if current_epoch == Epoch::default() {
// Nothing to claim in the first epoch
return Ok(token::Amount::zero());
}

let last_claim_epoch =
get_last_reward_claim_epoch(storage, source, validator)?;
if let Some(last_epoch) = last_claim_epoch {
if last_epoch == current_epoch {
// Already claimed in this epoch
return Ok(token::Amount::zero());
}
}

let mut reward_tokens = token::Amount::zero();

// Want to claim from `last_claim_epoch` to `current_epoch.prev()` since
// rewards are computed at the end of an epoch
let (claim_start, claim_end) = (
last_claim_epoch.unwrap_or_default(),
// Safe because of the check above
current_epoch.prev(),
);
let bond_amounts = bond_amounts_for_rewards(
storage,
&BondId {
source: source.clone(),
validator: validator.clone(),
},
claim_start,
claim_end,
)?;

let rewards_products = validator_rewards_products_handle(validator);
for (ep, bond_amount) in bond_amounts {
debug_assert!(ep >= claim_start);
debug_assert!(ep <= claim_end);
let rp = rewards_products.get(storage, &ep)?.unwrap_or_default();
let reward = rp * bond_amount;
reward_tokens += reward;
}

Ok(reward_tokens)
}

/// Add tokens to a rewards counter.
pub fn add_rewards_to_counter<S>(
storage: &mut S,
source: &Address,
validator: &Address,
new_rewards: token::Amount,
) -> storage_api::Result<()>
where
S: StorageRead + StorageWrite,
{
let key = storage_key::rewards_counter_key(source, validator);
let current_rewards =
storage.read::<token::Amount>(&key)?.unwrap_or_default();
storage.write(&key, current_rewards + new_rewards)
}

/// Take tokens from a rewards counter. Deletes the record after reading.
pub fn take_rewards_from_counter<S>(
storage: &mut S,
source: &Address,
validator: &Address,
) -> storage_api::Result<token::Amount>
where
S: StorageRead + StorageWrite,
{
let key = storage_key::rewards_counter_key(source, validator);
let current_rewards =
storage.read::<token::Amount>(&key)?.unwrap_or_default();
storage.delete(&key)?;
Ok(current_rewards)
}

/// Read the current token value in the rewards counter.
pub fn read_rewards_counter<S>(
storage: &S,
source: &Address,
validator: &Address,
) -> storage_api::Result<token::Amount>
where
S: StorageRead,
{
let key = storage_key::rewards_counter_key(source, validator);
Ok(storage.read::<token::Amount>(&key)?.unwrap_or_default())
}
Loading

0 comments on commit b888ba1

Please sign in to comment.