merge with wallet-delegation-ui-squashed

This commit is contained in:
Jędrzej Stuczyński
2022-06-06 17:18:48 +01:00
104 changed files with 2441 additions and 829 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ on:
jobs:
build:
runs-on: [ self-hosted, custom-linux-exoscale ]
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
+1 -1
View File
@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: [ self-hosted, custom-linux-exoscale ]
runs-on: [ self-hosted, custom-linux ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
+3 -1
View File
@@ -10,7 +10,9 @@ on:
jobs:
build:
runs-on: [ self-hosted, custom-linux-exoscale ]
runs-on: [ self-hosted, custom-linux ]
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
+19 -12
View File
@@ -4,37 +4,43 @@
### Added
- wallet: require password to switch accounts
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261])
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]).
- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284])
- validator-api: add detailed mixnode bond endpoints, and explorer-api makes use of that data to append stake saturation.
- validator-api: add Swagger to document the REST API ([#1249]).
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: compound and claim reward endpoints for operators and delegators ([#1302])
- wallet: require password to switch accounts
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- validator-api: add Swagger to document the REST API ([#1249]).
- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284])
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]).
### Fixed
- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- mixnet-contract: `estimated_delegator_reward` calculation ([#1284])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257])
- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
- mixnode, gateway: attempting to determine reconnection backoff to persistently failing mixnode could result in a crash ([#1260])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
### Changed
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
[#1257]: https://github.com/nymtech/nym/pull/1257
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1260]: https://github.com/nymtech/nym/pull/1260
[#1261]: https://github.com/nymtech/nym/pull/1261
[#1265]: https://github.com/nymtech/nym/pull/1265
[#1267]: https://github.com/nymtech/nym/pull/1267
[#1275]: https://github.com/nymtech/nym/pull/1275
@@ -42,6 +48,7 @@
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
[#1295]: https://github.com/nymtech/nym/pull/1295
[#1302]: https://github.com/nymtech/nym/pull/1302
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
Generated
+63 -2
View File
@@ -873,7 +873,6 @@ dependencies = [
"bip39",
"cfg-if 0.1.10",
"clap 3.1.8",
"coconut-bandwidth-contract-common",
"coconut-interface",
"credential-storage",
"credentials",
@@ -1166,6 +1165,42 @@ dependencies = [
"serde",
]
[[package]]
name = "cw-utils"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw3"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef"
dependencies = [
"cosmwasm-std",
"cw-utils",
"schemars",
"serde",
]
[[package]]
name = "cw4"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"serde",
]
[[package]]
name = "darling"
version = "0.13.4"
@@ -1567,6 +1602,14 @@ dependencies = [
"uint",
]
[[package]]
name = "execute"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "explorer-api"
version = "1.0.1"
@@ -1574,6 +1617,7 @@ dependencies = [
"chrono",
"humantime-serde",
"isocountry",
"itertools",
"log",
"mixnet-contract-common",
"network-defaults",
@@ -1950,7 +1994,6 @@ dependencies = [
name = "gateway-requests"
version = "0.1.0"
dependencies = [
"bincode",
"bs58",
"coconut-interface",
"credentials",
@@ -2909,6 +2952,18 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "multisig-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-utils",
"cw3",
"cw4",
"schemars",
"serde",
]
[[package]]
name = "native-tls"
version = "0.2.10"
@@ -3239,6 +3294,7 @@ dependencies = [
"coconut-interface",
"config",
"console-subscriber",
"cosmwasm-std",
"credential-storage",
"credentials",
"crypto",
@@ -3250,6 +3306,7 @@ dependencies = [
"humantime-serde",
"log",
"mixnet-contract-common",
"multisig-contract-common",
"nymcoconut",
"nymsphinx",
"okapi",
@@ -6283,16 +6340,20 @@ dependencies = [
"async-trait",
"base64",
"bip39",
"coconut-bandwidth-contract-common",
"coconut-interface",
"colored",
"config",
"cosmrs",
"cosmwasm-std",
"cw3",
"execute",
"flate2",
"futures",
"itertools",
"log",
"mixnet-contract-common",
"multisig-contract-common",
"network-defaults",
"prost 0.10.3",
"reqwest",
+2
View File
@@ -31,10 +31,12 @@ members = [
"common/credentials",
"common/crypto",
"common/crypto/dkg",
"common/execute",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/mixnode-common",
"common/network-defaults",
-1
View File
@@ -17,7 +17,6 @@ thiserror = "1.0"
url = "2.2"
tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
File diff suppressed because one or more lines are too long
+20 -31
View File
@@ -1,60 +1,49 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::Result;
use crate::{CONTRACT_ADDRESS, MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use coconut_bandwidth_contract_common::deposit::DepositData;
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
use network_defaults::DEFAULT_NETWORK;
use std::str::FromStr;
use url::Url;
use validator_client::nymd::{AccountId, Coin, Denom, NymdClient, SigningNymdClient};
use crate::error::Result;
use crate::{MNEMONIC, NYMD_URL};
use network_defaults::{DEFAULT_NETWORK, DENOM, VOUCHER_INFO};
use validator_client::nymd::traits::CoconutBandwidthSigningClient;
use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient};
pub(crate) struct Client {
nymd_client: NymdClient<SigningNymdClient>,
denom: Denom,
contract_address: AccountId,
}
impl Client {
pub fn new() -> Self {
let nymd_url = Url::from_str(NYMD_URL).unwrap();
let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap();
let nymd_client = NymdClient::connect_with_mnemonic(
DEFAULT_NETWORK,
nymd_url.as_ref(),
None,
None,
None,
mnemonic,
None,
)
.unwrap();
let denom = Denom::from_str(network_defaults::MIX_DENOM.base).unwrap();
let contract_address = AccountId::from_str(CONTRACT_ADDRESS).unwrap();
let nymd_client =
NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None)
.unwrap();
Client {
nymd_client,
denom,
contract_address,
}
Client { nymd_client }
}
pub async fn deposit(
&self,
amount: u64,
info: &str,
verification_key: String,
encryption_key: String,
fee: Option<Fee>,
) -> Result<String> {
let req = ExecuteMsg::DepositFunds {
data: DepositData::new(info.to_string(), verification_key, encryption_key),
};
let funds = vec![Coin::new(amount as u128, self.denom.to_string())];
let amount = Coin::new(amount as u128, DENOM.to_string());
Ok(self
.nymd_client
.execute(&self.contract_address, &req, Default::default(), "", funds)
.deposit(
amount,
String::from(VOUCHER_INFO),
verification_key,
encryption_key,
fee,
)
.await?
.transaction_hash
.to_string())
+1 -1
View File
@@ -55,9 +55,9 @@ impl Execute for Deposit {
let tx_hash = client
.deposit(
self.amount,
VOUCHER_INFO,
signing_keypair.public_key.clone(),
encryption_keypair.public_key.clone(),
None,
)
.await?;
@@ -83,7 +83,7 @@ impl GatewayClient {
) -> Self {
GatewayClient {
authenticated: false,
disabled_credentials_mode: true,
disabled_credentials_mode: false,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -100,8 +100,8 @@ impl GatewayClient {
}
}
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
self.disabled_credentials_mode = disabled_credentials_mode
pub fn set_disabled_credentials_mode(&mut self, _disabled_credentials_mode: bool) {
self.disabled_credentials_mode = false;
}
// TODO: later convert into proper builder methods
@@ -496,7 +496,6 @@ impl GatewayClient {
self.shared_key.as_ref().unwrap(),
iv,
)
.ok_or(GatewayClientError::SerializeCredential)?
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
@@ -10,8 +10,11 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
colored = "2.0"
cw3 = "0.13.1"
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -38,6 +41,7 @@ flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0-beta8", optional = true }
execute = { path = "../../execute" }
ts-rs = "6.1.2"
+108 -60
View File
@@ -2,16 +2,20 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use coconut_interface::{
BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody,
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use url::Url;
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::UptimeResponse;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
#[cfg(feature = "nymd-client")]
use network_defaults::DEFAULT_NETWORK;
@@ -29,8 +33,6 @@ use mixnet_contract_common::{
};
#[cfg(feature = "nymd-client")]
use std::collections::{HashMap, HashSet};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
#[cfg(feature = "nymd-client")]
#[must_use]
@@ -39,9 +41,9 @@ pub struct Config {
network: network_defaults::all::Network,
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
mixnet_contract_address: cosmrs::AccountId,
vesting_contract_address: cosmrs::AccountId,
bandwidth_claim_contract_address: cosmrs::AccountId,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -51,20 +53,22 @@ pub struct Config {
#[cfg(feature = "nymd-client")]
impl Config {
pub fn new(
network: network_defaults::all::Network,
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
) -> Self {
pub fn new(network: network_defaults::all::Network, nymd_url: Url, api_url: Url) -> Self {
Config {
network,
nymd_url,
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
mixnet_contract_address: DEFAULT_NETWORK
.mixnet_contract_address()
.parse()
.expect("Error parsing mixnet contract address"),
vesting_contract_address: DEFAULT_NETWORK
.vesting_contract_address()
.parse()
.expect("Error parsing vesting contract address"),
bandwidth_claim_contract_address: DEFAULT_NETWORK
.bandwidth_claim_contract_address()
.parse()
.expect("Error parsing bandwidth claim contract address"),
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
@@ -73,6 +77,21 @@ impl Config {
}
}
pub fn with_mixnode_contract_address(mut self, address: cosmrs::AccountId) -> Self {
self.mixnet_contract_address = address;
self
}
pub fn with_vesting_contract_address(mut self, address: cosmrs::AccountId) -> Self {
self.vesting_contract_address = address;
self
}
pub fn with_bandwidth_claim_contract_address(mut self, address: cosmrs::AccountId) -> Self {
self.bandwidth_claim_contract_address = address;
self
}
pub fn with_mixnode_page_limit(mut self, limit: Option<u32>) -> Config {
self.mixnode_page_limit = limit;
self
@@ -97,9 +116,9 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
pub network: network_defaults::all::Network,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
erc20_bridge_contract_address: Option<cosmrs::AccountId>,
mixnet_contract_address: cosmrs::AccountId,
vesting_contract_address: cosmrs::AccountId,
bandwidth_claim_contract_address: cosmrs::AccountId,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -122,18 +141,18 @@ impl Client<SigningNymdClient> {
let nymd_client = NymdClient::connect_with_mnemonic(
config.network,
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
config.vesting_contract_address.clone(),
config.erc20_bridge_contract_address.clone(),
mnemonic.clone(),
None,
)?;
)?
.with_mixnet_contract_address(config.mixnet_contract_address.clone())
.with_vesting_contract_address(config.vesting_contract_address.clone())
.with_bandwidth_claim_contract_address(config.bandwidth_claim_contract_address.clone());
Ok(Client {
network: config.network,
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
bandwidth_claim_contract_address: config.bandwidth_claim_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -148,12 +167,12 @@ impl Client<SigningNymdClient> {
self.nymd = NymdClient::connect_with_mnemonic(
self.network,
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.erc20_bridge_contract_address.clone(),
self.mnemonic.clone().unwrap(),
None,
)?;
)?
.with_mixnet_contract_address(self.mixnet_contract_address.clone())
.with_vesting_contract_address(self.vesting_contract_address.clone())
.with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone());
Ok(())
}
@@ -166,32 +185,15 @@ impl Client<SigningNymdClient> {
impl Client<QueryNymdClient> {
pub fn new_query(config: Config) -> Result<Client<QueryNymdClient>, ValidatorClientError> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
Some(config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap()
})),
Some(config.vesting_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(DEFAULT_NETWORK.vesting_contract_address()).unwrap()
})),
Some(
config
.erc20_bridge_contract_address
.clone()
.unwrap_or_else(|| {
cosmrs::AccountId::from_str(
DEFAULT_NETWORK.bandwidth_claim_contract_address(),
)
.unwrap()
}),
),
)?;
let nymd_client = NymdClient::connect(config.nymd_url.as_str())?
.with_mixnet_contract_address(config.mixnet_contract_address.clone())
.with_vesting_contract_address(config.vesting_contract_address.clone());
Ok(Client {
network: config.network,
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
erc20_bridge_contract_address: config.erc20_bridge_contract_address,
bandwidth_claim_contract_address: config.bandwidth_claim_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -203,12 +205,10 @@ impl Client<QueryNymdClient> {
}
pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.erc20_bridge_contract_address.clone(),
)?;
self.nymd = NymdClient::connect(new_endpoint.as_ref())?
.with_mixnet_contract_address(self.mixnet_contract_address.clone())
.with_vesting_contract_address(self.vesting_contract_address.clone())
.with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone());
Ok(())
}
}
@@ -222,10 +222,10 @@ impl<C> Client<C> {
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
self.mixnet_contract_address = Some(mixnet_contract_address)
self.mixnet_contract_address = mixnet_contract_address
}
pub fn get_mixnet_contract_address(&self) -> Option<cosmrs::AccountId> {
pub fn get_mixnet_contract_address(&self) -> cosmrs::AccountId {
self.mixnet_contract_address.clone()
}
@@ -233,18 +233,36 @@ impl<C> Client<C> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes_detailed().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes_detailed().await?)
}
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_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
Ok(self.validator_api.get_gateways().await?)
}
@@ -715,4 +733,34 @@ impl ApiClient {
) -> Result<VerificationKeyResponse, ValidatorClientError> {
Ok(self.validator_api.get_coconut_verification_key().await?)
}
pub async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorClientError> {
Ok(self
.validator_api
.verify_bandwidth_credential(request_body)
.await?)
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorClientError> {
Ok(self
.validator_api
.propose_release_funds(request_body)
.await?)
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorClientError> {
Ok(self
.validator_api
.execute_release_funds(request_body)
.await?)
}
}
@@ -19,7 +19,7 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
@@ -47,14 +47,15 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
NymdClient::<QueryNymdClient>::connect(url.as_str(), address, None, None)
NymdClient::<QueryNymdClient>::connect(url.as_str())
.map(|client| client.with_mixnet_contract_address(address))
.map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client)))
.ok()
});
@@ -22,7 +22,7 @@ pub struct Log {
/// Searches in logs for the first event of the given event type and in that event
/// for the first attribute with the given attribute key.
pub(crate) fn find_attribute<'a>(
pub fn find_attribute<'a>(
logs: &'a [Log],
event_type: &str,
attribute_key: &str,
@@ -124,6 +124,12 @@ pub enum NymdError {
#[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())]
BroadcastTimeout { hash: tx::Hash, timeout: Duration },
#[error("Cosmwasm std error: {0}")]
CosmwasmStdError(#[from] cosmwasm_std::StdError),
#[error("Coconut interface error: {0}")]
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
}
impl NymdError {
@@ -14,6 +14,7 @@ use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl;
use cosmrs::tx::Msg;
use cosmwasm_std::Uint128;
use execute::execute;
pub use fee::gas_price::GasPrice;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
@@ -23,8 +24,10 @@ use mixnet_contract_common::{
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
PagedRewardedSetResponse, QueryMsg, RewardedSetUpdateDetails,
};
use network_defaults::DEFAULT_NETWORK;
use serde::Serialize;
use std::convert::TryInto;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
@@ -58,30 +61,64 @@ pub mod wallet;
#[derive(Debug)]
pub struct NymdClient<C> {
client: C,
mixnet_contract_address: Option<AccountId>,
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
mixnet_contract_address: AccountId,
vesting_contract_address: AccountId,
bandwidth_claim_contract_address: AccountId,
coconut_bandwidth_contract_address: AccountId,
multisig_contract_address: AccountId,
client_address: Option<Vec<AccountId>>,
simulated_gas_multiplier: f32,
}
impl<C> NymdClient<C> {
pub fn with_mixnet_contract_address(mut self, address: AccountId) -> Self {
self.mixnet_contract_address = address;
self
}
pub fn with_vesting_contract_address(mut self, address: AccountId) -> Self {
self.vesting_contract_address = address;
self
}
pub fn with_bandwidth_claim_contract_address(mut self, address: AccountId) -> Self {
self.bandwidth_claim_contract_address = address;
self
}
pub fn with_coconut_bandwidth_contract_address(mut self, address: AccountId) -> Self {
self.coconut_bandwidth_contract_address = address;
self
}
pub fn with_multisig_contract_address(mut self, address: AccountId) -> Self {
self.multisig_contract_address = address;
self
}
}
impl NymdClient<QueryNymdClient> {
pub fn connect<U>(
endpoint: U,
mixnet_contract_address: Option<AccountId>,
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
) -> Result<NymdClient<QueryNymdClient>, NymdError>
pub fn connect<U>(endpoint: U) -> Result<NymdClient<QueryNymdClient>, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
Ok(NymdClient {
client: QueryNymdClient::new(endpoint)?,
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
client_address: None,
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
mixnet_contract_address: DEFAULT_NETWORK
.mixnet_contract_address()
.parse()
.expect("Error parsing mixnet contract address"),
vesting_contract_address: DEFAULT_NETWORK
.vesting_contract_address()
.parse()
.expect("Error parsing vesting contract address"),
bandwidth_claim_contract_address: DEFAULT_NETWORK
.bandwidth_claim_contract_address()
.parse()
.expect("Error parsing bandwidth claim contract address"),
coconut_bandwidth_contract_address: DEFAULT_NETWORK
.coconut_bandwidth_contract_address()
.parse()
.unwrap(),
multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(),
})
}
}
@@ -91,9 +128,6 @@ impl NymdClient<SigningNymdClient> {
pub fn connect_with_signer<U: Clone>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<NymdClient<SigningNymdClient>, NymdError>
@@ -110,20 +144,31 @@ impl NymdClient<SigningNymdClient> {
Ok(NymdClient {
client: SigningNymdClient::connect_with_signer(endpoint, signer, gas_price)?,
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
client_address: Some(client_address),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
mixnet_contract_address: DEFAULT_NETWORK
.mixnet_contract_address()
.parse()
.expect("Error parsing mixnet contract address"),
vesting_contract_address: DEFAULT_NETWORK
.vesting_contract_address()
.parse()
.expect("Error parsing vesting contract address"),
bandwidth_claim_contract_address: DEFAULT_NETWORK
.bandwidth_claim_contract_address()
.parse()
.expect("Error parsing bandwidth claim contract address"),
coconut_bandwidth_contract_address: DEFAULT_NETWORK
.coconut_bandwidth_contract_address()
.parse()
.unwrap(),
multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(),
})
}
pub fn connect_with_mnemonic<U: Clone>(
network: config::defaults::all::Network,
endpoint: U,
mixnet_contract_address: Option<AccountId>,
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
mnemonic: bip39::Mnemonic,
gas_price: Option<GasPrice>,
) -> Result<NymdClient<SigningNymdClient>, NymdError>
@@ -142,32 +187,48 @@ impl NymdClient<SigningNymdClient> {
Ok(NymdClient {
client: SigningNymdClient::connect_with_signer(endpoint, wallet, gas_price)?,
mixnet_contract_address,
vesting_contract_address,
erc20_bridge_contract_address,
client_address: Some(client_address),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
mixnet_contract_address: DEFAULT_NETWORK
.mixnet_contract_address()
.parse()
.expect("Error parsing mixnet contract address"),
vesting_contract_address: DEFAULT_NETWORK
.vesting_contract_address()
.parse()
.expect("Error parsing vesting contract address"),
bandwidth_claim_contract_address: DEFAULT_NETWORK
.bandwidth_claim_contract_address()
.parse()
.expect("Error parsing bandwidth claim contract address"),
coconut_bandwidth_contract_address: DEFAULT_NETWORK
.coconut_bandwidth_contract_address()
.parse()
.unwrap(),
multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(),
})
}
}
impl<C> NymdClient<C> {
pub fn mixnet_contract_address(&self) -> Result<&AccountId, NymdError> {
self.mixnet_contract_address
.as_ref()
.ok_or(NymdError::NoContractAddressAvailable)
pub fn mixnet_contract_address(&self) -> &AccountId {
&self.mixnet_contract_address
}
pub fn vesting_contract_address(&self) -> Result<&AccountId, NymdError> {
self.vesting_contract_address
.as_ref()
.ok_or(NymdError::NoContractAddressAvailable)
pub fn vesting_contract_address(&self) -> &AccountId {
&self.vesting_contract_address
}
pub fn erc20_bridge_contract_address(&self) -> Result<&AccountId, NymdError> {
self.erc20_bridge_contract_address
.as_ref()
.ok_or(NymdError::NoContractAddressAvailable)
pub fn bandwidth_claim_contract_address(&self) -> &AccountId {
&self.bandwidth_claim_contract_address
}
pub fn coconut_bandwidth_contract_address(&self) -> &AccountId {
&self.coconut_bandwidth_contract_address
}
pub fn multisig_contract_address(&self) -> &AccountId {
&self.multisig_contract_address
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
@@ -305,7 +366,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::StateParams {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -315,7 +376,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::QueryOperatorReward { address };
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -334,7 +395,7 @@ impl<C> NymdClient<C> {
proxy,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -351,7 +412,7 @@ impl<C> NymdClient<C> {
proxy_address,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -361,7 +422,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetCurrentEpoch {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -371,7 +432,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetContractVersion {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -388,7 +449,7 @@ impl<C> NymdClient<C> {
interval_id,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -398,7 +459,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetCurrentRewardedSetHeight {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -410,7 +471,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetRewardedSetUpdateDetails {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -430,7 +491,7 @@ impl<C> NymdClient<C> {
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -440,7 +501,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::LayerDistribution {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -450,7 +511,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetRewardPool {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -460,7 +521,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetCirculatingSupply {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -470,7 +531,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetSybilResistancePercent {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -480,7 +541,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetActiveSetWorkFactor {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -490,7 +551,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetIntervalRewardPercent {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -500,7 +561,7 @@ impl<C> NymdClient<C> {
{
let request = QueryMsg::GetEpochsInInterval {};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -514,7 +575,7 @@ impl<C> NymdClient<C> {
};
let response: MixOwnershipResponse = self
.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await?;
Ok(response.mixnode)
}
@@ -529,7 +590,7 @@ impl<C> NymdClient<C> {
};
let response: GatewayOwnershipResponse = self
.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await?;
Ok(response.gateway)
}
@@ -547,7 +608,7 @@ impl<C> NymdClient<C> {
limit: page_limit,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -564,7 +625,7 @@ impl<C> NymdClient<C> {
limit: page_limit,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -584,7 +645,7 @@ impl<C> NymdClient<C> {
limit: page_limit,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -604,7 +665,7 @@ impl<C> NymdClient<C> {
limit: page_limit,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -624,7 +685,7 @@ impl<C> NymdClient<C> {
proxy,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.query_contract_smart(self.mixnet_contract_address(), &request)
.await
}
@@ -799,87 +860,93 @@ impl<C> NymdClient<C> {
.await
}
pub async fn compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
#[execute("mixnet")]
fn _compound_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundOperatorReward",
vec![],
)
.await
(ExecuteMsg::CompoundOperatorReward {}, fee)
}
pub async fn claim_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
#[execute("mixnet")]
fn _claim_operator_reward(&self, fee: Option<Fee>) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimOperatorReward",
vec![],
)
.await
(ExecuteMsg::ClaimOperatorReward {}, fee)
}
pub async fn compound_delegator_reward(
#[execute("mixnet")]
fn _compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundDelegatorReward",
vec![],
)
.await
(ExecuteMsg::CompoundDelegatorReward { mix_identity }, fee)
}
pub async fn claim_delegator_reward(
#[execute("mixnet")]
fn _claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
) -> (ExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimDelegatorReward",
vec![],
)
.await
(ExecuteMsg::ClaimDelegatorReward { mix_identity }, fee)
}
#[execute("vesting")]
fn _vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> (VestingExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(
VestingExecuteMsg::CompoundDelegatorReward { mix_identity },
fee,
)
}
#[execute("vesting")]
fn _vesting_claim_operator_reward(&self, fee: Option<Fee>) -> (VestingExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(VestingExecuteMsg::ClaimOperatorReward {}, fee)
}
#[execute("vesting")]
fn _vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> (VestingExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(VestingExecuteMsg::CompoundOperatorReward {}, fee)
}
#[execute("vesting")]
fn _vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> (VestingExecuteMsg, Option<Fee>)
where
C: SigningCosmWasmClient + Sync,
{
(
VestingExecuteMsg::ClaimDelegatorReward { mix_identity },
fee,
)
}
/// Announce a mixnode, paying a fee.
@@ -902,7 +969,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Bonding mixnode from rust!",
@@ -933,7 +1000,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Bonding mixnode on behalf from rust!",
@@ -970,7 +1037,7 @@ impl<C> NymdClient<C> {
self.client
.execute_multiple(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
reqs,
fee,
"Bonding multiple mixnodes on behalf from rust!",
@@ -989,7 +1056,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Unbonding mixnode from rust!",
@@ -1013,7 +1080,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Unbonding mixnode on behalf from rust!",
@@ -1039,7 +1106,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Updating mixnode configuration from rust!",
@@ -1066,7 +1133,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Delegating to mixnode from rust!",
@@ -1096,7 +1163,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Delegating to mixnode on behalf from rust!",
@@ -1132,7 +1199,7 @@ impl<C> NymdClient<C> {
self.client
.execute_multiple(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
reqs,
fee,
"Delegating to multiple mixnodes on behalf from rust!",
@@ -1157,7 +1224,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Removing mixnode delegation from rust!",
@@ -1185,7 +1252,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Removing mixnode delegation on behalf from rust!",
@@ -1214,7 +1281,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Bonding gateway from rust!",
@@ -1245,7 +1312,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Bonding gateway on behalf from rust!",
@@ -1282,7 +1349,7 @@ impl<C> NymdClient<C> {
self.client
.execute_multiple(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
reqs,
fee,
"Bonding multiple gateways on behalf from rust!",
@@ -1301,7 +1368,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Unbonding gateway from rust!",
@@ -1326,7 +1393,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Unbonding gateway on behalf from rust!",
@@ -1349,7 +1416,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Updating contract state from rust!",
@@ -1368,7 +1435,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Advance current epoch",
@@ -1387,7 +1454,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Reconciling delegation events",
@@ -1406,7 +1473,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Snapshotting mixnodes",
@@ -1433,7 +1500,7 @@ impl<C> NymdClient<C> {
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
self.mixnet_contract_address(),
&req,
fee,
"Writing rewarded set",
@@ -0,0 +1,49 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg};
use async_trait::async_trait;
#[async_trait]
pub trait CoconutBandwidthSigningClient {
async fn deposit(
&self,
amount: Coin,
info: String,
verification_key: String,
encryption_key: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> CoconutBandwidthSigningClient for NymdClient<C> {
async fn deposit(
&self,
amount: Coin,
info: String,
verification_key: String,
encryption_key: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::DepositFunds {
data: DepositData::new(info.to_string(), verification_key, encryption_key),
};
self.client
.execute(
self.address(),
self.coconut_bandwidth_contract_address(),
&req,
fee,
"CoconutBandwidth::Deposit",
vec![amount],
)
.await
}
}
@@ -1,8 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
mod vesting_signing_client;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use multisig_query_client::QueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -0,0 +1,24 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use crate::nymd::{CosmWasmClient, NymdClient};
use multisig_contract_common::msg::{ProposalResponse, QueryMsg};
use async_trait::async_trait;
#[async_trait]
pub trait QueryClient {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> QueryClient for NymdClient<C> {
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse, NymdError> {
let request = QueryMsg::Proposal { proposal_id };
self.client
.query_contract_smart(self.multisig_contract_address(), &request)
.await
}
}
@@ -0,0 +1,116 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient};
use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg;
use multisig_contract_common::msg::ExecuteMsg;
use async_trait::async_trait;
use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg};
use cw3::Vote;
use network_defaults::DEFAULT_NETWORK;
#[async_trait]
pub trait MultisigSigningClient {
async fn propose_release_funds(
&self,
title: String,
blinded_serial_number: String,
voucher_value: u128,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vote_proposal(
&self,
proposal_id: u64,
yes: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn execute_proposal(
&self,
proposal_id: u64,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> MultisigSigningClient for NymdClient<C> {
async fn propose_release_funds(
&self,
title: String,
blinded_serial_number: String,
voucher_value: u128,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds {
funds: Coin::new(voucher_value, DEFAULT_NETWORK.mix_denom().base),
};
let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: self.coconut_bandwidth_contract_address().to_string(),
msg: to_binary(&release_funds_req)?,
funds: vec![],
});
let req = ExecuteMsg::Propose {
title,
description: blinded_serial_number,
msgs: vec![release_funds_msg],
latest: None,
};
self.client
.execute(
self.address(),
self.multisig_contract_address(),
&req,
fee,
"Multisig::Propose::Execute::ReleaseFunds",
vec![],
)
.await
}
async fn vote_proposal(
&self,
proposal_id: u64,
vote_yes: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let vote = if vote_yes { Vote::Yes } else { Vote::No };
let req = ExecuteMsg::Vote { proposal_id, vote };
self.client
.execute(
self.address(),
self.multisig_contract_address(),
&req,
fee,
"Multisig::Vote",
vec![],
)
.await
}
async fn execute_proposal(
&self,
proposal_id: u64,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::Execute { proposal_id };
self.client
.execute(
self.address(),
self.multisig_contract_address(),
&req,
fee,
"Multisig::Execute",
vec![],
)
.await
}
}
@@ -84,7 +84,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -99,7 +99,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -113,7 +113,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -127,7 +127,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -140,7 +140,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
@@ -152,7 +152,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
@@ -164,7 +164,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
@@ -178,7 +178,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -194,7 +194,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request)
.await
.map(Into::into)
}
@@ -204,7 +204,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_mixnode_pledge(&self, address: &str) -> Result<Option<PledgeData>, NymdError> {
@@ -212,7 +212,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
async fn get_gateway_pledge(&self, address: &str) -> Result<Option<PledgeData>, NymdError> {
@@ -220,7 +220,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
@@ -229,7 +229,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
address: address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart(self.vesting_contract_address(), &request)
.await
}
}
@@ -11,28 +11,6 @@ use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, Vesting
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
@@ -129,7 +107,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UpdateMixnetConfig",
@@ -150,7 +128,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UpdateMixnetAddress",
@@ -175,7 +153,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondGateway",
@@ -190,7 +168,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UnbondGateway",
@@ -213,7 +191,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUnbondGateway",
@@ -238,7 +216,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondMixnode",
@@ -253,7 +231,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UnbondMixnode",
@@ -276,7 +254,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUnbondMixnode",
@@ -296,7 +274,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::WithdrawVested",
@@ -320,7 +298,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUndelegation",
@@ -342,7 +320,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::DelegateToMixnode",
@@ -363,7 +341,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UndelegateFromMixnode",
@@ -389,7 +367,7 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
self.vesting_contract_address(),
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
@@ -397,78 +375,4 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::ClaimOperatorReward",
vec![],
)
.await
}
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CompoundOperatorReward",
vec![],
)
.await
}
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::ClaimDelegatorReward",
vec![],
)
.await
}
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CompoundDelegatorReward",
vec![],
)
.await
}
}
@@ -3,14 +3,18 @@
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use coconut_interface::{
BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody,
ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -85,6 +89,16 @@ impl Client {
.await
}
pub async fn get_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::DETAILED],
NO_PARAMS,
)
.await
}
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
@@ -98,6 +112,21 @@ impl Client {
.await
}
pub async fn get_active_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::ACTIVE,
routes::DETAILED,
],
NO_PARAMS,
)
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
@@ -106,6 +135,21 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::DETAILED,
],
NO_PARAMS,
)
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
@@ -331,6 +375,57 @@ impl Client {
)
.await
}
pub async fn verify_bandwidth_credential(
&self,
request_body: &VerifyCredentialBody,
) -> Result<VerifyCredentialResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn propose_release_funds(
&self,
request_body: &ProposeReleaseFundsRequestBody,
) -> Result<ProposeReleaseFundsResponse, ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_PROPOSE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
pub async fn execute_release_funds(
&self,
request_body: &ExecuteReleaseFundsRequestBody,
) -> Result<(), ValidatorAPIError> {
self.post_validator_api(
&[
routes::API_VERSION,
routes::COCONUT_ROUTES,
routes::BANDWIDTH,
routes::COCONUT_EXECUTE_RELEASE_FUNDS,
],
NO_PARAMS,
request_body,
)
.await
}
}
// utility function that should solve the double slash problem in validator API forever.
@@ -7,6 +7,7 @@ pub const API_VERSION: &str = VALIDATOR_API_VERSION;
pub const MIXNODES: &str = "mixnodes";
pub const GATEWAYS: &str = "gateways";
pub const DETAILED: &str = "detailed";
pub const ACTIVE: &str = "active";
pub const REWARDED: &str = "rewarded";
@@ -16,6 +17,9 @@ pub const BANDWIDTH: &str = "bandwidth";
pub const COCONUT_BLIND_SIGN: &str = "blind-sign";
pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential";
pub const COCONUT_VERIFICATION_KEY: &str = "verification-key";
pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential";
pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds";
pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds";
pub const STATUS_ROUTES: &str = "status";
pub const MIXNODE: &str = "mixnode";
+3 -5
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nymcoconut::CoconutError;
use thiserror::Error;
#[derive(Debug, Error)]
@@ -11,9 +12,6 @@ pub enum CoconutInterfaceError {
#[error("Could not decode base 58 string - {0}")]
MalformedString(#[from] bs58::decode::Error),
#[error("Not enough public attributes were specified")]
NotEnoughPublicAttributes,
#[error("Could not recover bandwidth value")]
InvalidBandwidth,
#[error("Coconut error - {0}")]
CoconutError(#[from] CoconutError),
}
+191 -46
View File
@@ -5,96 +5,158 @@ pub mod error;
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use error::CoconutInterfaceError;
pub use nymcoconut::*;
#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)]
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq)]
pub struct Credential {
#[getset(get = "pub")]
n_params: u32,
#[getset(get = "pub")]
theta: Theta,
public_attributes: Vec<Vec<u8>>,
#[getset(get = "pub")]
signature: Signature,
voucher_value: u64,
voucher_info: String,
}
impl Credential {
pub fn new(
n_params: u32,
theta: Theta,
voucher_value: String,
voucher_value: u64,
voucher_info: String,
signature: &Signature,
) -> Credential {
let public_attributes = vec![voucher_value.into_bytes(), voucher_info.into_bytes()];
Credential {
n_params,
theta,
public_attributes,
signature: *signature,
voucher_value,
voucher_info,
}
}
pub fn voucher_value(&self) -> Result<u64, CoconutInterfaceError> {
let bandwidth_vec = self
.public_attributes
.get(0)
.ok_or(CoconutInterfaceError::NotEnoughPublicAttributes)?
.to_owned();
let bandwidth_str = String::from_utf8(bandwidth_vec)
.map_err(|_| CoconutInterfaceError::InvalidBandwidth)?;
let value =
u64::from_str(&bandwidth_str).map_err(|_| CoconutInterfaceError::InvalidBandwidth)?;
pub fn blinded_serial_number(&self) -> String {
self.theta.blinded_serial_number_bs58()
}
Ok(value)
pub fn has_blinded_serial_number(
&self,
blinded_serial_number_bs58: &str,
) -> Result<bool, CoconutInterfaceError> {
Ok(self
.theta
.has_blinded_serial_number(blinded_serial_number_bs58)?)
}
pub fn voucher_value(&self) -> u64 {
self.voucher_value
}
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
let params = Parameters::new(self.n_params).unwrap();
let public_attributes = self
.public_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
let public_attributes = vec![
self.voucher_value.to_string().as_bytes(),
self.voucher_info.as_bytes(),
]
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
nymcoconut::verify_credential(&params, verification_key, &self.theta, &public_attributes)
}
pub fn as_bytes(&self) -> Vec<u8> {
let n_params_bytes = self.n_params.to_be_bytes();
let theta_bytes = self.theta.to_bytes();
let theta_bytes_len = theta_bytes.len();
let voucher_value_bytes = self.voucher_value.to_be_bytes();
let voucher_info_bytes = self.voucher_info.as_bytes();
let voucher_info_len = voucher_info_bytes.len();
let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len);
bytes.extend_from_slice(&n_params_bytes);
bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes());
bytes.extend_from_slice(&theta_bytes);
bytes.extend_from_slice(&voucher_value_bytes);
bytes.extend_from_slice(voucher_info_bytes);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, CoconutError> {
if bytes.len() < 28 {
return Err(CoconutError::Deserialization(String::from(
"To few bytes in credential",
)));
}
let mut four_byte = [0u8; 4];
let mut eight_byte = [0u8; 8];
four_byte.copy_from_slice(&bytes[..4]);
let n_params = u32::from_be_bytes(four_byte);
eight_byte.copy_from_slice(&bytes[4..12]);
let theta_len = u64::from_be_bytes(eight_byte);
if bytes.len() < 28 + theta_len as usize {
return Err(CoconutError::Deserialization(String::from(
"To few bytes in credential",
)));
}
let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize])
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]);
let voucher_value = u64::from_be_bytes(eight_byte);
let voucher_info = String::from_utf8(bytes[20 + theta_len as usize..].to_vec())
.map_err(|e| CoconutError::Deserialization(e.to_string()))?;
Ok(Credential {
n_params,
theta,
voucher_value,
voucher_info,
})
}
}
#[derive(Serialize, Deserialize, Debug, Getters, CopyGetters)]
impl Bytable for Credential {
fn to_byte_vec(&self) -> Vec<u8> {
self.as_bytes()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self, CoconutError> {
Credential::from_bytes(slice)
}
}
impl Base58 for Credential {}
#[derive(Serialize, Deserialize, Getters, CopyGetters)]
pub struct VerifyCredentialBody {
#[getset(get = "pub")]
n_params: u32,
credential: Credential,
#[getset(get = "pub")]
theta: Theta,
public_attributes: Vec<String>,
proposal_id: u64,
}
impl VerifyCredentialBody {
pub fn new(
n_params: u32,
theta: &Theta,
public_attributes: &[Attribute],
) -> VerifyCredentialBody {
pub fn new(credential: Credential, proposal_id: u64) -> VerifyCredentialBody {
VerifyCredentialBody {
n_params,
theta: theta.clone(),
public_attributes: public_attributes
.iter()
.map(|attr| attr.to_bs58())
.collect(),
credential,
proposal_id,
}
}
}
pub fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes
.iter()
.map(|x| Attribute::try_from_bs58(x).unwrap())
.collect()
#[derive(Debug, Serialize, Deserialize)]
pub struct VerifyCredentialResponse {
pub verification_result: bool,
}
impl VerifyCredentialResponse {
pub fn new(verification_result: bool) -> Self {
VerifyCredentialResponse {
verification_result,
}
}
}
// All strings are base58 encoded representations of structs
#[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)]
pub struct BlindSignRequestBody {
@@ -194,3 +256,86 @@ impl VerificationKeyResponse {
VerificationKeyResponse { key }
}
}
#[derive(Serialize, Deserialize, Getters, CopyGetters)]
pub struct ProposeReleaseFundsRequestBody {
#[getset(get = "pub")]
credential: Credential,
}
impl ProposeReleaseFundsRequestBody {
pub fn new(credential: Credential) -> Self {
ProposeReleaseFundsRequestBody { credential }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ProposeReleaseFundsResponse {
pub proposal_id: u64,
}
impl ProposeReleaseFundsResponse {
pub fn new(proposal_id: u64) -> Self {
ProposeReleaseFundsResponse { proposal_id }
}
}
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters)]
pub struct ExecuteReleaseFundsRequestBody {
#[getset(get = "pub")]
proposal_id: u64,
}
impl ExecuteReleaseFundsRequestBody {
pub fn new(proposal_id: u64) -> Self {
ExecuteReleaseFundsRequestBody { proposal_id }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde_coconut_credential() {
let voucher_value = 1000000u64;
let voucher_info = String::from("BandwidthVoucher");
let serial_number =
Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap();
let binding_number =
Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap();
let signature = Signature::try_from_bs58(
"ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\
7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\
ZR9j",
)
.unwrap();
let params = Parameters::new(4).unwrap();
let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\
woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\
FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\
JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\
UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\
wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\
1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\
nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\
ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\
6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\
Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y")
.unwrap();
let theta = prove_bandwidth_credential(
&params,
&verification_key,
&signature,
serial_number,
binding_number,
)
.unwrap();
let credential = Credential::new(4, theta, voucher_value, voucher_info);
let serialized_credential = credential.as_bytes();
let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap();
assert_eq!(credential, deserialized_credential);
}
}
@@ -0,0 +1,14 @@
[package]
name = "multisig-contract-common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cw-utils = { version = "0.13.1" }
cw3 = { version = "0.13.1" }
cw4 = { version = "0.13.1" }
cosmwasm-std = "1.0.0-beta6"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -0,0 +1 @@
pub mod msg;
@@ -1,7 +1,11 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{CosmosMsg, Empty};
pub use cw3::ProposalResponse;
use cw3::Vote;
use cw4::MemberChangedHookMsg;
use cw_utils::{Duration, Expiration, Threshold};
+3 -16
View File
@@ -51,12 +51,7 @@ pub async fn obtain_aggregate_verification_key(
let mut shares = Vec::with_capacity(validators.len());
let mut client = validator_client::ApiClient::new(validators[0].clone());
let response = client.get_coconut_verification_key().await?;
indices.push(1);
shares.push(response.key);
for (id, validator_url) in validators.iter().enumerate().skip(1) {
for (id, validator_url) in validators.iter().enumerate() {
client.change_validator_api(validator_url.clone());
let response = client.get_coconut_verification_key().await?;
indices.push((id + 1) as u64);
@@ -135,14 +130,7 @@ pub async fn obtain_aggregate_signature(
let mut validators_partial_vks: Vec<VerificationKey> = Vec::with_capacity(validators.len());
let mut client = validator_client::ApiClient::new(validators[0].clone());
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
let first =
obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key).await?;
shares.push(SignatureShare::new(first, 1));
for (id, validator_url) in validators.iter().enumerate().skip(1) {
for (id, validator_url) in validators.iter().enumerate() {
client.change_validator_api(validator_url.clone());
let validator_partial_vk = client.get_coconut_verification_key().await?;
validators_partial_vks.push(validator_partial_vk.key.clone());
@@ -193,8 +181,7 @@ pub fn prepare_credential_for_spending(
Ok(Credential::new(
PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
theta,
voucher_value.to_string(),
voucher_value,
voucher_info,
signature,
))
}
+1 -5
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use coconut_interface::{error::CoconutInterfaceError, CoconutError};
use coconut_interface::CoconutError;
use crypto::asymmetric::encryption::KeyRecoveryError;
use validator_client::ValidatorClientError;
@@ -20,10 +20,6 @@ pub enum Error {
#[error("Ran into a coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[cfg(feature = "coconut")]
#[error("Ran into a coconut interface error - {0}")]
CoconutInterfaceError(#[from] CoconutInterfaceError),
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "execute"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
syn = { version = "1", features = ["full"] }
quote = "1"
+110
View File
@@ -0,0 +1,110 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{
parse_macro_input, Block, ExprMethodCall, FnArg, Ident, ItemFn, LitStr, ReturnType, Token,
VisPublic, Visibility,
};
#[proc_macro_attribute]
pub fn execute(attr: TokenStream, item: TokenStream) -> TokenStream {
let f = parse_macro_input!(item as ItemFn);
let target = parse_macro_input!(attr as LitStr).value();
let cl = if target == "mixnet" {
quote! {self.mixnet_contract_address()}
} else if target == "vesting" {
quote! {self.vesting_contract_address()}
} else {
panic!("Only `mixnet` and `vesting` targets are supported!")
};
let cl = proc_macro::TokenStream::from(cl);
let cl = parse_macro_input!(cl as ExprMethodCall);
let orig_f = f.clone();
let mut execute_f = f.clone();
let mut simulate_f = f.clone();
let name = f.sig.ident;
let name_str = name.to_string();
let call_args = f.sig.inputs.into_iter().filter_map(|arg| match arg {
FnArg::Receiver(_) => None,
FnArg::Typed(arg) => Some(arg.pat),
});
let execute_args = call_args.clone();
let simulate_args = call_args;
execute_f.sig.asyncness = Some(Token![async](execute_f.sig.ident.span()));
simulate_f.sig.asyncness = Some(Token![async](simulate_f.sig.ident.span()));
execute_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](execute_f.sig.ident.span()),
});
simulate_f.vis = Visibility::Public(VisPublic {
pub_token: Token![pub](simulate_f.sig.ident.span()),
});
execute_f.sig.ident = Ident::new(
&format!("execute{}", execute_f.sig.ident),
execute_f.sig.ident.span(),
);
simulate_f.sig.ident = Ident::new(
&format!("simulate{}", simulate_f.sig.ident),
simulate_f.sig.ident.span(),
);
let execute_output = quote! {
-> Result<ExecuteResult, NymdError>
};
let o_ts = proc_macro::TokenStream::from(execute_output);
execute_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_output = quote! {
-> Result<SimulateResponse, NymdError>
};
let o_ts = proc_macro::TokenStream::from(simulate_output);
simulate_f.sig.output = parse_macro_input!(o_ts as ReturnType);
let simulate_block = quote! {
{
let (msg, _fee) = self.#name(#(#simulate_args),*);
let msg = self.wrap_contract_execute_message(
#cl,
&msg,
vec![],
)?;
self.simulate(vec![msg]).await
}
};
let ts = proc_macro::TokenStream::from(simulate_block);
simulate_f.block = Box::new(parse_macro_input!(ts as Block));
let execute_block = quote! {
{
let (req, fee) = self.#name(#(#execute_args),*);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(
self.address(),
#cl,
&req,
fee,
#name_str,
vec![],
)
.await
}
};
let ts = proc_macro::TokenStream::from(execute_block);
execute_f.block = Box::new(parse_macro_input!(ts as Block));
let out = quote! {
#orig_f
#execute_f
#simulate_f
};
out.into()
}
+8
View File
@@ -57,6 +57,14 @@ impl Network {
self.details().bandwidth_claim_contract_address
}
pub fn coconut_bandwidth_contract_address(&self) -> &str {
self.details().coconut_bandwidth_contract_address
}
pub fn multisig_contract_address(&self) -> &str {
self.details().multisig_contract_address
}
pub fn rewarding_validator_address(&self) -> &str {
self.details().rewarding_validator_address
}
+10 -2
View File
@@ -52,6 +52,8 @@ pub struct DefaultNetworkDetails {
mixnet_contract_address: &'static str,
vesting_contract_address: &'static str,
bandwidth_claim_contract_address: &'static str,
coconut_bandwidth_contract_address: &'static str,
multisig_contract_address: &'static str,
rewarding_validator_address: &'static str,
validators: Vec<ValidatorDetails>,
}
@@ -63,6 +65,8 @@ static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetwo
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
validators: mainnet::validators(),
});
@@ -74,6 +78,8 @@ static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetwo
mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
validators: sandbox::validators(),
});
@@ -85,6 +91,8 @@ static QA_DEFAULTS: Lazy<DefaultNetworkDetails> = Lazy::new(|| DefaultNetworkDet
mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS,
vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS,
bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS,
coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
validators: qa::validators(),
});
@@ -184,7 +192,7 @@ pub const ETH_ERC20_APPROVE_FUNCTION_NAME: &str = "approve";
// Ethereum constants used for token bridge
/// How much bandwidth (in bytes) one token can buy
const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024;
pub const BYTES_PER_UTOKEN: u64 = 1024;
/// Threshold for claiming more bandwidth: 1 MB
pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024;
@@ -193,7 +201,7 @@ pub const TOKENS_TO_BURN: u64 = 1;
/// How many ERC20 utokens should be burned to buy bandwidth
pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000;
/// Default bandwidth (in bytes) that we try to buy
pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN;
pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN;
pub const VOUCHER_INFO: &str = "BandwidthVoucher";
+3
View File
@@ -15,6 +15,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
+3
View File
@@ -15,6 +15,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
+3
View File
@@ -13,6 +13,9 @@ pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
"nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r";
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
+1 -2
View File
@@ -327,8 +327,7 @@ impl ProofCmCs {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, PartialEq)]
pub struct ProofKappaZeta {
// c
challenge: Scalar,
@@ -0,0 +1,49 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::G2Projective;
use group::Curve;
use std::convert::TryFrom;
use std::convert::TryInto;
use crate::error::{CoconutError, Result};
use crate::traits::{Base58, Bytable};
use crate::utils::try_deserialize_g2_projective;
pub struct BlindedSerialNumber {
pub(crate) inner: G2Projective,
}
impl TryFrom<&[u8]> for BlindedSerialNumber {
type Error = CoconutError;
fn try_from(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 96 {
return Err(
CoconutError::Deserialization(
format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()),
));
}
let inner = try_deserialize_g2_projective(
&bytes.try_into().unwrap(),
CoconutError::Deserialization(
"failed to deserialize the blinded serial number (zeta)".to_string(),
),
)?;
Ok(BlindedSerialNumber { inner })
}
}
impl Bytable for BlindedSerialNumber {
fn to_byte_vec(&self) -> Vec<u8> {
self.inner.to_affine().to_compressed().to_vec()
}
fn try_from_byte_slice(slice: &[u8]) -> Result<Self> {
Self::try_from(slice)
}
}
impl Base58 for BlindedSerialNumber {}
+2 -2
View File
@@ -19,6 +19,7 @@ use crate::utils::try_deserialize_g1_projective;
use crate::Attribute;
pub mod aggregation;
pub mod double_use;
pub mod issuance;
pub mod keygen;
pub mod setup;
@@ -27,8 +28,7 @@ pub mod verification;
pub type SignerIndex = u64;
// (h, s)
#[derive(Debug, Clone, Copy)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective);
pub type PartialSignature = Signature;
+15 -2
View File
@@ -10,6 +10,7 @@ use group::{Curve, Group};
use crate::error::{CoconutError, Result};
use crate::proofs::ProofKappaZeta;
use crate::scheme::double_use::BlindedSerialNumber;
use crate::scheme::setup::Parameters;
use crate::scheme::Signature;
use crate::scheme::VerificationKey;
@@ -19,8 +20,7 @@ use crate::Attribute;
// TODO NAMING: this whole thing
// Theta
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
#[derive(Debug, PartialEq)]
pub struct Theta {
// blinded_message (kappa)
pub blinded_message: G2Projective,
@@ -81,6 +81,12 @@ impl Theta {
)
}
pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result<bool> {
let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?;
let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner);
Ok(ret)
}
// blinded message (kappa) || blinded serial number (zeta) || credential || pi_v
pub fn to_bytes(&self) -> Vec<u8> {
let blinded_message_bytes = self.blinded_message.to_affine().to_compressed();
@@ -100,6 +106,13 @@ impl Theta {
pub fn from_bytes(bytes: &[u8]) -> Result<Theta> {
Theta::try_from(bytes)
}
pub fn blinded_serial_number_bs58(&self) -> String {
let blinded_serial_nuumber = BlindedSerialNumber {
inner: self.blinded_serial_number,
};
blinded_serial_nuumber.to_bs58()
}
}
impl Bytable for Theta {
+13
View File
@@ -508,6 +508,7 @@ dependencies = [
"cw3-fixed-multisig",
"cw4",
"cw4-group",
"multisig-contract-common",
"schemars",
"serde",
"thiserror",
@@ -1049,6 +1050,18 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "multisig-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-utils",
"cw3",
"cw4",
"schemars",
"serde",
]
[[package]]
name = "network-defaults"
version = "0.1.0"
@@ -28,6 +28,8 @@ schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
[dev-dependencies]
cosmwasm-schema = { version = "1.0.0-beta6" }
cw4-group = { path = "../cw4-group", version = "0.13.1" }
@@ -18,8 +18,8 @@ use cw_storage_plus::Bound;
use cw_utils::{maybe_addr, Expiration, ThresholdResponse};
use crate::error::ContractError;
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::state::{Config, CONFIG};
use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
// version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig";
@@ -499,7 +499,7 @@ mod tests {
max_voting_period: Duration,
) -> Addr {
let flex_id = app.store_code(contract_flex());
let msg = crate::msg::InstantiateMsg {
let msg = InstantiateMsg {
group_addr: group.to_string(),
threshold,
max_voting_period,
@@ -1,6 +1,5 @@
pub mod contract;
pub mod error;
pub mod msg;
pub mod state;
pub use crate::error::ContractError;
+10 -9
View File
@@ -6,21 +6,22 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
chrono = { version = "0.4.19", features = ["serde"] }
humantime-serde = "1.0"
isocountry = "0.3.2"
itertools = "0.10.3"
log = "0.4.0"
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
pretty_env_logger = "0.4.0"
reqwest = "0.11.4"
rocket = {version = "0.5.0-rc.1", features=["json"] }
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
serde = "1.0.126"
humantime-serde = "1.0"
serde_json = "1.0.66"
tokio = {version = "1.9.0", features = ["full"] }
chrono = { version = "0.4.19", features = ["serde"] }
schemars = { version = "0.8", features = ["preserve_order"] }
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
log = "0.4.0"
pretty_env_logger = "0.4.0"
schemars = { version = "0.8", features = ["preserve_order"] }
serde = "1.0.126"
serde_json = "1.0.66"
thiserror = "1.0.29"
tokio = {version = "1.9.0", features = ["full"] }
mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" }
network-defaults = { path = "../common/network-defaults" }
+2 -2
View File
@@ -5,13 +5,13 @@ An API that provides data for the [Network Explorer](../explorer).
Features:
- geolocates mixnodes using https://freegeoip.app/
- geolocates mixnodes using https://app.ipbase.com/
- calculates how many nodes are in each country
- proxies mixnode API requests to add HTTPS
## Running
Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://freegeoip.app/.
Supply the environment variable `GEO_IP_SERVICE_API_KEY` with a key from https://app.ipbase.com/.
Run as a service and reverse proxy with `nginx` to add `https` with Lets Encrypt.
+1 -10
View File
@@ -23,19 +23,10 @@ impl ThreadsafeValidatorClient {
}
pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient {
let network = DEFAULT_NETWORK;
let mixnet_contract = network.mixnet_contract_address().to_string();
let nymd_url = default_nymd_endpoints()[0].clone();
let api_url = default_api_endpoints()[0].clone();
let client_config = validator_client::Config::new(
network,
nymd_url,
api_url,
Some(mixnet_contract.parse().unwrap()),
None,
None,
);
let client_config = validator_client::Config::new(DEFAULT_NETWORK, nymd_url, api_url);
ThreadsafeValidatorClient(Arc::new(
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"),
@@ -51,7 +51,7 @@ impl GeoLocateTask {
.state
.inner
.mixnodes
.is_location_valid(&cache_item.mix_node.identity_key)
.is_location_valid(&cache_item.mix_node().identity_key)
.await
{
// when the cached location is valid, don't locate and continue to next mix node
@@ -59,14 +59,14 @@ impl GeoLocateTask {
}
// the mix node has not been located or is the cache time has expired
match locate(&cache_item.mix_node.host).await {
match locate(&cache_item.mix_node().host).await {
Ok(geo_location) => {
let location = Location::new(geo_location);
trace!(
"{} mix nodes already located. Ip {} is located in {:#?}",
i,
cache_item.mix_node.host,
cache_item.mix_node().host,
location.three_letter_iso_country_code,
);
@@ -80,7 +80,7 @@ impl GeoLocateTask {
self.state
.inner
.mixnodes
.set_location(&cache_item.mix_node.identity_key, Some(location))
.set_location(&cache_item.mix_node().identity_key, Some(location))
.await;
// one node has been located, so return out of the loop
@@ -89,22 +89,22 @@ impl GeoLocateTask {
Err(e) => match e {
LocateError::ReqwestError(e) => warn!(
"❌ Oh no! Location for {} failed {}",
cache_item.mix_node.host, e
cache_item.mix_node().host, e
),
LocateError::NotFound(e) => {
warn!(
"❌ Location for {} not found. Response body: {}",
cache_item.mix_node.host, e
cache_item.mix_node().host, e
);
self.state
.inner
.mixnodes
.set_location(&cache_item.mix_node.identity_key, None)
.set_location(&cache_item.mix_node().identity_key, None)
.await;
},
LocateError::RateLimited(e) => warn!(
"❌ Oh no, we've been rate limited! Location for {} failed. Response body: {}",
cache_item.mix_node.host, e
cache_item.mix_node().host, e
),
},
}
@@ -129,7 +129,7 @@ enum LocateError {
async fn locate(ip: &str) -> Result<GeoLocation, LocateError> {
let api_key = ::std::env::var("GEO_IP_SERVICE_API_KEY")
.expect("Env var GEO_IP_SERVICE_API_KEY is not set");
let uri = format!("{}/{}?apikey={}", crate::GEO_IP_SERVICE, ip, api_key);
let uri = format!("{}/?apikey={}&ip={}", crate::GEO_IP_SERVICE, api_key, ip);
match reqwest::get(uri.clone()).await {
Ok(response) => {
if response.status() == 429 {
+1 -1
View File
@@ -18,7 +18,7 @@ mod state;
mod tasks;
mod validators;
const GEO_IP_SERVICE: &str = "https://api.freegeoip.app/json";
const GEO_IP_SERVICE: &str = "https://api.ipbase.com/json";
const COUNTRY_DATA_REFRESH_INTERVAL: u64 = 60 * 15; // every 15 minutes
#[tokio::main]
+19
View File
@@ -1,9 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use itertools::Itertools;
use crate::client::ThreadsafeValidatorClient;
use mixnet_contract_common::Delegation;
use super::models::SummedDelegations;
pub(crate) async fn get_single_mixnode_delegations(
client: &ThreadsafeValidatorClient,
pubkey: &str,
@@ -21,3 +25,18 @@ pub(crate) async fn get_single_mixnode_delegations(
};
delegates
}
pub(crate) async fn get_single_mixnode_delegations_summed(
client: &ThreadsafeValidatorClient,
pubkey: &str,
) -> Vec<SummedDelegations> {
let delegations_by_owner = get_single_mixnode_delegations(client, pubkey)
.await
.into_iter()
.into_group_map_by(|delegation| delegation.owner.clone());
delegations_by_owner
.iter()
.filter_map(|(_, delegations)| SummedDelegations::from(delegations))
.collect()
}
+25 -7
View File
@@ -11,16 +11,19 @@ use rocket_okapi::settings::OpenApiSettings;
use mixnet_contract_common::Delegation;
use crate::mix_node::delegations::get_single_mixnode_delegations;
use crate::mix_node::delegations::{
get_single_mixnode_delegations, get_single_mixnode_delegations_summed,
};
use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats;
use crate::mix_node::models::{
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond,
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond, SummedDelegations,
};
use crate::state::ExplorerApiStateContext;
pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
settings: get_delegations,
get_delegations_summed,
get_by_id,
get_description,
get_stats,
@@ -54,6 +57,15 @@ pub(crate) async fn get_delegations(
Json(get_single_mixnode_delegations(&state.inner.validator_client, pubkey).await)
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/delegations/summed")]
pub(crate) async fn get_delegations_summed(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<SummedDelegations>> {
Json(get_single_mixnode_delegations_summed(&state.inner.validator_client, pubkey).await)
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/description")]
pub(crate) async fn get_description(
@@ -70,8 +82,8 @@ pub(crate) async fn get_description(
match state.inner.get_mix_node(pubkey).await {
Some(bond) => {
match get_mix_node_description(
&bond.mix_node.host,
&bond.mix_node.http_api_port,
&bond.mix_node().host,
&bond.mix_node().http_api_port,
)
.await
{
@@ -87,7 +99,10 @@ pub(crate) async fn get_description(
Err(e) => {
error!(
"Unable to get description for {} on {}:{} -> {}",
pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e
pubkey,
bond.mix_node().host,
bond.mix_node().http_api_port,
e
);
Option::None
}
@@ -114,7 +129,7 @@ pub(crate) async fn get_stats(
trace!("No valid cache value for {}", pubkey);
match state.inner.get_mix_node(pubkey).await {
Some(bond) => {
match get_mix_node_stats(&bond.mix_node.host, &bond.mix_node.http_api_port)
match get_mix_node_stats(&bond.mix_node().host, &bond.mix_node().http_api_port)
.await
{
Ok(response) => {
@@ -129,7 +144,10 @@ pub(crate) async fn get_stats(
Err(e) => {
error!(
"Unable to get description for {} on {}:{} -> {}",
pubkey, bond.mix_node.host, bond.mix_node.http_api_port, e
pubkey,
bond.mix_node().host,
bond.mix_node().http_api_port,
e
);
Option::None
}
+66
View File
@@ -4,6 +4,7 @@
use crate::cache::Cache;
use crate::mix_nodes::location::Location;
use mixnet_contract_common::{Addr, Coin, Layer, MixNode};
use mixnet_contract_common::{Delegation, IdentityKey};
use serde::Deserialize;
use serde::Serialize;
use std::sync::Arc;
@@ -29,6 +30,35 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub layer: Layer,
pub mix_node: MixNode,
pub avg_uptime: Option<u8>,
pub stake_saturation: f32,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct SummedDelegations {
pub owner: Addr,
pub node_identity: IdentityKey,
pub amount: Coin,
}
impl SummedDelegations {
pub fn from(delegations: &[Delegation]) -> Option<Self> {
let owner = get_common_owner(delegations)?;
let node_identity = get_common_node_identity(delegations)?;
let denom = get_common_denom(delegations)?;
let sum = delegations
.iter()
.map(|delegation| delegation.amount.amount)
.sum();
let amount = Coin { denom, amount: sum };
Some(SummedDelegations {
owner,
node_identity,
amount,
})
}
}
pub(crate) struct MixNodeCache {
@@ -137,3 +167,39 @@ pub(crate) struct EconomicDynamicsStats {
pub(crate) current_interval_uptime: u8,
}
fn get_common_owner(delegations: &[Delegation]) -> Option<Addr> {
let owner = delegations.iter().next()?.owner();
if delegations
.iter()
.any(|delegation| delegation.owner() != owner)
{
log::warn!("Unexpected different owners when summing delegations");
return None;
}
Some(owner)
}
fn get_common_node_identity(delegations: &[Delegation]) -> Option<String> {
let node_identity = delegations.iter().next()?.node_identity();
if delegations
.iter()
.any(|delegation| delegation.node_identity() != node_identity)
{
log::warn!("Unexpected different node identities when summing delegations");
return None;
}
Some(node_identity)
}
fn get_common_denom(delegations: &[Delegation]) -> Option<String> {
let denom = delegations.iter().next()?.amount.denom.clone();
if delegations
.iter()
.any(|delegation| delegation.amount.denom != denom)
{
log::warn!("Unexpected different coin denom when summing delegations");
return None;
}
Some(denom)
}
+20 -19
View File
@@ -8,8 +8,7 @@ use std::time::{Duration, SystemTime};
use serde::Serialize;
use tokio::sync::RwLock;
use mixnet_contract_common::MixNodeBond;
use validator_client::models::UptimeResponse;
use validator_client::models::{MixNodeBondAnnotated, UptimeResponse};
use crate::cache::Cache;
use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond};
@@ -32,7 +31,7 @@ pub(crate) struct MixNodeSummary {
#[derive(Clone, Debug)]
pub(crate) struct MixNodesResult {
pub(crate) valid_until: SystemTime,
pub(crate) all_mixnodes: HashMap<String, MixNodeBond>,
pub(crate) all_mixnodes: HashMap<String, MixNodeBondAnnotated>,
active_mixnodes: HashSet<String>,
rewarded_mixnodes: HashSet<String>,
}
@@ -61,7 +60,7 @@ impl MixNodesResult {
self.valid_until >= SystemTime::now()
}
fn get_mixnode(&self, pubkey: &str) -> Option<MixNodeBond> {
fn get_mixnode(&self, pubkey: &str) -> Option<MixNodeBondAnnotated> {
if self.is_valid() {
self.all_mixnodes.get(pubkey).cloned()
} else {
@@ -69,7 +68,7 @@ impl MixNodesResult {
}
}
fn get_mixnodes(&self) -> Option<HashMap<String, MixNodeBond>> {
fn get_mixnodes(&self) -> Option<HashMap<String, MixNodeBondAnnotated>> {
if self.is_valid() {
Some(self.all_mixnodes.clone())
} else {
@@ -128,11 +127,11 @@ impl ThreadsafeMixNodesCache {
);
}
pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option<MixNodeBond> {
pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option<MixNodeBondAnnotated> {
self.mixnodes.read().await.get_mixnode(pubkey)
}
pub(crate) async fn get_mixnodes(&self) -> Option<HashMap<String, MixNodeBond>> {
pub(crate) async fn get_mixnodes(&self) -> Option<HashMap<String, MixNodeBondAnnotated>> {
self.mixnodes.read().await.get_mixnodes()
}
@@ -151,13 +150,14 @@ impl ThreadsafeMixNodesCache {
match bond {
Some(bond) => Some(PrettyDetailedMixNodeBond {
location: location.and_then(|l| l.location.clone()),
status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key),
pledge_amount: bond.pledge_amount,
total_delegation: bond.total_delegation,
owner: bond.owner,
layer: bond.layer,
mix_node: bond.mix_node,
status: mixnodes_guard.determine_node_status(&bond.mix_node().identity_key),
pledge_amount: bond.mixnode_bond.pledge_amount,
total_delegation: bond.mixnode_bond.total_delegation,
owner: bond.mixnode_bond.owner,
layer: bond.mixnode_bond.layer,
mix_node: bond.mixnode_bond.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
stake_saturation: bond.stake_saturation,
}),
None => None,
}
@@ -172,18 +172,19 @@ impl ThreadsafeMixNodesCache {
.all_mixnodes
.values()
.map(|bond| {
let location = location_guard.get(&bond.mix_node.identity_key);
let copy = bond.clone();
let health = mixnode_health_guard.get(&bond.mix_node.identity_key);
let location = location_guard.get(&bond.mix_node().identity_key);
let copy = bond.mixnode_bond.clone();
let health = mixnode_health_guard.get(&bond.mix_node().identity_key);
PrettyDetailedMixNodeBond {
location: location.and_then(|l| l.location.clone()),
status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key),
status: mixnodes_guard.determine_node_status(&bond.mix_node().identity_key),
pledge_amount: copy.pledge_amount,
total_delegation: copy.total_delegation,
owner: copy.owner,
layer: copy.layer,
mix_node: copy.mix_node,
avg_uptime: health.map(|m| m.avg_uptime),
stake_saturation: bond.stake_saturation,
}
})
.collect()
@@ -191,14 +192,14 @@ impl ThreadsafeMixNodesCache {
pub(crate) async fn update_cache(
&self,
all_bonds: Vec<MixNodeBond>,
all_bonds: Vec<MixNodeBondAnnotated>,
rewarded_nodes: HashSet<String>,
active_nodes: HashSet<String>,
) {
let mut guard = self.mixnodes.write().await;
guard.all_mixnodes = all_bonds
.into_iter()
.map(|bond| (bond.mix_node.identity_key.to_string(), bond))
.map(|bond| (bond.mix_node().identity_key.to_string(), bond))
.collect();
guard.rewarded_mixnodes = rewarded_nodes;
guard.active_mixnodes = active_nodes;
+1 -1
View File
@@ -42,7 +42,7 @@ pub(crate) async fn index(
state.inner.ping.set_pending(pubkey).await;
// do the check
let ports = Some(port_check(&bond).await);
let ports = Some(port_check(&bond.mixnode_bond).await);
trace!("Tested mix node {}: {:?}", pubkey, ports);
let response = PingResponse {
ports,
+2 -2
View File
@@ -6,7 +6,7 @@ use log::info;
use serde::{Deserialize, Serialize};
use crate::client::ThreadsafeValidatorClient;
use mixnet_contract_common::MixNodeBond;
use validator_client::models::MixNodeBondAnnotated;
use crate::country_statistics::country_nodes_distribution::{
CountryNodesDistribution, ThreadsafeCountryNodesDistribution,
@@ -35,7 +35,7 @@ pub struct ExplorerApiState {
}
impl ExplorerApiState {
pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option<MixNodeBond> {
pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option<MixNodeBondAnnotated> {
self.mixnodes.get_mixnode(pubkey).await
}
}
+12 -12
View File
@@ -3,8 +3,8 @@
use std::future::Future;
use mixnet_contract_common::{GatewayBond, MixNodeBond};
use validator_client::models::UptimeResponse;
use mixnet_contract_common::GatewayBond;
use validator_client::models::{MixNodeBondAnnotated, UptimeResponse};
use validator_client::nymd::error::NymdError;
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
use validator_client::ValidatorClientError;
@@ -22,10 +22,10 @@ impl ExplorerApiTasks {
}
// a helper to remove duplicate code when grabbing active/rewarded/all mixnodes
async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec<MixNodeBond>
async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec<MixNodeBondAnnotated>
where
F: FnOnce(&'a validator_client::Client<QueryNymdClient>) -> Fut,
Fut: Future<Output = Result<Vec<MixNodeBond>, ValidatorClientError>>,
Fut: Future<Output = Result<Vec<MixNodeBondAnnotated>, ValidatorClientError>>,
{
let bonds = match f(&self.state.inner.validator_client.0).await {
Ok(result) => result,
@@ -39,9 +39,9 @@ impl ExplorerApiTasks {
bonds
}
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBond> {
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
info!("About to retrieve all mixnode bonds...");
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes)
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed)
.await
}
@@ -77,15 +77,15 @@ impl ExplorerApiTasks {
Ok(response)
}
async fn retrieve_rewarded_mixnodes(&self) -> Vec<MixNodeBond> {
async fn retrieve_rewarded_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
info!("About to retrieve rewarded mixnode bonds...");
self.retrieve_mixnodes(validator_client::Client::get_cached_rewarded_mixnodes)
self.retrieve_mixnodes(validator_client::Client::get_cached_rewarded_mixnodes_detailed)
.await
}
async fn retrieve_active_mixnodes(&self) -> Vec<MixNodeBond> {
async fn retrieve_active_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
info!("About to retrieve active mixnode bonds...");
self.retrieve_mixnodes(validator_client::Client::get_cached_active_mixnodes)
self.retrieve_mixnodes(validator_client::Client::get_cached_active_mixnodes_detailed)
.await
}
@@ -106,13 +106,13 @@ impl ExplorerApiTasks {
.retrieve_rewarded_mixnodes()
.await
.into_iter()
.map(|bond| bond.mix_node.identity_key)
.map(|bond| bond.mix_node().identity_key.clone())
.collect();
let active_nodes = self
.retrieve_active_mixnodes()
.await
.into_iter()
.map(|bond| bond.mix_node.identity_key)
.map(|bond| bond.mix_node().identity_key.clone())
.collect();
self.state
.inner
@@ -13,6 +13,7 @@ export type MixnodeRowType = {
layer: string;
profit_percentage: string;
avg_uptime: string;
stake_saturation: number;
};
export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] {
@@ -25,6 +26,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
const totalBond = pledge + delegations;
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
const profitPercentage = item.mix_node.profit_margin_percent || 0;
const stakeSaturation = typeof item.stake_saturation === 'number' ? item.stake_saturation * 100 : 0;
return {
id: item.owner,
status: item.status,
@@ -37,5 +39,6 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
layer: item?.layer || '',
profit_percentage: `${profitPercentage}%`,
avg_uptime: `${item.avg_uptime}%` || '-',
stake_saturation: stakeSaturation,
};
}
+21
View File
@@ -248,6 +248,27 @@ export const PageMixnodes: React.FC = () => {
</MuiLink>
),
},
{
field: 'stake_saturation',
headerName: 'Stake Saturation',
renderHeader: () => <CustomColumnHeading headingTitle="Stake Saturation" />,
headerClassName: 'MuiDataGrid-header-override',
width: 175,
headerAlign: 'left',
renderCell: (params: GridRenderCellParams) => (
<MuiLink
sx={{
textAlign: 'left',
color: params.value > 100 ? theme.palette.warning.main : 'inherit',
...getCellStyles(theme, params.row),
}}
component={RRDLink}
to={`/network-components/mixnode/${params.row.identity_key}`}
>
{`${params.value.toFixed(2)} %`}
</MuiLink>
),
},
];
const handlePageSize = (event: SelectChangeEvent<string>) => {
+1
View File
@@ -84,6 +84,7 @@ export interface MixNodeResponseItem {
};
mix_node: MixNode;
avg_uptime: number;
stake_saturation: number;
}
export type MixNodeResponse = MixNodeResponseItem[];
-1
View File
@@ -18,7 +18,6 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
bincode = "1.3"
crypto = { path = "../../common/crypto" }
pemstore = { path = "../../common/pemstore" }
@@ -25,9 +25,4 @@ pub enum HandshakeError {
MalformedRequest,
#[error("sent request was malformed")]
HandshakeFailure,
#[error("could not deserialize from slice: {source}")]
DeserializationError {
#[from]
source: bincode::Error,
},
}
+9 -13
View File
@@ -146,18 +146,13 @@ impl ClientControlRequest {
credential: &Credential,
shared_key: &SharedKeys,
iv: IV,
) -> Option<Self> {
match bincode::serialize(credential) {
Ok(serialized_credential) => {
let enc_credential =
shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
) -> Self {
let serialized_credential = credential.as_bytes();
let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
Some(ClientControlRequest::BandwidthCredential {
enc_credential,
iv: iv.to_bytes(),
})
}
_ => None,
ClientControlRequest::BandwidthCredential {
enc_credential,
iv: iv.to_bytes(),
}
}
@@ -167,8 +162,9 @@ impl ClientControlRequest {
shared_key: &SharedKeys,
iv: IV,
) -> Result<Credential, GatewayRequestsError> {
let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?;
bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption)
let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?;
Credential::from_bytes(&credential_bytes)
.map_err(|_| GatewayRequestsError::MalformedEncryption)
}
#[cfg(not(feature = "coconut"))]
+7 -11
View File
@@ -1,13 +1,8 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use std::convert::TryFrom;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(feature = "coconut")]
use credentials::error::Error;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
@@ -22,12 +17,13 @@ impl Bandwidth {
}
#[cfg(feature = "coconut")]
impl TryFrom<Credential> for Bandwidth {
type Error = Error;
fn try_from(credential: Credential) -> Result<Self, Self::Error> {
let value = credential.voucher_value()?;
Ok(Self { value })
impl From<Credential> for Bandwidth {
fn from(credential: Credential) -> Self {
let token_value = credential.voucher_value();
let bandwidth_bytes = token_value * network_defaults::BYTES_PER_UTOKEN;
Bandwidth {
value: bandwidth_bytes,
}
}
}
@@ -42,8 +42,8 @@ pub(crate) enum RequestHandlingError {
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
UnsupportedBandwidthValue(u64),
#[error("Provided bandwidth credential did not verify correctly")]
InvalidBandwidthCredential,
#[error("Provided bandwidth credential did not verify correctly on {0}")]
InvalidBandwidthCredential(String),
#[error("This gateway is not running in the disabled credentials mode")]
NotInDisabledCredentialsMode,
@@ -65,8 +65,12 @@ pub(crate) enum RequestHandlingError {
NymdError(#[from] validator_client::nymd::error::NymdError),
#[cfg(feature = "coconut")]
#[error("Provided coconut bandwidth credential did not have expected structure - {0}")]
CoconutBandwidthCredentialError(#[from] credentials::error::Error),
#[error("Validator API error")]
APIError(#[from] validator_client::ValidatorClientError),
#[cfg(feature = "coconut")]
#[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")]
NotEnoughValidatorAPIs { received: usize, needed: usize },
}
impl RequestHandlingError {
@@ -208,11 +212,55 @@ where
iv,
)?;
if !credential.verify(&self.inner.aggregated_verification_key) {
return Err(RequestHandlingError::InvalidBandwidthCredential);
if !credential.verify(
self.inner
.coconut_verifier
.as_ref()
.aggregated_verification_key(),
) {
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("credential failed to verify on gateway"),
));
}
let bandwidth = Bandwidth::try_from(credential)?;
let req = coconut_interface::ProposeReleaseFundsRequestBody::new(credential.clone());
let proposal_id = self
.inner
.coconut_verifier
.api_clients()
.get(0)
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
needed: 1,
received: 0,
})?
.propose_release_funds(&req)
.await?
.proposal_id;
let req = coconut_interface::VerifyCredentialBody::new(credential.clone(), proposal_id);
for client in self.inner.coconut_verifier.api_clients().iter().skip(1) {
if !client
.verify_bandwidth_credential(&req)
.await?
.verification_result
{
debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url());
}
}
let req = coconut_interface::ExecuteReleaseFundsRequestBody::new(proposal_id);
self.inner
.coconut_verifier
.api_clients()
.get(0)
.ok_or(RequestHandlingError::NotEnoughValidatorAPIs {
needed: 1,
received: 0,
})?
.execute_release_funds(&req)
.await?;
let bandwidth = Bandwidth::from(credential);
let bandwidth_value = bandwidth.value();
if bandwidth_value > i64::MAX as u64 {
@@ -254,11 +302,15 @@ where
.inner
.check_local_identity(&credential.gateway_identity())
{
return Err(RequestHandlingError::InvalidBandwidthCredential);
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
));
}
if !credential.verify_signature() {
return Err(RequestHandlingError::InvalidBandwidthCredential);
return Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
));
}
debug!("Verifying Ethereum for token burn...");
let gateway_owner = self
@@ -0,0 +1,27 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use coconut_interface::VerificationKey;
use validator_client::ApiClient;
pub struct CoconutVerifier {
api_clients: Vec<ApiClient>,
aggregated_verification_key: VerificationKey,
}
impl CoconutVerifier {
pub fn new(api_clients: Vec<ApiClient>, aggregated_verification_key: VerificationKey) -> Self {
CoconutVerifier {
api_clients,
aggregated_verification_key,
}
}
pub fn api_clients(&self) -> &Vec<ApiClient> {
&self.api_clients
}
pub fn aggregated_verification_key(&self) -> &VerificationKey {
&self.aggregated_verification_key
}
}
@@ -39,16 +39,9 @@ impl ERC20Bridge {
.expect("The list of validators is empty");
let mnemonic =
Mnemonic::from_str(&cosmos_mnemonic).expect("Invalid Cosmos mnemonic provided");
let nymd_client = NymdClient::connect_with_mnemonic(
DEFAULT_NETWORK,
nymd_url.as_ref(),
AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).ok(),
None,
AccountId::from_str(DEFAULT_NETWORK.bandwidth_claim_contract_address()).ok(),
mnemonic,
None,
)
.expect("Could not create nymd client");
let nymd_client =
NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None)
.expect("Could not create nymd client");
ERC20Bridge {
contract: eth_contract(web3.clone()),
@@ -95,7 +88,9 @@ impl ERC20Bridge {
}
}
Err(RequestHandlingError::InvalidBandwidthCredential)
Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
))
}
pub(crate) async fn verify_gateway_owner(
@@ -103,17 +98,22 @@ impl ERC20Bridge {
gateway_owner: String,
gateway_identity: &PublicKey,
) -> Result<(), RequestHandlingError> {
let owner_address = AccountId::from_str(&gateway_owner)
.map_err(|_| RequestHandlingError::InvalidBandwidthCredential)?;
let owner_address = AccountId::from_str(&gateway_owner).map_err(|_| {
RequestHandlingError::InvalidBandwidthCredential(String::from("gateway"))
})?;
let gateway_bond = self
.nymd_client
.owns_gateway(&owner_address)
.await?
.ok_or(RequestHandlingError::InvalidBandwidthCredential)?;
.ok_or_else(|| {
RequestHandlingError::InvalidBandwidthCredential(String::from("gateway"))
})?;
if gateway_bond.gateway.identity_key == gateway_identity.to_base58_string() {
Ok(())
} else {
Err(RequestHandlingError::InvalidBandwidthCredential)
Err(RequestHandlingError::InvalidBandwidthCredential(
String::from("gateway"),
))
}
}
@@ -121,9 +121,7 @@ impl ERC20Bridge {
&self,
credential: &TokenCredential,
) -> Result<(), RequestHandlingError> {
// It's ok to unwrap here, as the cosmos contract is set correctly
let erc20_bridge_contract_address =
self.nymd_client.erc20_bridge_contract_address().unwrap();
let bandwidth_claim_contract_address = self.nymd_client.bandwidth_claim_contract_address();
let req = ExecuteMsg::LinkPayment {
data: LinkPaymentData::new(
credential.verification_key().to_bytes(),
@@ -134,7 +132,7 @@ impl ERC20Bridge {
};
self.nymd_client
.execute(
erc20_bridge_contract_address,
bandwidth_claim_contract_address,
&req,
Default::default(),
"Linking payment",
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::active_clients::ActiveClientsStore;
#[cfg(feature = "coconut")]
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
use crate::node::client_handling::websocket::connection_handler::{
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
};
@@ -27,9 +29,6 @@ use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
@@ -77,11 +76,10 @@ pub(crate) struct FreshHandler<R, S, St> {
pub(crate) socket_connection: SocketStream<S>,
pub(crate) storage: St,
#[cfg(feature = "coconut")]
pub(crate) aggregated_verification_key: VerificationKey,
#[cfg(not(feature = "coconut"))]
pub(crate) erc20_bridge: Arc<ERC20Bridge>,
#[cfg(feature = "coconut")]
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
}
impl<R, S, St> FreshHandler<R, S, St>
@@ -102,7 +100,7 @@ where
local_identity: Arc<identity::KeyPair>,
storage: St,
active_clients_store: ActiveClientsStore,
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: Arc<ERC20Bridge>,
) -> Self {
FreshHandler {
@@ -114,7 +112,7 @@ where
local_identity,
storage,
#[cfg(feature = "coconut")]
aggregated_verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge,
}
@@ -15,6 +15,8 @@ pub(crate) use self::authenticated::AuthenticatedHandler;
pub(crate) use self::fresh::FreshHandler;
mod authenticated;
#[cfg(feature = "coconut")]
pub(crate) mod coconut;
#[cfg(not(feature = "coconut"))]
pub(crate) mod eth_events;
mod fresh;
@@ -14,7 +14,7 @@ use std::sync::Arc;
use tokio::task::JoinHandle;
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
@@ -25,8 +25,7 @@ pub(crate) struct Listener {
disabled_credentials_mode: bool,
#[cfg(feature = "coconut")]
aggregated_verification_key: VerificationKey,
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))]
erc20_bridge: Arc<ERC20Bridge>,
}
@@ -36,7 +35,7 @@ impl Listener {
address: SocketAddr,
local_identity: Arc<identity::KeyPair>,
disabled_credentials_mode: bool,
#[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge,
) -> Self {
Listener {
@@ -44,7 +43,7 @@ impl Listener {
local_identity,
disabled_credentials_mode,
#[cfg(feature = "coconut")]
aggregated_verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge: Arc::new(erc20_bridge),
}
@@ -84,7 +83,7 @@ impl Listener {
storage.clone(),
active_clients_store.clone(),
#[cfg(feature = "coconut")]
self.aggregated_verification_key.clone(),
Arc::clone(&self.coconut_verifier),
#[cfg(not(feature = "coconut"))]
Arc::clone(&self.erc20_bridge),
);
+25 -8
View File
@@ -18,11 +18,11 @@ use std::process;
use std::sync::Arc;
use crate::config::persistence::pathfinder::GatewayPathfinder;
#[cfg(feature = "coconut")]
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
#[cfg(not(feature = "coconut"))]
use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge;
#[cfg(feature = "coconut")]
use coconut_interface::VerificationKey;
#[cfg(feature = "coconut")]
use credentials::obtain_aggregate_verification_key;
use self::storage::PersistentStorage;
@@ -175,7 +175,7 @@ where
&self,
forwarding_channel: MixForwardingSender,
active_clients_store: ActiveClientsStore,
#[cfg(feature = "coconut")] verification_key: VerificationKey,
#[cfg(feature = "coconut")] coconut_verifier: Arc<CoconutVerifier>,
#[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge,
) {
info!("Starting client [web]socket listener...");
@@ -190,7 +190,7 @@ where
Arc::clone(&self.identity_keypair),
self.config.get_disabled_credentials_mode(),
#[cfg(feature = "coconut")]
verification_key,
coconut_verifier,
#[cfg(not(feature = "coconut"))]
erc20_bridge,
)
@@ -227,13 +227,27 @@ where
);
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_gateway_exists(&self) -> Option<String> {
fn random_api_client(&self) -> validator_client::ApiClient {
let endpoints = self.config.get_validator_api_endpoints();
let validator_api = endpoints
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
let validator_client = validator_client::ApiClient::new(validator_api.clone());
validator_client::ApiClient::new(validator_api.clone())
}
#[cfg(feature = "coconut")]
fn all_api_clients(&self) -> Vec<validator_client::ApiClient> {
self.config
.get_validator_api_endpoints()
.into_iter()
.map(validator_client::ApiClient::new)
.collect()
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_gateway_exists(&self) -> Option<String> {
let validator_client = self.random_api_client();
let existing_gateways = match validator_client.get_cached_gateways().await {
Ok(gateways) => gateways,
@@ -271,6 +285,9 @@ where
obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints())
.await
.expect("failed to contact validators to obtain their verification keys");
#[cfg(feature = "coconut")]
let coconut_verifier =
CoconutVerifier::new(self.all_api_clients(), validators_verification_key);
#[cfg(not(feature = "coconut"))]
let erc20_bridge = ERC20Bridge::new(
@@ -291,7 +308,7 @@ where
mix_forwarding_channel,
active_clients_store,
#[cfg(feature = "coconut")]
validators_verification_key,
Arc::new(coconut_verifier),
#[cfg(not(feature = "coconut"))]
erc20_bridge,
);
+7 -3
View File
@@ -244,14 +244,18 @@ impl MixNode {
atomic_verloc_results
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_node_exists(&mut self) -> Option<String> {
fn random_api_client(&self) -> validator_client::ApiClient {
let endpoints = self.config.get_validator_api_endpoints();
let validator_api = endpoints
.choose(&mut thread_rng())
.expect("The list of validator apis is empty");
let validator_client = validator_client::ApiClient::new(validator_api.clone());
validator_client::ApiClient::new(validator_api.clone())
}
// TODO: ask DH whether this function still makes sense in ^0.10
async fn check_if_same_ip_node_exists(&mut self) -> Option<String> {
let validator_client = self.random_api_client();
let existing_nodes = match validator_client.get_cached_mixnodes().await {
Ok(nodes) => nodes,
Err(err) => {
+69
View File
@@ -709,6 +709,15 @@ dependencies = [
"objc",
]
[[package]]
name = "coconut-bandwidth-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "coconut-interface"
version = "0.1.0"
@@ -1096,6 +1105,42 @@ dependencies = [
"serde",
]
[[package]]
name = "cw-utils"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "cw3"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef"
dependencies = [
"cosmwasm-std",
"cw-utils",
"schemars",
"serde",
]
[[package]]
name = "cw4"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52"
dependencies = [
"cosmwasm-std",
"cw-storage-plus",
"schemars",
"serde",
]
[[package]]
name = "darling"
version = "0.10.2"
@@ -1492,6 +1537,14 @@ version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "execute"
version = "0.1.0"
dependencies = [
"quote",
"syn",
]
[[package]]
name = "eyre"
version = "0.6.7"
@@ -2801,6 +2854,18 @@ dependencies = [
"ts-rs",
]
[[package]]
name = "multisig-contract-common"
version = "0.1.0"
dependencies = [
"cosmwasm-std",
"cw-utils",
"cw3",
"cw4",
"schemars",
"serde",
]
[[package]]
name = "native-tls"
version = "0.2.8"
@@ -5526,16 +5591,20 @@ dependencies = [
"async-trait",
"base64",
"bip39",
"coconut-bandwidth-contract-common",
"coconut-interface",
"colored",
"config",
"cosmrs",
"cosmwasm-std",
"cw3",
"execute",
"flate2",
"futures",
"itertools",
"log",
"mixnet-contract-common",
"multisig-contract-common",
"network-defaults",
"prost",
"reqwest",
+6 -9
View File
@@ -215,34 +215,31 @@ impl Config {
.flat_map(|c| c.validators().cloned())
}
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option<CosmosAccountId> {
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.mixnet_contract_address(network.into())
.expect("No mixnet contract address found in config")
.parse()
.ok()
.expect("Wrong format for mixnet contract address")
}
pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> Option<CosmosAccountId> {
pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.vesting_contract_address(network.into())
.expect("No vesting contract address found in config")
.parse()
.ok()
.expect("Wrong format for vesting contract address")
}
pub fn get_bandwidth_claim_contract_address(
&self,
network: WalletNetwork,
) -> Option<CosmosAccountId> {
pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId {
self.base
.networks
.bandwidth_claim_contract_address(network.into())
.expect("No bandwidth claim contract address found in config")
.parse()
.ok()
.expect("Wrong format for bandwidth claim contract address")
}
pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) {
+16
View File
@@ -66,6 +66,10 @@ fn main() {
mixnet::delegate::get_all_mix_delegations,
mixnet::delegate::undelegate_from_mixnode,
mixnet::epoch::get_current_epoch,
mixnet::rewards::claim_delegator_reward,
mixnet::rewards::claim_operator_reward,
mixnet::rewards::compound_operator_reward,
mixnet::rewards::compound_delegator_reward,
mixnet::send::send,
network_config::add_validator,
network_config::get_validator_api_urls,
@@ -86,6 +90,10 @@ fn main() {
validator_api::status::mixnode_reward_estimation,
validator_api::status::mixnode_stake_saturation,
validator_api::status::mixnode_status,
vesting::rewards::vesting_claim_delegator_reward,
vesting::rewards::vesting_claim_operator_reward,
vesting::rewards::vesting_compound_operator_reward,
vesting::rewards::vesting_compound_delegator_reward,
vesting::bond::vesting_bond_gateway,
vesting::bond::vesting_bond_mixnode,
vesting::bond::vesting_unbond_gateway,
@@ -123,6 +131,14 @@ fn main() {
simulate::vesting::simulate_vesting_unbond_mixnode,
simulate::vesting::simulate_vesting_update_mixnode,
simulate::vesting::simulate_withdraw_vested_coins,
simulate::vesting::simulate_vesting_claim_delegator_reward,
simulate::vesting::simulate_vesting_claim_operator_reward,
simulate::vesting::simulate_vesting_compound_operator_reward,
simulate::vesting::simulate_vesting_compound_delegator_reward,
simulate::mixnet::simulate_claim_delegator_reward,
simulate::mixnet::simulate_claim_operator_reward,
simulate::mixnet::simulate_compound_operator_reward,
simulate::mixnet::simulate_compound_delegator_reward,
])
.menu(Menu::new().add_default_app_submenu_if_macos())
.run(tauri::generate_context!())
@@ -73,7 +73,7 @@ pub async fn switch_network(
let denom = network.base_mix_denom();
Account::new(
client.nymd.mixnet_contract_address()?.to_string(),
client.nymd.mixnet_contract_address().to_string(),
client.nymd.address().to_string(),
denom.into(),
)
@@ -156,7 +156,7 @@ async fn _connect_with_mnemonic(
.find(|client| WalletNetwork::from(client.network) == default_network);
let account_for_default_network = match client_for_default_network {
Some(client) => Ok(Account::new(
client.nymd.mixnet_contract_address()?.to_string(),
client.nymd.mixnet_contract_address().to_string(),
client.nymd.address().to_string(),
default_network.base_mix_denom().into(),
)),
@@ -250,14 +250,12 @@ fn create_clients(
log::info!("Connecting to: api_url: {api_url} for {network}");
let mut client = validator_client::Client::new_signing(
validator_client::Config::new(
network.into(),
nymd_url,
api_url,
config.get_mixnet_contract_address(network),
config.get_vesting_contract_address(network),
config.get_bandwidth_claim_contract_address(network),
),
validator_client::Config::new(network.into(), nymd_url, api_url)
.with_mixnode_contract_address(config.get_mixnet_contract_address(network))
.with_vesting_contract_address(config.get_vesting_contract_address(network))
.with_bandwidth_claim_contract_address(
config.get_bandwidth_claim_contract_address(network),
),
mnemonic.clone(),
)?;
client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER);
@@ -3,4 +3,5 @@ pub mod admin;
pub mod bond;
pub mod delegate;
pub mod epoch;
pub mod rewards;
pub mod send;
@@ -0,0 +1,53 @@
use crate::error::BackendError;
use crate::nymd_client;
use crate::state::State;
use mixnet_contract_common::IdentityKey;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::Fee;
#[tauri::command]
pub async fn claim_operator_reward(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_claim_operator_reward(fee)
.await?;
Ok(())
}
#[tauri::command]
pub async fn compound_operator_reward(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_compound_operator_reward(fee)
.await?;
Ok(())
}
#[tauri::command]
pub async fn claim_delegator_reward(
mix_identity: IdentityKey,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_claim_delegator_reward(mix_identity, fee)
.await?;
Ok(())
}
#[tauri::command]
pub async fn compound_delegator_reward(
mix_identity: IdentityKey,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_compound_delegator_reward(mix_identity, fee)
.await?;
Ok(())
}
@@ -18,7 +18,7 @@ pub async fn simulate_update_contract_settings(
let mixnet_contract_settings_params: ContractStateParams = params.try_into()?;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -4,6 +4,7 @@
use crate::error::BackendError;
use crate::operations::simulate::{FeeDetails, SimulateResult};
use crate::State;
use mixnet_contract_common::IdentityKey;
use mixnet_contract_common::{ExecuteMsg, Gateway, MixNode};
use nym_types::currency::DecCoin;
use std::sync::Arc;
@@ -20,7 +21,7 @@ pub async fn simulate_bond_gateway(
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
// TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code
@@ -44,7 +45,7 @@ pub async fn simulate_unbond_gateway(
let guard = state.read().await;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -68,7 +69,7 @@ pub async fn simulate_bond_mixnode(
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -91,7 +92,7 @@ pub async fn simulate_unbond_mixnode(
let guard = state.read().await;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -112,7 +113,7 @@ pub async fn simulate_update_mixnode(
let guard = state.read().await;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -137,7 +138,7 @@ pub async fn simulate_delegate_to_mixnode(
let delegation = guard.attempt_convert_to_base_coin(amount)?;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -160,7 +161,7 @@ pub async fn simulate_undelegate_from_mixnode(
let guard = state.read().await;
let client = guard.current_client()?;
let mixnet_contract = client.nymd.mixnet_contract_address()?;
let mixnet_contract = client.nymd.mixnet_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -174,3 +175,59 @@ pub async fn simulate_undelegate_from_mixnode(
let result = client.nymd.simulate(vec![msg]).await?;
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_claim_operator_reward(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client.nymd.simulate_claim_operator_reward(None).await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_compound_operator_reward(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client.nymd.simulate_compound_operator_reward(None).await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_claim_delegator_reward(
mix_identity: IdentityKey,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_claim_delegator_reward(mix_identity, None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_compound_delegator_reward(
mix_identity: IdentityKey,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_compound_delegator_reward(mix_identity, None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
@@ -4,6 +4,7 @@
use crate::error::BackendError;
use crate::operations::simulate::{FeeDetails, SimulateResult};
use crate::State;
use mixnet_contract_common::IdentityKey;
use mixnet_contract_common::{Gateway, MixNode};
use nym_types::currency::DecCoin;
use std::sync::Arc;
@@ -21,7 +22,7 @@ pub async fn simulate_vesting_bond_gateway(
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -45,7 +46,7 @@ pub async fn simulate_vesting_unbond_gateway(
let guard = state.read().await;
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -69,7 +70,7 @@ pub async fn simulate_vesting_bond_mixnode(
let pledge = guard.attempt_convert_to_base_coin(pledge)?;
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -93,7 +94,7 @@ pub async fn simulate_vesting_unbond_mixnode(
let guard = state.read().await;
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -114,7 +115,7 @@ pub async fn simulate_vesting_update_mixnode(
let guard = state.read().await;
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -138,7 +139,7 @@ pub async fn simulate_withdraw_vested_coins(
let amount = guard.attempt_convert_to_base_coin(amount)?.into();
let client = guard.current_client()?;
let vesting_contract = client.nymd.vesting_contract_address()?;
let vesting_contract = client.nymd.vesting_contract_address();
let gas_price = client.nymd.gas_price().clone();
let msg = client.nymd.wrap_contract_execute_message(
@@ -150,3 +151,65 @@ pub async fn simulate_withdraw_vested_coins(
let result = client.nymd.simulate(vec![msg]).await?;
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_vesting_claim_operator_reward(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_vesting_claim_operator_reward(None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_vesting_compound_operator_reward(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_vesting_compound_operator_reward(None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_vesting_claim_delegator_reward(
mix_identity: IdentityKey,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_vesting_claim_delegator_reward(mix_identity, None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
#[tauri::command]
pub async fn simulate_vesting_compound_delegator_reward(
mix_identity: IdentityKey,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<FeeDetails, BackendError> {
let guard = state.read().await;
let client = guard.current_client()?;
let result = client
.nymd
.simulate_vesting_compound_delegator_reward(mix_identity, None)
.await?;
let gas_price = client.nymd.gas_price().clone();
guard.create_detailed_fee(SimulateResult::new(result.gas_info, gas_price))
}
@@ -16,7 +16,7 @@ pub async fn get_pending_vesting_delegation_events(
let guard = state.read().await;
let reg = guard.registered_coins()?;
let client = &guard.current_client()?.nymd;
let vesting_contract = client.vesting_contract_address()?;
let vesting_contract = client.vesting_contract_address();
let events = client
.get_pending_delegation_events(
@@ -1,3 +1,4 @@
pub mod bond;
pub mod delegate;
pub mod queries;
pub mod rewards;
@@ -0,0 +1,52 @@
use crate::error::BackendError;
use crate::nymd_client;
use crate::state::State;
use mixnet_contract_common::IdentityKey;
use std::sync::Arc;
use tokio::sync::RwLock;
use validator_client::nymd::Fee;
#[tauri::command]
pub async fn vesting_claim_operator_reward(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_vesting_claim_operator_reward(None)
.await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_compound_operator_reward(
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_vesting_compound_operator_reward(fee)
.await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_claim_delegator_reward(
mix_identity: IdentityKey,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_vesting_claim_delegator_reward(mix_identity, fee)
.await?;
Ok(())
}
#[tauri::command]
pub async fn vesting_compound_delegator_reward(
mix_identity: IdentityKey,
fee: Option<Fee>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
nymd_client!(state)
.execute_vesting_compound_delegator_reward(mix_identity, fee)
.await?;
Ok(())
}
+2 -9
View File
@@ -1,9 +1,2 @@
export interface Gateway {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
}
export interface Gateway { host: string, mix_port: number, clients_port: number, location: string, sphinx_key: string, identity_key: string, version: string, }
+2 -10
View File
@@ -1,10 +1,2 @@
export interface MixNode {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
profit_margin_percent: number;
}
export interface MixNode { host: string, mix_port: number, verloc_port: number, http_api_port: number, sphinx_key: string, identity_key: string, version: string, profit_margin_percent: number, }
@@ -1 +1,2 @@
export type RewardedSetNodeStatus = 'Active' | 'Standby';
export type RewardedSetNodeStatus = "Active" | "Standby";
+2
View File
@@ -52,9 +52,11 @@ schemars = { version = "0.8", features = ["preserve_order"] }
## internal
coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
config = { path = "../common/config" }
cosmwasm-std = "1.0.0-beta8"
crypto = { path="../common/crypto" }
gateway-client = { path="../common/client-libs/gateway-client" }
mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" }
multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
nymsphinx = { path="../common/nymsphinx" }
topology = { path="../common/topology" }
validator-api-requests = { path = "validator-api-requests" }
+13 -1
View File
@@ -2,9 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use crate::coconut::error::Result;
use validator_client::nymd::TxResponse;
use multisig_contract_common::msg::ProposalResponse;
use validator_client::nymd::{Fee, TxResponse};
#[async_trait]
pub trait Client {
async fn get_tx(&self, tx_hash: &str) -> Result<TxResponse>;
async fn get_proposal(&self, proposal_id: u64) -> Result<ProposalResponse>;
async fn propose_release_funds(
&self,
title: String,
blinded_serial_number: String,
voucher_value: u128,
fee: Option<Fee>,
) -> Result<u64>;
async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option<Fee>)
-> Result<()>;
async fn execute_proposal(&self, proposal_id: u64, fee: Option<Fee>) -> Result<()>;
}
+14
View File
@@ -63,8 +63,22 @@ pub enum CoconutError {
#[error("Error in coconut interface - {0}")]
CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError),
#[error("Could not create proposal for spending credential")]
CreateProposalError,
#[error("Storage error - {0}")]
StorageError(#[from] ValidatorApiStorageError),
#[error("Credentials error - {0}")]
CredentialsError(#[from] credentials::error::Error),
#[error(
"Incorrect credential proposal description. Expected blinded serial number in base 58"
)]
IncorrectProposal,
#[error("Internal error: {0}")]
InternalError(String),
}
impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError {
+98 -11
View File
@@ -14,15 +14,19 @@ use crate::ValidatorApiStorage;
use coconut_interface::{
Attribute, BlindSignRequest, BlindSignRequestBody, BlindedSignature, BlindedSignatureResponse,
KeyPair, Parameters, VerificationKeyResponse,
ExecuteReleaseFundsRequestBody, KeyPair, Parameters, ProposeReleaseFundsRequestBody,
ProposeReleaseFundsResponse, VerificationKey, VerificationKeyResponse, VerifyCredentialBody,
VerifyCredentialResponse,
};
use config::defaults::VALIDATOR_API_VERSION;
use credentials::coconut::params::{
ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm,
};
use credentials::obtain_aggregate_verification_key;
use crypto::asymmetric::encryption;
use crypto::shared_key::new_ephemeral_shared_key;
use crypto::symmetric::stream_cipher;
use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES};
use getset::{CopyGetters, Getters};
use rand_07::rngs::OsRng;
@@ -30,26 +34,33 @@ use rocket::fairing::AdHoc;
use rocket::serde::json::Json;
use rocket::State as RocketState;
use std::sync::Arc;
use tokio::sync::{Mutex, RwLock};
use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES};
use tokio::sync::Mutex;
use url::Url;
pub struct State {
client: Arc<RwLock<dyn LocalClient + Send + Sync>>,
client: Arc<dyn LocalClient + Send + Sync>,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
rng: Arc<Mutex<OsRng>>,
}
impl State {
pub(crate) fn new<C>(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> Self
pub(crate) fn new<C>(
client: C,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
) -> Self
where
C: LocalClient + Send + Sync + 'static,
{
let client = Arc::new(RwLock::new(client));
let client = Arc::new(client);
let rng = Arc::new(Mutex::new(OsRng));
Self {
client,
key_pair,
validator_apis,
storage,
rng,
}
@@ -109,6 +120,10 @@ impl State {
Ok(response)
}
}
pub async fn verification_key(&self) -> Result<VerificationKey> {
Ok(obtain_aggregate_verification_key(&self.validator_apis).await?)
}
}
#[derive(Getters, CopyGetters, Debug)]
@@ -135,11 +150,16 @@ impl InternalSignRequest {
}
}
pub fn stage<C>(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> AdHoc
pub fn stage<C>(
client: C,
key_pair: KeyPair,
validator_apis: Vec<Url>,
storage: ValidatorApiStorage,
) -> AdHoc
where
C: LocalClient + Send + Sync + 'static,
{
let state = State::new(client, key_pair, storage);
let state = State::new(client, key_pair, validator_apis, storage);
AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async {
rocket.manage(state).mount(
// this format! is so ugly...
@@ -150,7 +170,10 @@ impl InternalSignRequest {
routes![
post_blind_sign,
get_verification_key,
post_partial_bandwidth_credential
post_partial_bandwidth_credential,
verify_bandwidth_credential,
post_propose_release_funds,
post_execute_release_funds
],
)
})
@@ -183,8 +206,6 @@ pub async fn post_blind_sign(
}
let tx = state
.client
.read()
.await
.get_tx(blind_sign_request_body.tx_hash())
.await?;
let encryption_key = extract_encryption_key(&blind_sign_request_body, tx).await?;
@@ -226,3 +247,69 @@ pub async fn get_verification_key(
state.key_pair.verification_key(),
)))
}
#[post("/verify-bandwidth-credential", data = "<verify_credential_body>")]
pub async fn verify_bandwidth_credential(
verify_credential_body: Json<VerifyCredentialBody>,
state: &RocketState<State>,
) -> Result<Json<VerifyCredentialResponse>> {
let proposal_id = *verify_credential_body.0.proposal_id();
let proposal = state.client.get_proposal(proposal_id).await?;
// Proposal description is the blinded serial number
if !verify_credential_body
.0
.credential()
.has_blinded_serial_number(&proposal.description)?
{
return Err(CoconutError::IncorrectProposal);
}
let verification_key = state.verification_key().await?;
let verification_result = verify_credential_body
.0
.credential()
.verify(&verification_key);
// Vote yes or no on the proposal based on the verification result
state
.client
.vote_proposal(proposal_id, verification_result, None)
.await?;
Ok(Json(VerifyCredentialResponse::new(verification_result)))
}
#[post("/propose-release-funds", data = "<propose_release_funds>")]
pub async fn post_propose_release_funds(
propose_release_funds: Json<ProposeReleaseFundsRequestBody>,
state: &RocketState<State>,
) -> Result<Json<ProposeReleaseFundsResponse>> {
let verification_key = state.verification_key().await?;
if !propose_release_funds
.0
.credential()
.verify(&verification_key)
{
return Err(CoconutError::CreateProposalError);
}
let title = String::from("Create proposal to spend a coconut credential");
let blinded_serial_number = propose_release_funds.0.credential().blinded_serial_number();
let voucher_value = propose_release_funds.0.credential().voucher_value() as u128;
let proposal_id = state
.client
.propose_release_funds(title, blinded_serial_number, voucher_value, None)
.await?;
Ok(Json(ProposeReleaseFundsResponse::new(proposal_id)))
}
#[post("/execute-release-funds", data = "<execute_release_funds>")]
pub async fn post_execute_release_funds(
execute_release_funds: Json<ExecuteReleaseFundsRequestBody>,
state: &RocketState<State>,
) -> Result<Json<()>> {
let proposal_id = *execute_release_funds.0.proposal_id();
state.client.execute_proposal(proposal_id, None).await?;
Ok(Json(()))
}
+43 -3
View File
@@ -15,10 +15,11 @@ use credentials::coconut::params::{
};
use crypto::shared_key::recompute_shared_key;
use crypto::symmetric::stream_cipher;
use multisig_contract_common::msg::ProposalResponse;
use nymcoconut::{
prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters,
};
use validator_client::nymd::{tx::Hash, DeliverTx, Event, Tag, TxResponse};
use validator_client::nymd::{tx::Hash, DeliverTx, Event, Fee, Tag, TxResponse};
use validator_client::validator_api::routes::{
API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL,
COCONUT_ROUTES, COCONUT_VERIFICATION_KEY,
@@ -56,6 +57,37 @@ impl super::client::Client for DummyClient {
.cloned()
.ok_or(CoconutError::TxHashParseError)
}
async fn get_proposal(&self, _proposal_id: u64) -> Result<ProposalResponse> {
todo!()
}
async fn propose_release_funds(
&self,
_title: String,
_blinded_serial_number: String,
_voucher_value: u128,
_fee: Option<Fee>,
) -> Result<u64> {
todo!()
}
async fn vote_proposal(
&self,
_proposal_id: u64,
_vote_yes: bool,
_fee: Option<Fee>,
) -> Result<()> {
todo!()
}
async fn execute_proposal(
&self,
proposal_id: u64,
fee: Option<Fee>,
) -> crate::coconut::error::Result<()> {
todo!()
}
}
pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse {
@@ -87,7 +119,12 @@ async fn check_signer_verif_key(key_pair: KeyPair) {
let nymd_db = Arc::new(RwLock::new(HashMap::new()));
let nymd_client = DummyClient::new(&nymd_db);
let rocket = rocket::build().attach(InternalSignRequest::stage(nymd_client, key_pair, storage));
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage,
));
let client = Client::tracked(rocket)
.await
@@ -172,6 +209,7 @@ async fn signed_before() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
@@ -231,7 +269,7 @@ async fn state_functions() {
let mut db_dir = std::env::temp_dir();
db_dir.push(&key_pair.verification_key().to_bs58()[..8]);
let storage = ValidatorApiStorage::init(db_dir).await.unwrap();
let state = State::new(nymd_client, key_pair, storage.clone());
let state = State::new(nymd_client, key_pair, vec![], storage.clone());
let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E");
assert!(state.signed_before(&tx_hash).await.unwrap().is_none());
@@ -393,6 +431,7 @@ async fn blind_sign_correct() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
@@ -465,6 +504,7 @@ async fn signature_test() {
let rocket = rocket::build().attach(InternalSignRequest::stage(
nymd_client,
key_pair,
vec![],
storage.clone(),
));
let client = Client::tracked(rocket)
+52 -23
View File
@@ -2,16 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::config::template::config_template;
use config::defaults::{default_api_endpoints, DEFAULT_NETWORK};
use config::defaults::DEFAULT_NETWORK;
use config::NymConfig;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::Duration;
use url::Url;
#[cfg(feature = "coconut")]
use coconut_interface::{Base58, KeyPair};
mod template;
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
@@ -82,17 +79,20 @@ impl NymConfig for Config {
}
fn config_directory(&self) -> PathBuf {
self.root_directory().join("config")
self.root_directory().join(self.get_id()).join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join("data")
self.root_directory().join(self.get_id()).join("data")
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct Base {
/// ID specifies the human readable ID of this particular validator-api.
id: String,
local_validator: Url,
/// Address of the validator contract managing the network
@@ -105,6 +105,7 @@ pub struct Base {
impl Default for Base {
fn default() -> Self {
Base {
id: String::default(),
local_validator: DEFAULT_LOCAL_VALIDATOR
.parse()
.expect("default local validator is malformed!"),
@@ -128,11 +129,6 @@ pub struct NetworkMonitor {
#[serde(default)]
disabled_credentials_mode: bool,
/// Specifies list of all validators on the network issuing coconut credentials.
/// A special care must be taken to ensure they are in correct order.
/// The list must also contain THIS validator that is running the test
all_validator_apis: Vec<Url>,
/// Specifies the interval at which the network monitor sends the test packets.
#[serde(with = "humantime_serde")]
run_interval: Duration,
@@ -189,8 +185,10 @@ pub struct NetworkMonitor {
}
impl NetworkMonitor {
pub const DB_FILE: &'static str = "credentials_database.db";
fn default_credentials_database_path() -> PathBuf {
Config::default_data_directory(None).join("credentials_database.db")
Config::default_data_directory(None).join(Self::DB_FILE)
}
}
@@ -201,7 +199,6 @@ impl Default for NetworkMonitor {
min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY,
enabled: false,
disabled_credentials_mode: true,
all_validator_apis: default_api_endpoints(),
run_interval: DEFAULT_MONITOR_RUN_INTERVAL,
gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL,
gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE,
@@ -230,8 +227,10 @@ pub struct NodeStatusAPI {
}
impl NodeStatusAPI {
pub const DB_FILE: &'static str = "db.sqlite";
fn default_database_path() -> PathBuf {
Config::default_data_directory(None).join("db.sqlite")
Config::default_data_directory(None).join(Self::DB_FILE)
}
}
@@ -279,15 +278,31 @@ impl Default for Rewarding {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[cfg(feature = "coconut")]
pub struct CoconutSigner {
/// Specifies whether rewarding service is enabled in this process.
enabled: bool,
/// Base58 encoded signing keypair
keypair_bs58: String,
/// Path to the signing keypair
keypair_path: PathBuf,
/// Specifies list of all validators on the network issuing coconut credentials.
/// A special care must be taken to ensure they are in correct order.
/// The list must also contain THIS validator that is running the test
all_validator_apis: Vec<Url>,
}
#[cfg(feature = "coconut")]
impl Default for CoconutSigner {
fn default() -> Self {
CoconutSigner {
enabled: false,
keypair_path: PathBuf::default(),
all_validator_apis: config::defaults::default_api_endpoints(),
}
}
}
impl Config {
@@ -295,9 +310,18 @@ impl Config {
Config::default()
}
pub fn with_id(mut self, id: &str) -> Self {
self.base.id = id.to_string();
self.node_status_api.database_path =
Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE);
self.network_monitor.credentials_database_path =
Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE);
self
}
#[cfg(feature = "coconut")]
pub fn keypair(&self) -> KeyPair {
KeyPair::try_from_bs58(self.coconut_signer.keypair_bs58.clone()).unwrap()
pub fn keypair_path(&self) -> PathBuf {
self.coconut_signer.keypair_path.clone()
}
pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self {
@@ -337,13 +361,14 @@ impl Config {
}
#[cfg(feature = "coconut")]
pub fn with_keypair<S: Into<String>>(mut self, keypair_bs58: S) -> Self {
self.coconut_signer.keypair_bs58 = keypair_bs58.into();
pub fn with_keypair_path(mut self, keypair_path: PathBuf) -> Self {
self.coconut_signer.keypair_path = keypair_path;
self
}
#[cfg(feature = "coconut")]
pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self {
self.network_monitor.all_validator_apis = validator_api_urls;
self.coconut_signer.all_validator_apis = validator_api_urls;
self
}
@@ -374,6 +399,10 @@ impl Config {
self
}
pub fn get_id(&self) -> String {
self.base.id.clone()
}
pub fn get_network_monitor_enabled(&self) -> bool {
self.network_monitor.enabled
}
@@ -474,7 +503,7 @@ impl Config {
// fix dead code warnings as this method is only ever used with coconut feature
#[cfg(feature = "coconut")]
pub fn get_all_validator_api_endpoints(&self) -> Vec<Url> {
self.network_monitor.all_validator_apis.clone()
self.coconut_signer.all_validator_apis.clone()
}
// TODO: Remove if still unused
+23 -12
View File
@@ -10,12 +10,18 @@ pub(crate) fn config_template() -> &'static str {
[base]
# ID specifies the human readable ID of this particular validator-api.
id = '{{ base.id }}'
# Validator server to which the API will be getting information about the network.
local_validator = '{{ base.local_validator }}'
# Address of the validator contract managing the network.
mixnet_contract_address = '{{ base.mixnet_contract_address }}'
# Mnemonic used for rewarding and validator interaction
mnemonic = '{{ base.mnemonic }}'
##### network monitor config options #####
[network_monitor]
@@ -26,15 +32,6 @@ enabled = {{ network_monitor.enabled }}
# to claim bandwidth without presenting bandwidth credentials.
disabled_credentials_mode = {{ network_monitor.disabled_credentials_mode }}
# Specifies list of all validators on the network issuing coconut credentials.
# A special care must be taken to ensure they are in correct order.
# The list must also contain THIS validator that is running the test
all_validator_apis = [
{{#each network_monitor.all_validator_apis }}
'{{this}}',
{{/each}}
]
# Specifies the interval at which the network monitor sends the test packets.
run_interval = '{{ network_monitor.run_interval }}'
@@ -92,13 +89,27 @@ database_path = '{{ node_status_api.database_path }}'
# Specifies whether rewarding service is enabled in this process.
enabled = {{ rewarding.enabled }}
# Mnemonic (currently of the network monitor) used for rewarding
mnemonic = '{{ rewarding.mnemonic }}'
# Specifies the minimum percentage of monitor test run data present in order to
# distribute rewards for given interval.
# Note, only values in range 0-100 are valid
minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_threshold }}
[coconut_signer]
# Specifies whether rewarding service is enabled in this process.
enabled = {{ coconut_signer.enabled }}
# Path to the signing keypair
keypair_path = '{{ coconut_signer.keypair_path }}'
# Specifies list of all validators on the network issuing coconut credentials.
# A special care must be taken to ensure they are in correct order.
# The list must also contain THIS validator that is running the test
all_validator_apis = [
{{#each coconut_signer.all_validator_apis }}
'{{this}}',
{{/each}}
]
"#
}
+83 -25
View File
@@ -22,7 +22,7 @@ use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::time;
use validator_api_requests::models::MixnodeStatus;
use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus};
use validator_client::nymd::CosmWasmClient;
pub(crate) mod routes;
@@ -40,14 +40,14 @@ pub struct ValidatorCache {
}
struct ValidatorCacheInner {
mixnodes: Cache<Vec<MixNodeBond>>,
mixnodes: Cache<Vec<MixNodeBondAnnotated>>,
gateways: Cache<Vec<GatewayBond>>,
mixnodes_blacklist: Cache<HashSet<IdentityKey>>,
gateways_blacklist: Cache<HashSet<IdentityKey>>,
rewarded_set: Cache<Vec<MixNodeBond>>,
active_set: Cache<Vec<MixNodeBond>>,
rewarded_set: Cache<Vec<MixNodeBondAnnotated>>,
active_set: Cache<Vec<MixNodeBondAnnotated>>,
current_reward_params: Cache<EpochRewardParams>,
current_epoch: Cache<Option<Interval>>,
@@ -99,11 +99,32 @@ impl<C> ValidatorCacheRefresher<C> {
}
}
fn annotate_bond_with_details(
mixnodes: Vec<MixNodeBond>,
interval_reward_params: EpochRewardParams,
) -> Vec<MixNodeBondAnnotated> {
mixnodes
.into_iter()
.map(|mixnode_bond| {
let stake_saturation = mixnode_bond
.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.rewarded_set_size() as u32,
)
.to_num();
MixNodeBondAnnotated {
mixnode_bond,
stake_saturation,
}
})
.collect()
}
fn collect_rewarded_and_active_set_details(
&self,
all_mixnodes: &[MixNodeBond],
all_mixnodes: &[MixNodeBondAnnotated],
rewarded_set_identities: Vec<(IdentityKey, RewardedSetNodeStatus)>,
) -> (Vec<MixNodeBond>, Vec<MixNodeBond>) {
) -> (Vec<MixNodeBondAnnotated>, Vec<MixNodeBondAnnotated>) {
let mut active_set = Vec::new();
let mut rewarded_set = Vec::new();
let rewarded_set_identities = rewarded_set_identities
@@ -111,7 +132,7 @@ impl<C> ValidatorCacheRefresher<C> {
.collect::<HashMap<_, _>>();
for mix in all_mixnodes {
if let Some(status) = rewarded_set_identities.get(mix.identity()) {
if let Some(status) = rewarded_set_identities.get(mix.mixnode_bond.identity()) {
rewarded_set.push(mix.clone());
if status.is_active() {
active_set.push(mix.clone())
@@ -126,11 +147,16 @@ impl<C> ValidatorCacheRefresher<C> {
where
C: CosmWasmClient + Sync,
{
let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?;
let current_epoch = self.nymd_client.get_current_epoch().await?;
let (mixnodes, gateways) = tokio::try_join!(
self.nymd_client.get_mixnodes(),
self.nymd_client.get_gateways(),
)?;
let mixnodes = Self::annotate_bond_with_details(mixnodes, epoch_rewarding_params);
let (rewarded_set, active_set) = if let Ok(rewarded_set_identities) =
self.nymd_client.get_rewarded_set_identities().await
{
@@ -139,9 +165,6 @@ impl<C> ValidatorCacheRefresher<C> {
(Vec::new(), Vec::new())
};
let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?;
let current_epoch = self.nymd_client.get_current_epoch().await?;
info!(
"Updating validator cache. There are {} mixnodes and {} gateways",
mixnodes.len(),
@@ -184,9 +207,12 @@ impl<C> ValidatorCacheRefresher<C> {
pub(crate) fn validator_cache_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
settings: routes::get_mixnodes,
routes::get_mixnodes_detailed,
routes::get_gateways,
routes::get_active_set,
routes::get_active_set_detailed,
routes::get_rewarded_set,
routes::get_rewarded_set_detailed,
routes::get_blacklisted_mixnodes,
routes::get_blacklisted_gateways,
routes::get_epoch_reward_params,
@@ -210,10 +236,10 @@ impl ValidatorCache {
async fn update_cache(
&self,
mixnodes: Vec<MixNodeBond>,
mixnodes: Vec<MixNodeBondAnnotated>,
gateways: Vec<GatewayBond>,
rewarded_set: Vec<MixNodeBond>,
active_set: Vec<MixNodeBond>,
rewarded_set: Vec<MixNodeBondAnnotated>,
active_set: Vec<MixNodeBondAnnotated>,
epoch_rewarding_params: EpochRewardParams,
current_epoch: Interval,
) {
@@ -312,7 +338,7 @@ impl ValidatorCache {
error!("Failed to update gateways blacklist");
}
pub async fn mixnodes(&self) -> Vec<MixNodeBond> {
pub async fn mixnodes_detailed(&self) -> Vec<MixNodeBondAnnotated> {
let blacklist = self.mixnodes_blacklist().await;
let mixnodes = match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.mixnodes.clone(),
@@ -326,7 +352,7 @@ impl ValidatorCache {
mixnodes
.value
.iter()
.filter(|mix| !blacklist.value.contains(mix.identity()))
.filter(|mix| !blacklist.value.contains(mix.mixnode_bond.identity()))
.cloned()
.collect()
} else {
@@ -334,9 +360,23 @@ impl ValidatorCache {
}
}
pub async fn mixnodes(&self) -> Vec<MixNodeBond> {
self.mixnodes_detailed()
.await
.into_iter()
.map(|bond| bond.mixnode_bond)
.collect()
}
pub async fn mixnodes_all(&self) -> Vec<MixNodeBond> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.mixnodes.clone().into_inner(),
Ok(cache) => cache
.mixnodes
.clone()
.into_inner()
.into_iter()
.map(|bond| bond.mixnode_bond)
.collect(),
Err(e) => {
error!("{}", e);
Vec::new()
@@ -376,7 +416,7 @@ impl ValidatorCache {
}
}
pub async fn rewarded_set(&self) -> Cache<Vec<MixNodeBond>> {
pub async fn rewarded_set_detailed(&self) -> Cache<Vec<MixNodeBondAnnotated>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.rewarded_set.clone(),
Err(e) => {
@@ -386,7 +426,16 @@ impl ValidatorCache {
}
}
pub async fn active_set(&self) -> Cache<Vec<MixNodeBond>> {
pub async fn rewarded_set(&self) -> Vec<MixNodeBond> {
self.rewarded_set_detailed()
.await
.value
.into_iter()
.map(|bond| bond.mixnode_bond)
.collect()
}
pub async fn active_set_detailed(&self) -> Cache<Vec<MixNodeBondAnnotated>> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.active_set.clone(),
Err(e) => {
@@ -396,6 +445,15 @@ impl ValidatorCache {
}
}
pub async fn active_set(&self) -> Vec<MixNodeBond> {
self.active_set_detailed()
.await
.value
.into_iter()
.map(|bond| bond.mixnode_bond)
.collect()
}
pub(crate) async fn epoch_reward_params(&self) -> Cache<EpochRewardParams> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache.current_reward_params.clone(),
@@ -419,30 +477,30 @@ impl ValidatorCache {
pub async fn mixnode_details(
&self,
identity: IdentityKeyRef<'_>,
) -> (Option<MixNodeBond>, MixnodeStatus) {
) -> (Option<MixNodeBondAnnotated>, MixnodeStatus) {
// it might not be the most optimal to possibly iterate the entire vector to find (or not)
// the relevant value. However, the vectors are relatively small (< 10_000 elements, < 1000 for active set)
let active_set = &self.active_set().await.value;
let active_set = &self.active_set_detailed().await.value;
if let Some(bond) = active_set
.iter()
.find(|mix| mix.mix_node.identity_key == identity)
.find(|mix| mix.mix_node().identity_key == identity)
{
return (Some(bond.clone()), MixnodeStatus::Active);
}
let rewarded_set = &self.rewarded_set().await.value;
let rewarded_set = &self.rewarded_set_detailed().await.value;
if let Some(bond) = rewarded_set
.iter()
.find(|mix| mix.mix_node.identity_key == identity)
.find(|mix| mix.mix_node().identity_key == identity)
{
return (Some(bond.clone()), MixnodeStatus::Standby);
}
let all_bonded = &self.mixnodes().await;
let all_bonded = &self.mixnodes_detailed().await;
if let Some(bond) = all_bonded
.iter()
.find(|mix| mix.mix_node.identity_key == identity)
.find(|mix| mix.mix_node().identity_key == identity)
{
(Some(bond.clone()), MixnodeStatus::Inactive)
} else {
+27 -2
View File
@@ -8,6 +8,7 @@ use rocket::serde::json::Json;
use rocket::State;
use rocket_okapi::openapi;
use std::collections::HashSet;
use validator_api_requests::models::MixNodeBondAnnotated;
#[openapi(tag = "contract-cache")]
#[get("/mixnodes")]
@@ -15,6 +16,14 @@ pub async fn get_mixnodes(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeBond
Json(cache.mixnodes().await)
}
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/detailed")]
pub async fn get_mixnodes_detailed(
cache: &State<ValidatorCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.mixnodes_detailed().await)
}
#[openapi(tag = "contract-cache")]
#[get("/gateways")]
pub async fn get_gateways(cache: &State<ValidatorCache>) -> Json<Vec<GatewayBond>> {
@@ -24,13 +33,29 @@ pub async fn get_gateways(cache: &State<ValidatorCache>) -> Json<Vec<GatewayBond
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/rewarded")]
pub async fn get_rewarded_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeBond>> {
Json(cache.rewarded_set().await.value)
Json(cache.rewarded_set().await)
}
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/rewarded/detailed")]
pub async fn get_rewarded_set_detailed(
cache: &State<ValidatorCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.rewarded_set_detailed().await.value)
}
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/active")]
pub async fn get_active_set(cache: &State<ValidatorCache>) -> Json<Vec<MixNodeBond>> {
Json(cache.active_set().await.value)
Json(cache.active_set().await)
}
#[openapi(tag = "contract-cache")]
#[get("/mixnodes/active/detailed")]
pub async fn get_active_set_detailed(
cache: &State<ValidatorCache>,
) -> Json<Vec<MixNodeBondAnnotated>> {
Json(cache.active_set_detailed().await.value)
}
#[openapi(tag = "contract-cache")]
+46 -33
View File
@@ -23,17 +23,18 @@ use rocket::{Ignite, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
use rocket_okapi::mount_endpoints_and_merged_docs;
use rocket_okapi::swagger_ui::make_swagger_ui;
use std::process;
use std::sync::Arc;
use std::time::Duration;
use std::{fs, process};
use tokio::sync::Notify;
use url::Url;
// use validator_client::nymd::SigningNymdClient;
// use validator_client::ValidatorClientError;
use crate::rewarded_set_updater::RewardedSetUpdater;
#[cfg(feature = "coconut")]
use coconut::InternalSignRequest;
#[cfg(feature = "coconut")]
use coconut_interface::{Base58, KeyPair};
use validator_client::nymd::SigningNymdClient;
pub(crate) mod config;
@@ -48,18 +49,19 @@ mod swagger;
#[cfg(feature = "coconut")]
mod coconut;
const ID: &str = "id";
const MONITORING_ENABLED: &str = "enable-monitor";
const REWARDING_ENABLED: &str = "enable-rewarding";
const MIXNET_CONTRACT_ARG: &str = "mixnet-contract";
const MNEMONIC_ARG: &str = "mnemonic";
const WRITE_CONFIG_ARG: &str = "save-config";
const NYMD_VALIDATOR_ARG: &str = "nymd-validator";
const API_VALIDATORS_ARG: &str = "api-validators";
const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(feature = "coconut")]
const API_VALIDATORS_ARG: &str = "api-validators";
#[cfg(feature = "coconut")]
const KEYPAIR_ARG: &str = "keypair";
#[cfg(feature = "coconut")]
const COCONUT_ENABLED: &str = "enable-coconut";
@@ -73,7 +75,8 @@ const REWARDING_MONITOR_THRESHOLD_ARG: &str = "monitor-threshold";
const MIN_MIXNODE_RELIABILITY_ARG: &str = "min_mixnode_reliability";
const MIN_GATEWAY_RELIABILITY_ARG: &str = "min_gateway_reliability";
fn parse_validators(raw: &str) -> Vec<Url> {
#[cfg(feature = "coconut")]
fn parse_validators(raw: &str) -> Vec<url::Url> {
raw.split(',')
.map(|raw_validator| {
raw_validator
@@ -124,6 +127,12 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.version(crate_version!())
.long_version(&*build_details)
.author("Nymtech")
.arg(
Arg::with_name(ID)
.help("Id of the validator-api we want to run")
.long(ID)
.takes_value(true)
)
.arg(
Arg::with_name(MONITORING_ENABLED)
.help("specifies whether a network monitoring is enabled on this API")
@@ -159,12 +168,6 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.long(WRITE_CONFIG_ARG)
.short("w")
)
.arg(
Arg::with_name(API_VALIDATORS_ARG)
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
.long(API_VALIDATORS_ARG)
.takes_value(true)
)
.arg(
Arg::with_name(REWARDING_MONITOR_THRESHOLD_ARG)
.help("Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval.")
@@ -185,10 +188,16 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.takes_value(true)
.long(KEYPAIR_ARG),
)
.arg(
Arg::with_name(API_VALIDATORS_ARG)
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
.long(API_VALIDATORS_ARG)
.takes_value(true)
)
.arg(
Arg::with_name(COCONUT_ENABLED)
.help("Flag to indicate whether coconut signer authority is enabled on this API")
.requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG])
.requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG, API_VALIDATORS_ARG])
.long(COCONUT_ENABLED),
);
@@ -236,12 +245,18 @@ fn setup_logging() {
.filter_module("sled", log::LevelFilter::Warn)
.filter_module("tungstenite", log::LevelFilter::Warn)
.filter_module("tokio_tungstenite", log::LevelFilter::Warn)
.filter_module("_", log::LevelFilter::Warn)
.filter_module("rocket::server", log::LevelFilter::Warn)
.init();
}
fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
if let Some(id) = matches.value_of(ID) {
fs::create_dir_all(Config::default_config_directory(Some(id)))
.expect("Could not create config directory");
fs::create_dir_all(Config::default_data_directory(Some(id)))
.expect("Could not create data directory");
config = config.with_id(id);
}
if matches.is_present(MONITORING_ENABLED) {
config = config.with_network_monitor_enabled(true)
}
@@ -255,6 +270,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
config = config.with_coconut_signer_enabled(true)
}
#[cfg(feature = "coconut")]
if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) {
config = config.with_custom_validator_apis(parse_validators(raw_validators));
}
@@ -311,11 +327,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
#[cfg(feature = "coconut")]
if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) {
let keypair_bs58 = std::fs::read_to_string(keypair_path)
.unwrap()
.trim()
.to_string();
config = config.with_keypair(keypair_bs58)
config = config.with_keypair_path(keypair_path.into())
}
#[cfg(not(feature = "coconut"))]
@@ -402,7 +414,7 @@ fn expected_monitor_test_runs(config: &Config, interval_length: Duration) -> usi
async fn setup_rocket(
config: &Config,
liftoff_notify: Arc<Notify>,
_nymd_client: Option<Client<SigningNymdClient>>,
_nymd_client: Client<SigningNymdClient>,
) -> Result<Rocket<Ignite>> {
let openapi_settings = rocket_okapi::settings::OpenApiSettings::default();
let mut rocket = rocket::build();
@@ -434,9 +446,14 @@ async fn setup_rocket(
#[cfg(feature = "coconut")]
let rocket = if config.get_coconut_signer_enabled() {
let keypair_bs58 = fs::read_to_string(config.keypair_path())?
.trim()
.to_string();
let keypair = KeyPair::try_from_bs58(keypair_bs58)?;
rocket.attach(InternalSignRequest::stage(
_nymd_client.expect("Should have a signing client here"),
config.keypair(),
_nymd_client,
keypair,
config.get_all_validator_api_endpoints(),
storage.clone().unwrap(),
))
} else {
@@ -486,10 +503,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let system_version = env!("CARGO_PKG_VERSION");
// try to load config from the file, if it doesn't exist, use default values
let config = match Config::load_from_file(None) {
let id = matches.value_of(ID);
let config = match Config::load_from_file(id) {
Ok(cfg) => cfg,
Err(_) => {
let config_path = Config::default_config_file_path(None)
let config_path = Config::default_config_file_path(id)
.into_os_string()
.into_string()
.unwrap();
@@ -507,11 +525,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
return Ok(());
}
let signing_nymd_client = if matches.is_present(MNEMONIC_ARG) {
Some(Client::new_signing(&config))
} else {
None
};
let signing_nymd_client = Client::new_signing(&config);
let liftoff_notify = Arc::new(Notify::new());
@@ -529,9 +543,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
// if network monitor is disabled, we're not going to be sending any rewarding hence
// we're not starting signing client
if config.get_network_monitor_enabled() {
let nymd_client = signing_nymd_client.expect("We should have a signing client here");
let validator_cache_refresher = ValidatorCacheRefresher::new(
nymd_client.clone(),
signing_nymd_client.clone(),
config.get_caching_interval(),
validator_cache.clone(),
);
@@ -546,7 +559,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
tokio::spawn(async move { uptime_updater.run().await });
let mut rewarded_set_updater =
RewardedSetUpdater::new(nymd_client, validator_cache.clone(), storage).await?;
RewardedSetUpdater::new(signing_nymd_client, validator_cache.clone(), storage).await?;
// spawn rewarded set updater
tokio::spawn(async move { rewarded_set_updater.run().await.unwrap() });
@@ -590,7 +603,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
async fn main() -> Result<()> {
println!("Starting validator api...");
dotenv::dotenv()?;
let _ = dotenv::dotenv();
cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] {
// instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
+4 -2
View File
@@ -147,7 +147,7 @@ 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.estimate_reward(&reward_params) {
match bond.mixnode_bond.estimate_reward(&reward_params) {
Ok(reward_estimate) => {
let reponse = RewardEstimationResponse {
estimated_total_node_reward: reward_estimate.total_node_reward,
@@ -181,11 +181,13 @@ pub(crate) async fn get_mixnode_stake_saturation(
) -> Result<Json<StakeSaturationResponse>, ErrorResponse> {
let (bond, _) = cache.mixnode_details(&identity).await;
if let Some(bond) = bond {
// Recompute the stake saturation just so that we can confidentaly state that the `as_at`
// field is consistent and correct. Luckily this is very cheap.
let interval_reward_params = cache.epoch_reward_params().await;
let as_at = interval_reward_params.timestamp();
let interval_reward_params = interval_reward_params.into_inner();
let saturation = bond.stake_saturation(
let saturation = bond.mixnode_bond.stake_saturation(
interval_reward_params.circulating_supply(),
interval_reward_params.rewarded_set_size() as u32,
);

Some files were not shown because too many files have changed in this diff Show More