Compare commits

..

1 Commits

Author SHA1 Message Date
Mark Sinclair 4ca8cdcf5a Testing CI 2021-11-24 18:01:32 +00:00
517 changed files with 45858 additions and 59514 deletions
@@ -0,0 +1,58 @@
name: ERC20 Bridge Contract
on: [ push, pull_request ]
jobs:
matrix_prep:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
# creates the matrix strategy from build_matrix_includes.json
- uses: actions/checkout@v2
- id: set-matrix
uses: JoshuaTheMiller/conditional-build-matrix@main
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
erc20-bridge-contract:
needs: matrix_prep
strategy:
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.rust == 'nightly' }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
target: wasm32-unknown-unknown
override: true
components: rustfmt, clippy
- uses: actions-rs/cargo@v1
env:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/erc20-bridge/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/erc20-bridge/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/erc20-bridge/Cargo.toml -- -D warnings
@@ -1,4 +1,4 @@
name: Contracts
name: Mixnet Contract
on:
push:
@@ -21,7 +21,7 @@ jobs:
with:
inputFile: '.github/workflows/contract_matrix_includes.json'
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
contracts:
mixnet-contract:
# since it's going to be compiled into wasm, there's absolutely
# no point in running CI on different OS-es
runs-on: ubuntu-latest
@@ -45,20 +45,20 @@ jobs:
RUSTFLAGS: '-C link-arg=-s'
with:
command: build
args: --manifest-path contracts/Cargo.toml --all --target wasm32-unknown-unknown
args: --manifest-path contracts/mixnet/Cargo.toml --target wasm32-unknown-unknown
- uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path contracts/Cargo.toml
args: --manifest-path contracts/mixnet/Cargo.toml
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path contracts/Cargo.toml --all -- --check
args: --manifest-path contracts/mixnet/Cargo.toml -- --check
- uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path contracts/Cargo.toml --all -- -D warnings
args: --manifest-path contracts/mixnet/Cargo.toml -- -D warnings
-2
View File
@@ -22,8 +22,6 @@ jobs:
node-version: '14'
- run: npm install
continue-on-error: true
- name: Set environment from the example
run: cp .env.prod .env
- run: npm run test
continue-on-error: true
- run: npm run build
@@ -1,4 +1,4 @@
name: Publish Nym Wallet
name: Publish Tauri Wallet
on:
push:
tags:
-3
View File
@@ -1,3 +0,0 @@
unreleased=true
future-release=v0.12.0
since-tag=v0.11.0
+1 -2
View File
@@ -34,5 +34,4 @@ contracts/mixnet/code_id
contracts/mixnet/Justfile
contracts/mixnet/Makefile
validator-config
*.patch
validator-api-config.toml
*.patch
-1
View File
@@ -1 +0,0 @@
2.7.5
+879 -228
View File
File diff suppressed because it is too large Load Diff
Generated
+33 -148
View File
@@ -240,14 +240,6 @@ version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
[[package]]
name = "bandwidth-claim-contract"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
]
[[package]]
name = "base-x"
version = "0.2.8"
@@ -620,7 +612,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "0.12.0"
version = "0.11.0"
dependencies = [
"config",
"crypto",
@@ -876,7 +868,8 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.8.0"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb5204c6ddc4352c74297638b5561f2929d6334866c156e5f3c75e1e1a1436a"
dependencies = [
"prost",
"prost-types",
@@ -886,7 +879,8 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.3.0"
source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d31147fe89e547e74e2692e4bec35387e1ea406fe8cfb14c0ea177b58fd2a8a9"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -907,9 +901,8 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
@@ -920,18 +913,16 @@ dependencies = [
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "866713b2fe13f23038c7d8824c3059d1f28dd94685fb406d1533c4eeeefeefae"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dbb9939b31441dfa9af3ec9740c8a24d585688401eff1b6b386abb7ad0d10a8"
version = "0.14.1"
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -1092,7 +1083,6 @@ dependencies = [
"blake3",
"bs58",
"cipher",
"config",
"digest 0.9.0",
"ed25519-dalek",
"generic-array 0.14.4",
@@ -1102,7 +1092,6 @@ dependencies = [
"nymsphinx-types",
"pemstore",
"rand 0.7.3",
"subtle-encoding",
"x25519-dalek",
]
@@ -1218,17 +1207,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "cw-storage-plus"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b8b840947313c1a1cccf056836cd79a60b4526bdcd6582995be37dc97be4ae"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "darling"
version = "0.10.2"
@@ -1624,26 +1602,6 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "enum-iterator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enum_kind"
version = "0.2.1"
@@ -1669,6 +1627,14 @@ dependencies = [
"termcolor",
]
[[package]]
name = "erc20-bridge-contract"
version = "0.1.0"
dependencies = [
"schemars",
"serde",
]
[[package]]
name = "error-chain"
version = "0.12.4"
@@ -2307,19 +2273,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "git2"
version = "0.13.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6"
dependencies = [
"bitflags",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "glib"
version = "0.14.5"
@@ -3092,18 +3045,6 @@ version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "libgit2-sys"
version = "0.12.26+1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libm"
version = "0.2.1"
@@ -3121,18 +3062,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "lioness"
version = "0.1.2"
@@ -3573,7 +3502,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "0.12.1"
version = "0.11.0"
dependencies = [
"clap",
"client-core",
@@ -3600,14 +3529,13 @@ dependencies = [
"topology",
"url",
"validator-client",
"vergen",
"version-checker",
"websocket-requests",
]
[[package]]
name = "nym-client-wasm"
version = "0.12.0"
version = "0.11.0"
dependencies = [
"coconut-interface",
"console_error_panic_hook",
@@ -3631,20 +3559,18 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "0.12.1"
version = "0.11.0"
dependencies = [
"bandwidth-claim-contract",
"bip39",
"bs58",
"clap",
"coconut-interface",
"colored",
"config",
"credentials",
"crypto",
"dashmap",
"dirs",
"dotenv",
"erc20-bridge-contract",
"futures",
"gateway-client",
"gateway-requests",
@@ -3659,7 +3585,6 @@ dependencies = [
"rand 0.7.3",
"serde",
"sqlx",
"subtle-encoding",
"thiserror",
"tokio",
"tokio-stream",
@@ -3667,14 +3592,13 @@ dependencies = [
"tokio-util",
"url",
"validator-client",
"vergen",
"version-checker",
"web3",
]
[[package]]
name = "nym-mixnode"
version = "0.12.1"
version = "0.11.0"
dependencies = [
"bs58",
"clap",
@@ -3702,13 +3626,12 @@ dependencies = [
"topology",
"url",
"validator-client",
"vergen",
"version-checker",
]
[[package]]
name = "nym-network-requester"
version = "0.12.0"
version = "0.11.0"
dependencies = [
"clap",
"dirs",
@@ -3729,7 +3652,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "0.12.1"
version = "0.11.0"
dependencies = [
"clap",
"client-core",
@@ -3758,13 +3681,12 @@ dependencies = [
"topology",
"url",
"validator-client",
"vergen",
"version-checker",
]
[[package]]
name = "nym-validator-api"
version = "0.12.0"
version = "0.11.0"
dependencies = [
"anyhow",
"clap",
@@ -3799,7 +3721,6 @@ dependencies = [
"topology",
"url",
"validator-client",
"vergen",
"version-checker",
]
@@ -3973,11 +3894,10 @@ dependencies = [
[[package]]
name = "okapi"
version = "0.7.0-rc.1"
version = "0.6.0-alpha-1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce66b6366e049880a35c378123fddb630b1a1a3c37fa1ca70caaf4a09f6e2893"
checksum = "bb085e00daf8d75b9dbf0ffdb4738e69503e28898d9641fa8bdc6ad536c7bcf4"
dependencies = [
"log",
"schemars",
"serde",
"serde_json",
@@ -5214,12 +5134,10 @@ dependencies = [
[[package]]
name = "rocket_okapi"
version = "0.8.0-rc.1"
version = "0.7.0-alpha-1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
checksum = "8b2f4f48fb070f9f6c56d5663df5fa8a514406207744f4abd84661bfb24efd7d"
dependencies = [
"either",
"log",
"okapi",
"rocket",
"rocket_okapi_codegen",
@@ -5230,9 +5148,9 @@ dependencies = [
[[package]]
name = "rocket_okapi_codegen"
version = "0.8.0-rc.1"
version = "0.7.0-alpha-1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
checksum = "88ccf1550e1c806461a6b08e2ab64eb10701d41bf50bde59ab9aa3a57ab14d41"
dependencies = [
"darling 0.13.0",
"proc-macro2",
@@ -7211,7 +7129,6 @@ dependencies = [
"thiserror",
"ts-rs",
"url",
"vesting-contract",
]
[[package]]
@@ -7226,23 +7143,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "vergen"
version = "5.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e"
dependencies = [
"anyhow",
"cfg-if 1.0.0",
"chrono",
"enum-iterator",
"getset",
"git2",
"rustc_version 0.4.0",
"rustversion",
"thiserror",
]
[[package]]
name = "version-checker"
version = "0.1.0"
@@ -7268,21 +7168,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "vesting-contract"
version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus",
"getrandom 0.2.3",
"mixnet-contract",
"rand 0.8.4",
"schemars",
"serde",
"thiserror",
]
[[package]]
name = "waker-fn"
version = "1.1.0"
+1 -2
View File
@@ -25,7 +25,7 @@ members = [
"common/config",
"common/credentials",
"common/crypto",
"common/bandwidth-claim-contract",
"common/erc20-bridge-contract",
"common/mixnet-contract",
"common/mixnode-common",
"common/network-defaults",
@@ -62,7 +62,6 @@ default-members = [
"service-providers/network-requester",
"mixnode",
"validator-api",
"explorer-api",
]
exclude = ["explorer", "contracts", "tokenomics-py"]
-45
View File
@@ -1,45 +0,0 @@
all: clippy-all test fmt
happy: clippy-happy test fmt
clippy-all: clippy-all-main clippy-all-contracts clippy-all-wallet
clippy-happy: clippy-happy-main clippy-happy-contracts clippy-happy-wallet
test: test-main test-contracts test-wallet
fmt: fmt-main fmt-contracts fmt-wallet
clippy-happy-main:
cargo clippy
clippy-happy-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
clippy-happy-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml
clippy-all-main:
cargo clippy --all-features -- -D warnings
clippy-all-contracts:
cargo clippy --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
clippy-all-wallet:
cargo clippy --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
test-main:
cargo test --all-features
test-contracts:
cargo test --manifest-path contracts/Cargo.toml --all-features
test-wallet:
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
fmt-main:
cargo fmt --all
fmt-contracts:
cargo fmt --manifest-path contracts/Cargo.toml --all
fmt-wallet:
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
wasm:
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
+6 -6
View File
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* nym-gateway - acts sort of like a mailbox for mixnet messages, removing the need for directly delivery to potentially offline or firewalled devices.
* nym-network-monitor - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
* nym-explorer - a (projected) block explorer and (existing) mixnet viewer.
* nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* nym-wallet (currently in development)- a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0)
[![Build Status](https://img.shields.io/github/workflow/status/nymtech/nym/Continuous%20integration/develop?style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop)
@@ -40,13 +40,13 @@ Node, node operator and delegator rewards are determined according to the princi
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has pledged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `reward set` size, and set to 720 in testnet Sandbox.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has plaged to their node to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\omega_{i}">|fraction of total effort undertaken by node `i`, set to `1/k` in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=k">|number of nodes stakeholders are incentivised to create, set by the validators, a matter of governance. Currently determined by the `active set` size, and set to 5000 in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=\alpha">|Sybil attack resistance parameter - the higher this parameter is set the stronger the reduction in competitivness gets for a Sybil attacker.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PM_{i}">|declared profit margin of operator `i`, defaults to 10% in testnet Milhon.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 NYMT.
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}">|cost of operating node `i` for the duration of the rewarding eopoch, set to 40 Nym for testnet Milhon.
Node reward for node `i` is determined as:
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "0.12.0"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
@@ -13,6 +13,7 @@ use nymsphinx::utils::sample_poisson_duration;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::pin::Pin;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
use tokio::time;
@@ -164,8 +165,8 @@ impl LoopCoverTrafficStream<OsRng> {
}
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
self.run().await;
})
}
@@ -6,6 +6,7 @@ use futures::StreamExt;
use gateway_client::GatewayClient;
use log::*;
use nymsphinx::forwarding::packet::MixPacket;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
pub type BatchMixMessageSender = mpsc::UnboundedSender<Vec<MixPacket>>;
@@ -71,8 +72,8 @@ impl MixTrafficController {
}
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
self.run().await;
})
}
@@ -22,6 +22,7 @@ use nymsphinx::addressing::clients::Recipient;
use rand::{rngs::OsRng, CryptoRng, Rng};
use std::sync::Arc;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
mod acknowledgement_control;
@@ -169,8 +170,10 @@ impl RealMessagesController<OsRng> {
self.ack_control = Some(ack_control_fut.await.unwrap());
}
pub fn start(mut self) -> JoinHandle<Self> {
tokio::spawn(async move {
// &Handle is only passed for consistency sake with other client modules, but I think
// when we get to refactoring, we should apply gateway approach and make it implicit
pub fn start(mut self, handle: &Handle) -> JoinHandle<Self> {
handle.spawn(async move {
self.run().await;
self
})
@@ -15,6 +15,7 @@ use nymsphinx::params::{ReplySurbEncryptionAlgorithm, ReplySurbKeyDigestAlgorith
use nymsphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::runtime::Handle;
use tokio::task::JoinHandle;
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
@@ -290,8 +291,8 @@ impl RequestReceiver {
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
while let Some(request) = self.query_receiver.next().await {
match request {
ReceivedBufferMessage::ReceiverAnnounce(sender) => {
@@ -321,8 +322,8 @@ impl FragmentedMessageReceiver {
mixnet_packet_receiver,
}
}
fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
while let Some(new_messages) = self.mixnet_packet_receiver.next().await {
self.received_buffer.handle_new_received(new_messages).await;
}
@@ -354,9 +355,9 @@ impl ReceivedMessagesBufferController {
}
}
pub fn start(self) {
pub fn start(self, handle: &Handle) {
// TODO: should we do anything with JoinHandle(s) returned by start methods?
self.fragmented_message_receiver.start();
self.request_receiver.start();
self.fragmented_message_receiver.start(handle);
self.request_receiver.start(handle);
}
}
@@ -10,6 +10,7 @@ use std::ops::Deref;
use std::sync::Arc;
use std::time;
use std::time::Duration;
use tokio::runtime::Handle;
use tokio::sync::{RwLock, RwLockReadGuard};
use tokio::task::JoinHandle;
use topology::{nym_topology_from_bonds, NymTopology};
@@ -303,8 +304,8 @@ impl TopologyRefresher {
self.topology_accessor.is_routable().await
}
pub fn start(mut self) -> JoinHandle<()> {
tokio::spawn(async move {
pub fn start(mut self, handle: &Handle) -> JoinHandle<()> {
handle.spawn(async move {
loop {
tokio::time::sleep(self.refresh_rate).await;
self.refresh().await;
-14
View File
@@ -117,10 +117,6 @@ impl<T: NymConfig> Config<T> {
self.client.id = id;
}
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
self.client.testnet_mode = testnet_mode;
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
self.client.gateway_id = id.into();
}
@@ -157,10 +153,6 @@ impl<T: NymConfig> Config<T> {
self.client.id.clone()
}
pub fn get_testnet_mode(&self) -> bool {
self.client.testnet_mode
}
pub fn get_nym_root_directory(&self) -> PathBuf {
self.client.nym_root_directory.clone()
}
@@ -281,11 +273,6 @@ pub struct Client<T> {
/// ID specifies the human readable ID of this particular client.
id: String,
/// Indicates whether this client is running in a testnet mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
#[serde(default)]
testnet_mode: bool,
/// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>,
@@ -348,7 +335,6 @@ impl<T: NymConfig> Default for Client<T> {
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
testnet_mode: false,
validator_api_urls: default_api_endpoints(),
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
+1 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "0.12.1"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2018"
rust-version = "1.56"
@@ -48,10 +48,6 @@ network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
eth = []
[dev-dependencies]
serde_json = "1.0" # for the "textsend" example
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
@@ -3,5 +3,6 @@ module github.com/nymtech/nym/clients/native/examples/go
go 1.14
require (
github.com/btcsuite/btcutil v1.0.2 // indirect
github.com/gorilla/websocket v1.4.2
)
+1 -5
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
// Note: any changes to the template must be reflected in the appropriate structs in verloc.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
+63 -49
View File
@@ -32,6 +32,7 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use nymsphinx::anonymous_replies::ReplySurb;
use nymsphinx::receiver::ReconstructedMessage;
use tokio::runtime::Runtime;
use crate::client::config::{Config, SocketType};
use crate::websocket;
@@ -43,6 +44,11 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
@@ -62,6 +68,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
input_tx: None,
@@ -87,6 +94,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -99,7 +109,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start();
.start(self.runtime.handle());
}
fn start_real_traffic_controller(
@@ -121,6 +131,10 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -130,7 +144,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start();
.start(self.runtime.handle());
}
// buffer controlling all messages fetched from provider
@@ -148,10 +162,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start()
.start(self.runtime.handle())
}
async fn start_gateway_client(
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -168,44 +182,43 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
gateway_client
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -216,10 +229,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -227,7 +243,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start();
topology_refresher.start(self.runtime.handle());
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -240,7 +256,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
}
fn start_websocket_listener(
@@ -253,7 +269,8 @@ impl NymClient {
let websocket_handler =
websocket::Handler::new(msg_input, buffer_requester, self.as_mix_recipient());
websocket::Listener::new(self.config.get_listening_port()).start(websocket_handler);
websocket::Listener::new(self.config.get_listening_port())
.start(self.runtime.handle(), websocket_handler);
}
/// EXPERIMENTAL DIRECT RUST API
@@ -300,9 +317,9 @@ impl NymClient {
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -314,7 +331,7 @@ impl NymClient {
);
}
pub async fn start(&mut self) {
pub fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -346,17 +363,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+26 -36
View File
@@ -31,12 +31,6 @@ use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
@@ -72,28 +66,18 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("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 --testnet-mode instead")
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
.required(true));
app
}
@@ -219,7 +203,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -237,7 +221,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
config = override_config(config, matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -250,20 +234,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+2 -26
View File
@@ -5,18 +5,6 @@ use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
use url::Url;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[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;
pub(crate) mod upgrade;
@@ -56,24 +44,12 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config
+9 -19
View File
@@ -4,9 +4,6 @@
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -42,22 +39,15 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket (if applicable) to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("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 --testnet-mode instead")
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
@@ -82,7 +72,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -93,12 +83,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever();
}
+8 -41
View File
@@ -1,21 +1,19 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use clap::{App, ArgMatches};
pub mod client;
pub mod commands;
pub mod websocket;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
let arg_matches = App::new("Nym Client")
.version(crate_version!())
.long_version(&*long_version())
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.about("Implementation of the Nym Client")
.subcommand(commands::init::command_args())
@@ -23,13 +21,13 @@ async fn main() {
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
execute(arg_matches);
}
async fn execute(matches: ArgMatches<'static>) {
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
@@ -52,38 +50,7 @@ fn banner() -> String {
(client - version {:})
"#,
crate_version!()
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
env!("CARGO_PKG_VERSION")
)
}
+3 -2
View File
@@ -5,6 +5,7 @@ use super::handler::Handler;
use log::*;
use std::{net::SocketAddr, process, sync::Arc};
use tokio::io::AsyncWriteExt;
use tokio::runtime;
use tokio::{sync::Notify, task::JoinHandle};
enum State {
@@ -86,9 +87,9 @@ impl Listener {
}
}
pub(crate) fn start(mut self, handler: Handler) -> JoinHandle<()> {
pub(crate) fn start(mut self, rt_handle: &runtime::Handle, handler: Handler) -> JoinHandle<()> {
info!("Running websocket on {:?}", self.address.to_string());
tokio::spawn(async move { self.run(handler).await })
rt_handle.spawn(async move { self.run(handler).await })
}
}
+1 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "0.12.1"
version = "0.11.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2018"
rust-version = "1.56"
@@ -43,7 +43,3 @@ network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut"]
eth = []
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
-8
View File
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
fn main() {
vergen(Config::default()).expect("failed to extract build metadata")
}
+1 -5
View File
@@ -5,7 +5,7 @@ pub(crate) fn config_template() -> &'static str {
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
// Note: any changes to the template must be reflected in the appropriate structs in verloc.
r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
@@ -19,10 +19,6 @@ version = '{{ client.version }}'
# Human readable ID of this particular client.
id = '{{ client.id }}'
# Indicates whether this client is running in a testnet mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
{{#each client.validator_api_urls }}
+63 -49
View File
@@ -28,6 +28,7 @@ use gateway_client::{
use log::*;
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use tokio::runtime::Runtime;
use crate::client::config::Config;
use crate::socks::{
@@ -42,6 +43,11 @@ pub struct NymClient {
/// key filepaths, etc.
config: Config,
/// Tokio runtime used for futures execution.
// TODO: JS: Personally I think I prefer the implicit way of using it that we've done with the
// gateway.
runtime: Runtime,
/// KeyManager object containing smart pointers to all relevant keys used by the client.
key_manager: KeyManager,
}
@@ -52,6 +58,7 @@ impl NymClient {
let key_manager = KeyManager::load_keys(&pathfinder).expect("failed to load stored keys");
NymClient {
runtime: Runtime::new().unwrap(),
config,
key_manager,
}
@@ -75,6 +82,9 @@ impl NymClient {
mix_tx: BatchMixMessageSender,
) {
info!("Starting loop cover traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor which HAS TO be called within context of a tokio runtime
let _guard = self.runtime.enter();
LoopCoverTrafficStream::new(
self.key_manager.ack_key(),
@@ -87,7 +97,7 @@ impl NymClient {
self.as_mix_recipient(),
topology_accessor,
)
.start();
.start(self.runtime.handle());
}
fn start_real_traffic_controller(
@@ -109,6 +119,10 @@ impl NymClient {
);
info!("Starting real traffic stream...");
// we need to explicitly enter runtime due to "next_delay: time::delay_for(Default::default())"
// set in the constructor [of OutQueueControl] which HAS TO be called within context of a tokio runtime
// When refactoring this restriction should definitely be removed.
let _guard = self.runtime.enter();
RealMessagesController::new(
controller_config,
@@ -118,7 +132,7 @@ impl NymClient {
topology_accessor,
reply_key_storage,
)
.start();
.start(self.runtime.handle());
}
// buffer controlling all messages fetched from provider
@@ -136,10 +150,10 @@ impl NymClient {
mixnet_receiver,
reply_key_storage,
)
.start()
.start(self.runtime.handle())
}
async fn start_gateway_client(
fn start_gateway_client(
&mut self,
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
@@ -156,44 +170,43 @@ impl NymClient {
let gateway_identity = identity::PublicKey::from_base58_string(gateway_id)
.expect("provided gateway id is invalid!");
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
self.runtime.block_on(async {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
self.config.get_base().get_eth_endpoint(),
self.config.get_base().get_eth_private_key(),
self.config.get_base().get_backup_bandwidth_token_keys_dir(),
)
.expect("Could not create bandwidth controller");
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
let mut gateway_client = GatewayClient::new(
gateway_address,
self.key_manager.identity_keypair(),
gateway_identity,
Some(self.key_manager.gateway_shared_key()),
mixnet_message_sender,
ack_sender,
self.config.get_base().get_gateway_response_timeout(),
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
.authenticate_and_start()
.await
.expect("could not authenticate and start up the gateway connection");
gateway_client
gateway_client
})
}
// future responsible for periodically polling directory server and updating
// the current global view of topology
async fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
fn start_topology_refresher(&mut self, topology_accessor: TopologyAccessor) {
let topology_refresher_config = TopologyRefresherConfig::new(
self.config.get_base().get_validator_api_endpoints(),
self.config.get_base().get_topology_refresh_rate(),
@@ -204,10 +217,13 @@ impl NymClient {
// before returning, block entire runtime to refresh the current network view so that any
// components depending on topology would see a non-empty view
info!("Obtaining initial network topology");
topology_refresher.refresh().await;
self.runtime.block_on(topology_refresher.refresh());
// TODO: a slightly more graceful termination here
if !topology_refresher.is_topology_routable().await {
if !self
.runtime
.block_on(topology_refresher.is_topology_routable())
{
panic!(
"The current network topology seem to be insufficient to route any packets through\
- check if enough nodes and a gateway are online"
@@ -215,7 +231,7 @@ impl NymClient {
}
info!("Starting topology refresher...");
topology_refresher.start();
topology_refresher.start(self.runtime.handle());
}
// controller for sending sphinx packets to mixnet (either real traffic or cover traffic)
@@ -228,7 +244,7 @@ impl NymClient {
gateway_client: GatewayClient,
) {
info!("Starting mix traffic controller...");
MixTrafficController::new(mix_rx, gateway_client).start();
MixTrafficController::new(mix_rx, gateway_client).start(self.runtime.handle());
}
fn start_socks5_listener(
@@ -247,13 +263,14 @@ impl NymClient {
self.config.get_provider_mix_address(),
self.as_mix_recipient(),
);
tokio::spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
self.runtime
.spawn(async move { sphinx_socks.serve(msg_input, buffer_requester).await });
}
/// blocking version of `start` method. Will run forever (or until SIGINT is sent)
pub async fn run_forever(&mut self) {
self.start().await;
if let Err(e) = tokio::signal::ctrl_c().await {
pub fn run_forever(&mut self) {
self.start();
if let Err(e) = self.runtime.block_on(tokio::signal::ctrl_c()) {
error!(
"There was an error while capturing SIGINT - {:?}. We will terminate regardless",
e
@@ -265,7 +282,7 @@ impl NymClient {
);
}
pub async fn start(&mut self) {
pub fn start(&mut self) {
info!("Starting nym client");
// channels for inter-component communication
// TODO: make the channels be internally created by the relevant components
@@ -297,17 +314,14 @@ impl NymClient {
// the components are started in very specific order. Unless you know what you are doing,
// do not change that.
self.start_topology_refresher(shared_topology_accessor.clone())
.await;
self.start_topology_refresher(shared_topology_accessor.clone());
self.start_received_messages_buffer_controller(
received_buffer_request_receiver,
mixnet_messages_receiver,
reply_key_storage.clone(),
);
let gateway_client = self
.start_gateway_client(mixnet_messages_sender, ack_sender)
.await;
let gateway_client = self.start_gateway_client(mixnet_messages_sender, ack_sender);
self.start_mix_traffic_controller(sphinx_message_receiver, gateway_client);
self.start_real_traffic_controller(
+26 -36
View File
@@ -29,12 +29,6 @@ use url::Url;
use crate::client::config::Config;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
TESTNET_MODE_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
let app = App::new("init")
@@ -72,28 +66,18 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.hidden(true) // this will prevent this flag from being displayed in `--help`
.help("Mostly debug-related option to increase default traffic rate so that you would not need to modify config post init")
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("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 --testnet-mode instead")
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.required(true)
);
.required(true));
app
}
@@ -219,7 +203,7 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
@@ -238,7 +222,7 @@ pub async fn execute(matches: ArgMatches<'static>) {
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
config = override_config(config, matches);
if matches.is_present("fastmode") {
config.get_base_mut().set_high_default_traffic_volume();
}
@@ -251,20 +235,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gateway_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
let registration_fut = async {
let gate_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
config
.get_base_mut()
.with_gateway_id(gate_details.identity_key.to_base58_string());
let shared_keys =
register_with_gateway(&gate_details, key_manager.identity_keypair()).await;
(shared_keys, gate_details.clients_address())
};
// TODO: is there perhaps a way to make it work without having to spawn entire runtime?
let rt = tokio::runtime::Runtime::new().unwrap();
let (shared_keys, gateway_listener) = rt.block_on(registration_fut);
config
.get_base_mut()
.with_gateway_listener(gateway_details.clients_address());
.with_gateway_listener(gateway_listener);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
+2 -26
View File
@@ -9,18 +9,6 @@ pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_PRIVATE_KEY_ARG_NAME: &str = "eth_private_key";
#[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 parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
@@ -52,24 +40,12 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches) -> Confi
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_endpoint) = matches.value_of(ETH_ENDPOINT_ARG_NAME) {
if let Some(eth_endpoint) = matches.value_of("eth_endpoint") {
config.get_base_mut().with_eth_endpoint(eth_endpoint);
} else {
config
.get_base_mut()
.with_eth_endpoint(DEFAULT_ETH_ENDPOINT);
}
#[cfg(not(feature = "coconut"))]
if let Some(eth_private_key) = matches.value_of(ETH_PRIVATE_KEY_ARG_NAME) {
if let Some(eth_private_key) = matches.value_of("eth_private_key") {
config.get_base_mut().with_eth_private_key(eth_private_key);
} else {
config
.get_base_mut()
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
}
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
config.get_base_mut().with_testnet_mode(true)
}
config
+9 -19
View File
@@ -4,9 +4,6 @@
use crate::client::config::Config;
use crate::client::NymClient;
use crate::commands::override_config;
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -48,22 +45,15 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Port for the socket to listen on")
.takes_value(true)
);
#[cfg(feature = "eth")]
#[cfg(not(feature = "coconut"))]
let app = app
.arg(
Arg::with_name(TESTNET_MODE_ARG_NAME)
.long(TESTNET_MODE_ARG_NAME)
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
)
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
.long(ETH_ENDPOINT_ARG_NAME)
.help("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 --testnet-mode instead")
.arg(Arg::with_name("eth_endpoint")
.long("eth_endpoint")
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens")
.takes_value(true))
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
.long(ETH_PRIVATE_KEY_ARG_NAME)
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
.arg(Arg::with_name("eth_private_key")
.long("eth_private_key")
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens")
.takes_value(true));
app
@@ -88,7 +78,7 @@ fn version_check(cfg: &Config) -> bool {
}
}
pub async fn execute(matches: ArgMatches<'static>) {
pub fn execute(matches: &ArgMatches) {
let id = matches.value_of("id").unwrap();
let mut config = match Config::load_from_file(Some(id)) {
@@ -99,12 +89,12 @@ pub async fn execute(matches: ArgMatches<'static>) {
}
};
config = override_config(config, &matches);
config = override_config(config, matches);
if !version_check(&config) {
error!("failed the local version check");
return;
}
NymClient::new(config).run_forever().await;
NymClient::new(config).run_forever();
}
+7 -40
View File
@@ -1,14 +1,13 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use clap::{App, ArgMatches};
pub mod client;
mod commands;
pub mod socks;
#[tokio::main]
async fn main() {
fn main() {
dotenv::dotenv().ok();
setup_logging();
println!("{}", banner());
@@ -16,20 +15,19 @@ async fn main() {
let arg_matches = App::new("Nym Socks5 Proxy")
.version(env!("CARGO_PKG_VERSION"))
.author("Nymtech")
.long_version(&*long_version())
.about("A Socks5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address")
.subcommand(commands::init::command_args())
.subcommand(commands::run::command_args())
.subcommand(commands::upgrade::command_args())
.get_matches();
execute(arg_matches).await;
execute(arg_matches);
}
async fn execute(matches: ArgMatches<'static>) {
fn execute(matches: ArgMatches) {
match matches.subcommand() {
("init", Some(m)) => commands::init::execute(m.clone()).await,
("run", Some(m)) => commands::run::execute(m.clone()).await,
("init", Some(m)) => commands::init::execute(m),
("run", Some(m)) => commands::run::execute(m),
("upgrade", Some(m)) => commands::upgrade::execute(m),
_ => println!("{}", usage()),
}
@@ -52,38 +50,7 @@ fn banner() -> String {
(socks5 proxy - version {:})
"#,
crate_version!()
)
}
fn long_version() -> String {
format!(
r#"
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
"Build Version:",
env!("VERGEN_BUILD_SEMVER"),
"Commit SHA:",
env!("VERGEN_GIT_SHA"),
"Commit Date:",
env!("VERGEN_GIT_COMMIT_TIMESTAMP"),
"Commit Branch:",
env!("VERGEN_GIT_BRANCH"),
"rustc Version:",
env!("VERGEN_RUSTC_SEMVER"),
"rustc Channel:",
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
env!("CARGO_PKG_VERSION")
)
}
-6
View File
@@ -1,6 +0,0 @@
NYMD_URL=https://sandbox-validator.nymtech.net
VALIDATOR_API=https://sandbox-validator.nymtech.net/api
MIXNET_CONTRACT=nymt1ghd753shjuwexxywmgs4xz7x2q732vcnstz02j
VESTING_CONTRACT=nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s
CURRENCY_PREFIX=nymt
CHAIN_ID=nym-sandbox
+24 -66
View File
@@ -1,83 +1,41 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": {
"browser": true,
"es6": true,
"node": true
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2019,
"ecmaVersion": 2018,
"sourceType": "module"
},
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"plugins": ["prettier", "mocha"],
"extends": [
"airbnb-base",
"airbnb-typescript/base",
"prettier"],
"rules": {
"prettier/prettier": "error",
"import/prefer-default-export": "off",
"import/no-extraneous-dependencies": [
"no-console": "off",
"linebreak-style": "off",
"quotes": [
"error",
"double",
{
"devDependencies": [
"**/*.test.[jt]s",
"**/*.spec.[jt]s"
]
"allowTemplateLiterals": true
}
],
"import/extensions": [
"keyword-spacing": [
"error",
"ignorePackages",
{
"ts": "never",
"js": "never"
"before": true
}
],
"space-before-blocks": [
"error"
]
},
"overrides": [
{
"files": "**/*.ts",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint/eslint-plugin"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"no-use-before-define": [0],
"@typescript-eslint/no-use-before-define": [1],
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.ts",
"**/*.spec.ts"
]
}
],
"quotes": "off",
"@typescript-eslint/quotes": [
2,
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unused-vars": [2, { "argsIgnorePattern": "^_" }]
}
}
]
}
}
}
-6
View File
@@ -1,6 +0,0 @@
{
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 2
}
+12 -6
View File
@@ -3,20 +3,26 @@ Nym Validator Client
A TypeScript client for interacting with CosmWasm smart contracts in Nym validators.
Running examples
-----------------
With the code checked out, `cd examples`. This folder contains runnable example code that will set up a blockchain and allow you to interact with it through the client.
Running tests
-------------
The tests will be separated into three categories: unit, integration and mock.
Currently the command to run all tests:
```
npm test
```
The tests require `.env.example` being renamed to `.env`. The variables and their values for these tests are currently pointing to the `nym-sandbox` environment.
You can also trigger test execution with a test watcher. I don't have the centuries of life left to me that are needed to fight through the arcana of wiring up a working TypeScript mocha triggered execution setup, so for now my Cargo-based hack is:
`Tests are still in development` - the test libary is `jest` and the test script will execute currently with: `--coverage --verbosity false`
```
cargo watch -s "cd clients/validator && npm test"
```
It's ugly but works fine if you have Cargo installed. TypeScript setup help happily accepted here.
Generating Documentation
------------------------
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
setupFiles: ["dotenv/config"]
};
+3848
View File
File diff suppressed because it is too large Load Diff
+21 -26
View File
@@ -1,14 +1,15 @@
{
"name": "@nymproject/nym-validator-client",
"version": "0.19.0",
"version": "0.18.0",
"description": "A TypeScript client for interacting with smart contracts in Nym validators",
"repository": "https://github.com/nymtech/nym",
"main": "./dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "jest --coverage --verbose false",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"build": "tsc",
"test": "ts-mocha tests/**/*.test.ts",
"coverage": "nyc npm test",
"lint": "eslint \"**/*.ts\"",
"docs": "typedoc --out docs src/index.ts"
},
"keywords": [],
@@ -19,31 +20,25 @@
],
"license": "Apache-2.0",
"devDependencies": {
"@types/jest": "27.4.0",
"@typescript-eslint/eslint-plugin": "^5.7.0",
"@typescript-eslint/parser": "^5.7.0",
"@types/chai": "^4.2.15",
"@types/expect": "^24.3.0",
"@types/mocha": "^8.2.1",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"chai": "^4.2.0",
"eslint": "^7.18.0",
"eslint-config-airbnb": "^19.0.2",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-root-import": "^1.0.4",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.4.5",
"prettier": "^2.5.1",
"ts-jest": "^27.1.2",
"mocha": "^8.2.1",
"moq.ts": "^7.2.0",
"nyc": "^15.1.0",
"ts-mocha": "^8.0.0",
"typedoc": "^0.20.27",
"typescript": "^4.5.4"
"typescript": "^4.1.3"
},
"dependencies": {
"@cosmjs/cosmwasm-stargate": "^0.27.0-rc2",
"@cosmjs/crypto": "^0.27.0-rc2",
"@cosmjs/math": "^0.27.0-rc2",
"@cosmjs/proto-signing": "^0.27.0-rc2",
"@cosmjs/stargate": "^0.27.0-rc2",
"@cosmjs/tendermint-rpc": "^0.27.0-rc2",
"axios": "^0.21.1",
"cosmjs-types": "^0.4.0",
"dotenv": "^10.0.0"
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/stargate": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5"
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"no-console": "off",
"linebreak-style": "off",
"quotes": [
"error",
"double",
{
"allowTemplateLiterals": true
}
],
"keyword-spacing": [
"error",
{
"before": true
}
],
"space-before-blocks": [
"error"
]
}
}
+1
View File
@@ -0,0 +1 @@
15.0.1
+59
View File
@@ -0,0 +1,59 @@
import {GatewayBond, PagedGatewayResponse} from "../types";
import {INetClient} from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_GATEWAYS, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
/**
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. GatewaysCache instances repeatedly make requests for
* paged data about what gateways exist, and keep them locally in memory so that they're
* available for querying.
**/
export default class GatewaysCache {
gateways: GatewayBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.gateways = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of gateways.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
/// returns true.
async refreshGateways(contractAddress: string): Promise<GatewayBond[]> {
let newGateways: GatewayBond[] = [];
let response: PagedGatewayResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getGateways(contractAddress, this.perPage, next);
newGateways = newGateways.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.gateways = newGateways
return newGateways;
}
/// Makes requests to assemble a full list of gateways from validator-api
async refreshValidatorAPIGateways(urls: string[]): Promise<GatewayBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_GATEWAYS;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
+60
View File
@@ -0,0 +1,60 @@
import {MixNodeBond, PagedMixnodeResponse} from "../types";
import { INetClient } from "../net-client"
import {IQueryClient} from "../query-client";
import {VALIDATOR_API_MIXNODES, VALIDATOR_API_PORT} from "../index";
import axios from "axios";
export { MixnodesCache };
/**
* There are serious limits in smart contract systems, but we need to keep track of
* potentially thousands of nodes. MixnodeCache instances repeatedly make requests for
* paged data about what mixnodes exist, and keep them locally in memory so that they're
* available for querying.
* */
export default class MixnodesCache {
mixNodes: MixNodeBond[]
client: INetClient | IQueryClient
perPage: number
constructor(client: INetClient | IQueryClient, perPage: number) {
this.client = client;
this.mixNodes = [];
this.perPage = perPage;
}
/// Makes repeated requests to assemble a full list of nodes.
/// Requests continue to be make as long as `shouldMakeAnotherRequest()`
// returns true.
async refreshMixNodes(contractAddress: string): Promise<MixNodeBond[]> {
let newMixnodes: MixNodeBond[] = [];
let response: PagedMixnodeResponse;
let next: string | undefined = undefined;
for (;;) {
response = await this.client.getMixNodes(contractAddress, this.perPage, next);
newMixnodes = newMixnodes.concat(response.nodes)
next = response.start_next_after;
// if `start_next_after` is not set, we're done
if (!next) {
break
}
}
this.mixNodes = newMixnodes
return this.mixNodes;
}
/// Makes requests to assemble a full list of mixnodes from validator-api
async refreshValidatorAPIMixNodes(urls: string[]): Promise<MixNodeBond[]> {
for (const url of urls) {
const validator_api_url = new URL(url);
validator_api_url.port = VALIDATOR_API_PORT;
validator_api_url.pathname += VALIDATOR_API_MIXNODES;
const response = await axios.get(validator_api_url.toString());
if (response.status == 200) {
return response.data;
}
}
throw new Error("None of the provided validators seem to be alive")
}
}
+38 -33
View File
@@ -1,68 +1,73 @@
import { Decimal } from '@cosmjs/math';
import { Coin } from '@cosmjs/stargate';
import { Decimal } from "@cosmjs/math";
import { Coin } from ".";
// NARROW NO-BREAK SPACE (U+202F)
const thinSpace = '\u202F';
const thinSpace = "\u202F";
export function printableCoin(coin?: Coin): string {
if (!coin) {
return '0';
}
if (coin.denom.startsWith('u')) {
const ticker = coin.denom.slice(1).toUpperCase();
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
}
return coin.amount + thinSpace + coin.denom;
if (!coin) {
return "0";
}
if (coin.denom.startsWith("u")) {
const ticker = coin.denom.slice(1).toUpperCase();
return Decimal.fromAtomics(coin.amount, 6).toString() + thinSpace + ticker;
} else {
return coin.amount + thinSpace + coin.denom;
}
}
export function printableBalance(balance?: readonly Coin[]): string {
if (!balance || balance.length === 0) return '';
return balance.map(printableCoin).join(', ');
if (!balance || balance.length === 0) return "";
return balance.map(printableCoin).join(", ");
}
// converts display amount, such as "12.0346" to its native token representation,
// with 6 fractional digits. So in that case it would result in "12034600"
// Basically does the same job as `displayAmountToNative` but without the requirement
// of having the coinMap
export function printableBalanceToNative(amountToDisplay: string): string {
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
return decimalAmount.atomics;
export function printableBalanceToNative(amountToDisplay: string): string {
const decimalAmount = Decimal.fromUserInput(amountToDisplay, 6);
return decimalAmount.atomics;
}
// reciprocal of `printableBalanceToNative`, takes, for example 10000000 and returns 10
export function nativeToPrintable(nativeValue: string): string {
return Decimal.fromAtomics(nativeValue, 6).toString();
return Decimal.fromAtomics(nativeValue, 6).toString()
}
export interface MappedCoin {
readonly denom: string;
readonly fractionalDigits: number;
readonly denom: string;
readonly fractionalDigits: number;
}
export interface CoinMap {
readonly [key: string]: MappedCoin;
readonly [key: string]: MappedCoin;
}
export function nativeCoinToDisplay(coin: Coin, coinMap: CoinMap): Coin {
if (!coinMap) return coin;
if (!coinMap) return coin;
const coinToDisplay = coinMap[coin.denom];
if (!coinToDisplay) return coin;
const coinToDisplay = coinMap[coin.denom];
if (!coinToDisplay) return coin;
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
const amountToDisplay = Decimal.fromAtomics(coin.amount, coinToDisplay.fractionalDigits).toString();
return { denom: coinToDisplay.denom, amount: amountToDisplay };
return { denom: coinToDisplay.denom, amount: amountToDisplay };
}
// display amount is eg "12.0346", return is in native tokens
// with 6 fractional digits, this would be eg. "12034600"
export function displayAmountToNative(amountToDisplay: string, coinMap: CoinMap, nativeDenom: string): string {
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
if (fractionalDigits) {
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
return decimalAmount.atomics;
}
export function displayAmountToNative(
amountToDisplay: string,
coinMap: CoinMap,
nativeDenom: string,
): string {
const fractionalDigits = coinMap[nativeDenom]?.fractionalDigits;
if (fractionalDigits) {
// use https://github.com/CosmWasm/cosmjs/blob/v0.22.2/packages/math/src/decimal.ts
const decimalAmount = Decimal.fromUserInput(amountToDisplay, fractionalDigits);
return decimalAmount.atomics;
}
return amountToDisplay;
return amountToDisplay;
}
File diff suppressed because it is too large Load Diff
+207
View File
@@ -0,0 +1,207 @@
import { SigningCosmWasmClient, SigningCosmWasmClientOptions } from "@cosmjs/cosmwasm-stargate";
import {
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
import { DirectSecp256k1HdWallet, EncodeObject } from "@cosmjs/proto-signing";
import { Coin, StdFee } from "@cosmjs/stargate";
import { BroadcastTxResponse } from "@cosmjs/stargate"
import { nymGasLimits, nymGasPrice } from "./stargate-helper"
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
UploadMeta,
UploadResult
} from "@cosmjs/cosmwasm-stargate";
export interface INetClient {
clientAddress: string;
getBalance(address: string, denom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse>;
executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult>;
instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult>;
sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse>;
upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult>;
changeValidator(newUrl: string): Promise<void>
}
/**
* Takes care of network communication between this code and the validator.
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class NetClient implements INetClient {
clientAddress: string;
private cosmClient: SigningCosmWasmClient;
// helpers for changing validators without having to remake the wallet
private readonly wallet: DirectSecp256k1HdWallet;
private readonly signerOptions: SigningCosmWasmClientOptions;
private constructor(clientAddress: string, cosmClient: SigningCosmWasmClient, wallet: DirectSecp256k1HdWallet, signerOptions: SigningCosmWasmClientOptions) {
this.clientAddress = clientAddress;
this.cosmClient = cosmClient;
this.wallet = wallet;
this.signerOptions = signerOptions;
}
public static async connect(wallet: DirectSecp256k1HdWallet, url: string, prefix: string): Promise<INetClient> {
const [{address}] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
gasLimits: nymGasLimits,
};
const client = await SigningCosmWasmClient.connectWithSigner(url, wallet, signerOptions);
return new NetClient(address, client, wallet, signerOptions);
}
async changeValidator(url: string): Promise<void> {
this.cosmClient = await SigningCosmWasmClient.connectWithSigner(url, this.wallet, this.signerOptions);
}
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit,
start_after
}
});
}
}
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
mix_identity: mixIdentity,
address: delegatorAddress
}
});
}
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit,
start_after
}
});
}
}
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegation: {
gateway_identity: gatewayIdentity,
address: delegatorAddress
}
});
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, denom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, denom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
public executeContract(senderAddress: string, contractAddress: string, handleMsg: Record<string, unknown>, memo?: string, transferAmount?: readonly Coin[]): Promise<ExecuteResult> {
return this.cosmClient.execute(senderAddress, contractAddress, handleMsg, memo, transferAmount);
}
public signAndBroadcast(signerAddress: string, messages: readonly EncodeObject[], fee: StdFee, memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.signAndBroadcast(signerAddress, messages, fee, memo)
}
public sendTokens(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[], memo?: string): Promise<BroadcastTxResponse> {
return this.cosmClient.sendTokens(senderAddress, recipientAddress, transferAmount, memo);
}
public upload(senderAddress: string, wasmCode: Uint8Array, meta?: UploadMeta, memo?: string): Promise<UploadResult> {
return this.cosmClient.upload(senderAddress, wasmCode, meta, memo);
}
public instantiate(senderAddress: string, codeId: number, initMsg: Record<string, unknown>, label: string, options?: InstantiateOptions): Promise<InstantiateResult> {
return this.cosmClient.instantiate(senderAddress, codeId, initMsg, label, options);
}
public migrate(senderAddress: string, contractAddress: string, codeId: number, migrateMsg: Record<string, unknown>, memo?: string): Promise<MigrateResult> {
return this.cosmClient.migrate(senderAddress, contractAddress, codeId, migrateMsg, memo)
}
}
-182
View File
@@ -1,182 +0,0 @@
/*
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
// eslint-disable-next-line import/no-cycle
import { INymdQuery } from './query-client';
import {
ContractStateParams,
Delegation,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
interface SmartContractQuery {
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
}
export default class NymdQuerier implements INymdQuery {
client: SmartContractQuery;
constructor(client: SmartContractQuery) {
this.client = client;
}
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_contract_version: {},
});
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mix_nodes: {
limit,
start_after: startAfter,
},
});
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_gateways: {
limit,
start_after: startAfter,
},
});
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_mixnode: {
address,
},
});
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
owns_gateway: {
address,
},
});
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.client.queryContractSmart(mixnetContractAddress, {
state_params: {},
});
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
current_rewarding_interval: {},
});
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_all_network_delegations: {
start_after: startAfter,
limit,
},
});
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_mixnode_delegations: {
mix_identity: mixIdentity,
start_after: startAfter,
limit,
},
});
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegator_delegations: {
delegator,
start_after: startAfter,
limit,
},
});
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_delegation_details: {
mix_identity: mixIdentity,
delegator,
},
});
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.client.queryContractSmart(mixnetContractAddress, {
layer_distribution: {},
});
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_reward_pool: {},
});
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_circulating_supply: {},
});
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_epoch_reward_percent: {},
});
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_sybil_resistance_percent: {},
});
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.client.queryContractSmart(mixnetContractAddress, {
get_rewarding_status: {
mix_identity: mixIdentity,
rewarding_interval_nonce: rewardingIntervalNonce,
},
});
}
}
+139 -210
View File
@@ -1,218 +1,147 @@
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import { Coin } from "@cosmjs/stargate";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import {
Account,
Block,
Coin,
DeliverTxResponse,
IndexedTx,
SearchTxFilter,
SearchTxQuery,
SequenceResponse,
} from '@cosmjs/stargate';
import { JsonObject } from '@cosmjs/cosmwasm-stargate/build/queries';
import { Code, CodeDetails, Contract, ContractCodeHistoryEntry } from '@cosmjs/cosmwasm-stargate/build/cosmwasmclient';
// eslint-disable-next-line import/no-cycle
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier, { IValidatorApiQuery } from './validator-api-querier';
Delegation,
GatewayOwnershipResponse,
MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse,
PagedMixnodeResponse,
StateParams
} from "./types";
export interface ICosmWasmQuery {
// methods exposed by `CosmWasmClient`
getChainId(): Promise<string>;
getHeight(): Promise<number>;
getAccount(searchAddress: string): Promise<Account | null>;
getSequence(address: string): Promise<SequenceResponse>;
getBlock(height?: number): Promise<Block>;
getBalance(address: string, searchDenom: string): Promise<Coin>;
getTx(id: string): Promise<IndexedTx | null>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
disconnect(): void;
broadcastTx(tx: Uint8Array, timeoutMs?: number, pollIntervalMs?: number): Promise<DeliverTxResponse>;
getCodes(): Promise<readonly Code[]>;
getCodeDetails(codeId: number): Promise<CodeDetails>;
getContracts(codeId: number): Promise<readonly string[]>;
getContract(address: string): Promise<Contract>;
getContractCodeHistory(address: string): Promise<readonly ContractCodeHistoryEntry[]>;
queryContractRaw(address: string, key: Uint8Array): Promise<Uint8Array | null>;
queryContractSmart(address: string, queryMsg: Record<string, unknown>): Promise<JsonObject>;
export interface IQueryClient {
getBalance(address: string, stakeDenom: string): Promise<Coin | null>;
getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse>;
getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse>;
getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse>
getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation>
getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse>
getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation>
ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(contractAddress: string): Promise<StateParams>;
changeValidator(newUrl: string): Promise<void>
}
export interface INymdQuery {
// nym-specific implemented inside NymQuerier
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion>;
/**
* Takes care of network communication between this code and the validator.
* Depends on `SigningCosmWasClient`, which signs all requests using keypairs
* derived from on bech32 mnemonics.
*
* Wraps several methods from CosmWasmSigningClient so we can mock them for
* unit testing.
*/
export default class QueryClient implements IQueryClient {
private cosmClient: CosmWasmClient;
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse>;
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse>;
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse>;
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse>;
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams>;
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse>;
private constructor(cosmClient: CosmWasmClient) {
this.cosmClient = cosmClient;
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse>;
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse>;
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse>;
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation>;
public static async connect(url: string): Promise<IQueryClient> {
const client = await CosmWasmClient.connect(url)
return new QueryClient(client)
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution>;
getRewardPool(mixnetContractAddress: string): Promise<string>;
getCirculatingSupply(mixnetContractAddress: string): Promise<string>;
getEpochRewardPercent(mixnetContractAddress: string): Promise<number>;
getSybilResistancePercent(mixnetContractAddress: string): Promise<number>;
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus>;
}
export interface IQueryClient extends ICosmWasmQuery, INymdQuery, IValidatorApiQuery {}
export default class QueryClient extends CosmWasmClient implements IQueryClient {
private nymdQuerier: NymdQuerier;
private validatorApiQuerier: ValidatorApiQuerier;
private constructor(tmClient: Tendermint34Client, validatorApiUrl: string) {
super(tmClient);
this.nymdQuerier = new NymdQuerier(this);
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
}
public static async connectWithNym(nymdUrl: string, validatorApiUrl: string): Promise<QueryClient> {
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new QueryClient(tmClient, validatorApiUrl);
}
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nymdQuerier.getStateParams(mixnetContractAddress);
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
async changeValidator(url: string): Promise<void> {
this.cosmClient = await CosmWasmClient.connect(url)
}
public getMixNodes(contractAddress: string, limit: number, start_after?: string): Promise<PagedMixnodeResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_mix_nodes: {limit, start_after}});
}
}
public getGateways(contractAddress: string, limit: number, start_after?: string): Promise<PagedGatewayResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit}});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {get_gateways: {limit, start_after}});
}
}
public getMixDelegations(contractAddress: string, mixIdentity: string, limit: number, start_after?: string): Promise<PagedMixDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegations: {
mix_identity: mixIdentity,
limit,
start_after
}
});
}
}
public getMixDelegation(contractAddress: string, mixIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_mix_delegation: {
mix_identity: mixIdentity,
address: delegatorAddress
}
});
}
public getGatewayDelegations(contractAddress: string, gatewayIdentity: string, limit: number, start_after?: string): Promise<PagedGatewayDelegationsResponse> {
if (start_after == undefined) { // TODO: check if we can take this out, I'm not sure what will happen if we send an "undefined" so I'm playing it safe here.
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit
}
});
} else {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegations: {
gateway_identity: gatewayIdentity,
limit,
start_after
}
});
}
}
public getGatewayDelegation(contractAddress: string, gatewayIdentity: string, delegatorAddress: string): Promise<Delegation> {
return this.cosmClient.queryContractSmart(contractAddress, {
get_gateway_delegation: {
gateway_identity: gatewayIdentity,
address: delegatorAddress
}
});
}
public ownsMixNode(contractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_mixnode: {address}});
}
public ownsGateway(contractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.cosmClient.queryContractSmart(contractAddress, {owns_gateway: {address}});
}
public getBalance(address: string, stakeDenom: string): Promise<Coin | null> {
return this.cosmClient.getBalance(address, stakeDenom);
}
public getStateParams(contractAddress: string): Promise<StateParams> {
return this.cosmClient.queryContractSmart(contractAddress, {state_params: {}});
}
}
-467
View File
@@ -1,467 +0,0 @@
import {
ExecuteResult,
InstantiateOptions,
InstantiateResult,
MigrateResult,
SigningCosmWasmClient,
SigningCosmWasmClientOptions,
UploadResult,
} from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet, EncodeObject } from '@cosmjs/proto-signing';
import { Coin, DeliverTxResponse, SignerData, StdFee } from '@cosmjs/stargate';
import { Tendermint34Client } from '@cosmjs/tendermint-rpc';
import { ChangeAdminResult } from '@cosmjs/cosmwasm-stargate/build/signingcosmwasmclient';
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import { nymGasPrice } from './stargate-helper';
import { IQueryClient } from './query-client';
import NymdQuerier from './nymd-querier';
import {
ContractStateParams,
Delegation,
Gateway,
GatewayBond,
GatewayOwnershipResponse,
LayerDistribution,
MixnetContractVersion,
MixNode,
MixNodeBond,
MixOwnershipResponse,
PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse,
PagedGatewayResponse,
PagedMixDelegationsResponse,
PagedMixnodeResponse,
RewardingIntervalResponse,
RewardingStatus,
} from './types';
import ValidatorApiQuerier from './validator-api-querier';
// methods exposed by `SigningCosmWasmClient`
export interface ICosmWasmSigning {
simulate(signerAddress: string, messages: readonly EncodeObject[], memo: string | undefined): Promise<number>;
upload(
senderAddress: string,
wasmCode: Uint8Array,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<UploadResult>;
instantiate(
senderAddress: string,
codeId: number,
msg: Record<string, unknown>,
label: string,
fee: StdFee | 'auto' | number,
options?: InstantiateOptions,
): Promise<InstantiateResult>;
updateAdmin(
senderAddress: string,
contractAddress: string,
newAdmin: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
clearAdmin(
senderAddress: string,
contractAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<ChangeAdminResult>;
migrate(
senderAddress: string,
contractAddress: string,
codeId: number,
migrateMsg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<MigrateResult>;
execute(
senderAddress: string,
contractAddress: string,
msg: Record<string, unknown>,
fee: StdFee | 'auto' | number,
memo?: string,
funds?: readonly Coin[],
): Promise<ExecuteResult>;
sendTokens(
senderAddress: string,
recipientAddress: string,
amount: readonly Coin[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
delegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
undelegateTokens(
delegatorAddress: string,
validatorAddress: string,
amount: Coin,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
withdrawRewards(
delegatorAddress: string,
validatorAddress: string,
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
signAndBroadcast(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee | 'auto' | number,
memo?: string,
): Promise<DeliverTxResponse>;
sign(
signerAddress: string,
messages: readonly EncodeObject[],
fee: StdFee,
memo: string,
explicitSignerData?: SignerData,
): Promise<TxRaw>;
}
export interface INymSigning {
clientAddress: string;
}
export interface ISigningClient extends IQueryClient, ICosmWasmSigning, INymSigning {
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondMixNode(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
unbondGateway(mixnetContractAddress: string, fee?: StdFee | 'auto' | number, memo?: string): Promise<ExecuteResult>;
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee?: StdFee | 'auto' | number,
memo?: string,
): Promise<ExecuteResult>;
// I don't see any point in exposing rewarding / vesting-related (INSIDE mixnet contract, like "BondMixnodeOnBehalf")
// functionalities in our typescript client. However, if for some reason, we find we need them
// they're rather trivial to add.
}
export default class SigningClient extends SigningCosmWasmClient implements ISigningClient {
private nymdQuerier: NymdQuerier;
private validatorApiQuerier: ValidatorApiQuerier;
clientAddress: string;
private constructor(
clientAddress: string,
validatorApiUrl: string,
tmClient: Tendermint34Client,
wallet: DirectSecp256k1HdWallet,
signerOptions: SigningCosmWasmClientOptions,
) {
super(tmClient, wallet, signerOptions);
this.clientAddress = clientAddress;
this.nymdQuerier = new NymdQuerier(this);
this.validatorApiQuerier = new ValidatorApiQuerier(validatorApiUrl);
}
public static async connectWithNymSigner(
wallet: DirectSecp256k1HdWallet,
nymdUrl: string,
validatorApiUrl: string,
prefix: string,
): Promise<SigningClient> {
const [{ address }] = await wallet.getAccounts();
const signerOptions: SigningCosmWasmClientOptions = {
gasPrice: nymGasPrice(prefix),
};
const tmClient = await Tendermint34Client.connect(nymdUrl);
return new SigningClient(address, validatorApiUrl, tmClient, wallet, signerOptions);
}
// query related:
getContractVersion(mixnetContractAddress: string): Promise<MixnetContractVersion> {
return this.nymdQuerier.getContractVersion(mixnetContractAddress);
}
getMixNodesPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedMixnodeResponse> {
return this.nymdQuerier.getMixNodesPaged(mixnetContractAddress, limit, startAfter);
}
getGatewaysPaged(mixnetContractAddress: string, limit?: number, startAfter?: string): Promise<PagedGatewayResponse> {
return this.nymdQuerier.getGatewaysPaged(mixnetContractAddress, limit, startAfter);
}
ownsMixNode(mixnetContractAddress: string, address: string): Promise<MixOwnershipResponse> {
return this.nymdQuerier.ownsMixNode(mixnetContractAddress, address);
}
ownsGateway(mixnetContractAddress: string, address: string): Promise<GatewayOwnershipResponse> {
return this.nymdQuerier.ownsGateway(mixnetContractAddress, address);
}
getStateParams(mixnetContractAddress: string): Promise<ContractStateParams> {
return this.nymdQuerier.getStateParams(mixnetContractAddress);
}
getCurrentRewardingInterval(mixnetContractAddress: string): Promise<RewardingIntervalResponse> {
return this.nymdQuerier.getCurrentRewardingInterval(mixnetContractAddress);
}
getAllNetworkDelegationsPaged(
mixnetContractAddress: string,
limit?: number,
startAfter?: [string, string],
): Promise<PagedAllDelegationsResponse> {
return this.nymdQuerier.getAllNetworkDelegationsPaged(mixnetContractAddress, limit, startAfter);
}
getMixNodeDelegationsPaged(
mixnetContractAddress: string,
mixIdentity: string,
limit?: number,
startAfter?: string,
): Promise<PagedMixDelegationsResponse> {
return this.nymdQuerier.getMixNodeDelegationsPaged(mixnetContractAddress, mixIdentity, limit, startAfter);
}
getDelegatorDelegationsPaged(
mixnetContractAddress: string,
delegator: string,
limit?: number,
startAfter?: string,
): Promise<PagedDelegatorDelegationsResponse> {
return this.nymdQuerier.getDelegatorDelegationsPaged(mixnetContractAddress, delegator, limit, startAfter);
}
getDelegationDetails(mixnetContractAddress: string, mixIdentity: string, delegator: string): Promise<Delegation> {
return this.nymdQuerier.getDelegationDetails(mixnetContractAddress, mixIdentity, delegator);
}
getLayerDistribution(mixnetContractAddress: string): Promise<LayerDistribution> {
return this.nymdQuerier.getLayerDistribution(mixnetContractAddress);
}
getRewardPool(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getRewardPool(mixnetContractAddress);
}
getCirculatingSupply(mixnetContractAddress: string): Promise<string> {
return this.nymdQuerier.getCirculatingSupply(mixnetContractAddress);
}
getEpochRewardPercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getEpochRewardPercent(mixnetContractAddress);
}
getSybilResistancePercent(mixnetContractAddress: string): Promise<number> {
return this.nymdQuerier.getSybilResistancePercent(mixnetContractAddress);
}
getRewardingStatus(
mixnetContractAddress: string,
mixIdentity: string,
rewardingIntervalNonce: number,
): Promise<RewardingStatus> {
return this.nymdQuerier.getRewardingStatus(mixnetContractAddress, mixIdentity, rewardingIntervalNonce);
}
getCachedGateways(): Promise<GatewayBond[]> {
return this.validatorApiQuerier.getCachedGateways();
}
getCachedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getCachedMixnodes();
}
getActiveMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getActiveMixnodes();
}
getRewardedMixnodes(): Promise<MixNodeBond[]> {
return this.validatorApiQuerier.getRewardedMixnodes();
}
// signing related:
bondMixNode(
mixnetContractAddress: string,
mixNode: MixNode,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_mixnode: {
mix_node: mixNode,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondMixNode(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_mixnode: {},
},
fee,
memo,
);
}
bondGateway(
mixnetContractAddress: string,
gateway: Gateway,
ownerSignature: string,
pledge: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Bonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
bond_gateway: {
gateway,
owner_signature: ownerSignature,
},
},
fee,
memo,
[pledge],
);
}
unbondGateway(
mixnetContractAddress: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Gateway Unbonding from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
unbond_gateway: {},
},
fee,
memo,
);
}
delegateToMixNode(
mixnetContractAddress: string,
mixIdentity: string,
amount: Coin,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Delegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
delegate_to_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
[amount],
);
}
undelegateFromMixNode(
mixnetContractAddress: string,
mixIdentity: string,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default MixNode Undelegation from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
undelegate_from_mixnode: {
mix_identity: mixIdentity,
},
},
fee,
memo,
);
}
updateContractStateParams(
mixnetContractAddress: string,
newParams: ContractStateParams,
fee: StdFee | 'auto' | number = 'auto',
memo = 'Default Contract State Params Update from Typescript',
): Promise<ExecuteResult> {
return this.execute(
this.clientAddress,
mixnetContractAddress,
{
update_contract_state_params: newParams,
},
fee,
memo,
);
}
}
+19 -12
View File
@@ -1,19 +1,26 @@
import axios from 'axios';
import { GasPrice } from '@cosmjs/stargate';
import axios from "axios";
import { GasLimits, GasPrice } from "@cosmjs/stargate";
import { CosmWasmFeeTable, defaultGasLimits } from "@cosmjs/cosmwasm-stargate";
export const nymGasLimits: GasLimits<CosmWasmFeeTable> = {
...defaultGasLimits,
upload: 2_500_000,
init: 500_000,
migrate: 200_000,
exec: 250_000,
send: 80_000,
changeAdmin: 80_000,
};
export function nymGasPrice(prefix: string): GasPrice {
if (typeof prefix === 'string') {
return GasPrice.fromString(`0.025u${prefix}`); // TODO: ideally this ugly conversion shouldn't be hardcoded here.
}
else {
throw new Error(`${prefix} is not of type string`);
}
}
export const downloadWasm = async (url: string): Promise<Uint8Array> => {
const r = await axios.get(url, { responseType: 'arraybuffer' });
if (r.status !== 200) {
throw new Error(`Download error: ${r.status}`);
}
return r.data;
const r = await axios.get(url, {responseType: "arraybuffer"});
if (r.status !== 200) {
throw new Error(`Download error: ${r.status}`);
}
return r.data;
};
+112 -149
View File
@@ -1,162 +1,125 @@
import { Coin } from '@cosmjs/stargate';
import { Coin } from "@cosmjs/stargate";
// TODO: ideally we'd have re-exported those using that fancy crate that builds ts types from rust
export type MixnetContractVersion = {
build_timestamp: string;
build_version: string;
commit_sha: string;
commit_timestamp: string;
commit_branch: string;
rustc_version: string;
};
/// One page of a possible multi-page set of mixnodes. The paging interface is quite
/// inconvenient, as we don't have the two pieces of information we need to know
/// in order to do paging nicely (namely `currentPage` and `totalPages` parameters).
///
/// Instead, we have only `start_next_page_after`, i.e. the key of the last record
/// on this page. In order to get the *next* page, CosmWasm looks at that value,
/// finds the next record, and builds the next page starting there. This happens
/// **in series** rather than **in parallel** (!).
///
/// So we have some consistency problems:
///
/// * we can't make requests at a given block height, so the result set
/// which we assemble over time may change while requests are being made.
/// * at some point we will make a request for a `start_next_page_after` key
/// which has just been deleted from the database.
///
/// TODO: more robust error handling on the "deleted key" case.
export type PagedMixnodeResponse = {
nodes: MixNodeBond[];
per_page: number;
start_next_after?: string;
};
export type PagedGatewayResponse = {
nodes: GatewayBond[];
per_page: number;
start_next_after?: string;
};
export type MixOwnershipResponse = {
address: string;
mixnode?: MixNodeBond;
};
export type GatewayOwnershipResponse = {
address: string;
gateway?: GatewayBond;
};
export type ContractStateParams = {
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_pledge: string;
minimum_gateway_pledge: string;
mixnode_rewarded_set_size: number;
mixnode_active_set_size: number;
};
export type RewardingIntervalResponse = {
current_rewarding_interval_starting_block: number;
current_rewarding_interval_nonce: number;
rewarding_in_progress: boolean;
};
export type LayerDistribution = {
gateways: number;
layer1: number;
layer2: number;
layer3: number;
};
export type Delegation = {
owner: string;
node_identity: string;
amount: Coin;
block_height: number;
proxy?: string;
};
export type PagedMixDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedDelegatorDelegationsResponse = {
delegations: Delegation[];
start_next_after?: string;
};
export type PagedAllDelegationsResponse = {
delegations: Delegation[];
start_next_after?: [string, string];
};
export type RewardingResult = {
operator_reward: string;
total_delegator_reward: string;
};
export type NodeRewardParams = {
period_reward_pool: string;
k: string;
reward_blockstamp: number;
circulating_supply: string;
uptime: string;
sybil_resistance_percent: number;
};
export type DelegatorRewardParams = {
node_reward_params: NodeRewardParams;
sigma: number;
profit_margin: number;
node_profit: number;
};
export type PendingDelegatorRewarding = {
running_results: RewardingResult;
next_start: string;
rewarding_params: DelegatorRewardParams;
};
export type RewardingStatus = { Complete: RewardingResult } | { PendingNextDelegatorPage: PendingDelegatorRewarding };
export type MixnodeRewardingStatusResponse = {
status?: RewardingStatus;
};
export enum Layer {
Gateway,
One,
Two,
Three,
nodes: MixNodeBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
export type MixNodeBond = {
owner: string;
mix_node: MixNode;
layer: Layer;
bond_amount: Coin;
total_delegation: Coin;
};
// a temporary way of achieving the same paging behaviour for the gateways
// the same points made for `PagedResponse` stand here.
export type PagedGatewayResponse = {
nodes: GatewayBond[],
per_page: number, // TODO: camelCase
start_next_after: string, // TODO: camelCase
}
export type MixOwnershipResponse = {
address: string,
has_node: boolean,
}
export type GatewayOwnershipResponse = {
address: string,
has_gateway: boolean,
}
export type StateParams = {
epoch_length: number,
// ideally I'd want to define those as `number` rather than `string`, but
// rust-side they are defined as Uint128 and Decimal that don't have
// native javascript representations and therefore are interpreted as strings after deserialization
minimum_mixnode_bond: string,
minimum_gateway_bond: string,
mixnode_bond_reward_rate: string,
gateway_bond_reward_rate: string,
mixnode_delegation_reward_rate: string,
gateway_delegation_reward_rate: string,
mixnode_active_set_size: number,
gateway_active_set_size: number,
}
export type Delegation = {
owner: string,
amount: Coin,
}
export type PagedMixDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export type PagedGatewayDelegationsResponse = {
node_owner: string,
delegations: Delegation[],
start_next_after: string
}
export enum Layer {
Gateway,
One,
Two,
Three,
}
export type MixNodeBond = { // TODO: change name to MixNodeBond
owner: string,
mix_node: MixNode, // TODO: camelCase this later once everything else works
layer: Layer,
bond_amount: Coin,
total_delegation: Coin,
}
export type MixNode = {
host: string;
mix_port: number;
verloc_port: number;
http_api_port: number;
sphinx_key: string;
identity_key: string;
version: string;
};
host: string,
mix_port: number,
verloc_port: number,
http_api_port: number,
sphinx_key: string, // TODO: camelCase this later once everything else works
identity_key: string,
version: string,
}
export type GatewayBond = {
owner: string;
gateway: Gateway;
bond_amount: Coin;
total_delegation: Coin;
};
owner: string
gateway: Gateway,
bond_amount: Coin,
total_delegation: Coin,
}
export type Gateway = {
host: string;
mix_port: number;
clients_port: number;
location: string;
sphinx_key: string;
identity_key: string;
version: string;
};
host: string,
mix_port: number,
clients_port: number,
location: string,
sphinx_key: string,
identity_key: string,
version: string
}
export type SendRequest = {
senderAddress: string;
recipientAddress: string;
transferAmount: readonly Coin[];
};
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[]
}
+12 -16
View File
@@ -1,18 +1,14 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
import { Coin } from '@cosmjs/stargate';
import { EncodeObject } from '@cosmjs/proto-signing';
import { Coin } from "@cosmjs/stargate";
import { EncodeObject } from "@cosmjs/proto-signing";
export function makeBankMsgSend(
senderAddress: string,
recipientAddress: string,
transferAmount: readonly Coin[],
): EncodeObject {
return {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: {
fromAddress: senderAddress,
toAddress: recipientAddress,
amount: transferAmount,
},
};
}
export function makeBankMsgSend(senderAddress: string, recipientAddress: string, transferAmount: readonly Coin[]): EncodeObject {
return {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: {
fromAddress: senderAddress,
toAddress: recipientAddress,
amount: transferAmount,
},
};
}
@@ -1,75 +0,0 @@
/*
* Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
import axios from 'axios';
import { GatewayBond, MixNodeBond } from './types';
export const VALIDATOR_API_VERSION = '/v1';
export const VALIDATOR_API_GATEWAYS_PATH = `${VALIDATOR_API_VERSION}/gateways`;
export const VALIDATOR_API_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes`;
export const VALIDATOR_API_ACTIVE_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/active`;
export const VALIDATOR_API_REWARDED_MIXNODES_PATH = `${VALIDATOR_API_VERSION}/mixnodes/rewarded`;
export interface IValidatorApiQuery {
getCachedMixnodes(): Promise<MixNodeBond[]>;
getCachedGateways(): Promise<GatewayBond[]>;
getActiveMixnodes(): Promise<MixNodeBond[]>;
getRewardedMixnodes(): Promise<MixNodeBond[]>;
}
export default class ValidatorApiQuerier implements IValidatorApiQuery {
validatorApiUrl: string;
constructor(validatorApiUrl: string) {
this.validatorApiUrl = validatorApiUrl;
}
async getCachedMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getCachedGateways(): Promise<GatewayBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_GATEWAYS_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getActiveMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_ACTIVE_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
async getRewardedMixnodes(): Promise<MixNodeBond[]> {
const url = new URL(this.validatorApiUrl);
url.pathname += VALIDATOR_API_REWARDED_MIXNODES_PATH;
const response = await axios.get(url.toString());
if (response.status === 200) {
return response.data;
}
throw new Error('None of the provided validator APIs seem to be alive');
}
}
@@ -0,0 +1,100 @@
import { assert } from "chai";
import INetClient from "../../src/net-client";
import { Fixtures } from "../fixtures"
import { Mock, Times } from "moq.ts";
import GatewaysCache from "../../src/caches/gateways";
describe("Caching gateways: when the validator returns", () => {
context("an empty list", () => {
it("Should return an empty list", async () => {
const perPage = 100;
const contractAddress = "mockContractAddress";
const emptyPromise = Promise.resolve(Fixtures.GatewaysResp.empty());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(emptyPromise);
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual([], cache.gateways);
});
})
context("a list of gatways that fits in a page", () => {
it("Should return that one page list", async () => {
const perPage = 2;
const contractAddress = "mockContractAddress";
const onePagePromise = Promise.resolve(Fixtures.GatewaysResp.onePage());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(onePagePromise);
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list2(), cache.gateways);
})
})
context("a list of gateways that is longer than one page", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult = Fixtures.GatewaysResp.page1of2();
const halfPageResult = Fixtures.GatewaysResp.halfPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
mockClient.setup(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(instance => instance.getGateways(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list3(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("a list of gateways that is two filled pages", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("refreshing the cache twice", () => {
it("returns one full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.GatewaysResp.page1of2();
const fullPageResult2 = Fixtures.GatewaysResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new GatewaysCache(mockClient.object(), perPage);
await cache.refreshGateways(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
await cache.refreshGateways(contractAddress);
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, undefined), Times.Exactly(2));
mockClient.verify(netClient => netClient.getGateways(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(2));
assert.deepEqual(Fixtures.Gateways.list4(), cache.gateways); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
});
@@ -0,0 +1,96 @@
import { assert } from "chai";
import INetClient from "../../src/net-client";
import { Fixtures } from "../fixtures"
import { Mock, Times } from "moq.ts";
import { MixnodesCache } from "../../src/caches/mixnodes"
describe("Caching mixnodes: when the validator returns", () => {
context("an empty list", () => {
it("Should return an empty list", async () => {
const perPage = 100;
const contractAddress = "mockContractAddress";
const emptyPromise = Promise.resolve(Fixtures.MixNodesResp.empty());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(emptyPromise);
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress);
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual([], cache.mixNodes);
});
})
context("a list of nodes that fits in a page", () => {
it("Should return that one page list", async () => {
const perPage = 2;
const contractAddress = "mockContractAddress";
const onePagePromise = Promise.resolve(Fixtures.MixNodesResp.onePage());
const mockClient = new Mock<INetClient>().setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(onePagePromise);
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress);
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list2(), cache.mixNodes);
})
})
context("a list of nodes that is longer than one page", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult = Fixtures.MixNodesResp.page1of2();
const halfPageResult = Fixtures.MixNodesResp.halfPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult));
mockClient.setup(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after)).returns(Promise.resolve(halfPageResult));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(instance => instance.getMixNodes(contractAddress, perPage, fullPageResult.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list3(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("a list of nodes that is two filled pages", () => {
it("Should return the full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
context("refreshing the cache twice", () => {
it("returns one full list assembled from all pages", async () => {
const perPage = 2; // we get back 2 per page
const contractAddress = "mockContractAddress";
const fullPageResult1 = Fixtures.MixNodesResp.page1of2();
const fullPageResult2 = Fixtures.MixNodesResp.fullPage2of2();
const mockClient = new Mock<INetClient>()
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, undefined)).returns(Promise.resolve(fullPageResult1));
mockClient.setup(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after)).returns(Promise.resolve(fullPageResult2));
const cache = new MixnodesCache(mockClient.object(), perPage);
await cache.refreshMixNodes(contractAddress); // should make multiple paginated requests because there are two pages in the response fixture
await cache.refreshMixNodes(contractAddress);
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, undefined), Times.Exactly(1));
// mockClient.verify(netClient => netClient.getMixNodes(contractAddress, perPage, fullPageResult1.start_next_after), Times.Exactly(1));
assert.deepEqual(Fixtures.MixNodes.list4(), cache.mixNodes); // there are a total of 3 nodes in the validator lists, we get them all back
})
})
});
+156
View File
@@ -0,0 +1,156 @@
import { coins } from "@cosmjs/launchpad";
import {PagedGatewayResponse, PagedMixnodeResponse} from "../src/net-client";
import {GatewayBond, MixNodeBond} from "../src/types"
export namespace Fixtures {
export class MixNodes {
static single(): MixNodeBond {
return {
amount: coins(666, "unym"),
owner: "bob",
mix_node: {
host: "1.1.1.1",
layer: 1,
location: "London, UK",
sphinx_key: "foo",
version: "0.10.0",
}
};
}
static list1(): MixNodeBond[] {
return [MixNodes.single()]
}
static list2(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single()]
}
static list3(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single(), MixNodes.single()]
}
static list4(): MixNodeBond[] {
return [MixNodes.single(), MixNodes.single(), MixNodes.single(), MixNodes.single()]
}
}
export class MixNodesResp {
static empty(): PagedResponse {
return {
nodes: [],
per_page: 2,
start_next_after: null,
}
}
static onePage(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: null
}
}
static page1of2(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: "2"
}
}
static halfPage2of2(): PagedResponse {
return {
nodes: MixNodes.list1(),
per_page: 2,
start_next_after: null
}
}
static fullPage2of2(): PagedResponse {
return {
nodes: MixNodes.list2(),
per_page: 2,
start_next_after: null,
}
}
}
export class Gateways {
static single(): GatewayBond {
return {
amount: coins(666, "unym"),
owner: "bob",
gateway: {
mix_host: "1.1.1.1:1234",
clients_host: "ws://1.1.1.1:1235",
location: "London, UK",
identity_key: "bar",
sphinx_key: "foo",
version: "0.10.0",
}
};
}
static list1(): GatewayBond[] {
return [Gateways.single()]
}
static list2(): GatewayBond[] {
return [Gateways.single(), Gateways.single()]
}
static list3(): GatewayBond[] {
return [Gateways.single(), Gateways.single(), Gateways.single()]
}
static list4(): GatewayBond[] {
return [Gateways.single(), Gateways.single(), Gateways.single(), Gateways.single()]
}
}
export class GatewaysResp {
static empty(): PagedGatewayResponse {
return {
nodes: [],
per_page: 2,
start_next_after: "",
}
}
static onePage(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "",
}
}
static page1of2(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "2"
}
}
static halfPage2of2(): PagedGatewayResponse {
return {
nodes: Gateways.list1(),
per_page: 2,
start_next_after: "",
}
}
static fullPage2of2(): PagedGatewayResponse {
return {
nodes: Gateways.list2(),
per_page: 2,
start_next_after: "",
}
}
}
}
@@ -1,92 +0,0 @@
import validatorClient from "../../src/index";
import { config } from '../test-utils/config';
let client: validatorClient;
let mnemonic: string;
beforeEach(async () => {
mnemonic = validatorClient.randomMnemonic();
client = await validatorClient.connect(
mnemonic,
config.NYMD_URL as string,
config.VALIDATOR_API as string,
config.CURRENCY_PREFIX as string,
config.MIXNET_CONTRACT as string,
config.VESTING_CONTRACT as string
);
});
//todos
//we want to mock the majority of these tests
//and keep a few integration tests in place
describe("connect to the nym validator client and perform integration tests against the current testnet", () => {
test("get cached mixnodes", async () => {
try {
const response = await client.getCachedMixnodes();
//expect all mixnodes to have their owner address
response.forEach(mixnodeDetails => {
expect(mixnodeDetails.owner).toHaveLength(43)
});
}
catch (e) {
console.log(e);
}
});
test("get balance of address and denom of the network", async () => {
try {
//provide a users address and get their balance
//we expect their balance to be zero, as it's a new account
const address = await validatorClient.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
const response = await client.getBalance(address);
expect(response.amount).toStrictEqual("0");
expect(response.denom).toBe("unymt");
}
catch (e) {
console.log(e);
}
});
test("get minimium pledge amount for a mixnode", async () => {
try {
const response = await client.minimumMixnodePledge();
expect(response.amount).toBe("100000000");
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
}
catch (e) {
console.log(e);
}
});
test("get minimium gateway pledge amount", async () => {
try {
const response = await client.minimumGatewayPledge();
expect(response.amount).toBe("100000000");
expect(response.denom).toBe(config.CURRENCY_PREFIX as string);
}
catch (e) {
console.log(e);
}
});
test("get current mixnet contract address", () => {
try {
const response = client.mixnetContract;
expect(response).toStrictEqual(config.MIXNET_CONTRACT as string)
}
catch (e) {
console.log(e);
}
});
test("get current vesting contract address", () => {
try {
const response = client.vestingContract;
expect(response).toStrictEqual(config.VESTING_CONTRACT as string)
}
catch (e) {
console.log(e);
}
});
});
@@ -1,66 +0,0 @@
import SigningClient from '../../src/signing-client';
import validator from "../../src/index";
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { config } from '../test-utils/config';
let cosmWasmClient: CosmWasmClient;
let signingClient: SigningClient;
let mnemonic: string;
beforeEach(async () => {
cosmWasmClient = await SigningClient.connect(config.NYMD_URL as string);
mnemonic = validator.randomMnemonic();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic)
signingClient = await SigningClient.connectWithNymSigner(
wallet,
config.NYMD_URL as string,
config.VALIDATOR_API as string,
config.CURRENCY_PREFIX as string);
});
describe("alternate between the signing clients of nym and perform basic requests", () => {
test("retrieve balance using the cosmwasm client", async () => {
try {
const randomAddress = await validator.mnemonicToAddress(mnemonic, config.CURRENCY_PREFIX as string);
const balance = await cosmWasmClient.getBalance(randomAddress, config.CURRENCY_PREFIX as string);
expect(balance.denom).toStrictEqual(config.CURRENCY_PREFIX as string);
expect(balance.amount).toStrictEqual("0");
}
catch (e) {
console.log(e);
}
});
test("get the chain id of the network", async () => {
try {
//pass these values in environment variables in the future
const chainId = await cosmWasmClient.getChainId();
expect(chainId).toStrictEqual(config.CHAIN_ID as string);
}
catch (e) {
console.log(e);
}
});
test("get height then search its block", async () => {
try {
const height = await cosmWasmClient.getHeight()
const block = await cosmWasmClient.getBlock(height);
expect(block.header.chainId).toStrictEqual(config.CHAIN_ID as string);
expect(block.header.height).toStrictEqual(height);
}
catch (e) {
console.log(e);
}
});
test("get current reward pool", async () => {
try {
//this is due to change due to when rewards get distributed
const rewards = await signingClient.getRewardPool(config.MIXNET_CONTRACT as string);
expect(rewards).toStrictEqual("250000000000000");
}
catch (e) {
console.log(e);
}
});
});
@@ -1,70 +0,0 @@
import ValidatorApiQuerier from '../../src/validator-api-querier';
import { config } from '../test-utils/config';
import { now, elapsed} from '../test-utils/utils';
let client: ValidatorApiQuerier;
beforeEach(() => {
client = new ValidatorApiQuerier(config.VALIDATOR_API as string);
});
//todos
//we want to mock the majority of these tests
//and keep a few integration tests in place
describe("call out to the validator api and run queries", () => {
test.skip("build client and get all mixnodes", async () => {
try {
//this test was currently ran against a set of prefix data
//this will change
const ownerAddress = "nymt1ydqkmz0ddpvkd3l0vyf8k5xjrqtcnxxvhlpdsr";
let response = await client.getActiveMixnodes();
expect(response[0].owner).toStrictEqual(ownerAddress);
}
catch (e) {
console.log(e);
}
});
test("get rewarded mixnodes", async () => {
try {
// we assume that all mixnodes will have their owners address
// also active sets will determine rewarded mixnodes
let response = await client.getRewardedMixnodes();
response.forEach(rNode => {
expect(rNode.owner.length).toStrictEqual(43);
})
}
catch (e) {
console.log(e);
}
});
test("get cached gateways and it should be six", async () => {
try {
//current gateways running in sandbox-testnet 6
let response = await client.getCachedGateways();
expect(response.length).toStrictEqual(6);
}
catch (e) {
console.log(e);
}
});
test("get cached mixnodes", async () => {
try {
const start = now();
let response = await client.getCachedMixnodes();
response.forEach(mixnode => {
expect(mixnode.owner.length).toStrictEqual(43);
})
console.log(elapsed(start, true));
}
catch (e) {
console.log(e);
}
});
});
@@ -1,9 +0,0 @@
export const config = {
NYMD_URL: process.env.NYMD_URL,
VALIDATOR_API: process.env.VALIDATOR_API,
MIXNET_CONTRACT: process.env.MIXNET_CONTRACT,
VESTING_CONTRACT: process.env.MIXNET_CONTRACT,
USER_MNEMONIC: process.env.MNEMONIC,
CURRENCY_PREFIX: process.env.CURRENCY_PREFIX,
CHAIN_ID: process.env.CHAIN_ID
}
@@ -1,17 +0,0 @@
// timer actions
// Store current time as `start`
export const now = (eventName = null) => {
if (eventName) {
console.log(`Started ${eventName}..`);
}
return new Date().getTime();
}
//takes arg of start time
export const elapsed = (beginning: number, log = false) => {
const duration = new Date().getTime() - beginning;
if (log) {
console.log(`${duration / 1000}s`);
}
return duration;
}
@@ -1,10 +0,0 @@
const currency = require('../../src/currency');
describe("provide unit tests around the the currency module", () => {
test.skip("convert to native balance", () => {
const decimal = "12.0346";
const value = currency.printableBalanceToNative(decimal);
expect(value).toStrictEqual("12034600");
});
});
@@ -1,27 +0,0 @@
const stargate = require("../../src/stargate-helper");
import { config } from '../test-utils/config';
describe("test the stargate functions within the client", () => {
test("test that the gas price is returned correctly", () => {
const nymCurrency = config.CURRENCY_PREFIX as string;
const getGasPrice = stargate.nymGasPrice(nymCurrency);
expect(getGasPrice.denom).toBe(`u${nymCurrency}`);
});
test("provide invalid type returns an error message", () => {
//pass invalid type
expect(() => {
const nymCurrency = 13;
stargate.nymGasPrice(nymCurrency);
}).toThrow("13 is not of type string");
});
//provide test for downloading wasm
//mock this test
// test.skip("providing nothing returns", async () => {
// //pass invalid type
// const downloadWasm = stargate.downloadWasm("http://localhost");
// console.log(downloadWasm);
// })
});
@@ -1,19 +0,0 @@
import validator from "../../src/index";
import { config } from '../test-utils/config';
const NETWORK_DENOM = config.CURRENCY_PREFIX as string;
describe("perform basic interactions with the validator", () => {
test("build client and get all mixnodes", async () => {
const mnemonic = validator.randomMnemonic();
const mnemonicCount = mnemonic.split(" ").length;
expect(mnemonicCount).toStrictEqual(24);
});
test("build client and get all mixnodes", async () => {
const buildMnemonic = validator.randomMnemonic();
const mnemonic = await validator.mnemonicToAddress(buildMnemonic, NETWORK_DENOM);
expect(mnemonic).toHaveLength(43);
expect(mnemonic).toContain(NETWORK_DENOM);
});
});
+2 -10
View File
@@ -5,11 +5,7 @@
"esModuleInterop": true,
"strict": true,
"declaration": true,
"outDir": "./dist",
"types": [
"jest",
"node",
]
"outDir": "./dist"
},
"typedocOptions": {
"entryPoints": [
@@ -20,11 +16,7 @@
"exclude": [
"dist",
"examples",
"node_modules"
],
"include": [
"tests",
"./tests/*/*.tsx",
"./tests/*/*.ts"
"node_modules"
]
}
+1248 -2445
View File
File diff suppressed because it is too large Load Diff
+1 -1
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 = "0.12.0"
version = "0.11.0"
edition = "2018"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
-13
View File
@@ -27,7 +27,6 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
#[wasm_bindgen]
pub struct NymClient {
validator_server: Url,
testnet_mode: bool,
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
// however, once we eventually combine this code with the native-client's, it will make things
@@ -73,7 +72,6 @@ impl NymClient {
on_message: None,
on_gateway_connect: None,
testnet_mode: false,
}
}
@@ -86,11 +84,6 @@ impl NymClient {
self.on_gateway_connect = Some(on_connect)
}
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
console_log!("Setting testnet mode to {}", testnet_mode);
self.testnet_mode = testnet_mode;
}
fn self_recipient(&self) -> Recipient {
Recipient::new(
*self.identity.public_key(),
@@ -108,8 +101,6 @@ impl NymClient {
// Right now it's impossible to have async exported functions to take `&self` rather than self
pub async fn initial_setup(self) -> Self {
let testnet_mode = self.testnet_mode;
#[cfg(feature = "coconut")]
let bandwidth_controller = Some(BandwidthController::new(
vec![self.validator_server.clone()],
@@ -135,10 +126,6 @@ impl NymClient {
bandwidth_controller,
);
if testnet_mode {
gateway_client.set_testnet_mode(true)
}
gateway_client
.authenticate_and_start()
.await
@@ -40,7 +40,6 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
testnet_mode: bool,
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -76,7 +75,6 @@ impl GatewayClient {
) -> Self {
GatewayClient {
authenticated: false,
testnet_mode: false,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -92,10 +90,6 @@ impl GatewayClient {
}
}
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
self.testnet_mode = testnet_mode
}
// TODO: later convert into proper builder methods
pub fn with_reconnection_on_failure(&mut self, should_reconnect_on_failure: bool) {
self.should_reconnect_on_failure = should_reconnect_on_failure
@@ -125,7 +119,6 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
testnet_mode: false,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -520,17 +513,6 @@ impl GatewayClient {
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? {
ServerResponse::Bandwidth { available_total } => Ok(available_total),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
@@ -543,10 +525,6 @@ impl GatewayClient {
}
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
if self.testnet_mode {
info!("The client is running in testnet mode - attempting to claim bandwidth without a credential");
return self.try_claim_testnet_bandwidth().await;
}
#[cfg(feature = "coconut")]
let credential = self
@@ -10,7 +10,6 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
mixnet-contract = { path="../../../common/mixnet-contract" }
vesting-contract = { path="../../../contracts/vesting" }
serde = { version="1", features=["derive"] }
serde_json = "1"
reqwest = { version="0.11", features=["json"] }
@@ -27,13 +26,12 @@ network-defaults = { path = "../../network-defaults" }
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
#cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"], optional = true }
cosmrs = { version = "0.3", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.9", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true }
ts-rs = {version = "5.1", optional = true}
[features]
@@ -6,18 +6,13 @@ use crate::nymd::{
error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient,
};
#[cfg(feature = "nymd-client")]
use mixnet_contract::ContractStateParams;
use mixnet_contract::StateParams;
use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract::{GatewayBond, MixNodeBond, MixnodeRewardingStatusResponse};
#[cfg(feature = "nymd-client")]
use mixnet_contract::{
Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse,
};
use mixnet_contract::{GatewayBond, MixNodeBond};
#[cfg(feature = "nymd-client")]
use std::str::FromStr;
use mixnet_contract::{RawDelegationData, RewardingIntervalResponse};
use url::Url;
#[cfg(feature = "nymd-client")]
@@ -25,7 +20,6 @@ pub struct Config {
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -38,12 +32,10 @@ impl Config {
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
) -> Self {
Config {
nymd_url,
mixnet_contract_address,
vesting_contract_address,
api_url,
mixnode_page_limit: None,
gateway_page_limit: None,
@@ -70,7 +62,6 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
mixnet_contract_address: Option<cosmrs::AccountId>,
vesting_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -92,14 +83,11 @@ impl Client<SigningNymdClient> {
let nymd_client = NymdClient::connect_with_mnemonic(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone(),
config.vesting_contract_address.clone(),
mnemonic.clone(),
None,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: Some(mnemonic),
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -113,9 +101,7 @@ impl Client<SigningNymdClient> {
self.nymd = NymdClient::connect_with_mnemonic(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone(),
self.vesting_contract_address.clone(),
self.mnemonic.clone().unwrap(),
None,
)?;
Ok(())
}
@@ -127,19 +113,16 @@ impl Client<QueryNymdClient> {
let validator_api_client = validator_api::Client::new(config.api_url.clone());
let nymd_client = NymdClient::connect(
config.nymd_url.as_str(),
config.mixnet_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS)
.unwrap()
}),
config.vesting_contract_address.clone().unwrap_or_else(|| {
cosmrs::AccountId::from_str(network_defaults::DEFAULT_VESTING_CONTRACT_ADDRESS)
.unwrap()
}),
config
.mixnet_contract_address
.clone()
.ok_or(ValidatorClientError::NymdError(
NymdError::NoContractAddressAvailable,
))?,
)?;
Ok(Client {
mixnet_contract_address: config.mixnet_contract_address,
vesting_contract_address: config.vesting_contract_address,
mnemonic: None,
mixnode_page_limit: config.mixnode_page_limit,
gateway_page_limit: config.gateway_page_limit,
@@ -153,7 +136,6 @@ impl Client<QueryNymdClient> {
self.nymd = NymdClient::connect(
new_endpoint.as_ref(),
self.mixnet_contract_address.clone().unwrap(),
self.vesting_contract_address.clone().unwrap(),
)?;
Ok(())
}
@@ -183,18 +165,11 @@ impl<C> Client<C> {
Ok(self.validator_api.get_gateways().await?)
}
pub async fn get_contract_settings(&self) -> Result<ContractStateParams, ValidatorClientError>
pub async fn get_state_params(&self) -> Result<StateParams, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_contract_settings().await?)
}
pub async fn get_mixnet_contract_version(&self) -> Result<MixnetContractVersion, NymdError>
where
C: CosmWasmClient + Sync,
{
Ok(self.nymd.get_mixnet_contract_version().await?)
Ok(self.nymd.get_state_params().await?)
}
pub async fn get_current_rewarding_interval(
@@ -325,7 +300,9 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_network_delegations(&self) -> Result<Vec<Delegation>, ValidatorClientError>
pub async fn get_all_nymd_mixnode_delegations(
&self,
) -> Result<Vec<mixnet_contract::UnpackedDelegation<RawDelegationData>>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -334,7 +311,7 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_all_network_delegations_paged(
.get_all_mix_delegations_paged(
start_after.take(),
self.mixnode_delegations_page_limit,
)
@@ -351,10 +328,10 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_delegator_delegations(
pub async fn get_all_nymd_reverse_mixnode_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<Delegation>, ValidatorClientError>
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
@@ -363,13 +340,13 @@ impl<C> Client<C> {
loop {
let mut paged_response = self
.nymd
.get_delegator_delegations_paged(
delegation_owner.to_string(),
.get_reverse_mix_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegations);
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
@@ -381,6 +358,28 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_mixnode_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_mix_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -4,7 +4,7 @@
use crate::nymd::cosmwasm_client::helpers::create_pagination;
use crate::nymd::cosmwasm_client::types::{
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
SequenceResponse, SimulateResponse,
SequenceResponse,
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
@@ -14,19 +14,15 @@ use cosmrs::proto::cosmos::auth::v1beta1::{
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
};
use cosmrs::proto::cosmos::tx::v1beta1::{
SimulateRequest, SimulateResponse as ProtoSimulateResponse,
};
use cosmrs::proto::cosmwasm::wasm::v1::*;
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Code as AbciCode;
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{tx, AccountId, Coin, Denom, Tx};
use cosmrs::{tx, AccountId, Coin, Denom};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -53,11 +49,6 @@ pub trait CosmWasmClient: rpc::Client {
let res = self.abci_query(path, buf, None, false).await?;
match res.code {
AbciCode::Err(code) => return Err(NymdError::AbciError(code, res.log)),
AbciCode::Ok => (),
}
Ok(Res::decode(res.value.as_ref())?)
}
@@ -222,7 +213,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/Codes".parse().unwrap());
let mut raw_codes = Vec::new();
let mut pagination = None;
@@ -249,7 +240,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_code_details(&self, code_id: ContractCodeId) -> Result<CodeDetails, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/Code".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/Code".parse().unwrap());
let req = QueryCodeRequest { code_id };
@@ -264,7 +255,11 @@ pub trait CosmWasmClient: rpc::Client {
}
}
async fn get_contracts(&self, code_id: ContractCodeId) -> Result<Vec<AccountId>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractsByCode".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractsByCode"
.parse()
.unwrap(),
);
let mut raw_contracts = Vec::new();
let mut pagination = None;
@@ -295,7 +290,7 @@ pub trait CosmWasmClient: rpc::Client {
}
async fn get_contract(&self, address: &AccountId) -> Result<Contract, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractInfo".parse().unwrap());
let path = Some("/cosmwasm.wasm.v1beta1.Query/ContractInfo".parse().unwrap());
let req = QueryContractInfoRequest {
address: address.to_string(),
@@ -320,7 +315,11 @@ pub trait CosmWasmClient: rpc::Client {
&self,
address: &AccountId,
) -> Result<Vec<ContractCodeHistoryEntry>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/ContractHistory".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/ContractHistory"
.parse()
.unwrap(),
);
let mut raw_entries = Vec::new();
let mut pagination = None;
@@ -354,7 +353,11 @@ pub trait CosmWasmClient: rpc::Client {
address: &AccountId,
query_data: Vec<u8>,
) -> Result<Vec<u8>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/RawContractState".parse().unwrap());
let path = Some(
"/cosmwasm.wasm.v1beta1.Query/RawContractState"
.parse()
.unwrap(),
);
let req = QueryRawContractStateRequest {
address: address.to_string(),
@@ -378,7 +381,7 @@ pub trait CosmWasmClient: rpc::Client {
for<'a> T: Deserialize<'a>,
{
let path = Some(
"/cosmwasm.wasm.v1.Query/SmartContractState"
"/cosmwasm.wasm.v1beta1.Query/SmartContractState"
.parse()
.unwrap(),
);
@@ -397,27 +400,4 @@ pub trait CosmWasmClient: rpc::Client {
Ok(serde_json::from_slice(&res.data)?)
}
// deprecation warning is due to the fact the protobuf files built were based on cosmos-sdk 0.44,
// where they prefer using tx_bytes directly. However, in 0.42, which we are using at the time
// of writing this, the option does not work
#[allow(deprecated)]
async fn query_simulate(
&self,
tx: Option<Tx>,
tx_bytes: Vec<u8>,
) -> Result<SimulateResponse, NymdError> {
let path = Some("/cosmos.tx.v1beta1.Service/Simulate".parse().unwrap());
let req = SimulateRequest {
tx: tx.map(Into::into),
tx_bytes,
};
let res = self
.make_abci_query::<_, ProtoSimulateResponse>(path, req)
.await?;
res.try_into()
}
}
@@ -29,7 +29,7 @@ pub(crate) fn find_attribute<'a>(
) -> Option<&'a cosmwasm_std::Attribute> {
logs.iter()
.flat_map(|log| log.events.iter())
.find(|event| event.ty == event_type)?
.find(|event| event.kind == event_type)?
.attributes
.iter()
.find(|attr| attr.key == attribute_key)
@@ -61,7 +61,7 @@ mod tests {
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].msg_index, 0);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "1");
}
@@ -76,12 +76,12 @@ mod tests {
assert_eq!(parsed[2].msg_index, 2);
assert_eq!(parsed[0].events.len(), 1);
assert_eq!(parsed[0].events[0].ty, "message");
assert_eq!(parsed[0].events[0].kind, "message");
assert_eq!(parsed[0].events[0].attributes[3].key, "code_id");
assert_eq!(parsed[0].events[0].attributes[3].value, "9");
assert_eq!(parsed[2].events.len(), 1);
assert_eq!(parsed[2].events[0].ty, "message");
assert_eq!(parsed[2].events[0].kind, "message");
assert_eq!(parsed[2].events[0].attributes[2].key, "signer");
assert_eq!(
parsed[2].events[0].attributes[2].value,
@@ -3,7 +3,6 @@
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::GasPrice;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use std::convert::TryInto;
@@ -24,10 +23,9 @@ where
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<signing_client::Client, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
signing_client::Client::connect_with_signer(endpoint, signer, gas_price)
signing_client::Client::connect_with_signer(endpoint, signer)
}
@@ -1,100 +1,37 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::convert::TryInto;
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::proto::cosmos::tx::signing::v1beta1::SignMode;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{self, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, AccountId, Any, Coin, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use crate::nymd::cosmwasm_client::client::CosmWasmClient;
use crate::nymd::cosmwasm_client::helpers::{compress_wasm_code, CheckResponse};
use crate::nymd::cosmwasm_client::logs::{self, parse_raw_logs};
use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::error::NymdError;
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use crate::nymd::{CosmosCoin, GasPrice};
// we need to have **a** valid secp256k1 signature for simulation purposes.
// it doesn't matter what it is as long as it parses correctly
const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
54, 167, 169, 61, 100, 173, 231, 87, 1, 113, 179, 49, 102, 141, 67, 22, 170, 153, 52, 88, 178,
159, 200, 11, 37, 138, 76, 221, 187, 70, 104, 123, 98, 216, 190, 249, 149, 81, 1, 158, 0, 220,
32, 147, 101, 60, 64, 77, 44, 83, 221, 119, 170, 124, 109, 177, 73, 116, 46, 57, 102, 181, 98,
91,
];
use async_trait::async_trait;
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Any, Coin};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
fn signer(&self) -> &DirectSecp256k1HdWallet;
fn gas_price(&self) -> &GasPrice;
fn signer_public_key(&self, signer_address: &AccountId) -> Option<tx::SignerPublicKey> {
let signer_accounts = self.signer().try_derive_accounts().ok()?;
let account_from_signer = signer_accounts
.iter()
.find(|account| &account.address == signer_address)?;
let public_key = account_from_signer.public_key;
Some(public_key.into())
}
async fn simulate(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
memo: impl Into<String> + Send + 'static,
) -> Result<SimulateResponse, NymdError> {
let public_key = self.signer_public_key(signer_address);
let sequence_response = self.get_sequence(signer_address).await?;
let partial_tx = Tx {
body: tx::Body {
messages,
memo: memo.into(),
timeout_height: 0u32.into(),
extension_options: vec![],
non_critical_extension_options: vec![],
},
auth_info: tx::AuthInfo {
signer_infos: vec![tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_response.sequence,
}],
fee: tx::Fee::from_amount_and_gas(
CosmosCoin {
denom: "".parse().unwrap(),
amount: 0u64.into(),
},
0,
),
},
signatures: vec![DUMMY_SECP256K1_SIGNATURE.try_into().unwrap()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
}
async fn upload(
&self,
sender_address: &AccountId,
wasm_code: Vec<u8>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
mut meta: Option<UploadMeta>,
) -> Result<UploadResult, NymdError> {
let compressed = compress_wasm_code(&wasm_code)?;
let compressed_size = compressed.len();
@@ -105,6 +42,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let upload_msg = cosmwasm::MsgStoreCode {
sender: sender_address.clone(),
wasm_byte_code: compressed,
source: meta
.as_mut()
.map(|meta| meta.source.take())
.unwrap_or_default(),
builder: meta
.as_mut()
.map(|meta| meta.builder.take())
.unwrap_or_default(),
instantiate_permission: Default::default(),
}
.to_any()
@@ -116,13 +61,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or code_id is not a number, there's no way we can recover, we're probably connected
// to wrong validator or something
let code_id = logs::find_attribute(&logs, "store_code", "code_id")
let code_id = logs::find_attribute(&logs, "message", "code_id")
.unwrap()
.value
.parse()
@@ -136,7 +80,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
code_id,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -168,7 +111,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
// now this is a weird one. the protobuf files say this field is optional,
// but if you omit it, the initialisation will fail CheckTx
label: Some(label),
msg: serde_json::to_vec(msg)?,
init_msg: serde_json::to_vec(msg)?,
funds: options.map(|options| options.funds).unwrap_or_default(),
}
.to_any()
@@ -180,13 +123,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.check_response()?;
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
// TODO: should those strings be extracted into some constants?
// the reason I think unwrap here is fine is that if the transaction succeeded and those
// fields do not exist or address is malformed, there's no way we can recover, we're probably connected
// to wrong validator or something
let contract_address = logs::find_attribute(&logs, "instantiate", "_contract_address")
let contract_address = logs::find_attribute(&logs, "message", "contract_address")
.unwrap()
.value
.parse()
@@ -196,7 +138,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
contract_address,
logs,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -221,12 +162,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -249,12 +187,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -274,7 +209,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
code_id,
msg: serde_json::to_vec(msg)?,
migrate_msg: serde_json::to_vec(msg)?,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
@@ -284,12 +219,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -319,12 +251,9 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -359,12 +288,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
debug!(
"gas wanted: {:?}, gas used: {:?}",
tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used
);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
}
@@ -385,36 +316,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
.await?
.check_response()
}
async fn send_tokens_multiple<I>(
&self,
sender_address: &AccountId,
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
where
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
{
let messages = msgs
.into_iter()
.map(|(to_address, amount)| {
MsgSend {
from_address: sender_address.clone(),
to_address,
amount,
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.await?
.check_response()
.await
}
async fn delegate_tokens(
@@ -434,8 +336,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
.await?
.check_response()
.await
}
async fn undelegate_tokens(
@@ -455,8 +356,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
.await?
.check_response()
.await
}
async fn withdraw_rewards(
@@ -474,45 +374,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
.await?
.check_response()
}
// in this particular case we cannot generalise the argument to `&str` due to lifetime constraints
#[allow(clippy::ptr_arg)]
async fn determine_transaction_fee(
&self,
signer_address: &AccountId,
messages: &[Any],
fee: Fee,
memo: &String,
) -> Result<tx::Fee, NymdError> {
let fee = match fee {
Fee::Manual(fee) => fee,
Fee::Auto(multiplier) => {
debug!("Trying to simulate gas costs...");
// from what I've seen in manual testing, gas estimation does not exist if transaction
// fails to get executed (for example if you send 'BondMixnode" with invalid signature)
let gas_estimation = self
.simulate(signer_address, messages.to_vec(), memo.clone())
.await?
.gas_info
.ok_or(NymdError::GasEstimationFailure)?
.gas_used;
let multiplier = multiplier.unwrap_or(DEFAULT_SIMULATED_GAS_MULTIPLIER);
let gas = ((gas_estimation.value() as f32 * multiplier) as u64).into();
debug!("Gas estimation: {}", gas_estimation);
debug!("Multiplying the estimation by {}", multiplier);
debug!("Final gas limit used: {}", gas);
let fee = self.gas_price() * gas;
tx::Fee::from_amount_and_gas(fee, gas)
}
};
debug!("Fee used for the transaction: {:?}", fee);
Ok(fee)
.await
}
/// Broadcast a transaction, returning immediately.
@@ -523,10 +385,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_async::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -543,10 +401,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_sync::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -563,11 +417,6 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
let memo = memo.into();
let fee = self
.determine_transaction_fee(signer_address, &messages, fee, &memo)
.await?;
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
let tx_bytes = tx_raw
.to_bytes()
@@ -580,7 +429,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: tx::Fee,
fee: Fee,
memo: impl Into<String> + Send + 'static,
signer_data: SignerData,
) -> Result<tx::Raw, NymdError> {
@@ -618,7 +467,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: tx::Fee,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<tx::Raw, NymdError> {
// TODO: Future optimisation: rather than grabbing current account_number and sequence
@@ -636,28 +485,22 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
}
impl Client {
pub fn connect_with_signer<U>(
endpoint: U,
signer: DirectSecp256k1HdWallet,
gas_price: Option<GasPrice>,
) -> Result<Self, NymdError>
where
U: TryInto<HttpClientUrl, Error = TendermintRpcError>,
{
let rpc_client = HttpClient::new(endpoint)?;
Ok(Client {
rpc_client,
signer,
gas_price: gas_price.unwrap_or_default(),
})
Ok(Client { rpc_client, signer })
}
}
@@ -679,8 +522,4 @@ impl SigningCosmWasmClient for Client {
fn signer(&self) -> &DirectSecp256k1HdWallet {
&self.signer
}
fn gas_price(&self) -> &GasPrice {
&self.gas_price
}
}
@@ -7,19 +7,15 @@ use crate::nymd::cosmwasm_client::logs::Log;
use crate::nymd::error::NymdError;
use cosmrs::crypto::PublicKey;
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmrs::proto::cosmos::base::abci::v1beta1::{
GasInfo as ProtoGasInfo, Result as ProtoAbciResult,
};
use cosmrs::proto::cosmos::tx::v1beta1::SimulateResponse as ProtoSimulateResponse;
use cosmrs::proto::cosmwasm::wasm::v1::{
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::tendermint::chain;
use cosmrs::tx::{AccountNumber, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use serde::Serialize;
use std::convert::{TryFrom, TryInto};
use std::convert::TryFrom;
pub type ContractCodeId = u64;
@@ -74,6 +70,18 @@ pub struct Code {
/// sha256 hash of the code stored
pub data_hash: Vec<u8>,
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
impl TryFrom<CodeInfoResponse> for Code {
@@ -84,16 +92,31 @@ impl TryFrom<CodeInfoResponse> for Code {
code_id,
creator,
data_hash,
source,
builder,
} = value;
let creator = creator
.parse()
.map_err(|_| NymdError::MalformedAccountAddress(creator))?;
let source = if source.is_empty() {
None
} else {
Some(source)
};
let builder = if builder.is_empty() {
None
} else {
Some(builder)
};
Ok(Code {
code_id,
creator,
data_hash,
source,
builder,
})
}
}
@@ -219,101 +242,6 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
}
}
#[derive(Debug)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: Gas,
/// GasUsed is the amount of gas actually consumed.
pub gas_used: Gas,
}
impl From<ProtoGasInfo> for GasInfo {
fn from(value: ProtoGasInfo) -> Self {
GasInfo {
gas_wanted: value.gas_wanted.into(),
gas_used: value.gas_used.into(),
}
}
}
impl GasInfo {
pub fn new(gas_wanted: Gas, gas_used: Gas) -> Self {
GasInfo {
gas_wanted,
gas_used,
}
}
}
#[derive(Debug)]
pub struct AbciResult {
/// Data is any data returned from message or handler execution. It MUST be
/// length prefixed in order to separate data from multiple message executions.
pub data: Vec<u8>,
/// Log contains the log information from message or handler execution.
// todo: try to parse into Log?
pub log: String,
/// Events contains a slice of Event objects that were emitted during message
/// or handler execution.
pub events: Vec<abci::Event>,
}
impl TryFrom<ProtoAbciResult> for AbciResult {
type Error = NymdError;
fn try_from(value: ProtoAbciResult) -> Result<Self, Self::Error> {
let mut events = Vec::with_capacity(value.events.len());
for proto_event in value.events.into_iter() {
let type_str = proto_event.r#type;
let mut attributes = Vec::with_capacity(proto_event.attributes.len());
for proto_attribute in proto_event.attributes.into_iter() {
let stringified_ked = String::from_utf8(proto_attribute.key)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
let stringified_value = String::from_utf8(proto_attribute.value)
.map_err(|_| NymdError::DeserializationError("EventAttributeKey".to_owned()))?;
attributes.push(abci::tag::Tag {
key: stringified_ked.parse().unwrap(),
value: stringified_value.parse().unwrap(),
})
}
events.push(abci::Event {
type_str,
attributes,
})
}
Ok(AbciResult {
data: value.data,
log: value.log,
events,
})
}
}
#[derive(Debug)]
pub struct SimulateResponse {
pub gas_info: Option<GasInfo>,
pub result: Option<AbciResult>,
}
impl TryFrom<ProtoSimulateResponse> for SimulateResponse {
type Error = NymdError;
fn try_from(value: ProtoSimulateResponse) -> Result<Self, Self::Error> {
Ok(SimulateResponse {
gas_info: value.gas_info.map(|gas_info| gas_info.into()),
result: value.result.map(|result| result.try_into()).transpose()?,
})
}
}
// ##############################################################################
// types specific to the signing client (perhaps they should go to separate file)
// ##############################################################################
@@ -326,6 +254,21 @@ pub struct SignerData {
pub chain_id: chain::Id,
}
#[derive(Debug)]
pub struct UploadMeta {
/// An URL to a .tar.gz archive of the source code of the contract,
/// which can be used to reproducibly build the Wasm bytecode.
///
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub source: Option<String>,
/// A docker image (including version) to reproducibly build the Wasm bytecode from the source code.
///
/// @example ```cosmwasm/rust-optimizer:0.8.0```
/// @see https://github.com/CosmWasm/cosmwasm-verify
pub builder: Option<String>,
}
#[derive(Debug)]
pub struct UploadResult {
/// Size of the original wasm code in bytes
@@ -347,8 +290,6 @@ pub struct UploadResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -375,8 +316,6 @@ pub struct InstantiateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -385,8 +324,6 @@ pub struct ChangeAdminResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -395,8 +332,6 @@ pub struct MigrateResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
#[derive(Debug)]
@@ -405,6 +340,4 @@ pub struct ExecuteResult {
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
pub gas_info: GasInfo,
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::{abci, block};
use cosmrs::tendermint::block;
use cosmrs::{bip32, tx, AccountId};
use std::io;
use thiserror::Error;
@@ -102,12 +102,6 @@ pub enum NymdError {
#[error("The provided gas price is malformed")]
MalformedGasPrice,
#[error("Failed to estimate gas price for the transaction")]
GasEstimationFailure,
#[error("Abci query failed with code {0} - {1}")]
AbciError(u32, abci::Log),
}
impl NymdError {
@@ -1,33 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmrs::tx;
pub mod gas_price;
pub mod helpers;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: f32 = 1.3;
#[derive(Debug, Clone)]
pub enum Fee {
Manual(tx::Fee),
Auto(Option<f32>),
}
impl From<tx::Fee> for Fee {
fn from(fee: tx::Fee) -> Self {
Fee::Manual(fee)
}
}
impl From<f32> for Fee {
fn from(multiplier: f32) -> Self {
Fee::Auto(Some(multiplier))
}
}
impl Default for Fee {
fn default() -> Self {
Fee::Auto(Some(DEFAULT_SIMULATED_GAS_MULTIPLIER))
}
}
@@ -17,29 +17,17 @@ pub enum Operation {
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
UnbondGatewayOnBehalf,
UpdateContractSettings,
UpdateStateParams,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
@@ -55,27 +43,14 @@ impl fmt::Display for Operation {
Operation::ChangeAdmin => f.write_str("ChangeAdmin"),
Operation::Send => f.write_str("Send"),
Operation::BondMixnode => f.write_str("BondMixnode"),
Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"),
Operation::UnbondMixnode => f.write_str("UnbondMixnode"),
Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"),
Operation::BondGateway => f.write_str("BondGateway"),
Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"),
Operation::UnbondGateway => f.write_str("UnbondGateway"),
Operation::UnbondGatewayOnBehalf => f.write_str("UnbondGatewayOnBehalf"),
Operation::DelegateToMixnode => f.write_str("DelegateToMixnode"),
Operation::DelegateToMixnodeOnBehalf => f.write_str("DelegateToMixnodeOnBehalf"),
Operation::UndelegateFromMixnode => f.write_str("UndelegateFromMixnode"),
Operation::UndelegateFromMixnodeOnBehalf => {
f.write_str("UndelegateFromMixnodeOnBehalf")
}
Operation::UpdateContractSettings => f.write_str("UpdateContractSettings"),
Operation::UpdateStateParams => f.write_str("UpdateStateParams"),
Operation::BeginMixnodeRewarding => f.write_str("BeginMixnodeRewarding"),
Operation::FinishMixnodeRewarding => f.write_str("FinishMixnodeRewarding"),
Operation::TrackUnbondGateway => f.write_str("TrackUnbondGateway"),
Operation::TrackUnbondMixnode => f.write_str("TrackUnbondMixnode"),
Operation::WithdrawVestedCoins => f.write_str("WithdrawVestedCoins"),
Operation::TrackUndelegation => f.write_str("TrackUndelegation"),
Operation::CreatePeriodicVestingAccount => f.write_str("CreatePeriodicVestingAccount"),
}
}
}
@@ -84,34 +59,23 @@ impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
match self {
Operation::Upload => 3_000_000u64.into(),
Operation::Upload => 2_500_000u64.into(),
Operation::Init => 500_000u64.into(),
Operation::Migrate => 200_000u64.into(),
Operation::ChangeAdmin => 80_000u64.into(),
Operation::Send => 80_000u64.into(),
Operation::BondMixnode => 175_000u64.into(),
Operation::BondMixnodeOnBehalf => 200_000u64.into(),
Operation::UnbondMixnode => 175_000u64.into(),
Operation::UnbondMixnodeOnBehalf => 175_000u64.into(),
Operation::DelegateToMixnode => 175_000u64.into(),
Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(),
Operation::UndelegateFromMixnode => 175_000u64.into(),
Operation::UndelegateFromMixnodeOnBehalf => 175_000u64.into(),
Operation::BondGateway => 175_000u64.into(),
Operation::BondGatewayOnBehalf => 200_000u64.into(),
Operation::UnbondGateway => 175_000u64.into(),
Operation::UnbondGatewayOnBehalf => 200_000u64.into(),
Operation::UpdateContractSettings => 175_000u64.into(),
Operation::UpdateStateParams => 175_000u64.into(),
Operation::BeginMixnodeRewarding => 175_000u64.into(),
Operation::FinishMixnodeRewarding => 175_000u64.into(),
Operation::TrackUnbondGateway => 175_000u64.into(),
Operation::TrackUnbondMixnode => 175_000u64.into(),
Operation::WithdrawVestedCoins => 175_000u64.into(),
Operation::TrackUndelegation => 175_000u64.into(),
Operation::CreatePeriodicVestingAccount => 175_000u64.into(),
}
}
@@ -125,8 +89,9 @@ impl Operation {
Fee::from_amount_and_gas(fee, gas_limit)
}
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
Self::determine_custom_fee(gas_price, self.default_gas_limit())
pub(crate) fn determine_fee(&self, gas_price: &GasPrice, gas_limit: Option<Gas>) -> Fee {
let gas_limit = gas_limit.unwrap_or_else(|| self.default_gas_limit());
Self::determine_custom_fee(gas_price, gas_limit)
}
}
@@ -38,7 +38,7 @@ impl<'a> Mul<Gas> for &'a GasPrice {
// however, realistically that is impossible to happen as the resultant value
// would have to be way higher than our token limit of 10^15 (1 billion of tokens * 1 million for denomination)
// and max value of u128 is approximately 10^38
if limit_uint128 * gas_price_numerator > amount * gas_price_denominator {
if limit_uint128.u128() * gas_price_numerator > amount.u128() * gas_price_denominator {
amount += Uint128::new(1);
}
File diff suppressed because it is too large Load Diff
@@ -1,8 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod vesting_query_client;
mod vesting_signing_client;
pub use vesting_query_client::VestingQueryClient;
pub use vesting_signing_client::VestingSigningClient;
@@ -1,176 +0,0 @@
// Copyright 2021 - 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 cosmwasm_std::{Coin, Timestamp};
use vesting_contract::messages::QueryMsg as VestingQueryMsg;
#[async_trait]
pub trait VestingQueryClient {
async fn locked_coins(
&self,
address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError>;
async fn vesting_end_time(&self, vesting_account_address: &str)
-> Result<Timestamp, NymdError>;
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError>;
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError>;
}
#[async_trait]
impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
async fn locked_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::LockedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn spendable_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::SpendableCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vested_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestedCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_coins(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetVestingCoins {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_start_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetStartTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn vesting_end_time(
&self,
vesting_account_address: &str,
) -> Result<Timestamp, NymdError> {
let request = VestingQueryMsg::GetEndTime {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn original_vesting(&self, vesting_account_address: &str) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetOriginalVesting {
vesting_account_address: vesting_account_address.to_string(),
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_free(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedFree {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
async fn delegated_vesting(
&self,
vesting_account_address: &str,
block_time: Option<Timestamp>,
) -> Result<Coin, NymdError> {
let request = VestingQueryMsg::GetDelegatedVesting {
vesting_account_address: vesting_account_address.to_string(),
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.await
}
}
@@ -1,297 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
use crate::nymd::cosmwasm_client::types::ExecuteResult;
use crate::nymd::error::NymdError;
use crate::nymd::fee::helpers::Operation;
use crate::nymd::{cosmwasm_coin_to_cosmos_coin, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg;
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
&self,
owner_address: &str,
staking_address: Option<String>,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondGateway",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondGateway);
let req = VestingExecuteMsg::UnbondGateway {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondGateway",
vec![],
)
.await
}
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondGateway);
let req = VestingExecuteMsg::TrackUnbondGateway {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondGateway",
vec![],
)
.await
}
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::BondMixnode",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
)
.await
}
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondMixnode);
let req = VestingExecuteMsg::UnbondMixnode {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UnbondMixnode",
vec![],
)
.await
}
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondMixnode);
let req = VestingExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUnbondMixnode",
vec![],
)
.await
}
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::WithdrawVestedCoins);
let req = VestingExecuteMsg::WithdrawVestedCoins { amount };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::WithdrawVested",
vec![],
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUndelegation);
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::TrackUndelegation",
vec![],
)
.await
}
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::DeledateToMixnode",
vec![cosmwasm_coin_to_cosmos_coin(amount.to_owned())],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
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(
&self,
owner_address: &str,
staking_address: Option<String>,
start_time: Option<u64>,
amount: Coin,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let req = VestingExecuteMsg::CreateAccount {
owner_address: owner_address.to_string(),
staking_address,
start_time,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
)
.await
}
}
@@ -10,7 +10,7 @@ use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug, Clone)]
#[derive(Debug)]
struct Secp256k1Derivation {
hd_path: DerivationPath,
prefix: String,
@@ -40,7 +40,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
@@ -202,28 +202,14 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use super::*;
use network_defaults::BECH32_PREFIX;
#[test]
fn generating_account_addresses() {
let (addr1, addr2, addr3) = match BECH32_PREFIX {
"punk" => (
"punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2",
"punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn",
"punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962",
),
"nymt" => (
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
),
_ => panic!("Test needs to be updated with new bech32 prefix"),
};
// test vectors produced from our js wallet
let mnemonic_address = vec![
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", addr1),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", addr2),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", addr3)
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", "punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2"),
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", "punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn"),
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", "punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962")
];
for (mnemonic, address) in mnemonic_address.into_iter() {
@@ -1,4 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
-2
View File
@@ -19,9 +19,7 @@ x25519-dalek = "1.1"
ed25519-dalek = "1.0"
log = "0.4"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
# internal
nymsphinx-types = { path = "../nymsphinx/types" }
pemstore = { path = "../../common/pemstore" }
config = { path="../../common/config" }
+18 -34
View File
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
use ed25519_dalek::ed25519::signature::Signature as SignatureTrait;
pub use ed25519_dalek::SignatureError;
pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH};
use nymsphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -10,33 +10,33 @@ use rand::{CryptoRng, RngCore};
use std::fmt::{self, Display, Formatter};
#[derive(Debug)]
pub enum Ed25519RecoveryError {
pub enum KeyRecoveryError {
MalformedBytes(SignatureError),
MalformedString(bs58::decode::Error),
}
impl From<SignatureError> for Ed25519RecoveryError {
impl From<SignatureError> for KeyRecoveryError {
fn from(err: SignatureError) -> Self {
Ed25519RecoveryError::MalformedBytes(err)
KeyRecoveryError::MalformedBytes(err)
}
}
impl From<bs58::decode::Error> for Ed25519RecoveryError {
impl From<bs58::decode::Error> for KeyRecoveryError {
fn from(err: bs58::decode::Error) -> Self {
Ed25519RecoveryError::MalformedString(err)
KeyRecoveryError::MalformedString(err)
}
}
impl fmt::Display for Ed25519RecoveryError {
impl fmt::Display for KeyRecoveryError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Ed25519RecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
Ed25519RecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
KeyRecoveryError::MalformedBytes(err) => write!(f, "malformed bytes - {}", err),
KeyRecoveryError::MalformedString(err) => write!(f, "malformed string - {}", err),
}
}
}
impl std::error::Error for Ed25519RecoveryError {}
impl std::error::Error for KeyRecoveryError {}
/// Keypair for usage in ed25519 EdDSA.
pub struct KeyPair {
@@ -62,7 +62,7 @@ impl KeyPair {
&self.public_key
}
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(priv_bytes: &[u8], pub_bytes: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(KeyPair {
private_key: PrivateKey::from_bytes(priv_bytes)?,
public_key: PublicKey::from_bytes(pub_bytes)?,
@@ -115,7 +115,7 @@ impl PublicKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(PublicKey(ed25519_dalek::PublicKey::from_bytes(b)?))
}
@@ -123,7 +123,7 @@ impl PublicKey {
bs58::encode(self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -134,7 +134,7 @@ impl PublicKey {
}
impl PemStorableKey for PublicKey {
type Error = Ed25519RecoveryError;
type Error = KeyRecoveryError;
fn pem_type() -> &'static str {
"ED25519 PUBLIC KEY"
@@ -170,7 +170,7 @@ impl PrivateKey {
self.0.to_bytes()
}
pub fn from_bytes(b: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(b: &[u8]) -> Result<Self, KeyRecoveryError> {
Ok(PrivateKey(ed25519_dalek::SecretKey::from_bytes(b)?))
}
@@ -178,7 +178,7 @@ impl PrivateKey {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, KeyRecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
@@ -189,17 +189,10 @@ impl PrivateKey {
let sig = expanded_secret_key.sign(message, &public_key.0);
Signature(sig)
}
/// Signs text with the provided Ed25519 private key, returning a base58 signature
pub fn sign_text(&self, text: &str) -> String {
let signature_bytes = self.sign(text.as_ref()).to_bytes();
let signature = bs58::encode(signature_bytes).into_string();
signature
}
}
impl PemStorableKey for PrivateKey {
type Error = Ed25519RecoveryError;
type Error = KeyRecoveryError;
fn pem_type() -> &'static str {
"ED25519 PRIVATE KEY"
@@ -218,20 +211,11 @@ impl PemStorableKey for PrivateKey {
pub struct Signature(ed25519_dalek::Signature);
impl Signature {
pub fn to_base58_string(&self) -> String {
bs58::encode(&self.to_bytes()).into_string()
}
pub fn from_base58_string<I: AsRef<[u8]>>(val: I) -> Result<Self, Ed25519RecoveryError> {
let bytes = bs58::decode(val).into_vec()?;
Self::from_bytes(&bytes)
}
pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] {
self.0.to_bytes()
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, Ed25519RecoveryError> {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, SignatureError> {
Ok(Signature(ed25519_dalek::Signature::from_bytes(bytes)?))
}
}
@@ -1,87 +0,0 @@
use config::defaults;
use subtle_encoding::bech32;
#[derive(Debug, Clone, PartialEq)]
pub enum Bech32Error {
DecodeFailed(String),
WrongPrefix(String),
}
/// Try to decode the address (to make sure it's a valid bech32 encoding)
pub fn try_bech32_decode(address: &str) -> Result<String, Bech32Error> {
match bech32::decode(address) {
Err(e) => Err(Bech32Error::DecodeFailed(e.to_string())),
Ok((prefix, _)) => Ok(prefix),
}
}
pub fn validate_bech32_prefix(address: &str) -> Result<(), Bech32Error> {
let prefix = try_bech32_decode(address)?;
if prefix == defaults::BECH32_PREFIX {
Ok(())
} else {
Err(Bech32Error::WrongPrefix(format!(
"your bech32 address prefix should be {}, not {}",
defaults::BECH32_PREFIX,
prefix
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
mod decoding_bech32_addresses {
use super::*;
#[test]
fn total_crap_fails() {
let res = try_bech32_decode("crap");
assert_eq!(
Err(Bech32Error::DecodeFailed("bad encoding".to_string())),
res
);
}
#[test]
fn bad_checksum_fails() {
let chopped_address = "punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu"; // this has the final "0" chopped off
let res = try_bech32_decode(chopped_address);
assert_eq!(
Err(Bech32Error::DecodeFailed("checksum mismatch".to_string())),
res
);
}
#[test]
fn good_address_returns_prefix() {
let prefix = try_bech32_decode("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0");
assert_eq!(Ok("punk".to_string()), prefix);
}
}
#[cfg(test)]
mod ensuring_correct_bech32_prefix {
use super::*;
#[test]
fn wrong_prefix_fails() {
assert_eq!(
Err(Bech32Error::WrongPrefix(
"your bech32 address prefix should be nymt, not punk".to_string()
)),
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
)
}
#[test]
fn correct_prefix_works() {
assert_eq!(
Ok(()),
validate_bech32_prefix("nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9")
)
}
}
}
-1
View File
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
pub mod asymmetric;
pub mod bech32_address_validation;
pub mod crypto_hash;
pub mod hkdf;
pub mod hmac;
@@ -1,5 +1,5 @@
[package]
name = "bandwidth-claim-contract"
name = "erc20-bridge-contract"
version = "0.1.0"
edition = "2018"

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