Compare commits

..

4 Commits

Author SHA1 Message Date
Gala ba00782486 adding line break 2022-09-12 17:42:25 +02:00
Gala 047ca4f450 Merge branch 'develop' into 308-figma-diff-pass 2022-09-12 17:41:14 +02:00
Gala f6410979bc some cleaning 2022-09-12 17:39:03 +02:00
Gala 8ed7774e93 check if there is a pass and display different steps if its the case 2022-09-12 17:35:39 +02:00
519 changed files with 25130 additions and 27069 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
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 libudev-dev squashfs-tools
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
- name: Check out repository code
uses: actions/checkout@v2
+1 -7
View File
@@ -24,7 +24,7 @@ jobs:
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
if: matrix.os == 'ubuntu-latest'
- name: Check out repository code
@@ -107,12 +107,6 @@ jobs:
command: build
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
@@ -166,8 +166,8 @@ jobs:
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "${{ secrets.KEYBASE_CHANNEL_DEV_CORE_ID }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
@@ -0,0 +1,26 @@
name: Nightly builds on dispatch
on: workflow_dispatch
jobs:
notification:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nightly
NYM_PROJECT_NAME: "Notification test"
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
-11
View File
@@ -2,12 +2,6 @@ name: Publish Nym binaries
on:
workflow_dispatch:
inputs:
add_tokio_unstable:
description: 'True to add RUSTFLAGS="--cfg tokio_unstable"'
required: true
default: false
type: boolean
release:
types: [created]
@@ -31,11 +25,6 @@ jobs:
with:
script: |
core.setFailed('Release tag did not start with nym-binaries-...')
- name: Sets env vars for tokio if set in manual dispatch inputs
run: |
echo 'RUSTFLAGS="--cfg tokio_unstable"' >> $GITHUB_ENV
if: github.event_name == 'workflow_dispatch' && inputs.add_tokio_unstable == true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
@@ -89,7 +89,7 @@ async function sendKeybaseMessage(messageBody) {
});
const channel = {
name: context.env.KEYBASE_NYMBOT_TEAM || 'nymtech_bot',
name: 'nymtech_bot',
membersType: 'team',
topicName: context.keybase.channel,
topic_type: 'CHAT',
-10
View File
@@ -8,26 +8,16 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- nym-cli: added CLI tool for interacting with the Nyx blockchain and Nym mixnet smart contracts ([#1577])
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
- network-requester: added additional Blockstream Green wallet endpoint to `example.allowed.list` ([#1611](https://github.com/nymtech/nym/pull/1611))
- common/ledger: new library for communicating with a Ledger device ([#1640])
### Fixed
- validator-api, mixnode, gateway should now prefer values in config.toml over mainnet defaults ([#1645])
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
- socks5 client: graceful shutdown should fix error on disconnect in nym-connect ([#1591])
- wasm-client: fixed build errors on MacOS and changed example JS code to use mainnet ([#1585])
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
[#1577]: https://github.com/nymtech/nym/pull/1577
[#1585]: https://github.com/nymtech/nym/pull/1585
[#1591]: https://github.com/nymtech/nym/pull/1591
[#1640]: https://github.com/nymtech/nym/pull/1640
[#1645]: https://github.com/nymtech/nym/pull/1645
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
Generated
+759 -733
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -40,7 +40,6 @@ members = [
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/ledger",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -63,7 +62,6 @@ members = [
"common/topology",
"common/types",
"common/wasm-utils",
"common/completions",
"explorer-api",
"gateway",
"gateway/gateway-requests",
@@ -87,4 +85,4 @@ default-members = [
"explorer-api",
]
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect"]
exclude = ["explorer", "contracts", "tokenomics-py", "clients/webassembly", "nym-wallet", "nym-connect"]
+7 -24
View File
@@ -2,10 +2,10 @@ test: clippy-all cargo-test wasm fmt
test-all: test cargo-test-expensive
no-clippy: build cargo-test wasm fmt
happy: fmt clippy-happy test
clippy-all: clippy-main clippy-coconut clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet clippy-all-connect
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
cargo-test: test-main test-contracts test-wallet test-connect test-coconut
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive test-coconut-expensive
cargo-test: test-main test-contracts test-wallet test-connect
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
build: build-contracts build-wallet build-main build-connect
fmt: fmt-main fmt-contracts fmt-wallet fmt-connect
@@ -21,15 +21,8 @@ clippy-happy-wallet:
clippy-happy-connect:
cargo clippy --manifest-path nym-connect/Cargo.toml
clippy-main:
cargo clippy --workspace -- -D warnings
clippy-coconut:
cargo clippy --workspace --features coconut -- -D warnings
clippy-wasm:
cargo clippy --workspace --features wasm -- -D warnings
clippy-all-main:
cargo clippy --workspace --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
@@ -41,20 +34,10 @@ clippy-all-connect:
cargo clippy --workspace --manifest-path nym-connect/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --workspace
test-coconut:
cargo test --workspace --features coconut
test-wasm:
cargo test --workspace --features wasm
cargo test --all-features --workspace
test-main-expensive:
cargo test --workspace -- --ignored
test-coconut-expensive:
cargo test --workspace --features coconut -- --ignored
cargo test --all-features --workspace -- --ignored
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
@@ -136,7 +136,7 @@ impl LoopCoverTrafficStream<OsRng> {
let cover_message = generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&*self.ack_key,
&self.our_full_destination,
self.average_ack_delay,
self.average_packet_delay,
@@ -228,7 +228,7 @@ where
generate_loop_cover_packet(
&mut self.rng,
topology_ref,
&self.ack_key,
&*self.ack_key,
&self.our_full_destination,
self.config.average_ack_delay,
self.config.average_packet_delay,
@@ -13,7 +13,7 @@ use std::time::Duration;
use task::ShutdownListener;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_detailed, NymTopology};
use topology::{nym_topology_from_bonds, NymTopology};
use url::Url;
// I'm extremely curious why compiler NEVER complained about lack of Debug here before
@@ -266,8 +266,8 @@ impl TopologyRefresher {
};
let mixnodes_count = mixnodes.len();
let topology = nym_topology_from_detailed(mixnodes, gateways)
.filter_system_version(&self.client_version);
let topology =
nym_topology_from_bonds(mixnodes, gateways).filter_system_version(&self.client_version);
if !self.check_layer_distribution(&topology, mixnodes_count) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used.");
+32
View File
@@ -121,6 +121,16 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_id = id.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key<S: Into<String>>(&mut self, eth_private_key: S) {
self.client.eth_private_key = eth_private_key.into();
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_endpoint<S: Into<String>>(&mut self, eth_endpoint: S) {
self.client.eth_endpoint = eth_endpoint.into();
}
pub fn set_custom_validator_apis(&mut self, validator_api_urls: Vec<Url>) {
self.client.validator_api_urls = validator_api_urls;
}
@@ -199,6 +209,16 @@ impl<T: NymConfig> Config<T> {
self.client.database_path.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_endpoint(&self) -> String {
self.client.eth_endpoint.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_eth_private_key(&self) -> String {
self.client.eth_private_key.clone()
}
// Debug getters
pub fn get_average_packet_delay(&self) -> Duration {
self.debug.average_packet_delay
@@ -322,6 +342,14 @@ pub struct Client<T> {
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
eth_private_key: String,
/// Address to an Ethereum full node.
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// nym_home_directory specifies absolute path to the home nym Clients directory.
/// It is expected to use default value and hence .toml file should not redefine this field.
nym_root_directory: PathBuf,
@@ -347,6 +375,10 @@ impl<T: NymConfig> Default for Client<T> {
reply_encryption_key_store_path: Default::default(),
gateway_endpoint: Default::default(),
database_path: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
nym_root_directory: T::default_root_directory(),
super_struct: Default::default(),
}
+1 -2
View File
@@ -9,7 +9,7 @@ edition = "2021"
async-trait = "0.1.52"
bip39 = "1.0.1"
cfg-if = "0.1"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.0.10", features = ["cargo", "derive"] }
pickledb = "0.4.1"
rand = "0.7.3"
serde = { version = "1.0", features = ["derive"] }
@@ -19,7 +19,6 @@ tokio = { version = "1.19.1", features = ["rt-multi-thread", "net", "signal", "m
coconut-interface = { path = "../../common/coconut-interface" }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
-7
View File
@@ -3,7 +3,6 @@
use async_trait::async_trait;
use clap::{Args, Subcommand};
use completions::ArgShell;
use pickledb::PickleDb;
use rand::rngs::OsRng;
use std::str::FromStr;
@@ -29,12 +28,6 @@ pub(crate) enum Commands {
ListDeposits(ListDeposits),
/// Get a credential for a given deposit
GetCredential(GetCredential),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
#[async_trait]
-6
View File
@@ -12,8 +12,6 @@ cfg_if::cfg_if! {
use commands::{Commands, Execute};
use error::Result;
use network_defaults::setup_env;
use clap::CommandFactory;
use completions::fig_generate;
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
@@ -54,14 +52,10 @@ cfg_if::cfg_if! {
),
};
let bin_name = "nym-credential-client";
match &args.command {
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
Commands::Completions(s) => s.generate(&mut crate::Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut crate::Cli::into_app(), bin_name)
}
Ok(())
+2 -2
View File
@@ -20,7 +20,7 @@ futures = "0.3" # bunch of futures stuff, however, now that I think about it, it
# and the single instance of abortable we have should really be refactored anyway
url = "2.2"
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
log = "0.4" # self explanatory
pretty_env_logger = "0.4" # for formatting log messages
@@ -34,7 +34,6 @@ tokio-tungstenite = "0.14" # websocket
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
@@ -51,6 +50,7 @@ websocket-requests = { path = "websocket-requests" }
[features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
eth = []
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
+21 -103
View File
@@ -27,58 +27,6 @@
"node": ">=10.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3581,13 +3529,13 @@
}
},
"node_modules/terser": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -3635,6 +3583,14 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"engines": {
"node": ">= 8"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -4374,49 +4330,6 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7145,13 +7058,13 @@
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="
},
"terser": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
@@ -7159,6 +7072,11 @@
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ=="
}
}
},
@@ -49,6 +49,12 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
+2
View File
@@ -190,6 +190,8 @@ impl NymClient {
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
)
.expect("Could not create bandwidth controller");
+29 -2
View File
@@ -10,6 +10,9 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -44,9 +47,27 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
@@ -57,8 +78,14 @@ impl From<Init> for OverrideConfig {
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
+34 -14
View File
@@ -2,9 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::{Config, SocketType};
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
pub(crate) mod init;
pub(crate) mod run;
@@ -64,12 +69,6 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -79,19 +78,21 @@ pub(crate) struct OverrideConfig {
port: Option<u16>,
fastmode: bool,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-native-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
@@ -116,13 +117,32 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(feature = "coconut")]
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(any(feature = "eth", feature = "coconut"))]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
}
+21 -2
View File
@@ -36,9 +36,21 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
@@ -48,8 +60,15 @@ impl From<Run> for OverrideConfig {
disable_socket: run_config.disable_socket,
port: run_config.port,
fastmode: false,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
+2 -2
View File
@@ -103,7 +103,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
print_start_upgrade(&config_version, &to_version);
config
.get_base_mut()
@@ -111,7 +111,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(config_version, &to_version);
print_failed_upgrade(&config_version, &to_version);
process::exit(1);
});
+1 -2
View File
@@ -11,7 +11,7 @@ name = "nym_socks5"
path = "src/lib.rs"
[dependencies]
clap = { version = "3.2", features = ["cargo", "derive"] }
clap = { version = "3.2.8", features = ["cargo", "derive"] }
dirs = "4.0"
futures = "0.3"
log = "0.4"
@@ -27,7 +27,6 @@ url = "2.2"
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
config = { path = "../../common/config" }
completions = { path = "../../common/completions" }
credential-storage = { path = "../../common/credential-storage" }
credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
@@ -49,6 +49,12 @@ reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
# Addess to an Ethereum full node.
eth_endpoint = '{{ client.eth_endpoint }}'
##### additional client config options #####
# A gateway specific, optional, base58 stringified shared key used for
+2
View File
@@ -191,6 +191,8 @@ impl NymClient {
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
)
.expect("Could not create bandwidth controller");
+30 -2
View File
@@ -10,6 +10,9 @@ use crate::{
commands::{override_config, OverrideConfig},
};
#[cfg(all(feature = "eth", not(feature = "coconut")))]
use crate::commands::{DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY};
#[derive(Args, Clone)]
pub(crate) struct Init {
/// Id of the nym-mixnet-client we want to create config for.
@@ -44,9 +47,27 @@ pub(crate) struct Init {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_ENDPOINT))
)]
eth_endpoint: String,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead")
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(
long,
default_value_if("enabled-credentials-mode", None, Some(DEFAULT_ETH_PRIVATE_KEY))
)]
eth_private_key: String,
}
impl From<Init> for OverrideConfig {
@@ -55,8 +76,15 @@ impl From<Init> for OverrideConfig {
validators: init_config.validators,
port: init_config.port,
fastmode: init_config.fastmode,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: init_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Some(init_config.eth_private_key),
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
}
}
}
+34 -14
View File
@@ -2,15 +2,20 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::config::Config;
use clap::CommandFactory;
use clap::{Parser, Subcommand};
use completions::{fig_generate, ArgShell};
use config::parse_validators;
pub mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_ENDPOINT: &str =
"https://rinkeby.infura.io/v3/00000000000000000000000000000000";
#[cfg(not(feature = "coconut"))]
pub(crate) const DEFAULT_ETH_PRIVATE_KEY: &str =
"0000000000000000000000000000000000000000000000000000000000000001";
fn long_version() -> String {
format!(
r#"
@@ -65,12 +70,6 @@ pub(crate) enum Commands {
Run(run::Run),
/// Try to upgrade the client
Upgrade(upgrade::Upgrade),
/// Generate shell completions
Completions(ArgShell),
/// Generate Fig specification
GenerateFigSpec,
}
// Configuration that can be overridden.
@@ -79,19 +78,21 @@ pub(crate) struct OverrideConfig {
port: Option<u16>,
fastmode: bool,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: bool,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: Option<String>,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Option<String>,
}
pub(crate) async fn execute(args: &Cli) {
let bin_name = "nym-socks5-client";
match &args.command {
Commands::Init(m) => init::execute(m).await,
Commands::Run(m) => run::execute(m).await,
Commands::Upgrade(m) => upgrade::execute(m),
Commands::Completions(s) => s.generate(&mut Cli::into_app(), bin_name),
Commands::GenerateFigSpec => fig_generate(&mut Cli::into_app(), bin_name),
}
}
@@ -110,12 +111,31 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.with_port(port);
}
#[cfg(feature = "coconut")]
#[cfg(all(not(feature = "eth"), not(feature = "coconut")))]
{
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT.to_string());
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY.to_string());
}
#[cfg(any(feature = "eth", feature = "coconut"))]
{
if args.enabled_credentials_mode {
config.get_base_mut().with_disabled_credentials(false)
}
}
#[cfg(all(feature = "eth", not(feature = "coconut")))]
{
if let Some(eth_endpoint) = args.eth_endpoint {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
}
if let Some(eth_private_key) = args.eth_private_key {
config.get_base_mut().with_eth_private_key(eth_private_key);
}
}
if args.fastmode {
config.get_base_mut().set_high_default_traffic_volume();
+20 -2
View File
@@ -40,9 +40,21 @@ pub(crate) struct Run {
/// Set this client to work in a enabled credentials mode that would attempt to use gateway
/// with bandwidth credential requirement.
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
#[clap(long)]
enabled_credentials_mode: bool,
/// URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20
/// tokens. If you don't want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_endpoint: Option<String>,
/// Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't
/// want to set this value, use --enabled-credentials-mode instead
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
eth_private_key: Option<String>,
}
impl From<Run> for OverrideConfig {
@@ -52,8 +64,14 @@ impl From<Run> for OverrideConfig {
port: run_config.port,
fastmode: false,
#[cfg(feature = "coconut")]
#[cfg(any(feature = "eth", feature = "coconut"))]
enabled_credentials_mode: run_config.enabled_credentials_mode,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_private_key: run_config.eth_private_key,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
}
}
}
+2 -2
View File
@@ -102,7 +102,7 @@ fn minor_0_12_upgrade(
Version::new(0, 12, 0)
};
print_start_upgrade(config_version, &to_version);
print_start_upgrade(&config_version, &to_version);
config
.get_base_mut()
@@ -110,7 +110,7 @@ fn minor_0_12_upgrade(
config.save_to_file(None).unwrap_or_else(|err| {
eprintln!("failed to overwrite config file! - {:?}", err);
print_failed_upgrade(config_version, &to_version);
print_failed_upgrade(&config_version, &to_version);
process::exit(1);
});
+2 -2
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "1.0.0"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -32,7 +32,7 @@ credentials = { path = "../../common/credentials", optional = true }
crypto = { path = "../../common/crypto" }
nymsphinx = { path = "../../common/nymsphinx" }
topology = { path = "../../common/topology" }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
wasm-utils = { path = "../../common/wasm-utils" }
+3 -4
View File
@@ -11,7 +11,6 @@ This client is part of the [Nym](https://nymtech.net/docs) project. It's written
## Security Status
From a security point of view, this module is not yet complete. Key missing features include, but are not limited to: cover traffic, sending packets with delay according to Poisson distribution.
They should be implemented soon. You can build your applications, but don't rely on it for strong anonymity yet if your application needs cover traffic.
## Using it
@@ -38,6 +37,6 @@ To be clear, this is not something that most JS developers need to worry about,
If you're a Nym platform developer who's made changes to the Rust (or JS) files and wants to re-publish the package to NPM, here's how you do it:
1. bump version numbers as necessary for SemVer
2. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
3. `cd pkg && npm publish --access=public` will publish your changed package to NPM
1. `wasm-pack build --scope nymproject` builds the wasm binaries into the `pkg` directory (not in source control)
2. bump version numbers as necessary for SemVer
3. `wasm-pack publish --access=public` will publish your changed package to NPM
+6 -6
View File
@@ -13,8 +13,8 @@
// limitations under the License.
import {
NymClient,
set_panic_hook
NymClient,
set_panic_hook
} from "@nymproject/nym-client-wasm"
// current limitation of rust-wasm for async stuff : (
@@ -25,7 +25,7 @@ async function main() {
set_panic_hook();
// validator server we will use to get topology from
const validator = "https://validator.nymtech.net/api";
const validator = "https://sandbox-validator.nymtech.net/api"; //"http://localhost:8081";
client = new NymClient(validator);
@@ -69,7 +69,7 @@ async function sendMessageTo() {
* @param {string} message
*/
function displaySend(message) {
let timestamp = new Date().toISOString().slice(11, 21);
let timestamp = new Date().toISOString().substr(11, 12);
let sendDiv = document.createElement("div")
let paragraph = document.createElement("p")
@@ -90,11 +90,11 @@ function displayReceived(message) {
const content = message.message
const replySurb = message.replySurb
let timestamp = new Date().toISOString().slice(11, 21);
let timestamp = new Date().toISOString().substr(11, 12);
let receivedDiv = document.createElement("div")
let paragraph = document.createElement("p")
paragraph.setAttribute('style', 'color: green')
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content)
let paragraphContent = document.createTextNode(timestamp + " received >>> " + content + ((replySurb != null) ? "Reply SURB was attached here (but we can't do anything with it yet" : " (NO REPLY-SURB AVAILABLE)"))
paragraph.appendChild(paragraphContent)
receivedDiv.appendChild(paragraph)
document.getElementById("output").appendChild(receivedDiv)
+25 -115
View File
@@ -36,64 +36,6 @@
"node": ">=10.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -3330,14 +3272,14 @@
}
},
"node_modules/terser": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@@ -3430,6 +3372,15 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser/node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/thunky": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz",
@@ -3877,55 +3828,6 @@
"integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
"dev": true
},
"@jridgewell/gen-mapping": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
"integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"dev": true
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true
},
"@jridgewell/source-map": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz",
"integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6432,15 +6334,23 @@
"dev": true
},
"terser": {
"version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"version": "5.12.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.12.1.tgz",
"integrity": "sha512-NXbs+7nisos5E+yXwAD+y7zrcTkMqb0dEJxIGtSKPdCBzopf7ni4odPul2aechpV7EXNvOudYOX2bb5tln1jbQ==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"dependencies": {
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"terser-webpack-plugin": {
+2 -2
View File
@@ -34,6 +34,6 @@
"webpack-dev-server": "^4.7.4"
},
"dependencies": {
"@nymproject/nym-client-wasm": "1.0.0"
"@nymproject/nym-client-wasm": "file:../pkg"
}
}
}
@@ -9,7 +9,7 @@ module.exports = {
},
mode: "development",
plugins: [
new CopyWebpackPlugin({ patterns: ['index.html'] })
new CopyWebpackPlugin({patterns: ['index.html']})
],
experiments: { syncWebAssembly: true }
experiments: { asyncWebAssembly: true }
};
+2 -2
View File
@@ -11,7 +11,7 @@ use rand::rngs::OsRng;
use received_processor::ReceivedMessagesProcessor;
use std::sync::Arc;
use std::time::Duration;
use topology::{gateway, nym_topology_from_detailed, NymTopology};
use topology::{gateway, nym_topology_from_bonds, NymTopology};
use url::Url;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
@@ -265,7 +265,7 @@ impl NymClient {
Ok(gateways) => gateways,
};
let topology = nym_topology_from_detailed(mixnodes, gateways);
let topology = nym_topology_from_bonds(mixnodes, gateways);
let version = env!("CARGO_PKG_VERSION");
topology.filter_system_version(version)
}
+5 -4
View File
@@ -10,12 +10,13 @@ edition = "2021"
# TODO: (for this and other crates), similarly to 'tokio', import only required "futures" modules rather than
# the entire crate
futures = "0.3"
json = "0.12.4"
log = "0.4"
thiserror = "1.0"
url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
secp256k1 = { version = "0.20.3", optional = true }
web3 = { version = "0.17.0", default-features = false, optional = true }
secp256k1 = "0.20.3"
web3 = { version = "0.17.0", default-features = false }
async-trait = { version = "0.1.51" }
# internal
@@ -79,5 +80,5 @@ features = ["js"]
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
wasm = []
default = ["web3/default", "secp256k1"]
wasm = ["web3/wasm", "web3/http", "web3/signing"]
default = ["web3/default"]
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::GatewayClientError;
use std::str::FromStr;
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::Storage;
@@ -14,8 +15,6 @@ use crate::wasm_storage::StorageError;
#[cfg(all(not(target_arch = "wasm32"), feature = "coconut"))]
use credential_storage::error::StorageError;
#[cfg(feature = "coconut")]
use std::str::FromStr;
#[cfg(feature = "coconut")]
use {
coconut_interface::Base58,
@@ -24,12 +23,65 @@ use {
},
};
#[cfg(not(feature = "coconut"))]
use {
credentials::token::bandwidth::TokenCredential,
crypto::asymmetric::identity,
network_defaults::{
eth_contract::ETH_ERC20_JSON_ABI, eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE,
ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_ERC20_APPROVE_FUNCTION_NAME,
ETH_ERC20_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN, UTOKENS_TO_BURN,
},
pemstore::traits::PemStorableKeyPair,
rand::rngs::OsRng,
secp256k1::SecretKey,
web3::{
contract::{Contract, Options},
ethabi::Token,
signing::{Key, SecretKeyRef},
transports::Http,
types::{Address, U256, U64},
Web3,
},
};
#[cfg(not(feature = "coconut"))]
pub fn eth_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_CONTRACT_ADDRESS),
json::parse(ETH_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[cfg(not(feature = "coconut"))]
pub fn eth_erc20_contract(web3: Web3<Http>) -> Contract<Http> {
Contract::from_json(
web3.eth(),
Address::from(ETH_ERC20_CONTRACT_ADDRESS),
json::parse(ETH_ERC20_JSON_ABI)
.expect("Invalid json abi")
.dump()
.as_bytes(),
)
.expect("Invalid json abi")
}
#[derive(Clone)]
pub struct BandwidthController<St: Storage> {
#[allow(dead_code)]
storage: St,
#[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>,
#[cfg(not(feature = "coconut"))]
contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
erc20_contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
eth_private_key: SecretKey,
}
impl<St> BandwidthController<St>
@@ -45,8 +97,60 @@ where
}
#[cfg(not(feature = "coconut"))]
pub fn new(storage: St) -> Result<Self, GatewayClientError> {
Ok(BandwidthController { storage })
pub fn new(
storage: St,
eth_endpoint: String,
eth_private_key: String,
) -> Result<Self, GatewayClientError> {
// Fail early, on invalid url
let transport =
Http::new(&eth_endpoint).map_err(|_| GatewayClientError::InvalidURL(eth_endpoint))?;
let web3 = web3::Web3::new(transport);
// Fail early, on invalid abi
let contract = eth_contract(web3.clone());
let erc20_contract = eth_erc20_contract(web3);
let eth_private_key = secp256k1::SecretKey::from_str(&eth_private_key)
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
Ok(BandwidthController {
storage,
contract,
erc20_contract,
eth_private_key,
})
}
#[cfg(not(feature = "coconut"))]
async fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
self.storage
.insert_erc20_credential(
keypair.public_key().to_base58_string(),
keypair.private_key().to_base58_string(),
)
.await?;
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
let data = self.storage.get_next_erc20_credential().await?;
let public_key = identity::PublicKey::from_base58_string(data.public_key).unwrap();
let private_key = identity::PrivateKey::from_base58_string(data.private_key).unwrap();
Ok(identity::KeyPair::from_keys(private_key, public_key))
}
#[cfg(not(feature = "coconut"))]
async fn mark_keypair_as_spent(
&self,
keypair: &identity::KeyPair,
) -> Result<(), GatewayClientError> {
self.storage
.consume_erc20_credential(keypair.public_key().to_base58_string())
.await?;
Ok(())
}
#[cfg(feature = "coconut")]
@@ -75,4 +179,206 @@ where
&verification_key,
)?)
}
#[cfg(not(feature = "coconut"))]
pub async fn prepare_token_credential(
&self,
gateway_identity: identity::PublicKey,
gateway_owner: String,
) -> Result<TokenCredential, GatewayClientError> {
let kp = match self.restore_keypair().await {
Ok(kp) => kp,
Err(_) => {
let mut rng = OsRng;
let kp = identity::KeyPair::new(&mut rng);
self.backup_keypair(&kp).await?;
kp
}
};
let verification_key = *kp.public_key();
let signed_verification_key = kp.private_key().sign(&verification_key.to_bytes());
self.buy_token_credential(verification_key, signed_verification_key, gateway_owner)
.await?;
self.mark_keypair_as_spent(&kp).await?;
let message: Vec<u8> = verification_key
.to_bytes()
.iter()
.chain(gateway_identity.to_bytes().iter())
.copied()
.collect();
let signature = kp.private_key().sign(&message);
Ok(TokenCredential::new(
verification_key,
gateway_identity,
BANDWIDTH_VALUE,
signature,
))
}
#[cfg(not(feature = "coconut"))]
pub async fn buy_token_credential(
&self,
verification_key: identity::PublicKey,
signed_verification_key: identity::Signature,
gateway_owner: String,
) -> Result<(), GatewayClientError> {
let confirmations = if cfg!(debug_assertions) {
1
} else {
ETH_MIN_BLOCK_DEPTH
};
// 15 seconds per confirmation block + 10 seconds of network overhead + 20 seconds of wait for kill
log::info!(
"Waiting for Ethereum transaction. This should take about {} seconds",
(confirmations + 1) * 15 + 30
);
let mut options = Options::default();
let estimation = self
.erc20_contract
.estimate_gas(
ETH_ERC20_APPROVE_FUNCTION_NAME,
(
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
Token::Uint(U256::from(UTOKENS_TO_BURN)),
),
SecretKeyRef::from(&self.eth_private_key).address(),
options.clone(),
)
.await?;
options.gas = Some(estimation);
log::info!("Calling ERC20 approve in 10 seconds with an estimated gas of {}. Kill the process if you want to abort", estimation);
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
#[cfg(target_arch = "wasm32")]
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
log::error!(
"the timer has gone away while waiting for possible kill! - {}",
err
);
}
let recipt = self
.erc20_contract
.signed_call_with_confirmations(
ETH_ERC20_APPROVE_FUNCTION_NAME,
(
Token::Address(Address::from(ETH_CONTRACT_ADDRESS)),
Token::Uint(U256::from(UTOKENS_TO_BURN)),
),
options,
1, // One confirmation is enough, as we'll be consuming the approved token next anyway
&self.eth_private_key,
)
.await?;
if Some(U64::from(0u64)) == recipt.status {
return Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Approve transaction status is 0 (failure): {:?}",
recipt.logs,
)),
));
} else {
log::info!(
"Approved {} tokens for bandwidth use on Ethereum",
TOKENS_TO_BURN
);
}
let mut options = Options::default();
let estimation = self
.contract
.estimate_gas(
ETH_BURN_FUNCTION_NAME,
(
Token::Uint(U256::from(UTOKENS_TO_BURN)),
Token::Uint(U256::from(&verification_key.to_bytes())),
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
Token::String(gateway_owner.clone()),
),
SecretKeyRef::from(&self.eth_private_key).address(),
options.clone(),
)
.await?;
options.gas = Some(estimation);
log::info!("Generating bandwidth on ETH contract in 10 seconds with an estimated gas of {}. \
Kill the process if you want to abort. Keep in mind that if you abort now, you'll still have \
some tokens approved for bandwidth spending from the previous action. \
If you don't want that, you'll need to manually decreaseAllowance to revert the approval.", estimation);
#[cfg(not(target_arch = "wasm32"))]
tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
#[cfg(target_arch = "wasm32")]
if let Err(err) = fluvio_wasm_timer::Delay::new(std::time::Duration::from_secs(10)).await {
log::error!(
"the timer has gone away while waiting for possible kill! - {}",
err
);
}
let recipt = self
.contract
.signed_call_with_confirmations(
ETH_BURN_FUNCTION_NAME,
(
Token::Uint(U256::from(UTOKENS_TO_BURN)),
Token::Uint(U256::from(&verification_key.to_bytes())),
Token::Bytes(signed_verification_key.to_bytes().to_vec()),
Token::String(gateway_owner),
),
options,
confirmations,
&self.eth_private_key,
)
.await?;
if Some(U64::from(0u64)) == recipt.status {
Err(GatewayClientError::BurnTokenError(
web3::Error::InvalidResponse(format!(
"Transaction status is 0 (failure): {:?}",
recipt.logs,
)),
))
} else {
log::info!(
"Bought bandwidth on Ethereum: {} MB",
BANDWIDTH_VALUE / 1024 / 1024
);
Ok(())
}
}
}
#[cfg(not(feature = "coconut"))]
#[cfg(test)]
mod tests {
use network_defaults::ETH_EVENT_NAME;
use super::*;
#[test]
fn parse_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_contract(web3);
}
#[test]
fn parse_erc20_contract() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
// test no panic occurs
eth_erc20_contract(web3);
}
#[test]
fn check_event_name_constant_against_abi() {
let transport =
Http::new("https://rinkeby.infura.io/v3/00000000000000000000000000000000").unwrap();
let web3 = web3::Web3::new(transport);
let contract = eth_contract(web3);
assert!(contract.abi().event(ETH_EVENT_NAME).is_ok());
}
}
@@ -15,6 +15,8 @@ use crate::wasm_storage::PersistentStorage;
use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
@@ -307,8 +309,6 @@ impl GatewayClient {
async {
if let Some(mut s) = m_shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
@@ -317,7 +317,7 @@ impl GatewayClient {
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = std::future::pending::<()>().fuse();
let mut shutdown = Box::pin(async {}.fuse());
loop {
futures::select! {
@@ -552,6 +552,30 @@ impl GatewayClient {
Ok(())
}
#[cfg(not(feature = "coconut"))]
async fn claim_token_bandwidth(
&mut self,
credential: TokenCredential,
) -> Result<(), GatewayClientError> {
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_token_bandwidth_credential(
&credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
async fn try_claim_testnet_bandwidth(&mut self) -> Result<(), GatewayClientError> {
let msg = ClientControlRequest::ClaimFreeTestnetBandwidth.into();
self.bandwidth_remaining = match self.send_websocket_message(msg).await? {
@@ -590,10 +614,17 @@ impl GatewayClient {
.prepare_coconut_credential()
.await?;
#[cfg(not(feature = "coconut"))]
return self.try_claim_testnet_bandwidth().await;
let credential = self
.bandwidth_controller
.as_ref()
.unwrap()
.prepare_token_credential(self.gateway_identity, _gateway_owner)
.await?;
#[cfg(feature = "coconut")]
return self.claim_coconut_bandwidth(credential).await;
#[cfg(not(feature = "coconut"))]
return self.claim_token_bandwidth(credential).await;
}
fn estimate_required_bandwidth(&self, packets: &[MixPacket]) -> i64 {
@@ -113,8 +113,6 @@ impl PartiallyDelegated {
async {
if let Some(mut s) = m_shutdown {
s.recv().await
} else {
std::future::pending::<()>().await
}
}
.fuse()
@@ -123,7 +121,7 @@ impl PartiallyDelegated {
tokio::pin!(shutdown);
#[cfg(target_arch = "wasm32")]
let mut shutdown = std::future::pending::<()>().fuse();
let mut shutdown = Box::pin(async {}.fuse());
let ret_err = loop {
futures::select! {
@@ -26,6 +26,13 @@ pub struct CoconutCredential {
pub signature: String,
}
pub struct ERC20Credential {
pub id: i64,
pub public_key: String,
pub private_key: String,
pub consumed: bool,
}
#[async_trait]
pub trait Storage: Send + Sync {
async fn insert_coconut_credential(
@@ -40,6 +47,16 @@ pub trait Storage: Send + Sync {
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError>;
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError>;
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError>;
}
#[async_trait]
@@ -62,4 +79,20 @@ impl Storage for PersistentStorage {
async fn remove_coconut_credential(&self, _id: i64) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn insert_erc20_credential(
&self,
_public_key: String,
_private_key: String,
) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn consume_erc20_credential(&self, _public_key: String) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
}
@@ -186,7 +186,7 @@ impl Client {
address.into(),
receiver,
initial_connection_timeout,
&current_reconnection_attempt,
&*current_reconnection_attempt,
)
.await
});
+281 -252
View File
@@ -2,33 +2,35 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{validator_api, ValidatorClientError};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::NodeId;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
};
#[cfg(feature = "nymd-client")]
use crate::nymd::traits::MixnetQueryClient;
use validator_api_requests::models::{MixNodeBondAnnotated, UptimeResponse};
#[cfg(feature = "nymd-client")]
use crate::nymd::{self, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient};
use crate::nymd::{
self, error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract_common::{
mixnode::MixNodeBond,
pending_events::{PendingEpochEvent, PendingIntervalEvent},
Delegation, RewardedSetNodeStatus, UnbondedMixnode,
mixnode::DelegationEvent, ContractStateParams, Delegation, IdentityKey, Interval,
MixnetContractVersion, MixnodeRewardingStatusResponse, RewardedSetNodeStatus,
RewardedSetUpdateDetails,
};
#[cfg(feature = "nymd-client")]
use network_defaults::NymNetworkDetails;
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::MixNodeBondAnnotated;
use std::collections::{HashMap, HashSet};
#[cfg(feature = "nymd-client")]
#[must_use]
@@ -189,9 +191,12 @@ impl Client<QueryNymdClient> {
}
}
// nymd wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
// 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) {
@@ -203,22 +208,203 @@ impl<C> Client<C> {
self.nymd.mixnet_contract_address().clone()
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnodes(
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
pub async fn get_cached_mixnodes_detailed(
&self,
) -> Result<Vec<(NodeId, RewardedSetNodeStatus)>, ValidatorClientError>
) -> 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?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_operator_rewards(&self, address: String) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_operator_rewards(address).await?.u128())
}
pub async fn get_delegator_rewards(
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity, proxy)
.await?
.u128())
}
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address, proxy_address)
.await?)
}
pub async fn get_current_epoch(&self) -> Result<Interval, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_epoch().await?)
}
pub async fn get_current_operator_cost(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_current_operator_cost().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
self.nymd.get_mixnet_contract_version().await
}
pub async fn get_rewarding_status(
&self,
mix_identity: mixnet_contract_common::IdentityKey,
rewarding_interval_nonce: u32,
) -> Result<MixnodeRewardingStatusResponse, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_rewarding_status(mix_identity, rewarding_interval_nonce)
.await?)
}
pub async fn get_reward_pool(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_reward_pool().await?.u128())
}
pub async fn get_circulating_supply(&self) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_circulating_supply().await?.u128())
}
pub async fn get_sybil_resistance_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_sybil_resistance_percent().await?)
}
pub async fn get_active_set_work_factor(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_active_set_work_factor().await?)
}
pub async fn get_epochs_in_interval(&self) -> Result<u64, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_epochs_in_interval().await?)
}
pub async fn get_interval_reward_percent(&self) -> Result<u8, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_interval_reward_percent().await?)
}
pub async fn get_current_rewarded_set_update_details(
&self,
) -> Result<RewardedSetUpdateDetails, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.query_current_rewarded_set_update_details()
.await?)
}
// basically handles paging for us
pub async fn get_all_nymd_rewarded_set_mixnode_identities(
&self,
) -> Result<Vec<(IdentityKey, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut identities = Vec::new();
let mut start_after = None;
let mut height = None;
loop {
let mut paged_response = self
.nymd
.get_rewarded_set_paged(start_after.take(), self.rewarded_set_page_limit)
.get_rewarded_set_identities_paged(
start_after.take(),
self.rewarded_set_page_limit,
height,
)
.await?;
identities.append(&mut paged_response.nodes);
identities.append(&mut paged_response.identities);
if height.is_none() {
// keep using the same height (the first query happened at the most recent height)
height = Some(paged_response.at_height)
}
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -230,122 +416,83 @@ impl<C> Client<C> {
Ok(identities)
}
pub async fn get_all_nymd_mixnode_bonds(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
pub async fn get_nymd_rewarded_and_active_sets(
&self,
) -> Result<Vec<(MixNodeBond, RewardedSetNodeStatus)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnode_bonds_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.collect::<HashMap<_, _>>();
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
Ok(all_mixnodes
.into_iter()
.filter_map(|node| {
rewarded_set_identities
.get(node.identity())
.map(|status| (node, *status))
})
.collect())
}
pub async fn get_all_nymd_mixnodes_detailed(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError>
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_rewarded_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnodes_detailed_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let rewarded_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.map(|(identity, _status)| identity)
.collect::<HashSet<_>>();
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
Ok(all_mixnodes
.into_iter()
.filter(|node| rewarded_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_unbonded_mixnodes(
&self,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
/// If you need both rewarded and the active set, consider using [Self::get_nymd_rewarded_and_active_sets] instead
pub async fn get_nymd_active_set(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_paged(self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
let all_mixnodes = self.get_all_nymd_mixnodes().await?;
let active_set_identities = self
.get_all_nymd_rewarded_set_mixnode_identities()
.await?
.into_iter()
.filter_map(|(identity, status)| {
if status.is_active() {
Some(identity)
} else {
None
}
})
.collect::<HashSet<_>>();
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
Ok(all_mixnodes
.into_iter()
.filter(|node| active_set_identities.contains(node.identity()))
.collect())
}
pub async fn get_all_nymd_unbonded_mixnodes_by_owner(
&self,
owner: &cosmrs::AccountId,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
pub async fn get_all_nymd_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_owner_paged(owner, self.mixnode_page_limit, start_after.take())
.await?;
mixnodes.append(&mut paged_response.nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(mixnodes)
}
pub async fn get_all_nymd_unbonded_mixnodes_by_identity(
&self,
identity_key: String,
) -> Result<Vec<(NodeId, UnbondedMixnode)>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut mixnodes = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_unbonded_by_identity_paged(
identity_key.clone(),
self.mixnode_page_limit,
start_after.take(),
)
.get_mixnodes_paged(start_after.take(), self.mixnode_page_limit)
.await?;
mixnodes.append(&mut paged_response.nodes);
@@ -361,7 +508,7 @@ impl<C> Client<C> {
pub async fn get_all_nymd_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut gateways = Vec::new();
let mut start_after = None;
@@ -384,18 +531,18 @@ impl<C> Client<C> {
pub async fn get_all_nymd_single_mixnode_delegations(
&self,
mix_id: NodeId,
identity: IdentityKey,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_mixnode_delegations_paged(
mix_id,
.get_mix_delegations_paged(
identity.clone(),
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -417,7 +564,7 @@ impl<C> Client<C> {
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
@@ -442,128 +589,10 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_pending_epoch_events(
pub async fn get_mixnode_avg_uptimes(
&self,
) -> Result<Vec<PendingEpochEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_epoch_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
pub async fn get_all_nymd_pending_interval_events(
&self,
) -> Result<Vec<PendingIntervalEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync + Send,
{
let mut events = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_pending_interval_events_paged(start_after.take(), self.rewarded_set_page_limit)
.await?;
events.append(&mut paged_response.events);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(events)
}
}
// validator-api wrappers
#[cfg(feature = "nymd-client")]
impl<C> Client<C> {
pub fn change_validator_api(&mut self, new_endpoint: Url) {
self.validator_api.change_url(new_endpoint)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
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<MixNodeDetails>, 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<MixNodeDetails>, 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?)
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
}
pub async fn blind_sign(
@@ -601,17 +630,17 @@ impl ApiClient {
pub async fn get_cached_active_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_active_mixnodes().await?)
}
pub async fn get_cached_rewarded_mixnodes(
&self,
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_rewarded_mixnodes().await?)
}
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorClientError> {
Ok(self.validator_api.get_mixnodes().await?)
}
@@ -623,7 +652,7 @@ impl ApiClient {
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<GatewayCoreStatusResponse, ValidatorClientError> {
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_gateway_core_status_count(identity, since)
@@ -632,39 +661,39 @@ impl ApiClient {
pub async fn get_mixnode_core_status_count(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
) -> Result<CoreNodeStatusResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_core_status_count(mix_id, since)
.get_mixnode_core_status_count(identity, since)
.await?)
}
pub async fn get_mixnode_status(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_status(mix_id).await?)
Ok(self.validator_api.get_mixnode_status(identity).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_reward_estimation(mix_id)
.get_mixnode_reward_estimation(identity)
.await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self
.validator_api
.get_mixnode_stake_saturation(mix_id)
.get_mixnode_stake_saturation(identity)
.await?)
}
@@ -1,12 +1,11 @@
use crate::nymd::error::NymdError;
use crate::nymd::{Config as ClientConfig, NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use crate::nymd::traits::MixnetQueryClient;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use network_defaults::NymNetworkDetails;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
@@ -18,12 +17,12 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, cosmrs::AccountId, H>,
) -> (
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<NymNetworkDetails, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
@@ -46,16 +45,16 @@ pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
api_urls: impl Iterator<Item = (NymNetworkDetails, Url)>,
mixnet_contract_address: HashMap<NymNetworkDetails, cosmrs::AccountId, H>,
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
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();
let config = ClientConfig::try_from_nym_network_details(&network)
let config = ClientConfig::try_from_nym_network_details(&network.details())
.expect("failed to create valid nymd client config");
if let Ok(mut client) = NymdClient::<QueryNymdClient>::connect(config, url.as_str()) {
@@ -81,7 +80,7 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<NymNetworkDetails, Vec<(Url, bool)>> {
) -> HashMap<Network, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
@@ -93,7 +92,7 @@ fn extract_and_collect_results_into_map(
}
async fn test_nymd_connection(
network: NymNetworkDetails,
network: Network,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
@@ -105,47 +104,56 @@ async fn test_nymd_connection(
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!("Checking: nymd_url: {url}: {}: {}", "failed".red(), e);
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {url}: {}, but with abci error: {code}: {log}",
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!("Checking: nymd_url: {url}: {}: {error}", "failed".red());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {url}: {}, but with error: {e}",
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!("Checking: nymd_url: {url}: {}", "success".green());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
true
}
Err(e) => {
log::debug!("Checking: nymd_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(
network: NymNetworkDetails,
url: &Url,
client: &ApiClient,
) -> ConnectionResult {
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
@@ -153,15 +161,21 @@ async fn test_api_connection(
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {url}: {}", "success".green());
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
Err(e) => {
log::debug!("Checking: api_url: {url}: {}: {e}", "failed".red());
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
@@ -169,8 +183,8 @@ async fn test_api_connection(
}
enum ClientForConnectionTest {
Nymd(NymNetworkDetails, Url, Box<NymdClient<QueryNymdClient>>),
Api(NymNetworkDetails, Url, ApiClient),
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
}
impl ClientForConnectionTest {
@@ -203,12 +217,12 @@ impl fmt::Display for UrlType {
#[derive(Debug)]
enum ConnectionResult {
Nymd(NymNetworkDetails, Url, bool),
Api(NymNetworkDetails, Url, bool),
Nymd(Network, Url, bool),
Api(Network, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&NymNetworkDetails, &Url, &bool) {
fn result(&self) -> (&Network, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
@@ -225,8 +239,11 @@ impl ConnectionResult {
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (_network, url, result) = self.result();
let (network, url, result) = self.result();
let url_type = self.url_type();
write!(f, "{url}: {url_type}: connection is successful: {result}")
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
}
}
File diff suppressed because it is too large Load Diff
@@ -1,387 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey};
use mixnet_contract_common::mixnode::{
MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
};
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
use mixnet_contract_common::rewarding::{
EstimatedCurrentEpochRewardResponse, PendingRewardResponse,
};
use mixnet_contract_common::{
delegation, ContractBuildInformation, ContractState, ContractStateParams,
CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse,
IdentityKey, IntervalEventId, LayerDistribution, MixOwnershipResponse, MixnodeDetailsResponse,
NodeId, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse,
PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
};
use serde::Deserialize;
#[async_trait]
pub trait MixnetQueryClient {
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>;
// state/sys-params-related
async fn get_mixnet_contract_version(&self) -> Result<ContractBuildInformation, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetContractVersion {})
.await
}
async fn get_rewarding_validator_address(&self) -> Result<AccountId, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingValidatorAddress {})
.await
}
async fn get_mixnet_contract_settings(&self) -> Result<ContractStateParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStateParams {})
.await
}
async fn get_mixnet_contract_state(&self) -> Result<ContractState, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetState {})
.await
}
async fn get_rewarding_parameters(&self) -> Result<RewardingParams, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardingParams {})
.await
}
async fn get_current_interval_details(&self) -> Result<CurrentIntervalResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetCurrentIntervalDetails {})
.await
}
async fn get_rewarded_set_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedRewardedSetResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
.await
}
// mixnode-related:
async fn get_mixnode_bonds_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodeBondsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
.await
}
async fn get_mixnodes_detailed_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedMixnodesDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
.await
}
async fn get_unbonded_paged(
&self,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
.await
}
async fn get_unbonded_by_owner_paged(
&self,
owner: &AccountId,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
owner: owner.to_string(),
limit,
start_after,
})
.await
}
async fn get_unbonded_by_identity_paged(
&self,
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
) -> Result<PagedUnbondedMixnodesResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
})
.await
}
async fn get_owned_mixnode(
&self,
address: &AccountId,
) -> Result<MixOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedMixnode {
address: address.to_string(),
})
.await
}
async fn get_mixnode_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
.await
}
async fn get_mixnode_rewarding_details(
&self,
mix_id: NodeId,
) -> Result<MixnodeRewardingDetailsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
.await
}
async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
.await
}
async fn get_unbonded_mixnode_information(
&self,
mix_id: NodeId,
) -> Result<UnbondedMixnodeResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
.await
}
async fn get_layer_distribution(&self) -> Result<LayerDistribution, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {})
.await
}
// gateway-related:
async fn get_gateways_paged(
&self,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> Result<PagedGatewayResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGateways { start_after, limit })
.await
}
/// Checks whether there is a bonded gateway associated with the provided identity key
async fn get_gateway_bond(
&self,
identity: IdentityKey,
) -> Result<GatewayBondResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetGatewayBond { identity })
.await
}
/// Checks whether there is a bonded gateway associated with the provided client's address
async fn get_owned_gateway(
&self,
address: &AccountId,
) -> Result<GatewayOwnershipResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedGateway {
address: address.to_string(),
})
.await
}
// delegation-related:
/// Gets list of all delegations towards particular mixnode on particular page.
async fn get_mixnode_delegations_paged(
&self,
mix_id: NodeId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMixNodeDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
start_after,
limit,
})
.await
}
/// Gets list of all the mixnodes to which a particular address delegated.
async fn get_delegator_delegations_paged(
&self,
delegator: String,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
) -> Result<PagedDelegatorDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
delegator,
start_after,
limit,
})
.await
}
/// Checks value of delegation of given client towards particular mixnode.
async fn get_delegation_details(
&self,
mix_id: NodeId,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<MixNodeDelegationResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails {
mix_id,
delegator: delegator.to_string(),
proxy,
})
.await
}
/// Gets all the delegations on the entire network
async fn get_all_network_delegations_paged(
&self,
start_after: Option<delegation::StorageKey>,
limit: Option<u32>,
) -> Result<PagedAllDelegationsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllDelegations { start_after, limit })
.await
}
// rewards related
async fn get_pending_operator_reward(
&self,
operator: &AccountId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingOperatorReward {
address: operator.to_string(),
})
.await
}
async fn get_pending_mixnode_operator_reward(
&self,
mix_id: NodeId,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
.await
}
async fn get_pending_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
) -> Result<PendingRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_operator_reward(
&self,
mix_id: NodeId,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
estimated_performance,
})
.await
}
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_delegator_reward(
&self,
delegator: &AccountId,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
) -> Result<EstimatedCurrentEpochRewardResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
estimated_performance,
})
.await
}
// interval-related
async fn get_pending_epoch_events_paged(
&self,
start_after: Option<EpochEventId>,
limit: Option<u32>,
) -> Result<PendingEpochEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingEpochEvents { start_after, limit })
.await
}
async fn get_pending_interval_events_paged(
&self,
start_after: Option<IntervalEventId>,
limit: Option<u32>,
) -> Result<PendingIntervalEventsResponse, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingIntervalEvents { start_after, limit })
.await
}
async fn get_mixnode_details_by_identity(
&self,
mix_identity: IdentityKey,
) -> Result<Option<MixNodeDetails>, NymdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity {
mix_identity,
})
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for NymdClient<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.client
.query_contract_smart(self.mixnet_contract_address(), &query)
.await
}
}
#[async_trait]
impl<C> MixnetQueryClient for crate::Client<C>
where
C: CosmWasmClient + Sync + Send,
{
async fn query_mixnet_contract<T>(&self, query: MixnetQueryMsg) -> Result<T, NymdError>
where
for<'a> T: Deserialize<'a>,
{
self.nymd.query_mixnet_contract(query).await
}
}
@@ -1,496 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Fee, NymdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId,
};
#[async_trait]
pub trait MixnetSigningClient {
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
// state/sys-params-related
async fn update_rewarding_validator_address(
&self,
address: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingValidatorAddress {
address: address.to_string(),
},
vec![],
)
.await
}
async fn update_contract_state_params(
&self,
updated_parameters: ContractStateParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters },
vec![],
)
.await
}
async fn update_active_set_size(
&self,
active_set_size: u32,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
},
vec![],
)
.await
}
async fn update_rewarding_parameters(
&self,
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateRewardingParams {
updated_params,
force_immediately,
},
vec![],
)
.await
}
async fn update_interval_config(
&self,
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateIntervalConfig {
epochs_in_interval,
epoch_duration_secs,
force_immediately,
},
vec![],
)
.await
}
async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
},
vec![],
)
.await
}
async fn reconcile_epoch_events(
&self,
limit: Option<u32>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::ReconcileEpochEvents { limit },
vec![],
)
.await
}
// mixnode-related:
async fn bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_mixnode_on_behalf(
&self,
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondMixnode {}, vec![])
.await
}
async fn unbond_mixnode_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondMixnodeOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn update_mixnode_cost_params_on_behalf(
&self,
owner: AccountId,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf {
new_costs,
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfig { new_config },
vec![],
)
.await
}
async fn update_mixnode_config_on_behalf(
&self,
owner: AccountId,
new_config: MixNodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// gateway-related:
async fn bond_gateway(
&self,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGateway {
gateway,
owner_signature,
},
vec![pledge],
)
.await
}
async fn bond_gateway_on_behalf(
&self,
owner: AccountId,
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondGatewayOnBehalf {
gateway,
owner_signature,
owner: owner.to_string(),
},
vec![pledge],
)
.await
}
async fn unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondGateway {}, vec![])
.await
}
async fn unbond_gateway_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UnbondGatewayOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
&self,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnode { mix_id },
vec![amount],
)
.await
}
async fn delegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![amount],
)
.await
}
async fn undelegate_from_mixnode(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
}
async fn undelegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_id,
delegate: delegate.to_string(),
},
vec![],
)
.await
}
// reward-related
async fn reward_mixnode(
&self,
mix_id: NodeId,
performance: Performance,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
},
vec![],
)
.await
}
async fn withdraw_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::WithdrawOperatorReward {}, vec![])
.await
}
async fn withdraw_operator_reward_on_behalf(
&self,
owner: AccountId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf {
owner: owner.to_string(),
},
vec![],
)
.await
}
async fn withdraw_delegator_reward(
&self,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id },
vec![],
)
.await
}
async fn withdraw_delegator_reward_on_behalf(
&self,
owner: AccountId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf {
mix_id,
owner: owner.to_string(),
},
vec![],
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for NymdClient<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.default_memo();
self.client
.execute(
self.address(),
self.mixnet_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
}
#[async_trait]
impl<C> MixnetSigningClient for crate::Client<C>
where
C: SigningCosmWasmClient + Sync + Send,
{
async fn execute_mixnet_contract(
&self,
fee: Option<Fee>,
msg: MixnetExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
self.nymd.execute_mixnet_contract(fee, msg, funds).await
}
}
@@ -1,10 +1,8 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod coconut_bandwidth_query_client;
mod coconut_bandwidth_signing_client;
mod mixnet_query_client;
mod mixnet_signing_client;
mod multisig_query_client;
mod multisig_signing_client;
mod vesting_query_client;
@@ -12,8 +10,6 @@ mod vesting_signing_client;
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
pub use mixnet_query_client::MixnetQueryClient;
pub use mixnet_signing_client::MixnetSigningClient;
pub use multisig_query_client::MultisigQueryClient;
pub use multisig_signing_client::MultisigSigningClient;
pub use vesting_query_client::VestingQueryClient;
@@ -7,7 +7,7 @@ use crate::nymd::error::NymdError;
use crate::nymd::NymdClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use mixnet_contract_common::NodeId;
use mixnet_contract_common::IdentityKey;
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, AllDelegationsResponse, DelegationTimesResponse,
@@ -76,12 +76,12 @@ pub trait VestingQueryClient {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_id: NodeId,
mix_identity: String,
) -> Result<DelegationTimesResponse, NymdError>;
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, NodeId, u64)>,
start_after: Option<(u32, IdentityKey, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError>;
@@ -269,11 +269,11 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_id: NodeId,
mix_identity: String,
) -> Result<DelegationTimesResponse, NymdError> {
let request = VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
mix_id,
mix_identity,
};
self.client
.query_contract_smart(self.vesting_contract_address(), &request)
@@ -282,7 +282,7 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, NodeId, u64)>,
start_after: Option<(u32, IdentityKey, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NymdError> {
let request = VestingQueryMsg::GetAllDelegations { start_after, limit };
@@ -6,28 +6,14 @@ use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::{Coin, Fee, NymdClient};
use async_trait::async_trait;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixNode, NodeId};
use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
#[async_trait]
pub trait VestingSigningClient {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
profix_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -57,12 +43,10 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
@@ -81,21 +65,21 @@ pub trait VestingSigningClient {
async fn vesting_track_undelegation(
&self,
address: &str,
mix_id: NodeId,
mix_identity: IdentityKey,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode(
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'a>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode(
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'a>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
@@ -111,46 +95,15 @@ pub trait VestingSigningClient {
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn execute_vesting_contract(
&self,
fee: Option<Fee>,
msg: VestingExecuteMsg,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let memo = msg.name().to_string();
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&msg,
fee,
memo,
funds,
)
.await
}
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateMixnodeCostParams { new_costs },
vec![],
)
.await
}
async fn vesting_update_mixnode_config(
&self,
new_config: MixNodeConfigUpdate,
profit_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnodeConfig { new_config };
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
self.client
.execute(
self.address(),
@@ -250,22 +203,26 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
},
vec![],
)
.await
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::BondMixnode",
vec![],
)
.await
}
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
@@ -305,7 +262,6 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn withdraw_vested_coins(
&self,
amount: Coin,
@@ -326,54 +282,72 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_id: NodeId,
mix_identity: IdentityKey,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_id,
amount: amount.into(),
},
vec![],
)
.await
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode(
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'a>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::DelegateToMixnode {
mix_id,
amount: amount.into(),
},
vec![],
)
.await
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
amount: amount.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::DelegateToMixnode",
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode(
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'a>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address(),
&req,
fee,
"VestingContract::UndelegateFromMixnode",
vec![],
)
.await
}
async fn create_periodic_vesting_account(
@@ -72,7 +72,7 @@ impl DirectSecp256k1HdWallet {
}
fn derive_keypair(&self, hd_path: &DerivationPath) -> Result<Secp256k1Keypair, NymdError> {
let extended_private_key = XPrv::derive_from_path(self.seed, hd_path)?;
let extended_private_key = XPrv::derive_from_path(&self.seed, hd_path)?;
let private_key: SigningKey = extended_private_key.into();
let public_key = private_key.public_key();
@@ -207,9 +207,8 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use network_defaults::NymNetworkDetails;
use super::*;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
@@ -219,9 +218,7 @@ mod tests {
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefix = NymNetworkDetails::new_mainnet()
.chain_details
.bech32_account_prefix;
let prefix = MAINNET.bech32_prefix();
let addrs = vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
@@ -3,18 +3,17 @@
use crate::validator_api::error::ValidatorAPIError;
use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use mixnet_contract_common::mixnode::MixNodeDetails;
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
use validator_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CosmosAddressResponse, VerificationKeyResponse,
VerifyCredentialBody, VerifyCredentialResponse,
};
use validator_api_requests::models::{
GatewayCoreStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeCoreStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
CoreNodeStatusResponse, InclusionProbabilityResponse, MixNodeBondAnnotated,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -58,7 +57,6 @@ impl Client {
V: AsRef<str>,
{
let url = create_api_url(&self.url, path, params);
log::trace!("url: {:?}", url.as_str());
Ok(self.reqwest_client.get(url).send().await?.json().await?)
}
@@ -85,7 +83,7 @@ impl Client {
}
}
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
pub async fn get_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS)
.await
}
@@ -105,7 +103,7 @@ impl Client {
.await
}
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
pub async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE],
NO_PARAMS,
@@ -128,7 +126,7 @@ impl Client {
.await
}
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorAPIError> {
pub async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeBond>, ValidatorAPIError> {
self.query_validator_api(
&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED],
NO_PARAMS,
@@ -151,11 +149,28 @@ impl Client {
.await
}
pub async fn get_probs_mixnode_rewarded(
&self,
mixnode_id: &str,
) -> Result<HashMap<String, f32>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::MIXNODES,
routes::REWARDED,
routes::INCLUSION_CHANCE,
mixnode_id,
],
NO_PARAMS,
)
.await
}
pub async fn get_gateway_core_status_count(
&self,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<GatewayCoreStatusResponse, ValidatorAPIError> {
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
@@ -184,16 +199,16 @@ impl Client {
pub async fn get_mixnode_core_status_count(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, ValidatorAPIError> {
) -> Result<CoreNodeStatusResponse, ValidatorAPIError> {
if let Some(since) = since {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
CORE_STATUS_COUNT,
],
&[(SINCE_ARG, since.to_string())],
@@ -205,8 +220,7 @@ impl Client {
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
CORE_STATUS_COUNT,
identity,
],
NO_PARAMS,
)
@@ -216,14 +230,14 @@ impl Client {
pub async fn get_mixnode_status(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<MixnodeStatusResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
routes::STATUS,
],
NO_PARAMS,
@@ -233,14 +247,14 @@ impl Client {
pub async fn get_mixnode_reward_estimation(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<RewardEstimationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
routes::REWARD_ESTIMATION,
],
NO_PARAMS,
@@ -250,14 +264,14 @@ impl Client {
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<StakeSaturationResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
routes::STAKE_SATURATION,
],
NO_PARAMS,
@@ -267,14 +281,14 @@ impl Client {
pub async fn get_mixnode_inclusion_probability(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<InclusionProbabilityResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
routes::INCLUSION_CHANCE,
],
NO_PARAMS,
@@ -284,14 +298,27 @@ impl Client {
pub async fn get_mixnode_avg_uptime(
&self,
mix_id: NodeId,
identity: IdentityKeyRef<'_>,
) -> Result<UptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
&mix_id.to_string(),
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::AVG_UPTIME,
],
NO_PARAMS,
-5
View File
@@ -28,11 +28,6 @@ pub fn pretty_cosmwasm_coin(coin: &CosmWasmCoin) -> String {
format!("{} {}", amount, denom)
}
pub fn pretty_decimal_with_denom(value: Decimal, denom: &str) -> String {
// TODO: we might have to truncate the value here (that's why I moved it to separate function)
format!("{} {}", value, denom)
}
pub fn show_error<E>(e: E)
where
E: Display,
@@ -51,7 +51,7 @@ pub async fn query_balance(
return;
}
let denom = args.denom.unwrap_or_default();
let denom = args.denom.unwrap_or_else(|| "".to_string());
for coin in coins {
if denom.is_empty() || denom.eq_ignore_ascii_case(&coin.denom) {
@@ -4,16 +4,12 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
use mixnet_contract_common::Coin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity_key: String,
#[clap(long)]
pub amount: u128,
@@ -24,25 +20,10 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.delegate_to_mixnode(mix_id, coin.into(), None)
.delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -8,8 +8,8 @@ use crate::context::SigningClientWithValidatorAPI;
use crate::utils::{pretty_cosmwasm_coin, show_error_passthrough};
use comfy_table::Table;
use cosmwasm_std::Addr;
use mixnet_contract_common::{Delegation, PendingEpochEvent, PendingEpochEventData};
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::Delegation;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -26,7 +26,19 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
.map_err(show_error_passthrough);
let mixnet_contract_events = client
.get_all_nymd_pending_epoch_events()
.nymd
.get_pending_delegation_events(client.nymd.address().to_string(), None)
.await
.map_err(show_error_passthrough);
let vesting_contract = client.nymd.vesting_contract_address();
let vesting_contract_events = client
.nymd
.get_pending_delegation_events(
client.nymd.address().to_string(),
Some(vesting_contract.to_string()),
)
.await
.map_err(show_error_passthrough);
@@ -46,6 +58,13 @@ pub async fn execute(_args: Args, client: SigningClientWithValidatorAPI) {
print_delegation_events(res, &client).await;
}
}
if let Ok(res) = vesting_contract_events {
if !res.is_empty() {
println!();
println!("Pending delegations (locked tokens):");
print_delegation_events(res, &client).await;
}
}
}
async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidatorAPI) -> String {
@@ -58,17 +77,14 @@ async fn to_iso_timestamp(block_height: u32, client: &SigningClientWithValidator
async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec!["Timestamp", "Mix Id", "Delegation", "Proxy"]);
table.set_header(vec!["Timestamp", "Identity Key", "Delegation", "Proxy"]);
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.height as u32, client).await,
delegation.mix_id.to_string(),
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
delegation
.proxy
.map(Addr::into_string)
.unwrap_or_else(|| "-".into()),
format!("{:?}", delegation.proxy),
]);
}
@@ -76,53 +92,36 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
}
async fn print_delegation_events(
events: Vec<PendingEpochEvent>,
events: Vec<DelegationEvent>,
client: &SigningClientWithValidatorAPI,
) {
let mut table = Table::new();
table.set_header(vec![
"Timestamp",
"Mix id",
"Identity Key",
"Delegation",
"Event Type",
"Proxy",
]);
for event in events {
match event.event {
PendingEpochEventData::Delegate {
owner,
mix_id,
amount,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
pretty_cosmwasm_coin(&amount),
"Delegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
match event {
DelegationEvent::Delegate(delegation) => {
table.add_row(vec![
to_iso_timestamp(delegation.block_height as u32, client).await,
delegation.node_identity.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
"Delegate".to_string(),
]);
}
PendingEpochEventData::Undelegate {
owner,
mix_id,
proxy,
} => {
if owner.as_str() == client.nymd.address().as_ref() {
table.add_row(vec![
"not-sure-if-applicable".into(),
mix_id.to_string(),
"-".to_string(),
"Undelegate".to_string(),
proxy.map(Addr::into_string).unwrap_or_else(|| "-".into()),
]);
}
DelegationEvent::Undelegate(undelegate) => {
table.add_row(vec![
to_iso_timestamp(undelegate.block_height() as u32, client).await,
undelegate.mix_identity().to_string(),
"-".to_string(),
"Undelegate".to_string(),
]);
}
_ => {}
}
}
@@ -4,38 +4,18 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity_key: String,
}
pub async fn claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.withdraw_delegator_reward(mix_id, None)
.execute_claim_delegator_reward(args.identity_key, None)
.await
.expect("failed to claim delegator-reward");
@@ -4,38 +4,18 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity: String,
}
pub async fn vesting_claim_delegator_reward(args: Args, client: SigningClient) {
info!("Claim vesting delegator reward");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.withdraw_delegator_reward_on_behalf(client.address().clone(), mix_id, None)
.execute_vesting_claim_delegator_reward(args.identity, None)
.await
.expect("failed to claim vesting delegator-reward");
@@ -4,38 +4,18 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity_key: String,
}
pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.undelegate_from_mixnode(mix_id, None)
.remove_mixnode_delegation(&*args.identity_key, None)
.await
.expect("failed to remove stake from mixnode!");
@@ -4,8 +4,7 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::{Coin, NodeId};
use validator_client::nymd::traits::MixnetQueryClient;
use mixnet_contract_common::Coin;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -13,10 +12,7 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity_key: String,
#[clap(long)]
pub amount: u128,
@@ -27,25 +23,10 @@ pub async fn vesting_delegate_to_mixnode(args: Args, client: SigningClient) {
info!("Starting vesting delegation to mixnode");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_delegate_to_mixnode(mix_id, coin.into(), None)
.vesting_delegate_to_mixnode(&*args.identity_key, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -3,8 +3,7 @@
use clap::Parser;
use log::info;
use mixnet_contract_common::NodeId;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
use crate::context::SigningClient;
@@ -12,32 +11,14 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
pub identity_key: String,
}
pub async fn vesting_undelegate_from_mixnode(args: Args, client: SigningClient) {
info!("removing stake from vesting mix-node");
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.vesting_undelegate_from_mixnode(mix_id, None)
.vesting_undelegate_from_mixnode(&*args.identity_key, None)
.await
.expect("failed to remove stake from vesting account on mixnode!");
@@ -6,7 +6,6 @@ use clap::Parser;
use log::{info, warn};
use mixnet_contract_common::Coin;
use network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -4,7 +4,6 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -68,7 +68,7 @@ pub async fn vesting_bond_gateway(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_bond_gateway(gateway, &args.signature, coin.into(), None)
.vesting_bond_gateway(gateway, &*args.signature, coin.into(), None)
.await
.expect("failed to bond gateway!");
@@ -4,7 +4,6 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -2,15 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use mixnet_contract_common::Coin;
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::traits::MixnetSigningClient;
use validator_client::nymd::CosmWasmCoin;
use crate::context::SigningClient;
@@ -43,12 +40,6 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -80,23 +71,13 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) {
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.bond_mixnode(mixnode, cost_params, args.signature, coin.into(), None)
.bond_mixnode(mixnode, args.signature, coin.into(), None)
.await
.expect("failed to bond mixnode!");
@@ -4,7 +4,6 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
@@ -13,7 +12,7 @@ pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
info!("Claim operator reward");
let res = client
.withdraw_operator_reward(None)
.execute_claim_operator_reward(None)
.await
.expect("failed to claim operator reward");
@@ -4,7 +4,6 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -16,7 +15,7 @@ pub async fn vesting_claim_operator_reward(client: SigningClient) {
info!("Claim vesting operator reward");
let res = client
.withdraw_operator_reward_on_behalf(client.address().clone(), None)
.execute_vesting_claim_operator_reward(None)
.await
.expect("failed to claim vesting operator reward");
@@ -3,8 +3,8 @@
use clap::{Args, Subcommand};
pub mod update_config;
pub mod vesting_update_config;
pub mod update_profit_percent;
pub mod vesting_update_profit_percent;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -15,12 +15,8 @@ pub struct MixnetOperatorsMixnodeSettings {
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsMixnodeSettingsCommands {
/// Update mixnode configuration
UpdateConfig(update_config::Args),
/// Update mixnode configuration for a mixnode bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
/// Update mixnode cost parameters
UpdateCostParameters,
/// Update mixnode cost parameters for a mixnode bonded with locked tokens
VestingUpdateCostParameters,
/// Update profit percentage
UpdateProfitPercentage(update_profit_percent::Args),
/// Update profit percentage for a mixnode bonded with locked tokens
VestingUpdateProfitPercentage(vesting_update_profit_percent::Args),
}
@@ -1,68 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.update_mixnode_config(update, None)
.await
.expect("updating mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -0,0 +1,24 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
}
pub async fn update_profit_percent(args: Args, client: SigningClient) {
info!("Update mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.update_mixnode_config(args.profit_percent, None)
.await
.expect("updating mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -1,69 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use mixnet_contract_common::MixNodeConfigUpdate;
use validator_client::nymd::traits::MixnetQueryClient;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub verloc_port: Option<u16>,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting mix node config!");
let current_details = match client
.get_owned_mixnode(client.address())
.await
.expect("failed to query the chain for mixnode details")
.mixnode_details
{
Some(details) => details,
None => {
log::warn!("this operator does not own a mixnode to update");
return;
}
};
let update = MixNodeConfigUpdate {
host: args
.host
.unwrap_or(current_details.bond_information.mix_node.host),
mix_port: args
.mix_port
.unwrap_or(current_details.bond_information.mix_node.mix_port),
verloc_port: args
.verloc_port
.unwrap_or(current_details.bond_information.mix_node.verloc_port),
http_api_port: args
.http_api_port
.unwrap_or(current_details.bond_information.mix_node.http_api_port),
version: args
.version
.unwrap_or(current_details.bond_information.mix_node.version),
};
let res = client
.vesting_update_mixnode_config(update, None)
.await
.expect("updating vesting mix-node config");
info!("mixnode config updated: {:?}", res)
}
@@ -0,0 +1,28 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_percent: u8,
#[clap(long)]
pub gas: Option<u64>,
}
pub async fn vesting_update_profit_percent(client: SigningClient, args: Args) {
info!("Update vesting mix node profit percent - get those rewards!");
//profit percent between 1-100
let res = client
.vesting_update_mixnode_config(args.profit_percent, None)
.await
.expect("updating vesting mix-node profit percent");
info!("profit percentage updated: {:?}", res)
}
@@ -3,7 +3,6 @@
use clap::Parser;
use log::info;
use validator_client::nymd::traits::MixnetSigningClient;
use crate::context::SigningClient;
@@ -3,14 +3,13 @@
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use mixnet_contract_common::{Coin, MixNodeCostParams};
use mixnet_contract_common::{MixNode, Percent};
use mixnet_contract_common::Coin;
use mixnet_contract_common::MixNode;
use network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use validator_client::nymd::{CosmWasmCoin, VestingSigningClient};
use validator_client::nymd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -41,12 +40,6 @@ pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u8>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
@@ -79,23 +72,13 @@ pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str
sphinx_key: args.sphinx_key,
identity_key: args.identity_key,
version: args.version,
profit_margin_percent: args.profit_margin_percent.unwrap_or(10),
};
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
},
};
let res = client
.vesting_bond_mixnode(mixnode, cost_params, &args.signature, coin.into(), None)
.vesting_bond_mixnode(mixnode, &*args.signature, coin.into(), None)
.await
.expect("failed to bond vesting mixnode!");
@@ -5,7 +5,7 @@ use clap::Parser;
use comfy_table::Table;
use crate::context::QueryClientWithValidatorAPI;
use crate::utils::{pretty_decimal_with_denom, show_error};
use crate::utils::{pretty_cosmwasm_coin, show_error};
#[derive(Debug, Parser)]
pub struct Args {
@@ -19,8 +19,7 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
Ok(res) => match args.identity_key {
Some(identity_key) => {
let node = res.iter().find(|node| {
node.bond_information
.mix_node
node.mix_node
.identity_key
.to_string()
.eq_ignore_ascii_case(&identity_key)
@@ -34,7 +33,6 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
let mut table = Table::new();
table.set_header(vec![
"Mix id",
"Identity Key",
"Owner",
"Host",
@@ -43,15 +41,13 @@ pub async fn query(args: Args, client: &QueryClientWithValidatorAPI) {
"Version",
]);
for node in res {
let denom = &node.bond_information.original_pledge().denom;
table.add_row(vec![
node.mix_id().to_string(),
node.bond_information.mix_node.identity_key.clone(),
node.bond_information.owner.clone().into_string(),
node.bond_information.mix_node.host.clone(),
pretty_decimal_with_denom(node.rewarding_details.operator, denom),
pretty_decimal_with_denom(node.rewarding_details.delegates, denom),
node.bond_information.mix_node.version,
node.mix_node.identity_key.to_string(),
node.owner.to_string(),
node.mix_node.host.to_string(),
pretty_cosmwasm_coin(&node.pledge_amount),
pretty_cosmwasm_coin(&node.total_delegation()),
node.mix_node.version,
]);
}
@@ -51,7 +51,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let res = client
.create_periodic_vesting_account(
&args.address,
&*args.address,
args.staking_address,
Some(vesting),
coin.into(),
@@ -70,7 +70,7 @@ pub async fn create(args: Args, client: SigningClient, network_details: &NymNetw
let send_coin_response = client
.send(
&AccountId::from_str(&args.address).unwrap(),
&AccountId::from_str(&*args.address).unwrap(),
vec![coin.into()],
"payment made :)",
None,
-11
View File
@@ -1,11 +0,0 @@
[package]
name = "completions"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
clap = { version = "3.2", features = ["derive"] }
clap_complete = "3.2"
clap_complete_fig = "3.2"
-58
View File
@@ -1,58 +0,0 @@
use clap::builder::Command;
use clap::clap_derive::ArgEnum;
use clap::Args;
use clap_complete::generator::generate;
use clap_complete::Shell as ClapShell;
use std::io;
pub fn fig_generate(command: &mut Command, name: &str) {
clap_complete::generate(
clap_complete_fig::Fig,
command,
name,
&mut std::io::stdout(),
)
}
#[derive(ArgEnum, Copy, Clone)]
pub enum Shell {
Bash,
Elvish,
Fish,
PowerShell,
Zsh,
}
#[derive(Args, Copy, Clone)]
pub struct ArgShell {
#[clap(arg_enum, value_name = "SHELL")]
shell: Shell,
}
impl ArgShell {
pub fn generate(&self, command: &mut Command, name: &str) {
self.shell.generate(command, name)
}
}
impl Shell {
pub fn generate(&self, command: &mut Command, name: &str) {
match &self {
Self::Bash => {
generate(ClapShell::Bash, command, name, &mut io::stdout());
}
Self::Elvish => {
generate(ClapShell::Elvish, command, name, &mut io::stdout());
}
Self::Fish => {
generate(ClapShell::Fish, command, name, &mut io::stdout());
}
Self::PowerShell => {
generate(ClapShell::PowerShell, command, name, &mut io::stdout());
}
Self::Zsh => {
generate(ClapShell::Zsh, command, name, &mut io::stdout());
}
}
}
}
+1
View File
@@ -9,6 +9,7 @@ edition = "2021"
[dependencies]
cfg-if = "1.0.0"
handlebars = "3.0.1"
humantime-serde = "1.0"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5.6"
-1
View File
@@ -88,7 +88,6 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
let location = custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
log::info!("Configuration file will be saved to {:?}", location);
cfg_if::cfg_if! {
if #[cfg(unix)] {
@@ -8,4 +8,3 @@ edition = "2021"
[dependencies]
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
@@ -28,26 +28,3 @@ pub fn may_find_attribute(event: &Event, key: &str) -> Option<String> {
}
None
}
pub trait OptionallyAddAttribute {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self;
}
impl OptionallyAddAttribute for Event {
fn add_optional_attribute(
self,
key: impl Into<String>,
value: Option<impl Into<String>>,
) -> Self {
if let Some(value) = value {
self.add_attribute(key, value)
} else {
// TODO: perhaps if value doesn't exist, we should emit explicit 'null'?
self
}
}
}
@@ -2,6 +2,3 @@
// SPDX-License-Identifier: Apache-2.0
pub mod events;
pub mod types;
pub use types::*;
@@ -1,33 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
// TODO: there's no reason this couldn't be used for proper binaries, but in that case
// perhaps the struct should get renamed and moved to a "more" common crate
#[derive(Debug, Serialize, Deserialize)]
pub struct ContractBuildInformation {
// VERGEN_BUILD_TIMESTAMP
/// Provides the build timestamp, for example `2021-02-23T20:14:46.558472672+00:00`.
pub build_timestamp: String,
// VERGEN_BUILD_SEMVER
/// Provides the build version, for example `0.1.0-9-g46f83e1`.
pub build_version: String,
// VERGEN_GIT_SHA
/// Provides the hash of the commit that was used for the build, for example `46f83e112520533338245862d366f6a02cef07d4`.
pub commit_sha: String,
// VERGEN_GIT_COMMIT_TIMESTAMP
/// Provides the timestamp of the commit that was used for the build, for example `2021-02-23T08:08:02-05:00`.
pub commit_timestamp: String,
// VERGEN_GIT_BRANCH
/// Provides the name of the git branch that was used for the build, for example `master`.
pub commit_branch: String,
// VERGEN_RUSTC_SEMVER
/// Provides the rustc version that was used for the build, for example `1.52.0-nightly`.
pub rustc_version: String,
}
@@ -8,24 +8,23 @@ rust-version = "1.62"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bs58 = "0.4.0"
cosmwasm-std = "1.0.0"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
schemars = "0.8"
thiserror = "1.0"
contracts-common = { path = "../contracts-common" }
serde_json = "1.0.0"
# TO CHECK WHETHER STILL NEEDED:
fixed = { version = "1.1", features = ["serde"] }
az = "1.1"
log = "0.4.14"
time = { version = "0.3.6", features = ["parsing", "formatting"] }
ts-rs = { version = "6.1.2", optional = true }
ts-rs = "6.1.2"
contracts-common = { path = "../contracts-common" }
[dev-dependencies]
rand_chacha = "0.3"
time = { version = "0.3.5", features = ["serde", "macros"] }
[features]
default = []
generate-ts = ['ts-rs']
generate-ts = []
@@ -1,25 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Decimal;
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
// I'm using this one as opposed to "Decimal::one()", as this provides us with higher accuracy
// whilst providing no noticable drawbacks.
pub const UNIT_DELEGATION_BASE: Decimal =
Decimal::raw(1_000_000_000 * 1_000_000_000_000_000_000u128);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unit_delegation_didnt_change() {
// a sanity check test to make sure Decimal's `DECIMAL_FRACTIONAL` internal implementation hasn't changed
assert_eq!(
UNIT_DELEGATION_BASE,
Decimal::one() * Decimal::from_atomics(1_000_000_000u32, 0).unwrap()
)
}
}
@@ -1,135 +1,151 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
use crate::{Addr, NodeId};
use cosmwasm_std::{Coin, Decimal};
use crate::{Addr, IdentityKey};
use cosmwasm_std::{Coin, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
// just use a string representation of those so that we wouldn't need to bother with decoding bytes
// and trying to figure out whether they're valid, etc
pub type OwnerProxySubKey = String;
pub type StorageKey = (NodeId, OwnerProxySubKey);
type OwnerAddressBytes = Vec<u8>;
type BlockHeight = u64;
pub fn generate_owner_storage_subkey(address: &Addr, proxy: Option<&Addr>) -> String {
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
if let Some(proxy) = &proxy {
let key_bytes = address
address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect::<Vec<_>>();
bs58::encode(key_bytes).into_string()
.collect()
} else {
address.clone().into_string()
address.as_bytes().to_vec()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
/// Id of the MixNode that this delegation was performed against.
#[serde(alias = "node_id")]
pub mix_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
/// Value of the "unit delegation" associated with the mixnode at the time of delegation.
#[serde(alias = "crr")]
pub cumulative_reward_ratio: Decimal,
/// Original delegation amount. Note that it is never mutated as delegation accumulates rewards.
pub node_identity: IdentityKey,
pub amount: Coin,
pub block_height: u64,
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of another address
}
/// Block height where this delegation occurred.
pub height: u64,
impl Eq for Delegation {}
/// Proxy address used to delegate the funds on behalf of another address
pub proxy: Option<Addr>,
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Delegation {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.node_identity.hash(state);
self.block_height.hash(state);
self.proxy.hash(state);
}
}
impl Delegation {
pub fn new(
owner: Addr,
mix_id: NodeId,
cumulative_reward_ratio: Decimal,
node_identity: IdentityKey,
amount: Coin,
height: u64,
block_height: BlockHeight,
proxy: Option<Addr>,
) -> Self {
Delegation {
owner,
mix_id,
cumulative_reward_ratio,
node_identity,
amount,
height,
block_height,
proxy,
}
}
pub fn generate_storage_key(
mix_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
(mix_id, generate_owner_storage_subkey(owner_address, proxy))
pub fn storage_key(&self) -> (IdentityKey, OwnerAddressBytes, BlockHeight) {
(
self.node_identity(),
self.proxy_storage_key(),
self.block_height(),
)
}
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
mix_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(mix_id, owner_proxy_subkey)
pub fn event_storage_key(&self) -> (OwnerAddressBytes, BlockHeight, IdentityKey) {
(
self.proxy_storage_key(),
self.block_height(),
self.node_identity(),
)
}
pub fn dec_amount(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed our base coin amount is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.amount.amount, 0).unwrap()
pub fn proxy_storage_key(&self) -> OwnerAddressBytes {
generate_storage_key(&self.owner, self.proxy.as_ref())
}
pub fn proxy_storage_key(&self) -> OwnerProxySubKey {
generate_owner_storage_subkey(&self.owner, self.proxy.as_ref())
pub fn proxy(&self) -> Option<&Addr> {
self.proxy.as_ref()
}
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref())
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
self.amount.amount += amount;
if let Some(at_height) = at_height {
self.block_height = at_height;
}
}
pub fn amount(&self) -> &Coin {
&self.amount
}
pub fn node_identity(&self) -> IdentityKey {
self.node_identity.clone()
}
pub fn owner(&self) -> Addr {
self.owner.clone()
}
pub fn block_height(&self) -> u64 {
self.block_height
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedMixNodeDelegationsResponse {
impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} delegated towards {} by {} at block {}",
self.amount, self.node_identity, self.owner, self.block_height
)
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedMixDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<OwnerProxySubKey>,
pub start_next_after: Option<(String, u64)>,
}
impl PagedMixNodeDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<OwnerProxySubKey>) -> Self {
PagedMixNodeDelegationsResponse {
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(String, u64)>) -> Self {
PagedMixDelegationsResponse {
delegations,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<(NodeId, OwnerProxySubKey)>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedDelegatorDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(NodeId, OwnerProxySubKey)>,
) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<IdentityKey>) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
start_next_after,
@@ -137,32 +153,20 @@ impl PagedDelegatorDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeDelegationResponse {
pub delegation: Option<Delegation>,
pub mixnode_still_bonded: bool,
}
impl MixNodeDelegationResponse {
pub fn new(delegation: Option<Delegation>, mixnode_still_bonded: bool) -> Self {
MixNodeDelegationResponse {
delegation,
mixnode_still_bonded,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct PagedAllDelegationsResponse {
pub delegations: Vec<Delegation>,
pub start_next_after: Option<StorageKey>,
pub start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
}
impl PagedAllDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<StorageKey>) -> Self {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(IdentityKey, Vec<u8>, u64)>,
) -> Self {
PagedAllDelegationsResponse {
delegations,
start_next_after,
start_next_after: start_next_after.map(|(id, addr, height)| (id, addr, height)),
}
}
}
@@ -1,139 +1,23 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::NodeId;
use cosmwasm_std::{Addr, Coin, Decimal};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum MixnetContractError {
#[error("Overflow Error")]
OverflowError(#[from] cosmwasm_std::OverflowError),
#[error("reward_blockstamp field not set, set_reward_blockstamp must be called before attempting to issue rewards")]
BlockstampNotSet,
#[error("{source}")]
TryFromIntError {
#[from]
source: std::num::TryFromIntError,
},
#[error("Error casting from U128")]
CastError,
#[error("{source}")]
StdErr {
#[from]
source: cosmwasm_std::StdError,
},
#[error("Provided percent value is greater than 100%")]
InvalidPercent,
#[error("Attempted to subtract decimals with overflow ({minuend}.sub({subtrahend}))")]
OverflowDecimalSubtraction {
minuend: Decimal,
subtrahend: Decimal,
},
#[error("Attempted to subtract with overflow ({minuend}.sub({subtrahend}))")]
OverflowSubtraction { minuend: u64, subtrahend: u64 },
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Mixnode ({mix_id}) does not exist")]
MixNodeBondNotFound { mix_id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
#[error("{owner} does not seem to own any gateways")]
NoAssociatedGatewayBond { owner: Addr },
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
#[error("This address has already bonded a gateway")]
AlreadyOwnsGateway,
#[error("Gateway with this identity already exists. Its owner is {owner}")]
DuplicateGateway { owner: Addr },
#[error("Unauthorized")]
Unauthorized,
#[error("No tokens were sent for the bonding")]
NoBondFound,
#[error("No funds were provided for the delegation")]
EmptyDelegation,
#[error("Wrong coin denomination. Received: {received}, expected: {expected}")]
WrongDenom { received: String, expected: String },
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
ProxyMismatch { existing: String, incoming: String },
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
#[error("Failed to recover ed25519 signature from its base58 representation - {0}")]
MalformedEd25519Signature(String),
#[error("Provided ed25519 signature did not verify correctly")]
InvalidEd25519Signature,
#[error("Can't perform the specified action as the current epoch is still progress. It started at {epoch_start} and finishes at {epoch_end}, while the current block time is {current_block_time}")]
EpochInProgress {
current_block_time: u64,
epoch_start: i64,
epoch_end: i64,
},
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { mix_id: NodeId },
#[error("Mixnode {mix_id} has already unbonded")]
MixnodeHasUnbonded { mix_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
#[error(
"Could not find any delegation information associated with mixnode {mix_id} for {address} (proxy: {proxy:?})"
)]
NoMixnodeDelegationFound {
mix_id: NodeId,
address: String,
proxy: Option<String>,
},
#[error("Provided message to update rewarding params did not contain any updates")]
EmptyParamsChangeMsg,
#[error("Provided active set size is bigger than the rewarded set")]
InvalidActiveSetSize,
#[error("Provided rewarded set size is smaller than the active set")]
InvalidRewardedSetSize,
#[error("Provided active set size is zero")]
ZeroActiveSet,
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("Received unexpected value for the active set. Got: {received}, expected: {expected}")]
UnexpectedActiveSetSize { received: u32, expected: u32 },
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { mix_id: NodeId },
#[error("Division by zero at {}", line!())]
DivisionByZero,
}
@@ -1,90 +1,32 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::NodeRewardResult;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer};
use cosmwasm_std::{Addr, Coin, Event, Uint128};
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{ContractStateParams, IdentityKeyRef, Interval, Layer, NodeId};
pub use contracts_common::events::*;
use cosmwasm_std::{Addr, Coin, Decimal, Event};
pub const EVENT_VERSION_PREFIX: &str = "v2_";
pub enum MixnetEventType {
MixnodeBonding,
GatewayBonding,
GatewayUnbonding,
PendingMixnodeUnbonding,
MixnodeUnbonding,
MixnodeConfigUpdate,
PendingMixnodeCostParamsUpdate,
MixnodeCostParamsUpdate,
MixnodeRewarding,
WithdrawDelegatorReward,
WithdrawOperatorReward,
PendingActiveSetUpdate,
ActiveSetUpdate,
PendingIntervalRewardingParamsUpdate,
IntervalRewardingParamsUpdate,
PendingDelegation,
PendingUndelegation,
Delegation,
DelegationOnUnbonding,
Undelegation,
ContractSettingsUpdate,
RewardingValidatorUpdate,
AdvanceEpoch,
ExecutePendingEpochEvents,
ExecutePendingIntervalEvents,
ReconcilePendingEvents,
PendingIntervalConfigUpdate,
IntervalConfigUpdate,
}
impl From<MixnetEventType> for String {
fn from(typ: MixnetEventType) -> Self {
typ.to_string()
}
}
impl ToString for MixnetEventType {
fn to_string(&self) -> String {
let event_name = match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::GatewayBonding => "gateway_bonding",
MixnetEventType::GatewayUnbonding => "gateway_unbonding",
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
MixnetEventType::MixnodeConfigUpdate => "mixnode_config_update",
MixnetEventType::MixnodeUnbonding => "mixnode_unbonding",
MixnetEventType::PendingMixnodeCostParamsUpdate => "pending_mixnode_cost_params_update",
MixnetEventType::MixnodeCostParamsUpdate => "mixnode_cost_params_update",
MixnetEventType::MixnodeRewarding => "mix_rewarding",
MixnetEventType::WithdrawDelegatorReward => "withdraw_delegator_reward",
MixnetEventType::WithdrawOperatorReward => "withdraw_operator_reward",
MixnetEventType::PendingActiveSetUpdate => "pending_active_set_update",
MixnetEventType::ActiveSetUpdate => "active_set_update",
MixnetEventType::PendingIntervalRewardingParamsUpdate => {
"pending_interval_rewarding_params_update"
}
MixnetEventType::IntervalRewardingParamsUpdate => "interval_rewarding_params_update",
MixnetEventType::PendingDelegation => "pending_delegation",
MixnetEventType::PendingUndelegation => "pending_undelegation",
MixnetEventType::Delegation => "delegation",
MixnetEventType::Undelegation => "undelegation",
MixnetEventType::ContractSettingsUpdate => "settings_update",
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
MixnetEventType::AdvanceEpoch => "advance_epoch",
MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events",
MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events",
MixnetEventType::ReconcilePendingEvents => "reconcile_pending_events",
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
};
format!("{}{}", EVENT_VERSION_PREFIX, event_name)
}
}
// FIXME: This should becoma an Enum
// event types
pub const DELEGATION_EVENT_TYPE: &str = "delegation";
pub const PENDING_DELEGATION_EVENT_TYPE: &str = "pending_delegation";
pub const RECONCILE_DELEGATION_EVENT_TYPE: &str = "reconcile_delegation";
pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation";
pub const PENDING_UNDELEGATION_EVENT_TYPE: &str = "pending_undelegation";
pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding";
pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding";
pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding";
pub const MIXNODE_UNBONDING_EVENT_TYPE: &str = "mixnode_unbonding";
pub const SETTINGS_UPDATE_EVENT_TYPE: &str = "settings_update";
pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding";
pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding";
pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -99,44 +41,35 @@ pub const DELEGATION_TARGET_KEY: &str = "delegation_target";
pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height";
// bonding/unbonding
pub const MIX_ID_KEY: &str = "mix_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
// settings change
pub const OLD_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "old_minimum_mixnode_pledge";
pub const OLD_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "old_minimum_gateway_pledge";
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
pub const OLD_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "old_mixnode_rewarded_set_size";
pub const OLD_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "old_mixnode_active_set_size";
pub const OLD_ACTIVE_SET_WORK_FACTOR_KEY: &str = "old_active_set_work_factor";
pub const NEW_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "new_minimum_mixnode_pledge";
pub const NEW_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "new_minimum_gateway_pledge";
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
pub const UPDATED_MIXNODE_CONFIG_KEY: &str = "updated_mixnode_config";
pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
pub const NEW_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "new_mixnode_rewarded_set_size";
pub const NEW_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "new_mixnode_active_set_size";
// rewarding
pub const INTERVAL_KEY: &str = "interval_details";
pub const INTERVAL_ID_KEY: &str = "interval_id";
pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const TOTAL_PLEDGE_KEY: &str = "pledge";
pub const TOTAL_DELEGATIONS_KEY: &str = "delegated";
pub const OPERATOR_REWARD_KEY: &str = "operator_reward";
pub const DELEGATES_REWARD_KEY: &str = "delegates_reward";
pub const APPROXIMATE_TIME_LEFT_SECS_KEY: &str = "approximate_time_left_secs";
pub const INTERVAL_REWARDING_PARAMS_UPDATE_KEY: &str = "interval_rewarding_params_update";
pub const UPDATED_INTERVAL_REWARDING_PARAMS_KEY: &str = "updated_interval_rewarding_params";
pub const PRIOR_DELEGATES_KEY: &str = "prior_delegates";
pub const PRIOR_UNIT_DELEGATION_KEY: &str = "prior_unit_delegation";
pub const LAMBDA_KEY: &str = "lambda";
pub const SIGMA_KEY: &str = "sigma";
pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards";
pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations";
pub const NO_REWARD_REASON_KEY: &str = "no_reward_reason";
pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found";
pub const BOND_TOO_FRESH_VALUE: &str = "bond_too_fresh";
pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance";
pub const ZERO_UPTIME_VALUE: &str = "zero_uptime";
// rewarded set update
pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size";
@@ -147,140 +80,156 @@ pub const CURRENT_INTERVAL_ID_KEY: &str = "current_interval";
pub const NEW_CURRENT_INTERVAL_KEY: &str = "new_current_interval";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const BLOCK_HEIGHT_KEY: &str = "block_height";
pub const CHECKPOINT_MIXNODES_EVENT: &str = "checkpoint_mixnodes";
pub const RECONCILIATION_ERROR_EVENT: &str = "reconciliation_error";
// interval
pub const EVENTS_EXECUTED_KEY: &str = "number_of_events_executed";
pub const REWARDED_SET_NODES_KEY: &str = "rewarded_set_nodes";
pub const NEW_EPOCHS_DURATION_SECS_KEY: &str = "new_epoch_durations_secs";
pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
pub fn new_checkpoint_mixnodes_event(block_height: u64) -> Event {
Event::new(CHECKPOINT_MIXNODES_EVENT)
.add_attribute(BLOCK_HEIGHT_KEY, format!("{}", block_height))
}
pub fn new_error_event(err: String) -> Event {
Event::new(RECONCILIATION_ERROR_EVENT).add_attribute("error", err)
}
pub fn new_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
let mut event = Event::new(DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
pub fn new_delegation_on_unbonded_node_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::PendingDelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
let mut event =
Event::new(PENDING_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_withdraw_operator_reward_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::WithdrawOperatorReward)
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(
pub fn new_reconcile_delegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Coin,
mix_id: NodeId,
amount: &Coin,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::WithdrawDelegatorReward)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
let mut event =
Event::new(RECONCILE_DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_active_set_update_event(new_size: u32) -> Event {
Event::new(MixnetEventType::ActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(COMPOUND_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_active_set_update_event(
new_size: u32,
approximate_time_remaining_secs: i64,
pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::PendingActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
let mut event =
Event::new(COMPOUND_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
pub fn new_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
updated: IntervalRewardParams,
) -> Event {
Event::new(MixnetEventType::IntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated.to_inline_json(),
)
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_pending_rewarding_params_update_event(
update: IntervalRewardingParamsUpdate,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingIntervalRewardingParamsUpdate)
.add_attribute(
INTERVAL_REWARDING_PARAMS_UPDATE_KEY,
update.to_inline_json(),
)
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
}
pub fn new_undelegation_event(delegator: &Addr, proxy: &Option<Addr>, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::Undelegation)
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_claim_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_identity: IdentityKeyRef<'_>,
amount: Uint128,
) -> Event {
let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_pending_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
mix_id: NodeId,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
let mut event =
Event::new(PENDING_UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
}
pub fn new_gateway_bonding_event(
@@ -289,11 +238,16 @@ pub fn new_gateway_bonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayBonding)
let mut event = Event::new(GATEWAY_BONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_gateway_unbonding_event(
@@ -302,11 +256,16 @@ pub fn new_gateway_unbonding_event(
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayUnbonding)
let mut event = Event::new(GATEWAY_UNBONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_bonding_event(
@@ -314,92 +273,55 @@ pub fn new_mixnode_bonding_event(
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
assigned_layer: Layer,
) -> Event {
// coin implements Display trait and we use that implementation here
Event::new(MixnetEventType::MixnodeBonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
let mut event = Event::new(MIXNODE_BONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(NODE_IDENTITY_KEY, identity);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding).add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_mixnode_unbonding_event(
pub fn new_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
let mut event = Event::new(MIXNODE_UNBONDING_EVENT_TYPE)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
.add_attribute(NODE_IDENTITY_KEY, identity);
pub fn new_mixnode_config_update_event(
mix_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: NodeId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_mixnode_cost_params_update_event(
mix_id: NodeId,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_rewarding_validator_address_update_event(old: Addr, new: Addr) -> Event {
Event::new(MixnetEventType::RewardingValidatorUpdate)
.add_attribute(OLD_REWARDING_VALIDATOR_ADDRESS_KEY, old)
.add_attribute(NEW_REWARDING_VALIDATOR_ADDRESS_KEY, new)
// coin implements Display trait and we use that implementation here
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_settings_update_event(
old_params: &ContractStateParams,
new_params: &ContractStateParams,
) -> Event {
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
let mut event = Event::new(SETTINGS_UPDATE_EVENT_TYPE);
if old_params.minimum_mixnode_pledge != new_params.minimum_mixnode_pledge {
event = event
.add_attribute(
OLD_MINIMUM_MIXNODE_PLEDGE_KEY,
old_params.minimum_mixnode_pledge.to_string(),
old_params.minimum_mixnode_pledge,
)
.add_attribute(
NEW_MINIMUM_MIXNODE_PLEDGE_KEY,
new_params.minimum_mixnode_pledge.to_string(),
new_params.minimum_mixnode_pledge,
)
}
@@ -407,128 +329,128 @@ pub fn new_settings_update_event(
event = event
.add_attribute(
OLD_MINIMUM_GATEWAY_PLEDGE_KEY,
old_params.minimum_gateway_pledge.to_string(),
old_params.minimum_gateway_pledge,
)
.add_attribute(
NEW_MINIMUM_GATEWAY_PLEDGE_KEY,
new_params.minimum_gateway_pledge.to_string(),
new_params.minimum_gateway_pledge,
)
}
if old_params.minimum_mixnode_delegation != new_params.minimum_mixnode_delegation {
if let Some(ref old) = old_params.minimum_mixnode_delegation {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
} else {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
}
if let Some(ref new) = new_params.minimum_mixnode_delegation {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
} else {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
}
if old_params.mixnode_rewarded_set_size != new_params.mixnode_rewarded_set_size {
event = event
.add_attribute(
OLD_MIXNODE_REWARDED_SET_SIZE_KEY,
old_params.mixnode_rewarded_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_REWARDED_SET_SIZE_KEY,
new_params.mixnode_rewarded_set_size.to_string(),
)
}
if old_params.mixnode_active_set_size != new_params.mixnode_active_set_size {
event = event
.add_attribute(
OLD_MIXNODE_ACTIVE_SET_SIZE_KEY,
old_params.mixnode_active_set_size.to_string(),
)
.add_attribute(
NEW_MIXNODE_ACTIVE_SET_SIZE_KEY,
new_params.mixnode_active_set_size.to_string(),
)
}
event
}
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
pub fn new_not_found_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_rewarding_event(
interval: Interval,
mix_id: NodeId,
reward_distribution: RewardDistribution,
prior_delegates: Decimal,
prior_unit_delegation: Decimal,
pub fn new_too_fresh_bond_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
.add_attribute(PRIOR_UNIT_DELEGATION_KEY, prior_unit_delegation.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
)
.add_attribute(
DELEGATES_REWARD_KEY,
reward_distribution.delegates.to_string(),
)
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, BOND_TOO_FRESH_VALUE)
}
pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event {
Event::new(MixnetEventType::AdvanceEpoch)
.add_attribute(
NEW_CURRENT_EPOCH_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(REWARDED_SET_NODES_KEY, rewarded_nodes.to_string())
}
pub fn new_pending_epoch_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingEpochEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_pending_interval_events_execution_event(executed: u32) -> Event {
Event::new(MixnetEventType::ExecutePendingIntervalEvents)
.add_attribute(EVENTS_EXECUTED_KEY, executed.to_string())
}
pub fn new_reconcile_pending_events() -> Event {
Event::new(MixnetEventType::ReconcilePendingEvents)
}
pub fn new_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
updated_rewarding_params: IntervalRewardParams,
pub fn new_zero_uptime_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::IntervalConfigUpdate)
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(NO_REWARD_REASON_KEY, ZERO_UPTIME_VALUE)
}
#[allow(clippy::too_many_arguments)]
pub fn new_mix_operator_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
node_reward_result: NodeRewardResult,
node_pledge: Uint128,
node_delegation: Uint128,
) -> Event {
Event::new(OPERATOR_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(TOTAL_PLEDGE_KEY, node_pledge)
.add_attribute(TOTAL_DELEGATIONS_KEY, node_delegation)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
TOTAL_MIXNODE_REWARD_KEY,
node_reward_result.reward().to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string())
.add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string())
}
pub fn new_mix_delegators_rewarding_event(
interval_id: u32,
identity: IdentityKeyRef<'_>,
delegation_rewards_distributed: Uint128,
further_delegations: bool,
) -> Event {
Event::new(MIX_DELEGATORS_REWARDING_EVENT_TYPE)
.add_attribute(INTERVAL_ID_KEY, interval_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(
UPDATED_INTERVAL_REWARDING_PARAMS_KEY,
updated_rewarding_params.to_inline_json(),
DISTRIBUTED_DELEGATION_REWARDS_KEY,
delegation_rewards_distributed,
)
.add_attribute(
FURTHER_DELEGATIONS_TO_REWARD_KEY,
further_delegations.to_string(),
)
}
pub fn new_pending_interval_config_update_event(
epochs_in_interval: u32,
epoch_duration_secs: u64,
approximate_time_remaining_secs: i64,
// note that when this event is emitted, we'll know the current block height
pub fn new_change_rewarded_set_event(
active_set_size: u32,
rewarded_set_size: u32,
nodes_in_rewarded_set: u32,
) -> Event {
Event::new(MixnetEventType::PendingIntervalConfigUpdate)
.add_attribute(
NEW_EPOCHS_DURATION_SECS_KEY,
epoch_duration_secs.to_string(),
)
.add_attribute(NEW_EPOCHS_IN_INTERVAL, epochs_in_interval.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
)
Event::new(CHANGE_REWARDED_SET_EVENT_TYPE)
.add_attribute(ACTIVE_SET_SIZE_KEY, active_set_size.to_string())
.add_attribute(REWARDED_SET_SIZE_KEY, rewarded_set_size.to_string())
.add_attribute(NODES_IN_REWARDED_SET_KEY, nodes_in_rewarded_set.to_string())
}
pub fn new_advance_interval_event(interval: Interval) -> Event {
Event::new(ADVANCE_INTERVAL_EVENT_TYPE)
.add_attribute(NEW_CURRENT_INTERVAL_KEY, interval.to_string())
}
pub fn new_advance_epoch_event(interval: Interval) -> Event {
Event::new(ADVANCE_EPOCH_EVENT_TYPE).add_attribute(NEW_CURRENT_EPOCH_KEY, interval.to_string())
}
@@ -1,6 +1,3 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to code generated by JsonSchema
#![allow(clippy::field_reassign_with_default)]
@@ -1,13 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::pending_events::{PendingEpochEvent, PendingIntervalEvent};
use crate::{EpochId, IntervalId};
use cosmwasm_std::Env;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
use std::time::Duration;
use time::OffsetDateTime;
@@ -60,16 +59,12 @@ pub(crate) mod string_rfc3339_offset_date_time {
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize)]
pub struct Interval {
id: IntervalId,
epochs_in_interval: u32,
id: u32,
#[serde(with = "string_rfc3339_offset_date_time")]
current_epoch_start: OffsetDateTime,
current_epoch_id: EpochId,
epoch_length: Duration,
total_elapsed_epochs: EpochId,
start: OffsetDateTime,
length: Duration,
}
impl JsonSchema for Interval {
@@ -86,46 +81,20 @@ impl JsonSchema for Interval {
let object_validation = schema_object.object();
object_validation
.properties
.insert("id".to_owned(), gen.subschema_for::<IntervalId>());
.insert("id".to_owned(), gen.subschema_for::<u32>());
object_validation.required.insert("id".to_owned());
object_validation
.properties
.insert("epochs_in_interval".to_owned(), gen.subschema_for::<u32>());
object_validation
.required
.insert("epochs_in_interval".to_owned());
// PrimitiveDateTime does not implement JsonSchema. However it has a custom
// serialization to string, so we just specify the schema to be String.
object_validation.properties.insert(
"current_epoch_start".to_owned(),
gen.subschema_for::<String>(),
);
object_validation
.required
.insert("current_epoch_start".to_owned());
object_validation.properties.insert(
"current_epoch_id".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("current_epoch_id".to_owned());
.properties
.insert("start".to_owned(), gen.subschema_for::<String>());
object_validation.required.insert("start".to_owned());
object_validation
.properties
.insert("epoch_length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("epoch_length".to_owned());
object_validation.properties.insert(
"total_elapsed_epochs".to_owned(),
gen.subschema_for::<EpochId>(),
);
object_validation
.required
.insert("total_elapsed_epochs".to_owned());
.insert("length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("length".to_owned());
Schema::Object(schema_object)
}
@@ -133,451 +102,328 @@ impl JsonSchema for Interval {
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_interval(epochs_in_interval: u32, epoch_length: Duration, env: &Env) -> Self {
pub fn init_epoch(env: Env) -> Self {
Interval {
id: 0,
epochs_in_interval,
// I really don't see a way for this to fail, unless the blockchain is lying to us
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.expect("Invalid timestamp from env.block.time"),
current_epoch_id: 0,
epoch_length,
total_elapsed_epochs: 0,
start: OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64)
.expect("Invalid timestamp from env.block.time"),
length: Duration::from_secs(3600),
}
}
pub const fn current_epoch_id(&self) -> EpochId {
self.current_epoch_id
pub fn is_over(&self, env: Env) -> bool {
self.end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub const fn current_interval_id(&self) -> IntervalId {
pub fn in_progress(&self, env: Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.start_unix_timestamp() <= block_time && block_time < self.end_unix_timestamp()
}
pub fn update_duration(&mut self, secs: u64) {
self.length = Duration::from_secs(secs);
}
pub const fn length_secs(&self) -> u64 {
self.length.as_secs()
}
/// Returns the next interval.
#[must_use]
pub fn next(&self) -> Self {
Interval {
id: self.id + 1,
start: self.end(),
length: self.length,
}
}
pub fn next_on_chain(&self, env: Env) -> Self {
let start = self
.end()
.max(OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap());
Interval {
id: self.id + 1,
start,
length: self.length,
}
}
/// Returns the last interval.
pub fn previous(&self) -> Option<Self> {
if self.id > 0 {
Some(Interval {
id: self.id - 1,
start: self.start - self.length,
length: self.length,
})
} else {
None
}
}
/// Determines whether the provided datetime is contained within the interval
///
/// # Arguments
///
/// * `datetime`: specified datetime
pub fn contains(&self, datetime: OffsetDateTime) -> bool {
self.start <= datetime && datetime <= self.end()
}
/// Determines whether the provided unix timestamp is contained within the interval
///
/// # Arguments
///
/// * `timestamp`: specified timestamp
pub fn contains_timestamp(&self, timestamp: i64) -> bool {
self.start_unix_timestamp() <= timestamp && timestamp <= self.end_unix_timestamp()
}
/// Returns new instance of [Interval] such that the provided datetime would be within
/// its duration.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn current(&self, now: OffsetDateTime) -> Option<Self> {
let mut candidate = *self;
if now > self.start {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains(now) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Returns new instance of [Interval] such that the provided unix timestamp would be within
/// its duration.
///
/// # Arguments
///
/// * `now_unix`: current unix time
pub fn current_with_timestamp(&self, now_unix: i64) -> Option<Self> {
let mut candidate = *self;
if now_unix > self.start_unix_timestamp() {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.next();
}
} else {
loop {
if candidate.contains_timestamp(now_unix) {
return Some(candidate);
}
candidate = candidate.previous()?;
}
}
}
/// Checks whether this interval has already finished
///
/// # Arguments
///
/// * `now`: current datetime
pub fn has_elapsed(&self, now: OffsetDateTime) -> bool {
self.end() < now
}
/// Returns id of this interval
pub const fn id(&self) -> u32 {
self.id
}
pub const fn epochs_in_interval(&self) -> u32 {
self.epochs_in_interval
}
pub fn force_change_epochs_in_interval(&mut self, epochs_in_interval: u32) {
self.epochs_in_interval = epochs_in_interval;
if self.current_epoch_id >= epochs_in_interval {
// we have to go to the next interval as we can't
// have the same (interval, epoch) combo as we had in the past
self.id += self.current_epoch_id / epochs_in_interval;
self.current_epoch_id %= epochs_in_interval;
}
}
pub fn change_epoch_length(&mut self, epoch_length: Duration) {
self.epoch_length = epoch_length
}
pub const fn total_elapsed_epochs(&self) -> u32 {
self.total_elapsed_epochs
}
pub const fn current_epoch_absolute_id(&self) -> u32 {
// since we count epochs starting from 0, if n epochs have elapsed, the current one has absolute id of n
self.total_elapsed_epochs
}
#[inline]
pub fn is_current_epoch_over(&self, env: &Env) -> bool {
self.current_epoch_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_epoch_end(&self, env: &Env) -> i64 {
if self.is_current_epoch_over(env) {
0
/// Determines amount of time left until this interval finishes.
///
/// # Arguments
///
/// * `now`: current datetime
pub fn until_end(&self, now: OffsetDateTime) -> Option<Duration> {
let remaining = self.end() - now;
if remaining.is_negative() {
None
} else {
self.current_epoch_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
#[inline]
pub fn is_current_interval_over(&self, env: &Env) -> bool {
self.current_interval_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn secs_until_current_interval_end(&self, env: &Env) -> i64 {
if self.is_current_interval_over(env) {
0
} else {
self.current_interval_end_unix_timestamp() - env.block.time.seconds() as i64
}
}
pub fn current_epoch_in_progress(&self, env: &Env) -> bool {
let block_time = env.block.time.seconds() as i64;
self.current_epoch_start_unix_timestamp() <= block_time
&& block_time < self.current_epoch_end_unix_timestamp()
}
pub fn update_epoch_duration(&mut self, secs: u64) {
self.epoch_length = Duration::from_secs(secs);
}
pub const fn epoch_length_secs(&self) -> u64 {
self.epoch_length.as_secs()
}
/// Returns the next epoch. If if would result in advancing the interval,
/// the relevant changes are applied.
#[must_use]
pub fn advance_epoch(&self) -> Self {
// remember we start from 0th epoch, so if we're supposed to have 100 epochs in interval,
// epoch 99 is going to be the last one
if self.current_epoch_id == self.epochs_in_interval - 1 {
Interval {
id: self.id + 1,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: 0,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
} else {
Interval {
id: self.id,
epochs_in_interval: self.epochs_in_interval,
current_epoch_start: self.current_epoch_end(),
current_epoch_id: self.current_epoch_id + 1,
epoch_length: self.epoch_length,
total_elapsed_epochs: self.total_elapsed_epochs + 1,
}
remaining.try_into().ok()
}
}
/// Returns the starting datetime of this interval.
pub const fn current_epoch_start(&self) -> OffsetDateTime {
self.current_epoch_start
pub const fn start(&self) -> OffsetDateTime {
self.start
}
/// Returns the length of this interval.
pub const fn epoch_length(&self) -> Duration {
self.epoch_length
pub const fn length(&self) -> Duration {
self.length
}
/// Returns the ending datetime of the current epoch.
pub fn current_epoch_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epoch_length
/// Returns the ending datetime of this interval.
pub fn end(&self) -> OffsetDateTime {
self.start + self.length
}
pub fn epochs_until_interval_end(&self) -> u32 {
self.epochs_in_interval - self.current_epoch_id
/// Returns the unix timestamp of the start of this interval.
pub const fn start_unix_timestamp(&self) -> i64 {
self.start().unix_timestamp()
}
/// Returns the ending datetime of the current interval.
pub fn current_interval_end(&self) -> OffsetDateTime {
self.current_epoch_start + self.epochs_until_interval_end() * self.epoch_length
}
/// Returns the unix timestamp of the start of the current epoch.
pub const fn current_epoch_start_unix_timestamp(&self) -> i64 {
self.current_epoch_start().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current epoch.
#[inline]
pub fn current_epoch_end_unix_timestamp(&self) -> i64 {
self.current_epoch_end().unix_timestamp()
}
/// Returns the unix timestamp of the end of the current interval.
#[inline]
pub fn current_interval_end_unix_timestamp(&self) -> i64 {
self.current_interval_end().unix_timestamp()
/// Returns the unix timestamp of the end of this interval.
pub fn end_unix_timestamp(&self) -> i64 {
self.end().unix_timestamp()
}
}
impl Display for Interval {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let length = self.epoch_length_secs();
let length = self.length().as_secs();
let full_hours = length / 3600;
let rem = length % 3600;
write!(
f,
"Interval {}: epoch {}/{} (current epoch begun at: {}; epoch lengths: {}h {}s)",
"Interval {}: {} - {} ({}h {}s)",
self.id,
self.current_epoch_id + 1,
self.epochs_in_interval,
self.current_epoch_start,
self.start(),
self.end(),
full_hours,
rem
)
}
}
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct CurrentIntervalResponse {
pub interval: Interval,
pub current_blocktime: u64,
pub is_current_interval_over: bool,
pub is_current_epoch_over: bool,
}
impl CurrentIntervalResponse {
pub fn new(interval: Interval, env: Env) -> Self {
CurrentIntervalResponse {
interval,
current_blocktime: env.block.time.seconds(),
is_current_interval_over: interval.is_current_interval_over(&env),
is_current_epoch_over: interval.is_current_epoch_over(&env),
}
}
pub fn time_until_current_epoch_end(&self) -> Duration {
if self.is_current_epoch_over {
Duration::from_secs(0)
} else {
let remaining_secs =
self.interval.current_epoch_end_unix_timestamp() - self.current_blocktime as i64;
// this should never be negative, but better safe than sorry and guard ourselves against that case
if remaining_secs <= 0 {
Duration::from_secs(0)
} else {
Duration::from_secs(remaining_secs as u64)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingEpochEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingEpochEvent>,
pub start_next_after: Option<u32>,
}
impl PendingEpochEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingEpochEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingEpochEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct PendingIntervalEventsResponse {
pub seconds_until_executable: i64,
pub events: Vec<PendingIntervalEvent>,
pub start_next_after: Option<u32>,
}
impl PendingIntervalEventsResponse {
pub fn new(
seconds_until_executable: i64,
events: Vec<PendingIntervalEvent>,
start_next_after: Option<u32>,
) -> Self {
PendingIntervalEventsResponse {
seconds_until_executable,
events,
start_next_after,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_env;
use rand_chacha::rand_core::{RngCore, SeedableRng};
#[test]
fn advancing_epoch() {
// just advancing epoch
fn previous() {
let interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
id: 1,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
let expected = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 24,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 1,
start: time::macros::datetime!(2021-08-22 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.advance_epoch());
assert_eq!(expected, interval.previous().unwrap());
// results in advancing interval
let genesis_interval = Interval {
id: 0,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(genesis_interval.previous().is_none());
}
#[test]
fn next() {
let interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 42,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
let expected = Interval {
id: 1,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 13:00 UTC),
current_epoch_id: 0,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 43,
start: time::macros::datetime!(2021-08-24 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert_eq!(expected, interval.advance_epoch())
assert_eq!(expected, interval.next())
}
#[test]
fn checking_for_epoch_ends() {
let env = mock_env();
// epoch just begun
fn checking_for_datetime_inclusion() {
let interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64 - 100,
)
.unwrap(),
current_epoch_id: 23,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
assert!(!interval.is_current_epoch_over(&env));
// current time == current epoch start
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap();
assert!(!interval.is_current_epoch_over(&env));
// it must contain its own boundaries
assert!(interval.contains(interval.start));
assert!(interval.contains(interval.end()));
// epoch HASN'T yet begun (weird edge case, but can happen if we decide to manually adjust things)
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64 + 100).unwrap();
assert!(!interval.is_current_epoch_over(&env));
let in_the_midle = interval.start + Duration::from_secs(interval.length.as_secs() / 2);
assert!(interval.contains(in_the_midle));
// current_time = EXACTLY end of the epoch
let mut interval = interval;
interval.current_epoch_start =
OffsetDateTime::from_unix_timestamp(env.block.time.seconds() as i64).unwrap()
- interval.epoch_length;
assert!(interval.is_current_epoch_over(&env));
// revert time a bit more
interval.current_epoch_start -= Duration::from_secs(42);
assert!(interval.is_current_epoch_over(&env));
// revert by A LOT -> epoch still should be in finished state
interval.current_epoch_start -= Duration::from_secs(5 * 31 * 60 * 60);
assert!(interval.is_current_epoch_over(&env));
assert!(!interval.contains(interval.next().end()));
assert!(!interval.contains(interval.previous().unwrap().start()));
}
#[test]
fn interval_end() {
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: time::macros::datetime!(2021-08-23 12:00 UTC),
current_epoch_id: 99,
epoch_length: Duration::from_secs(60 * 60),
total_elapsed_epochs: 0,
fn determining_current_interval() {
let first_interval = Interval {
id: 100,
start: time::macros::datetime!(2021-08-23 12:00 UTC),
length: Duration::from_secs(24 * 60 * 60),
};
// interval just before
let fake_now = first_interval.start - Duration::from_secs(123);
assert_eq!(first_interval.previous(), first_interval.current(fake_now));
// this interval (start boundary)
assert_eq!(
interval.current_epoch_start + interval.epoch_length,
interval.current_interval_end()
first_interval,
first_interval.current(first_interval.start).unwrap()
);
interval.current_epoch_id -= 1;
// this interval (in the middle)
let fake_now = first_interval.start + Duration::from_secs(123);
assert_eq!(first_interval, first_interval.current(fake_now).unwrap());
// this interval (end boundary)
assert_eq!(
interval.current_epoch_start + 2 * interval.epoch_length,
interval.current_interval_end()
first_interval,
first_interval.current(first_interval.end()).unwrap()
);
interval.current_epoch_id -= 10;
// next interval
let fake_now = first_interval.end() + Duration::from_secs(123);
assert_eq!(
interval.current_epoch_start + 12 * interval.epoch_length,
interval.current_interval_end()
first_interval.next(),
first_interval.current(fake_now).unwrap()
);
interval.current_epoch_id = 0;
// few intervals in the past
let fake_now = first_interval.start()
- first_interval.length
- first_interval.length
- first_interval.length;
assert_eq!(
interval.current_epoch_start + interval.epochs_in_interval * interval.epoch_length,
interval.current_interval_end()
first_interval
.previous()
.unwrap()
.previous()
.unwrap()
.previous()
.unwrap(),
first_interval.current(fake_now).unwrap()
);
}
#[test]
fn checking_for_interval_ends() {
let env = mock_env();
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval {
id: 0,
epochs_in_interval: 100,
current_epoch_start: OffsetDateTime::from_unix_timestamp(
env.block.time.seconds() as i64
)
.unwrap(),
current_epoch_id: 98,
epoch_length,
total_elapsed_epochs: 0,
};
// current epoch just started (we still have to finish 2 epochs)
assert!(!interval.is_current_interval_over(&env));
// still need to finish the 99th epoch
interval.current_epoch_start -= epoch_length;
assert!(!interval.is_current_interval_over(&env));
// it JUST finished
interval.current_epoch_start -= epoch_length;
assert!(interval.is_current_interval_over(&env));
// nobody updated the interval data, but the current one should still be in finished state
interval.current_epoch_start -= 10 * epoch_length;
assert!(interval.is_current_interval_over(&env));
}
#[test]
fn getting_current_full_epoch_id() {
let env = mock_env();
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let epoch_length = Duration::from_secs(60 * 60);
let mut interval = Interval::init_interval(100, epoch_length, &env);
// normal situation
for i in 0u32..2000 {
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
let mut interval = Interval::init_interval(100, epoch_length, &env);
for i in 0u32..2000 {
// every few epochs decide to change epochs in interval
if i % 7 == 0 {
let new_epochs_in_interval = (rng.next_u32() % 200) + 42;
interval.force_change_epochs_in_interval(new_epochs_in_interval)
}
// make sure full epoch id is always monotonically increasing
assert_eq!(interval.current_epoch_absolute_id(), i);
interval = interval.advance_epoch();
}
// few intervals in the future
let fake_now = first_interval.end()
+ first_interval.length
+ first_interval.length
+ first_interval.length;
assert_eq!(
first_interval.next().next().next(),
first_interval.current(fake_now).unwrap()
);
}
}
@@ -1,7 +1,6 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod constants;
pub mod delegation;
pub mod error;
pub mod events;
@@ -9,31 +8,29 @@ pub mod gateway;
mod interval;
pub mod mixnode;
mod msg;
pub mod pending_events;
pub mod reward_params;
pub mod rewarding;
mod types;
pub use contracts_common::types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
pub const MIXNODE_DELEGATORS_PAGE_LIMIT: usize = 250;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixNodeDelegationsResponse,
PagedMixDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
};
pub use interval::{
CurrentIntervalResponse, Interval, PendingEpochEventsResponse, PendingIntervalEventsResponse,
};
pub use interval::Interval;
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsResponse, PagedMixnodeBondsResponse,
RewardedSetNodeStatus, UnbondedMixnode,
Layer, MixNode, MixNodeBond, MixOwnershipResponse, MixnodeBondResponse, PagedMixnodeResponse,
RewardedSetNodeStatus,
};
pub use msg::*;
pub use pending_events::{
PendingEpochEvent, PendingEpochEventData, PendingIntervalEvent, PendingIntervalEventData,
};
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
pub use types::*;
pub type U128 = fixed::types::U75F53;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
}
File diff suppressed because it is too large Load Diff
@@ -1,342 +1,160 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::delegation::OwnerProxySubKey;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
};
use crate::{delegation, ContractStateParams, NodeId, Percent};
use crate::reward_params::NodeRewardParams;
use crate::ContractStateParams;
use crate::{Gateway, IdentityKey, MixNode};
use cosmwasm_std::Decimal;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InstantiateMsg {
pub rewarding_validator_address: String,
pub vesting_contract_address: String,
pub rewarding_denom: String,
pub epochs_in_interval: u32,
pub epoch_duration: Duration,
pub initial_rewarding_params: InitialRewardingParams,
pub mixnet_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct InitialRewardingParams {
pub initial_reward_pool: Decimal,
pub initial_staking_supply: Decimal,
pub sybil_resistance: Percent,
pub active_set_work_factor: Decimal,
pub interval_pool_emission: Percent,
pub rewarded_set_size: u32,
pub active_set_size: u32,
}
impl InitialRewardingParams {
pub fn into_rewarding_params(self, epochs_in_interval: u32) -> RewardingParams {
let epoch_reward_budget = self.initial_reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
RewardingParams {
interval: IntervalRewardParams {
reward_pool: self.initial_reward_pool,
staking_supply: self.initial_staking_supply,
epoch_reward_budget,
stake_saturation_point,
sybil_resistance: self.sybil_resistance,
active_set_work_factor: self.active_set_work_factor,
interval_pool_emission: self.interval_pool_emission,
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// state/sys-params-related
UpdateRewardingValidatorAddress {
address: String,
},
UpdateContractStateParams {
updated_parameters: ContractStateParams,
InitEpoch {},
ReconcileDelegations {},
CheckpointMixnodes {},
CompoundOperatorRewardOnBehalf {
owner: String,
},
UpdateActiveSetSize {
active_set_size: u32,
force_immediately: bool,
CompoundDelegatorRewardOnBehalf {
owner: String,
mix_identity: IdentityKey,
},
UpdateRewardingParams {
updated_params: IntervalRewardingParamsUpdate,
force_immediately: bool,
CompoundOperatorReward {},
CompoundDelegatorReward {
mix_identity: IdentityKey,
},
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
force_immediately: bool,
CompoundReward {
operator: Option<String>,
delegator: Option<String>,
mix_identity: Option<IdentityKey>,
proxy: Option<String>,
},
AdvanceCurrentEpoch {
new_rewarded_set: Vec<NodeId>,
expected_active_set_size: u32,
},
ReconcileEpochEvents {
limit: Option<u32>,
},
// mixnode-related:
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: String,
owner: String,
},
UnbondMixnode {},
UnbondMixnodeOnBehalf {
owner: String,
},
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
},
UpdateMixnodeCostParamsOnBehalf {
new_costs: MixNodeCostParams,
owner: String,
},
UpdateMixnodeConfig {
new_config: MixNodeConfigUpdate,
profit_margin_percent: u8,
},
UpdateMixnodeConfigOnBehalf {
new_config: MixNodeConfigUpdate,
profit_margin_percent: u8,
owner: String,
},
// gateway-related:
BondGateway {
gateway: Gateway,
owner_signature: String,
},
UnbondGateway {},
UpdateContractStateParams(ContractStateParams),
DelegateToMixnode {
mix_identity: IdentityKey,
},
UndelegateFromMixnode {
mix_identity: IdentityKey,
},
RewardMixnode {
identity: IdentityKey,
// percentage value in range 0-100
params: NodeRewardParams,
},
// RewardNextMixDelegators {
// mix_identity: IdentityKey,
// // id of the current rewarding interval
// interval_id: u32,
// },
DelegateToMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
UndelegateFromMixnodeOnBehalf {
mix_identity: IdentityKey,
delegate: String,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
owner: String,
owner_signature: String,
},
UnbondMixnodeOnBehalf {
owner: String,
},
BondGatewayOnBehalf {
gateway: Gateway,
owner: String,
owner_signature: String,
},
UnbondGateway {},
UnbondGatewayOnBehalf {
owner: String,
},
// delegation-related:
DelegateToMixnode {
mix_id: NodeId,
WriteRewardedSet {
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
},
DelegateToMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
UndelegateFromMixnode {
mix_id: NodeId,
},
UndelegateFromMixnodeOnBehalf {
mix_id: NodeId,
delegate: String,
},
// reward-related
RewardMixnode {
mix_id: NodeId,
performance: Performance,
},
WithdrawOperatorReward {},
WithdrawOperatorRewardOnBehalf {
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
ClaimOperatorReward {},
ClaimOperatorRewardOnBehalf {
owner: String,
},
WithdrawDelegatorReward {
mix_id: NodeId,
ClaimDelegatorReward {
mix_identity: IdentityKey,
},
WithdrawDelegatorRewardOnBehalf {
mix_id: NodeId,
ClaimDelegatorRewardOnBehalf {
mix_identity: IdentityKey,
owner: String,
},
}
impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
format!("updating rewarding validator to {}", address)
}
ExecuteMsg::UpdateContractStateParams { .. } => {
"updating mixnet state parameters".into()
}
ExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
} => format!(
"updating active set size to {}. forced: {}",
active_set_size, force_immediately
),
ExecuteMsg::UpdateRewardingParams {
force_immediately, ..
} => format!(
"updating mixnet rewarding parameters. forced: {}",
force_immediately
),
ExecuteMsg::UpdateIntervalConfig {
force_immediately, ..
} => format!(
"updating mixnet interval configuration. forced: {}",
force_immediately
),
ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(),
ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(),
ExecuteMsg::BondMixnode { mix_node, .. } => {
format!("bonding mixnode {}", mix_node.identity_key)
}
ExecuteMsg::BondMixnodeOnBehalf { mix_node, .. } => {
format!("bonding mixnode {} on behalf", mix_node.identity_key)
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } => {
"updating mixnode cost parameters on behalf".into()
}
ExecuteMsg::UpdateMixnodeConfig { .. } => "updating mixnode configuration".into(),
ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. } => {
"updating mixnode configuration on behalf".into()
}
ExecuteMsg::BondGateway { gateway, .. } => {
format!("bonding gateway {}", gateway.identity_key)
}
ExecuteMsg::BondGatewayOnBehalf { gateway, .. } => {
format!("bonding gateway {} on behalf", gateway.identity_key)
}
ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(),
ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(),
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {}", mix_id),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {} on behalf", mix_id)
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
format!("removing delegation from mixnode {}", mix_id)
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, .. } => {
format!("removing delegation from mixnode {} on behalf", mix_id)
}
ExecuteMsg::RewardMixnode {
mix_id,
performance,
} => format!(
"rewarding mixnode {} for performance {}",
mix_id, performance
),
ExecuteMsg::WithdrawOperatorReward { .. } => "withdrawing operator reward".into(),
ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => {
"withdrawing operator reward on behalf".into()
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
format!("withdrawing delegator reward from mixnode {}", mix_id)
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => format!(
"withdrawing delegator reward from mixnode {} on behalf",
mix_id
),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
// state/sys-params-related
GetContractVersion {},
GetBlacklistedNodes {},
GetCurrentOperatorCost {},
GetRewardingValidatorAddress {},
GetStateParams {},
GetState {},
GetRewardingParams {},
GetCurrentIntervalDetails {},
GetRewardedSet {
GetAllDelegationKeys {},
DebugGetAllDelegationValues {},
GetContractVersion {},
GetMixNodes {
limit: Option<u32>,
start_after: Option<NodeId>,
start_after: Option<IdentityKey>,
},
// mixnode-related:
GetMixNodeBonds {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetMixNodesDetailed {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodes {
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByOwner {
owner: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetUnbondedMixNodesByIdentityKey {
identity_key: String,
limit: Option<u32>,
start_after: Option<NodeId>,
},
GetOwnedMixnode {
address: String,
},
GetMixnodeDetails {
mix_id: NodeId,
},
GetMixnodeRewardingDetails {
mix_id: NodeId,
},
GetStakeSaturation {
mix_id: NodeId,
},
GetUnbondedMixNodeInformation {
mix_id: NodeId,
},
GetBondedMixnodeDetailsByIdentity {
mix_identity: IdentityKey,
},
GetLayerDistribution {},
// gateway-related:
GetGateways {
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
OwnsMixnode {
address: String,
},
OwnsGateway {
address: String,
},
GetMixnodeBond {
identity: IdentityKey,
},
GetGatewayBond {
identity: IdentityKey,
},
GetOwnedGateway {
address: String,
},
// delegation-related:
StateParams {},
// gets all [paged] delegations associated with particular mixnode
GetMixnodeDelegations {
mix_id: NodeId,
mix_identity: IdentityKey,
// since `start_after` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
start_after: Option<String>,
start_after: Option<(String, u64)>,
limit: Option<u32>,
},
// gets all [paged] delegations associated with particular delegator
@@ -344,56 +162,81 @@ pub enum QueryMsg {
// since `delegator` is user-provided input, we can't use `Addr` as we
// can't guarantee it's validated.
delegator: String,
start_after: Option<(NodeId, OwnerProxySubKey)>,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
// gets delegation associated with particular mixnode, delegator pair
GetDelegationDetails {
mix_id: NodeId,
mix_identity: IdentityKey,
delegator: String,
proxy: Option<String>,
},
// gets all delegations in the system
GetAllDelegations {
start_after: Option<delegation::StorageKey>,
LayerDistribution {},
GetRewardPool {},
GetCirculatingSupply {},
GetStakingSupply {},
GetIntervalRewardPercent {},
GetSybilResistancePercent {},
GetActiveSetWorkFactor {},
GetRewardingStatus {
mix_identity: IdentityKey,
interval_id: u32,
},
GetRewardedSet {
height: Option<u64>,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
// rewards related
GetPendingOperatorReward {
GetRewardedSetUpdateDetails {},
GetCurrentRewardedSetHeight {},
GetRewardedSetRefreshBlocks {},
GetCurrentEpoch {},
GetEpochsInInterval {},
QueryOperatorReward {
address: String,
},
GetPendingMixNodeOperatorReward {
mix_id: NodeId,
},
GetPendingDelegatorReward {
QueryDelegatorReward {
address: String,
mix_id: NodeId,
mix_identity: IdentityKey,
proxy: Option<String>,
},
// given the provided performance, estimate the reward at the end of the current epoch
GetEstimatedCurrentEpochOperatorReward {
mix_id: NodeId,
estimated_performance: Performance,
GetPendingDelegationEvents {
owner_address: String,
proxy_address: Option<String>,
},
GetEstimatedCurrentEpochDelegatorReward {
address: String,
mix_id: NodeId,
proxy: Option<String>,
estimated_performance: Performance,
GetCheckpointsForMixnode {
mix_identity: IdentityKey,
},
// interval-related
GetPendingEpochEvents {
limit: Option<u32>,
start_after: Option<u32>,
},
GetPendingIntervalEvents {
limit: Option<u32>,
start_after: Option<u32>,
GetMixnodeAtHeight {
mix_identity: IdentityKey,
height: u64,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct MigrateMsg {}
pub struct MigrateMsg {
pub nodes_to_remove: Option<Vec<NodeToRemove>>,
}
impl MigrateMsg {
pub fn nodes_to_remove(&self) -> Vec<NodeToRemove> {
self.nodes_to_remove.clone().unwrap_or_default()
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct NodeToRemove {
owner: String,
proxy: Option<String>,
}
impl NodeToRemove {
pub fn owner(&self) -> &str {
&self.owner
}
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
}
@@ -1,77 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::MixNodeCostParams;
use crate::reward_params::IntervalRewardingParamsUpdate;
use crate::{EpochEventId, IntervalEventId, NodeId};
use cosmwasm_std::{Addr, Coin};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingEpochEvent {
pub id: EpochEventId,
pub event: PendingEpochEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingEpochEventData {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
Delegate {
owner: Addr,
mix_id: NodeId,
amount: Coin,
proxy: Option<Addr>,
},
Undelegate {
owner: Addr,
mix_id: NodeId,
proxy: Option<Addr>,
},
UnbondMixnode {
mix_id: NodeId,
},
UpdateActiveSetSize {
new_size: u32,
},
}
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
fn from(data: (EpochEventId, PendingEpochEventData)) -> Self {
PendingEpochEvent {
id: data.0,
event: data.1,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct PendingIntervalEvent {
pub id: EpochEventId,
pub event: PendingIntervalEventData,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub enum PendingIntervalEventData {
ChangeMixCostParams {
mix_id: NodeId,
new_costs: MixNodeCostParams,
},
UpdateRewardingParams {
update: IntervalRewardingParamsUpdate,
},
UpdateIntervalConfig {
epochs_in_interval: u32,
epoch_duration_secs: u64,
},
}
impl From<(IntervalEventId, PendingIntervalEventData)> for PendingIntervalEvent {
fn from(data: (IntervalEventId, PendingIntervalEventData)) -> Self {
PendingIntervalEvent {
id: data.0,
event: data.1,
}
}
}
@@ -1,264 +1,288 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{error::MixnetContractError, Percent};
use cosmwasm_std::Decimal;
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub type Performance = Percent;
/// Parameters required by the mix-mining reward distribution that do not change during an interval.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct IntervalRewardParams {
/// Current value of the rewarding pool.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub reward_pool: Decimal,
/// Current value of the staking supply.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub staking_supply: Decimal,
// computed values
/// Current value of the computed reward budget per epoch, per node.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub epoch_reward_budget: Decimal,
/// Current value of the stake saturation point.
/// It is expected to be constant throughout the interval.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub stake_saturation_point: Decimal,
// constants(-ish)
// default: 30%
/// Current value of the sybil resistance percent (`alpha`).
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub sybil_resistance: Percent,
// default: 10
/// Current active set work factor.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub active_set_work_factor: Decimal,
// default: 2%
/// Current maximum interval pool emission.
/// Assuming all nodes in the rewarded set are fully saturated and have 100% performance,
/// this % of the reward pool would get distributed in rewards to all operators and its delegators.
/// It is not really expected to be changing very often.
/// As a matter of fact, unless there's a very specific reason, it should remain constant.
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub interval_pool_emission: Percent,
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
result: StoredNodeRewardResult,
epoch_id: u32,
}
impl IntervalRewardParams {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
impl NodeEpochRewards {
pub fn new(params: NodeRewardParams, result: StoredNodeRewardResult, epoch_id: u32) -> Self {
Self {
params,
result,
epoch_id,
}
}
pub fn epoch_id(&self) -> u32 {
self.epoch_id
}
pub fn sigma(&self) -> U128 {
self.result.sigma()
}
pub fn lambda(&self) -> U128 {
self.result.lambda()
}
pub fn params(&self) -> NodeRewardParams {
self.params
}
pub fn reward(&self) -> Uint128 {
self.result.reward()
}
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.params.operator_cost(base_operator_cost)
}
pub fn node_profit(&self, base_operator_cost: u64) -> U128 {
let reward = U128::from_num(self.reward().u128());
// if operating cost is higher then the reward node profit is 0
reward.saturating_sub(self.operator_cost(base_operator_cost))
}
pub fn operator_reward(
&self,
profit_margin: U128,
base_operator_cost: u64,
) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit(base_operator_cost);
let operator_base_reward = reward.min(self.operator_cost(base_operator_cost));
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
base_operator_cost: u64,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let staking_supply = U128::from_num(epoch_reward_params.staking_supply());
let scaled_delegation_amount = delegation_amount / staking_supply;
let check_div_by_zero =
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let delegator_reward =
(ONE - profit_margin) * check_div_by_zero * self.node_profit(base_operator_cost);
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardingParams.ts")
)]
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
pub struct RewardingParams {
/// Parameters that should remain unchanged throughout an interval.
pub interval: IntervalRewardParams,
// while the active set size can change between epochs to accommodate for bandwidth demands,
// the active set size should be unchanged between epochs and should only be adjusted between
// intervals. However, it makes more sense to keep both of those values together as they're
// very strongly related to each other.
pub rewarded_set_size: u32,
pub active_set_size: u32,
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct EpochRewardParams {
epoch_reward_pool: Uint128,
rewarded_set_size: Uint128,
active_set_size: Uint128,
#[serde(alias = "circulating_supply")]
staking_supply: Uint128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
}
impl RewardingParams {
pub fn active_node_work(&self) -> Decimal {
self.interval.active_set_work_factor * self.standby_node_work()
impl EpochRewardParams {
pub fn new(
epoch_reward_pool: u128,
rewarded_set_size: u128,
active_set_size: u128,
staking_supply: u128,
sybil_resistance_percent: u8,
active_set_work_factor: u8,
) -> EpochRewardParams {
EpochRewardParams {
epoch_reward_pool: Uint128::new(epoch_reward_pool),
rewarded_set_size: Uint128::new(rewarded_set_size),
active_set_size: Uint128::new(active_set_size),
staking_supply: Uint128::new(staking_supply),
sybil_resistance_percent,
active_set_work_factor,
}
}
pub fn standby_node_work(&self) -> Decimal {
let f = self.interval.active_set_work_factor;
let k = self.dec_rewarded_set_size();
let one = Decimal::one();
// nodes in reserve
let k_r = self.dec_standby_set_size();
one / (f * k - (f - one) * k_r)
// technically it's identical to what would have been derived with a Default implementation,
// however, I prefer to be explicit about it, as a `Default::default` value makes no sense
// apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway
// (it's guarded behind an `initialised` flag)
pub fn new_empty() -> Self {
EpochRewardParams {
epoch_reward_pool: Uint128::new(0),
staking_supply: Uint128::new(0),
sybil_resistance_percent: 0,
rewarded_set_size: Uint128::new(0),
active_set_size: Uint128::new(0),
active_set_work_factor: 0,
}
}
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size, 0).unwrap()
pub fn rewarded_set_size(&self) -> u128 {
self.rewarded_set_size.u128()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.active_set_size, 0).unwrap()
pub fn active_set_size(&self) -> u128 {
self.active_set_size.u128()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
Decimal::from_atomics(self.rewarded_set_size - self.active_set_size, 0).unwrap()
pub fn staking_supply(&self) -> u128 {
self.staking_supply.u128()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(new_epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch_reward_pool.u128()
}
pub fn try_change_active_set_size(
&mut self,
new_active_set_size: u32,
) -> Result<(), MixnetContractError> {
if new_active_set_size == 0 {
return Err(MixnetContractError::ZeroActiveSet);
}
if new_active_set_size > self.rewarded_set_size {
return Err(MixnetContractError::InvalidActiveSetSize);
}
self.active_set_size = new_active_set_size;
Ok(())
pub fn sybil_resistance_percent(&self) -> u8 {
self.sybil_resistance_percent
}
pub fn try_apply_updates(
&mut self,
updates: IntervalRewardingParamsUpdate,
epochs_in_interval: u32,
) -> Result<(), MixnetContractError> {
if !updates.contains_updates() {
return Err(MixnetContractError::EmptyParamsChangeMsg);
}
let mut recompute_epoch_budget = false;
let mut recompute_saturation_point = false;
if let Some(reward_pool) = updates.reward_pool {
recompute_epoch_budget = true;
self.interval.reward_pool = reward_pool;
}
if let Some(staking_supply) = updates.staking_supply {
recompute_saturation_point = true;
self.interval.staking_supply = staking_supply;
}
if let Some(sybil_resistance_percent) = updates.sybil_resistance_percent {
self.interval.sybil_resistance = sybil_resistance_percent;
}
if let Some(active_set_work_factor) = updates.active_set_work_factor {
self.interval.active_set_work_factor = active_set_work_factor;
}
if let Some(interval_pool_emission) = updates.interval_pool_emission {
recompute_epoch_budget = true;
self.interval.interval_pool_emission = interval_pool_emission;
}
if let Some(rewarded_set_size) = updates.rewarded_set_size {
if rewarded_set_size == 0 {
return Err(MixnetContractError::ZeroRewardedSet);
}
if rewarded_set_size < self.active_set_size {
return Err(MixnetContractError::InvalidRewardedSetSize);
}
recompute_saturation_point = true;
self.rewarded_set_size = rewarded_set_size;
}
if recompute_epoch_budget {
self.interval.epoch_reward_budget = self.interval.reward_pool
/ Decimal::from_atomics(epochs_in_interval, 0).unwrap()
* self.interval.interval_pool_emission;
}
if recompute_saturation_point {
self.interval.stake_saturation_point = self.interval.staking_supply
/ Decimal::from_atomics(self.rewarded_set_size, 0).unwrap();
}
Ok(())
pub fn active_set_work_factor(&self) -> u8 {
self.active_set_work_factor
}
}
// TODO: possibly refactor this
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema)]
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct NodeRewardParams {
pub performance: Percent,
pub in_active_set: bool,
reward_blockstamp: u64,
uptime: Uint128,
in_active_set: bool,
}
impl NodeRewardParams {
pub fn new(performance: Percent, in_active_set: bool) -> Self {
pub fn new(reward_blockstamp: u64, uptime: u128, in_active_set: bool) -> NodeRewardParams {
NodeRewardParams {
performance,
reward_blockstamp,
uptime: Uint128::new(uptime),
in_active_set,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/IntervalRewardingParamsUpdate.ts")
)]
#[derive(
Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, PartialOrd, Serialize, JsonSchema,
)]
pub struct IntervalRewardingParamsUpdate {
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub reward_pool: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub staking_supply: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub sybil_resistance_percent: Option<Percent>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub active_set_work_factor: Option<Decimal>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
pub interval_pool_emission: Option<Percent>,
pub rewarded_set_size: Option<u32>,
}
impl IntervalRewardingParamsUpdate {
pub fn contains_updates(&self) -> bool {
// essentially at least a single field has to be a `Some`
self.reward_pool.is_some()
|| self.staking_supply.is_some()
|| self.sybil_resistance_percent.is_some()
|| self.active_set_work_factor.is_some()
|| self.interval_pool_emission.is_some()
|| self.rewarded_set_size.is_some()
pub fn operator_cost(&self, base_operator_cost: u64) -> U128 {
self.performance() * U128::from_num(base_operator_cost)
}
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
pub fn uptime(&self) -> Uint128 {
self.uptime
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.reward_blockstamp = blockstamp;
}
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Eq, Serialize, Deserialize, Copy)]
pub struct RewardParams {
pub epoch: EpochRewardParams,
pub node: NodeRewardParams,
}
impl RewardParams {
pub fn new(epoch: EpochRewardParams, node: NodeRewardParams) -> RewardParams {
RewardParams { epoch, node }
}
pub fn omega(&self) -> U128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if denom == 0 {
return U128::ZERO;
}
// Div by zero checked above
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
}
pub fn idle_nodes(&self) -> Uint128 {
self.epoch.rewarded_set_size - self.epoch.active_set_size
}
pub fn active_set_work_factor(&self) -> U128 {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
pub fn performance(&self) -> U128 {
self.node.performance()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
pub fn epoch_reward_pool(&self) -> u128 {
self.epoch.epoch_reward_pool.u128()
}
pub fn rewarded_set_size(&self) -> u128 {
self.epoch.rewarded_set_size.u128()
}
pub fn staking_supply(&self) -> u128 {
self.epoch.staking_supply.u128()
}
pub fn reward_blockstamp(&self) -> u64 {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> Uint128 {
self.node.uptime()
}
pub fn one_over_k(&self) -> U128 {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
}
}

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