Tokenomics rewards (#802)

* Initial analysis

* Implement core rewards distribution

* Tests and refactoring for better testability

* Feature gate ts-rs in mixnet-contract

* No more floats

* Fix performance calculation

* Add migration

* Bandwidth fix, reduce inflation pool size

* Update tokenomics

* Refactor rewarding and replace num crate

* Address review comments

* Cleanup, better test values

* Simplify formula

* Cleanup, add rewarding formulas to README

* Address review comments

* Cleanup rebase

* [ci skip] Generate TS types

* fmt

Co-authored-by: Drazen Urch <durch@users.noreply.guthub.com>
This commit is contained in:
Drazen Urch
2021-11-10 11:14:06 +01:00
committed by GitHub
parent 8bcc931d9b
commit eae4276381
31 changed files with 1108 additions and 136 deletions
+2 -2
View File
@@ -75,7 +75,7 @@ impl<C> ValidatorCacheRefresher<C> {
{
let (mixnodes, gateways) = tokio::try_join!(
self.nymd_client.get_mixnodes(),
self.nymd_client.get_gateways()
self.nymd_client.get_gateways(),
)?;
let state_params = self.nymd_client.get_state_params().await?;
@@ -83,7 +83,7 @@ impl<C> ValidatorCacheRefresher<C> {
info!(
"Updating validator cache. There are {} mixnodes and {} gateways",
mixnodes.len(),
gateways.len()
gateways.len(),
);
self.cache
+28
View File
@@ -93,6 +93,34 @@ impl<C> Client<C> {
Ok(time)
}
pub(crate) async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.0.read().await.get_reward_pool().await?)
}
pub(crate) async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.0.read().await.get_circulating_supply().await?)
}
pub(crate) async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.0.read().await.get_sybil_resistance_percent().await?)
}
pub(crate) async fn get_epoch_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.0.read().await.get_epoch_reward_percent().await?)
}
pub(crate) async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
+54 -4
View File
@@ -12,6 +12,7 @@ use crate::storage::models::{
};
use crate::storage::ValidatorApiStorage;
use log::{error, info};
use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{ExecuteMsg, IdentityKey};
use std::collections::HashMap;
use std::convert::TryInto;
@@ -48,6 +49,19 @@ pub(crate) struct MixnodeToReward {
/// Total number of individual addresses that have delegated to this particular node
pub(crate) total_delegations: usize,
/// Node absolute uptime over total active set uptime
params: Option<NodeRewardParams>,
}
impl MixnodeToReward {
/// Somewhat clumsy way of feature gatting tokenomics payments. In a tokenomics scenario this will never be None at reward time. We levarage that to Into a different ExecuteMsg variant
fn params(&self) -> Option<NodeRewardParams> {
if cfg!(feature = "tokenomics") {
self.params
} else {
None
}
}
}
pub(crate) struct FailedMixnodeRewardChunkDetails {
@@ -62,9 +76,16 @@ pub(crate) struct FailureData {
impl<'a> From<&'a MixnodeToReward> for ExecuteMsg {
fn from(node: &MixnodeToReward) -> Self {
ExecuteMsg::RewardMixnode {
identity: node.identity.clone(),
uptime: node.uptime.u8() as u32,
if let Some(params) = node.params() {
ExecuteMsg::RewardMixnodeV2 {
identity: node.identity.clone(),
params,
}
} else {
ExecuteMsg::RewardMixnode {
identity: node.identity.clone(),
uptime: node.uptime.u8() as u32,
}
}
}
}
@@ -185,12 +206,13 @@ impl Rewarder {
// by people hesitating to delegate to nodes without them and thus those nodes disappearing
// from the active set (once introduced)
let mixnode_delegators = self.produce_active_mixnode_delegators_map().await?;
let state = self.nymd_client.get_state_params().await?;
// 1. go through all active mixnodes
// 2. filter out nodes that are currently not in the active set (as `mixnode_delegators` was obtained by
// querying the validator)
// 3. determine uptime and attach delegators count
let eligible_nodes = active_mixnodes
let mut eligible_nodes: Vec<MixnodeToReward> = active_mixnodes
.iter()
.filter_map(|mix| {
mixnode_delegators
@@ -199,11 +221,39 @@ impl Rewarder {
identity: mix.identity.clone(),
uptime: mix.last_day,
total_delegations,
params: None,
})
})
.filter(|node| node.uptime.u8() > 0)
.collect();
if cfg!(feature = "tokenomics") {
let reward_pool = self.nymd_client.get_reward_pool().await?;
let circulating_supply = self.nymd_client.get_circulating_supply().await?;
let sybil_resistance_percent = self.nymd_client.get_sybil_resistance_percent().await?;
let epoch_reward_percent = self.nymd_client.get_epoch_reward_percent().await?;
let k = state.mixnode_active_set_size;
let period_reward_pool = (reward_pool / 100) * epoch_reward_percent as u128;
info!("Rewarding pool stats");
info!("-- Reward pool: {} unym", reward_pool);
info!("---- Epoch reward pool: {} unym", period_reward_pool);
info!("-- Circulating supply: {} unym", circulating_supply);
for mix in eligible_nodes.iter_mut() {
mix.params = Some(NodeRewardParams::new(
period_reward_pool,
k.into(),
0,
circulating_supply,
mix.uptime.u8().into(),
sybil_resistance_percent,
));
}
} else {
info!("Tokenomics feature is OFF");
}
Ok(eligible_nodes)
}