Feature/active sets (#764)

* Added separate gateway active set size

* Grabbing contract state

* Defined PartialOrd on MixnodeBond and GatewayBond

* Some initial stub for active set

* Unit tests for mixnode and gateway bond partialord implementation

* Obtaining active sets

* Active nodes routes

* Additional methods on validator client

* Added state migration

* Feature locking unused import

* Fixed State test fixture

* Included block height for partial_ord

* Missing post-merge imports

* api on the client for active nodes

* Native/socks5/wasm clients using active nodes

* Rewarding only active nodes

* Updated validator client StateParams definition

* Gateway active set size

* Contract migration update

* Cargo fmt

* Updated TauriStateParams

* [ci skip] Generate TS types

Co-authored-by: jstuczyn <jstuczyn@users.noreply.github.com>
This commit is contained in:
Jędrzej Stuczyński
2021-09-28 09:38:22 +01:00
committed by GitHub
parent e891c68158
commit 668325a4ce
34 changed files with 1436 additions and 235 deletions
@@ -107,7 +107,7 @@ impl TopologyAccessor {
self.inner.read().await.into()
}
async fn update_global_topology(&mut self, new_topology: Option<NymTopology>) {
async fn update_global_topology(&self, new_topology: Option<NymTopology>) {
self.inner.write().await.update(new_topology);
}
@@ -130,19 +130,26 @@ impl Default for TopologyAccessor {
pub struct TopologyRefresherConfig {
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
client_version: String,
}
impl TopologyRefresherConfig {
pub fn new(validator_api_urls: Vec<Url>, refresh_rate: time::Duration) -> Self {
pub fn new(
validator_api_urls: Vec<Url>,
refresh_rate: time::Duration,
client_version: String,
) -> Self {
TopologyRefresherConfig {
validator_api_urls,
refresh_rate,
client_version,
}
}
}
pub struct TopologyRefresher {
validator_client: validator_client::ApiClient,
client_version: String,
validator_api_urls: Vec<Url>,
topology_accessor: TopologyAccessor,
@@ -158,6 +165,7 @@ impl TopologyRefresher {
TopologyRefresher {
validator_client: validator_client::ApiClient::new(cfg.validator_api_urls[0].clone()),
client_version: cfg.client_version,
validator_api_urls: cfg.validator_api_urls,
topology_accessor,
refresh_rate: cfg.refresh_rate,
@@ -177,12 +185,71 @@ impl TopologyRefresher {
.change_validator_api(self.validator_api_urls[self.currently_used_api].clone())
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
/// * `mixnodes_count`: total number of active mixnodes
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
mixnodes_count: usize,
) -> bool {
let mixes = active_topology.mixes();
if active_topology.gateways().is_empty() {
return false;
}
// trivial check to see if have at least a single node on each layer (regardless of active set size)
if mixes.get(&1).is_none() || mixes.get(&2).is_none() || mixes.get(&3).is_none() {
return false;
}
let upper_bound = (mixnodes_count as f32 * 0.66) as usize;
let lower_bound = (mixnodes_count as f32 * 0.15) as usize;
let layer1 = mixes.get(&1).unwrap().len();
let layer2 = mixes.get(&2).unwrap().len();
let layer3 = mixes.get(&3).unwrap().len();
if layer1 < lower_bound || layer1 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer2 < lower_bound || layer2 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
if layer3 < lower_bound || layer3 > upper_bound {
warn!(
"nodes: {}, layer1: {}, layer2: {}, layer3: {}",
mixnodes_count, layer1, layer2, layer3
);
return false;
}
true
}
async fn get_current_compatible_topology(&self) -> Option<NymTopology> {
// TODO: optimization for the future:
// only refresh mixnodes on timer and refresh gateways only when
// we have to send to a new, unknown, gateway
let mixnodes = match self.validator_client.get_cached_mixnodes().await {
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
Err(err) => {
error!("failed to get network mixnodes - {}", err);
return None;
@@ -190,7 +257,7 @@ impl TopologyRefresher {
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
let gateways = match self.validator_client.get_cached_active_gateways().await {
Err(err) => {
error!("failed to get network gateways - {}", err);
return None;
@@ -198,11 +265,16 @@ impl TopologyRefresher {
Ok(gateways) => gateways,
};
let topology = nym_topology_from_bonds(mixnodes, gateways);
let mixnodes_count = mixnodes.len();
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
// TODO: I didn't want to change it now, but the expected system version should rather be put in config
// rather than pulled from package version of `client_core`
Some(topology.filter_system_version(env!("CARGO_PKG_VERSION")))
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
None
} else {
Some(topology)
}
}
pub async fn refresh(&mut self) {
+1
View File
@@ -236,6 +236,7 @@ impl NymClient {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
+1
View File
@@ -224,6 +224,7 @@ impl NymClient {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
env!("CARGO_PKG_VERSION").to_string(),
);
let mut topology_refresher =
TopologyRefresher::new(topology_refresher_config, topology_accessor);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-validator-client",
"version": "0.17.0",
"version": "0.18.0",
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
+2 -2
View File
@@ -1,7 +1,7 @@
import { GatewayBond } from "../types";
import {GatewayBond, PagedGatewayResponse} from "../types";
import {INetClient} from "../net-client"
import {IQueryClient} from "../query-client";
import {PagedGatewayResponse, VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
+3 -3
View File
@@ -1,13 +1,13 @@
import { MixNodeBond } from "../types";
import {MixNodeBond, PagedMixnodeResponse} from "../types";
import { INetClient } from "../net-client"
import {IQueryClient} from "../query-client";
import {PagedMixnodeResponse, VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
export { MixnodesCache };
/**
* There are serious limits in smart contract systems, but we need to keep track of
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
* paged data about what mixnodes exist, and keep them locally in memory so that they're
* available for querying.
+22 -84
View File
@@ -1,9 +1,19 @@
import NetClient, { INetClient } from "./net-client";
import { Gateway, GatewayBond, MixNode, MixNodeBond, SendRequest } from "./types";
import { Bip39, Random } from "@cosmjs/crypto";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import NetClient, {INetClient} from "./net-client";
import {
StateParams,
Delegation,
PagedMixDelegationsResponse,
PagedGatewayDelegationsResponse,
MixNodeBond,
MixNode,
GatewayBond,
Gateway,
SendRequest
} from "./types";
import {Bip39, Random} from "@cosmjs/crypto";
import {DirectSecp256k1HdWallet, EncodeObject} from "@cosmjs/proto-signing";
import MixnodesCache from "./caches/mixnodes";
import { buildFeeTable, coin, Coin, coins, StdFee } from "@cosmjs/stargate";
import {buildFeeTable, coin, Coin, coins, StdFee} from "@cosmjs/stargate";
import {
ExecuteResult,
InstantiateOptions,
@@ -22,17 +32,17 @@ import {
nativeToPrintable
} from "./currency";
import GatewaysCache from "./caches/gateways";
import QueryClient, { IQueryClient } from "./query-client";
import { nymGasLimits, nymGasPrice } from "./stargate-helper";
import { BroadcastTxSuccess, isBroadcastTxFailure } from "@cosmjs/stargate";
import { makeBankMsgSend } from "./utils";
import QueryClient, {IQueryClient} from "./query-client";
import {nymGasLimits, nymGasPrice} from "./stargate-helper";
import {BroadcastTxSuccess, isBroadcastTxFailure} from "@cosmjs/stargate";
import {makeBankMsgSend} from "./utils";
export const VALIDATOR_API_PORT = "8080";
export const VALIDATOR_API_GATEWAYS = "v1/gateways";
export const VALIDATOR_API_MIXNODES = "v1/mixnodes";
export { coins, coin };
export { Coin };
export {coins, coin};
export {Coin};
export {
displayAmountToNative,
nativeCoinToDisplay,
@@ -42,7 +52,7 @@ export {
MappedCoin,
CoinMap
}
export { nymGasLimits, nymGasPrice }
export {nymGasLimits, nymGasPrice}
export default class ValidatorClient {
private readonly client: INetClient | IQueryClient
@@ -619,75 +629,3 @@ export default class ValidatorClient {
}
}
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
/// inconvenient, as we don't have the two pieces of information we need to know
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
///
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
/// finds the next record, and builds the next page starting there. This happens
/// **in series** rather than **in parallel** (!).
///
/// So we have some consistency problems:
///
/// * we can't make requests at a given block height, so the result set
/// which we assemble over time may change while requests are being made.
/// * at some point we will make a request for a `start_next_page_after` key
/// which has just been deleted from the database.
///
/// TODO: more robust error handling on the "deleted key" case.
export type PagedMixnodeResponse = {
nodes: MixNodeBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
// a temporary way of achieving the same paging behaviour for the gateways
// the same points made for `PagedResponse` stand here.
export type PagedGatewayResponse = {
nodes: GatewayBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
export type MixOwnershipResponse = {
address: string,
has_node: boolean,
}
export type GatewayOwnershipResponse = {
address: string,
has_gateway: boolean,
}
export type StateParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_bond: string,
minimum_gateway_bond: string,
mixnode_bond_reward_rate: string,
gateway_bond_reward_rate: string,
mixnode_delegation_reward_rate: string,
gateway_delegation_reward_rate: string,
mixnode_active_set_size: number,
}
export type Delegation = {
owner: string,
amount: Coin,
}
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
+1 -1
View File
@@ -6,7 +6,7 @@ import {
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./index";
} from "./types";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, StdFee } from "@cosmjs/stargate";
import { BroadcastTxResponse } from "@cosmjs/stargate"
+1 -1
View File
@@ -7,7 +7,7 @@ import {
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./index";
} from "./types";
export interface IQueryClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
+75
View File
@@ -1,5 +1,80 @@
import { Coin } from "@cosmjs/stargate";
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
/// inconvenient, as we don't have the two pieces of information we need to know
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
///
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
/// finds the next record, and builds the next page starting there. This happens
/// **in series** rather than **in parallel** (!).
///
/// So we have some consistency problems:
///
/// * we can't make requests at a given block height, so the result set
/// which we assemble over time may change while requests are being made.
/// * at some point we will make a request for a `start_next_page_after` key
/// which has just been deleted from the database.
///
/// TODO: more robust error handling on the "deleted key" case.
export type PagedMixnodeResponse = {
nodes: MixNodeBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
// a temporary way of achieving the same paging behaviour for the gateways
// the same points made for `PagedResponse` stand here.
export type PagedGatewayResponse = {
nodes: GatewayBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
export type MixOwnershipResponse = {
address: string,
has_node: boolean,
}
export type GatewayOwnershipResponse = {
address: string,
has_gateway: boolean,
}
export type StateParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_bond: string,
minimum_gateway_bond: string,
mixnode_bond_reward_rate: string,
gateway_bond_reward_rate: string,
mixnode_delegation_reward_rate: string,
gateway_delegation_reward_rate: string,
mixnode_active_set_size: number,
gateway_active_set_size: number,
}
export type Delegation = {
owner: string,
amount: Coin,
}
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export enum Layer {
Gateway,
One,
+2 -2
View File
@@ -263,12 +263,12 @@ impl NymClient {
pub(crate) async fn get_nym_topology(&self) -> NymTopology {
let validator_client = validator_client::ApiClient::new(self.validator_server.clone());
let mixnodes = match validator_client.get_cached_mixnodes().await {
let mixnodes = match validator_client.get_cached_active_mixnodes().await {
Err(err) => panic!("{}", err),
Ok(mixes) => mixes,
};
let gateways = match validator_client.get_cached_gateways().await {
let gateways = match validator_client.get_cached_active_gateways().await {
Err(err) => panic!("{}", err),
Ok(gateways) => gateways,
};
@@ -5,6 +5,9 @@
use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond};
@@ -170,6 +173,13 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_state_params().await?)
}
// basically handles paging for us
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
@@ -414,6 +424,18 @@ impl ApiClient {
self.validator_api.change_url(new_endpoint);
}
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_active_gateways(
&self,
) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_gateways().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -68,6 +68,16 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE])
.await
}
pub async fn get_active_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS, routes::ACTIVE])
.await
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -7,5 +7,7 @@ pub const API_VERSION: &str = VALIDATOR_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const ACTIVE: &str = "active";
pub const COCONUT_BLIND_SIGN: &str = "blind_sign";
pub const COCONUT_VERIFICATION_KEY: &str = "verification_key";
+147 -1
View File
@@ -5,12 +5,13 @@ use crate::{IdentityKey, SphinxKey};
use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
pub struct Gateway {
pub host: String,
pub mix_port: u16,
@@ -60,6 +61,64 @@ impl GatewayBond {
}
}
impl PartialOrd for GatewayBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != self.total_delegation.denom {
return None;
}
if other.bond_amount.denom != other.total_delegation.denom {
return None;
}
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond + delegation
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
let delegation_cmp = self
.total_delegation
.amount
.partial_cmp(&other.total_delegation.amount)?;
if delegation_cmp != Ordering::Equal {
return Some(delegation_cmp);
}
// then check block height
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
if height_cmp != Ordering::Equal {
return Some(height_cmp);
}
// finally go by the rest of the fields in order. It doesn't really matter at this point
// but we should be deterministic.
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
if owner_cmp != Ordering::Equal {
return Some(owner_cmp);
}
self.gateway.partial_cmp(&other.gateway)
}
}
impl Display for GatewayBond {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
@@ -96,3 +155,90 @@ pub struct GatewayOwnershipResponse {
pub address: Addr,
pub has_gateway: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn gateway_fixture() -> Gateway {
Gateway {
host: "1.1.1.1".to_string(),
mix_port: 123,
clients_port: 456,
location: "foomplandia".to_string(),
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
}
}
#[test]
fn gateway_bond_partial_ord() {
let _150foos = Coin::new(150, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let gate1 = GatewayBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
block_height: 100,
gateway: gateway_fixture(),
};
let gate2 = GatewayBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate3 = GatewayBond {
bond_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate4 = GatewayBond {
bond_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
block_height: 120,
gateway: gateway_fixture(),
};
let gate5 = GatewayBond {
bond_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
block_height: 120,
gateway: gateway_fixture(),
};
// summary:
// gate1: 150bond + 50delegation, foo1, 100
// gate2: 150bond + 50delegation, foo2, 120
// gate3: 50bond + 150delegation, foo3, 120
// gate4: 150bond + 0delegation, foo4, 120
// gate5: 0bond + 150delegation, foo5, 120
// highest total bond+delegation is used
// then bond followed by delegation
// finally just the rest of the fields
// gate1 has higher total than gate4 or gate5
assert!(gate1 > gate4);
assert!(gate1 > gate5);
// gate1 has the same total as gate3, however, gate1 has more tokens in bond
assert!(gate1 > gate3);
// same case for gate4 and gate5
assert!(gate4 > gate5);
// same bond and delegation, so it's just ordered by height
assert!(gate1 < gate2);
}
}
+160 -2
View File
@@ -6,12 +6,13 @@ use cosmwasm_std::{coin, Addr, Coin};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::cmp::Ordering;
use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize, JsonSchema, TS)]
pub struct MixNode {
pub host: String,
pub mix_port: u16,
@@ -23,7 +24,9 @@ pub struct MixNode {
pub version: String,
}
#[derive(Copy, Clone, Debug, Serialize_repr, PartialEq, Deserialize_repr, JsonSchema)]
#[derive(
Copy, Clone, Debug, Serialize_repr, PartialEq, PartialOrd, Deserialize_repr, JsonSchema,
)]
#[repr(u8)]
pub enum Layer {
Gateway = 0,
@@ -78,6 +81,69 @@ impl MixNodeBond {
}
}
impl PartialOrd for MixNodeBond {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// first remove invalid cases
if self.bond_amount.denom != self.total_delegation.denom {
return None;
}
if other.bond_amount.denom != other.total_delegation.denom {
return None;
}
if self.bond_amount.denom != other.bond_amount.denom {
return None;
}
// try to order by total bond + delegation
let total_cmp = (self.bond_amount.amount + self.total_delegation.amount)
.partial_cmp(&(self.bond_amount.amount + self.total_delegation.amount))?;
if total_cmp != Ordering::Equal {
return Some(total_cmp);
}
// then if those are equal, prefer higher bond over delegation
let bond_cmp = self
.bond_amount
.amount
.partial_cmp(&other.bond_amount.amount)?;
if bond_cmp != Ordering::Equal {
return Some(bond_cmp);
}
// then look at delegation (I'm not sure we can get here, but better safe than sorry)
let delegation_cmp = self
.total_delegation
.amount
.partial_cmp(&other.total_delegation.amount)?;
if delegation_cmp != Ordering::Equal {
return Some(delegation_cmp);
}
// then check block height
let height_cmp = self.block_height.partial_cmp(&other.block_height)?;
if height_cmp != Ordering::Equal {
return Some(height_cmp);
}
// finally go by the rest of the fields in order. It doesn't really matter at this point
// but we should be deterministic.
let owner_cmp = self.owner.partial_cmp(&other.owner)?;
if owner_cmp != Ordering::Equal {
return Some(owner_cmp);
}
let layer_cmp = self.layer.partial_cmp(&other.layer)?;
if layer_cmp != Ordering::Equal {
return Some(layer_cmp);
}
self.mix_node.partial_cmp(&other.mix_node)
}
}
impl Display for MixNodeBond {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
@@ -114,3 +180,95 @@ pub struct MixOwnershipResponse {
pub address: Addr,
pub has_node: bool,
}
#[cfg(test)]
mod tests {
use super::*;
fn mixnode_fixture() -> MixNode {
MixNode {
host: "1.1.1.1".to_string(),
mix_port: 123,
verloc_port: 456,
http_api_port: 789,
sphinx_key: "sphinxkey".to_string(),
identity_key: "identitykey".to_string(),
version: "0.11.0".to_string(),
}
}
#[test]
fn mixnode_bond_partial_ord() {
let _150foos = Coin::new(150, "foo");
let _50foos = Coin::new(50, "foo");
let _0foos = Coin::new(0, "foo");
let mix1 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo1"),
layer: Layer::One,
block_height: 100,
mix_node: mixnode_fixture(),
};
let mix2 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _50foos.clone(),
owner: Addr::unchecked("foo2"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
};
let mix3 = MixNodeBond {
bond_amount: _50foos,
total_delegation: _150foos.clone(),
owner: Addr::unchecked("foo3"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
};
let mix4 = MixNodeBond {
bond_amount: _150foos.clone(),
total_delegation: _0foos.clone(),
owner: Addr::unchecked("foo4"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
};
let mix5 = MixNodeBond {
bond_amount: _0foos,
total_delegation: _150foos,
owner: Addr::unchecked("foo5"),
layer: Layer::One,
block_height: 120,
mix_node: mixnode_fixture(),
};
// summary:
// mix1: 150bond + 50delegation, foo1, 100
// mix2: 150bond + 50delegation, foo2, 120
// mix3: 50bond + 150delegation, foo3, 120
// mix4: 150bond + 0delegation, foo4, 120
// mix5: 0bond + 150delegation, foo5, 120
// highest total bond+delegation is used
// then bond followed by delegation
// finally just the rest of the fields
// mix1 has higher total than mix4 or mix5
assert!(mix1 > mix4);
assert!(mix1 > mix5);
// mix1 has the same total as mix3, however, mix1 has more tokens in bond
assert!(mix1 > mix3);
// same case for mix4 and mix5
assert!(mix4 > mix5);
// same bond and delegation, so it's just ordered by height
assert!(mix1 < mix2);
}
}
+7 -1
View File
@@ -37,6 +37,7 @@ pub struct StateParams {
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_active_set_size: u32,
pub gateway_active_set_size: u32,
}
impl Display for StateParams {
@@ -67,8 +68,13 @@ impl Display for StateParams {
)?;
write!(
f,
"mixnode active set size: {} ]",
"mixnode active set size: {}",
self.mixnode_active_set_size
)?;
write!(
f,
"gateway active set size: {} ]",
self.gateway_active_set_size
)
}
}
+595 -5
View File
@@ -2,6 +2,45 @@
# 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.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93f52ce8fac3d0e6720a92b0576d737c01b1b5db4dd786e962e5925f00bf755"
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 = "base64"
version = "0.13.0"
@@ -38,6 +77,12 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "bumpalo"
version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9df67f7bf9ef8498769f994239c45613ef0c5899415fb58e9add412d2c1a538"
[[package]]
name = "byte-tools"
version = "0.3.1"
@@ -50,6 +95,12 @@ 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"
@@ -178,6 +229,41 @@ 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.0"
@@ -205,6 +291,45 @@ 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"
@@ -237,6 +362,12 @@ 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.4"
@@ -253,6 +384,18 @@ 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"
@@ -269,6 +412,12 @@ dependencies = [
"subtle",
]
[[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"
@@ -279,6 +428,27 @@ 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"
@@ -304,7 +474,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
@@ -315,7 +485,7 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
@@ -377,6 +547,12 @@ 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"
@@ -388,6 +564,19 @@ dependencies = [
"unicode-normalization",
]
[[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.7"
@@ -400,12 +589,18 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "008b0281ca8032567c9711cd48631781c15228301860a39b32deb28d63125e46"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"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.100"
@@ -418,7 +613,7 @@ version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
]
[[package]]
@@ -433,6 +628,12 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[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"
@@ -441,6 +642,7 @@ dependencies = [
"schemars",
"serde",
"serde_repr",
"ts-rs",
]
[[package]]
@@ -461,10 +663,54 @@ dependencies = [
name = "network-defaults"
version = "0.1.0"
dependencies = [
"serde",
"time",
"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"
@@ -477,6 +723,15 @@ 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 = "percent-encoding"
version = "2.1.0"
@@ -526,6 +781,25 @@ 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.5"
@@ -536,6 +810,29 @@ 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.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[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.24"
@@ -560,6 +857,30 @@ 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"
@@ -578,6 +899,41 @@ 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 = "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"
@@ -608,6 +964,12 @@ dependencies = [
"syn",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "serde"
version = "1.0.122"
@@ -689,7 +1051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer 0.9.0",
"cfg-if",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
@@ -705,6 +1067,18 @@ 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.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "spki"
version = "0.4.0"
@@ -714,18 +1088,206 @@ 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.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "swc_atoms"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "837a3ef86c2817228e733b6f173c821fd76f9eb21a0bc9001a826be48b00b4e7"
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.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ed2e930f5a1a4071fe62c90fd3a296f6030e5d94bfe13993244423caf59a78"
dependencies = [
"pmutil",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "swc_visit"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a423caa0b4585118164dbad8f1ad52b592a9a9370b25decc4d84c6b4309132c0"
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.65"
@@ -797,6 +1359,28 @@ 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.13.0"
@@ -839,6 +1423,12 @@ 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.1"
+51 -64
View File
@@ -10,9 +10,9 @@ use cosmwasm_std::{
entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Uint128,
};
use mixnet_contract::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RawDelegationData, StateParams,
};
use cosmwasm_storage::singleton_read;
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
use serde::{Deserialize, Serialize};
pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2;
@@ -29,6 +29,7 @@ pub const INITIAL_MIXNODE_DELEGATION_REWARD_RATE: u64 = 110;
pub const INITIAL_GATEWAY_DELEGATION_REWARD_RATE: u64 = 110;
pub const INITIAL_MIXNODE_ACTIVE_SET_SIZE: u32 = 100;
pub const INITIAL_GATEWAY_ACTIVE_SET_SIZE: u32 = 20;
fn default_initial_state(owner: Addr) -> State {
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
@@ -48,6 +49,7 @@ fn default_initial_state(owner: Addr) -> State {
mixnode_delegation_reward_rate,
gateway_delegation_reward_rate,
mixnode_active_set_size: INITIAL_MIXNODE_ACTIVE_SET_SIZE,
gateway_active_set_size: INITIAL_GATEWAY_ACTIVE_SET_SIZE,
},
mixnode_epoch_bond_reward: calculate_epoch_reward_rate(
INITIAL_DEFAULT_EPOCH_LENGTH,
@@ -207,69 +209,54 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
}
#[entry_point]
pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
use crate::storage::{
gateway_delegations, gateway_delegations_read, gateway_delegations_read_old, gateways_read,
mix_delegations, mix_delegations_read, mix_delegations_read_old, mixnodes_read,
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
#[derive(Serialize, Deserialize)]
struct OldStateParams {
epoch_length: u32,
minimum_mixnode_bond: Uint128,
minimum_gateway_bond: Uint128,
mixnode_bond_reward_rate: Decimal,
gateway_bond_reward_rate: Decimal,
mixnode_delegation_reward_rate: Decimal,
gateway_delegation_reward_rate: Decimal,
mixnode_active_set_size: u32,
}
// adding gateways active set
#[derive(Serialize, Deserialize)]
struct OldState {
owner: Addr,
network_monitor_address: Addr,
params: OldStateParams,
mixnode_epoch_bond_reward: Decimal,
gateway_epoch_bond_reward: Decimal,
mixnode_epoch_delegation_reward: Decimal,
gateway_epoch_delegation_reward: Decimal,
}
let old_state: OldState = singleton_read(deps.storage, b"config").load()?;
let new_state = State {
owner: old_state.owner,
network_monitor_address: old_state.network_monitor_address,
params: StateParams {
epoch_length: old_state.params.epoch_length,
minimum_mixnode_bond: old_state.params.minimum_mixnode_bond,
minimum_gateway_bond: old_state.params.minimum_gateway_bond,
mixnode_bond_reward_rate: old_state.params.mixnode_bond_reward_rate,
gateway_bond_reward_rate: old_state.params.gateway_bond_reward_rate,
mixnode_delegation_reward_rate: old_state.params.mixnode_delegation_reward_rate,
gateway_delegation_reward_rate: old_state.params.gateway_delegation_reward_rate,
mixnode_active_set_size: old_state.params.mixnode_active_set_size,
gateway_active_set_size: INITIAL_GATEWAY_ACTIVE_SET_SIZE,
},
mixnode_epoch_bond_reward: old_state.mixnode_epoch_bond_reward,
gateway_epoch_bond_reward: old_state.gateway_epoch_bond_reward,
mixnode_epoch_delegation_reward: old_state.mixnode_epoch_delegation_reward,
gateway_epoch_delegation_reward: old_state.gateway_epoch_delegation_reward,
};
use crate::transactions::delegations;
use cosmwasm_std::{Order, StdResult};
use mixnet_contract::{GatewayBond, MixNodeBond};
// Read existing delegations data, drop invalid values, and rewrite delegations data with valid data only
fn overwrite_mixnode_delegations_data(
identity: &str,
deps: &mut DepsMut,
) -> Result<(), ContractError> {
let delegations_bucket = mix_delegations_read(deps.storage, identity);
let old_delegations_bucket = mix_delegations_read_old(deps.storage, identity);
let mut delegations_vec = delegations(delegations_bucket)?;
let old_delegations = delegations::<Uint128>(old_delegations_bucket)?;
for delegation in old_delegations {
delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1)))
}
for (key, delegation) in delegations_vec {
mix_delegations(deps.storage, identity).save(&key, &delegation)?;
}
Ok(())
}
fn overwrite_gateway_delegations_data(
identity: &str,
deps: &mut DepsMut,
) -> Result<(), ContractError> {
let delegations_bucket = gateway_delegations_read(deps.storage, identity);
let old_delegations_bucket = gateway_delegations_read_old(deps.storage, identity);
let mut delegations_vec = delegations(delegations_bucket)?;
let old_delegations = delegations::<Uint128>(old_delegations_bucket)?;
for delegation in old_delegations {
delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1)))
}
for (key, delegation) in delegations_vec {
gateway_delegations(deps.storage, identity).save(&key, &delegation)?;
}
Ok(())
}
let mixnet_bonds = mixnodes_read(deps.storage)
.range(None, None, Order::Ascending)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<MixNodeBond>>>()?;
for bond in mixnet_bonds {
overwrite_mixnode_delegations_data(bond.identity(), &mut deps)?;
}
let gateway_bonds = gateways_read(deps.storage)
.range(None, None, Order::Ascending)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<GatewayBond>>>()?;
for bond in gateway_bonds {
overwrite_gateway_delegations_data(bond.identity(), &mut deps)?;
}
config(deps.storage).save(&new_state)?;
Ok(Default::default())
}
+1
View File
@@ -643,6 +643,7 @@ mod tests {
mixnode_delegation_reward_rate: "7.89".parse().unwrap(),
gateway_delegation_reward_rate: "0.12".parse().unwrap(),
mixnode_active_set_size: 1000,
gateway_active_set_size: 20,
},
mixnode_epoch_bond_reward: "1.23".parse().unwrap(),
gateway_epoch_bond_reward: "4.56".parse().unwrap(),
-18
View File
@@ -318,14 +318,6 @@ pub fn mix_delegations_read<'a>(
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
// https://github.com/nymtech/nym/blob/122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7/contracts/mixnet/src/storage.rs
pub fn mix_delegations_read_old<'a>(
storage: &'a dyn Storage,
mix_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> {
Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
}
@@ -357,16 +349,6 @@ pub fn gateway_delegations_read<'a>(
)
}
pub fn gateway_delegations_read_old<'a>(
storage: &'a dyn Storage,
gateway_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(
storage,
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
)
}
pub fn reverse_gateway_delegations<'a>(
storage: &'a mut dyn Storage,
owner: &Addr,
+1
View File
@@ -1658,6 +1658,7 @@ pub mod tests {
INITIAL_GATEWAY_DELEGATION_REWARD_RATE,
),
mixnode_active_set_size: 42, // change something
gateway_active_set_size: 5,
};
// cannot be updated from non-owner account
@@ -20,6 +20,7 @@ pub struct TauriStateParams {
mixnode_delegation_reward_rate: String,
gateway_delegation_reward_rate: String,
mixnode_active_set_size: u32,
gateway_active_set_size: u32,
}
impl From<StateParams> for TauriStateParams {
@@ -33,6 +34,7 @@ impl From<StateParams> for TauriStateParams {
mixnode_delegation_reward_rate: p.mixnode_delegation_reward_rate.to_string(),
gateway_delegation_reward_rate: p.gateway_delegation_reward_rate.to_string(),
mixnode_active_set_size: p.mixnode_active_set_size,
gateway_active_set_size: p.gateway_active_set_size,
}
}
}
@@ -50,6 +52,7 @@ impl TryFrom<TauriStateParams> for StateParams {
mixnode_delegation_reward_rate: Decimal::from_str(p.mixnode_delegation_reward_rate.as_str())?,
gateway_delegation_reward_rate: Decimal::from_str(p.gateway_delegation_reward_rate.as_str())?,
mixnode_active_set_size: p.mixnode_active_set_size,
gateway_active_set_size: p.gateway_active_set_size,
})
}
}
@@ -7,4 +7,5 @@ export interface TauriStateParams {
mixnode_delegation_reward_rate: string;
gateway_delegation_reward_rate: string;
mixnode_active_set_size: number;
gateway_active_set_size: number;
}
+150 -5
View File
@@ -4,10 +4,10 @@
use crate::nymd_client::Client;
use anyhow::Result;
use config::defaults::VALIDATOR_API_VERSION;
use mixnet_contract::{GatewayBond, MixNodeBond};
use mixnet_contract::{GatewayBond, MixNodeBond, StateParams};
use rocket::fairing::AdHoc;
use serde::Serialize;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
@@ -31,6 +31,12 @@ struct ValidatorCacheInner {
initialised: AtomicBool,
mixnodes: RwLock<Cache<Vec<MixNodeBond>>>,
gateways: RwLock<Cache<Vec<GatewayBond>>>,
active_mixnodes_available: AtomicBool,
active_gateways_available: AtomicBool,
current_mixnode_active_set_size: AtomicUsize,
current_gateway_active_set_size: AtomicUsize,
}
#[derive(Default, Serialize, Clone)]
@@ -75,13 +81,17 @@ impl<C> ValidatorCacheRefresher<C> {
self.nymd_client.get_gateways()
)?;
let state_params = self.nymd_client.get_state_params().await?;
info!(
"Updating validator cache. There are {} mixnodes and {} gateways",
mixnodes.len(),
gateways.len()
);
self.cache.update_cache(mixnodes, gateways).await;
self.cache
.update_cache(mixnodes, gateways, state_params)
.await;
Ok(())
}
@@ -117,12 +127,95 @@ impl ValidatorCache {
rocket.manage(Self::new()).mount(
// this format! is so ugly...
format!("/{}", VALIDATOR_API_VERSION),
routes![routes::get_mixnodes, routes::get_gateways],
routes![
routes::get_mixnodes,
routes::get_gateways,
routes::get_active_mixnodes,
routes::get_active_gateways
],
)
})
}
async fn update_cache(&self, mixnodes: Vec<MixNodeBond>, gateways: Vec<GatewayBond>) {
// TODO: check if all nodes can be compared together,
// i.e. they all have the same denom for bonds and delegations
fn verify_mixnodes(&self, mixnodes: &[MixNodeBond]) -> bool {
if mixnodes.is_empty() {
return true;
}
let expected_denom = &mixnodes[0].bond_amount.denom;
for mixnode in mixnodes {
if &mixnode.bond_amount.denom != expected_denom
|| &mixnode.total_delegation.denom != expected_denom
{
return false;
}
}
true
}
// TODO: check if all nodes can be compared together,
// i.e. they all have the same denom for bonds and delegations
fn verify_gateways(&self, gateways: &[GatewayBond]) -> bool {
if gateways.is_empty() {
return true;
}
let expected_denom = &gateways[0].bond_amount.denom;
for gateway in gateways {
if &gateway.bond_amount.denom != expected_denom
|| &gateway.total_delegation.denom != expected_denom
{
return false;
}
}
false
}
async fn update_cache(
&self,
mut mixnodes: Vec<MixNodeBond>,
mut gateways: Vec<GatewayBond>,
state: StateParams,
) {
// if our data is valid, it means the active sets are available,
// otherwise we must explicitly indicate nobody can read this data
if self.verify_mixnodes(&mixnodes) {
// partial_cmp can only fail if the nodes have different denomination,
// but we just checked for that hence the unwraps are fine here
// Note the reverse order of comparison so that the "highest" node would be first
mixnodes.sort_by(|a, b| b.partial_cmp(a).unwrap());
self.inner
.active_mixnodes_available
.store(true, Ordering::SeqCst);
self.inner
.current_mixnode_active_set_size
.store(state.mixnode_active_set_size as usize, Ordering::SeqCst);
} else {
self.inner
.active_mixnodes_available
.store(false, Ordering::SeqCst);
}
if self.verify_gateways(&gateways) {
// partial_cmp can only fail if the nodes have different denomination,
// but we just checked for that hence the unwraps are fine here
// Note the reverse order of comparison so that the "highest" node would be first
gateways.sort_by(|a, b| b.partial_cmp(a).unwrap());
self.inner
.active_gateways_available
.store(true, Ordering::SeqCst);
self.inner
.current_gateway_active_set_size
.store(state.gateway_active_set_size as usize, Ordering::SeqCst);
} else {
self.inner
.active_gateways_available
.store(false, Ordering::SeqCst);
}
self.inner.mixnodes.write().await.set(mixnodes);
self.inner.gateways.write().await.set(gateways);
}
@@ -135,6 +228,54 @@ impl ValidatorCache {
self.inner.gateways.read().await.clone()
}
pub async fn active_mixnodes(&self) -> Option<Cache<Vec<MixNodeBond>>> {
// if active set is available, it means it is already sorted
if self.inner.active_mixnodes_available.load(Ordering::SeqCst) {
let cache = self.inner.mixnodes.read().await;
let timestamp = cache.as_at;
let nodes = cache
.value
.iter()
.take(
self.inner
.current_mixnode_active_set_size
.load(Ordering::SeqCst),
)
.cloned()
.collect();
Some(Cache {
value: nodes,
as_at: timestamp,
})
} else {
None
}
}
pub async fn active_gateways(&self) -> Option<Cache<Vec<GatewayBond>>> {
// if active set is available, it means it is already sorted
if self.inner.active_gateways_available.load(Ordering::SeqCst) {
let cache = self.inner.gateways.read().await;
let timestamp = cache.as_at;
let nodes = cache
.value
.iter()
.take(
self.inner
.current_gateway_active_set_size
.load(Ordering::SeqCst),
)
.cloned()
.collect();
Some(Cache {
value: nodes,
as_at: timestamp,
})
} else {
None
}
}
pub fn initialised(&self) -> bool {
self.inner.initialised.load(Ordering::Relaxed)
}
@@ -158,6 +299,10 @@ impl ValidatorCacheInner {
initialised: AtomicBool::new(false),
mixnodes: RwLock::new(Cache::default()),
gateways: RwLock::new(Cache::default()),
active_mixnodes_available: AtomicBool::new(false),
active_gateways_available: AtomicBool::new(false),
current_mixnode_active_set_size: Default::default(),
current_gateway_active_set_size: Default::default(),
}
}
}
+14
View File
@@ -15,3 +15,17 @@ pub(crate) async fn get_mixnodes(cache: &State<ValidatorCache>) -> Json<Vec<MixN
pub(crate) async fn get_gateways(cache: &State<ValidatorCache>) -> Json<Vec<GatewayBond>> {
Json(cache.gateways().await.value)
}
#[get("/mixnodes/active")]
pub(crate) async fn get_active_mixnodes(
cache: &State<ValidatorCache>,
) -> Option<Json<Vec<MixNodeBond>>> {
cache.active_mixnodes().await.map(|cache| Json(cache.value))
}
#[get("/gateways/active")]
pub(crate) async fn get_active_gateways(
cache: &State<ValidatorCache>,
) -> Option<Json<Vec<GatewayBond>>> {
cache.active_gateways().await.map(|cache| Json(cache.value))
}
+8 -1
View File
@@ -8,7 +8,7 @@ use crate::rewarding::{
PER_MIXNODE_DELEGATION_GAS_INCREASE, REWARDING_GAS_LIMIT_MULTIPLIER,
};
use config::defaults::DEFAULT_VALIDATOR_API_PORT;
use mixnet_contract::{Delegation, ExecuteMsg, GatewayBond, IdentityKey, MixNodeBond};
use mixnet_contract::{Delegation, ExecuteMsg, GatewayBond, IdentityKey, MixNodeBond, StateParams};
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::{
@@ -106,6 +106,13 @@ impl<C> Client<C> {
self.0.read().await.get_all_nymd_gateways().await
}
pub(crate) async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
self.0.read().await.get_state_params().await
}
pub(crate) async fn get_mixnode_delegations(
&self,
identity: IdentityKey,
+48 -25
View File
@@ -170,7 +170,7 @@ impl Rewarder {
/// 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_mixnode_delegators_map(
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
@@ -189,8 +189,13 @@ impl Rewarder {
// instantaneous.
let mut map = HashMap::new();
let bonded_mixnodes = self.validator_cache.mixnodes().await.into_inner();
for mix in bonded_mixnodes.into_iter() {
let active_bonded_mixnodes = self
.validator_cache
.active_mixnodes()
.await
.ok_or(RewardingError::NoMixnodesToReward)?
.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?;
@@ -202,14 +207,19 @@ impl Rewarder {
/// Queries the smart contract in order to obtain the current list of bonded gateways and then
/// for each gateway determines how many delegators it has.
async fn produce_gateway_delegators_map(
async fn produce_active_gateway_delegators_map(
&self,
) -> Result<HashMap<IdentityKey, usize>, RewardingError> {
// look at comments in `produce_mixnode_delegators_map` for some optimisation elaboration
let mut map = HashMap::new();
let bonded_gateways = self.validator_cache.gateways().await.into_inner();
for gateway in bonded_gateways.into_iter() {
let active_bonded_gateways = self
.validator_cache
.active_gateways()
.await
.ok_or(RewardingError::NoGatewaysToReward)?
.into_inner();
for gateway in active_bonded_gateways.into_iter() {
let delegator_count = self
.get_gateway_delegators_count(gateway.gateway.identity_key.clone())
.await?;
@@ -237,8 +247,8 @@ impl Rewarder {
/// 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 only checked whether the node
/// is currently bonded and has uptime > 0.
/// 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.
///
@@ -259,10 +269,10 @@ 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_mixnode_delegators_map().await?;
let mixnode_delegators = self.produce_active_mixnode_delegators_map().await?;
// 1. go through all active mixnodes
// 2. filter out nodes that are currently not bonded (as `mixnode_delegators` was obtained by
// 2. filter out nodes that are currently not in the active set (as `mixnode_delegators` was obtained by
// querying the validator)
// 3. determine uptime and attach delegators count
let eligible_nodes = active_mixnodes
@@ -286,8 +296,8 @@ impl Rewarder {
/// Given the list of gateways 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 only checked whether the node
/// is currently bonded and has uptime > 0.
/// 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 non-mixing ports are open.
///
/// The method also obtains the number of delegators towards the node in order to more accurately
@@ -301,7 +311,7 @@ impl Rewarder {
&self,
active_gateways: &[GatewayStatusReport],
) -> Result<Vec<GatewayToReward>, RewardingError> {
let gateway_delegators = self.produce_gateway_delegators_map().await?;
let gateway_delegators = self.produce_active_gateway_delegators_map().await?;
let eligible_nodes = active_gateways
.iter()
@@ -329,7 +339,7 @@ impl Rewarder {
/// # Arguments
///
/// * `epoch`: the specified epoch.
async fn get_active_nodes(
async fn get_active_monitor_nodes(
&self,
epoch: Epoch,
) -> Result<(Vec<MixnodeStatusReport>, Vec<GatewayStatusReport>), RewardingError> {
@@ -450,25 +460,29 @@ impl Rewarder {
///
/// * `epoch_rewarding_id`: id of the current epoch rewarding.
///
/// * `active_mixnodes`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
/// * `active_monitor_mixnodes`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
///
/// * `active_gateways`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
/// * `active_monitor_gateways`: list of the nodes that were tested at least once by the network monitor
/// in the last epoch.
async fn distribute_rewards(
&self,
epoch_rewarding_id: i64,
active_mixnodes: &[MixnodeStatusReport],
active_gateways: &[GatewayStatusReport],
active_monitor_mixnodes: &[MixnodeStatusReport],
active_monitor_gateways: &[GatewayStatusReport],
) -> Result<(RewardingReport, Option<FailureData>), RewardingError> {
let mut failure_data = FailureData::default();
let eligible_mixnodes = self.determine_eligible_mixnodes(active_mixnodes).await?;
let eligible_mixnodes = self
.determine_eligible_mixnodes(active_monitor_mixnodes)
.await?;
if eligible_mixnodes.is_empty() {
return Err(RewardingError::NoMixnodesToReward);
}
let eligible_gateways = self.determine_eligible_gateways(active_gateways).await?;
let eligible_gateways = self
.determine_eligible_gateways(active_monitor_gateways)
.await?;
if eligible_gateways.is_empty() {
return Err(RewardingError::NoGatewaysToReward);
}
@@ -718,7 +732,8 @@ impl Rewarder {
);
// get nodes that were active during the epoch
let (active_mixnodes, active_gateways) = self.get_active_nodes(epoch).await?;
let (active_monitor_mixnodes, active_monitor_gateways) =
self.get_active_monitor_nodes(epoch).await?;
// insert information about beginning the procedure (so that if we crash during it,
// we wouldn't attempt to possibly double reward operators)
@@ -728,7 +743,11 @@ impl Rewarder {
.await?;
let (report, failure_data) = self
.distribute_rewards(epoch_rewarding_id, &active_mixnodes, &active_gateways)
.distribute_rewards(
epoch_rewarding_id,
&active_monitor_mixnodes,
&active_monitor_gateways,
)
.await?;
self.storage
@@ -764,7 +783,11 @@ impl Rewarder {
"Updating historical daily uptimes of all nodes and purging old status reports..."
);
self.storage
.update_historical_uptimes(&epoch_iso_8601, &active_mixnodes, &active_gateways)
.update_historical_uptimes(
&epoch_iso_8601,
&active_monitor_mixnodes,
&active_monitor_gateways,
)
.await?;
self.storage.purge_old_statuses(two_days_ago).await?;
}
+3 -3
View File
@@ -443,7 +443,7 @@ impl NodeStatusStorage {
Ok(run_count as usize)
}
/// Given lists of reports of all active mixnodes and gateways, inserts the data into the
/// Given lists of reports of all monitor-active mixnodes and gateways, inserts the data into the
/// historical uptime tables.
///
/// This method is called at every reward cycle. Note that currently to work as expected, it
@@ -453,8 +453,8 @@ impl NodeStatusStorage {
/// # Arguments
///
/// * `today_iso_8601`: today's date expressed in ISO 8601, i.e. YYYY-MM-DD
/// * `mixnode_reports`: slice of reports for all active mixnodes
/// * `gateway_reports`: slice of reports for all active gateways
/// * `mixnode_reports`: slice of reports for all monitor-active mixnodes
/// * `gateway_reports`: slice of reports for all monitor-active gateways
pub(crate) async fn update_historical_uptimes(
&self,
today_iso_8601: &str,
+12 -2
View File
@@ -102,13 +102,23 @@ export default function AdminForm(props: AdminFormProps) {
<Grid item xs={12}>
<TextField
required
id="active_set"
name="active_set"
id="mixnode_active_set"
name="mixnode_active_set"
label="Mixnode Active Set Size"
defaultValue={props.currentParams.mixnode_active_set_size}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
required
id="gateway_active_set"
name="gateway_active_set"
label="Gateway Active Set Size"
defaultValue={props.currentParams.gateway_active_set_size}
fullWidth
/>
</Grid>
</Grid>
<div className={classes.buttons}>
<Button
+4
View File
@@ -1,2 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+1 -1
View File
@@ -15,7 +15,7 @@
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
"@nymproject/nym-validator-client": "0.17.0",
"@nymproject/nym-validator-client": "0.18.0",
"@types/react-dom": "^17.0.3",
"bs58": "^4.0.1",
"next": "11.1.1",
+2 -1
View File
@@ -84,7 +84,8 @@ export default function Admin() {
mixnode_delegation_reward_rate: event.target.mix_delegation_reward.value,
gateway_delegation_reward_rate: event.target.gateway_delegation_reward.value,
epoch_length: parseInt(event.target.epoch_length.value),
mixnode_active_set_size: parseInt(event.target.active_set.value),
mixnode_active_set_size: parseInt(event.target.mixnode_active_set.value),
gateway_active_set_size: parseInt(event.target.gateway_active_set.value),
};
setUpdatingState(true)
await client.updateStateParams(newState)
+4 -3
View File
@@ -1356,9 +1356,10 @@
dependencies:
"@napi-rs/triples" "^1.0.3"
"@nymproject/nym-validator-client@0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz#b03af24295cbd2f1b94ffd0c38c5a493e994d111"
"@nymproject/nym-validator-client@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.18.0.tgz#4dd72bafdf6c72b603242f32c0bb9a1f9e475b98"
integrity sha512-FO1T15S2BJVuMoPA2yOaH40aD3hKJPKJVyX5ix7eJEbBWIdsYNoVeVc/soHhaAU1FIy2uU0G/FZQkaUYJGGb7Q==
dependencies:
"@cosmjs/cosmwasm-stargate" "^0.25.5"
"@cosmjs/math" "^0.25.5"