From f3a926375a211cf5ae93d61b810e7778288e9c94 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 21 Jun 2022 14:04:27 +0200 Subject: [PATCH] Change epoch length to 10 minutes (#1345) * Change epoch length to 10 minutes * Derive operator cost, and reward params from epoch length * Fix tests * Migrate back to hourly * Remove length migration * Push everything --- .../validator-client/src/client.rs | 7 +++ .../validator-client/src/nymd/mod.rs | 10 +++ .../mixnet-contract/src/interval.rs | 8 +++ .../mixnet-contract/src/mixnode.rs | 32 ++++++---- .../mixnet-contract/src/msg.rs | 1 + .../mixnet-contract/src/reward_params.rs | 27 ++++---- common/network-defaults/src/lib.rs | 3 +- contracts/mixnet/src/constants.rs | 3 +- contracts/mixnet/src/contract.rs | 63 ++++++++++++++++++- contracts/mixnet/src/rewards/transactions.rs | 55 +++++++++------- contracts/mixnet/src/support/helpers.rs | 21 ++++++- .../nym-wallet/src/types/rust/AppEnv.ts | 2 + .../nym-wallet/src/types/rust/ValidatorUrl.ts | 2 + .../src/types/rust/ValidatorUrls.ts | 2 + validator-api/src/contract_cache/mod.rs | 19 ++++++ validator-api/src/node_status_api/routes.rs | 6 +- validator-api/src/nymd_client.rs | 7 +++ 17 files changed, 219 insertions(+), 49 deletions(-) create mode 100644 nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts create mode 100644 nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts create mode 100644 nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index b28f4e33aa..8bd355e985 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -318,6 +318,13 @@ impl Client { Ok(self.nymd.get_current_epoch().await?) } + pub async fn get_current_operator_cost(&self) -> Result + where + C: CosmWasmClient + Sync, + { + Ok(self.nymd.get_current_operator_cost().await?) + } + pub async fn get_mixnet_contract_version(&self) -> Result where C: CosmWasmClient + Sync, diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index abf64bb98a..224be115d5 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -438,6 +438,16 @@ impl NymdClient { .await } + pub async fn get_current_operator_cost(&self) -> Result + where + C: CosmWasmClient + Sync, + { + let request = QueryMsg::GetCurrentOperatorCost {}; + self.client + .query_contract_smart(self.mixnet_contract_address(), &request) + .await + } + pub async fn get_mixnet_contract_version(&self) -> Result where C: CosmWasmClient + Sync, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs index 9d005aa6cf..c263c5184b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/interval.rs @@ -121,6 +121,14 @@ impl Interval { self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp() } + pub fn update_duration(&mut self, secs: u64) { + self.length = Duration::from_secs(secs); + } + + pub const fn length_secs(&self) -> u64 { + self.length.as_secs() + } + /// Returns the next interval. #[must_use] pub fn next(&self) -> Self { diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index bd30b91eda..a674fd75fc 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -431,6 +431,7 @@ impl MixNodeBond { pub fn estimate_reward( &self, + base_operator_cost: u64, params: &RewardParams, ) -> Result { let total_node_reward = self @@ -439,15 +440,15 @@ impl MixNodeBond { .checked_to_num::() .unwrap_or_default(); let node_profit = self - .node_profit(params) + .node_profit(params, base_operator_cost) .checked_to_num::() .unwrap_or_default(); let operator_cost = params .node - .operator_cost() + .operator_cost(base_operator_cost) .checked_to_num::() .unwrap_or_default(); - let operator_reward = self.operator_reward(params); + let operator_reward = self.operator_reward(params, base_operator_cost); // Total reward has to be the sum of operator and delegator rewards let delegators_reward = node_profit.saturating_sub(operator_reward); @@ -479,21 +480,25 @@ impl MixNodeBond { } } - pub fn node_profit(&self, params: &RewardParams) -> U128 { + pub fn node_profit(&self, params: &RewardParams, base_operator_cost: u64) -> U128 { self.reward(params) .reward() - .saturating_sub(params.node.operator_cost()) + .saturating_sub(params.node.operator_cost(base_operator_cost)) } - pub fn operator_reward(&self, params: &RewardParams) -> u128 { + pub fn operator_reward(&self, params: &RewardParams, base_operator_cost: u64) -> u128 { let reward = self.reward(params); - if reward.sigma == 0 { + if reward.sigma == 0u128 { return 0; } - let profit = reward.reward.saturating_sub(params.node.operator_cost()); + let profit = reward + .reward + .saturating_sub(params.node.operator_cost(base_operator_cost)); - let operator_base_reward = reward.reward.min(params.node.operator_cost()); + let operator_base_reward = reward + .reward + .min(params.node.operator_cost(base_operator_cost)); // Div by zero checked above let operator_reward = (self.profit_margin() + (ONE - self.profit_margin()) * reward.lambda / reward.sigma) @@ -521,11 +526,16 @@ impl MixNodeBond { } } - pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 { + pub fn reward_delegation( + &self, + delegation_amount: Uint128, + params: &RewardParams, + base_operator_cost: u64, + ) -> u128 { let reward_params = DelegatorRewardParams::new( self.sigma(params), self.profit_margin(), - self.node_profit(params), + self.node_profit(params, base_operator_cost), params.to_owned(), ); reward_params.determine_delegation_reward(delegation_amount) diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index dedd68c443..e4ef05920b 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -115,6 +115,7 @@ pub enum ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { + GetCurrentOperatorCost {}, GetRewardingValidatorAddress {}, GetAllDelegationKeys {}, DebugGetAllDelegationValues {}, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs index 702371cbc6..e853ab8bcf 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/reward_params.rs @@ -1,7 +1,6 @@ use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128}; use az::CheckedCast; use cosmwasm_std::Uint128; -use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -41,19 +40,23 @@ impl NodeEpochRewards { self.result.reward() } - pub fn operator_cost(&self) -> U128 { - self.params.operator_cost() + pub fn operator_cost(&self, base_operator_cost: u64) -> U128 { + self.params.operator_cost(base_operator_cost) } - pub fn node_profit(&self) -> U128 { + pub fn node_profit(&self, base_operator_cost: u64) -> U128 { let reward = U128::from_num(self.reward().u128()); // if operating cost is higher then the reward node profit is 0 - reward.saturating_sub(self.operator_cost()) + reward.saturating_sub(self.operator_cost(base_operator_cost)) } - pub fn operator_reward(&self, profit_margin: U128) -> Result { - let reward = self.node_profit(); - let operator_base_reward = reward.min(self.operator_cost()); + pub fn operator_reward( + &self, + profit_margin: U128, + base_operator_cost: u64, + ) -> Result { + let reward = self.node_profit(base_operator_cost); + let operator_base_reward = reward.min(self.operator_cost(base_operator_cost)); let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) { value } else { @@ -74,6 +77,7 @@ impl NodeEpochRewards { &self, delegation_amount: Uint128, profit_margin: U128, + base_operator_cost: u64, epoch_reward_params: EpochRewardParams, ) -> Result { // change all values into their fixed representations @@ -89,7 +93,8 @@ impl NodeEpochRewards { return Err(MixnetContractError::DivisionByZero); }; - let delegator_reward = (ONE - profit_margin) * check_div_by_zero * self.node_profit(); + let delegator_reward = + (ONE - profit_margin) * check_div_by_zero * self.node_profit(base_operator_cost); let reward = delegator_reward.max(U128::ZERO); if let Some(int_reward) = reward.checked_cast() { @@ -186,8 +191,8 @@ impl NodeRewardParams { } } - pub fn operator_cost(&self) -> U128 { - self.performance() * U128::from_num(DEFAULT_OPERATOR_INTERVAL_COST) + pub fn operator_cost(&self, base_operator_cost: u64) -> U128 { + self.performance() * U128::from_num(base_operator_cost) } pub fn uptime(&self) -> Uint128 { diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index 1b0cfa9518..cb3f71f166 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -210,7 +210,8 @@ pub const VALIDATOR_API_VERSION: &str = "v1"; /// 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 interval costs to Nyms. We'll also assume a cost of 40$ per interval(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms // pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; // 40$/(30 days) at 1 Nym == 1$ -pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$ +// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 55_556; // 40$/1hr at 1 Nym == 1$ +// pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 9259; // 40$/1hr/6 at 1 Nym == 1$ // TODO: is there a way to get this from the chain pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000; diff --git a/contracts/mixnet/src/constants.rs b/contracts/mixnet/src/constants.rs index cb4457c680..a19aa4fef3 100644 --- a/contracts/mixnet/src/constants.rs +++ b/contracts/mixnet/src/constants.rs @@ -13,4 +13,5 @@ pub const ACTIVE_SET_WORK_FACTOR: u8 = 10; // and we can't change this easily to `Duration`, because then the entire rewarded set storage // would be messed up... (as we look up stuff "by blocks") pub const REWARDED_SET_REFRESH_BLOCKS: u64 = 720; // with blocktime being approximately 5s, it should be roughly 1h -pub const EPOCHS_IN_INTERVAL: u64 = 720; // Hours in a month +pub const INTERVAL_SECONDS: u64 = 86400 * 30; // 30 days +pub const DEFAULT_OPERATOR_INTERVAL_COST: u64 = 40_000_000; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 3ee7e5eef7..e6b8dcc9f2 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -397,7 +397,12 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result { to_binary(&query_rewarded_set_refresh_minimum_blocks()) } - QueryMsg::GetEpochsInInterval {} => to_binary(&crate::constants::EPOCHS_IN_INTERVAL), + QueryMsg::GetEpochsInInterval {} => { + to_binary(&crate::support::helpers::epochs_in_interval(deps.storage)?) + } + QueryMsg::GetCurrentOperatorCost {} => to_binary( + &crate::support::helpers::current_operator_epoch_cost(deps.storage)?, + ), QueryMsg::GetCurrentEpoch {} => to_binary(&query_current_epoch(deps.storage)?), QueryMsg::QueryOperatorReward { address } => to_binary( &crate::rewards::queries::query_operator_reward(deps, address)?, @@ -438,8 +443,62 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result) -> Result<(), ContractError> { + let mut epoch = crate::interval::storage::current_epoch(deps.storage)?; + epoch.update_duration(600); + crate::interval::storage::save_epoch(deps.storage, &epoch)?; + + Ok(()) +} + +fn _migrate_contract_state_params(deps: DepsMut<'_>) -> Result<(), ContractError> { + use crate::mixnet_contract_settings::storage::CONTRACT_STATE; + use cw_storage_plus::Item; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize)] + struct OldContractState { + pub owner: Addr, + pub rewarding_validator_address: Addr, + pub params: OldContractStateParams, + } + + #[derive(Serialize, Deserialize)] + struct OldContractStateParams { + pub minimum_mixnode_pledge: Uint128, + pub minimum_gateway_pledge: Uint128, + pub mixnode_rewarded_set_size: u32, + pub mixnode_active_set_size: u32, + } + + const OLD_CONTRACT_STATE: Item<'_, OldContractState> = Item::new("config"); + + let old_contract_state = OLD_CONTRACT_STATE.load(deps.storage)?; + + let old_params = old_contract_state.params; + + let new_params = ContractStateParams { + minimum_mixnode_pledge: old_params.minimum_mixnode_pledge, + minimum_gateway_pledge: old_params.minimum_gateway_pledge, + mixnode_rewarded_set_size: old_params.mixnode_rewarded_set_size, + mixnode_active_set_size: old_params.mixnode_active_set_size, + staking_supply: INITIAL_STAKING_SUPPLY, + }; + + let new_contract_state = ContractState { + owner: old_contract_state.owner, + rewarding_validator_address: old_contract_state.rewarding_validator_address, + params: new_params, + }; + + CONTRACT_STATE.save(deps.storage, &new_contract_state)?; + + Ok(()) +} + #[entry_point] -pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { +pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result { + update_epoch_duration(deps)?; Ok(Default::default()) } diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 06451d56b0..bdbd9c70bc 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -13,7 +13,7 @@ use crate::error::ContractError; use crate::mixnodes::storage::mixnodes; use crate::mixnodes::storage::{self as mixnodes_storage, StoredMixnodeBond}; use crate::rewards::helpers; -use crate::support::helpers::is_authorized; +use crate::support::helpers::{is_authorized, operator_cost_at_epoch}; use config::defaults::DENOM; use cosmwasm_std::{ coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, MessageInfo, Order, Response, @@ -317,7 +317,9 @@ pub fn calculate_operator_reward( { if let Some(ref epoch_rewards) = bond.epoch_rewards { // Compound rewards from previous heights - match epoch_rewards.operator_reward(bond.profit_margin()) { + let operator_cost = + operator_cost_at_epoch(storage, epoch_rewards.epoch_id())?; + match epoch_rewards.operator_reward(bond.profit_margin(), operator_cost) { Ok(reward) => return Ok(accumulated_reward + reward), Err(err) => { debug_with_visibility( @@ -528,12 +530,15 @@ pub fn calculate_delegator_reward( .flatten() { if let Some(ref epoch_rewards) = bond.epoch_rewards { + let operator_cost = + operator_cost_at_epoch(storage, epoch_rewards.epoch_id()).unwrap(); // Compound rewards from previous heights match epoch_reward_params_for_id(storage, epoch_rewards.epoch_id()) { Ok(params) => { let reward_at_height = match epoch_rewards.delegation_reward( delegation_at_height + accumulated_reward, bond.profit_margin(), + operator_cost, params, ) { Ok(reward) => { @@ -703,7 +708,6 @@ pub(crate) fn try_reward_mixnode( #[cfg(test)] pub mod tests { use super::*; - use crate::constants::EPOCHS_IN_INTERVAL; use crate::delegations::transactions::{ _try_remove_delegation_from_mixnode, try_delegate_to_mixnode, }; @@ -718,6 +722,7 @@ pub mod tests { use crate::mixnodes::storage as mixnodes_storage; use crate::mixnodes::storage::StoredMixnodeBond; use crate::rewards::transactions::try_reward_mixnode; + use crate::support::helpers::{current_operator_epoch_cost, epochs_in_interval}; use crate::support::tests; use crate::support::tests::test_helpers; use az::CheckedCast; @@ -1093,8 +1098,9 @@ pub mod tests { .collect::>(); assert_eq!(0, checkpoints.len()); - let period_reward_pool = (INITIAL_REWARD_POOL / 100 / EPOCHS_IN_INTERVAL as u128) - * INTERVAL_REWARD_PERCENT as u128; + let period_reward_pool = + (INITIAL_REWARD_POOL / 100 / epochs_in_interval(&deps.storage).unwrap() as u128) + * INTERVAL_REWARD_PERCENT as u128; assert_eq!(period_reward_pool, 6_944_444_444); let circulating_supply = storage::circulating_supply(&deps.storage).unwrap().u128(); assert_eq!(circulating_supply, 750_000_000_000_000u128); @@ -1459,7 +1465,7 @@ pub mod tests { // TODO: perform deeper investigation into this number as it seem to not have compounded // reward on the initial 8000 delegation and only have done it starting from 16000 - assert_eq!(alice_reward, Uint128::new(2737978)); + assert_eq!(alice_reward, Uint128::new(2737979)); let mix_0 = mixnodes.load(&deps.storage, &node_identity_1).unwrap(); @@ -1489,7 +1495,7 @@ pub mod tests { let delegation = delegations.first().unwrap(); assert_eq!( delegation.amount.amount, - Uint128::new(16000000000 + 2737978) + Uint128::new(16000000000 + 2737979) ); let mix_1 = mixnodes @@ -1513,7 +1519,7 @@ pub mod tests { let operator_reward = calculate_operator_reward(&deps.storage, &deps.api, &Addr::unchecked("alice"), &mix_1) .unwrap(); - assert_eq!(operator_reward, Uint128::new(2278902)); + assert_eq!(operator_reward, Uint128::new(2278901)); assert_eq!(mix_1_reward_result.sigma(), U128::from_num(0.0002f64)); assert_eq!(mix_1_reward_result.lambda(), U128::from_num(0.0001f64)); @@ -1534,6 +1540,7 @@ pub mod tests { fn test_tokenomics_rewarding() { use crate::constants::INTERVAL_REWARD_PERCENT; use crate::contract::INITIAL_REWARD_POOL; + use crate::support::helpers::epochs_in_interval; type U128 = fixed::types::U75F53; @@ -1543,8 +1550,9 @@ pub mod tests { .load(deps.as_ref().storage) .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; - let period_reward_pool = (INITIAL_REWARD_POOL / 100 / EPOCHS_IN_INTERVAL as u128) - * INTERVAL_REWARD_PERCENT as u128; + let period_reward_pool = + (INITIAL_REWARD_POOL / 100 / epochs_in_interval(&deps.storage).unwrap() as u128) + * INTERVAL_REWARD_PERCENT as u128; assert_eq!(period_reward_pool, 6_944_444_444); let circulating_supply = storage::circulating_supply(&deps.storage).unwrap().u128(); assert_eq!(circulating_supply, 750_000_000_000_000u128); @@ -1619,7 +1627,7 @@ pub mod tests { params.set_reward_blockstamp(env.block.height); - assert_eq!(params.performance(), U128::from_num(0.8999999999999999)); + assert_eq!(params.performance(), U128::from_num(0.8999999999999999f64)); let mix_1_reward_result = mix_1.reward(¶ms); @@ -1633,30 +1641,37 @@ pub mod tests { ); assert_eq!(mix_1_reward_result.reward().int(), 233202u128); - assert_eq!(mix_1.node_profit(¶ms).int(), 183202u128); + let base_operator_cost = current_operator_epoch_cost(&deps.storage).unwrap(); + + assert_eq!( + mix_1.node_profit(¶ms, base_operator_cost).int(), + 183203u128 + ); assert_ne!( mix_1_reward_result.reward(), - mix_1.node_profit(¶ms).int() + mix_1.node_profit(¶ms, base_operator_cost).int() ); - let mix1_operator_reward = mix_1.operator_reward(¶ms); + let mix1_operator_reward = mix_1.operator_reward(¶ms, base_operator_cost); - let mix1_delegator1_reward = mix_1.reward_delegation(Uint128::new(8000_000000), ¶ms); + let mix1_delegator1_reward = + mix_1.reward_delegation(Uint128::new(8000_000000), ¶ms, base_operator_cost); - let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms); + let mix1_delegator2_reward = + mix_1.reward_delegation(Uint128::new(2000_000000), ¶ms, base_operator_cost); assert_eq!(mix1_operator_reward, 150761); - assert_eq!(mix1_delegator1_reward, 65952); + assert_eq!(mix1_delegator1_reward, 65953); assert_eq!(mix1_delegator2_reward, 16488); assert_eq!( mix_1_reward_result.reward().int(), - mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1 + mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward ); assert_eq!( - mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1, + mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward, mix_1_reward_result.reward().int() ); @@ -1694,14 +1709,12 @@ pub mod tests { + mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward - + 1 // There is a rounding error here it seems ); assert_eq!( storage::REWARD_POOL.load(&deps.storage).unwrap().u128(), INITIAL_REWARD_POOL - (mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward) - - 1 // Same rounding error, its 1 ucoin, it will manifest/correct when the rewards are claimed ); // it's all correctly saved diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index c9b0d9c78c..7776e5522b 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -1,6 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::constants::{DEFAULT_OPERATOR_INTERVAL_COST, INTERVAL_SECONDS}; +use crate::interval::storage::{current_epoch, EPOCHS}; use crate::mixnodes::storage as mixnodes_storage; use crate::{constants, gateways::storage as gateways_storage}; @@ -15,6 +17,23 @@ pub(crate) fn is_authorized(sender: String, storage: &dyn Storage) -> Result<(), Ok(()) } +pub fn epochs_in_interval(storage: &dyn Storage) -> Result { + let epoch = current_epoch(storage)?; + Ok(INTERVAL_SECONDS / epoch.length_secs()) +} + +#[allow(dead_code)] +pub fn current_operator_epoch_cost(storage: &dyn Storage) -> Result { + Ok(DEFAULT_OPERATOR_INTERVAL_COST / epochs_in_interval(storage)?) +} + +pub fn operator_cost_at_epoch(storage: &dyn Storage, epoch_id: u32) -> Result { + let epoch = EPOCHS.load(storage, epoch_id)?; + // This is historical, so we can't use the function defined above + let epochs_in_interval = INTERVAL_SECONDS / epoch.length_secs(); + Ok(DEFAULT_OPERATOR_INTERVAL_COST / epochs_in_interval) +} + pub(crate) fn epoch_reward_params( storage: &dyn Storage, ) -> Result { @@ -23,7 +42,7 @@ pub(crate) fn epoch_reward_params( .map(|settings| settings.params)?; let reward_pool = crate::rewards::storage::REWARD_POOL.load(storage)?; let interval_reward_percent = crate::constants::INTERVAL_REWARD_PERCENT; - let epochs_in_interval = crate::constants::EPOCHS_IN_INTERVAL; + let epochs_in_interval = epochs_in_interval(storage)?; let epoch_reward_params = EpochRewardParams::new( (reward_pool.u128() / 100 / epochs_in_interval as u128) * interval_reward_percent as u128, diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts new file mode 100644 index 0000000000..3f47419469 --- /dev/null +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/AppEnv.ts @@ -0,0 +1,2 @@ + +export interface AppEnv { ADMIN_ADDRESS: string | null, SHOW_TERMINAL: string | null, ENABLE_QA_MODE: string | null, } \ No newline at end of file diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts new file mode 100644 index 0000000000..9c74679bac --- /dev/null +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrl.ts @@ -0,0 +1,2 @@ + +export interface ValidatorUrl { url: string, name: string | null, } \ No newline at end of file diff --git a/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts new file mode 100644 index 0000000000..2ac6693e0b --- /dev/null +++ b/nym-wallet/nym-wallet-types/nym-wallet/src/types/rust/ValidatorUrls.ts @@ -0,0 +1,2 @@ + +export interface Validator { nymd_url: string, nymd_name: string | null, api_url: string | null, } \ No newline at end of file diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index 7e1506dfab..dc75cafef1 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -51,6 +51,7 @@ struct ValidatorCacheInner { current_reward_params: Cache, current_epoch: Cache>, + current_operator_base_cost: Cache, } fn current_unix_timestamp() -> i64 { @@ -149,6 +150,7 @@ impl ValidatorCacheRefresher { { let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?; let current_epoch = self.nymd_client.get_current_epoch().await?; + let current_operator_base_cost = self.nymd_client.get_current_operator_cost().await?; let (mixnodes, gateways) = tokio::try_join!( self.nymd_client.get_mixnodes(), @@ -179,6 +181,7 @@ impl ValidatorCacheRefresher { active_set, epoch_rewarding_params, current_epoch, + current_operator_base_cost, ) .await; @@ -234,6 +237,7 @@ impl ValidatorCache { }) } + #[allow(clippy::too_many_arguments)] async fn update_cache( &self, mixnodes: Vec, @@ -242,6 +246,7 @@ impl ValidatorCache { active_set: Vec, epoch_rewarding_params: EpochRewardParams, current_epoch: Interval, + current_operator_base_cost: u64, ) { match time::timeout(Duration::from_millis(100), self.inner.write()).await { Ok(mut cache) => { @@ -251,6 +256,9 @@ impl ValidatorCache { cache.active_set.update(active_set); cache.current_reward_params.update(epoch_rewarding_params); cache.current_epoch.update(Some(current_epoch)); + cache + .current_operator_base_cost + .update(current_operator_base_cost); } Err(e) => { error!("{}", e); @@ -474,6 +482,16 @@ impl ValidatorCache { } } + pub(crate) async fn base_operator_cost(&self) -> Cache { + match time::timeout(Duration::from_millis(100), self.inner.read()).await { + Ok(cache) => cache.current_operator_base_cost.clone(), + Err(e) => { + error!("{}", e); + Cache::new(0) + } + } + } + pub async fn mixnode_details( &self, identity: IdentityKeyRef<'_>, @@ -542,6 +560,7 @@ impl ValidatorCacheInner { // setting it to a dummy value on creation is fine, as nothing will be able to ready from it // since 'initialised' flag won't be set current_epoch: Cache::new(None), + current_operator_base_cost: Cache::new(0), } } } diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 1cf9a85e5b..dc95fb2db4 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -131,6 +131,7 @@ pub(crate) async fn get_mixnode_reward_estimation( let reward_params = cache.epoch_reward_params().await; let as_at = reward_params.timestamp(); let reward_params = reward_params.into_inner(); + let base_operator_cost = cache.base_operator_cost().await.into_inner(); let current_epoch = cache.current_epoch().await.into_inner(); info!("{:?}", current_epoch); @@ -147,7 +148,10 @@ pub(crate) async fn get_mixnode_reward_estimation( let node_reward_params = NodeRewardParams::new(0, uptime.u8() as u128, status.is_active()); let reward_params = RewardParams::new(reward_params, node_reward_params); - match bond.mixnode_bond.estimate_reward(&reward_params) { + match bond + .mixnode_bond + .estimate_reward(base_operator_cost, &reward_params) + { Ok(reward_estimate) => { let reponse = RewardEstimationResponse { estimated_total_node_reward: reward_estimate.total_node_reward, diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index fa511d6c4f..380a6f890c 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -176,6 +176,13 @@ impl Client { self.0.read().await.get_current_epoch().await } + pub(crate) async fn get_current_operator_cost(&self) -> Result + where + C: CosmWasmClient + Sync, + { + self.0.read().await.get_current_operator_cost().await + } + pub(crate) async fn get_current_epoch_reward_params( &self, ) -> Result