Feature/batch delegator rewarding (#898)

* Unnrolled the loop into separate function

* Ugly way of saving rewarding status

* Initial way of rewarding next page of delegators

* Attribute passing

* Promoted transactions to directory

* Moved rewarding-related functionalities into separate file

* [ci skip] Generate TS types

* Better errors on double rewarding attempt

* Removed old rewarding call

* Test fixes

* Some cleanup

* Paged mixnode rewarding test + serde fixes

* Tests for delegator rewarding

* ExecuteMsg for MixDelegatorRewarding

* Made validator-api code compliable

with bunch of todo!() macros

* Removed Option wrapper from params in MixnodeToReward

* Calculating uptime for entire epoch

* Created shared MIXNODE_DELEGATORS_PAGE_LIMIT constant

* Using new rewarding messages in validator API

* cargo fmt

* Updated wallet state types

* Additional test for correct rewarding information

* Query for rewarding status

* Additional test regarding delegator rewarding

* Client methods for obtaining rewarding status

* Validator API checking for full rewarding

* Removed unused field from validator api config template

* Waiting for MINIMUM number of test routes

* Waiting initialisation_backoff in the early return case

* Fixes crash condition in validator API when calculating last day uptime

* Fixed typo

* Dealing with the case of rewarding mixnode with 0 uptime

* Removed temporary unwrap

* Guarding against 0-size rewarded/active sets

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2021-11-23 15:36:20 +00:00
committed by GitHub
parent 78a5dbbf05
commit 085761a9fb
27 changed files with 2990 additions and 2934 deletions
Generated
+1
View File
@@ -1791,6 +1791,7 @@ dependencies = [
"az",
"bytemuck",
"half",
"serde",
"typenum",
]
@@ -10,7 +10,7 @@ use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use url::Url;
@@ -181,6 +181,20 @@ impl<C> Client<C> {
Ok(self.nymd.get_current_rewarding_interval().await?)
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
@@ -14,10 +14,10 @@ use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmwasm_std::{Coin, Uint128};
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, PagedAllDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData, RewardingIntervalResponse,
StateParams,
LayerDistribution, MixNode, MixOwnershipResponse, MixnodeRewardingStatusResponse,
PagedAllDelegationsResponse, PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse, PagedReverseMixDelegationsResponse, QueryMsg, RawDelegationData,
RewardingIntervalResponse, StateParams,
};
use serde::Serialize;
use std::collections::HashMap;
@@ -230,6 +230,23 @@ impl<C> NymdClient<C> {
.await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetRewardingStatus {
mix_identity,
rewarding_interval_nonce,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
pub async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError>
where
C: CosmWasmClient + Sync,
+1 -1
View File
@@ -17,7 +17,7 @@ schemars = "0.8"
ts-rs = { version = "5.1", optional = true }
thiserror = "1.0"
network-defaults = { path = "../network-defaults" }
fixed = "1.1"
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
log = "0.4.14"
+3 -4
View File
@@ -8,6 +8,8 @@ pub mod mixnode;
mod msg;
mod types;
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedMixDelegationsResponse,
@@ -16,7 +18,4 @@ pub use delegation::{
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use types::{
IdentityKey, IdentityKeyRef, LayerDistribution, RewardingIntervalResponse, SphinxKey,
StateParams,
};
pub use types::*;
+94 -23
View File
@@ -14,6 +14,10 @@ use std::fmt::Display;
type U128 = fixed::types::U75F53; // u128 with 18 significant digits
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema)]
pub struct MixNode {
@@ -110,7 +114,7 @@ impl NodeRewardParams {
}
pub fn one_over_k(&self) -> U128 {
U128::from_num(1) / U128::from_num(self.k.u128())
ONE / U128::from_num(self.k.u128())
}
pub fn alpha(&self) -> U128 {
@@ -118,6 +122,90 @@ impl NodeRewardParams {
}
}
// cosmwasm's limited serde doesn't work with U128 directly
#[allow(non_snake_case)]
pub mod fixed_U128_as_string {
use super::U128;
use serde::de::Error;
use serde::Deserialize;
use std::str::FromStr;
pub fn serialize<S>(val: &U128, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let s = (*val).to_string();
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<U128, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
U128::from_str(&s).map_err(|err| {
D::Error::custom(format!(
"failed to deserialize U128 with its string representation - {}",
err
))
})
}
}
// everything required to reward delegator of given mixnode
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
pub struct DelegatorRewardParams {
node_reward_params: NodeRewardParams,
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
sigma: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
profit_margin: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
node_profit: U128,
}
impl DelegatorRewardParams {
pub fn new(mixnode_bond: &MixNodeBond, node_reward_params: NodeRewardParams) -> Self {
DelegatorRewardParams {
sigma: mixnode_bond.sigma(&node_reward_params),
profit_margin: mixnode_bond.profit_margin(),
node_profit: mixnode_bond.node_profit(&node_reward_params),
node_reward_params,
}
}
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.node_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward =
(ONE - self.profit_margin) * scaled_delegation_amount / self.sigma * self.node_profit;
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast delegator reward ({}) to u128, returning 0",
reward,
);
0u128
}
}
pub fn node_reward_params(&self) -> &NodeRewardParams {
&self.node_reward_params
}
}
#[derive(Debug)]
pub struct NodeRewardResult {
reward: U128,
@@ -228,14 +316,14 @@ impl MixNodeBond {
pub fn reward(&self, params: &NodeRewardParams) -> NodeRewardResult {
// Assuming uniform work distribution across the network this is one_over_k * k
let omega_k = U128::from_num(1u128);
let omega_k = ONE;
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.period_reward_pool()
* (sigma * omega_k + params.alpha() * lambda * sigma * params.k())
/ (U128::from_num(1) + params.alpha());
/ (ONE + params.alpha());
NodeRewardResult {
reward,
@@ -261,7 +349,7 @@ impl MixNodeBond {
};
let operator_base_reward = reward.reward.min(params.operator_cost());
let operator_reward = (self.profit_margin()
+ (U128::from_num(1) - self.profit_margin()) * reward.lambda / reward.sigma)
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0));
@@ -288,25 +376,8 @@ impl MixNodeBond {
}
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &NodeRewardParams) -> u128 {
let scaled_delegation_amount =
U128::from_num(delegation_amount.u128()) / U128::from_num(params.circulating_supply());
let delegator_reward = (U128::from_num(1) - self.profit_margin())
* scaled_delegation_amount
/ self.sigma(params)
* self.node_profit(params);
let reward = delegator_reward.max(U128::from_num(0));
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast delegator reward ({}) to u128, returning 0 - mixnode {}",
reward,
self.identity()
);
0u128
}
let reward_params = DelegatorRewardParams::new(self, *params);
reward_params.determine_delegation_reward(delegation_amount)
}
}
+10 -9
View File
@@ -37,15 +37,6 @@ pub enum ExecuteMsg {
rewarding_interval_nonce: u32,
},
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
uptime: u32,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
FinishMixnodeRewarding {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
@@ -59,6 +50,12 @@ pub enum ExecuteMsg {
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
RewardNextMixDelegators {
mix_identity: IdentityKey,
// nonce of the current rewarding interval
rewarding_interval_nonce: u32,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -103,6 +100,10 @@ pub enum QueryMsg {
GetCirculatingSupply {},
GetEpochRewardPercent {},
GetSybilResistancePercent {},
GetRewardingStatus {
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
+33 -17
View File
@@ -1,8 +1,9 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::DelegatorRewardParams;
use crate::Layer;
use cosmwasm_std::{Decimal, Uint128};
use cosmwasm_std::Uint128;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{self, Display, Formatter};
@@ -35,14 +36,13 @@ pub struct RewardingIntervalResponse {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StateParams {
pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
// so currently epoch_length is being unused and validator API performs rewarding
// based on its own epoch length config value. I guess that's fine for time being
// however, in the future, the contract constant should be controlling it instead.
// pub epoch_length: u32, // length of a rewarding epoch/interval, expressed in hours
pub minimum_mixnode_bond: Uint128, // minimum amount a mixnode must bond to get into the system
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
// number of mixnode that are going to get rewarded during current rewarding interval (k_m)
// based on overall demand for private bandwidth-
pub mixnode_rewarded_set_size: u32,
@@ -55,19 +55,8 @@ pub struct StateParams {
impl Display for StateParams {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "Contract state parameters: [ ")?;
write!(f, "epoch length: {}; ", self.epoch_length)?;
write!(f, "minimum mixnode bond: {}; ", self.minimum_mixnode_bond)?;
write!(f, "minimum gateway bond: {}; ", self.minimum_gateway_bond)?;
write!(
f,
"mixnode bond reward rate: {}; ",
self.mixnode_bond_reward_rate
)?;
write!(
f,
"mixnode delegation reward rate: {}; ",
self.mixnode_delegation_reward_rate
)?;
write!(
f,
"mixnode rewarded set size: {}",
@@ -81,6 +70,33 @@ impl Display for StateParams {
}
}
#[derive(Default, Debug, Serialize, Deserialize, PartialEq)]
pub struct RewardingResult {
pub operator_reward: Uint128,
pub total_delegator_reward: Uint128,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PendingDelegatorRewarding {
// keep track of the running rewarding results so we'd known how much was the operator and its delegators rewarded
pub running_results: RewardingResult,
pub next_start: String,
pub rewarding_params: DelegatorRewardParams,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum RewardingStatus {
Complete(RewardingResult),
PendingNextDelegatorPage(PendingDelegatorRewarding),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct MixnodeRewardingStatusResponse {
pub status: Option<RewardingStatus>,
}
// type aliases for better reasoning about available data
pub type IdentityKey = String;
pub type IdentityKeyRef<'a> = &'a str;
+6 -681
View File
@@ -2,45 +2,6 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ast_node"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e96d5444b02f3080edac8a144f6baf29b2fb6ff589ad4311559731a7c7529381"
dependencies = [
"darling",
"pmutil",
"proc-macro2",
"quote",
"swc_macros_common",
"syn",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "az"
version = "1.1.2"
@@ -53,12 +14,6 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.7.3"
@@ -89,12 +44,6 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bumpalo"
version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
[[package]]
name = "byte-tools"
version = "0.3.1"
@@ -113,12 +62,6 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -247,41 +190,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "der"
version = "0.4.4"
@@ -309,45 +217,6 @@ dependencies = [
"generic-array 0.14.4",
]
[[package]]
name = "dprint-core"
version = "0.35.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93bd44f40b1881477837edc7112695d4b174f058c36c1cbc4c50f8d0482e2ac8"
dependencies = [
"bumpalo",
"fnv",
"serde",
]
[[package]]
name = "dprint-plugin-typescript"
version = "0.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ba0077bd2ab9235848e793fbbfb563e6a04b4c8e4149827802a84063c15805"
dependencies = [
"dprint-core",
"dprint-swc-ecma-ast-view",
"fnv",
"serde",
"swc_common",
"swc_ecmascript",
]
[[package]]
name = "dprint-swc-ecma-ast-view"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecf692a2ee5c5f699ed0e95f21686cf6367f3a591e5d8e7bd3041bbf184651f9"
dependencies = [
"bumpalo",
"fnv",
"num-bigint",
"swc_atoms",
"swc_common",
"swc_ecmascript",
]
[[package]]
name = "dyn-clone"
version = "1.0.4"
@@ -380,12 +249,6 @@ dependencies = [
"thiserror",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "elliptic-curve"
version = "0.10.6"
@@ -402,18 +265,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "enum_kind"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b940da354ae81ef0926c5eaa428207b8f4f091d3956c891dfbd124162bed99"
dependencies = [
"pmutil",
"proc-macro2",
"swc_macros_common",
"syn",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
@@ -439,15 +290,10 @@ dependencies = [
"az",
"bytemuck",
"half",
"serde",
"typenum",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@@ -458,27 +304,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "from_variant"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0951635027ca477be98f8774abd6f0345233439d63f307e47101acb40c7cc63d"
dependencies = [
"pmutil",
"proc-macro2",
"swc_macros_common",
"syn",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.12.4"
@@ -504,7 +329,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@@ -515,7 +340,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
@@ -589,12 +414,6 @@ dependencies = [
"serde",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.3"
@@ -606,28 +425,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "is-macro"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a322dd16d960e322c3d92f541b4c1a4f0a2e81e1fdeee430d8cecc8b72e8015f"
dependencies = [
"Inflector",
"pmutil",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "itoa"
version = "0.4.8"
@@ -640,40 +437,25 @@ version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
"ecdsa",
"elliptic-curve",
"sha2",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013"
[[package]]
name = "lock_api"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
"cfg-if",
]
[[package]]
@@ -688,12 +470,6 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "mixnet-contract"
version = "0.1.0"
@@ -707,7 +483,6 @@ dependencies = [
"serde",
"serde_repr",
"thiserror",
"ts-rs",
]
[[package]]
@@ -735,49 +510,6 @@ dependencies = [
"url",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
[[package]]
name = "opaque-debug"
version = "0.2.3"
@@ -790,40 +522,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "owning_ref"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff55baddef9e4ad00f88b6c743a2a8062d4c6ade126c2a528644b8e444d52ce"
dependencies = [
"stable_deref_trait",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@@ -873,25 +571,6 @@ dependencies = [
"sha-1",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]]
name = "pkcs8"
version = "0.7.6"
@@ -902,29 +581,6 @@ dependencies = [
"spki",
]
[[package]]
name = "pmutil"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3894e5d549cccbe44afecf72922f277f603cd4bb0219c8342631ef18fffbe004"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.32"
@@ -949,30 +605,6 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -991,50 +623,6 @@ dependencies = [
"getrandom 0.2.3",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "ryu"
version = "1.0.5"
@@ -1065,18 +653,6 @@ dependencies = [
"syn",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.130"
@@ -1158,7 +734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cfg-if",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
@@ -1174,18 +750,6 @@ dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "siphasher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
[[package]]
name = "smallvec"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "spki"
version = "0.4.1"
@@ -1195,207 +759,18 @@ dependencies = [
"der",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"parking_lot",
"phf_shared",
"precomputed-hash",
"serde",
]
[[package]]
name = "string_cache_codegen"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
]
[[package]]
name = "string_enum"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f584cc881e9e5f1fd6bf827b0444aa94c30d8fe6378cf241071b5f5700b2871f"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"swc_macros_common",
"syn",
]
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f5229fe227ff0060e13baa386d6e368797700eab909523f730008d191ee53ae"
dependencies = [
"string_cache",
"string_cache_codegen",
]
[[package]]
name = "swc_common"
version = "0.10.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c93df65683ec1a001e15ce1de438c7c2c226c0c2462d1cb93fa1bd2a7664170b"
dependencies = [
"ast_node",
"cfg-if 0.1.10",
"either",
"from_variant",
"fxhash",
"log",
"num-bigint",
"once_cell",
"owning_ref",
"scoped-tls",
"serde",
"string_cache",
"swc_eq_ignore_macros",
"swc_visit",
"unicode-width",
]
[[package]]
name = "swc_ecma_ast"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83eb6a73820660a5af3c24ae1d436e84e4d4c13822021140011361e678df247b"
dependencies = [
"is-macro",
"num-bigint",
"serde",
"string_enum",
"swc_atoms",
"swc_common",
]
[[package]]
name = "swc_ecma_parser"
version = "0.52.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03250697857164f16fa98f8e1726f566652d13e52ea3f0c3ecea9deb63ee327"
dependencies = [
"either",
"enum_kind",
"fxhash",
"log",
"num-bigint",
"serde",
"smallvec",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_ecma_visit",
"unicode-xid",
]
[[package]]
name = "swc_ecma_visit"
version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3d60b9dc97ae4f181d4d60f43142d8ac9669953db410bcedefb29a14627e19"
dependencies = [
"num-bigint",
"swc_atoms",
"swc_common",
"swc_ecma_ast",
"swc_visit",
]
[[package]]
name = "swc_ecmascript"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ffb53afe008c15d4dc4957e80148c4b457659f93e4d4e8736eaeae352e48ec8"
dependencies = [
"swc_ecma_ast",
"swc_ecma_parser",
]
[[package]]
name = "swc_eq_ignore_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c8f200a2eaed938e7c1a685faaa66e6d42fa9e17da5f62572d3cbc335898f5e"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "swc_macros_common"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf7c68e78ffbcba3d38abe6d0b76a0e1a37888b5c9301db3426537207090ada3"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "swc_visit"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8511a4788ab29daf00bee23e425aac92c9be4eec74c98fec4a45d0e710be695"
dependencies = [
"either",
"swc_visit_macros",
]
[[package]]
name = "swc_visit_macros"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b2825fee79f10d0166e8e650e79c7a862fb991db275743083f07555d7641f0"
dependencies = [
"Inflector",
"pmutil",
"proc-macro2",
"quote",
"swc_macros_common",
"syn",
]
[[package]]
name = "syn"
version = "1.0.81"
@@ -1467,28 +842,6 @@ dependencies = [
"serde",
]
[[package]]
name = "ts-rs"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "369e48de67506679b3a576b0faf666fa9f9acf2fd00b4c61e28bdb6c8e08ec06"
dependencies = [
"dprint-plugin-typescript",
"ts-rs-macros",
]
[[package]]
name = "ts-rs-macros"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f269e8fd28e26b4cdbd01f81f345aaf666131511e54a735a76a614b5062d0a5a"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.14.0"
@@ -1528,12 +881,6 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-width"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
[[package]]
name = "unicode-xid"
version = "0.2.2"
@@ -1570,28 +917,6 @@ version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "zeroize"
version = "1.4.2"
+19 -36
View File
@@ -3,29 +3,21 @@
use std::u128;
use crate::helpers::calculate_epoch_reward_rate;
use crate::state::State;
use crate::storage::{config, layer_distribution};
use crate::{error::ContractError, queries, transactions};
use config::defaults::REWARDING_VALIDATOR_ADDRESS;
use cosmwasm_std::{
entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Uint128,
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2;
/// Constant specifying minimum of coin required to bond a gateway
pub const INITIAL_GATEWAY_BOND: Uint128 = Uint128(100_000000);
/// Constant specifying minimum of coin required to bond a mixnode
pub const INITIAL_MIXNODE_BOND: Uint128 = Uint128(100_000000);
// percentage annual increase. Given starting value of x, we expect to have 1.1x at the end of the year
pub const INITIAL_MIXNODE_BOND_REWARD_RATE: u64 = 110;
pub const INITIAL_MIXNODE_DELEGATION_REWARD_RATE: u64 = 110;
pub const INITIAL_MIXNODE_REWARDED_SET_SIZE: u32 = 200;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
@@ -33,36 +25,22 @@ pub const INITIAL_REWARD_POOL: u128 = 250_000_000_000_000;
pub const EPOCH_REWARD_PERCENT: u8 = 2; // Used to calculate epoch reward pool
pub const DEFAULT_SYBIL_RESISTANCE_PERCENT: u8 = 30;
// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
// We'll be assuming a few more things, profit margin and cost function. Since we don't have reliable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
pub const DEFAULT_COST_PER_EPOCH: u32 = 40_000_000;
fn default_initial_state(owner: Addr, env: Env) -> State {
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE);
State {
owner,
rewarding_validator_address: Addr::unchecked(REWARDING_VALIDATOR_ADDRESS), // we trust our hardcoded value
params: StateParams {
epoch_length: INITIAL_DEFAULT_EPOCH_LENGTH,
minimum_mixnode_bond: INITIAL_MIXNODE_BOND,
minimum_gateway_bond: INITIAL_GATEWAY_BOND,
mixnode_bond_reward_rate,
mixnode_delegation_reward_rate,
mixnode_rewarded_set_size: INITIAL_MIXNODE_REWARDED_SET_SIZE,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
},
rewarding_interval_starting_block: env.block.height,
latest_rewarding_interval_nonce: 0,
rewarding_in_progress: false,
mixnode_epoch_bond_reward: calculate_epoch_reward_rate(
INITIAL_DEFAULT_EPOCH_LENGTH,
mixnode_bond_reward_rate,
),
mixnode_epoch_delegation_reward: calculate_epoch_reward_rate(
INITIAL_DEFAULT_EPOCH_LENGTH,
mixnode_delegation_reward_rate,
),
}
}
@@ -105,18 +83,6 @@ pub fn execute(
ExecuteMsg::UpdateStateParams(params) => {
transactions::try_update_state_params(deps, info, params)
}
ExecuteMsg::RewardMixnode {
identity,
uptime,
rewarding_interval_nonce,
} => transactions::try_reward_mixnode(
deps,
env,
info,
identity,
uptime,
rewarding_interval_nonce,
),
ExecuteMsg::RewardMixnodeV2 {
identity,
params,
@@ -141,6 +107,15 @@ pub fn execute(
ExecuteMsg::FinishMixnodeRewarding {
rewarding_interval_nonce,
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
ExecuteMsg::RewardNextMixDelegators {
mix_identity,
rewarding_interval_nonce,
} => transactions::try_reward_next_mixnode_delegators_v2(
deps,
info,
mix_identity,
rewarding_interval_nonce,
),
}
}
@@ -199,6 +174,14 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
QueryMsg::GetCirculatingSupply {} => to_binary(&queries::query_circulating_supply(deps)),
QueryMsg::GetEpochRewardPercent {} => to_binary(&EPOCH_REWARD_PERCENT),
QueryMsg::GetSybilResistancePercent {} => to_binary(&DEFAULT_SYBIL_RESISTANCE_PERCENT),
QueryMsg::GetRewardingStatus {
mix_identity,
rewarding_interval_nonce,
} => to_binary(&queries::query_rewarding_status(
deps,
mix_identity,
rewarding_interval_nonce,
)?),
};
Ok(query_res?)
+12 -11
View File
@@ -45,15 +45,15 @@ pub enum ContractError {
#[error("No coin was sent for the bonding, you must send {}", DENOM)]
NoBondFound,
#[error("The bond reward rate for mixnode was set to be lower than 1")]
DecreasingMixnodeBondReward,
#[error("The delegation reward rate for mixnode was set to be lower than 1")]
DecreasingMixnodeDelegationReward,
#[error("Provided active set size is bigger than the demanded set")]
InvalidActiveSetSize,
#[error("Provided active set size is zero")]
ZeroActiveSet,
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("The node had uptime larger than 100%")]
UnexpectedUptime,
@@ -80,15 +80,10 @@ pub enum ContractError {
identity: IdentityKey,
address: Addr,
},
#[error("Overflow error!")]
Overflow(#[from] cosmwasm_std::OverflowError),
#[error("We tried to remove more funds then are available in the Reward pool. Wanted to remove {to_remove}, but have only {reward_pool}")]
OutOfFunds { to_remove: u128, reward_pool: u128 },
#[error("Invalid ratio")]
Ratio(#[from] mixnet_contract::error::MixnetContractError),
#[error("Received invalid rewarding interval nonce. Expected {expected}, received {received}")]
InvalidRewardingIntervalNonce { received: u32, expected: u32 },
@@ -100,4 +95,10 @@ pub enum ContractError {
#[error("Mixnode {identity} has already been rewarded during the current rewarding interval")]
MixnodeAlreadyRewarded { identity: IdentityKey },
#[error("Some of mixnodes {identity} delegators are still pending reward")]
DelegatorsPendingReward { identity: IdentityKey },
#[error("Mixnode's {identity} operator has not been rewarded yet - cannot perform delegator rewarding until that happens")]
MixnodeOperatorNotRewarded { identity: IdentityKey },
}
+1 -119
View File
@@ -1,78 +1,16 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::ContractError;
use crate::transactions::OLD_DELEGATIONS_CHUNK_SIZE;
use cosmwasm_std::{Decimal, Order, StdError, StdResult, Uint128};
use cosmwasm_std::{Order, StdError, StdResult};
use cosmwasm_storage::ReadonlyBucket;
use mixnet_contract::{Addr, IdentityKey, PagedAllDelegationsResponse, UnpackedDelegation};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::ops::Sub;
// for time being completely ignore concept of a leap year and assume each year is exactly 365 days
// i.e. 8760 hours
const HOURS_IN_YEAR: u128 = 8760;
const DECIMAL_FRACTIONAL: Uint128 = Uint128(1_000_000_000_000_000_000u128);
// cosmwasm bucket internal value
const NAMESPACE_LENGTH: usize = 2;
pub fn decimal_to_uint128(value: Decimal) -> Uint128 {
value * DECIMAL_FRACTIONAL
}
pub fn uint128_to_decimal(value: Uint128) -> Decimal {
Decimal::from_ratio(value, DECIMAL_FRACTIONAL)
}
pub(crate) fn calculate_epoch_reward_rate(
epoch_length: u32,
annual_reward_rate: Decimal,
) -> Decimal {
// this is more of a sanity check as the contract does not allow setting annual reward rates
// to be lower than 1.
debug_assert!(annual_reward_rate >= Decimal::one());
// converts reward rate, like 1.25 into the expected gain, like 0.25
let annual_reward = annual_reward_rate.sub(Decimal::one());
// do a simple cross-multiplication:
// `annual_reward` - `HOURS_IN_YEAR`
// x - `epoch_length`
//
// x = `annual_reward` * `epoch_length` / `HOURS_IN_YEAR`
// converts reward, like 0.25 into 250000000000000000
let annual_reward_uint128 = decimal_to_uint128(annual_reward);
// calculates `annual_reward_uint128` * `epoch_length` / `HOURS_IN_YEAR`
let epoch_reward_uint128 = annual_reward_uint128.multiply_ratio(epoch_length, HOURS_IN_YEAR);
// note: this returns a % reward, like 0.05 rather than reward rate (like 1.05)
uint128_to_decimal(epoch_reward_uint128)
}
pub(crate) fn scale_reward_by_uptime(
reward: Decimal,
uptime: u32,
) -> Result<Decimal, ContractError> {
if uptime > 100 {
return Err(ContractError::UnexpectedUptime);
}
let uptime_ratio = Decimal::from_ratio(uptime, 100u128);
// if we do not convert into a more precise representation, we might end up with, for example,
// reward 0.05 and uptime of 50% which would produce 0.50 * 0.05 = 0 (because of u128 representation)
// and also the above would be impossible to compute as Mul<Decimal> for Decimal is not implemented
//
// but with the intermediate conversion, we would have
// 0.50 * 50_000_000_000_000_000 = 25_000_000_000_000_000
// which converted back would give us the proper 0.025
let uptime_ratio_u128 = decimal_to_uint128(uptime_ratio);
let scaled = reward * uptime_ratio_u128;
Ok(uint128_to_decimal(scaled))
}
// Extracts the node identity and owner of a delegation from the bytes used as
// key in the delegation buckets.
fn extract_identity_and_owner(bytes: Vec<u8>) -> StdResult<(Addr, IdentityKey)> {
@@ -213,7 +151,6 @@ mod tests {
use crate::support::tests::helpers;
use cosmwasm_std::testing::mock_dependencies;
use mixnet_contract::RawDelegationData;
use std::str::FromStr;
#[test]
fn delegations_iterator() {
@@ -249,61 +186,6 @@ mod tests {
assert!(delegations.next().is_none());
}
#[test]
fn calculating_epoch_reward_rate() {
// 1.10
let annual_reward_rate = Decimal::from_ratio(110u128, 100u128);
// if the epoch is (for some reason) exactly one year,
// the reward rate should be unchanged
let per_epoch_rate = calculate_epoch_reward_rate(HOURS_IN_YEAR as u32, annual_reward_rate);
// 0.10
let expected = annual_reward_rate.sub(Decimal::one());
assert_eq!(expected, per_epoch_rate);
// 24 hours
let per_epoch_rate = calculate_epoch_reward_rate(24, annual_reward_rate);
// 0.1 / 365
let expected = Decimal::from_ratio(1u128, 3650u128);
assert_eq!(expected, per_epoch_rate);
let expected_per_epoch_rate_excel = Decimal::from_str("0.000273972602739726").unwrap();
assert_eq!(expected_per_epoch_rate_excel, per_epoch_rate);
// 1 hour
let per_epoch_rate = calculate_epoch_reward_rate(1, annual_reward_rate);
// 0.1 / 8760
let expected = Decimal::from_ratio(1u128, 87600u128);
assert_eq!(expected, per_epoch_rate);
}
#[test]
fn scaling_reward_by_uptime() {
// 0.05
let epoch_reward = Decimal::from_ratio(5u128, 100u128);
// scaling by 100 does nothing
let scaled = scale_reward_by_uptime(epoch_reward, 100).unwrap();
assert_eq!(epoch_reward, scaled);
// scaling by 0 makes the reward 0
let scaled = scale_reward_by_uptime(epoch_reward, 0).unwrap();
assert_eq!(Decimal::zero(), scaled);
// 50 halves it
let scaled = scale_reward_by_uptime(epoch_reward, 50).unwrap();
let expected = Decimal::from_ratio(25u128, 1000u128);
assert_eq!(expected, scaled);
// 10 takes 1/10th
let scaled = scale_reward_by_uptime(epoch_reward, 10).unwrap();
let expected = Decimal::from_ratio(5u128, 1000u128);
assert_eq!(expected, scaled);
// anything larger than 100 returns an error
assert!(scale_reward_by_uptime(epoch_reward, 101).is_err())
}
#[test]
fn identity_and_owner_deserialization() {
assert!(extract_identity_and_owner(vec![]).is_err());
+258 -11
View File
@@ -6,23 +6,23 @@ use crate::helpers::get_all_delegations_paged;
use crate::storage::{
all_mix_delegations_read, circulating_supply, config_read, gateways_owners_read, gateways_read,
mix_delegations_read, mixnodes_owners_read, mixnodes_read, read_layer_distribution,
read_state_params, reverse_mix_delegations_read, reward_pool_value,
read_state_params, reverse_mix_delegations_read, reward_pool_value, rewarded_mixnodes_read,
};
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr, Deps, Order, StdResult, Uint128};
use mixnet_contract::{
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
MixOwnershipResponse, PagedAllDelegationsResponse, PagedGatewayResponse,
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseMixDelegationsResponse,
RawDelegationData, RewardingIntervalResponse, StateParams,
MixOwnershipResponse, MixnodeRewardingStatusResponse, PagedAllDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
PagedReverseMixDelegationsResponse, RawDelegationData, RewardingIntervalResponse, StateParams,
};
const BOND_PAGE_MAX_LIMIT: u32 = 100;
const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
// currently the maximum limit before running into memory issue is somewhere between 1150 and 1200
pub(crate) const DELEGATION_PAGE_MAX_LIMIT: u32 = 750;
pub(crate) const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500;
const DELEGATION_PAGE_MAX_LIMIT: u32 = 500;
const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 250;
pub fn query_mixnodes_paged(
deps: Deps,
@@ -226,6 +226,17 @@ pub(crate) fn query_mixnode_delegation(
}
}
pub(crate) fn query_rewarding_status(
deps: Deps,
mix_identity: IdentityKey,
rewarding_interval_nonce: u32,
) -> StdResult<MixnodeRewardingStatusResponse> {
let status = rewarded_mixnodes_read(deps.storage, rewarding_interval_nonce)
.may_load(mix_identity.as_bytes())?;
Ok(MixnodeRewardingStatusResponse { status })
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
@@ -581,19 +592,14 @@ pub(crate) mod tests {
owner: Addr::unchecked("someowner"),
rewarding_validator_address: Addr::unchecked("monitor"),
params: StateParams {
epoch_length: 1,
minimum_mixnode_bond: 123u128.into(),
minimum_gateway_bond: 456u128.into(),
mixnode_bond_reward_rate: "1.23".parse().unwrap(),
mixnode_delegation_reward_rate: "7.89".parse().unwrap(),
mixnode_rewarded_set_size: 1000,
mixnode_active_set_size: 500,
},
rewarding_interval_starting_block: 123,
latest_rewarding_interval_nonce: 0,
rewarding_in_progress: false,
mixnode_epoch_bond_reward: "1.23".parse().unwrap(),
mixnode_epoch_delegation_reward: "7.89".parse().unwrap(),
};
config(deps.as_mut().storage).save(&dummy_state).unwrap();
@@ -1114,4 +1120,245 @@ pub(crate) mod tests {
assert_eq!(2, page2.delegated_nodes.len());
}
}
#[cfg(test)]
mod querying_for_rewarding_status {
use super::*;
use crate::support::tests::helpers::{add_mixnode, node_rewarding_params_fixture};
use crate::transactions::{
try_add_mixnode, try_begin_mixnode_rewarding, try_delegate_to_mixnode,
try_finish_mixnode_rewarding, try_reward_mixnode_v2,
try_reward_next_mixnode_delegators_v2, MINIMUM_BLOCK_AGE_FOR_REWARDING,
};
use mixnet_contract::{RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT};
#[test]
fn returns_empty_status_for_unrewarded_nodes() {
let mut deps = helpers::init_contract();
let env = mock_env();
let current_state = config_read(deps.as_mut().storage).load().unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_identity = add_mixnode("bob", good_mixnode_bond(), &mut deps);
assert!(
query_rewarding_status(deps.as_ref(), node_identity.clone(), 1)
.unwrap()
.status
.is_none()
);
// node was rewarded but for different epoch
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode_v2(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
assert!(query_rewarding_status(deps.as_ref(), node_identity, 2)
.unwrap()
.status
.is_none());
}
#[test]
fn returns_complete_status_for_fully_rewarded_node() {
// with single page
let mut deps = helpers::init_contract();
let mut env = mock_env();
let current_state = config_read(deps.as_mut().storage).load().unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_identity = "bobsnode".to_string();
try_add_mixnode(
deps.as_mut(),
env.clone(),
mock_info("bob", &good_mixnode_bond()),
MixNode {
identity_key: node_identity.clone(),
..helpers::mix_node_fixture()
},
)
.unwrap();
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode_v2(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
node_rewarding_params_fixture(100),
1,
)
.unwrap();
try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_eq!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
// with multiple pages
let node_identity = "alicesnode".to_string();
try_add_mixnode(
deps.as_mut(),
env.clone(),
mock_info("alice", &good_mixnode_bond()),
MixNode {
identity_key: node_identity.clone(),
..helpers::mix_node_fixture()
},
)
.unwrap();
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 2).unwrap();
try_reward_mixnode_v2(
deps.as_mut(),
env.clone(),
info.clone(),
node_identity.clone(),
node_rewarding_params_fixture(100),
2,
)
.unwrap();
// rewards all pending
try_reward_next_mixnode_delegators_v2(
deps.as_mut(),
info.clone(),
node_identity.to_string(),
2,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 2).unwrap();
assert!(matches!(res.status, Some(RewardingStatus::Complete(..))));
match res.status.unwrap() {
RewardingStatus::Complete(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.total_delegator_reward
);
}
_ => unreachable!(),
}
}
#[test]
fn returns_pending_next_delegator_page_status_when_there_are_more_delegators_to_reward() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
let current_state = config_read(deps.as_mut().storage).load().unwrap();
let rewarding_validator_address = current_state.rewarding_validator_address;
let node_identity = "bobsnode".to_string();
try_add_mixnode(
deps.as_mut(),
env.clone(),
mock_info("bob", &good_mixnode_bond()),
MixNode {
identity_key: node_identity.clone(),
..helpers::mix_node_fixture()
},
)
.unwrap();
for i in 0..MIXNODE_DELEGATORS_PAGE_LIMIT + 123 {
try_delegate_to_mixnode(
deps.as_mut(),
env.clone(),
mock_info(
&*format!("delegator{:04}", i),
&vec![coin(200_000000, DENOM)],
),
node_identity.clone(),
)
.unwrap();
}
env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING;
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap();
try_reward_mixnode_v2(
deps.as_mut(),
env.clone(),
info,
node_identity.clone(),
node_rewarding_params_fixture(100),
1,
)
.unwrap();
let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap();
assert!(matches!(
res.status,
Some(RewardingStatus::PendingNextDelegatorPage(..))
));
match res.status.unwrap() {
RewardingStatus::PendingNextDelegatorPage(result) => {
assert_ne!(
RewardingResult::default().operator_reward,
result.running_results.operator_reward
);
assert_ne!(
RewardingResult::default().total_delegator_reward,
result.running_results.total_delegator_reward
);
assert_eq!(
&*format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT),
result.next_start
);
}
_ => unreachable!(),
}
}
}
}
+1 -5
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Addr, Decimal};
use cosmwasm_std::Addr;
use mixnet_contract::StateParams;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -18,8 +18,4 @@ pub struct State {
pub rewarding_interval_starting_block: u64,
pub latest_rewarding_interval_nonce: u32,
pub rewarding_in_progress: bool,
// helper values to avoid having to recalculate them on every single payment operation
pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
}
+9 -321
View File
@@ -1,19 +1,17 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract::INITIAL_REWARD_POOL;
use crate::error::ContractError;
use crate::state::State;
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
use crate::{error::ContractError, queries};
use config::defaults::TOTAL_SUPPLY;
use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128};
use cosmwasm_std::{StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
RawDelegationData, StateParams,
RawDelegationData, RewardingStatus, StateParams,
};
use serde::de::DeserializeOwned;
use serde::Serialize;
@@ -185,7 +183,10 @@ pub fn mixnodes_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey
// we want to treat this bucket as a set so we don't really care about what type of data is being stored.
// I went with u8 as after serialization it takes only a single byte of space, while if a `()` was used,
// it would have taken 4 bytes (representation of 'null')
pub fn rewarded_mixnodes(storage: &mut dyn Storage, rewarding_interval_nonce: u32) -> Bucket<u8> {
pub(crate) fn rewarded_mixnodes(
storage: &mut dyn Storage,
rewarding_interval_nonce: u32,
) -> Bucket<RewardingStatus> {
Bucket::multilevel(
storage,
&[
@@ -195,13 +196,10 @@ pub fn rewarded_mixnodes(storage: &mut dyn Storage, rewarding_interval_nonce: u3
)
}
// we want to treat this bucket as a set so we don't really care about what type of data is being stored.
// I went with u8 as after serialization it takes only a single byte of space, while if a `()` was used,
// it would have taken 4 bytes (representation of 'null')
pub fn rewarded_mixnodes_read(
pub(crate) fn rewarded_mixnodes_read(
storage: &dyn Storage,
rewarding_interval_nonce: u32,
) -> ReadonlyBucket<u8> {
) -> ReadonlyBucket<RewardingStatus> {
ReadonlyBucket::multilevel(
storage,
&[
@@ -211,105 +209,6 @@ pub fn rewarded_mixnodes_read(
)
}
// helpers
pub(crate) fn increase_mix_delegated_stakes(
storage: &mut dyn Storage,
mix_identity: IdentityKeyRef,
scaled_reward_rate: Decimal,
reward_blockstamp: u64,
) -> StdResult<Uint128> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
let mut total_rewarded = Uint128::zero();
let mut chunk_start: Option<Vec<_>> = None;
loop {
// get `chunk_size` of delegations
let delegations_chunk = mix_delegations_read(storage, mix_identity)
.range(chunk_start.as_deref(), None, Order::Ascending)
.take(chunk_size)
.collect::<StdResult<Vec<_>>>()?;
if delegations_chunk.is_empty() {
break;
}
// append 0 byte to the last value to start with whatever is the next succeeding key
chunk_start = Some(
delegations_chunk
.last()
.unwrap()
.0
.iter()
.cloned()
.chain(std::iter::once(0u8))
.collect(),
);
// and for each of them increase the stake proportionally to the reward
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
// since they delegated
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
let reward = delegation.amount * scaled_reward_rate;
delegation.amount += reward;
total_rewarded += reward;
mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?;
}
}
}
Ok(total_rewarded)
}
pub(crate) fn increase_mix_delegated_stakes_v2(
storage: &mut dyn Storage,
bond: &MixNodeBond,
params: &NodeRewardParams,
) -> Result<Uint128, ContractError> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
let mut total_rewarded = Uint128::zero();
let mut chunk_start: Option<Vec<_>> = None;
loop {
// get `chunk_size` of delegations
let delegations_chunk = mix_delegations_read(storage, bond.identity())
.range(chunk_start.as_deref(), None, Order::Ascending)
.take(chunk_size)
.collect::<StdResult<Vec<_>>>()?;
if delegations_chunk.is_empty() {
break;
}
// append 0 byte to the last value to start with whatever is the next succeeding key
chunk_start = Some(
delegations_chunk
.last()
.unwrap()
.0
.iter()
.cloned()
.chain(std::iter::once(0u8))
.collect(),
);
// and for each of them increase the stake proportionally to the reward
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
// since they delegated
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING
<= params.reward_blockstamp()
{
let reward = bond.reward_delegation(delegation.amount, params);
delegation.amount += Uint128(reward);
total_rewarded += Uint128(reward);
mix_delegations(storage, bond.identity()).save(&delegator_address, &delegation)?;
}
}
}
Ok(total_rewarded)
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_mixnode_bond(
@@ -401,7 +300,6 @@ mod tests {
use crate::helpers::identity_and_owner_to_bytes;
use crate::support::tests::helpers::{
gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture,
raw_delegation_fixture,
};
use config::defaults::DENOM;
use cosmwasm_std::testing::{mock_dependencies, MockStorage};
@@ -539,216 +437,6 @@ mod tests {
assert_eq!(raw_delegation2, res2);
}
#[cfg(test)]
mod increasing_mix_delegated_stakes {
use super::*;
use crate::queries::query_mixnode_delegations_paged;
use cosmwasm_std::testing::mock_dependencies;
#[test]
fn when_there_are_no_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
42,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// there are no 'new' delegations magically added
assert!(
query_mixnode_delegations_paged(deps.as_ref(), node_identity, None, None)
.unwrap()
.delegations
.is_empty()
)
}
#[test]
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(1), total_increase);
// amount is incremented, block height remains the same
assert_eq!(
RawDelegationData::new(1001u128.into(), 42),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// amount is not incremented
assert_eq!(
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
);
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
// there is an increase now, that the lock period has passed
assert_eq!(Uint128(1), total_increase);
// amount is incremented
assert_eq!(
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(100), total_increase);
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
#[test]
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(
Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10),
total_increase
);
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
}
}
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
+17 -2
View File
@@ -1,10 +1,12 @@
#[cfg(test)]
pub mod helpers {
use super::*;
use crate::contract::query;
use crate::contract::{instantiate, INITIAL_MIXNODE_BOND};
use crate::contract::{
query, DEFAULT_SYBIL_RESISTANCE_PERCENT, EPOCH_REWARD_PERCENT, INITIAL_REWARD_POOL,
};
use crate::transactions::{try_add_gateway, try_add_mixnode};
use config::defaults::DENOM;
use config::defaults::{DENOM, TOTAL_SUPPLY};
use cosmwasm_std::from_binary;
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::testing::mock_env;
@@ -17,6 +19,7 @@ pub mod helpers {
use cosmwasm_std::OwnedDeps;
use cosmwasm_std::{coin, Uint128};
use cosmwasm_std::{Empty, MemoryStorage};
use mixnet_contract::mixnode::NodeRewardParams;
use mixnet_contract::{
Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse,
PagedMixnodeResponse, QueryMsg, RawDelegationData,
@@ -188,4 +191,16 @@ pub mod helpers {
amount: INITIAL_MIXNODE_BOND,
}]
}
// when exact values are irrelevant and what matters is the action of rewarding
pub fn node_rewarding_params_fixture(uptime: u128) -> NodeRewardParams {
NodeRewardParams::new(
(INITIAL_REWARD_POOL / 100) * EPOCH_REWARD_PERCENT as u128,
50 as u128,
0,
TOTAL_SUPPLY - INITIAL_REWARD_POOL,
uptime,
DEFAULT_SYBIL_RESISTANCE_PERCENT,
)
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -1298,6 +1298,7 @@ dependencies = [
"az",
"bytemuck",
"half",
"serde",
"typenum",
]
@@ -1,22 +1,17 @@
use crate::format_err;
use crate::state::State;
use cosmwasm_std::Decimal;
use cosmwasm_std::Uint128;
use mixnet_contract::StateParams;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
#[cfg_attr(test, derive(ts_rs::TS))]
#[derive(Serialize, Deserialize)]
pub struct TauriStateParams {
epoch_length: u32,
minimum_mixnode_bond: String,
minimum_gateway_bond: String,
mixnode_bond_reward_rate: String,
mixnode_delegation_reward_rate: String,
mixnode_rewarded_set_size: u32,
mixnode_active_set_size: u32,
}
@@ -24,11 +19,8 @@ pub struct TauriStateParams {
impl From<StateParams> for TauriStateParams {
fn from(p: StateParams) -> TauriStateParams {
TauriStateParams {
epoch_length: p.epoch_length,
minimum_mixnode_bond: p.minimum_mixnode_bond.to_string(),
minimum_gateway_bond: p.minimum_gateway_bond.to_string(),
mixnode_bond_reward_rate: p.mixnode_bond_reward_rate.to_string(),
mixnode_delegation_reward_rate: p.mixnode_delegation_reward_rate.to_string(),
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
mixnode_active_set_size: p.mixnode_active_set_size,
}
@@ -40,11 +32,8 @@ impl TryFrom<TauriStateParams> for StateParams {
fn try_from(p: TauriStateParams) -> Result<StateParams, Self::Error> {
Ok(StateParams {
epoch_length: p.epoch_length,
minimum_mixnode_bond: Uint128::try_from(p.minimum_mixnode_bond.as_str())?,
minimum_gateway_bond: Uint128::try_from(p.minimum_gateway_bond.as_str())?,
mixnode_bond_reward_rate: Decimal::from_str(p.mixnode_bond_reward_rate.as_str())?,
mixnode_delegation_reward_rate: Decimal::from_str(p.mixnode_delegation_reward_rate.as_str())?,
mixnode_rewarded_set_size: p.mixnode_rewarded_set_size,
mixnode_active_set_size: p.mixnode_active_set_size,
})
-3
View File
@@ -1,9 +1,6 @@
export interface TauriStateParams {
epoch_length: number;
minimum_mixnode_bond: string;
minimum_gateway_bond: string;
mixnode_bond_reward_rate: string;
mixnode_delegation_reward_rate: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
}
-2
View File
@@ -57,8 +57,6 @@ credentials = { path = "../common/credentials", optional = true }
[features]
coconut = ["coconut-interface", "credentials", "gateway-client/coconut"]
default = ["tokenomics"]
tokenomics = []
[build-dependencies]
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
-3
View File
@@ -16,9 +16,6 @@ local_validator = '{{ base.local_validator }}'
# Address of the validator contract managing the network.
mixnet_contract_address = '{{ base.mixnet_contract_address }}'
# Mnemonic (currently of the network monitor) used for rewarding
mnemonic = '{{ base.mnemonic }}'
##### network monitor config options #####
[network_monitor]
+1 -1
View File
@@ -17,7 +17,7 @@ use time::OffsetDateTime;
pub struct InvalidUptime;
// value in range 0-100
#[derive(Clone, Copy, Serialize, Deserialize, Debug)]
#[derive(Clone, Copy, Serialize, Deserialize, Debug, Default)]
pub struct Uptime(u8);
impl Uptime {
+105 -5
View File
@@ -8,16 +8,18 @@ use crate::rewarding::{
};
use config::defaults::DEFAULT_VALIDATOR_API_PORT;
use mixnet_contract::{
Delegation, ExecuteMsg, GatewayBond, IdentityKey, MixNodeBond, RewardingIntervalResponse,
StateParams,
Delegation, ExecuteMsg, GatewayBond, IdentityKey, MixNodeBond, MixnodeRewardingStatusResponse,
RewardingIntervalResponse, StateParams, MIXNODE_DELEGATORS_PAGE_LIMIT,
};
use serde::Serialize;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time::sleep;
use validator_client::nymd::{
hash::{Hash, SHA256_HASH_SIZE},
CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, TendermintTime,
CosmWasmClient, CosmosCoin, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient,
TendermintTime,
};
use validator_client::ValidatorClientError;
@@ -155,6 +157,21 @@ impl<C> Client<C> {
self.0.read().await.get_current_rewarding_interval().await
}
pub(crate) async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
self.0
.read()
.await
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await
}
/// Obtains the hash of a block specified by the provided height.
/// If the resulting digest is empty, a `None` is returned instead.
///
@@ -235,7 +252,77 @@ impl<C> Client<C> {
Ok(())
}
pub(crate) async fn reward_mixnodes(
pub(crate) async fn reward_mixnode_and_all_delegators(
&self,
node: &MixnodeToReward,
rewarding_interval_nonce: u32,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
{
// determine how many times we are going to have to call the delegator rewarding,
// note that it doesn't include the "base" call to `RewardMixnode` that rewards one page
let further_calls = node.total_delegations / MIXNODE_DELEGATORS_PAGE_LIMIT;
// start with the base call to reward operator and first page of delegators
let fee = self
.estimate_mixnode_reward_fees(1, MIXNODE_DELEGATORS_PAGE_LIMIT)
.await;
let msgs = vec![(
node.to_reward_execute_msg_v2(rewarding_interval_nonce),
vec![],
)];
let memo = format!(
"operator + {} delegators rewarding",
MIXNODE_DELEGATORS_PAGE_LIMIT
);
self.execute_multiple_with_retry(msgs, fee, memo).await?;
// reward rest of delegators
let mut remaining_delegators = node.total_delegations - MIXNODE_DELEGATORS_PAGE_LIMIT;
let delegator_rewarding_msg = (
node.to_next_delegator_reward_execute_msg_v2(rewarding_interval_nonce),
vec![],
);
for _ in 0..further_calls {
let delegators_in_call = remaining_delegators.min(MIXNODE_DELEGATORS_PAGE_LIMIT);
let fee = self
.estimate_mixnode_reward_fees(1, delegators_in_call)
.await;
let msgs = vec![delegator_rewarding_msg.clone()];
let memo = format!("rewarding another {} delegators", delegators_in_call);
self.execute_multiple_with_retry(msgs, fee, memo).await?;
remaining_delegators -= MIXNODE_DELEGATORS_PAGE_LIMIT;
}
Ok(())
}
pub(crate) async fn reward_mix_delegators(
&self,
node: &MixnodeToReward,
rewarding_interval_nonce: u32,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
{
// the fee is a tricky subject here because we don't know exactly how many delegators we missed,
// let's aim for the worst case scenario and assume it was the entire page
let fee = self
.estimate_mixnode_reward_fees(1, MIXNODE_DELEGATORS_PAGE_LIMIT)
.await;
let delegator_rewarding_msg = (
node.to_next_delegator_reward_execute_msg_v2(rewarding_interval_nonce),
vec![],
);
let memo = "rewarding delegators".to_string();
self.execute_multiple_with_retry(vec![delegator_rewarding_msg], fee, memo)
.await
}
pub(crate) async fn reward_mixnodes_with_single_page_of_delegators(
&self,
nodes: &[MixnodeToReward],
rewarding_interval_nonce: u32,
@@ -249,12 +336,25 @@ impl<C> Client<C> {
.await;
let msgs: Vec<(ExecuteMsg, _)> = nodes
.iter()
.map(|node| node.to_execute_msg(rewarding_interval_nonce))
.map(|node| node.to_reward_execute_msg_v2(rewarding_interval_nonce))
.zip(std::iter::repeat(Vec::new()))
.collect();
let memo = format!("rewarding {} mixnodes", msgs.len());
self.execute_multiple_with_retry(msgs, fee, memo).await
}
async fn execute_multiple_with_retry<M>(
&self,
msgs: Vec<(M, Vec<CosmosCoin>)>,
fee: Fee,
memo: String,
) -> Result<(), RewardingError>
where
C: SigningCosmWasmClient + Sync,
M: Serialize + Clone + Send,
{
let contract = self
.0
.read()
+245 -140
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
use crate::cache::ValidatorCache;
use crate::node_status_api::models::{MixnodeStatusReport, Uptime};
use crate::node_status_api::ONE_DAY;
use crate::nymd_client::Client;
use crate::rewarding::epoch::Epoch;
@@ -13,8 +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 mixnet_contract::{ExecuteMsg, IdentityKey, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT};
use std::convert::TryInto;
use std::time::Duration;
use time::OffsetDateTime;
@@ -40,47 +38,39 @@ pub(crate) const PER_MIXNODE_DELEGATION_GAS_INCREASE: u64 = 2750;
// the calculated total gas limit is going to get multiplied by that value.
pub(crate) const REWARDING_GAS_LIMIT_MULTIPLIER: f64 = 1.05;
pub(crate) const MAX_TO_REWARD_AT_ONCE: usize = 50;
#[derive(Debug, Clone)]
pub(crate) struct MixnodeToReward {
pub(crate) identity: IdentityKey,
pub(crate) uptime: Uptime,
/// 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>,
pub(crate) params: 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
// TODO: to re-integrate in another PR that combines rewarded/active sets with tokenomics
#[allow(dead_code)]
fn params(&self) -> Option<NodeRewardParams> {
if cfg!(feature = "tokenomics") {
self.params
} else {
None
}
/// Somewhat clumsy way of feature gatting tokenomics payments. In a tokenomics scenario this will never be None at reward time. We leverage that to Into a different ExecuteMsg variant
fn params(&self) -> NodeRewardParams {
self.params
}
}
impl MixnodeToReward {
pub(crate) fn to_execute_msg(&self, rewarding_interval_nonce: u32) -> ExecuteMsg {
ExecuteMsg::RewardMixnode {
pub(crate) fn to_reward_execute_msg_v2(&self, rewarding_interval_nonce: u32) -> ExecuteMsg {
ExecuteMsg::RewardMixnodeV2 {
identity: self.identity.clone(),
uptime: self.uptime.u8() as u32,
params: self.params(),
rewarding_interval_nonce,
}
}
// TODO: to re-integrate in another PR that combines rewarded/active sets with tokenomics
#[allow(dead_code)]
pub(crate) fn to_execute_msg_v2(&self, rewarding_interval_nonce: u32) -> ExecuteMsg {
ExecuteMsg::RewardMixnodeV2 {
identity: self.identity.clone(),
params: self.params().unwrap(),
pub(crate) fn to_next_delegator_reward_execute_msg_v2(
&self,
rewarding_interval_nonce: u32,
) -> ExecuteMsg {
ExecuteMsg::RewardNextMixDelegators {
mix_identity: self.identity.clone(),
rewarding_interval_nonce,
}
}
@@ -149,56 +139,18 @@ impl Rewarder {
.len())
}
/// Queries the smart contract in order to obtain the current list of bonded mixnodes and then
/// for each mixnode determines how many delegators it has.
async fn produce_active_mixnode_delegators_map(
&self,
) -> Result<HashMap<IdentityKey, usize>, RewardingError> {
// Technically we could optimise it by creating a concurrent stream and executing multiple
// queries concurrently.
//
// I've actually tested that approach and for 5300 nodes running it all sequentially was taking around 19s
// while running it with 20 concurrent queries was taking around 4.5s.
// Note that the results were a bit biased as I was testing it against remote validator
// while in real world this would be making only local requests.
// During the test my average ping times to the machine were around 2.6ms.
// So I guess the network latency was 2.6ms * 5300 = 13.78s in total in the sequential case.
//
// HOWEVER, even if the method was taking that long in real world,
// in the grand scheme of things it makes absolutely no difference. If the rewards
// distribution is delayed by 15s, it changes nothing as the process itself is not
// instantaneous.
let mut map = HashMap::new();
let active_bonded_mixnodes = self.validator_cache.active_mixnodes().await.into_inner();
for mix in active_bonded_mixnodes.into_iter() {
let delegator_count = self
.get_mixnode_delegators_count(mix.mix_node.identity_key.clone())
.await?;
map.insert(mix.mix_node.identity_key, delegator_count);
}
Ok(map)
}
/// Given the list of mixnodes that were tested in the last epoch, tries to determine the
/// subset that are eligible for any rewards.
///
/// As of right now, it is a rather straightforward process. It is checked whether the node
/// is currently bonded, has uptime > 0 and is part of the "active" set.
/// Unlike the typescript rewards script, it currently does not look at the verloc data nor
/// whether the non-mixing ports are open.
/// Obtain the list of current 'rewarded' set, determine their uptime in the provided epoch
/// and attach information required for rewarding.
///
/// The method also obtains the number of delegators towards the node in order to more accurately
/// approximate the required gas fees when distributing the rewards.
///
/// # Arguments
///
/// * `active_mixnodes`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
/// * `epoch`: current rewarding epoch
async fn determine_eligible_mixnodes(
&self,
active_mixnodes: &[MixnodeStatusReport],
epoch: Epoch,
) -> Result<Vec<MixnodeToReward>, RewardingError> {
// Currently we don't have as many 'features' as in the typescript reward script,
// such as we don't check ports or verloc data anymore. However, that's fine as
@@ -206,75 +158,103 @@ impl Rewarder {
// and the lack of port data / verloc data will eventually be balanced out anyway
// 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 mut eligible_nodes: Vec<MixnodeToReward> = active_mixnodes
.iter()
.filter_map(|mix| {
mixnode_delegators
.get(&mix.identity)
.map(|&total_delegations| MixnodeToReward {
identity: mix.identity.clone(),
uptime: mix.last_day,
total_delegations,
params: None,
})
})
.filter(|node| node.uptime.u8() > 0)
.collect();
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?;
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;
// TODO: question to @durch: is k active set or 'rewarded' set?
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);
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(
// 1. get list of 'rewarded' nodes
// 2. for each of them determine their delegator count
// 3. for each of them determine their uptime for the epoch
let rewarded_nodes = self.validator_cache.rewarded_mixnodes().await.into_inner();
let mut nodes_with_delegations = Vec::with_capacity(rewarded_nodes.len());
for rewarded_node in rewarded_nodes {
let delegator_count = self
.get_mixnode_delegators_count(rewarded_node.mix_node.identity_key.clone())
.await?;
nodes_with_delegations.push((rewarded_node, delegator_count));
}
let mut eligible_nodes = Vec::with_capacity(nodes_with_delegations.len());
for (rewarded_node, total_delegations) in nodes_with_delegations {
let uptime = self
.storage
.get_average_mixnode_uptime_in_interval(
rewarded_node.identity(),
epoch.start_unix_timestamp(),
epoch.end_unix_timestamp(),
)
.await?;
eligible_nodes.push(MixnodeToReward {
identity: rewarded_node.mix_node.identity_key,
total_delegations,
params: NodeRewardParams::new(
period_reward_pool,
k.into(),
0,
circulating_supply,
mix.uptime.u8().into(),
uptime.u8().into(),
sybil_resistance_percent,
));
}
} else {
info!("Tokenomics feature is OFF");
),
})
}
Ok(eligible_nodes)
}
/// Obtains the lists of all mixnodes that were tested at least a single time
/// by the network monitor in the specified epoch.
/// Check whether every node, and their delegators, on the provided list were fully rewarded
/// in the specified interval.
///
/// # Arguments
/// It is used to deal with edge cases such that mixnode had exactly full page of delegations and
/// somebody created a new delegation thus causing the "last" delegator to possibly be pushed
/// onto the next page that the validator API was not aware of.
///
/// * `epoch`: the specified epoch.
async fn get_active_monitor_mixnodes(
/// * `eligible_mixnodes`: list of the nodes that were eligible to receive rewards.
/// * `rewarding_interval_nonce`: nonce associated with the current rewarding interval
async fn verify_rewarding_completion(
&self,
epoch: Epoch,
) -> Result<Vec<MixnodeStatusReport>, RewardingError> {
Ok(self
.storage
.get_all_active_mixnode_reports_in_interval(
epoch.start_unix_timestamp(),
epoch.end_unix_timestamp(),
)
.await?)
eligible_mixnodes: &[MixnodeToReward],
current_rewarding_nonce: u32,
) -> (Vec<MixnodeToReward>, Vec<MixnodeToReward>) {
let mut unrewarded = Vec::new();
let mut further_delegators_present = Vec::new();
for mix in eligible_mixnodes {
match self
.nymd_client
.get_rewarding_status(mix.identity.clone(), current_rewarding_nonce)
.await
{
Ok(rewarding_status) => match rewarding_status.status {
// that case is super weird, it implies the node hasn't been rewarded at all!
// maybe the transaction timed out twice or something? In any case, we should attempt
// the reward for the final time!
None => unrewarded.push(mix.clone()),
Some(RewardingStatus::PendingNextDelegatorPage(_)) => {
further_delegators_present.push(mix.clone())
}
Some(RewardingStatus::Complete(_)) => {}
},
Err(err) => {
error!(
"failed to query rewarding status of {} - {}",
mix.identity, err
)
}
}
}
(unrewarded, further_delegators_present)
}
/// Using the list of mixnodes eligible for rewards, chunks it into pre-defined sized-chunks
@@ -290,7 +270,7 @@ impl Rewarder {
///
/// # Arguments
///
/// * `eligible_mixnodes`: list of the nodes that are eligible to receive non-zero rewards.
/// * `eligible_mixnodes`: list of the nodes that are eligible to receive rewards.
/// * `rewarding_interval_nonce`: nonce associated with the current rewarding interval
async fn distribute_rewards_to_mixnodes(
&self,
@@ -299,10 +279,80 @@ impl Rewarder {
) -> Option<Vec<FailedMixnodeRewardChunkDetails>> {
let mut failed_chunks = Vec::new();
for (i, mix_chunk) in eligible_mixnodes.chunks(MAX_TO_REWARD_AT_ONCE).enumerate() {
// construct chunks such that we reward at most MIXNODE_DELEGATORS_PAGE_LIMIT delegators per block
// nodes with > MIXNODE_DELEGATORS_PAGE_LIMIT delegators that have to be treated in a special way,
// because we cannot batch them together
let mut individually_rewarded = Vec::new();
// sets of nodes that together they have < MIXNODE_DELEGATORS_PAGE_LIMIT delegators
let mut batch_rewarded = vec![vec![]];
let mut current_batch_i = 0;
let mut current_batch_total = 0;
// right now put mixes into batches super naively, if it doesn't fit into the current one,
// create a new one.
for mix in eligible_mixnodes {
// if mixnode has uptime of 0, no rewarding will actually happen regardless of number of delegators,
// so we can just batch it with the current batch
if mix.params.uptime() == 0 {
batch_rewarded[current_batch_i].push(mix.clone());
continue;
}
if mix.total_delegations > MIXNODE_DELEGATORS_PAGE_LIMIT {
individually_rewarded.push(mix)
} else if current_batch_total + mix.total_delegations < MIXNODE_DELEGATORS_PAGE_LIMIT {
batch_rewarded[current_batch_i].push(mix.clone());
current_batch_total += mix.total_delegations;
} else {
batch_rewarded.push(vec![mix.clone()]);
current_batch_i += 1;
current_batch_total = 0;
}
}
let mut total_rewarded = 0;
// start rewarding, first the nodes that are dealt with individually, i.e. nodes that
// need to have their own special blocks due to number of delegators
for mix in individually_rewarded {
if let Err(err) = self
.nymd_client
.reward_mixnodes(mix_chunk, rewarding_interval_nonce)
.reward_mixnode_and_all_delegators(mix, rewarding_interval_nonce)
.await
{
// this is a super weird edge case that we didn't catch change to sequence and
// resent rewards unnecessarily, but the mempool saved us from executing it again
// however, still we want to wait until we're sure we're into the next block
if !err.is_tendermint_duplicate() {
error!("failed to reward mixnode with all delegators... - {}", err);
failed_chunks.push(FailedMixnodeRewardChunkDetails {
possibly_unrewarded: vec![mix.clone()],
error_message: err.to_string(),
});
}
sleep(Duration::from_secs(11)).await;
}
total_rewarded += 1;
let percentage = total_rewarded as f32 * 100.0 / eligible_mixnodes.len() as f32;
info!(
"Rewarded {} / {} mixnodes\t{:.2}%",
total_rewarded,
eligible_mixnodes.len(),
percentage
);
}
// then we move onto the chunks
for mix_chunk in batch_rewarded {
if let Err(err) = self
.nymd_client
.reward_mixnodes_with_single_page_of_delegators(
&mix_chunk,
rewarding_interval_nonce,
)
.await
{
// this is a super weird edge case that we didn't catch change to sequence and
@@ -317,11 +367,12 @@ impl Rewarder {
}
sleep(Duration::from_secs(11)).await;
}
let rewarded = i * MAX_TO_REWARD_AT_ONCE + mix_chunk.len();
let percentage = rewarded as f32 * 100.0 / eligible_mixnodes.len() as f32;
total_rewarded += mix_chunk.len();
let percentage = total_rewarded as f32 * 100.0 / eligible_mixnodes.len() as f32;
info!(
"Rewarded {} / {} mixnodes\t{:.2}%",
rewarded,
total_rewarded,
eligible_mixnodes.len(),
percentage
);
@@ -334,28 +385,53 @@ impl Rewarder {
}
}
/// For each mixnode on the list, try to "continue" rewarding its delegators.
/// Note: due to the checks inside the smart contract, it's impossible to accidentally
/// reward the same mixnode (or delegator) twice during particular rewarding interval.
///
/// Realistically if this method is ever called, it will be only done once per node, so there's
/// no need to determine the exact number of missed delegators.
///
/// * `nodes`: mixnodes which delegators did not receive all rewards in this epoch.
/// * `rewarding_interval_nonce`: nonce associated with the current rewarding interval.
async fn reward_missed_delegators(
&self,
nodes: &[MixnodeToReward],
rewarding_interval_nonce: u32,
) {
for missed_node in nodes {
if let Err(err) = self
.nymd_client
.reward_mix_delegators(missed_node, rewarding_interval_nonce)
.await
{
warn!(
"failed to attempt to reward missed delegators of node {} - {}",
missed_node.identity, err
)
}
}
}
/// Using the list of active mixnode and gateways, determine which of them are eligible for
/// rewarding and distribute the rewards.
///
/// # Arguments
///
/// * `epoch_rewarding_id`: id of the current epoch rewarding as stored in the databse.
///
/// * `active_monitor_mixnodes`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
/// * `epoch_rewarding_id`: id of the current epoch rewarding as stored in the database.
/// * `epoch`: current rewarding epoch
async fn distribute_rewards(
&self,
epoch_rewarding_database_id: i64,
active_monitor_mixnodes: &[MixnodeStatusReport],
epoch: Epoch,
) -> Result<(RewardingReport, Option<FailureData>), RewardingError> {
let mut failure_data = FailureData::default();
let eligible_mixnodes = self
.determine_eligible_mixnodes(active_monitor_mixnodes)
.await?;
let eligible_mixnodes = self.determine_eligible_mixnodes(epoch).await?;
if eligible_mixnodes.is_empty() {
return Err(RewardingError::NoMixnodesToReward);
}
let total_eligible = eligible_mixnodes.len();
let current_rewarding_nonce = self
.nymd_client
@@ -369,9 +445,43 @@ impl Rewarder {
.distribute_rewards_to_mixnodes(&eligible_mixnodes, current_rewarding_nonce + 1)
.await;
let mut nodes_to_verify = eligible_mixnodes;
// if there's some underlying networking error or something, don't keep retrying forever
let mut retries_allowed = 5;
loop {
if retries_allowed <= 0 {
break;
}
let (unrewarded, mut pending_delegators) = self
.verify_rewarding_completion(&nodes_to_verify, current_rewarding_nonce + 1)
.await;
if unrewarded.is_empty() && pending_delegators.is_empty() {
// we're all good - everyone got their rewards
break;
}
if !unrewarded.is_empty() {
// no need to save failure data as we already know about those from the very first run
self.distribute_rewards_to_mixnodes(&unrewarded, current_rewarding_nonce + 1)
.await;
}
if !pending_delegators.is_empty() {
self.reward_missed_delegators(&pending_delegators, current_rewarding_nonce + 1)
.await;
}
// no point in verifying EVERYTHING again, just check the nodes that went through retries
nodes_to_verify = unrewarded;
nodes_to_verify.append(&mut pending_delegators);
retries_allowed -= 1;
}
let report = RewardingReport {
epoch_rewarding_id: epoch_rewarding_database_id,
eligible_mixnodes: eligible_mixnodes.len() as i64,
eligible_mixnodes: total_eligible as i64,
possibly_unrewarded_mixnodes: failure_data
.mixnodes
.as_ref()
@@ -426,7 +536,7 @@ impl Rewarder {
.insert_possibly_unrewarded_mixnode(PossiblyUnrewardedMixnode {
chunk_id,
identity: node.identity,
uptime: node.uptime.u8(),
uptime: node.params.uptime() as u8,
})
.await?;
}
@@ -553,9 +663,6 @@ impl Rewarder {
epoch
);
// get nodes that were active during the epoch
let active_monitor_mixnodes = self.get_active_monitor_mixnodes(epoch).await?;
// insert information about beginning the procedure (so that if we crash during it,
// we wouldn't attempt to possibly double reward operators)
let epoch_rewarding_id = self
@@ -563,9 +670,7 @@ impl Rewarder {
.insert_started_epoch_rewarding(epoch.start_unix_timestamp())
.await?;
let (report, failure_data) = self
.distribute_rewards(epoch_rewarding_id, &active_monitor_mixnodes)
.await?;
let (report, failure_data) = self.distribute_rewards(epoch_rewarding_id, epoch).await?;
self.storage
.finish_rewarding_epoch_and_insert_report(report)
+50 -2
View File
@@ -4,7 +4,7 @@
use crate::network_monitor::monitor::summary_producer::NodeResult;
use crate::network_monitor::test_route::TestRoute;
use crate::node_status_api::models::{
GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory,
GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, Uptime,
ValidatorApiStorageError,
};
use crate::node_status_api::{ONE_DAY, ONE_HOUR};
@@ -264,6 +264,54 @@ impl ValidatorApiStorage {
))
}
/// Based on the data available in the validator API, determines the average uptime of particular
/// mixnode during the specified time interval.
///
/// # Arguments
///
/// * `identity`: base58-encoded identity of the mixnode.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `end`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_average_mixnode_uptime_in_interval(
&self,
identity: &str,
start: UnixTimestamp,
end: UnixTimestamp,
) -> Result<Uptime, ValidatorApiStorageError> {
let mixnode_database_id = match self
.manager
.get_mixnode_id(identity)
.await
.map_err(|_| ValidatorApiStorageError::InternalDatabaseError)?
{
Some(id) => id,
None => return Ok(Uptime::zero()),
};
let monitor_runs = self.get_monitor_runs_count(start, end).await?;
let mixnode_statuses = self
.manager
.get_mixnode_statuses_by_id(mixnode_database_id, start, end)
.await
.map_err(|_| ValidatorApiStorageError::InternalDatabaseError)?;
let mut total: f32 = 0.0;
for mixnode_status in mixnode_statuses {
total += mixnode_status.reliability as f32;
}
let uptime = match Uptime::from_uptime_sum(total, monitor_runs) {
Ok(uptime) => uptime,
Err(_) => {
// this should really ever happen...
error!("mixnode {} has uptime > 100!", identity);
Uptime::default()
}
};
Ok(uptime)
}
/// Obtain status reports of mixnodes that were active in the specified time interval.
///
/// # Arguments
@@ -633,7 +681,7 @@ impl ValidatorApiStorage {
}
////////////////////////////////////////////////////////////////////////
// TODO: Should all of the below really return a "NodeStatusApi" Errors?
// TODO: Should all of the below really return a "ValidatorApiStorageError" Errors?
////////////////////////////////////////////////////////////////////////
/// Inserts information about starting new epoch rewarding into the database.