Skip to content

Commit

Permalink
Feature/rust rewarding (#750)
Browse files Browse the repository at this point in the history
* Calculating gas fees

* Ability to set custom fees

* Added extra test

* Removed commented code

* Moved all msg types to common contract crate

* Temporarily disabling get_tx method

* Finishing up nymd client API

* Comment fix

* Remaining fee values

* Some cleanup

* Removed needless borrow

* Fixed imports in contract tests

* Moved error types around

* New ValidatorClient

* Experiment with new type of defaults

* Removed dead module

* Dealt with unwrap

* Migrated mixnode to use new validator client

* Migrated gateway to use new validator client

* Mixnode and gateway adjustments

* More exported defaults

* Clients using new validator client

* Fixed mixnode upgrade

* Moved default values to a new crate

* Changed behaviour of validator client features

* Migrated basic functions of validator api

* Updated config + fixed startup

* Fixed wasm client build

* Integration with the explorer api

* Removed tokio dev dependency

* Needless borrow

* Fixex wasm client build

* Fixed tauri client build

* Needless borrows

* New tables for rewarding

* Updated cosmos-sdk version

* Removed reward-specific node status routes

* New rewarding-specific config entries

* Additional network defaults

* Initial periodic rewards from validator api

* Replaced print with log

* Filtering nodes with uptime > 0

* Additional failure logging statements

* Fixed operation ordering

* Adjusted next rewarding epoch determination

* Modified rewarding behaviour to keep track of rewarding in progress

* Improved error message on config load failure

* Additional log statement

* Adjusted rewarding gas limit calculation

* Made naming slightly more consistent

* Fixed incorrect parentheses placement

* Fixed fee calculation

* Cargo fmt

* Removed failed merge artifacts

* Introduced comment for any future reward modification

* typos

* Helper functions for the future

* Making @mfahampshire 's life easier

* Redesigned epoch + rewarding skipped epochs (if possible)

* Removed old merge artifacts

* Naming consistency

* Constraining arguments

* Removed unnecessary if branch

* Ignore monitor check for current epoch

* Additional checks for current epoch data

* Monitor threshold check

* cargo fmt

* Fixed post-merge issues in transactions.rs
  • Loading branch information
jstuczyn authored Sep 24, 2021
1 parent 5dfaff6 commit 020cad8
Show file tree
Hide file tree
Showing 30 changed files with 2,579 additions and 335 deletions.
24 changes: 22 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions common/client-libs/validator-client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ impl<C> Client<C> {
self.mixnet_contract_address = Some(mixnet_contract_address)
}

pub fn get_mixnet_contract_address(&self) -> Option<cosmrs::AccountId> {
self.mixnet_contract_address.clone()
}

pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, Simple
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
Expand Down Expand Up @@ -256,6 +257,48 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
})
}

async fn execute_multiple<I, M>(
&self,
sender_address: &AccountId,
contract_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<ExecuteResult, NymdError>
where
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
M: Serialize,
{
let messages = msgs
.into_iter()
.map(|(msg, funds)| {
cosmwasm::MsgExecuteContract {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(&msg)?,
funds,
}
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;

let tx_res = self
.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()?;

debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);

Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
})
}

async fn send_tokens(
&self,
sender_address: &AccountId,
Expand Down
17 changes: 7 additions & 10 deletions common/client-libs/validator-client/src/nymd/fee_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
use crate::nymd::GasPrice;
use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use cosmwasm_std::Uint128;

#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Operation {
Expand All @@ -28,13 +27,7 @@ pub enum Operation {
}

pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
let limit_uint128 = Uint128::from(gas_limit.value());
let amount = gas_price.amount * limit_uint128;
assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: gas_price.denom.clone(),
amount: (amount.u128() as u64).into(),
}
gas_price * gas_limit
}

impl Operation {
Expand All @@ -61,16 +54,20 @@ impl Operation {
}
}

pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
pub(crate) fn determine_custom_fee(gas_price: &GasPrice, gas_limit: Gas) -> Fee {
// we need to know 2 of the following 3 parameters (the third one is being implicit) in order to construct Fee:
// (source: https://docs.cosmos.network/v0.42/basics/gas-fees.html)
// - gas price
// - gas limit
// - fees
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
let fee = calculate_fee(gas_price, gas_limit);
Fee::from_amount_and_gas(fee, gas_limit)
}

pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
Self::determine_custom_fee(gas_price, gas_limit)
}
}

#[cfg(test)]
Expand Down
47 changes: 45 additions & 2 deletions common/client-libs/validator-client/src/nymd/gas_price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

use crate::nymd::error::NymdError;
use config::defaults;
use cosmrs::Denom;
use cosmwasm_std::Decimal;
use cosmrs::tx::Gas;
use cosmrs::{Coin, Denom};
use cosmwasm_std::{Decimal, Fraction, Uint128};
use std::ops::Mul;
use std::str::FromStr;

/// A gas price, i.e. the price of a single unit of gas. This is typically a fraction of
Expand All @@ -18,6 +20,36 @@ pub struct GasPrice {
pub denom: Denom,
}

impl<'a> Mul<Gas> for &'a GasPrice {
type Output = Coin;

fn mul(self, gas_limit: Gas) -> Self::Output {
let limit_uint128 = Uint128::from(gas_limit.value());
let mut amount = self.amount * limit_uint128;

let gas_price_numerator = self.amount.numerator();
let gas_price_denominator = self.amount.denominator();

// gas price is a fraction of the smallest fee token unit, so we must ensure that
// for any multiplication, we have rounded up
//
// I don't really like the this solution as it has a theoretical chance of
// overflowing (internally cosmwasm uses U256 to avoid that)
// however, realistically that is impossible to happen as the resultant value
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
// and max value of u128 is approximately 10^38
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
amount += Uint128::new(1);
}

assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: self.denom.clone(),
amount: (amount.u128() as u64).into(),
}
}
}

impl FromStr for GasPrice {
type Err = NymdError;

Expand Down Expand Up @@ -78,4 +110,15 @@ mod tests {
assert!("0.025 upunk".parse::<GasPrice>().is_err());
assert!("0.025UPUNK".parse::<GasPrice>().is_err());
}

#[test]
fn gas_limit_multiplication() {
// real world example that caused an issue when the result was rounded down
let gas_price: GasPrice = "0.025upunk".parse().unwrap();
let gas_limit: Gas = 157500u64.into();

let fee = &gas_price * gas_limit;
// the failing behaviour was result value of 3937
assert_eq!(fee.amount, 3938u64.into());
}
}
31 changes: 30 additions & 1 deletion common/client-libs/validator-client/src/nymd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmrs::tx::{Fee, Gas};

use cosmwasm_std::Coin;
use mixnet_contract::{
Expand All @@ -29,6 +28,8 @@ pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::gas_price::GasPrice;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{Fee, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{AccountId, Denom};
pub use signing_client::Client as SigningNymdClient;
Expand Down Expand Up @@ -150,6 +151,17 @@ impl<C> NymdClient<C> {
operation.determine_fee(&self.gas_price, gas_limit)
}

pub fn calculate_custom_fee(&self, gas_limit: impl Into<Gas>) -> Fee {
Operation::determine_custom_fee(&self.gas_price, gas_limit.into())
}

pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.client.get_block(None).await?.block.header.time)
}

pub async fn get_balance(&self, address: &AccountId) -> Result<Option<CosmosCoin>, NymdError>
where
C: CosmWasmClient + Sync,
Expand Down Expand Up @@ -391,6 +403,23 @@ impl<C> NymdClient<C> {
.await
}

pub async fn execute_multiple<I, M>(
&self,
contract_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
I: IntoIterator<Item = (M, Vec<CosmosCoin>)> + Send,
M: Serialize,
{
self.client
.execute_multiple(self.address(), contract_address, msgs, fee, memo)
.await
}

pub async fn upload(
&self,
wasm_code: Vec<u8>,
Expand Down
1 change: 1 addition & 0 deletions common/network-defaults/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ edition = "2018"

[dependencies]
url = "2.2"
time = { version = "0.3", features = ["macros"] }
6 changes: 6 additions & 0 deletions common/network-defaults/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0

use std::time::Duration;
use time::OffsetDateTime;
use url::Url;

pub struct ValidatorDetails<'a> {
Expand Down Expand Up @@ -80,3 +82,7 @@ pub const DEFAULT_SOCKS5_LISTENING_PORT: u16 = 1080;
pub const DEFAULT_VALIDATOR_API_PORT: u16 = 8080;

pub const VALIDATOR_API_VERSION: &str = "v1";

// REWARDING
pub const DEFAULT_FIRST_EPOCH_START: OffsetDateTime = time::macros::datetime!(2021-08-23 12:00 UTC);
pub const DEFAULT_EPOCH_LENGTH: Duration = Duration::from_secs(24 * 60 * 60); // 24h
21 changes: 19 additions & 2 deletions contracts/mixnet/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion contracts/mixnet/src/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@ pub(crate) fn try_update_state_params(
Ok(Response::default())
}

// Note: if any changes are made to this function or anything it is calling down the stack,
// for example delegation reward distribution, the gas limits must be retested and both
// validator-api/src/rewarding/mod.rs::{MIXNODE_REWARD_OP_BASE_GAS_LIMIT, PER_MIXNODE_DELEGATION_GAS_INCREASE}
// must be updated appropriately.
pub(crate) fn try_reward_mixnode(
deps: DepsMut,
env: Env,
Expand Down Expand Up @@ -496,6 +500,10 @@ pub(crate) fn try_reward_mixnode(
})
}

// Note: if any changes are made to this function or anything it is calling down the stack,
// for example delegation reward distribution, the gas limits must be retested and both
// validator-api/src/rewarding/mod.rs::{GATEWAY_REWARD_OP_BASE_GAS_LIMIT, PER_GATEWAY_DELEGATION_GAS_INCREASE}
// must be updated appropriately.
pub(crate) fn try_reward_gateway(
deps: DepsMut,
env: Env,
Expand Down Expand Up @@ -524,7 +532,7 @@ pub(crate) fn try_reward_gateway(
}

// check if the bond even exists
let mut current_bond = match gateways(deps.storage).load(gateway_identity.as_bytes()) {
let mut current_bond = match gateways_read(deps.storage).load(gateway_identity.as_bytes()) {
Ok(bond) => bond,
Err(_) => {
return Ok(Response {
Expand Down
Loading

0 comments on commit 020cad8

Please sign in to comment.