diff --git a/Cargo.lock b/Cargo.lock index 99aa0cac83..c3af75358c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,6 +260,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" + [[package]] name = "binascii" version = "0.1.4" @@ -277,14 +283,16 @@ dependencies = [ [[package]] name = "bip32" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d0f0fc59c7ba0333eed9dcc1b6980baa7b7a4dc7c6c5885994d0674f7adf34" +checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" dependencies = [ "bs58", - "hkd32", "hmac", - "k256", + "k256 0.10.1", + "once_cell", + "pbkdf2", + "rand_core 0.6.3", "ripemd160", "sha2", "subtle 2.4.1", @@ -342,9 +350,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.0.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f" +checksum = "526c210b4520e416420759af363083471656e819a75e831b8d2c9d5a584f2413" dependencies = [ "arrayref", "arrayvec 0.7.1", @@ -406,8 +414,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" dependencies = [ "digest 0.9.0", - "ff", - "group", + "ff 0.10.1", + "group 0.10.0", "pairing", "rand_core 0.6.3", "subtle 2.4.1", @@ -762,6 +770,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df" +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "const_fn" version = "0.4.8" @@ -774,6 +788,13 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contracts-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -875,8 +896,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.8.0" -source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0254ffee603f5301d6a66963d9e1cc5091479c22e2e925e1f7689c8027a0828" dependencies = [ "prost", "prost-types", @@ -885,15 +907,16 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.3.0" -source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d" dependencies = [ "bip32", "cosmos-sdk-proto", - "ecdsa", + "ecdsa 0.13.4", "eyre", "getrandom 0.2.3", - "k256", + "k256 0.10.1", "prost", "prost-types", "rand_core 0.6.3", @@ -913,7 +936,7 @@ checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a" dependencies = [ "digest 0.9.0", "ed25519-zebra", - "k256", + "k256 0.9.6", "rand_core 0.5.1", "thiserror", ] @@ -1118,6 +1141,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.3", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -1335,7 +1370,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2adca118c71ecd9ae094d4b68257b3fdfcb711a612b9eec7b5a0d27a5a70a5b4" dependencies = [ - "const-oid", + "const-oid 0.6.1", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", ] [[package]] @@ -1544,17 +1588,29 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ - "der", - "elliptic-curve", + "der 0.4.3", + "elliptic-curve 0.10.6", "hmac", "signature", ] [[package]] -name = "ed25519" -version = "1.2.0" +name = "ecdsa" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4620d40f6d2601794401d6dd95a5cf69b6c157852539470eeda433a99b3c0efc" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der 0.5.1", + "elliptic-curve 0.11.6", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74e1069e39f1454367eb2de793ed062fac4c35c2934b76a81d90dd9abcd28816" dependencies = [ "signature", ] @@ -1599,16 +1655,33 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" dependencies = [ - "crypto-bigint", - "ff", + "crypto-bigint 0.2.10", + "ff 0.10.1", "generic-array 0.14.4", - "group", - "pkcs8", + "group 0.10.0", + "pkcs8 0.7.6", "rand_core 0.6.3", "subtle 2.4.1", "zeroize", ] +[[package]] +name = "elliptic-curve" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decb3a27ea454a5f23f96eb182af0671c12694d64ecc33dada74edd1301f6cfc" +dependencies = [ + "crypto-bigint 0.3.2", + "der 0.5.1", + "ff 0.11.0", + "generic-array 0.14.4", + "group 0.11.0", + "rand_core 0.6.3", + "sec1", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "embed_plist" version = "1.2.0" @@ -1729,7 +1802,7 @@ dependencies = [ "humantime-serde", "isocountry", "log", - "mixnet-contract", + "mixnet-contract-common", "network-defaults", "okapi", "pretty_env_logger", @@ -1780,6 +1853,16 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "ff" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +dependencies = [ + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "field-offset" version = "0.3.4" @@ -2422,7 +2505,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "byteorder", - "ff", + "ff 0.10.1", + "rand_core 0.6.3", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff 0.11.0", "rand_core 0.6.3", "subtle 2.4.1", ] @@ -2595,20 +2689,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b" -[[package]] -name = "hkd32" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f2a5541afe0725f0b95619d6af614f48c1b176385b8aa30918cfb8c4bfafc8" -dependencies = [ - "hmac", - "once_cell", - "pbkdf2", - "rand_core 0.6.3", - "sha2", - "zeroize", -] - [[package]] name = "hkdf" version = "0.11.0" @@ -3050,8 +3130,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" dependencies = [ "cfg-if 1.0.0", - "ecdsa", - "elliptic-curve", + "ecdsa 0.12.4", + "elliptic-curve 0.10.6", + "sha2", +] + +[[package]] +name = "k256" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7511aa19fa182a8a4885760c4e5675b17173b02ae86ec5d376d34f5278c874b9" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa 0.13.4", + "elliptic-curve 0.11.6", + "sec1", "sha2", "sha3", ] @@ -3309,10 +3402,11 @@ dependencies = [ ] [[package]] -name = "mixnet-contract" +name = "mixnet-contract-common" version = "0.1.0" dependencies = [ "az", + "contracts-common", "cosmwasm-std", "fixed", "log", @@ -3779,7 +3873,7 @@ dependencies = [ "getset", "humantime-serde", "log", - "mixnet-contract", + "mixnet-contract-common", "nymsphinx", "pin-project", "pretty_env_logger", @@ -3798,6 +3892,7 @@ dependencies = [ "tokio", "topology", "url", + "validator-api-requests", "validator-client", "vergen", "version-checker", @@ -3813,9 +3908,9 @@ dependencies = [ "criterion", "digest 0.9.0", "doc-comment", - "ff", + "ff 0.10.1", "getrandom 0.2.3", - "group", + "group 0.10.0", "itertools", "rand 0.8.4", "serde", @@ -3829,7 +3924,7 @@ name = "nymsphinx" version = "0.1.0" dependencies = [ "crypto", - "mixnet-contract", + "mixnet-contract-common", "nymsphinx-acknowledgements", "nymsphinx-addressing", "nymsphinx-anonymous-replies", @@ -4062,7 +4157,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" dependencies = [ - "group", + "group 0.10.0", ] [[package]] @@ -4155,9 +4250,9 @@ checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" [[package]] name = "pbkdf2" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", ] @@ -4415,8 +4510,19 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ - "der", - "spki", + "der 0.4.3", + "spki 0.4.1", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", ] [[package]] @@ -5055,6 +5161,17 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint 0.3.2", + "hmac", + "zeroize", +] + [[package]] name = "rfd" version = "0.4.4" @@ -5409,6 +5526,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der 0.5.1", + "generic-array 0.14.4", + "pkcs8 0.8.0", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "secp256k1" version = "0.20.3" @@ -5874,7 +6004,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ - "der", + "der 0.4.3", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", ] [[package]] @@ -6590,18 +6730,17 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.0" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d1cdb0236becb17ab35a2ed1566503e412fd910944dc940239857bb7663652" +checksum = "a9ef686b8ecd36550d0581f0989c9d8607090b23005df479d8e6ba348b5800b9" dependencies = [ "async-trait", "bytes", - "chrono", "ed25519", "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.10.1", "num-traits", "once_cell", "prost", @@ -6616,14 +6755,15 @@ dependencies = [ "subtle 2.4.1", "subtle-encoding", "tendermint-proto", + "time 0.3.3", "zeroize", ] [[package]] name = "tendermint-config" -version = "0.23.0" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a1f94250d30e3011130a09756b05985d8dbfbd562cf261b5a17e36d89a37992" +checksum = "ecc127f82e7a8c7337c1f293d65a821380d3407c4c44bc979ef4da0ebc3b31ed" dependencies = [ "flex-error", "serde", @@ -6635,12 +6775,11 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.23.0" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff16a7b42bdbcf31c8cd10a4cffc7631f2a301360ba3a3f71dde48eabfa5bced" +checksum = "a9e0a0251fd81bed7420bea0f4d91c2a58f6c9fa34d5b2f70bed0ba8890de5aa" dependencies = [ "bytes", - "chrono", "flex-error", "num-derive", "num-traits", @@ -6649,20 +6788,20 @@ dependencies = [ "serde", "serde_bytes", "subtle-encoding", + "time 0.3.3", ] [[package]] name = "tendermint-rpc" -version = "0.23.0" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7842dcd5edb60b077572aa92ff8b29fc810b9b463310f9810a2607474130db4" +checksum = "626c493e9ced3a9de37583bbd929f4b6dbd49aa513ab9b4776aa44a9332ce9b5" dependencies = [ "async-trait", "bytes", - "chrono", "flex-error", "futures", - "getrandom 0.1.16", + "getrandom 0.2.3", "http", "hyper", "hyper-proxy", @@ -6677,6 +6816,7 @@ dependencies = [ "tendermint-config", "tendermint-proto", "thiserror", + "time 0.3.3", "tokio", "tracing", "url", @@ -6955,7 +7095,7 @@ dependencies = [ "bs58", "crypto", "log", - "mixnet-contract", + "mixnet-contract-common", "nymsphinx-addressing", "nymsphinx-types", "rand 0.7.3", @@ -7187,6 +7327,14 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "validator-api-requests" +version = "0.1.0" +dependencies = [ + "serde", + "ts-rs", +] + [[package]] name = "validator-client" version = "0.1.0" @@ -7201,7 +7349,7 @@ dependencies = [ "flate2", "itertools", "log", - "mixnet-contract", + "mixnet-contract-common", "network-defaults", "prost", "reqwest", @@ -7211,6 +7359,7 @@ dependencies = [ "thiserror", "ts-rs", "url", + "validator-api-requests", "vesting-contract", ] @@ -7275,12 +7424,18 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", - "mixnet-contract", - "rand 0.8.4", + "mixnet-contract-common", "schemars", "serde", "thiserror", + "vesting-contract-common", +] + +[[package]] +name = "vesting-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5537a3d5a5..90a214fe8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,9 @@ members = [ "common/credentials", "common/crypto", "common/bandwidth-claim-contract", - "common/mixnet-contract", + "common/cosmwasm-smart-contracts/contracts-common", + "common/cosmwasm-smart-contracts/mixnet-contract", + "common/cosmwasm-smart-contracts/vesting-contract", "common/mixnode-common", "common/network-defaults", "common/nonexhaustive-delayqueue", @@ -52,6 +54,7 @@ members = [ "mixnode", "service-providers/network-requester", "validator-api", + "validator-api/validator-api-requests", ] default-members = [ diff --git a/README.md b/README.md index 14531cf0a5..4a10ea119a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr ### Building -Platform build instructions are available on [our docs site](https://nymtech.net/docs/0.11.0/overview/index/). +Platform build instructions are available on [our docs site](https://nymtech.net/docs/stable/run-nym-nodes/build-nym). +Wallet build instructions are also available on [our docs site](https://nymtech.net/docs/stable/nym-apps/wallet#for-developers). ### Developing diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 236bd71754..e5f831efbe 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -9,7 +9,7 @@ rust-version = "1.56" [dependencies] base64 = "0.13" -mixnet-contract = { path="../../../common/mixnet-contract" } +mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } vesting-contract = { path="../../../contracts/vesting" } serde = { version="1", features=["derive"] } serde_json = "1" @@ -20,6 +20,7 @@ url = { version = "2.2", features = ["serde"] } coconut-interface = { path = "../../coconut-interface" } network-defaults = { path = "../../network-defaults" } +validator-api-requests = { path = "../../../validator-api/validator-api-requests" } # required for nymd-client # at some point it might be possible to make it wasm-compatible @@ -27,8 +28,7 @@ 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.4.1", 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 } @@ -38,3 +38,4 @@ ts-rs = {version = "5.1", optional = true} [features] nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] +typescript-types = ["ts-rs", "validator-api-requests/ts-rs"] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 8ad6e843d7..4a3653fb21 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -6,19 +6,23 @@ use crate::nymd::{ error::NymdError, CosmWasmClient, NymdClient, QueryNymdClient, SigningNymdClient, }; #[cfg(feature = "nymd-client")] -use mixnet_contract::ContractStateParams; +use mixnet_contract_common::ContractStateParams; use crate::{validator_api, ValidatorClientError}; use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; #[cfg(feature = "nymd-client")] -use mixnet_contract::{ +use mixnet_contract_common::{ Delegation, MixnetContractVersion, MixnodeRewardingStatusResponse, RewardingIntervalResponse, }; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; #[cfg(feature = "nymd-client")] use std::str::FromStr; use url::Url; +use validator_api_requests::models::{ + CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, + StakeSaturationResponse, +}; #[cfg(feature = "nymd-client")] pub struct Config { @@ -208,7 +212,7 @@ impl Client { pub async fn get_rewarding_status( &self, - mix_identity: mixnet_contract::IdentityKey, + mix_identity: mixnet_contract_common::IdentityKey, rewarding_interval_nonce: u32, ) -> Result where @@ -297,8 +301,8 @@ impl Client { pub async fn get_all_nymd_single_mixnode_delegations( &self, - identity: mixnet_contract::IdentityKey, - ) -> Result, ValidatorClientError> + identity: mixnet_contract_common::IdentityKey, + ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, { @@ -420,6 +424,12 @@ impl ApiClient { Ok(self.validator_api.get_active_mixnodes().await?) } + pub async fn get_cached_rewarded_mixnodes( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_rewarded_mixnodes().await?) + } + pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { Ok(self.validator_api.get_mixnodes().await?) } @@ -428,6 +438,55 @@ impl ApiClient { Ok(self.validator_api.get_gateways().await?) } + pub async fn get_gateway_core_status_count( + &self, + identity: IdentityKeyRef<'_>, + since: Option, + ) -> Result { + Ok(self + .validator_api + .get_gateway_core_status_count(identity, since) + .await?) + } + + pub async fn get_mixnode_core_status_count( + &self, + identity: IdentityKeyRef<'_>, + since: Option, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_core_status_count(identity, since) + .await?) + } + + pub async fn get_mixnode_status( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self.validator_api.get_mixnode_status(identity).await?) + } + + pub async fn get_mixnode_reward_estimation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_reward_estimation(identity) + .await?) + } + + pub async fn get_mixnode_stake_saturation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + Ok(self + .validator_api + .get_mixnode_stake_saturation(identity) + .await?) + } + pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/lib.rs b/common/client-libs/validator-client/src/lib.rs index 9ef577d97f..251501114d 100644 --- a/common/client-libs/validator-client/src/lib.rs +++ b/common/client-libs/validator-client/src/lib.rs @@ -9,6 +9,7 @@ pub mod validator_api; pub use crate::client::ApiClient; pub use crate::error::ValidatorClientError; +pub use validator_api_requests::*; #[cfg(feature = "nymd-client")] pub use client::{Client, Config}; diff --git a/common/client-libs/validator-client/src/nymd/fee/helpers.rs b/common/client-libs/validator-client/src/nymd/fee/helpers.rs index cc16e21cb5..0ddbf15356 100644 --- a/common/client-libs/validator-client/src/nymd/fee/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee/helpers.rs @@ -20,6 +20,7 @@ pub enum Operation { BondMixnodeOnBehalf, UnbondMixnode, UnbondMixnodeOnBehalf, + UpdateMixnodeConfig, DelegateToMixnode, DelegateToMixnodeOnBehalf, UndelegateFromMixnode, @@ -57,6 +58,7 @@ impl fmt::Display for Operation { Operation::BondMixnode => f.write_str("BondMixnode"), Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"), Operation::UnbondMixnode => f.write_str("UnbondMixnode"), + Operation::UpdateMixnodeConfig => f.write_str("UpdateMixnodeConfig"), Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"), Operation::BondGateway => f.write_str("BondGateway"), Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"), @@ -94,6 +96,7 @@ impl Operation { Operation::BondMixnodeOnBehalf => 200_000u64.into(), Operation::UnbondMixnode => 175_000u64.into(), Operation::UnbondMixnodeOnBehalf => 175_000u64.into(), + Operation::UpdateMixnodeConfig => 175_000u64.into(), Operation::DelegateToMixnode => 175_000u64.into(), Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(), Operation::UndelegateFromMixnode => 175_000u64.into(), diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 72f72a220f..dfaa1abbb8 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -13,7 +13,7 @@ use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl}; use cosmwasm_std::{Coin, Uint128}; pub use fee::gas_price::GasPrice; use fee::helpers::Operation; -use mixnet_contract::{ +use mixnet_contract_common::{ ContractStateParams, Delegation, ExecuteMsg, Gateway, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNode, MixNodeBond, MixOwnershipResponse, MixnetContractVersion, MixnodeRewardingStatusResponse, PagedAllDelegationsResponse, @@ -289,7 +289,7 @@ impl NymdClient { pub async fn get_rewarding_status( &self, - mix_identity: mixnet_contract::IdentityKey, + mix_identity: mixnet_contract_common::IdentityKey, rewarding_interval_nonce: u32, ) -> Result where @@ -773,6 +773,31 @@ impl NymdClient { .await } + /// Update the configuration of a mixnode. Right now, only possible for profit margin. + pub async fn update_mixnode_config( + &self, + profit_margin_percent: u8, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = self.operation_fee(Operation::UpdateMixnodeConfig); + + let req = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "Updating mixnode configuration from rust!", + Vec::new(), + ) + .await + } + /// Delegates specified amount of stake to particular mixnode. pub async fn delegate_to_mixnode( &self, diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index 558a5af2ba..8c862f0851 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -8,7 +8,7 @@ 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 mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode}; use vesting_contract::messages::ExecuteMsg as VestingExecuteMsg; #[async_trait] diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 8b993a5b7e..6511b1157c 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -1,16 +1,24 @@ -// Copyright 2021 - Nym Technologies SA +// Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 use crate::validator_api::error::ValidatorAPIError; +use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use serde::{Deserialize, Serialize}; use url::Url; +use validator_api_requests::models::{ + CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, + StakeSaturationResponse, +}; pub mod error; pub(crate) mod routes; type PathSegments<'a> = &'a [&'a str]; +type Params<'a, K, V> = &'a [(K, V)]; + +const NO_PARAMS: Params<'_, &'_ str, &'_ str> = &[]; pub struct Client { url: Url, @@ -30,24 +38,33 @@ impl Client { self.url = new_url } - async fn query_validator_api(&self, path: PathSegments<'_>) -> Result + async fn query_validator_api( + &self, + path: PathSegments<'_>, + params: Params<'_, K, V>, + ) -> Result where for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, { - let url = create_api_url(&self.url, path); + let url = create_api_url(&self.url, path, params); Ok(self.reqwest_client.get(url).send().await?.json().await?) } - async fn post_validator_api( + async fn post_validator_api( &self, path: PathSegments<'_>, + params: Params<'_, K, V>, json_body: &B, ) -> Result where B: Serialize + ?Sized, for<'a> T: Deserialize<'a>, + K: AsRef, + V: AsRef, { - let url = create_api_url(&self.url, path); + let url = create_api_url(&self.url, path, params); Ok(self .reqwest_client .post(url) @@ -59,18 +76,142 @@ impl Client { } pub async fn get_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES]) + self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES], NO_PARAMS) .await } pub async fn get_gateways(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS]) + self.query_validator_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS) .await } pub async fn get_active_mixnodes(&self) -> Result, ValidatorAPIError> { - self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE]) + self.query_validator_api( + &[routes::API_VERSION, routes::MIXNODES, routes::ACTIVE], + NO_PARAMS, + ) + .await + } + + pub async fn get_rewarded_mixnodes(&self) -> Result, ValidatorAPIError> { + self.query_validator_api( + &[routes::API_VERSION, routes::MIXNODES, routes::REWARDED], + NO_PARAMS, + ) + .await + } + + pub async fn get_gateway_core_status_count( + &self, + identity: IdentityKeyRef<'_>, + since: Option, + ) -> Result { + if let Some(since) = since { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::GATEWAY, + identity, + CORE_STATUS_COUNT, + ], + &[(SINCE_ARG, since.to_string())], + ) .await + } else { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::GATEWAY, + identity, + ], + NO_PARAMS, + ) + .await + } + } + + pub async fn get_mixnode_core_status_count( + &self, + identity: IdentityKeyRef<'_>, + since: Option, + ) -> Result { + if let Some(since) = since { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + identity, + CORE_STATUS_COUNT, + ], + &[(SINCE_ARG, since.to_string())], + ) + .await + } else { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + identity, + ], + NO_PARAMS, + ) + .await + } + } + + pub async fn get_mixnode_status( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + identity, + routes::STATUS, + ], + NO_PARAMS, + ) + .await + } + + pub async fn get_mixnode_reward_estimation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + identity, + routes::REWARD_ESTIMATION, + ], + NO_PARAMS, + ) + .await + } + + pub async fn get_mixnode_stake_saturation( + &self, + identity: IdentityKeyRef<'_>, + ) -> Result { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::STATUS_ROUTES, + routes::MIXNODE, + identity, + routes::STAKE_SATURATION, + ], + NO_PARAMS, + ) + .await } pub async fn blind_sign( @@ -79,6 +220,7 @@ impl Client { ) -> Result { self.post_validator_api( &[routes::API_VERSION, routes::COCONUT_BLIND_SIGN], + NO_PARAMS, request_body, ) .await @@ -87,13 +229,20 @@ impl Client { pub async fn get_coconut_verification_key( &self, ) -> Result { - self.query_validator_api(&[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY]) - .await + self.query_validator_api( + &[routes::API_VERSION, routes::COCONUT_VERIFICATION_KEY], + NO_PARAMS, + ) + .await } } // utility function that should solve the double slash problem in validator API forever. -fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url { +fn create_api_url, V: AsRef>( + base: &Url, + segments: PathSegments<'_>, + params: Params<'_, K, V>, +) -> Url { let mut url = base.clone(); let mut path_segments = url .path_segments_mut() @@ -108,6 +257,10 @@ fn create_api_url(base: &Url, segments: PathSegments<'_>) -> Url { // and can be dropped drop(path_segments); + if !params.is_empty() { + url.query_pairs_mut().extend_pairs(params); + } + url } @@ -122,51 +275,66 @@ mod tests { // works with 1 segment assert_eq!( "http://foomp.com/foo", - create_api_url(&base_url, &["foo"]).as_str() + create_api_url(&base_url, &["foo"], NO_PARAMS).as_str() ); // works with 2 segments assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["foo", "bar"]).as_str() + create_api_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str() ); // works with leading slash assert_eq!( "http://foomp.com/foo", - create_api_url(&base_url, &["/foo"]).as_str() + create_api_url(&base_url, &["/foo"], NO_PARAMS).as_str() ); assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["/foo", "bar"]).as_str() + create_api_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str() ); assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["foo", "/bar"]).as_str() + create_api_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str() ); // works with trailing slash assert_eq!( "http://foomp.com/foo", - create_api_url(&base_url, &["foo/"]).as_str() + create_api_url(&base_url, &["foo/"], NO_PARAMS).as_str() ); assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["foo/", "bar"]).as_str() + create_api_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str() ); assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["foo", "bar/"]).as_str() + create_api_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str() ); // works with both leading and trailing slash assert_eq!( "http://foomp.com/foo", - create_api_url(&base_url, &["/foo/"]).as_str() + create_api_url(&base_url, &["/foo/"], NO_PARAMS).as_str() ); assert_eq!( "http://foomp.com/foo/bar", - create_api_url(&base_url, &["/foo/", "/bar/"]).as_str() + create_api_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str() + ); + + // adds params + assert_eq!( + "http://foomp.com/foo/bar?foomp=baz", + create_api_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str() + ); + assert_eq!( + "http://foomp.com/foo/bar?arg1=val1&arg2=val2", + create_api_url( + &base_url, + &["/foo/", "/bar/"], + &[("arg1", "val1"), ("arg2", "val2")] + ) + .as_str() ); } } diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 4a22b48150..7259ed6610 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -8,6 +8,18 @@ pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; pub const ACTIVE: &str = "active"; +pub const REWARDED: &str = "rewarded"; -pub const COCONUT_BLIND_SIGN: &str = "blind_sign"; -pub const COCONUT_VERIFICATION_KEY: &str = "verification_key"; +pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; +pub const COCONUT_VERIFICATION_KEY: &str = "verification-key"; + +pub const STATUS_ROUTES: &str = "status"; +pub const MIXNODE: &str = "mixnode"; +pub const GATEWAY: &str = "gateway"; + +pub const CORE_STATUS_COUNT: &str = "core-status-count"; +pub const SINCE_ARG: &str = "since"; + +pub const STATUS: &str = "status"; +pub const REWARD_ESTIMATION: &str = "reward-estimation"; +pub const STAKE_SATURATION: &str = "stake-saturation"; diff --git a/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml new file mode 100644 index 0000000000..26c0b3d4af --- /dev/null +++ b/common/cosmwasm-smart-contracts/contracts-common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "contracts-common" +version = "0.1.0" +authors = ["Jędrzej Stuczyński "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-std = "1.0.0-beta3" diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/events.rs b/common/cosmwasm-smart-contracts/contracts-common/src/events.rs new file mode 100644 index 0000000000..d9b5b363ca --- /dev/null +++ b/common/cosmwasm-smart-contracts/contracts-common/src/events.rs @@ -0,0 +1,30 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::Event; + +/// Looks up value of particular attribute in the provided event. If it fails to find it, +/// the function panics. +/// +/// # Arguments +/// +/// * `event`: event to search through. +/// * `key`: key associated with the particular attribute +pub fn must_find_attribute(event: &Event, key: &str) -> String { + may_find_attribute(event, key).unwrap() +} + +/// Looks up value of particular attribute in the provided event. Returns None if it does not exist. +/// +/// # Arguments +/// +/// * `event`: event to search through. +/// * `key`: key associated with the particular attribute +pub fn may_find_attribute(event: &Event, key: &str) -> Option { + for attr in &event.attributes { + if attr.key == key { + return Some(attr.value.clone()); + } + } + None +} diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs new file mode 100644 index 0000000000..58d367a2f5 --- /dev/null +++ b/common/cosmwasm-smart-contracts/contracts-common/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod events; diff --git a/common/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml similarity index 77% rename from common/mixnet-contract/Cargo.toml rename to common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 6f17d0e879..ba0edd57b1 100644 --- a/common/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mixnet-contract" +name = "mixnet-contract-common" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2018" @@ -14,10 +14,12 @@ serde_repr = "0.1" schemars = "0.8" ts-rs = { version = "5.1", optional = true } thiserror = "1.0" -network-defaults = { path = "../network-defaults" } +network-defaults = { path = "../../network-defaults" } fixed = { version = "1.1", features = ["serde"] } az = "1.1" log = "0.4.14" +contracts-common = { path = "../contracts-common" } + [features] default = [] diff --git a/common/mixnet-contract/src/delegation.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs similarity index 100% rename from common/mixnet-contract/src/delegation.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/delegation.rs diff --git a/common/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs similarity index 100% rename from common/mixnet-contract/src/error.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs new file mode 100644 index 0000000000..d464e96acf --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/events.rs @@ -0,0 +1,353 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mixnode::NodeRewardResult; +use crate::{ContractStateParams, Delegation, IdentityKeyRef, Layer}; +use cosmwasm_std::{Addr, Coin, Event, Uint128}; + +pub use contracts_common::events::*; + +// event types +pub const DELEGATION_EVENT_TYPE: &str = "delegation"; +pub const UNDELEGATION_EVENT_TYPE: &str = "undelegation"; +pub const GATEWAY_BONDING_EVENT_TYPE: &str = "gateway_bonding"; +pub const GATEWAY_UNBONDING_EVENT_TYPE: &str = "gateway_unbonding"; +pub const MIXNODE_BONDING_EVENT_TYPE: &str = "mixnode_bonding"; +pub const MIXNODE_UNBONDING_EVENT_TYPE: &str = "mixnode_unbonding"; +pub const SETTINGS_UPDATE_EVENT_TYPE: &str = "settings_update"; +pub const BEGIN_REWARDING_EVENT_TYPE: &str = "begin_rewarding"; +pub const OPERATOR_REWARDING_EVENT_TYPE: &str = "mix_rewarding"; +pub const MIX_DELEGATORS_REWARDING_EVENT_TYPE: &str = "mix_delegators_rewarding"; +pub const FINISH_REWARDING_EVENT_TYPE: &str = "finish_rewarding"; + +// attributes that are used in multiple places +pub const OWNER_KEY: &str = "owner"; +pub const AMOUNT_KEY: &str = "amount"; +pub const PROXY_KEY: &str = "proxy"; + +// event-specific attributes + +// delegation/undelegation +pub const DELEGATOR_KEY: &str = "delegator"; +pub const DELEGATION_TARGET_KEY: &str = "delegation_target"; +pub const DELEGATION_HEIGHT_KEY: &str = "delegation_latest_block_height"; + +// bonding/unbonding +pub const NODE_IDENTITY_KEY: &str = "identity"; +pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer"; + +// settings change +pub const OLD_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "old_minimum_mixnode_pledge"; +pub const OLD_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "old_minimum_gateway_pledge"; +pub const OLD_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "old_mixnode_rewarded_set_size"; +pub const OLD_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "old_mixnode_active_set_size"; +pub const OLD_ACTIVE_SET_WORK_FACTOR_KEY: &str = "old_active_set_work_factor"; + +pub const NEW_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "new_minimum_mixnode_pledge"; +pub const NEW_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "new_minimum_gateway_pledge"; +pub const NEW_MIXNODE_REWARDED_SET_SIZE_KEY: &str = "new_mixnode_rewarded_set_size"; +pub const NEW_MIXNODE_ACTIVE_SET_SIZE_KEY: &str = "new_mixnode_active_set_size"; +pub const NEW_ACTIVE_SET_WORK_FACTOR_KEY: &str = "new_active_set_work_factor"; + +// rewarding +pub const REWARDING_INTERVAL_NONCE_KEY: &str = "rewarding_interval_nonce"; +pub const TOTAL_MIXNODE_REWARD_KEY: &str = "total_node_reward"; +pub const OPERATOR_REWARD_KEY: &str = "operator_reward"; +pub const LAMBDA_KEY: &str = "lambda"; +pub const SIGMA_KEY: &str = "sigma"; +pub const DISTRIBUTED_DELEGATION_REWARDS_KEY: &str = "distributed_delegation_rewards"; +pub const FURTHER_DELEGATIONS_TO_REWARD_KEY: &str = "further_delegations"; +pub const NO_REWARD_REASON_KEY: &str = "no_reward_reason"; +pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found"; +pub const BOND_TOO_FRESH_VALUE: &str = "bond_too_fresh"; +pub const ZERO_UPTIME_VALUE: &str = "zero_uptime"; + +pub fn new_delegation_event( + delegator: &Addr, + proxy: &Option, + amount: &Coin, + mix_identity: IdentityKeyRef, +) -> Event { + let mut event = Event::new(DELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event + .add_attribute(AMOUNT_KEY, amount.to_string()) + .add_attribute(DELEGATION_TARGET_KEY, mix_identity) +} + +pub fn new_undelegation_event( + delegator: &Addr, + proxy: &Option, + old_delegation: &Delegation, + mix_identity: IdentityKeyRef, +) -> Event { + let mut event = Event::new(UNDELEGATION_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event + .add_attribute(AMOUNT_KEY, old_delegation.amount.to_string()) + .add_attribute( + DELEGATION_HEIGHT_KEY, + old_delegation.block_height.to_string(), + ) + .add_attribute(DELEGATION_TARGET_KEY, mix_identity) +} + +pub fn new_gateway_bonding_event( + owner: &Addr, + proxy: &Option, + amount: &Coin, + identity: IdentityKeyRef, +) -> Event { + let mut event = Event::new(GATEWAY_BONDING_EVENT_TYPE) + .add_attribute(OWNER_KEY, owner) + .add_attribute(NODE_IDENTITY_KEY, identity); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event.add_attribute(AMOUNT_KEY, amount.to_string()) +} + +pub fn new_gateway_unbonding_event( + owner: &Addr, + proxy: &Option, + amount: &Coin, + identity: IdentityKeyRef, +) -> Event { + let mut event = Event::new(GATEWAY_UNBONDING_EVENT_TYPE) + .add_attribute(OWNER_KEY, owner) + .add_attribute(NODE_IDENTITY_KEY, identity); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event.add_attribute(AMOUNT_KEY, amount.to_string()) +} + +pub fn new_mixnode_bonding_event( + owner: &Addr, + proxy: &Option, + amount: &Coin, + identity: IdentityKeyRef, + assigned_layer: Layer, +) -> Event { + let mut event = Event::new(MIXNODE_BONDING_EVENT_TYPE) + .add_attribute(OWNER_KEY, owner) + .add_attribute(NODE_IDENTITY_KEY, identity); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event + .add_attribute(ASSIGNED_LAYER_KEY, assigned_layer) + .add_attribute(AMOUNT_KEY, amount.to_string()) +} + +pub fn new_mixnode_unbonding_event( + owner: &Addr, + proxy: &Option, + amount: &Coin, + identity: IdentityKeyRef, +) -> Event { + let mut event = Event::new(MIXNODE_UNBONDING_EVENT_TYPE) + .add_attribute(OWNER_KEY, owner) + .add_attribute(NODE_IDENTITY_KEY, identity); + + if let Some(proxy) = proxy { + event = event.add_attribute(PROXY_KEY, proxy) + } + + // coin implements Display trait and we use that implementation here + event.add_attribute(AMOUNT_KEY, amount.to_string()) +} + +pub fn new_settings_update_event( + old_params: &ContractStateParams, + new_params: &ContractStateParams, +) -> Event { + let mut event = Event::new(SETTINGS_UPDATE_EVENT_TYPE); + + if old_params.minimum_mixnode_pledge != new_params.minimum_mixnode_pledge { + event = event + .add_attribute( + OLD_MINIMUM_MIXNODE_PLEDGE_KEY, + old_params.minimum_mixnode_pledge, + ) + .add_attribute( + NEW_MINIMUM_MIXNODE_PLEDGE_KEY, + new_params.minimum_mixnode_pledge, + ) + } + + if old_params.minimum_gateway_pledge != new_params.minimum_gateway_pledge { + event = event + .add_attribute( + OLD_MINIMUM_GATEWAY_PLEDGE_KEY, + old_params.minimum_gateway_pledge, + ) + .add_attribute( + NEW_MINIMUM_GATEWAY_PLEDGE_KEY, + new_params.minimum_gateway_pledge, + ) + } + + if old_params.mixnode_rewarded_set_size != new_params.mixnode_rewarded_set_size { + event = event + .add_attribute( + OLD_MIXNODE_REWARDED_SET_SIZE_KEY, + old_params.mixnode_rewarded_set_size.to_string(), + ) + .add_attribute( + NEW_MIXNODE_REWARDED_SET_SIZE_KEY, + new_params.mixnode_rewarded_set_size.to_string(), + ) + } + + if old_params.mixnode_active_set_size != new_params.mixnode_active_set_size { + event = event + .add_attribute( + OLD_MIXNODE_ACTIVE_SET_SIZE_KEY, + old_params.mixnode_active_set_size.to_string(), + ) + .add_attribute( + NEW_MIXNODE_ACTIVE_SET_SIZE_KEY, + new_params.mixnode_active_set_size.to_string(), + ) + } + + if old_params.active_set_work_factor != new_params.active_set_work_factor { + event = event + .add_attribute( + OLD_ACTIVE_SET_WORK_FACTOR_KEY, + old_params.active_set_work_factor.to_string(), + ) + .add_attribute( + NEW_ACTIVE_SET_WORK_FACTOR_KEY, + new_params.active_set_work_factor.to_string(), + ) + } + + event +} + +pub fn new_begin_rewarding_event(rewarding_interval_nonce: u32) -> Event { + Event::new(BEGIN_REWARDING_EVENT_TYPE).add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) +} + +pub fn new_finish_rewarding_event(rewarding_interval_nonce: u32) -> Event { + Event::new(FINISH_REWARDING_EVENT_TYPE).add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) +} + +pub fn new_not_found_mix_operator_rewarding_event( + rewarding_interval_nonce: u32, + identity: IdentityKeyRef, +) -> Event { + Event::new(OPERATOR_REWARDING_EVENT_TYPE) + .add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) + .add_attribute(NODE_IDENTITY_KEY, identity) + .add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE) +} + +pub fn new_too_fresh_bond_mix_operator_rewarding_event( + rewarding_interval_nonce: u32, + identity: IdentityKeyRef, +) -> Event { + Event::new(OPERATOR_REWARDING_EVENT_TYPE) + .add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) + .add_attribute(NODE_IDENTITY_KEY, identity) + .add_attribute(NO_REWARD_REASON_KEY, BOND_TOO_FRESH_VALUE) +} + +pub fn new_zero_uptime_mix_operator_rewarding_event( + rewarding_interval_nonce: u32, + identity: IdentityKeyRef, +) -> Event { + Event::new(OPERATOR_REWARDING_EVENT_TYPE) + .add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) + .add_attribute(NODE_IDENTITY_KEY, identity) + .add_attribute(NO_REWARD_REASON_KEY, ZERO_UPTIME_VALUE) +} + +pub fn new_mix_operator_rewarding_event( + rewarding_interval_nonce: u32, + identity: IdentityKeyRef, + node_reward_result: NodeRewardResult, + operator_reward: Uint128, + delegation_rewards_distributed: Uint128, + further_delegations: bool, +) -> Event { + Event::new(OPERATOR_REWARDING_EVENT_TYPE) + .add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) + .add_attribute(NODE_IDENTITY_KEY, identity) + .add_attribute( + TOTAL_MIXNODE_REWARD_KEY, + node_reward_result.reward().to_string(), + ) + .add_attribute(LAMBDA_KEY, node_reward_result.lambda().to_string()) + .add_attribute(SIGMA_KEY, node_reward_result.sigma().to_string()) + .add_attribute(OPERATOR_REWARD_KEY, operator_reward) + .add_attribute( + DISTRIBUTED_DELEGATION_REWARDS_KEY, + delegation_rewards_distributed, + ) + .add_attribute( + FURTHER_DELEGATIONS_TO_REWARD_KEY, + further_delegations.to_string(), + ) +} + +pub fn new_mix_delegators_rewarding_event( + rewarding_interval_nonce: u32, + identity: IdentityKeyRef, + delegation_rewards_distributed: Uint128, + further_delegations: bool, +) -> Event { + Event::new(MIX_DELEGATORS_REWARDING_EVENT_TYPE) + .add_attribute( + REWARDING_INTERVAL_NONCE_KEY, + rewarding_interval_nonce.to_string(), + ) + .add_attribute(NODE_IDENTITY_KEY, identity) + .add_attribute( + DISTRIBUTED_DELEGATION_REWARDS_KEY, + delegation_rewards_distributed, + ) + .add_attribute( + FURTHER_DELEGATIONS_TO_REWARD_KEY, + further_delegations.to_string(), + ) +} diff --git a/common/mixnet-contract/src/gateway.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs similarity index 100% rename from common/mixnet-contract/src/gateway.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/gateway.rs diff --git a/common/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs similarity index 97% rename from common/mixnet-contract/src/lib.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index 813fa3b894..6f1854cc12 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -3,6 +3,7 @@ mod delegation; pub mod error; +pub mod events; mod gateway; pub mod mixnode; mod msg; diff --git a/common/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs similarity index 97% rename from common/mixnet-contract/src/mixnode.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index e53af230ab..ff77ec628a 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -53,6 +53,16 @@ pub enum Layer { Three = 3, } +impl From for String { + fn from(layer: Layer) -> Self { + if layer == Layer::Gateway { + "gateway".to_string() + } else { + (layer as u8).to_string() + } + } +} + #[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)] pub struct NodeRewardParams { period_reward_pool: Uint128, @@ -243,7 +253,7 @@ impl DelegatorRewardParams { } } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct NodeRewardResult { reward: U128, lambda: U128, @@ -327,6 +337,11 @@ impl MixNodeBond { self.total_delegation.clone() } + pub fn stake_saturation(&self, circulating_supply: u128, rewarded_set_size: u32) -> U128 { + self.total_bond_to_circulating_supply(circulating_supply) + * U128::from_num(rewarded_set_size) + } + pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 { U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply) } diff --git a/common/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs similarity index 98% rename from common/mixnet-contract/src/msg.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index c744a4f56e..8f74824ec0 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -20,6 +20,9 @@ pub enum ExecuteMsg { owner_signature: String, }, UnbondMixnode {}, + UpdateMixnodeConfig { + profit_margin_percent: u8, + }, BondGateway { gateway: Gateway, owner_signature: String, diff --git a/common/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs similarity index 100% rename from common/mixnet-contract/src/types.rs rename to common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs diff --git a/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml new file mode 100644 index 0000000000..278244b8f0 --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "vesting-contract-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-std = "1.0.0-beta3" diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs new file mode 100644 index 0000000000..946383b7e4 --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/events.rs @@ -0,0 +1,133 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use cosmwasm_std::{Addr, Coin, Event, Timestamp}; + +// event types +pub const WITHDRAW_EVENT_TYPE: &str = "vested_coins_withdraw"; +pub const OWNERSHIP_TRANSFER_EVENT_TYPE: &str = "ownership_transfer"; +pub const STAKING_ADDRESS_UPDATE_EVENT_TYPE: &str = "staking_address_update"; +pub const NEW_PERIODIC_VESTING_ACCOUNT_EVENT_TYPE: &str = "new_periodic_vesting_account"; + +pub const VESTING_DELEGATION_EVENT_TYPE: &str = "vesting_delegation"; +pub const VESTING_UNDELEGATION_EVENT_TYPE: &str = "vesting_undelegation"; +pub const VESTING_GATEWAY_BONDING_EVENT_TYPE: &str = "vesting_gateway_bonding"; +pub const VESTING_GATEWAY_UNBONDING_EVENT_TYPE: &str = "vesting_gateway_unbonding"; +pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding"; +pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding"; + +pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond"; +pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond"; +pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation"; + +// attributes that are used in multiple places +pub const OWNER_KEY: &str = "owner"; +pub const AMOUNT_KEY: &str = "amount"; + +// event-specific attributes + +// withdraw +pub const REMAINING_SPENDABLE_KEY: &str = "remaining_spendable"; + +// ownership transfer +pub const FROM_ACCOUNT_KEY: &str = "from"; +pub const TO_ACCOUNT_KEY: &str = "to"; +pub const NO_VALUE_VALUE: &str = "none"; + +// new vesting account +pub const START_TIME_KEY: &str = "start_time"; +pub const STAKING_ADDRESS_KEY: &str = "staking_address"; + +// OPEN QUESTION: would it make sense to also emit amount of vesting/locked coins here? +// however, then it would require additional storage reads. +pub fn new_vested_coins_withdraw_event( + address: &Addr, + amount: &Coin, + remaining_spendable: &Coin, +) -> Event { + Event::new(WITHDRAW_EVENT_TYPE) + .add_attribute(OWNER_KEY, address) + .add_attribute(AMOUNT_KEY, amount.to_string()) + .add_attribute(REMAINING_SPENDABLE_KEY, remaining_spendable.to_string()) +} + +pub fn new_ownership_transfer_event(from: &Addr, to: &Addr) -> Event { + Event::new(OWNERSHIP_TRANSFER_EVENT_TYPE) + .add_attribute(FROM_ACCOUNT_KEY, from) + .add_attribute(TO_ACCOUNT_KEY, to) +} + +pub fn new_staking_address_update_event(from: &Option, to: &Option) -> Event { + let mut event = Event::new(OWNERSHIP_TRANSFER_EVENT_TYPE); + + if let Some(from) = from { + event = event.add_attribute(FROM_ACCOUNT_KEY, from) + } else { + event = event.add_attribute(FROM_ACCOUNT_KEY, NO_VALUE_VALUE); + } + + if let Some(to) = to { + event = event.add_attribute(TO_ACCOUNT_KEY, to) + } else { + event = event.add_attribute(TO_ACCOUNT_KEY, NO_VALUE_VALUE); + } + + event +} + +pub fn new_periodic_vesting_account_event( + owner_address: &Addr, + amount: &Coin, + staking_address: &Option, + start_time: Timestamp, +) -> Event { + let mut event = Event::new(NEW_PERIODIC_VESTING_ACCOUNT_EVENT_TYPE) + .add_attribute(OWNER_KEY, owner_address) + .add_attribute(AMOUNT_KEY, amount.to_string()); + + if let Some(staking_address) = staking_address { + event = event.add_attribute(STAKING_ADDRESS_KEY, staking_address); + } + + event.add_attribute(START_TIME_KEY, start_time.to_string()) +} + +// In most cases the events are rather barebone as there's no point in attaching +// bunch of data to them as it would be redundant. It is because in most cases when the event is emitted +// a call to the mixnet contract is made that throws another event with relevant attributes already attached. + +pub fn new_vesting_gateway_bonding_event() -> Event { + Event::new(VESTING_GATEWAY_BONDING_EVENT_TYPE) +} + +pub fn new_vesting_gateway_unbonding_event() -> Event { + Event::new(VESTING_GATEWAY_UNBONDING_EVENT_TYPE) +} + +pub fn new_vesting_mixnode_bonding_event() -> Event { + Event::new(VESTING_MIXNODE_BONDING_EVENT_TYPE) +} + +pub fn new_vesting_mixnode_unbonding_event() -> Event { + Event::new(VESTING_MIXNODE_UNBONDING_EVENT_TYPE) +} + +pub fn new_vesting_delegation_event() -> Event { + Event::new(VESTING_DELEGATION_EVENT_TYPE) +} + +pub fn new_vesting_undelegation_event() -> Event { + Event::new(VESTING_UNDELEGATION_EVENT_TYPE) +} + +pub fn new_track_mixnode_unbond_event() -> Event { + Event::new(TRACK_MIXNODE_UNBOND_EVENT_TYPE) +} + +pub fn new_track_gateway_unbond_event() -> Event { + Event::new(TRACK_GATEWAY_UNBOND_EVENT_TYPE) +} + +pub fn new_track_undelegation_event() -> Event { + Event::new(TRACK_UNDELEGATION_EVENT_TYPE) +} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs new file mode 100644 index 0000000000..002130e5fd --- /dev/null +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod events; diff --git a/common/crypto/Cargo.toml b/common/crypto/Cargo.toml index cf0032de13..f55a364a68 100644 --- a/common/crypto/Cargo.toml +++ b/common/crypto/Cargo.toml @@ -9,7 +9,7 @@ edition = "2018" [dependencies] aes = { version = "0.7.4", features = ["ctr"] } bs58 = "0.4.0" -blake3 = { version = "1.0.0", features = ["traits-preview"] } +blake3 = { version = "~1.2.0", features = ["traits-preview"] } digest = "0.9.0" generic-array = "0.14" hkdf = "0.11.0" diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index a7f7f2003b..f350490376 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -8,11 +8,15 @@ use url::Url; pub mod eth_contract; #[cfg(network = "milhon")] pub mod milhon; +#[cfg(network = "qa")] +pub mod qa; #[cfg(network = "sandbox")] pub mod sandbox; #[cfg(network = "milhon")] pub use milhon::*; +#[cfg(network = "qa")] +pub use qa::*; #[cfg(network = "sandbox")] pub use sandbox::*; @@ -58,6 +62,14 @@ pub fn default_validators() -> Vec { ] } +#[cfg(network = "qa")] +pub fn default_validators() -> Vec { + vec![ValidatorDetails::new( + "https://qa-validator.nymtech.net", + Some("https://qa-validator.nymtech.net/api"), + )] +} + #[cfg(network = "sandbox")] pub fn default_validators() -> Vec { vec![ValidatorDetails::new( diff --git a/common/network-defaults/src/qa.rs b/common/network-defaults/src/qa.rs new file mode 100644 index 0000000000..c31b8bc379 --- /dev/null +++ b/common/network-defaults/src/qa.rs @@ -0,0 +1,17 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub const BECH32_PREFIX: &str = "nymt"; +pub const DENOM: &str = "unymt"; + +pub const DEFAULT_MIXNET_CONTRACT_ADDRESS: &str = "nymt14hj2tavq8fpesdwxxcu44rty3hh90vhuysqrsr"; +pub const DEFAULT_VESTING_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; +pub const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; +pub const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1dn52nx8wv9wkqmrvj6tcmdzh4es6jt8tr7f6j9"; +pub const ETH_CONTRACT_ADDRESS: [u8; 20] = + hex_literal::hex!("9fEE3e28c17dbB87310A51F13C4fbf4331A6f102"); + +// Name of the event triggered by the eth contract. If the event name is changed, +// this would also need to be changed; It is currently tested against the json abi +pub const ETH_EVENT_NAME: &str = "Burned"; +pub const ETH_BURN_FUNCTION_NAME: &str = "burnTokenForAccessCode"; diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index 4c2feb78a3..ce94c101b0 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -25,7 +25,7 @@ crypto = { path = "../crypto" } topology = { path = "../topology" } [dev-dependencies] -mixnet-contract = { path = "../mixnet-contract" } +mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } # do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require # net2 via tokio-util -> tokio -> mio -> net2 diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index c3e9aebd9d..d06afa9a91 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -186,7 +186,7 @@ impl Default for MessageReceiver { mod message_receiver { use super::*; use crypto::asymmetric::identity; - use mixnet_contract::Layer; + use mixnet_contract_common::Layer; use nymsphinx_addressing::clients::Recipient; use rand::rngs::OsRng; use std::collections::HashMap; diff --git a/common/topology/Cargo.toml b/common/topology/Cargo.toml index 199505b83f..7d7bb7ecc3 100644 --- a/common/topology/Cargo.toml +++ b/common/topology/Cargo.toml @@ -13,7 +13,7 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } ## internal crypto = { path = "../crypto" } -mixnet-contract = { path = "../mixnet-contract" } +mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" } nymsphinx-addressing = { path = "../nymsphinx/addressing" } nymsphinx-types = { path = "../nymsphinx/types" } version-checker = { path = "../version-checker" } diff --git a/common/topology/src/gateway.rs b/common/topology/src/gateway.rs index a0c068bfd6..70284f1c3e 100644 --- a/common/topology/src/gateway.rs +++ b/common/topology/src/gateway.rs @@ -3,7 +3,7 @@ use crate::{filter, NetworkAddress}; use crypto::asymmetric::{encryption, identity}; -use mixnet_contract::GatewayBond; +use mixnet_contract_common::GatewayBond; use nymsphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress}; use nymsphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index eb32901f18..c14256512a 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -3,7 +3,7 @@ use crate::filter::VersionFilterable; use log::warn; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract_common::{GatewayBond, MixNodeBond}; use nymsphinx_addressing::nodes::NodeIdentity; use nymsphinx_types::Node as SphinxNode; use rand::Rng; @@ -272,7 +272,7 @@ mod converting_mixes_to_vec { use crypto::asymmetric::{encryption, identity}; use super::*; - use mixnet_contract::Layer; + use mixnet_contract_common::Layer; #[test] fn returns_a_vec_with_hashmap_values() { diff --git a/common/topology/src/mix.rs b/common/topology/src/mix.rs index 2f2faf5a4d..f28c9175b1 100644 --- a/common/topology/src/mix.rs +++ b/common/topology/src/mix.rs @@ -3,7 +3,7 @@ use crate::{filter, NetworkAddress}; use crypto::asymmetric::{encryption, identity}; -use mixnet_contract::{Layer, MixNodeBond}; +use mixnet_contract_common::{Layer, MixNodeBond}; use nymsphinx_addressing::nodes::NymNodeRoutingAddress; use nymsphinx_types::Node as SphinxNode; use std::convert::{TryFrom, TryInto}; diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 5874784f8e..f374cb6199 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -236,6 +236,13 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contracts-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "cosmwasm-crypto" version = "1.0.0-beta3" @@ -326,7 +333,7 @@ dependencies = [ "log", "nymsphinx-types", "pemstore", - "rand 0.7.3", + "rand", "subtle-encoding", "x25519-dalek", ] @@ -458,7 +465,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", + "rand", "serde", "sha2", "zeroize", @@ -592,10 +599,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -833,21 +838,6 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "mixnet-contract" version = "0.1.0" -dependencies = [ - "az", - "cosmwasm-std", - "fixed", - "log", - "network-defaults", - "schemars", - "serde", - "serde_repr", - "thiserror", -] - -[[package]] -name = "mixnet-contracts" -version = "0.1.0" dependencies = [ "bs58", "config", @@ -857,9 +847,9 @@ dependencies = [ "crypto", "cw-storage-plus", "fixed", - "mixnet-contract", - "rand 0.7.3", - "rand_chacha 0.2.2", + "mixnet-contract-common", + "rand", + "rand_chacha", "schemars", "serde", "thiserror", @@ -867,6 +857,22 @@ dependencies = [ "vesting-contract", ] +[[package]] +name = "mixnet-contract-common" +version = "0.1.0" +dependencies = [ + "az", + "contracts-common", + "cosmwasm-std", + "fixed", + "log", + "network-defaults", + "schemars", + "serde", + "serde_repr", + "thiserror", +] + [[package]] name = "network-defaults" version = "0.1.0" @@ -1067,21 +1073,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha 0.2.2", + "rand_chacha", "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", + "rand_hc", ] [[package]] @@ -1094,16 +1088,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -1129,7 +1113,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" dependencies = [ "num-traits", - "rand 0.7.3", + "rand", ] [[package]] @@ -1141,15 +1125,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "regex" version = "1.5.4" @@ -1330,7 +1305,7 @@ dependencies = [ "hmac", "lioness", "log", - "rand 0.7.3", + "rand", "rand_distr", "sha2", "subtle 2.4.1", @@ -1558,12 +1533,18 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", - "mixnet-contract", - "rand 0.8.4", + "mixnet-contract-common", "schemars", "serde", "thiserror", + "vesting-contract-common", +] + +[[package]] +name = "vesting-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", ] [[package]] diff --git a/contracts/mixnet/Cargo.toml b/contracts/mixnet/Cargo.toml index 25de04f9db..5fc7aba1f0 100644 --- a/contracts/mixnet/Cargo.toml +++ b/contracts/mixnet/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mixnet-contracts" +name = "mixnet-contract" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2018" @@ -16,7 +16,7 @@ exclude = [ crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract = { path = "../../common/mixnet-contract" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } config = { path = "../../common/config"} vesting-contract = { path = "../vesting" } diff --git a/contracts/mixnet/examples/schema.rs b/contracts/mixnet/examples/schema.rs index 0059dfb5a4..8f390777ca 100644 --- a/contracts/mixnet/examples/schema.rs +++ b/contracts/mixnet/examples/schema.rs @@ -4,7 +4,7 @@ extern crate mixnet_contract; use cosmwasm_schema::{export_schema, remove_schemas, schema_for}; -use mixnet_contract::{ExecuteMsg, InstantiateMsg, MixNodeBond, QueryMsg}; +use mixnet_contract_common::{ExecuteMsg, InstantiateMsg, MixNodeBond, QueryMsg}; use std::env::current_dir; use std::fs::create_dir_all; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index d46c7aa3c7..eaff16f8ce 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -23,7 +23,9 @@ use crate::rewards::storage as rewards_storage; use cosmwasm_std::{ entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; -use mixnet_contract::{ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; +use mixnet_contract_common::{ + ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, +}; /// Constant specifying minimum of coin required to bond a gateway pub const INITIAL_GATEWAY_PLEDGE: Uint128 = Uint128::new(100_000_000); @@ -104,6 +106,14 @@ pub fn execute( ExecuteMsg::UnbondMixnode {} => { crate::mixnodes::transactions::try_remove_mixnode(deps, info) } + ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + } => crate::mixnodes::transactions::try_update_mixnode_config( + deps, + env, + info, + profit_margin_percent, + ), ExecuteMsg::BondGateway { gateway, owner_signature, @@ -292,7 +302,7 @@ pub mod tests { use config::defaults::DENOM; use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; use cosmwasm_std::{coins, from_binary}; - use mixnet_contract::PagedMixnodeResponse; + use mixnet_contract_common::PagedMixnodeResponse; #[test] fn initialize_contract() { diff --git a/contracts/mixnet/src/delegations/queries.rs b/contracts/mixnet/src/delegations/queries.rs index 4bc9ceed71..258f1d548d 100644 --- a/contracts/mixnet/src/delegations/queries.rs +++ b/contracts/mixnet/src/delegations/queries.rs @@ -7,10 +7,10 @@ use cosmwasm_std::Deps; use cosmwasm_std::Order; use cosmwasm_std::StdResult; use cw_storage_plus::{Bound, PrimaryKey}; -use mixnet_contract::PagedAllDelegationsResponse; -use mixnet_contract::PagedDelegatorDelegationsResponse; -use mixnet_contract::PagedMixDelegationsResponse; -use mixnet_contract::{Delegation, IdentityKey}; +use mixnet_contract_common::{ + Delegation, IdentityKey, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, + PagedMixDelegationsResponse, +}; pub(crate) fn query_all_network_delegations_paged( deps: Deps, @@ -140,7 +140,7 @@ pub(crate) mod tests { #[cfg(test)] mod querying_for_mixnode_delegations_paged { use super::*; - use mixnet_contract::IdentityKey; + use mixnet_contract_common::IdentityKey; #[test] fn retrieval_obeys_limits() { @@ -281,7 +281,7 @@ pub(crate) mod tests { mod querying_for_all_mixnode_delegations_paged { use super::*; use crate::support::tests::test_helpers; - use mixnet_contract::IdentityKey; + use mixnet_contract_common::IdentityKey; #[test] fn retrieval_obeys_limits() { @@ -444,7 +444,7 @@ pub(crate) mod tests { // add delegation from a different address let delegation = Delegation::new( - delegation_owner2.clone(), + delegation_owner2, node_identity1.clone(), coin(1234, DENOM), 1234, @@ -474,7 +474,7 @@ pub(crate) mod tests { // add delegation for a different node let delegation = Delegation::new( delegation_owner1.clone(), - node_identity2.clone(), + node_identity2, coin(1234, DENOM), 1234, None, @@ -493,11 +493,7 @@ pub(crate) mod tests { identity: node_identity1.clone(), address: Addr::unchecked(delegation_owner1.clone()) }), - query_mixnode_delegation( - deps.as_ref(), - node_identity1.clone(), - delegation_owner1.to_string() - ) + query_mixnode_delegation(deps.as_ref(), node_identity1, delegation_owner1.to_string()) ) } diff --git a/contracts/mixnet/src/delegations/storage.rs b/contracts/mixnet/src/delegations/storage.rs index 46fd0cc7fd..8e4f3cedcb 100644 --- a/contracts/mixnet/src/delegations/storage.rs +++ b/contracts/mixnet/src/delegations/storage.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex}; -use mixnet_contract::{Addr, Delegation, IdentityKey}; +use mixnet_contract_common::{Addr, Delegation, IdentityKey}; // storage prefixes const DELEGATION_PK_NAMESPACE: &str = "dl"; @@ -64,7 +64,7 @@ pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, Delega mod tests { use crate::delegations::storage; use cosmwasm_std::Addr; - use mixnet_contract::IdentityKey; + use mixnet_contract_common::IdentityKey; #[cfg(test)] mod reverse_mix_delegations { @@ -74,7 +74,7 @@ mod tests { use cosmwasm_std::testing::mock_env; use cosmwasm_std::{coin, Order}; use cw_storage_plus::PrimaryKey; - use mixnet_contract::Delegation; + use mixnet_contract_common::Delegation; #[test] fn reverse_mix_delegation_exists() { @@ -94,7 +94,7 @@ mod tests { storage::delegations() .save( &mut deps.storage, - (node_identity.clone(), delegation_owner.clone()).joined_key(), + (node_identity, delegation_owner.clone()).joined_key(), &dummy_data, ) .unwrap(); @@ -131,7 +131,7 @@ mod tests { // add delegation for a different node let dummy_data = Delegation::new( delegation_owner1.clone(), - node_identity2.clone(), + node_identity2, delegation.clone(), mock_env().block.height, None, @@ -156,14 +156,14 @@ mod tests { let dummy_data = Delegation::new( delegation_owner2.clone(), node_identity1.clone(), - delegation.clone(), + delegation, mock_env().block.height, None, ); storage::delegations() .save( &mut deps.storage, - (node_identity1.clone(), delegation_owner2.clone()).joined_key(), + (node_identity1.clone(), delegation_owner2).joined_key(), &dummy_data, ) .unwrap(); diff --git a/contracts/mixnet/src/delegations/transactions.rs b/contracts/mixnet/src/delegations/transactions.rs index 5de8764e93..9d060e891c 100644 --- a/contracts/mixnet/src/delegations/transactions.rs +++ b/contracts/mixnet/src/delegations/transactions.rs @@ -7,8 +7,8 @@ use crate::support::helpers::generate_storage_key; use config::defaults::DENOM; use cosmwasm_std::{coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response}; use cw_storage_plus::PrimaryKey; -use mixnet_contract::Delegation; -use mixnet_contract::IdentityKey; +use mixnet_contract_common::events::{new_delegation_event, new_undelegation_event}; +use mixnet_contract_common::{Delegation, IdentityKey}; use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg; fn validate_delegation_stake(mut delegation: Vec) -> Result { @@ -113,16 +113,21 @@ pub(crate) fn _try_delegate_to_mixnode( } None => Delegation::new( delegate.to_owned(), - mix_identity, - amount, + mix_identity.clone(), + amount.clone(), env.block.height, - proxy, + proxy.clone(), ), }) }, )?; - Ok(Response::default()) + Ok(Response::new().add_event(new_delegation_event( + &delegate, + &proxy, + &amount, + &mix_identity, + ))) } pub(crate) fn try_remove_delegation_from_mixnode( @@ -204,14 +209,19 @@ pub(crate) fn _try_remove_delegation_from_mixnode( let msg = Some(VestingContractExecuteMsg::TrackUndelegation { owner: delegate.as_str().to_string(), mix_identity: mix_identity.clone(), - amount: old_delegation.amount, + amount: old_delegation.amount.clone(), }); let track_undelegation_msg = wasm_execute(proxy, &msg, coins(0, DENOM))?; response = response.add_message(track_undelegation_msg); } - Ok(response) + Ok(response.add_event(new_undelegation_event( + &delegate, + &proxy, + &old_delegation, + &mix_identity, + ))) } } } @@ -530,7 +540,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), env1, - mock_info(delegation_owner1.as_str(), &[delegation1.clone()]), + mock_info(delegation_owner1.as_str(), &[delegation1]), identity.clone(), ) .unwrap(); @@ -544,7 +554,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), env2, - mock_info(delegation_owner2.as_str(), &[delegation2.clone()]), + mock_info(delegation_owner2.as_str(), &[delegation2]), identity.clone(), ) .unwrap(); @@ -699,7 +709,7 @@ mod tests { try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner.as_str(), &vec![delegation_amount.clone()]), + mock_info(delegation_owner.as_str(), &[delegation_amount.clone()]), identity.clone(), ) .unwrap(); @@ -722,6 +732,7 @@ mod tests { #[cfg(test)] mod removing_mix_stake_delegation { + use crate::delegations::queries::query_mixnode_delegation; use cosmwasm_std::coin; use cosmwasm_std::testing::mock_env; use cosmwasm_std::testing::mock_info; @@ -774,11 +785,27 @@ mod tests { identity.clone(), ) .unwrap(); - assert_eq!( - Ok(Response::new().add_message(BankMsg::Send { + let delegation = query_mixnode_delegation( + deps.as_ref(), + identity.clone(), + delegation_owner.clone().into_string(), + ) + .unwrap(); + + let expected_response = Response::new() + .add_message(BankMsg::Send { to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), - })), + }) + .add_event(new_undelegation_event( + &delegation_owner, + &None, + &delegation, + &identity, + )); + + assert_eq!( + Ok(expected_response), try_remove_delegation_from_mixnode( deps.as_mut(), mock_info(delegation_owner.as_str(), &[]), @@ -819,12 +846,27 @@ mod tests { identity.clone(), ) .unwrap(); - try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); - assert_eq!( - Ok(Response::new().add_message(BankMsg::Send { + let delegation = query_mixnode_delegation( + deps.as_ref(), + identity.clone(), + delegation_owner.clone().into_string(), + ) + .unwrap(); + let expected_response = Response::new() + .add_message(BankMsg::Send { to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), - })), + }) + .add_event(new_undelegation_event( + &delegation_owner, + &None, + &delegation, + &identity, + )); + + try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); + assert_eq!( + Ok(expected_response), try_remove_delegation_from_mixnode( deps.as_mut(), mock_info(delegation_owner.as_str(), &[]), @@ -853,7 +895,7 @@ mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), mock_env(), - mock_info(delegation_owner1.as_str(), &[delegation1.clone()]), + mock_info(delegation_owner1.as_str(), &[delegation1]), identity.clone(), ) .is_ok()); diff --git a/contracts/mixnet/src/error.rs b/contracts/mixnet/src/error.rs index 3cdccc1f13..46e85e9de0 100644 --- a/contracts/mixnet/src/error.rs +++ b/contracts/mixnet/src/error.rs @@ -3,7 +3,7 @@ use config::defaults::DENOM; use cosmwasm_std::{Addr, StdError}; -use mixnet_contract::IdentityKey; +use mixnet_contract_common::IdentityKey; use thiserror::Error; /// Custom errors for contract failure conditions. diff --git a/contracts/mixnet/src/gateways/queries.rs b/contracts/mixnet/src/gateways/queries.rs index 2305f470c6..4125ca2143 100644 --- a/contracts/mixnet/src/gateways/queries.rs +++ b/contracts/mixnet/src/gateways/queries.rs @@ -5,7 +5,9 @@ use super::storage; use crate::mixnodes::storage::{BOND_PAGE_DEFAULT_LIMIT, BOND_PAGE_MAX_LIMIT}; // Keeps gateway and mixnode retrieval in sync by re-using the constant. Could be split into its own constant. use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; -use mixnet_contract::{GatewayBond, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse}; +use mixnet_contract_common::{ + GatewayBond, GatewayOwnershipResponse, IdentityKey, PagedGatewayResponse, +}; pub(crate) fn query_gateways_paged( deps: Deps, diff --git a/contracts/mixnet/src/gateways/storage.rs b/contracts/mixnet/src/gateways/storage.rs index 33b5ef9ab0..723a8c6ea0 100644 --- a/contracts/mixnet/src/gateways/storage.rs +++ b/contracts/mixnet/src/gateways/storage.rs @@ -3,7 +3,7 @@ use cosmwasm_std::Addr; use cw_storage_plus::{Index, IndexList, IndexedMap, UniqueIndex}; -use mixnet_contract::{GatewayBond, IdentityKeyRef}; +use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; // storage prefixes const GATEWAYS_PK_NAMESPACE: &str = "gt"; @@ -41,9 +41,7 @@ mod tests { use cosmwasm_std::StdResult; use cosmwasm_std::Storage; use cosmwasm_std::{coin, Addr, Uint128}; - use mixnet_contract::GatewayBond; - use mixnet_contract::IdentityKey; - use mixnet_contract::{Gateway, IdentityKeyRef}; + use mixnet_contract_common::{Gateway, GatewayBond, IdentityKey, IdentityKeyRef}; // currently this is only used in tests but may become useful later on pub(crate) fn read_gateway_pledge_amount( @@ -87,7 +85,7 @@ mod tests { let gateway_bond = GatewayBond { pledge_amount: coin(pledge_amount, DENOM), - owner: node_owner.clone(), + owner: node_owner, block_height: 12_345, gateway: Gateway { identity_key: node_identity.clone(), diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index 14cd6881c6..6426b44357 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -9,7 +9,8 @@ use config::defaults::DENOM; use cosmwasm_std::{ coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128, }; -use mixnet_contract::{Gateway, GatewayBond, Layer}; +use mixnet_contract_common::events::{new_gateway_bonding_event, new_gateway_unbonding_event}; +use mixnet_contract_common::{Gateway, GatewayBond, Layer}; use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg; pub fn try_add_gateway( @@ -97,12 +98,24 @@ pub(crate) fn _try_add_gateway( &gateway.identity_key, )?; - let bond = GatewayBond::new(pledge, owner, env.block.height, gateway, proxy); + let gateway_identity = gateway.identity_key.clone(); + let bond = GatewayBond::new( + pledge.clone(), + owner.clone(), + env.block.height, + gateway, + proxy.clone(), + ); storage::gateways().save(deps.storage, bond.identity(), &bond)?; mixnet_params_storage::increment_layer_count(deps.storage, Layer::Gateway)?; - Ok(Response::new()) + Ok(Response::new().add_event(new_gateway_bonding_event( + &owner, + &proxy, + &pledge, + &gateway_identity, + ))) } pub fn try_remove_gateway_on_behalf( @@ -155,23 +168,24 @@ pub(crate) fn _try_remove_gateway( // decrement layer count mixnet_params_storage::decrement_layer_count(deps.storage, Layer::Gateway)?; - let mut response = Response::new() - .add_message(return_tokens) - .add_attribute("action", "unbond") - .add_attribute("address", owner.clone()) - .add_attribute("gateway_bond", gateway_bond.to_string()); + let mut response = Response::new().add_message(return_tokens); if let Some(proxy) = &proxy { let msg = VestingContractExecuteMsg::TrackUnbondGateway { owner: owner.as_str().to_string(), - amount: gateway_bond.pledge_amount, + amount: gateway_bond.pledge_amount(), }; let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?; response = response.add_message(track_unbond_message); } - Ok(response) + Ok(response.add_event(new_gateway_unbonding_event( + &owner, + &proxy, + &gateway_bond.pledge_amount, + gateway_bond.identity(), + ))) } fn validate_gateway_pledge( @@ -212,12 +226,10 @@ pub mod tests { use crate::support::tests; use crate::support::tests::test_helpers; use config::defaults::DENOM; - use cosmwasm_std::attr; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, BankMsg, Response}; use cosmwasm_std::{from_binary, Addr, Uint128}; - use mixnet_contract::Gateway; - use mixnet_contract::{ExecuteMsg, PagedGatewayResponse, QueryMsg}; + use mixnet_contract_common::{ExecuteMsg, Gateway, PagedGatewayResponse, QueryMsg}; #[test] fn gateway_add() { @@ -454,19 +466,6 @@ pub mod tests { let msg = ExecuteMsg::UnbondGateway {}; let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - // we should see log messages come back showing an unbond message - let expected_attributes = vec![ - attr("action", "unbond"), - attr("address", "fred"), - attr( - "gateway_bond", - format!( - "amount: {} {}, owner: fred, identity: {}", - INITIAL_GATEWAY_PLEDGE, DENOM, fred_identity - ), - ), - ]; - // we should see a funds transfer from the contract back to fred let expected_message = BankMsg::Send { to_address: String::from(info.sender), @@ -474,11 +473,17 @@ pub mod tests { }; // run the executor and check that we got back the correct results - let expected = Response::new() - .add_attributes(expected_attributes) - .add_message(expected_message); + let expected_response = + Response::new() + .add_message(expected_message) + .add_event(new_gateway_unbonding_event( + &Addr::unchecked("fred"), + &None, + &tests::fixtures::good_gateway_pledge()[0], + &fred_identity, + )); - assert_eq!(remove_fred, expected); + assert_eq!(expected_response, remove_fred); // only 1 node now exists, owned by bob: let gateway_bonds = tests::queries::get_gateways(&mut deps); diff --git a/contracts/mixnet/src/mixnet_contract_settings/models.rs b/contracts/mixnet/src/mixnet_contract_settings/models.rs index 0e5dcd8cb4..6b9d5d73f9 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/models.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/models.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::Addr; -use mixnet_contract::ContractStateParams; +use mixnet_contract_common::ContractStateParams; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/mixnet/src/mixnet_contract_settings/queries.rs b/contracts/mixnet/src/mixnet_contract_settings/queries.rs index 2178ed8930..68427a8173 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/queries.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/queries.rs @@ -3,7 +3,9 @@ use super::storage; use cosmwasm_std::{Deps, StdResult}; -use mixnet_contract::{ContractStateParams, MixnetContractVersion, RewardingIntervalResponse}; +use mixnet_contract_common::{ + ContractStateParams, MixnetContractVersion, RewardingIntervalResponse, +}; pub(crate) fn query_contract_settings_params(deps: Deps) -> StdResult { storage::CONTRACT_STATE diff --git a/contracts/mixnet/src/mixnet_contract_settings/storage.rs b/contracts/mixnet/src/mixnet_contract_settings/storage.rs index 3ec217c352..2f2bd48664 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/storage.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/storage.rs @@ -5,8 +5,7 @@ use crate::mixnet_contract_settings::models::ContractState; use cosmwasm_std::StdResult; use cosmwasm_std::Storage; use cw_storage_plus::Item; -use mixnet_contract::Layer; -use mixnet_contract::LayerDistribution; +use mixnet_contract_common::{Layer, LayerDistribution}; pub(crate) const CONTRACT_STATE: Item = Item::new("config"); pub(crate) const LAYERS: Item = Item::new("layers"); diff --git a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs index 21bd496be6..e087584c4d 100644 --- a/contracts/mixnet/src/mixnet_contract_settings/transactions.rs +++ b/contracts/mixnet/src/mixnet_contract_settings/transactions.rs @@ -6,7 +6,8 @@ use crate::error::ContractError; use cosmwasm_std::DepsMut; use cosmwasm_std::MessageInfo; use cosmwasm_std::Response; -use mixnet_contract::ContractStateParams; +use mixnet_contract_common::events::new_settings_update_event; +use mixnet_contract_common::ContractStateParams; pub(crate) fn try_update_contract_settings( deps: DepsMut, @@ -34,10 +35,12 @@ pub(crate) fn try_update_contract_settings( return Err(ContractError::InvalidActiveSetSize); } + let response = Response::new().add_event(new_settings_update_event(&state.params, ¶ms)); + state.params = params; storage::CONTRACT_STATE.save(deps.storage, &state)?; - Ok(Response::default()) + Ok(response) } #[cfg(test)] @@ -49,7 +52,7 @@ pub mod tests { use crate::support::tests::test_helpers; use cosmwasm_std::testing::mock_info; use cosmwasm_std::Response; - use mixnet_contract::ContractStateParams; + use mixnet_contract_common::ContractStateParams; #[test] fn updating_contract_settings() { @@ -63,14 +66,13 @@ pub mod tests { active_set_work_factor: 10, }; + let initial_params = storage::CONTRACT_STATE + .load(deps.as_ref().storage) + .unwrap() + .params; + // sanity check to ensure new_params are different than the default ones - assert_ne!( - new_params, - storage::CONTRACT_STATE - .load(deps.as_ref().storage) - .unwrap() - .params - ); + assert_ne!(new_params, initial_params); // cannot be updated from non-owner account let info = mock_info("not-the-creator", &[]); @@ -80,7 +82,10 @@ pub mod tests { // but works fine from the creator account let info = mock_info("creator", &[]); let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone()); - assert_eq!(res, Ok(Response::default())); + assert_eq!( + res, + Ok(Response::new().add_event(new_settings_update_event(&initial_params, &new_params))) + ); // and the state is actually updated let current_state = storage::CONTRACT_STATE.load(deps.as_ref().storage).unwrap(); @@ -90,21 +95,21 @@ pub mod tests { let info = mock_info("creator", &[]); let mut new_params = current_state.params.clone(); new_params.mixnode_rewarded_set_size = new_params.mixnode_active_set_size - 1; - let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone()); + let res = try_update_contract_settings(deps.as_mut(), info, new_params); assert_eq!(Err(ContractError::InvalidActiveSetSize), res); // error is thrown for 0 size rewarded set let info = mock_info("creator", &[]); let mut new_params = current_state.params.clone(); new_params.mixnode_rewarded_set_size = 0; - let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone()); + let res = try_update_contract_settings(deps.as_mut(), info, new_params); assert_eq!(Err(ContractError::ZeroRewardedSet), res); // error is thrown for 0 size active set let info = mock_info("creator", &[]); - let mut new_params = current_state.params.clone(); + let mut new_params = current_state.params; new_params.mixnode_active_set_size = 0; - let res = try_update_contract_settings(deps.as_mut(), info, new_params.clone()); + let res = try_update_contract_settings(deps.as_mut(), info, new_params); assert_eq!(Err(ContractError::ZeroActiveSet), res); } } diff --git a/contracts/mixnet/src/mixnodes/bonding_queries.rs b/contracts/mixnet/src/mixnodes/bonding_queries.rs index 4e485f1b77..e916e7263e 100644 --- a/contracts/mixnet/src/mixnodes/bonding_queries.rs +++ b/contracts/mixnet/src/mixnodes/bonding_queries.rs @@ -4,7 +4,9 @@ use super::storage; use cosmwasm_std::{Deps, Order, StdResult}; use cw_storage_plus::Bound; -use mixnet_contract::{IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; +use mixnet_contract_common::{ + IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse, +}; pub fn query_mixnodes_paged( deps: Deps, diff --git a/contracts/mixnet/src/mixnodes/layer_queries.rs b/contracts/mixnet/src/mixnodes/layer_queries.rs index 2ce21648f3..7a45c58167 100644 --- a/contracts/mixnet/src/mixnodes/layer_queries.rs +++ b/contracts/mixnet/src/mixnodes/layer_queries.rs @@ -3,7 +3,7 @@ use crate::mixnet_contract_settings::storage as mixnet_params_storage; use cosmwasm_std::{Deps, StdResult}; -use mixnet_contract::LayerDistribution; +use mixnet_contract_common::LayerDistribution; pub(crate) fn query_layer_distribution(deps: Deps) -> StdResult { mixnet_params_storage::LAYERS.load(deps.storage) diff --git a/contracts/mixnet/src/mixnodes/storage.rs b/contracts/mixnet/src/mixnodes/storage.rs index cbbcf749b4..15d365f0d9 100644 --- a/contracts/mixnet/src/mixnodes/storage.rs +++ b/contracts/mixnet/src/mixnodes/storage.rs @@ -4,7 +4,7 @@ use config::defaults::DENOM; use cosmwasm_std::{StdResult, Storage, Uint128}; use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex}; -use mixnet_contract::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond}; +use mixnet_contract_common::{Addr, Coin, IdentityKeyRef, Layer, MixNode, MixNodeBond}; use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; @@ -49,7 +49,6 @@ pub(crate) struct StoredMixnodeBond { pub layer: Layer, pub block_height: u64, pub mix_node: MixNode, - pub profit_margin_percent: Option, pub proxy: Option, } @@ -60,7 +59,6 @@ impl StoredMixnodeBond { layer: Layer, block_height: u64, mix_node: MixNode, - profit_margin_percent: Option, proxy: Option, ) -> Self { StoredMixnodeBond { @@ -69,7 +67,6 @@ impl StoredMixnodeBond { layer, block_height, mix_node, - profit_margin_percent, proxy, } } @@ -141,8 +138,7 @@ mod tests { use config::defaults::DENOM; use cosmwasm_std::testing::MockStorage; use cosmwasm_std::{coin, Addr, Uint128}; - use mixnet_contract::IdentityKey; - use mixnet_contract::MixNode; + use mixnet_contract_common::{IdentityKey, MixNode}; #[test] fn mixnode_single_read_retrieval() { @@ -173,14 +169,13 @@ mod tests { let mixnode_bond = StoredMixnodeBond { pledge_amount: coin(pledge_value, DENOM), - owner: node_owner.clone(), + owner: node_owner, layer: Layer::One, block_height: 12_345, mix_node: MixNode { identity_key: node_identity.clone(), ..tests::fixtures::mix_node_fixture() }, - profit_margin_percent: None, proxy: None, }; diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 3f7c3d40e3..252128324a 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -11,7 +11,8 @@ use config::defaults::DENOM; use cosmwasm_std::{ coins, wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response, Uint128, }; -use mixnet_contract::MixNode; +use mixnet_contract_common::events::{new_mixnode_bonding_event, new_mixnode_unbonding_event}; +use mixnet_contract_common::MixNode; use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg; pub fn try_add_mixnode( @@ -109,13 +110,12 @@ fn _try_add_mixnode( let layer = layer_distribution.choose_with_fewest(); let stored_bond = StoredMixnodeBond::new( - pledge_amount, - owner, + pledge_amount.clone(), + owner.clone(), layer, env.block.height, mix_node, - None, - proxy, + proxy.clone(), ); // technically we don't have to set the total_delegation bucket, but it makes things easier @@ -133,7 +133,13 @@ fn _try_add_mixnode( mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?; - Ok(Response::new()) + Ok(Response::new().add_event(new_mixnode_bonding_event( + &owner, + &proxy, + &pledge_amount, + identity, + stored_bond.layer, + ))) } pub fn try_remove_mixnode_on_behalf( @@ -186,22 +192,60 @@ pub(crate) fn _try_remove_mixnode( // decrement layer count mixnet_params_storage::decrement_layer_count(deps.storage, mixnode_bond.layer)?; - let mut response = Response::new() - .add_message(return_tokens) - .add_attribute("action", "unbond") - .add_attribute("mixnode_bond", mixnode_bond.to_string()); + let mut response = Response::new().add_message(return_tokens); if let Some(proxy) = &proxy { let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner: owner.as_str().to_string(), - amount: mixnode_bond.pledge_amount, + amount: mixnode_bond.pledge_amount(), }; let track_unbond_message = wasm_execute(proxy, &msg, coins(0, DENOM))?; response = response.add_message(track_unbond_message); } - Ok(response) + Ok(response.add_event(new_mixnode_unbonding_event( + &owner, + &proxy, + &mixnode_bond.pledge_amount, + mixnode_bond.identity(), + ))) +} + +pub(crate) fn try_update_mixnode_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + profit_margin_percent: u8, +) -> Result { + let owner = deps.api.addr_validate(info.sender.as_ref())?; + let mix_identity = storage::mixnodes() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(ContractError::NoAssociatedMixNodeBond { owner })? + .1 + .identity() + .clone(); + + // We don't have to check lower bound as its an u8 + if profit_margin_percent > 100 { + return Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent, + )); + } + + storage::mixnodes().update(deps.storage, &mix_identity, |mixnode_bond_opt| { + mixnode_bond_opt + .map(|mut mixnode_bond| { + mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent; + mixnode_bond.block_height = env.block.height; + mixnode_bond + }) + .ok_or(ContractError::NoBondFound) + })?; + + Ok(Response::new()) } fn validate_mixnode_pledge( @@ -242,13 +286,12 @@ pub mod tests { use crate::support::tests; use crate::support::tests::test_helpers; use config::defaults::DENOM; - use cosmwasm_std::attr; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, BankMsg, Response}; use cosmwasm_std::{from_binary, Addr, Uint128}; - use mixnet_contract::Layer; - use mixnet_contract::MixNode; - use mixnet_contract::{ExecuteMsg, LayerDistribution, PagedMixnodeResponse, QueryMsg}; + use mixnet_contract_common::{ + ExecuteMsg, Layer, LayerDistribution, MixNode, PagedMixnodeResponse, QueryMsg, + }; #[test] fn mixnode_add() { @@ -507,18 +550,6 @@ pub mod tests { let msg = ExecuteMsg::UnbondMixnode {}; let remove_fred = execute(deps.as_mut(), mock_env(), info.clone(), msg).unwrap(); - // we should see log messages come back showing an unbond message - let expected_attributes = vec![ - attr("action", "unbond"), - attr( - "mixnode_bond", - format!( - "amount: {}{}, owner: fred, identity: {}", - INITIAL_MIXNODE_PLEDGE, DENOM, fred_identity - ), - ), - ]; - // we should see a funds transfer from the contract back to fred let expected_message = BankMsg::Send { to_address: String::from(info.sender), @@ -526,11 +557,17 @@ pub mod tests { }; // run the executor and check that we got back the correct results - let expected = Response::new() - .add_attributes(expected_attributes) - .add_message(expected_message); + let expected_response = + Response::new() + .add_message(expected_message) + .add_event(new_mixnode_unbonding_event( + &Addr::unchecked("fred"), + &None, + &tests::fixtures::good_gateway_pledge()[0], + &fred_identity, + )); - assert_eq!(remove_fred, expected); + assert_eq!(expected_response, remove_fred); // only 1 node now exists, owned by bob: let mix_node_bonds = tests::queries::get_mix_nodes(&mut deps); @@ -587,6 +624,77 @@ pub mod tests { ); } + #[test] + fn updating_mixnode_config() { + let sender = "bob"; + let mut deps = test_helpers::init_contract(); + let info = mock_info(sender, &[]); + + // try updating a non existing mixnode bond + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent: 10, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::NoAssociatedMixNodeBond { + owner: Addr::unchecked(sender) + }) + ); + + test_helpers::add_mixnode( + sender, + tests::fixtures::good_mixnode_pledge(), + deps.as_mut(), + ); + + // check the initial profit margin is set to the fixture value + let fixture_profit_margin = tests::fixtures::mix_node_fixture().profit_margin_percent; + assert_eq!( + fixture_profit_margin, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + + // try updating with an invalid value + let profit_margin_percent = 101; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent + )) + ); + + let profit_margin_percent = fixture_profit_margin + 10; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!( + profit_margin_percent, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + } + #[test] fn validating_mixnode_bond() { // you must send SOME funds @@ -649,6 +757,6 @@ pub mod tests { assert_eq!(alice_node.mix_node.identity_key, alice_identity); assert_eq!(alice_node.layer, Layer::One); assert_eq!(bob_node.mix_node.identity_key, bob_identity); - assert_eq!(bob_node.layer, mixnet_contract::Layer::Two); + assert_eq!(bob_node.layer, mixnet_contract_common::Layer::Two); } } diff --git a/contracts/mixnet/src/rewards/helpers.rs b/contracts/mixnet/src/rewards/helpers.rs index 593e2eb7f9..c363cb5e39 100644 --- a/contracts/mixnet/src/rewards/helpers.rs +++ b/contracts/mixnet/src/rewards/helpers.rs @@ -5,8 +5,8 @@ use super::storage; use crate::error::ContractError; use crate::mixnodes::storage as mixnodes_storage; use cosmwasm_std::{Addr, Storage, Uint128}; -use mixnet_contract::mixnode::DelegatorRewardParams; -use mixnet_contract::{ +use mixnet_contract_common::mixnode::DelegatorRewardParams; +use mixnet_contract_common::{ IdentityKey, IdentityKeyRef, PendingDelegatorRewarding, RewardingResult, RewardingStatus, }; diff --git a/contracts/mixnet/src/rewards/queries.rs b/contracts/mixnet/src/rewards/queries.rs index 7d643c073d..a52f74dcf0 100644 --- a/contracts/mixnet/src/rewards/queries.rs +++ b/contracts/mixnet/src/rewards/queries.rs @@ -4,7 +4,7 @@ use super::storage; use cosmwasm_std::Uint128; use cosmwasm_std::{Deps, StdResult}; -use mixnet_contract::{IdentityKey, MixnodeRewardingStatusResponse}; +use mixnet_contract_common::{IdentityKey, MixnodeRewardingStatusResponse}; pub(crate) fn query_reward_pool(deps: Deps) -> StdResult { storage::REWARD_POOL.load(deps.storage) @@ -46,7 +46,9 @@ pub(crate) mod tests { }; use config::defaults::DENOM; use cosmwasm_std::{coin, Addr}; - use mixnet_contract::{RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT}; + use mixnet_contract_common::{ + RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT, + }; #[test] fn returns_empty_status_for_unrewarded_nodes() { @@ -75,14 +77,14 @@ pub(crate) mod tests { try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 1).unwrap(); try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info.clone(), node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), 1, ) .unwrap(); - try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap(); + try_finish_mixnode_rewarding(deps.as_mut(), info, 1).unwrap(); assert!(query_rewarding_status(deps.as_ref(), node_identity, 2) .unwrap() @@ -120,7 +122,7 @@ pub(crate) mod tests { 1, ) .unwrap(); - try_finish_mixnode_rewarding(deps.as_mut(), info.clone(), 1).unwrap(); + try_finish_mixnode_rewarding(deps.as_mut(), info, 1).unwrap(); let res = query_rewarding_status(deps.as_ref(), node_identity, 1).unwrap(); assert!(matches!(res.status, Some(RewardingStatus::Complete(..)))); @@ -151,10 +153,7 @@ pub(crate) mod tests { try_delegate_to_mixnode( deps.as_mut(), env.clone(), - mock_info( - &*format!("delegator{:04}", i), - &vec![coin(200_000000, DENOM)], - ), + mock_info(&*format!("delegator{:04}", i), &[coin(200_000000, DENOM)]), node_identity.clone(), ) .unwrap(); @@ -167,7 +166,7 @@ pub(crate) mod tests { try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info.clone(), node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), @@ -176,13 +175,8 @@ pub(crate) mod tests { .unwrap(); // rewards all pending - try_reward_next_mixnode_delegators( - deps.as_mut(), - info.clone(), - node_identity.to_string(), - 2, - ) - .unwrap(); + try_reward_next_mixnode_delegators(deps.as_mut(), info, node_identity.to_string(), 2) + .unwrap(); let res = query_rewarding_status(deps.as_ref(), node_identity, 2).unwrap(); assert!(matches!(res.status, Some(RewardingStatus::Complete(..)))); @@ -222,10 +216,7 @@ pub(crate) mod tests { try_delegate_to_mixnode( deps.as_mut(), env.clone(), - mock_info( - &*format!("delegator{:04}", i), - &vec![coin(200_000000, DENOM)], - ), + mock_info(&*format!("delegator{:04}", i), &[coin(200_000000, DENOM)]), node_identity.clone(), ) .unwrap(); @@ -238,7 +229,7 @@ pub(crate) mod tests { try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), diff --git a/contracts/mixnet/src/rewards/storage.rs b/contracts/mixnet/src/rewards/storage.rs index 56ce6a4964..7f8e1765c9 100644 --- a/contracts/mixnet/src/rewards/storage.rs +++ b/contracts/mixnet/src/rewards/storage.rs @@ -5,7 +5,7 @@ use crate::error::ContractError; use config::defaults::TOTAL_SUPPLY; use cosmwasm_std::{StdResult, Storage, Uint128}; use cw_storage_plus::{Item, Map, U32Key}; -use mixnet_contract::{IdentityKey, RewardingStatus}; +use mixnet_contract_common::{IdentityKey, RewardingStatus}; pub(crate) const REWARD_POOL: Item = Item::new("pool"); pub(crate) const REWARDING_STATUS: Map<(U32Key, IdentityKey), RewardingStatus> = Map::new("rm"); diff --git a/contracts/mixnet/src/rewards/transactions.rs b/contracts/mixnet/src/rewards/transactions.rs index 7457f0e3ed..438ad9504d 100644 --- a/contracts/mixnet/src/rewards/transactions.rs +++ b/contracts/mixnet/src/rewards/transactions.rs @@ -9,8 +9,13 @@ use crate::mixnodes::storage as mixnodes_storage; use crate::rewards::helpers; use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, StdResult, Storage, Uint128}; use cw_storage_plus::{Bound, PrimaryKey}; -use mixnet_contract::mixnode::{DelegatorRewardParams, NodeRewardParams}; -use mixnet_contract::{ +use mixnet_contract_common::events::{ + new_begin_rewarding_event, new_finish_rewarding_event, new_mix_delegators_rewarding_event, + new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event, + new_too_fresh_bond_mix_operator_rewarding_event, new_zero_uptime_mix_operator_rewarding_event, +}; +use mixnet_contract_common::mixnode::{DelegatorRewardParams, NodeRewardParams}; +use mixnet_contract_common::{ IdentityKey, RewardingResult, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT, }; @@ -106,10 +111,7 @@ pub(crate) fn try_begin_mixnode_rewarding( mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?; - Ok(Response::new().add_attribute( - "rewarding interval nonce", - rewarding_interval_nonce.to_string(), - )) + Ok(Response::new().add_event(new_begin_rewarding_event(rewarding_interval_nonce))) } fn reward_mix_delegators( @@ -232,21 +234,26 @@ pub(crate) fn try_reward_next_mixnode_delegators( let mut rewarding_results = next_page_info.running_results; rewarding_results.total_delegator_reward += delegation_rewarding_result.total_rewarded; - let round_increase = delegation_rewarding_result.total_rewarded.to_string(); + let round_increase = delegation_rewarding_result.total_rewarded; let more_delegators = delegation_rewarding_result.start_next.is_some(); helpers::update_rewarding_status( deps.storage, rewarding_interval_nonce, - mix_identity, + mix_identity.clone(), rewarding_results, delegation_rewarding_result.start_next, next_page_info.rewarding_params, )?; - Ok(Response::new() - .add_attribute("current round delegation increase", round_increase) - .add_attribute("more delegators to reward", more_delegators.to_string())) + Ok( + Response::new().add_event(new_mix_delegators_rewarding_event( + rewarding_interval_nonce, + &mix_identity, + round_increase, + more_delegators, + )), + ) } } } @@ -283,68 +290,91 @@ pub(crate) fn try_reward_mixnode( let current_bond = match mixnodes_storage::read_full_mixnode_bond(deps.storage, &mix_identity)? { Some(bond) => bond, - None => return Ok(Response::new().add_attribute("result", "bond not found")), + None => { + return Ok( + Response::new().add_event(new_not_found_mix_operator_rewarding_event( + rewarding_interval_nonce, + &mix_identity, + )), + ) + } }; - let mut node_reward = "0".to_string(); - let mut operator_reward = Uint128::zero(); - let mut total_delegation_increase = Uint128::zero(); - let mut more_delegators = false; - - // check if node is old enough for rewarding and if its uptime is non-zero - if current_bond.block_height + storage::MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height - && params.uptime() > 0 - { - let mut node_reward_params = params; - node_reward_params.set_reward_blockstamp(env.block.height); - - let operator_reward_result = current_bond.reward(&node_reward_params); - node_reward = operator_reward_result.reward().to_string(); - - // Omitting the price per packet function now, it follows that base operator reward is the node_reward - operator_reward = Uint128::new(current_bond.operator_reward(&node_reward_params)); - - let delegator_params = DelegatorRewardParams::new(¤t_bond, node_reward_params); - let delegation_rewarding_result = - reward_mix_delegators(deps.storage, mix_identity.clone(), None, delegator_params)?; - - helpers::update_post_rewarding_storage( - deps.storage, - &mix_identity, - operator_reward, - delegation_rewarding_result.total_rewarded, - )?; - - let rewarding_results = RewardingResult { - operator_reward, - total_delegator_reward: delegation_rewarding_result.total_rewarded, - }; - - total_delegation_increase = rewarding_results.total_delegator_reward; - more_delegators = delegation_rewarding_result.start_next.is_some(); - - helpers::update_rewarding_status( - deps.storage, - rewarding_interval_nonce, - mix_identity, - rewarding_results, - delegation_rewarding_result.start_next, - delegator_params, - )?; - } else { - // node is not eligible for rewarding, so we're done immediately + // check if node is old enough for rewarding + if current_bond.block_height + storage::MINIMUM_BLOCK_AGE_FOR_REWARDING > env.block.height { storage::REWARDING_STATUS.save( deps.storage, - (rewarding_interval_nonce.into(), mix_identity), + (rewarding_interval_nonce.into(), mix_identity.clone()), &RewardingStatus::Complete(Default::default()), )?; + + return Ok( + Response::new().add_event(new_too_fresh_bond_mix_operator_rewarding_event( + rewarding_interval_nonce, + &mix_identity, + )), + ); } - Ok(Response::new() - .add_attribute("node reward", node_reward) - .add_attribute("operator reward", operator_reward) - .add_attribute("total delegation increase", total_delegation_increase) - .add_attribute("more delegators to reward", more_delegators.to_string())) + // check if it has non-zero uptime + if params.uptime() == 0 { + storage::REWARDING_STATUS.save( + deps.storage, + (rewarding_interval_nonce.into(), mix_identity.clone()), + &RewardingStatus::Complete(Default::default()), + )?; + + return Ok( + Response::new().add_event(new_zero_uptime_mix_operator_rewarding_event( + rewarding_interval_nonce, + &mix_identity, + )), + ); + } + + let mut node_reward_params = params; + node_reward_params.set_reward_blockstamp(env.block.height); + + let node_reward_result = current_bond.reward(&node_reward_params); + + // Omitting the price per packet function now, it follows that base operator reward is the node_reward + let operator_reward = Uint128::new(current_bond.operator_reward(&node_reward_params)); + + let delegator_params = DelegatorRewardParams::new(¤t_bond, node_reward_params); + let delegation_rewarding_result = + reward_mix_delegators(deps.storage, mix_identity.clone(), None, delegator_params)?; + + helpers::update_post_rewarding_storage( + deps.storage, + &mix_identity, + operator_reward, + delegation_rewarding_result.total_rewarded, + )?; + + let rewarding_results = RewardingResult { + operator_reward, + total_delegator_reward: delegation_rewarding_result.total_rewarded, + }; + let total_delegator_reward = rewarding_results.total_delegator_reward; + let further_delegations = delegation_rewarding_result.start_next.is_some(); + + helpers::update_rewarding_status( + deps.storage, + rewarding_interval_nonce, + mix_identity.clone(), + rewarding_results, + delegation_rewarding_result.start_next, + delegator_params, + )?; + + Ok(Response::new().add_event(new_mix_operator_rewarding_event( + rewarding_interval_nonce, + &mix_identity, + node_reward_result, + operator_reward, + total_delegator_reward, + further_delegations, + ))) } pub(crate) fn try_finish_mixnode_rewarding( @@ -374,7 +404,7 @@ pub(crate) fn try_finish_mixnode_rewarding( state.rewarding_in_progress = false; mixnet_params_storage::CONTRACT_STATE.save(deps.storage, &state)?; - Ok(Response::new()) + Ok(Response::new().add_event(new_finish_rewarding_event(rewarding_interval_nonce))) } #[cfg(test)] @@ -394,11 +424,15 @@ pub mod tests { use config::defaults::DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::Coin; - use cosmwasm_std::{attr, Order}; + use cosmwasm_std::Order; use cosmwasm_std::{coin, coins, Addr, Uint128}; - use mixnet_contract::mixnode::NodeRewardParams; - use mixnet_contract::{Delegation, MixNode}; - use mixnet_contract::{IdentityKey, Layer}; + use mixnet_contract_common::events::{ + must_find_attribute, BOND_TOO_FRESH_VALUE, DISTRIBUTED_DELEGATION_REWARDS_KEY, + FURTHER_DELEGATIONS_TO_REWARD_KEY, NO_REWARD_REASON_KEY, OPERATOR_REWARDING_EVENT_TYPE, + OPERATOR_REWARD_KEY, TOTAL_MIXNODE_REWARD_KEY, + }; + use mixnet_contract_common::mixnode::NodeRewardParams; + use mixnet_contract_common::{Delegation, IdentityKey, Layer, MixNode}; #[cfg(test)] mod beginning_mixnode_rewarding { @@ -426,7 +460,7 @@ pub mod tests { let res = try_begin_mixnode_rewarding( deps.as_mut(), - env.clone(), + env, mock_info(rewarding_validator_address.as_ref(), &[]), 1, ); @@ -452,7 +486,7 @@ pub mod tests { let res = try_begin_mixnode_rewarding( deps.as_mut(), - env.clone(), + env, mock_info(rewarding_validator_address.as_ref(), &[]), 2, ); @@ -547,7 +581,7 @@ pub mod tests { let res = try_begin_mixnode_rewarding( deps.as_mut(), - env.clone(), + env, mock_info(rewarding_validator_address.as_ref(), &[]), 43, ); @@ -774,9 +808,9 @@ pub mod tests { let res = try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, - node_identity.clone(), + node_identity, tests::fixtures::node_rewarding_params_fixture(100), 1, ); @@ -836,9 +870,9 @@ pub mod tests { let res = try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, - node_identity.clone(), + node_identity, tests::fixtures::node_rewarding_params_fixture(100), 1, ); @@ -900,7 +934,7 @@ pub mod tests { deps.as_mut(), env, info, - node_identity.clone(), + node_identity, tests::fixtures::node_rewarding_params_fixture(100), 2, ); @@ -923,14 +957,13 @@ pub mod tests { let initial_delegation = 20000_000000; let mixnode_bond = StoredMixnodeBond { pledge_amount: coin(initial_bond, DENOM), - owner: node_owner.clone(), + owner: node_owner, layer: Layer::One, block_height: env.block.height, mix_node: MixNode { identity_key: node_identity.clone(), ..tests::fixtures::mix_node_fixture() }, - profit_margin_percent: Some(10), proxy: None, }; @@ -988,13 +1021,11 @@ pub mod tests { .unwrap() .u128() ); - - assert_eq!(res.attributes[0], attr("node reward", "0")); - assert_eq!(res.attributes[1], attr("operator reward", "0")); - assert_eq!(res.attributes[2], attr("total delegation increase", "0")); + assert_eq!(1, res.events.len()); + assert_eq!(OPERATOR_REWARDING_EVENT_TYPE, res.events[0].ty); assert_eq!( - res.attributes[3], - attr("more delegators to reward", false.to_string()) + BOND_TOO_FRESH_VALUE, + must_find_attribute(&res.events[0], NO_REWARD_REASON_KEY) ); // reward can happen now, but only for bonded node @@ -1027,12 +1058,18 @@ pub mod tests { .u128() ); - assert_ne!(res.attributes[0], attr("node reward", "0")); - assert_ne!(res.attributes[1], attr("operator reward", "0")); - assert_eq!(res.attributes[2], attr("total delegation increase", "0")); + assert_eq!(1, res.events.len()); + let event = &res.events[0]; + assert_eq!(OPERATOR_REWARDING_EVENT_TYPE, event.ty); + assert_ne!("0", must_find_attribute(event, TOTAL_MIXNODE_REWARD_KEY)); + assert_ne!("0", must_find_attribute(event, OPERATOR_REWARD_KEY)); assert_eq!( - res.attributes[3], - attr("more delegators to reward", false.to_string()) + "0", + must_find_attribute(event, DISTRIBUTED_DELEGATION_REWARDS_KEY) + ); + assert_eq!( + false.to_string(), + must_find_attribute(event, FURTHER_DELEGATIONS_TO_REWARD_KEY) ); // reward happens now, both for node owner and delegators @@ -1047,7 +1084,7 @@ pub mod tests { try_begin_mixnode_rewarding(deps.as_mut(), env.clone(), info.clone(), 3).unwrap(); let res = try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info.clone(), node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), @@ -1070,12 +1107,18 @@ pub mod tests { > initial_delegation ); - assert_ne!(res.attributes[0], attr("node reward", "0")); - assert_ne!(res.attributes[1], attr("operator reward", "0")); - assert_ne!(res.attributes[2], attr("total delegation increase", "0")); + assert_eq!(1, res.events.len()); + let event = &res.events[0]; + assert_eq!(OPERATOR_REWARDING_EVENT_TYPE, event.ty); + assert_ne!("0", must_find_attribute(event, TOTAL_MIXNODE_REWARD_KEY)); + assert_ne!("0", must_find_attribute(event, OPERATOR_REWARD_KEY)); + assert_ne!( + "0", + must_find_attribute(event, DISTRIBUTED_DELEGATION_REWARDS_KEY) + ); assert_eq!( - res.attributes[3], - attr("more delegators to reward", false.to_string()) + false.to_string(), + must_find_attribute(event, FURTHER_DELEGATIONS_TO_REWARD_KEY) ); } @@ -1198,7 +1241,7 @@ pub mod tests { ); assert_eq!( mixnodes_storage::TOTAL_DELEGATION - .load(&deps.storage, &node_identity.clone()) + .load(&deps.storage, &node_identity) .unwrap() .u128(), pre_reward_delegation + mix1_delegator1_reward + mix1_delegator2_reward @@ -1214,7 +1257,7 @@ pub mod tests { // it's all correctly saved match storage::REWARDING_STATUS - .load(deps.as_ref().storage, (1.into(), node_identity.clone())) + .load(deps.as_ref().storage, (1.into(), node_identity)) .unwrap() { RewardingStatus::Complete(result) => assert_eq!( @@ -1244,6 +1287,7 @@ pub mod tests { .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; + #[allow(clippy::inconsistent_digit_grouping)] let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; @@ -1263,7 +1307,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{}", i), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -1291,8 +1335,8 @@ pub mod tests { ) .unwrap(); assert_eq!( - res.attributes[3], - attr("more delegators to reward", false.to_string()) + false.to_string(), + must_find_attribute(&res.events[0], FURTHER_DELEGATIONS_TO_REWARD_KEY) ); try_finish_mixnode_rewarding( @@ -1325,6 +1369,7 @@ pub mod tests { .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; + #[allow(clippy::inconsistent_digit_grouping)] let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; @@ -1344,7 +1389,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{}", i), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -1372,8 +1417,8 @@ pub mod tests { ) .unwrap(); assert_eq!( - res.attributes[3], - attr("more delegators to reward", false.to_string()) + false.to_string(), + must_find_attribute(&res.events[0], FURTHER_DELEGATIONS_TO_REWARD_KEY) ); try_finish_mixnode_rewarding( @@ -1406,6 +1451,7 @@ pub mod tests { .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; + #[allow(clippy::inconsistent_digit_grouping)] let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; @@ -1425,7 +1471,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -1453,8 +1499,8 @@ pub mod tests { ) .unwrap(); assert_eq!( - res.attributes[3], - attr("more delegators to reward", true.to_string()) + true.to_string(), + must_find_attribute(&res.events[0], FURTHER_DELEGATIONS_TO_REWARD_KEY) ); try_finish_mixnode_rewarding( @@ -1477,7 +1523,7 @@ pub mod tests { let delegation = test_helpers::read_delegation( &deps.storage, - node_identity.clone(), + node_identity, format!("delegator{:04}", MIXNODE_DELEGATORS_PAGE_LIMIT), ) .unwrap(); @@ -1493,6 +1539,8 @@ pub mod tests { let mut env = mock_env(); let node_owner: Addr = Addr::unchecked("bob"); + + #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), coins(10000_000_000, DENOM), @@ -1512,7 +1560,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &vec![coin(base_delegation, DENOM)], + &[coin(base_delegation, DENOM)], ), node_identity.clone(), ) @@ -1531,7 +1579,7 @@ pub mod tests { for delegation in delegations_storage::delegations() .idx .mixnode - .prefix(node_identity.clone()) + .prefix(node_identity) .range(deps.as_ref().storage, None, None, Order::Ascending) { actual_reward += @@ -1546,6 +1594,8 @@ pub mod tests { // with paging let node_owner: Addr = Addr::unchecked("alice"); + + #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), coins(10000_000_000, DENOM), @@ -1565,7 +1615,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &vec![coin(base_delegation, DENOM)], + &[coin(base_delegation, DENOM)], ), node_identity.clone(), ) @@ -1629,7 +1679,7 @@ pub mod tests { for delegation in delegations_storage::delegations() .idx .mixnode - .prefix(node_identity.clone()) + .prefix(node_identity) .range(deps.as_ref().storage, Some(start), None, Order::Ascending) { actual_reward += @@ -1686,7 +1736,7 @@ pub mod tests { try_begin_mixnode_rewarding( deps.as_mut(), - env.clone(), + env, mock_info(rewarding_validator_address.as_ref(), &[]), 1, ) @@ -1718,6 +1768,8 @@ pub mod tests { let rewarding_validator_address = current_state.rewarding_validator_address; let node_owner: Addr = Addr::unchecked("alice"); + + #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), coins(10000_000_000, DENOM), @@ -1753,7 +1805,7 @@ pub mod tests { assert_eq!( Err(ContractError::MixnodeAlreadyRewarded { - identity: node_identity.clone() + identity: node_identity }), res ); @@ -1767,6 +1819,8 @@ pub mod tests { // there was another page of delegators, but they were already dealt with let node_owner: Addr = Addr::unchecked("bob"); + + #[allow(clippy::inconsistent_digit_grouping)] let node_identity = test_helpers::add_mixnode( node_owner.as_str(), coins(10000_000_000, DENOM), @@ -1777,10 +1831,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), env.clone(), - mock_info( - &*format!("delegator{:04}", i), - &vec![coin(2000_000000, DENOM)], - ), + mock_info(&*format!("delegator{:04}", i), &[coin(2000_000000, DENOM)]), node_identity.clone(), ) .unwrap(); @@ -1799,7 +1850,7 @@ pub mod tests { try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), @@ -1825,7 +1876,7 @@ pub mod tests { assert_eq!( Err(ContractError::MixnodeAlreadyRewarded { - identity: node_identity.clone() + identity: node_identity }), res ); @@ -1848,6 +1899,7 @@ pub mod tests { .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; + #[allow(clippy::inconsistent_digit_grouping)] let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; @@ -1869,7 +1921,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -1889,7 +1941,7 @@ pub mod tests { try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), @@ -1945,6 +1997,7 @@ pub mod tests { .unwrap(); let rewarding_validator_address = current_state.rewarding_validator_address; + #[allow(clippy::inconsistent_digit_grouping)] let mix_bond = Uint128::new(10000_000_000); let delegation_value = 2000_000000; @@ -1966,7 +2019,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", i), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -1979,7 +2032,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), env.clone(), - mock_info("delegator0123", &vec![coin(delegation_value, DENOM)]), + mock_info("delegator0123", &[coin(delegation_value, DENOM)]), node_identity.clone(), ) .unwrap(); @@ -1989,7 +2042,7 @@ pub mod tests { env.clone(), mock_info( &*format!("delegator{:04}", 123 + MIXNODE_DELEGATORS_PAGE_LIMIT), - &vec![coin(delegation_value, DENOM)], + &[coin(delegation_value, DENOM)], ), node_identity.clone(), ) @@ -2008,7 +2061,7 @@ pub mod tests { try_reward_mixnode( deps.as_mut(), - env.clone(), + env, info, node_identity.clone(), tests::fixtures::node_rewarding_params_fixture(100), diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index a5994b42d2..94c273879e 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -5,7 +5,7 @@ use crate::error::ContractError; use crate::gateways::storage as gateways_storage; use crate::mixnodes::storage as mixnodes_storage; use cosmwasm_std::{Addr, Deps, Storage}; -use mixnet_contract::IdentityKeyRef; +use mixnet_contract_common::IdentityKeyRef; pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec { if let Some(proxy) = &proxy { diff --git a/contracts/mixnet/src/support/tests/fixtures.rs b/contracts/mixnet/src/support/tests/fixtures.rs index af4f6ab14f..cd1861e344 100644 --- a/contracts/mixnet/src/support/tests/fixtures.rs +++ b/contracts/mixnet/src/support/tests/fixtures.rs @@ -6,8 +6,8 @@ use crate::mixnodes::storage as mixnodes_storage; use crate::{mixnodes::storage::StoredMixnodeBond, support::tests}; use config::defaults::{DENOM, TOTAL_SUPPLY}; use cosmwasm_std::{coin, Addr, Coin}; -use mixnet_contract::mixnode::NodeRewardParams; -use mixnet_contract::{Gateway, GatewayBond, Layer, MixNode}; +use mixnet_contract_common::mixnode::NodeRewardParams; +use mixnet_contract_common::{Gateway, GatewayBond, Layer, MixNode}; pub fn mix_node_fixture() -> MixNode { MixNode { @@ -59,7 +59,6 @@ pub(crate) fn stored_mixnode_bond_fixture(owner: &str) -> mixnodes_storage::Stor ..super::fixtures::mix_node_fixture() }, None, - None, ) } diff --git a/contracts/mixnet/src/support/tests/messages.rs b/contracts/mixnet/src/support/tests/messages.rs index e7f8af7a91..fabea69ad4 100644 --- a/contracts/mixnet/src/support/tests/messages.rs +++ b/contracts/mixnet/src/support/tests/messages.rs @@ -1,4 +1,4 @@ -use mixnet_contract::{ExecuteMsg, Gateway, IdentityKey, MixNode}; +use mixnet_contract_common::{ExecuteMsg, Gateway, IdentityKey, MixNode}; use rand::thread_rng; use crate::support::tests; diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 7b0b99fff8..3218b72162 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -28,7 +28,7 @@ pub mod test_helpers { use cosmwasm_std::{Addr, StdResult, Storage}; use cosmwasm_std::{Empty, MemoryStorage}; use cw_storage_plus::PrimaryKey; - use mixnet_contract::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode}; + use mixnet_contract_common::{Delegation, Gateway, IdentityKeyRef, InstantiateMsg, MixNode}; use rand::thread_rng; pub fn add_mixnode(sender: &str, stake: Vec, deps: DepsMut) -> String { diff --git a/contracts/mixnet/src/support/tests/queries.rs b/contracts/mixnet/src/support/tests/queries.rs index 4c8d00195e..9a9900501b 100644 --- a/contracts/mixnet/src/support/tests/queries.rs +++ b/contracts/mixnet/src/support/tests/queries.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ testing::{mock_env, MockApi, MockQuerier, MockStorage}, Addr, Coin, OwnedDeps, }; -use mixnet_contract::{ +use mixnet_contract_common::{ GatewayBond, MixNodeBond, PagedGatewayResponse, PagedMixnodeResponse, QueryMsg, }; diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 931d7c2d75..069d31ae3e 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -14,7 +14,8 @@ exclude = [ crate-type = ["cdylib", "rlib"] [dependencies] -mixnet-contract = { path = "../../common/mixnet-contract" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } +vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" } config = { path = "../../common/config" } cosmwasm-std = { version = "1.0.0-beta3"} @@ -22,6 +23,4 @@ cw-storage-plus = { version = "0.10.3", features = ["iterator"] } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.23" } -rand = {version = "0.8.4", features = ["std_rng"]} -getrandom = { version = "0.2.3", features = ["js"]} \ No newline at end of file +thiserror = { version = "1.0.23" } \ No newline at end of file diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 44a68ae853..89f469f1b5 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -7,10 +7,15 @@ use crate::traits::{ use crate::vesting::{populate_vesting_periods, Account}; use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM}; use cosmwasm_std::{ - entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, + coin, entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Timestamp, Uint128, }; -use mixnet_contract::{Gateway, IdentityKey, MixNode}; +use mixnet_contract_common::{Gateway, IdentityKey, MixNode}; +use vesting_contract_common::events::{ + new_ownership_transfer_event, new_periodic_vesting_account_event, + new_staking_address_update_event, new_track_gateway_unbond_event, + new_track_mixnode_unbond_event, new_track_undelegation_event, new_vested_coins_withdraw_event, +}; // We're using a 24 month vesting period with 3 months sub-periods. // There are 8 three month periods in two years @@ -96,6 +101,10 @@ pub fn try_withdraw_vested_coins( info: MessageInfo, deps: DepsMut, ) -> Result { + if amount.denom != DENOM { + return Err(ContractError::WrongDenom(amount.denom, DENOM.to_string())); + } + let address = info.sender.clone(); let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; if address != account.owner_address() { @@ -111,12 +120,16 @@ pub fn try_withdraw_vested_coins( let send_tokens = BankMsg::Send { to_address: account.owner_address().as_str().to_string(), - amount: vec![amount], + amount: vec![amount.clone()], }; Ok(Response::new() - .add_attribute("action", "whitdraw") - .add_message(send_tokens)) + .add_message(send_tokens) + .add_event(new_vested_coins_withdraw_event( + &address, + &amount, + &coin(new_balance, &amount.denom), + ))) } else { Err(ContractError::InsufficientSpendable( account.owner_address().as_str().to_string(), @@ -135,7 +148,7 @@ fn try_transfer_ownership( let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; if address == account.owner_address() { account.transfer_ownership(&to_address, deps.storage)?; - Ok(Response::default()) + Ok(Response::new().add_event(new_ownership_transfer_event(&address, &to_address))) } else { Err(ContractError::NotOwner(account.owner_address().to_string())) } @@ -150,8 +163,9 @@ fn try_update_staking_address( let to_address = to_address.and_then(|x| deps.api.addr_validate(&x).ok()); let mut account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?; if address == account.owner_address() { - account.update_staking_address(to_address, deps.storage)?; - Ok(Response::default()) + let old = account.staking_address().cloned(); + account.update_staking_address(to_address.clone(), deps.storage)?; + Ok(Response::new().add_event(new_staking_address_update_event(&old, &to_address))) } else { Err(ContractError::NotOwner(account.owner_address().to_string())) } @@ -186,7 +200,7 @@ pub fn try_track_unbond_gateway( } let account = account_from_address(owner, deps.storage, deps.api)?; account.try_track_unbond_gateway(amount, deps.storage)?; - Ok(Response::default()) + Ok(Response::new().add_event(new_track_gateway_unbond_event())) } pub fn try_bond_mixnode( @@ -217,7 +231,7 @@ pub fn try_track_unbond_mixnode( } let account = account_from_address(owner, deps.storage, deps.api)?; account.try_track_unbond_mixnode(amount, deps.storage)?; - Ok(Response::default()) + Ok(Response::new().add_event(new_track_mixnode_unbond_event())) } fn try_track_undelegation( @@ -232,7 +246,7 @@ fn try_track_undelegation( } let account = account_from_address(address, deps.storage, deps.api)?; account.track_undelegation(mix_identity, amount, deps.storage)?; - Ok(Response::default()) + Ok(Response::new().add_event(new_track_undelegation_event())) } fn try_delegate_to_mixnode( @@ -275,15 +289,24 @@ fn try_create_periodic_vesting_account( }; let start_time = start_time.unwrap_or_else(|| env.block.time.seconds()); let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS); + + let start_time = Timestamp::from_seconds(start_time); Account::new( - owner_address, - staking_address, - coin, - Timestamp::from_seconds(start_time), + owner_address.clone(), + staking_address.clone(), + coin.clone(), + start_time, periods, deps.storage, )?; - Ok(Response::default()) + Ok( + Response::new().add_event(new_periodic_vesting_account_event( + &owner_address, + &coin, + &staking_address, + start_time, + )), + ) } #[entry_point] diff --git a/contracts/vesting/src/errors.rs b/contracts/vesting/src/errors.rs index 3651ac48b5..fa1b8fc5f7 100644 --- a/contracts/vesting/src/errors.rs +++ b/contracts/vesting/src/errors.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{Addr, StdError}; -use mixnet_contract::IdentityKey; +use mixnet_contract_common::IdentityKey; use thiserror::Error; #[derive(Error, Debug, PartialEq)] @@ -24,11 +24,11 @@ pub enum ContractError { ImprobableVestingAmount(u128), #[error("Address {0} has already bonded a node")] AlreadyBonded(String), - #[error("Recieved empty funds vector")] + #[error("Received empty funds vector")] EmptyFunds, - #[error("Recieved wrong denom: {0}, expected {1}")] + #[error("Received wrong denom: {0}, expected {1}")] WrongDenom(String, String), - #[error("Recieved multiple denoms, expected 1")] + #[error("Received multiple denoms, expected 1")] MultipleDenoms, #[error("No delegations found for account {0}, mix_identity {1}")] NoSuchDelegation(Addr, IdentityKey), diff --git a/contracts/vesting/src/messages.rs b/contracts/vesting/src/messages.rs index fffec2ca15..f32661818a 100644 --- a/contracts/vesting/src/messages.rs +++ b/contracts/vesting/src/messages.rs @@ -1,6 +1,5 @@ use cosmwasm_std::{Coin, Timestamp}; -use mixnet_contract::IdentityKey; -use mixnet_contract::{Gateway, MixNode}; +use mixnet_contract_common::{Gateway, IdentityKey, MixNode}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index f8227d8581..eb24ba5409 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -1,8 +1,9 @@ use crate::errors::ContractError; use crate::vesting::Account; use cosmwasm_std::{Addr, Api, Storage}; -use cw_storage_plus::Map; +use cw_storage_plus::{Item, Map}; +pub const KEY: Item = Item::new("key"); const ACCOUNTS: Map = Map::new("acc"); pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), ContractError> { diff --git a/contracts/vesting/src/traits/bonding.rs b/contracts/vesting/src/traits/bonding.rs index 73ec75024b..9ab5066d72 100644 --- a/contracts/vesting/src/traits/bonding.rs +++ b/contracts/vesting/src/traits/bonding.rs @@ -1,6 +1,6 @@ use crate::errors::ContractError; use cosmwasm_std::{Coin, Env, Response, Storage}; -use mixnet_contract::{Gateway, MixNode}; +use mixnet_contract_common::{Gateway, MixNode}; pub trait MixnodeBondingAccount { fn try_bond_mixnode( diff --git a/contracts/vesting/src/traits/delegating_account.rs b/contracts/vesting/src/traits/delegating_account.rs index bc133911d9..c2d7104893 100644 --- a/contracts/vesting/src/traits/delegating_account.rs +++ b/contracts/vesting/src/traits/delegating_account.rs @@ -1,6 +1,6 @@ use crate::errors::ContractError; use cosmwasm_std::{Coin, Env, Response, Storage, Timestamp, Uint128}; -use mixnet_contract::IdentityKey; +use mixnet_contract_common::IdentityKey; pub trait DelegatingAccount { fn try_delegate_to_mixnode( diff --git a/contracts/vesting/src/vesting/account/delegating_account.rs b/contracts/vesting/src/vesting/account/delegating_account.rs index 20b67c971e..c39ae4a870 100644 --- a/contracts/vesting/src/vesting/account/delegating_account.rs +++ b/contracts/vesting/src/vesting/account/delegating_account.rs @@ -3,8 +3,11 @@ use crate::traits::DelegatingAccount; use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM}; use cosmwasm_std::{wasm_execute, Coin, Env, Order, Response, Storage, Timestamp, Uint128}; use cw_storage_plus::Map; -use mixnet_contract::ExecuteMsg as MixnetExecuteMsg; -use mixnet_contract::IdentityKey; +use mixnet_contract_common::ExecuteMsg as MixnetExecuteMsg; +use mixnet_contract_common::IdentityKey; +use vesting_contract_common::events::{ + new_vesting_delegation_event, new_vesting_undelegation_event, +}; use super::Account; @@ -34,8 +37,8 @@ impl DelegatingAccount for Account { self.track_delegation(env.block.time, mix_identity, current_balance, coin, storage)?; Ok(Response::new() - .add_attribute("action", "delegate to mixnode on behalf") - .add_message(delegate_to_mixnode)) + .add_message(delegate_to_mixnode) + .add_event(new_vesting_delegation_event())) } fn try_undelegate_from_mixnode( @@ -64,8 +67,8 @@ impl DelegatingAccount for Account { )?; Ok(Response::new() - .add_attribute("action", "undelegate to mixnode on behalf") - .add_message(undelegate_from_mixnode)) + .add_message(undelegate_from_mixnode) + .add_event(new_vesting_undelegation_event())) } fn track_delegation( diff --git a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs index b4100557b2..ce54d8ab60 100644 --- a/contracts/vesting/src/vesting/account/gateway_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/gateway_bonding_account.rs @@ -3,7 +3,10 @@ use crate::errors::ContractError; use crate::traits::GatewayBondingAccount; use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; -use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, Gateway}; +use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, Gateway}; +use vesting_contract_common::events::{ + new_vesting_gateway_bonding_event, new_vesting_gateway_unbonding_event, +}; use super::Account; @@ -50,8 +53,8 @@ impl GatewayBondingAccount for Account { self.save_gateway_pledge(pledge_data, storage)?; Ok(Response::new() - .add_attribute("action", "bond gateway on behalf") - .add_message(bond_gateway_msg)) + .add_message(bond_gateway_msg) + .add_event(new_vesting_gateway_bonding_event())) } fn try_unbond_gateway(&self, storage: &dyn Storage) -> Result { @@ -63,8 +66,8 @@ impl GatewayBondingAccount for Account { let unbond_msg = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![])?; Ok(Response::new() - .add_attribute("action", "unbond gateway on behalf") - .add_message(unbond_msg)) + .add_message(unbond_msg) + .add_event(new_vesting_gateway_unbonding_event())) } else { Err(ContractError::NoBondFound( self.owner_address().as_str().to_string(), diff --git a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs index e62f9fea00..ea78f580e1 100644 --- a/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs +++ b/contracts/vesting/src/vesting/account/mixnode_bonding_account.rs @@ -3,7 +3,10 @@ use crate::errors::ContractError; use crate::traits::MixnodeBondingAccount; use config::defaults::DEFAULT_MIXNET_CONTRACT_ADDRESS; use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128}; -use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, MixNode}; +use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, MixNode}; +use vesting_contract_common::events::{ + new_vesting_mixnode_bonding_event, new_vesting_mixnode_unbonding_event, +}; use super::Account; @@ -50,8 +53,8 @@ impl MixnodeBondingAccount for Account { self.save_mixnode_pledge(pledge_data, storage)?; Ok(Response::new() - .add_attribute("action", "bond mixnode on behalf") - .add_message(bond_mixnode_mag)) + .add_message(bond_mixnode_mag) + .add_event(new_vesting_mixnode_bonding_event())) } fn try_unbond_mixnode(&self, storage: &dyn Storage) -> Result { @@ -63,8 +66,8 @@ impl MixnodeBondingAccount for Account { let unbond_msg = wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![])?; Ok(Response::new() - .add_attribute("action", "unbond mixnode on behalf") - .add_message(unbond_msg)) + .add_message(unbond_msg) + .add_event(new_vesting_mixnode_unbonding_event())) } else { Err(ContractError::NoBondFound( self.owner_address().as_str().to_string(), diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 424a6ca27f..2ce20122ee 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -1,12 +1,10 @@ use super::{PledgeData, VestingPeriod}; use crate::contract::NUM_VESTING_PERIODS; use crate::errors::ContractError; -use crate::storage::save_account; +use crate::storage::{save_account, KEY}; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; use cw_storage_plus::{Item, Map}; -use mixnet_contract::IdentityKey; -use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; +use mixnet_contract_common::IdentityKey; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -20,17 +18,10 @@ const BALANCE_SUFFIX: &str = "ba"; const PLEDGE_SUFFIX: &str = "bo"; const GATEWAY_SUFFIX: &str = "ga"; -fn generate_storage_key(b: &[u8], storage: &dyn Storage) -> Result { - let mut rng = StdRng::seed_from_u64(b.iter().fold(0, |acc, x| acc + *x as u64)); - // Be paranoid and check for collisions - loop { - let key = rng.next_u64().to_string(); - let balance_key = format!("{}{}", key, BALANCE_SUFFIX); - let balance: Item = Item::new(&balance_key); - if balance.may_load(storage)?.is_none() { - return Ok(key); - } - } +fn generate_storage_key(storage: &mut dyn Storage) -> Result { + let key = KEY.may_load(storage)?.unwrap_or(0) + 1; + KEY.save(storage, &key)?; + Ok(key) } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -52,7 +43,7 @@ impl Account { periods: Vec, storage: &mut dyn Storage, ) -> Result { - let storage_key = generate_storage_key(owner_address.as_bytes(), storage)?; + let storage_key = generate_storage_key(storage)?.to_string(); let amount = coin.amount; let account = Account { owner_address, diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 35d6d03d08..b0a0f6e71a 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -46,7 +46,7 @@ mod tests { use config::defaults::DENOM; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128}; - use mixnet_contract::{Gateway, MixNode}; + use mixnet_contract_common::{Gateway, MixNode}; #[test] fn test_account_creation() { @@ -78,10 +78,7 @@ mod tests { // Test key collision avoidance let account_again = vesting_account_fixture(&mut deps.storage, &env); - assert_eq!( - created_account.balance_key(), - "5032709489228919411ba".to_string() - ); + assert_eq!(created_account.balance_key(), "1ba".to_string()); assert_ne!(created_account.balance_key(), account_again.balance_key()); } @@ -127,7 +124,7 @@ mod tests { let msg = ExecuteMsg::WithdrawVestedCoins { amount: Coin { amount: Uint128::new(1), - denom: "nym".to_string(), + denom: DENOM.to_string(), }, }; let info = mock_info("new_owner", &[]); diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index 3719d89d7a..24a9357530 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -22,6 +22,6 @@ log = "0.4.0" pretty_env_logger = "0.4.0" thiserror = "1.0.29" -mixnet-contract = { path = "../common/mixnet-contract" } +mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index a09231ceae..55cd428798 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -1,10 +1,12 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::location::{GeoLocation, Location}; +use crate::state::ExplorerApiStateContext; use log::{info, warn}; use reqwest::Error as ReqwestError; use thiserror::Error; -use crate::mix_nodes::{GeoLocation, Location}; -use crate::state::ExplorerApiStateContext; - pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, } @@ -34,7 +36,15 @@ impl GeoLocateTask { } async fn locate_mix_nodes(&mut self) { - let mixnode_bonds = self.state.inner.mix_nodes.get().await.value; + // I'm unwrapping to the default value to get rid of an extra indentation level from the `if let Some(...) = ...` + // If the value is None, we'll unwrap to an empty hashmap and the `values()` loop won't do any work anyway + let mixnode_bonds = self + .state + .inner + .mix_nodes + .get_mixnodes() + .await + .unwrap_or_default(); for (i, cache_item) in mixnode_bonds.values().enumerate() { if self diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 7312962d77..999e9f3fc7 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -36,13 +36,11 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); - info!( - "Using validator API - {}", - network_defaults::default_api_endpoints()[0].clone() - ); + let validator_api_url = network_defaults::default_api_endpoints()[0].clone(); + info!("Using validator API - {}", validator_api_url); // spawn concurrent tasks - mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start(); + mix_nodes::tasks::MixNodesTasks::new(self.state.clone(), validator_api_url).start(); country_statistics::distribution::CountryStatisticsDistributionTask::new( self.state.clone(), ) diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 79b2b5750e..c3888f393e 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -1,16 +1,16 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_node::models::{NodeDescription, NodeStats}; +use crate::mix_nodes::delegations::{get_mixnode_delegations, get_single_mixnode_delegations}; +use crate::state::ExplorerApiStateContext; +use mixnet_contract_common::Delegation; use reqwest::Error as ReqwestError; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; -use serde::Serialize; - -use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; - -use crate::mix_node::models::{NodeDescription, NodeStats}; -use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; -use crate::state::ExplorerApiStateContext; pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ @@ -21,16 +21,6 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, ] } -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub(crate) struct PrettyMixNodeBondWithLocation { - pub location: Option, - pub pledge_amount: Coin, - pub total_delegation: Coin, - pub owner: Addr, - pub layer: Layer, - pub mix_node: MixNode, -} - #[openapi(tag = "mix_node")] #[get("//delegations")] pub(crate) async fn get_delegations(pubkey: &str) -> Json> { diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 8aca0dc33e..835bfe3991 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -1,11 +1,33 @@ -use std::sync::Arc; -use std::time::SystemTime; - -use serde::Deserialize; -use serde::Serialize; -use tokio::sync::RwLock; +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::mix_node::cache::Cache; +use crate::mix_nodes::location::Location; +use mixnet_contract_common::{Addr, Coin, Layer, MixNode}; +use serde::Deserialize; +use serde::Serialize; +use std::sync::Arc; +use std::time::SystemTime; +use tokio::sync::RwLock; + +#[derive(Clone, Debug, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub(crate) enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set +} + +#[derive(Clone, Debug, Serialize, JsonSchema)] +pub(crate) struct PrettyDetailedMixNodeBond { + pub location: Option, + pub status: MixnodeStatus, + pub pledge_amount: Coin, + pub total_delegation: Coin, + pub owner: Addr, + pub layer: Layer, + pub mix_node: MixNode, +} pub(crate) struct MixNodeCache { pub(crate) descriptions: Cache, diff --git a/explorer-api/src/mix_nodes/delegations.rs b/explorer-api/src/mix_nodes/delegations.rs new file mode 100644 index 0000000000..a6f0ee1c05 --- /dev/null +++ b/explorer-api/src/mix_nodes/delegations.rs @@ -0,0 +1,31 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use mixnet_contract_common::Delegation; + +pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec { + let client = super::utils::new_nymd_client(); + let delegates = match client + .get_all_nymd_single_mixnode_delegations(pubkey.to_string()) + .await + { + Ok(result) => result, + Err(e) => { + error!("Could not get delegations for mix node {}: {:?}", pubkey, e); + vec![] + } + }; + delegates +} + +pub(crate) async fn get_mixnode_delegations() -> Vec { + let client = super::utils::new_nymd_client(); + let delegates = match client.get_all_network_delegations().await { + Ok(result) => result, + Err(e) => { + error!("Could not get all mix delegations: {:?}", e); + vec![] + } + }; + delegates +} diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs index efe75f6221..a2b8b0eaba 100644 --- a/explorer-api/src/mix_nodes/http.rs +++ b/explorer-api/src/mix_nodes/http.rs @@ -1,12 +1,11 @@ +use crate::mix_node::models::PrettyDetailedMixNodeBond; +use crate::state::ExplorerApiStateContext; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; -use crate::mix_node::http::PrettyMixNodeBondWithLocation; -use crate::state::ExplorerApiStateContext; - pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: list] } @@ -15,6 +14,6 @@ pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, #[get("/")] pub(crate) async fn list( state: &State, -) -> Json> { - Json(state.inner.mix_nodes.get_mixnodes_with_location().await) +) -> Json> { + Json(state.inner.mix_nodes.get_detailed_mixnodes().await) } diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs new file mode 100644 index 0000000000..80f24f6635 --- /dev/null +++ b/explorer-api/src/mix_nodes/location.rs @@ -0,0 +1,62 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +pub(crate) type LocationCache = HashMap; + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub(crate) struct GeoLocation { + pub(crate) ip: String, + pub(crate) country_code: String, + pub(crate) country_name: String, + pub(crate) region_code: String, + pub(crate) region_name: String, + pub(crate) city: String, + pub(crate) zip_code: String, + pub(crate) time_zone: String, + pub(crate) latitude: f32, + pub(crate) longitude: f32, + pub(crate) metro_code: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct LocationCacheItem { + pub(crate) location: Option, + pub(crate) valid_until: SystemTime, +} + +#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] +pub(crate) struct Location { + pub(crate) two_letter_iso_country_code: String, + pub(crate) three_letter_iso_country_code: String, + pub(crate) country_name: String, + pub(crate) lat: f32, + pub(crate) lng: f32, +} + +impl Location { + pub(crate) fn new(geo_location: GeoLocation) -> Self { + let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location); + Location { + country_name: geo_location.country_name, + two_letter_iso_country_code: geo_location.country_code, + three_letter_iso_country_code, + lat: geo_location.latitude, + lng: geo_location.longitude, + } + } +} + +impl LocationCacheItem { + pub(crate) fn new_from_location(location: Option) -> Self { + LocationCacheItem { + location, + valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day + } + } +} diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index dcbd5638e2..dacff38e32 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -1,241 +1,14 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::time::Duration; + +pub(crate) mod delegations; pub(crate) mod http; +pub(crate) mod location; +pub(crate) mod models; pub(crate) mod tasks; -mod utils; +pub(crate) mod utils; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::{Duration, SystemTime}; - -use rocket::tokio::sync::RwLock; -use serde::{Deserialize, Serialize}; - -use crate::mix_node::http::PrettyMixNodeBondWithLocation; -use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; -use mixnet_contract::{Delegation, MixNodeBond}; -use network_defaults::{ - default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, -}; -use validator_client::nymd::QueryNymdClient; - -pub(crate) type LocationCache = HashMap; - -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub(crate) struct GeoLocation { - pub(crate) ip: String, - pub(crate) country_code: String, - pub(crate) country_name: String, - pub(crate) region_code: String, - pub(crate) region_name: String, - pub(crate) city: String, - pub(crate) zip_code: String, - pub(crate) time_zone: String, - pub(crate) latitude: f32, - pub(crate) longitude: f32, - pub(crate) metro_code: u32, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct LocationCacheItem { - pub(crate) location: Option, - pub(crate) valid_until: SystemTime, -} - -#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] -pub(crate) struct Location { - pub(crate) two_letter_iso_country_code: String, - pub(crate) three_letter_iso_country_code: String, - pub(crate) country_name: String, - pub(crate) lat: f32, - pub(crate) lng: f32, -} - -impl Location { - pub(crate) fn new(geo_location: GeoLocation) -> Self { - let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location); - Location { - country_name: geo_location.country_name, - two_letter_iso_country_code: geo_location.country_code, - three_letter_iso_country_code, - lat: geo_location.latitude, - lng: geo_location.longitude, - } - } -} - -impl LocationCacheItem { - pub(crate) fn new_from_location(location: Option) -> Self { - LocationCacheItem { - location, - valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day - } - } -} - -#[derive(Clone, Debug)] -pub(crate) struct MixNodesResult { - pub(crate) valid_until: SystemTime, - pub(crate) value: HashMap, - location_cache: LocationCache, -} - -#[derive(Clone)] -pub(crate) struct ThreadsafeMixNodesResult { - inner: Arc>, -} - -impl ThreadsafeMixNodesResult { - pub(crate) fn new() -> Self { - ThreadsafeMixNodesResult { - inner: Arc::new(RwLock::new(MixNodesResult { - value: HashMap::new(), - valid_until: SystemTime::now() - Duration::from_secs(60), // in the past - location_cache: LocationCache::new(), - })), - } - } - - pub(crate) fn new_with_location_cache(location_cache: LocationCache) -> Self { - ThreadsafeMixNodesResult { - inner: Arc::new(RwLock::new(MixNodesResult { - value: HashMap::new(), - valid_until: SystemTime::now() - Duration::from_secs(60), // in the past - location_cache, - })), - } - } - - pub(crate) async fn is_location_valid(&self, identity_key: &str) -> bool { - self.inner - .read() - .await - .location_cache - .get(identity_key) - .map(|cache_item| cache_item.valid_until > SystemTime::now()) - .unwrap_or(false) - } - - pub(crate) async fn get_location_cache(&self) -> LocationCache { - self.inner.read().await.location_cache.clone() - } - - pub(crate) async fn set_location(&self, identity_key: &str, location: Option) { - let mut guard = self.inner.write().await; - - // cache the location for this mix node so that it can be used when the mix node list is refreshed - guard.location_cache.insert( - identity_key.to_string(), - LocationCacheItem::new_from_location(location), - ); - } - - pub(crate) async fn get(&self) -> MixNodesResult { - // check ttl - let valid_until = self.inner.read().await.valid_until; - - if valid_until < SystemTime::now() { - // force reload - self.refresh().await; - } - - // return in-memory cache - self.inner.read().await.clone() - } - - pub(crate) async fn get_mixnodes_with_location(&self) -> Vec { - let guard = self.inner.read().await; - guard - .value - .values() - .map(|bond| { - let location = guard.location_cache.get(&bond.mix_node.identity_key); - let copy = bond.clone(); - PrettyMixNodeBondWithLocation { - location: location.and_then(|l| l.location.clone()), - pledge_amount: copy.pledge_amount, - total_delegation: copy.total_delegation, - owner: copy.owner, - layer: copy.layer, - mix_node: copy.mix_node, - } - }) - .collect() - } - - pub(crate) async fn refresh(&self) { - // get mixnodes and cache the new value - let value = retrieve_mixnodes().await; - let location_cache = self.inner.read().await.location_cache.clone(); - *self.inner.write().await = MixNodesResult { - value: value - .into_iter() - .map(|bond| (bond.mix_node.identity_key.to_string(), bond)) - .collect(), - valid_until: SystemTime::now() + Duration::from_secs(30), - location_cache, - }; - } -} - -pub(crate) async fn retrieve_mixnodes() -> Vec { - let client = new_validator_client(); - - info!("About to retrieve mixnode bonds..."); - - let bonds: Vec = match client.get_cached_mixnodes().await { - Ok(result) => result, - Err(e) => { - error!("Unable to retrieve mixnode bonds: {:?}", e); - vec![] - } - }; - info!("Fetched {} mixnode bonds", bonds.len()); - bonds -} - -pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec { - let client = new_nymd_client(); - let delegates = match client - .get_all_nymd_single_mixnode_delegations(pubkey.to_string()) - .await - { - Ok(result) => result, - Err(e) => { - error!("Could not get delegations for mix node {}: {:?}", pubkey, e); - vec![] - } - }; - delegates -} - -pub(crate) async fn get_mixnode_delegations() -> Vec { - let client = new_nymd_client(); - let delegates = match client.get_all_network_delegations().await { - Ok(result) => result, - Err(e) => { - error!("Could not get all mix delegations: {:?}", e); - vec![] - } - }; - delegates -} - -fn new_nymd_client() -> validator_client::Client { - let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(); - let nymd_url = default_nymd_endpoints()[0].clone(); - let api_url = default_api_endpoints()[0].clone(); - - let client_config = validator_client::Config::new( - nymd_url, - api_url, - Some(mixnet_contract.parse().unwrap()), - None, - ); - - validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!") -} - -// TODO: inject constants -fn new_validator_client() -> validator_client::ApiClient { - validator_client::ApiClient::new(default_api_endpoints()[0].clone()) -} +pub(crate) const MIXNODES_CACHE_REFRESH_RATE: Duration = Duration::from_secs(30); +pub(crate) const MIXNODES_CACHE_ENTRY_TTL: Duration = Duration::from_secs(60); diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs new file mode 100644 index 0000000000..7a07131fc1 --- /dev/null +++ b/explorer-api/src/mix_nodes/models.rs @@ -0,0 +1,150 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; +use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; +use crate::mix_nodes::MIXNODES_CACHE_ENTRY_TTL; +use mixnet_contract_common::MixNodeBond; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::{Duration, SystemTime}; +use tokio::sync::RwLock; + +#[derive(Clone, Debug)] +pub(crate) struct MixNodesResult { + pub(crate) valid_until: SystemTime, + pub(crate) all_mixnodes: HashMap, + active_mixnodes: HashSet, + rewarded_mixnodes: HashSet, +} + +impl MixNodesResult { + fn new() -> Self { + MixNodesResult { + valid_until: SystemTime::now() - Duration::from_secs(60), // in the past + all_mixnodes: HashMap::new(), + active_mixnodes: HashSet::new(), + rewarded_mixnodes: HashSet::new(), + } + } + + fn determine_node_status(&self, public_key: &str) -> MixnodeStatus { + if self.active_mixnodes.contains(public_key) { + MixnodeStatus::Active + } else if self.rewarded_mixnodes.contains(public_key) { + MixnodeStatus::Standby + } else { + MixnodeStatus::Inactive + } + } + + fn is_valid(&self) -> bool { + self.valid_until >= SystemTime::now() + } + + fn get_mixnode(&self, pubkey: &str) -> Option { + if self.is_valid() { + self.all_mixnodes.get(pubkey).cloned() + } else { + None + } + } + + fn get_mixnodes(&self) -> Option> { + if self.is_valid() { + Some(self.all_mixnodes.clone()) + } else { + None + } + } +} + +#[derive(Clone)] +pub(crate) struct ThreadsafeMixNodesResult { + mixnode_results: Arc>, + location_cache: Arc>, +} + +impl ThreadsafeMixNodesResult { + pub(crate) fn new() -> Self { + ThreadsafeMixNodesResult { + mixnode_results: Arc::new(RwLock::new(MixNodesResult::new())), + location_cache: Arc::new(RwLock::new(LocationCache::new())), + } + } + + pub(crate) fn new_with_location_cache(location_cache: LocationCache) -> Self { + ThreadsafeMixNodesResult { + mixnode_results: Arc::new(RwLock::new(MixNodesResult::new())), + location_cache: Arc::new(RwLock::new(location_cache)), + } + } + + pub(crate) async fn is_location_valid(&self, identity_key: &str) -> bool { + self.location_cache + .read() + .await + .get(identity_key) + .map(|cache_item| cache_item.valid_until > SystemTime::now()) + .unwrap_or(false) + } + + pub(crate) async fn get_location_cache(&self) -> LocationCache { + self.location_cache.read().await.clone() + } + + pub(crate) async fn set_location(&self, identity_key: &str, location: Option) { + // cache the location for this mix node so that it can be used when the mix node list is refreshed + self.location_cache.write().await.insert( + identity_key.to_string(), + LocationCacheItem::new_from_location(location), + ); + } + + pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option { + self.mixnode_results.read().await.get_mixnode(pubkey) + } + + pub(crate) async fn get_mixnodes(&self) -> Option> { + self.mixnode_results.read().await.get_mixnodes() + } + + pub(crate) async fn get_detailed_mixnodes(&self) -> Vec { + let mixnodes_guard = self.mixnode_results.read().await; + let location_guard = self.location_cache.read().await; + + mixnodes_guard + .all_mixnodes + .values() + .map(|bond| { + let location = location_guard.get(&bond.mix_node.identity_key); + let copy = bond.clone(); + PrettyDetailedMixNodeBond { + location: location.and_then(|l| l.location.clone()), + status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key), + pledge_amount: copy.pledge_amount, + total_delegation: copy.total_delegation, + owner: copy.owner, + layer: copy.layer, + mix_node: copy.mix_node, + } + }) + .collect() + } + + pub(crate) async fn update_cache( + &self, + all_bonds: Vec, + rewarded_nodes: HashSet, + active_nodes: HashSet, + ) { + let mut guard = self.mixnode_results.write().await; + guard.all_mixnodes = all_bonds + .into_iter() + .map(|bond| (bond.mix_node.identity_key.to_string(), bond)) + .collect(); + guard.rewarded_mixnodes = rewarded_nodes; + guard.active_mixnodes = active_nodes; + guard.valid_until = SystemTime::now() + MIXNODES_CACHE_ENTRY_TTL; + } +} diff --git a/explorer-api/src/mix_nodes/tasks.rs b/explorer-api/src/mix_nodes/tasks.rs index f89cd61217..b6257c1660 100644 --- a/explorer-api/src/mix_nodes/tasks.rs +++ b/explorer-api/src/mix_nodes/tasks.rs @@ -1,24 +1,93 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::MIXNODES_CACHE_REFRESH_RATE; use crate::state::ExplorerApiStateContext; +use mixnet_contract_common::MixNodeBond; +use reqwest::Url; +use std::future::Future; +use validator_client::ValidatorClientError; pub(crate) struct MixNodesTasks { state: ExplorerApiStateContext, + validator_api_client: validator_client::ApiClient, } impl MixNodesTasks { - pub(crate) fn new(state: ExplorerApiStateContext) -> Self { - MixNodesTasks { state } + pub(crate) fn new(state: ExplorerApiStateContext, validator_api_endpoint: Url) -> Self { + MixNodesTasks { + state, + validator_api_client: validator_client::ApiClient::new(validator_api_endpoint), + } + } + + // a helper to remove duplicate code when grabbing active/rewarded/all mixnodes + async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec + where + F: FnOnce(&'a validator_client::ApiClient) -> Fut, + Fut: Future, ValidatorClientError>>, + { + let bonds = match f(&self.validator_api_client).await { + Ok(result) => result, + Err(e) => { + error!("Unable to retrieve mixnode bonds: {:?}", e); + vec![] + } + }; + + info!("Fetched {} mixnode bonds", bonds.len()); + bonds + } + + async fn retrieve_all_mixnodes(&self) -> Vec { + info!("About to retrieve all mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_mixnodes) + .await + } + + async fn retrieve_rewarded_mixnodes(&self) -> Vec { + info!("About to retrieve rewarded mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_rewarded_mixnodes) + .await + } + + async fn retrieve_active_mixnodes(&self) -> Vec { + info!("About to retrieve active mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_active_mixnodes) + .await + } + + async fn update_mixnode_cache(&self) { + let all_bonds = self.retrieve_all_mixnodes().await; + let rewarded_nodes = self + .retrieve_rewarded_mixnodes() + .await + .into_iter() + .map(|bond| bond.mix_node.identity_key) + .collect(); + let active_nodes = self + .retrieve_active_mixnodes() + .await + .into_iter() + .map(|bond| bond.mix_node.identity_key) + .collect(); + self.state + .inner + .mix_nodes + .update_cache(all_bonds, rewarded_nodes, active_nodes) + .await; } pub(crate) fn start(self) { info!("Spawning mix nodes task runner..."); tokio::spawn(async move { - let mut interval_timer = tokio::time::interval(std::time::Duration::from_secs(2)); + let mut interval_timer = tokio::time::interval(MIXNODES_CACHE_REFRESH_RATE); loop { // wait for the next interval tick interval_timer.tick().await; info!("Updating mix node cache..."); - self.state.inner.mix_nodes.refresh().await; + self.update_mixnode_cache().await; info!("Done"); } }); diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index 7e46ce7afd..542bfe8660 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,5 +1,12 @@ -use crate::mix_nodes::GeoLocation; +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::location::GeoLocation; use isocountry::CountryCode; +use network_defaults::{ + default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, +}; +use validator_client::nymd::QueryNymdClient; pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { match CountryCode::for_alpha2(&geo.country_code) { @@ -13,3 +20,18 @@ pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String } } } + +pub(crate) fn new_nymd_client() -> validator_client::Client { + let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(); + let nymd_url = default_nymd_endpoints()[0].clone(); + let api_url = default_api_endpoints()[0].clone(); + + let client_config = validator_client::Config::new( + nymd_url, + api_url, + Some(mixnet_contract.parse().unwrap()), + None, + ); + + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!") +} diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 006938bec6..67a2730bb7 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -8,7 +8,7 @@ use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; -use mixnet_contract::MixNodeBond; +use mixnet_contract_common::MixNodeBond; use crate::ping::models::PingResponse; use crate::state::ExplorerApiStateContext; diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index 3202abc9ea..676c4f9423 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -5,13 +5,14 @@ use chrono::{DateTime, Utc}; use log::info; use serde::{Deserialize, Serialize}; -use mixnet_contract::MixNodeBond; +use mixnet_contract_common::MixNodeBond; use crate::country_statistics::country_nodes_distribution::{ ConcurrentCountryNodesDistribution, CountryNodesDistribution, }; use crate::mix_node::models::ThreadsafeMixNodeCache; -use crate::mix_nodes::{LocationCache, ThreadsafeMixNodesResult}; +use crate::mix_nodes::location::LocationCache; +use crate::mix_nodes::models::ThreadsafeMixNodesResult; use crate::ping::models::ThreadsafePingCache; // TODO: change to an environment variable with a default value @@ -27,7 +28,7 @@ pub struct ExplorerApiState { impl ExplorerApiState { pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option { - self.mix_nodes.get().await.value.get(pubkey).cloned() + self.mix_nodes.get_mixnode(pubkey).await } } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 7ab0c36296..3be5369708 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -164,6 +164,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" + [[package]] name = "bincode" version = "1.3.3" @@ -175,14 +181,16 @@ dependencies = [ [[package]] name = "bip32" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d0f0fc59c7ba0333eed9dcc1b6980baa7b7a4dc7c6c5885994d0674f7adf34" +checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f" dependencies = [ "bs58", - "hkd32", "hmac", - "k256", + "k256 0.10.1", + "once_cell", + "pbkdf2", + "rand_core 0.6.3", "ripemd160", "sha2", "subtle 2.4.1", @@ -292,8 +300,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" dependencies = [ "digest 0.9.0", - "ff", - "group", + "ff 0.10.1", + "group 0.10.0", "pairing", "rand_core 0.6.3", "subtle 2.4.1", @@ -555,12 +563,25 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + [[package]] name = "constant_time_eq" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +[[package]] +name = "contracts-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -651,8 +672,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.8.0" -source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0254ffee603f5301d6a66963d9e1cc5091479c22e2e925e1f7689c8027a0828" dependencies = [ "prost", "prost-types", @@ -661,15 +683,16 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.3.0" -source = "git+https://github.com/cosmos/cosmos-rust?rev=e5a1872083abb3d88fa62dda966e7f5408deba58#e5a1872083abb3d88fa62dda966e7f5408deba58" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d" dependencies = [ "bip32", "cosmos-sdk-proto", - "ecdsa", + "ecdsa 0.13.4", "eyre", "getrandom 0.2.3", - "k256", + "k256 0.10.1", "prost", "prost-types", "rand_core 0.6.3", @@ -689,7 +712,7 @@ checksum = "a380b87642204557629c9b72988c47b55fbfe6d474960adba56b22331504956a" dependencies = [ "digest 0.9.0", "ed25519-zebra", - "k256", + "k256 0.9.6", "rand_core 0.5.1", "thiserror", ] @@ -833,6 +856,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.4", + "rand_core 0.6.3", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "crypto-mac" version = "0.7.0" @@ -989,7 +1024,16 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" dependencies = [ - "const-oid", + "const-oid 0.6.2", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", ] [[package]] @@ -1147,12 +1191,24 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ - "der", - "elliptic-curve", + "der 0.4.5", + "elliptic-curve 0.10.6", "hmac", "signature", ] +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der 0.5.1", + "elliptic-curve 0.11.6", + "rfc6979", + "signature", +] + [[package]] name = "ed25519" version = "1.3.0" @@ -1202,16 +1258,33 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b" dependencies = [ - "crypto-bigint", - "ff", + "crypto-bigint 0.2.11", + "ff 0.10.1", "generic-array 0.14.4", - "group", - "pkcs8", + "group 0.10.0", + "pkcs8 0.7.6", "rand_core 0.6.3", "subtle 2.4.1", "zeroize", ] +[[package]] +name = "elliptic-curve" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "decb3a27ea454a5f23f96eb182af0671c12694d64ecc33dada74edd1301f6cfc" +dependencies = [ + "crypto-bigint 0.3.2", + "der 0.5.1", + "ff 0.11.0", + "generic-array 0.14.4", + "group 0.11.0", + "rand_core 0.6.3", + "sec1", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "embed_plist" version = "1.2.0" @@ -1274,6 +1347,16 @@ dependencies = [ "subtle 2.4.1", ] +[[package]] +name = "ff" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +dependencies = [ + "rand_core 0.6.3", + "subtle 2.4.1", +] + [[package]] name = "field-offset" version = "0.3.4" @@ -1775,7 +1858,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" dependencies = [ "byteorder", - "ff", + "ff 0.10.1", + "rand_core 0.6.3", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff 0.11.0", "rand_core 0.6.3", "subtle 2.4.1", ] @@ -1936,20 +2030,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" -[[package]] -name = "hkd32" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84f2a5541afe0725f0b95619d6af614f48c1b176385b8aa30918cfb8c4bfafc8" -dependencies = [ - "hmac", - "once_cell", - "pbkdf2", - "rand_core 0.6.3", - "sha2", - "zeroize", -] - [[package]] name = "hkdf" version = "0.11.0" @@ -2291,8 +2371,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea" dependencies = [ "cfg-if 1.0.0", - "ecdsa", - "elliptic-curve", + "ecdsa 0.12.4", + "elliptic-curve 0.10.6", + "sha2", +] + +[[package]] +name = "k256" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7511aa19fa182a8a4885760c4e5675b17173b02ae86ec5d376d34f5278c874b9" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa 0.13.4", + "elliptic-curve 0.11.6", + "sec1", "sha2", "sha3", ] @@ -2503,10 +2596,11 @@ dependencies = [ ] [[package]] -name = "mixnet-contract" +name = "mixnet-contract-common" version = "0.1.0" dependencies = [ "az", + "contracts-common", "cosmwasm-std", "fixed", "log", @@ -2711,7 +2805,7 @@ dependencies = [ "credentials", "dirs", "eyre", - "mixnet-contract", + "mixnet-contract-common", "rand 0.6.5", "serde", "serde_json", @@ -2732,9 +2826,9 @@ dependencies = [ "bls12_381", "bs58", "digest 0.9.0", - "ff", + "ff 0.10.1", "getrandom 0.2.3", - "group", + "group 0.10.0", "itertools", "rand 0.8.4", "serde", @@ -2855,7 +2949,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" dependencies = [ - "group", + "group 0.10.0", ] [[package]] @@ -2928,9 +3022,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pbkdf2" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739" dependencies = [ "crypto-mac 0.11.1", ] @@ -3165,8 +3259,19 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" dependencies = [ - "der", - "spki", + "der 0.4.5", + "spki 0.4.1", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der 0.5.1", + "spki 0.5.4", + "zeroize", ] [[package]] @@ -3683,6 +3788,17 @@ dependencies = [ "winreg", ] +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint 0.3.2", + "hmac", + "zeroize", +] + [[package]] name = "rfd" version = "0.4.4" @@ -3852,6 +3968,19 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der 0.5.1", + "generic-array 0.14.4", + "pkcs8 0.8.0", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "security-framework" version = "2.4.2" @@ -4180,7 +4309,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" dependencies = [ - "der", + "der 0.4.5", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der 0.5.1", ] [[package]] @@ -4707,9 +4846,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9015fdeab074f9b8f97dcb89c2bb2ec8537c89423e95551e8d7ecdfbab58a329" +checksum = "a9ef686b8ecd36550d0581f0989c9d8607090b23005df479d8e6ba348b5800b9" dependencies = [ "async-trait", "bytes", @@ -4717,7 +4856,7 @@ dependencies = [ "ed25519-dalek", "flex-error", "futures", - "k256", + "k256 0.10.1", "num-traits", "once_cell", "prost", @@ -4738,9 +4877,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2b2e6d4442bab49319dbacdfd79c5929bc6e0b35d1e0d959ff5b79fddf3f018" +checksum = "ecc127f82e7a8c7337c1f293d65a821380d3407c4c44bc979ef4da0ebc3b31ed" dependencies = [ "flex-error", "serde", @@ -4752,9 +4891,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da86f6e52ced9c2f24c4ae57662ce8a44dd90ee7bc47bae27a710b02d48e193c" +checksum = "a9e0a0251fd81bed7420bea0f4d91c2a58f6c9fa34d5b2f70bed0ba8890de5aa" dependencies = [ "bytes", "flex-error", @@ -4770,15 +4909,15 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.23.2" +version = "0.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f5f4875c36798e5590894a5176cf8d75e4bc81ec34ced1b9a87609e7d56861" +checksum = "626c493e9ced3a9de37583bbd929f4b6dbd49aa513ab9b4776aa44a9332ce9b5" dependencies = [ "async-trait", "bytes", "flex-error", "futures", - "getrandom 0.1.16", + "getrandom 0.2.3", "http", "hyper", "hyper-proxy", @@ -5130,6 +5269,14 @@ dependencies = [ "getrandom 0.2.3", ] +[[package]] +name = "validator-api-requests" +version = "0.1.0" +dependencies = [ + "serde", + "ts-rs", +] + [[package]] name = "validator-client" version = "0.1.0" @@ -5144,7 +5291,7 @@ dependencies = [ "flate2", "itertools", "log", - "mixnet-contract", + "mixnet-contract-common", "network-defaults", "prost", "reqwest", @@ -5154,6 +5301,7 @@ dependencies = [ "thiserror", "ts-rs", "url", + "validator-api-requests", "vesting-contract", ] @@ -5188,12 +5336,18 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", - "mixnet-contract", - "rand 0.8.4", + "mixnet-contract-common", "schemars", "serde", "thiserror", + "vesting-contract-common", +] + +[[package]] +name = "vesting-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", ] [[package]] diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index 8b2da3c711..457444a2c1 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -24,18 +24,18 @@ dirs = "4.0" bip39 = "1.0" thiserror = "1.0" tendermint-rpc = "0.23.0" -url = "2.0" +url = "2.2" rand = "0.6.5" eyre = "0.6.5" -cosmrs = { git = "https://github.com/cosmos/cosmos-rust", rev="e5a1872083abb3d88fa62dda966e7f5408deba58", features = ["rpc", "bip32", "cosmwasm"] } +cosmrs = { version = "0.4.1", features = ["rpc", "bip32", "cosmwasm"] } cosmwasm-std = "1.0.0-beta3" validator-client = { path = "../../common/client-libs/validator-client", features = [ "nymd-client", ] } -mixnet-contract = { path = "../../common/mixnet-contract" } +mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" } config = { path = "../../common/config" } coconut-interface = { path = "../../common/coconut-interface" } credentials = { path = "../../common/credentials" } @@ -43,13 +43,13 @@ credentials = { path = "../../common/credentials" } [dev-dependencies] ts-rs = "5.1" -[dev-dependencies.mixnet-contract] -path = "../../common/mixnet-contract" +[dev-dependencies.mixnet-contract-common] +path = "../../common/cosmwasm-smart-contracts/mixnet-contract" features = ["ts-rs"] [dev-dependencies.validator-client] path = "../../common/client-libs/validator-client" -features = ["ts-rs"] +features = ["typescript-types"] [features] default = ["custom-protocol"] diff --git a/nym-wallet/src-tauri/src/coin.rs b/nym-wallet/src-tauri/src/coin.rs index ecfe2faa4f..5df1d9532b 100644 --- a/nym-wallet/src-tauri/src/coin.rs +++ b/nym-wallet/src-tauri/src/coin.rs @@ -46,6 +46,14 @@ impl FromStr for Denom { } } +impl TryFrom for Denom { + type Error = BackendError; + + fn try_from(value: CosmosDenom) -> Result { + Denom::from_str(&value.to_string()) + } +} + #[cfg_attr(test, derive(ts_rs::TS))] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] pub struct Coin { diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index 90fbf670fa..a5107fe393 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -5,15 +5,12 @@ use config::defaults::{default_validators, ValidatorDetails, DEFAULT_MIXNET_CONT use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; -use std::str::FromStr; -use tendermint_rpc::Url; +use url::Url; mod template; use template::config_template; -use crate::error::BackendError; - #[derive(Debug, Default, Deserialize, Serialize, Clone)] #[serde(deny_unknown_fields)] pub struct Config { @@ -68,23 +65,33 @@ impl NymConfig for Config { } impl Config { - pub fn get_nymd_validator_url(&self) -> Result { + pub fn get_nymd_validator_url(&self) -> Url { // TODO make this a random choice if let Some(validator_details) = self.base.validators.first() { - match tendermint_rpc::Url::from_str(&validator_details.nymd_url().to_string()) { - Ok(url) => Ok(url), - Err(e) => Err(e.into()), - } + validator_details.nymd_url() } else { panic!("No validators found in config") } } - pub fn get_mixnet_contract_address(&self) -> String { - self.base.mixnet_contract_address.clone() + pub fn get_validator_api_url(&self) -> Url { + // TODO make this a random choice + if let Some(validator_details) = self.base.validators.first() { + validator_details.api_url().expect("no api url provided") + } else { + panic!("No validators found in config") + } } - // pub fn get_mnemonic(&self) -> String { - // self.base.mnemonic.clone() - // } + pub fn get_mixnet_contract_address(&self) -> cosmrs::AccountId { + self + .base + .mixnet_contract_address + .parse() + .expect("stored mixnet contract address is not a valid account address") + } + + pub fn get_vesting_contract_address(&self) -> Option { + None + } } diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index 605cc78047..a99090ba56 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -1,6 +1,7 @@ use serde::{Serialize, Serializer}; use thiserror::Error; use validator_client::nymd::error::NymdError; +use validator_client::validator_api::error::ValidatorAPIError; #[derive(Error, Debug)] pub enum BackendError { @@ -29,6 +30,11 @@ pub enum BackendError { #[from] source: eyre::Report, }, + #[error("{source}")] + ValidatorApiError { + #[from] + source: ValidatorAPIError, + }, #[error("Client has not been initialized yet, connect with mnemonic to initialize")] ClientNotInitialized, #[error("No balance available for address {0}")] diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index e402137e39..d2e873a297 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -3,7 +3,7 @@ windows_subsystem = "windows" )] -use mixnet_contract::{Gateway, MixNode}; +use mixnet_contract_common::{Gateway, MixNode}; use std::sync::Arc; use tauri::Menu; use tokio::sync::RwLock; @@ -19,6 +19,7 @@ mod utils; use crate::menu::AddDefaultSubmenus; use crate::operations::mixnet; +use crate::operations::validator_api; use crate::operations::vesting; use crate::state::State; @@ -36,13 +37,14 @@ fn main() { mixnet::bond::bond_mixnode, mixnet::bond::unbond_gateway, mixnet::bond::unbond_mixnode, + mixnet::bond::update_mixnode, mixnet::bond::mixnode_bond_details, mixnet::bond::gateway_bond_details, mixnet::delegate::delegate_to_mixnode, mixnet::delegate::get_reverse_mix_delegations_paged, mixnet::delegate::undelegate_from_mixnode, mixnet::send::send, - utils::get_approximate_fee, + utils::outdated_get_approximate_fee, utils::major_to_minor, utils::minor_to_major, utils::owns_gateway, @@ -62,6 +64,11 @@ fn main() { vesting::queries::original_vesting, vesting::queries::delegated_free, vesting::queries::delegated_vesting, + validator_api::status::mixnode_core_node_status, + validator_api::status::gateway_core_node_status, + validator_api::status::mixnode_status, + validator_api::status::mixnode_reward_estimation, + validator_api::status::mixnode_stake_saturation, ]) .menu(Menu::new().add_default_app_submenu_if_macos()) .run(tauri::generate_context!()) @@ -71,16 +78,21 @@ fn main() { #[cfg(test)] mod test { ts_rs::export! { - mixnet_contract::MixNode => "../src/types/rust/mixnode.ts", + mixnet_contract_common::MixNode => "../src/types/rust/mixnode.ts", crate::coin::Coin => "../src/types/rust/coin.ts", crate::mixnet::account::Balance => "../src/types/rust/balance.ts", - mixnet_contract::Gateway => "../src/types/rust/gateway.ts", + mixnet_contract_common::Gateway => "../src/types/rust/gateway.ts", crate::mixnet::send::TauriTxResult => "../src/types/rust/tauritxresult.ts", crate::mixnet::send::TransactionDetails => "../src/types/rust/transactiondetails.ts", validator_client::nymd::fee::helpers::Operation => "../src/types/rust/operation.ts", crate::coin::Denom => "../src/types/rust/denom.ts", crate::utils::DelegationResult => "../src/types/rust/delegationresult.ts", crate::mixnet::account::Account => "../src/types/rust/account.ts", - crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts" + crate::mixnet::admin::TauriContractStateParams => "../src/types/rust/stateparams.ts", + validator_client::models::CoreNodeStatusResponse => "../src/types/corenodestatusresponse.ts", + validator_client::models::MixnodeStatus => "../src/types/rust/mixnodestatus.ts", + validator_client::models::MixnodeStatusResponse => "../src/types/rust/mixnodestatusresponse.ts", + validator_client::models::RewardEstimationResponse => "../src/types/rust/rewardestimationresponse.ts", + validator_client::models::StakeSaturationResponse => "../src/types/rust/stakesaturaionresponse.ts", } } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5824b355a7..320f84e4d4 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -1,14 +1,16 @@ -use crate::client; use crate::coin::{Coin, Denom}; use crate::config::Config; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use bip39::{Language, Mnemonic}; use serde::{Deserialize, Serialize}; +use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; use tokio::sync::RwLock; -use validator_client::nymd::{AccountId, NymdClient, SigningNymdClient}; +use validator_client::nymd::SigningNymdClient; +use validator_client::Client; #[cfg_attr(test, derive(ts_rs::TS))] #[derive(Serialize, Deserialize)] @@ -37,14 +39,14 @@ pub async fn connect_with_mnemonic( _connect_with_mnemonic(mnemonic, &r_state.config()) }; - let contract_address = client.mixnet_contract_address()?.to_string(); - let client_address = client.address().to_string(); - let denom = client.denom()?; + let contract_address = client.nymd.mixnet_contract_address()?.to_string(); + let client_address = client.nymd.address().to_string(); + let denom = client.nymd.denom()?; let account = Account { contract_address, client_address, - denom: Denom::from_str(&denom.to_string())?, + denom: denom.try_into()?, mnemonic: None, }; @@ -58,8 +60,8 @@ pub async fn connect_with_mnemonic( pub async fn get_balance( state: tauri::State<'_, Arc>>, ) -> Result { - match client!(state) - .get_mixnet_balance(client!(state).address()) + match nymd_client!(state) + .get_mixnet_balance(nymd_client!(state).address()) .await { Ok(Some(coin)) => { @@ -73,7 +75,7 @@ pub async fn get_balance( }) } Ok(None) => Err(BackendError::NoBalance( - client!(state).address().to_string(), + nymd_client!(state).address().to_string(), )), Err(e) => Err(BackendError::from(e)), } @@ -94,13 +96,15 @@ fn random_mnemonic() -> Mnemonic { Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap() } -fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> NymdClient { - match NymdClient::connect_with_mnemonic( - config.get_nymd_validator_url().unwrap(), - Some(AccountId::from_str(&config.get_mixnet_contract_address()).unwrap()), - None, +fn _connect_with_mnemonic(mnemonic: Mnemonic, config: &Config) -> Client { + match validator_client::Client::new_signing( + validator_client::Config::new( + config.get_nymd_validator_url(), + config.get_validator_api_url(), + Some(config.get_mixnet_contract_address()), + config.get_vesting_contract_address(), + ), mnemonic, - None, ) { Ok(client) => client, Err(e) => panic!("{}", e), diff --git a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs index 961a57b4e7..14bf378378 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/admin.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/admin.rs @@ -1,8 +1,8 @@ -use crate::client; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use cosmwasm_std::Uint128; -use mixnet_contract::ContractStateParams; +use mixnet_contract_common::ContractStateParams; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use std::sync::Arc; @@ -48,7 +48,7 @@ impl TryFrom for ContractStateParams { pub async fn get_contract_settings( state: tauri::State<'_, Arc>>, ) -> Result { - Ok(client!(state).get_contract_settings().await?.into()) + Ok(nymd_client!(state).get_contract_settings().await?.into()) } #[tauri::command] @@ -57,7 +57,7 @@ pub async fn update_contract_settings( state: tauri::State<'_, Arc>>, ) -> Result { let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; - client!(state) + nymd_client!(state) .update_contract_settings(mixnet_contract_settings_params.clone()) .await?; Ok(mixnet_contract_settings_params.into()) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index b1801b576f..9a2cf0cb30 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -1,9 +1,9 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use crate::{Gateway, MixNode}; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract_common::{GatewayBond, MixNodeBond}; use std::convert::TryInto; use std::sync::Arc; use tokio::sync::RwLock; @@ -15,7 +15,7 @@ pub async fn bond_gateway( owner_signature: String, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state) + nymd_client!(state) .bond_gateway(gateway, owner_signature, pledge.try_into()?) .await?; Ok(()) @@ -25,7 +25,7 @@ pub async fn bond_gateway( pub async fn unbond_gateway( state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state).unbond_gateway().await?; + nymd_client!(state).unbond_gateway().await?; Ok(()) } @@ -33,7 +33,7 @@ pub async fn unbond_gateway( pub async fn unbond_mixnode( state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state).unbond_mixnode().await?; + nymd_client!(state).unbond_mixnode().await?; Ok(()) } @@ -44,19 +44,30 @@ pub async fn bond_mixnode( pledge: Coin, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state) + nymd_client!(state) .bond_mixnode(mixnode, owner_signature, pledge.try_into()?) .await?; Ok(()) } +#[tauri::command] +pub async fn update_mixnode( + profit_margin_percent: u8, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + nymd_client!(state) + .update_mixnode_config(profit_margin_percent) + .await?; + Ok(()) +} + #[tauri::command] pub async fn mixnode_bond_details( state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { let guard = state.read().await; let client = guard.client()?; - let bond = client.owns_mixnode(client.address()).await?; + let bond = client.nymd.owns_mixnode(client.nymd.address()).await?; Ok(bond) } @@ -66,6 +77,6 @@ pub async fn gateway_bond_details( ) -> Result, BackendError> { let guard = state.read().await; let client = guard.client()?; - let bond = client.owns_gateway(client.address()).await?; + let bond = client.nymd.owns_gateway(client.nymd.address()).await?; Ok(bond) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs index 1fb96308e4..dbf56cc0ad 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/delegate.rs @@ -1,10 +1,10 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use crate::utils::DelegationResult; use cosmwasm_std::Coin as CosmWasmCoin; -use mixnet_contract::PagedDelegatorDelegationsResponse; +use mixnet_contract_common::PagedDelegatorDelegationsResponse; use std::convert::TryInto; use std::sync::Arc; use tokio::sync::RwLock; @@ -16,11 +16,11 @@ pub async fn delegate_to_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { let delegation: CosmWasmCoin = amount.try_into()?; - client!(state) + nymd_client!(state) .delegate_to_mixnode(identity, &delegation) .await?; Ok(DelegationResult::new( - &client!(state).address().to_string(), + &nymd_client!(state).address().to_string(), identity, Some(delegation.into()), )) @@ -31,9 +31,11 @@ pub async fn undelegate_from_mixnode( identity: &str, state: tauri::State<'_, Arc>>, ) -> Result { - client!(state).remove_mixnode_delegation(identity).await?; + nymd_client!(state) + .remove_mixnode_delegation(identity) + .await?; Ok(DelegationResult::new( - &client!(state).address().to_string(), + &nymd_client!(state).address().to_string(), identity, None, )) @@ -44,8 +46,8 @@ pub async fn get_reverse_mix_delegations_paged( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) - .get_delegator_delegations_paged(client!(state).address().to_string(), None, None) + nymd_client!(state) + .get_delegator_delegations_paged(nymd_client!(state).address().to_string(), None, None) .await?, ) } diff --git a/nym-wallet/src-tauri/src/operations/mixnet/send.rs b/nym-wallet/src-tauri/src/operations/mixnet/send.rs index 417aa42177..1176d86227 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/send.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/send.rs @@ -1,6 +1,6 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use serde::{Deserialize, Serialize}; use std::convert::TryInto; @@ -51,13 +51,13 @@ pub async fn send( ) -> Result { let address = AccountId::from_str(address)?; let cosmos_amount: CosmosCoin = amount.clone().try_into()?; - let result = client!(state) + let result = nymd_client!(state) .send(&address, vec![cosmos_amount], memo) .await?; Ok(TauriTxResult::new( result, TransactionDetails { - from_address: client!(state).address().to_string(), + from_address: nymd_client!(state).address().to_string(), to_address: address.to_string(), amount, }, diff --git a/nym-wallet/src-tauri/src/operations/mod.rs b/nym-wallet/src-tauri/src/operations/mod.rs index 9bfd654e73..c49e27d4d2 100644 --- a/nym-wallet/src-tauri/src/operations/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mod.rs @@ -1,2 +1,3 @@ pub mod mixnet; +pub mod validator_api; pub mod vesting; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/mod.rs b/nym-wallet/src-tauri/src/operations/validator_api/mod.rs new file mode 100644 index 0000000000..3529bb0b4a --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/validator_api/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod status; diff --git a/nym-wallet/src-tauri/src/operations/validator_api/status.rs b/nym-wallet/src-tauri/src/operations/validator_api/status.rs new file mode 100644 index 0000000000..cb2fd9d17a --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/validator_api/status.rs @@ -0,0 +1,69 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::api_client; +use crate::error::BackendError; +use crate::state::State; +use std::sync::Arc; +use tokio::sync::RwLock; +use validator_client::models::{ + CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, +}; + +#[tauri::command] +pub async fn mixnode_core_node_status( + identity: &str, + since: Option, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_mixnode_core_status_count(identity, since) + .await?, + ) +} + +#[tauri::command] +pub async fn gateway_core_node_status( + identity: &str, + since: Option, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_gateway_core_status_count(identity, since) + .await?, + ) +} + +#[tauri::command] +pub async fn mixnode_status( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok(api_client!(state).get_mixnode_status(identity).await?) +} + +#[tauri::command] +pub async fn mixnode_reward_estimation( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_mixnode_reward_estimation(identity) + .await?, + ) +} + +#[tauri::command] +pub async fn mixnode_stake_saturation( + identity: &str, + state: tauri::State<'_, Arc>>, +) -> Result { + Ok( + api_client!(state) + .get_mixnode_stake_saturation(identity) + .await?, + ) +} diff --git a/nym-wallet/src-tauri/src/operations/vesting/bond.rs b/nym-wallet/src-tauri/src/operations/vesting/bond.rs index 58a559b414..8962d9c58c 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/bond.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/bond.rs @@ -1,6 +1,6 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use crate::{Gateway, MixNode}; use std::convert::TryInto; @@ -15,7 +15,7 @@ pub async fn vesting_bond_gateway( owner_signature: String, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state) + nymd_client!(state) .vesting_bond_gateway(gateway, &owner_signature, pledge.try_into()?) .await?; Ok(()) @@ -25,7 +25,7 @@ pub async fn vesting_bond_gateway( pub async fn vesting_unbond_gateway( state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state).vesting_unbond_gateway().await?; + nymd_client!(state).vesting_unbond_gateway().await?; Ok(()) } @@ -33,7 +33,7 @@ pub async fn vesting_unbond_gateway( pub async fn vesting_unbond_mixnode( state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state).vesting_unbond_mixnode().await?; + nymd_client!(state).vesting_unbond_mixnode().await?; Ok(()) } @@ -44,7 +44,7 @@ pub async fn vesting_bond_mixnode( pledge: Coin, state: tauri::State<'_, Arc>>, ) -> Result<(), BackendError> { - client!(state) + nymd_client!(state) .vesting_bond_mixnode(mixnode, &owner_signature, pledge.try_into()?) .await?; Ok(()) diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index ed7bb14135..c807ce2297 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -1,6 +1,6 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use crate::utils::DelegationResult; use cosmwasm_std::Coin as CosmWasmCoin; @@ -16,11 +16,11 @@ pub async fn vesting_delegate_to_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { let delegation: CosmWasmCoin = amount.try_into()?; - client!(state) + nymd_client!(state) .vesting_delegate_to_mixnode(identity, &delegation) .await?; Ok(DelegationResult::new( - &client!(state).address().to_string(), + &nymd_client!(state).address().to_string(), identity, Some(delegation.into()), )) @@ -31,11 +31,11 @@ pub async fn vesting_undelegate_from_mixnode( identity: &str, state: tauri::State<'_, Arc>>, ) -> Result { - client!(state) + nymd_client!(state) .vesting_undelegate_from_mixnode(identity) .await?; Ok(DelegationResult::new( - &client!(state).address().to_string(), + &nymd_client!(state).address().to_string(), identity, None, )) diff --git a/nym-wallet/src-tauri/src/operations/vesting/queries.rs b/nym-wallet/src-tauri/src/operations/vesting/queries.rs index 3512ce8a9d..be6e68d663 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/queries.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/queries.rs @@ -1,6 +1,6 @@ -use crate::client; use crate::coin::Coin; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use cosmwasm_std::Timestamp; use std::sync::Arc; @@ -14,7 +14,7 @@ pub async fn locked_coins( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .locked_coins(address, block_time.map(Timestamp::from_seconds)) .await? .into(), @@ -28,7 +28,7 @@ pub async fn spendable_coins( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .spendable_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -45,7 +45,7 @@ pub async fn vested_coins( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .vested_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -62,7 +62,7 @@ pub async fn vesting_coins( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .vesting_coins( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -78,7 +78,7 @@ pub async fn vesting_start_time( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .vesting_start_time(vesting_account_address) .await? .seconds(), @@ -91,7 +91,7 @@ pub async fn vesting_end_time( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .vesting_end_time(vesting_account_address) .await? .seconds(), @@ -104,7 +104,7 @@ pub async fn original_vesting( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .original_vesting(vesting_account_address) .await? .into(), @@ -118,7 +118,7 @@ pub async fn delegated_free( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .delegated_free( vesting_account_address, block_time.map(Timestamp::from_seconds), @@ -135,7 +135,7 @@ pub async fn delegated_vesting( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) + nymd_client!(state) .delegated_vesting( vesting_account_address, block_time.map(Timestamp::from_seconds), diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index 8603d0c592..2b423cf744 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,15 +1,16 @@ use crate::config::Config; use crate::error::BackendError; -use validator_client::nymd::{NymdClient, SigningNymdClient}; +use validator_client::nymd::SigningNymdClient; +use validator_client::Client; #[derive(Default)] pub struct State { config: Config, - signing_client: Option>, + signing_client: Option>, } impl State { - pub fn client(&self) -> Result<&NymdClient, BackendError> { + pub fn client(&self) -> Result<&Client, BackendError> { self .signing_client .as_ref() @@ -20,7 +21,7 @@ impl State { self.config.clone() } - pub fn set_client(&mut self, signing_client: NymdClient) { + pub fn set_client(&mut self, signing_client: Client) { self.signing_client = Some(signing_client) } } @@ -31,3 +32,17 @@ macro_rules! client { $state.read().await.client()? }; } + +#[macro_export] +macro_rules! nymd_client { + ($state:ident) => { + $state.read().await.client()?.nymd + }; +} + +#[macro_export] +macro_rules! api_client { + ($state:ident) => { + $state.read().await.client()?.validator_api + }; +} diff --git a/nym-wallet/src-tauri/src/utils.rs b/nym-wallet/src-tauri/src/utils.rs index 83b35ae9be..7cb64f91f0 100644 --- a/nym-wallet/src-tauri/src/utils.rs +++ b/nym-wallet/src-tauri/src/utils.rs @@ -1,6 +1,6 @@ -use crate::client; use crate::coin::{Coin, Denom}; use crate::error::BackendError; +use crate::nymd_client; use crate::state::State; use crate::Operation; use serde::{Deserialize, Serialize}; @@ -24,8 +24,8 @@ pub async fn owns_mixnode( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) - .owns_mixnode(client!(state).address()) + nymd_client!(state) + .owns_mixnode(nymd_client!(state).address()) .await? .is_some(), ) @@ -36,8 +36,8 @@ pub async fn owns_gateway( state: tauri::State<'_, Arc>>, ) -> Result { Ok( - client!(state) - .owns_gateway(client!(state).address()) + nymd_client!(state) + .owns_gateway(nymd_client!(state).address()) .await? .is_some(), ) @@ -46,11 +46,11 @@ pub async fn owns_gateway( // NOTE: this uses OUTDATED defaults that might have no resemblance with the reality // as for the actual transaction, the gas cost is being simulated beforehand #[tauri::command] -pub async fn get_approximate_fee( +pub async fn outdated_get_approximate_fee( operation: Operation, state: tauri::State<'_, Arc>>, ) -> Result { - let approximate_fee = operation.default_fee(client!(state).gas_price()); + let approximate_fee = operation.default_fee(nymd_client!(state).gas_price()); let mut coin = Coin::new("0", &Denom::Major); for f in approximate_fee.amount { coin = coin + f.into(); diff --git a/nym-wallet/src/components/AppBar.tsx b/nym-wallet/src/components/AppBar.tsx index f2437d2c56..c30773a1fd 100644 --- a/nym-wallet/src/components/AppBar.tsx +++ b/nym-wallet/src/components/AppBar.tsx @@ -1,9 +1,10 @@ -import React, { useContext, useEffect } from 'react' +import React, { useContext } from 'react' import { AppBar as MuiAppBar, Divider, Grid, IconButton, Toolbar, Typography, useMediaQuery } from '@mui/material' import { Box } from '@mui/system' -import { Logout, SettingsOutlined } from '@mui/icons-material' +import { Logout } from '@mui/icons-material' import { ClientContext } from '../context/main' import { CopyToClipboard } from '.' +import { Node as NodeIcon } from '../svg-icons/node' export const AppBar = () => { const { userBalance, clientDetails, showSettings, logOut, handleShowSettings } = useContext(ClientContext) @@ -20,7 +21,6 @@ export const AppBar = () => { {matches && ( <> - { sx={{ color: showSettings ? 'primary.main' : 'nym.background.dark' }} size="small" > - + diff --git a/nym-wallet/src/components/NymCard.tsx b/nym-wallet/src/components/NymCard.tsx index bfaf7fa18a..8a6704e62a 100644 --- a/nym-wallet/src/components/NymCard.tsx +++ b/nym-wallet/src/components/NymCard.tsx @@ -1,28 +1,28 @@ import React from 'react' -import { Card, CardContent, CardHeader } from '@mui/material' +import { Box, Card, CardContent, CardHeader } from '@mui/material' import { styled } from '@mui/material/styles' export const NymCard: React.FC<{ title: string | React.ReactElement subheader?: string Action?: React.ReactNode + Icon?: any noPadding?: boolean -}> = ({ title, subheader, Action, noPadding, children }) => { +}> = ({ title, subheader, Action, Icon, noPadding, children }) => { return ( + {Icon && } {title} + + } subheader={subheader} data-testid={title} titleTypographyProps={{ variant: 'h5' }} subheaderTypographyProps={{ variant: 'subtitle1' }} action={Action} - sx={{ - color: 'nym.background.dark', - py: 2.5, - px: 4, - borderBottom: (theme) => `1px solid ${theme.palette.grey[200]}`, - }} /> {noPadding ? {children} : {children}} @@ -30,7 +30,6 @@ export const NymCard: React.FC<{ } const CardContentNoPadding = styled(CardContent)(({ theme }) => ({ - background: theme.palette.grey[50], padding: 0, '&:last-child': { paddingBottom: 0, diff --git a/nym-wallet/src/images/node.svg b/nym-wallet/src/images/node.svg new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/nym-wallet/src/images/node.svg @@ -0,0 +1 @@ + diff --git a/nym-wallet/src/pages/balance/index.tsx b/nym-wallet/src/pages/balance/index.tsx index 2fe320becf..b74260f061 100644 --- a/nym-wallet/src/pages/balance/index.tsx +++ b/nym-wallet/src/pages/balance/index.tsx @@ -1,6 +1,6 @@ import React, { useContext, useEffect } from 'react' import { Alert, Button, Grid, Link, Typography } from '@mui/material' -import { OpenInNew } from '@mui/icons-material' +import { AccountBalanceWalletOutlined, OpenInNew } from '@mui/icons-material' import { NymCard } from '../../components' import { Layout } from '../../layouts' @@ -15,7 +15,7 @@ export const Balance = () => { return ( - + {userBalance.error && ( diff --git a/nym-wallet/src/pages/bond/BondForm.tsx b/nym-wallet/src/pages/bond/BondForm.tsx index 55609d15fc..8092c680c0 100644 --- a/nym-wallet/src/pages/bond/BondForm.tsx +++ b/nym-wallet/src/pages/bond/BondForm.tsx @@ -125,7 +125,7 @@ export const BondForm = ({ return ( - + @@ -375,9 +375,8 @@ export const BondForm = ({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - padding: 2, + padding: 3, + pt: 0, }} > diff --git a/nym-wallet/src/pages/bond/index.tsx b/nym-wallet/src/pages/bond/index.tsx index 77a84a0036..ae87acca4f 100644 --- a/nym-wallet/src/pages/bond/index.tsx +++ b/nym-wallet/src/pages/bond/index.tsx @@ -8,6 +8,7 @@ import { getGasFee, unbond } from '../../requests' import { TFee } from '../../types' import { useCheckOwnership } from '../../hooks/useCheckOwnership' import { ClientContext } from '../../context/main' +import { Bond as BondIcon } from '../../svg-icons/bond' import { SuccessView } from './SuccessView' export const Bond = () => { @@ -35,7 +36,7 @@ export const Bond = () => { return ( - + {ownership?.hasOwnership && ( { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - padding: 2, + padding: 3, + pt: 0, }} > diff --git a/nym-wallet/src/pages/delegate/index.tsx b/nym-wallet/src/pages/delegate/index.tsx index 4720553348..a5845744c3 100644 --- a/nym-wallet/src/pages/delegate/index.tsx +++ b/nym-wallet/src/pages/delegate/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react' -import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material' +import { Alert, AlertTitle, Box, Button, CircularProgress, Link, Typography } from '@mui/material' import { DelegateForm } from './DelegateForm' import { Layout } from '../../layouts' import { NymCard } from '../../components' @@ -7,6 +7,8 @@ import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus import { TFee } from '../../types' import { getGasFee } from '../../requests' import { SuccessView } from './SuccessView' +import { Delegate as DelegateIcon } from '../../svg-icons' +import { urls } from '../../context/main' export const Delegate = () => { const [status, setStatus] = useState(EnumRequestStatus.initial) @@ -28,7 +30,13 @@ export const Delegate = () => { return ( - + {isLoading && ( { justifyContent: 'flex-end', borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, bgcolor: 'grey.100', - p: 2, + p: 3, + pt: 0, }} > )} diff --git a/nym-wallet/src/pages/send/index.tsx b/nym-wallet/src/pages/send/index.tsx index 43c51bf44e..7015225435 100644 --- a/nym-wallet/src/pages/send/index.tsx +++ b/nym-wallet/src/pages/send/index.tsx @@ -1,13 +1,14 @@ -import React, { useState } from 'react' +import React from 'react' import { NymCard } from '../../components' import { SendWizard } from './SendWizard' import { Layout } from '../../layouts' import { MAJOR_CURRENCY } from '../../context/main' +import { ArrowForward } from '@mui/icons-material' export const Send = () => { return ( - + diff --git a/nym-wallet/src/pages/settings/index.tsx b/nym-wallet/src/pages/settings/index.tsx index fca3b126dc..40812c3fc2 100644 --- a/nym-wallet/src/pages/settings/index.tsx +++ b/nym-wallet/src/pages/settings/index.tsx @@ -1,6 +1,5 @@ import React, { useContext, useEffect, useState } from 'react' -import { Alert, Box, Dialog, Typography } from '@mui/material' -import { SettingsOutlined } from '@mui/icons-material' +import { Alert, Box, Dialog } from '@mui/material' import { NymCard } from '../../components' import { ClientContext } from '../../context/main' import { Tabs } from './tabs' @@ -10,6 +9,7 @@ import { NodeStats } from './node-stats' import { Overview } from './overview' import { getMixnodeBondDetails } from '../../requests' import { TMixnodeBondDetails } from '../../types' +import { Node } from '../../svg-icons/node' const tabs = ['Profile', 'System variables', 'Node stats'] @@ -24,24 +24,22 @@ export const Settings = () => { setMixnodeDetails(details) } if (showSettings) getBondDetails() - }, [showSettings]) + }, [showSettings, selectedTab]) - const handleTabChange = (event: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab) + const handleTabChange = (_: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab) return showSettings ? ( - Settings + +
Settings
} noPadding > <> - - Node settings - {!mixnodeDetails && ( @@ -50,9 +48,7 @@ export const Settings = () => {
)} {selectedTab === 0 && mixnodeDetails && } - {selectedTab === 1 && mixnodeDetails && ( - - )} + {selectedTab === 1 && mixnodeDetails && } {selectedTab === 2 && mixnodeDetails && }
diff --git a/nym-wallet/src/pages/settings/node-stats.tsx b/nym-wallet/src/pages/settings/node-stats.tsx index 24d186ffef..99b8a9d8a3 100644 --- a/nym-wallet/src/pages/settings/node-stats.tsx +++ b/nym-wallet/src/pages/settings/node-stats.tsx @@ -2,7 +2,6 @@ import { OpenInNew } from '@mui/icons-material' import { Button, Link, Stack, Typography } from '@mui/material' import React, { useEffect } from 'react' import { urls } from '../../context/main' -import { useCheckOwnership } from '../../hooks/useCheckOwnership' export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => { return ( diff --git a/nym-wallet/src/pages/settings/profile.tsx b/nym-wallet/src/pages/settings/profile.tsx index 228d63e8ad..181caafcfd 100644 --- a/nym-wallet/src/pages/settings/profile.tsx +++ b/nym-wallet/src/pages/settings/profile.tsx @@ -6,7 +6,7 @@ import { InfoTooltip } from '../../components/InfoToolTip' export const Profile = () => { return ( <> - + @@ -18,12 +18,10 @@ export const Profile = () => { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[300]}`, - bgcolor: 'grey.200', - padding: 2, + padding: 3, }} > - diff --git a/nym-wallet/src/pages/settings/system-variables.tsx b/nym-wallet/src/pages/settings/system-variables.tsx index befdbd72bc..30b2efb4d1 100644 --- a/nym-wallet/src/pages/settings/system-variables.tsx +++ b/nym-wallet/src/pages/settings/system-variables.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import React, { useContext, useEffect, useState } from 'react' import { Box, Button, @@ -15,24 +15,18 @@ import { AccessTimeOutlined, PercentOutlined } from '@mui/icons-material' import { yupResolver } from '@hookform/resolvers/yup' import { useForm } from 'react-hook-form' import { InfoTooltip } from '../../components/InfoToolTip' -import { EnumNodeType, TMixnodeBondDetails } from '../../types' +import { TMixnodeBondDetails } from '../../types' import { validationSchema } from './validationSchema' -import { bond, unbond } from '../../requests' -import { ClientContext } from '../../context/main' +import { getGasFee, updateMixnode } from '../../requests' +import { ClientContext, MAJOR_CURRENCY } from '../../context/main' type TFormData = { profitMarginPercent: number - signature: string } -export const SystemVariables = ({ - mixnodeDetails, - pledge, -}: { - mixnodeDetails: TMixnodeBondDetails['mix_node'] - pledge: TMixnodeBondDetails['pledge_amount'] -}) => { +export const SystemVariables = ({ mixnodeDetails }: { mixnodeDetails: TMixnodeBondDetails['mix_node'] }) => { const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>() + const [configFee, setConfigFee] = useState() const { register, @@ -40,34 +34,34 @@ export const SystemVariables = ({ formState: { errors, isSubmitting }, } = useForm({ resolver: yupResolver(validationSchema), - defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString(), signature: '' }, + defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString() }, }) + useEffect(() => { + ;(async () => { + const fee = await getGasFee('UpdateMixnodeConfig') + setConfigFee(fee.amount) + })() + }, []) + const { userBalance } = useContext(ClientContext) const onSubmit = async (data: TFormData) => { - await unbond(EnumNodeType.mixnode) - await bond({ - type: EnumNodeType.mixnode, - data: { ...mixnodeDetails, profit_margin_percent: data.profitMarginPercent }, - pledge: { denom: 'Minor', amount: pledge.amount }, - //hardcoded for the moment as required in bonding but not necessary - ownerSignature: data.signature, - }) - .then(() => { + try { + await updateMixnode({ profitMarginPercent: data.profitMarginPercent }).then(() => { userBalance.fetchBalance() setNodeUpdateResponse('success') }) - .catch((e) => { - setNodeUpdateResponse('failed') - console.log(e) - }) + } catch (e) { + setNodeUpdateResponse('failed') + console.log(e) + } } return ( <> - - + + - + } />} /> - + } />} /> - `1px solid ${theme.palette.grey[300]}`, - bgcolor: 'grey.200', - padding: 2, + p: 3, + pt: 0, }} > {nodeUpdateResponse === 'success' ? ( @@ -128,7 +114,9 @@ export const SystemVariables = ({ ) : nodeUpdateResponse === 'failed' ? ( Node updated failed ) : ( - + + Fee for this transaction: {`${configFee} ${MAJOR_CURRENCY}`}{' '} + )} diff --git a/nym-wallet/src/pages/settings/validationSchema.ts b/nym-wallet/src/pages/settings/validationSchema.ts index 4e76f3a6d4..5b96add517 100644 --- a/nym-wallet/src/pages/settings/validationSchema.ts +++ b/nym-wallet/src/pages/settings/validationSchema.ts @@ -2,5 +2,4 @@ import * as Yup from 'yup' export const validationSchema = Yup.object({ profitMarginPercent: Yup.number().typeError('profit margin percent must be a number').min(0).max(100).required(), - signature: Yup.string().required(), }) diff --git a/nym-wallet/src/pages/unbond/index.tsx b/nym-wallet/src/pages/unbond/index.tsx index 53291a39a6..42d9f1cfa0 100644 --- a/nym-wallet/src/pages/unbond/index.tsx +++ b/nym-wallet/src/pages/unbond/index.tsx @@ -5,6 +5,7 @@ import { Layout } from '../../layouts' import { useCheckOwnership } from '../../hooks/useCheckOwnership' import { ClientContext } from '../../context/main' import { unbond } from '../../requests' +import { Unbond as UnbondIcon } from '../../svg-icons' export const Unbond = () => { const [isLoading, setIsLoading] = useState(false) @@ -20,7 +21,7 @@ export const Unbond = () => { return ( - + {ownership?.hasOwnership && ( { display: 'flex', justifyContent: 'center', p: 3, + pt: 0, }} > diff --git a/nym-wallet/src/pages/undelegate/UndelegateForm.tsx b/nym-wallet/src/pages/undelegate/UndelegateForm.tsx index aedb9884a9..42b297d093 100644 --- a/nym-wallet/src/pages/undelegate/UndelegateForm.tsx +++ b/nym-wallet/src/pages/undelegate/UndelegateForm.tsx @@ -60,7 +60,7 @@ export const UndelegateForm = ({ return ( - + `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - p: 2, + p: 3, + pt: 0, }} > diff --git a/nym-wallet/src/pages/undelegate/index.tsx b/nym-wallet/src/pages/undelegate/index.tsx index 4e90d739f9..89cfb41470 100644 --- a/nym-wallet/src/pages/undelegate/index.tsx +++ b/nym-wallet/src/pages/undelegate/index.tsx @@ -6,6 +6,7 @@ import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus import { Alert, AlertTitle, Box, Button, CircularProgress } from '@mui/material' import { getGasFee, getReverseMixDelegations } from '../../requests' import { TFee, TPagedDelegations } from '../../types' +import { Undelegate as UndelegateIcon } from '../../svg-icons' export const Undelegate = () => { const [message, setMessage] = useState() @@ -42,7 +43,7 @@ export const Undelegate = () => { return ( - + {isLoading && ( { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: '1px solid grey[200]', - bgcolor: 'grey.100', - p: 2, + p: 3, + pt: 0, }} > diff --git a/nym-wallet/src/requests/index.ts b/nym-wallet/src/requests/index.ts index f07d219540..0d1053484e 100644 --- a/nym-wallet/src/requests/index.ts +++ b/nym-wallet/src/requests/index.ts @@ -10,7 +10,6 @@ import { TauriContractStateParams, TauriTxResult, TCreateAccount, - TDelegation, TMixnodeBondDetails, TPagedDelegations, TSignInWithMnemonic, @@ -28,7 +27,7 @@ export const majorToMinor = async (amount: string): Promise => await invok // NOTE: this uses OUTDATED defaults that might have no resemblance with the reality // as for the actual transaction, the gas cost is being simulated beforehand export const getGasFee = async (operation: Operation): Promise => - await invoke('get_approximate_fee', { operation }) + await invoke('outdated_get_approximate_fee', { operation }) export const delegate = async ({ type, @@ -50,6 +49,7 @@ export const undelegate = async ({ export const send = async (args: { amount: Coin; address: string; memo: string }): Promise => await invoke('send', args) + export const checkMixnodeOwnership = async (): Promise => await invoke('owns_mixnode') export const checkGatewayOwnership = async (): Promise => await invoke('owns_gateway') @@ -83,3 +83,6 @@ export const getReverseGatewayDelegations = async (): Promise export const getMixnodeBondDetails = async (): Promise => await invoke('mixnode_bond_details') + +export const updateMixnode = async ({ profitMarginPercent }: { profitMarginPercent: number }) => + await invoke('update_mixnode', { profitMarginPercent }) diff --git a/nym-wallet/src/svg-icons/node.tsx b/nym-wallet/src/svg-icons/node.tsx new file mode 100644 index 0000000000..dcaa794d86 --- /dev/null +++ b/nym-wallet/src/svg-icons/node.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import { SvgIcon, SvgIconProps } from '@mui/material' + +export const Node = (props: SvgIconProps) => { + return ( + + + + ) +} diff --git a/nym-wallet/src/types/corenodestatusresponse.ts b/nym-wallet/src/types/corenodestatusresponse.ts new file mode 100644 index 0000000000..2a743b37a9 --- /dev/null +++ b/nym-wallet/src/types/corenodestatusresponse.ts @@ -0,0 +1,4 @@ +export interface CoreNodeStatusResponse { + identity: string; + count: number; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnodestatus.ts b/nym-wallet/src/types/rust/mixnodestatus.ts new file mode 100644 index 0000000000..75849a8ce9 --- /dev/null +++ b/nym-wallet/src/types/rust/mixnodestatus.ts @@ -0,0 +1 @@ +export type MixnodeStatus = "Active" | "Standby" | "Inactive" | "NotFound"; \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnodestatusresponse.ts b/nym-wallet/src/types/rust/mixnodestatusresponse.ts new file mode 100644 index 0000000000..6015807b4f --- /dev/null +++ b/nym-wallet/src/types/rust/mixnodestatusresponse.ts @@ -0,0 +1,5 @@ +import { MixnodeStatus } from "./mixnodestatus"; + +export interface MixnodeStatusResponse { + status: MixnodeStatus; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/operation.ts b/nym-wallet/src/types/rust/operation.ts index 545c7bbe81..6c8d38c221 100644 --- a/nym-wallet/src/types/rust/operation.ts +++ b/nym-wallet/src/types/rust/operation.ts @@ -8,6 +8,7 @@ export type Operation = | "BondMixnodeOnBehalf" | "UnbondMixnode" | "UnbondMixnodeOnBehalf" + | "UpdateMixnodeConfig" | "DelegateToMixnode" | "DelegateToMixnodeOnBehalf" | "UndelegateFromMixnode" diff --git a/nym-wallet/src/types/rust/rewardestimationresponse.ts b/nym-wallet/src/types/rust/rewardestimationresponse.ts new file mode 100644 index 0000000000..973641f97c --- /dev/null +++ b/nym-wallet/src/types/rust/rewardestimationresponse.ts @@ -0,0 +1,9 @@ +export interface RewardEstimationResponse { + estimated_total_node_reward: bigint; + estimated_operator_reward: bigint; + estimated_delegators_reward: bigint; + current_epoch_start: bigint; + current_epoch_end: bigint; + current_epoch_uptime: number; + as_at: bigint; +} \ No newline at end of file diff --git a/nym-wallet/src/types/rust/stakesaturaionresponse.ts b/nym-wallet/src/types/rust/stakesaturaionresponse.ts new file mode 100644 index 0000000000..78044fb241 --- /dev/null +++ b/nym-wallet/src/types/rust/stakesaturaionresponse.ts @@ -0,0 +1,4 @@ +export interface StakeSaturationResponse { + saturation: number; + as_at: bigint; +} \ No newline at end of file diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 5f52d05c1d..2e12d321ab 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -39,7 +39,7 @@ time = { version = "0.3", features = ["serde-human-readable", "parsing"]} anyhow = "1" getset = "0.1.1" -rocket_sync_db_pools = {version = "0.1.0-rc.1", default-features = false} +rocket_sync_db_pools = { version = "0.1.0-rc.1", default-features = false } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]} @@ -47,9 +47,10 @@ sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros" config = { path = "../common/config" } crypto = { path="../common/crypto" } gateway-client = { path="../common/client-libs/gateway-client" } -mixnet-contract = { path="../common/mixnet-contract" } +mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" } nymsphinx = { path="../common/nymsphinx" } topology = { path="../common/topology" } +validator-api-requests = { path = "validator-api-requests" } validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] } version-checker = { path="../common/version-checker" } coconut-interface = { path = "../common/coconut-interface", optional = true } diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index e35a84c41b..865af46966 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -2,9 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd_client::Client; +use crate::rewarding::EpochRewardParams; +use ::time::OffsetDateTime; use anyhow::Result; use config::defaults::VALIDATOR_API_VERSION; -use mixnet_contract::{ContractStateParams, GatewayBond, MixNodeBond, RewardingIntervalResponse}; +use mixnet_contract_common::{ + ContractStateParams, GatewayBond, IdentityKey, IdentityKeyRef, MixNodeBond, + RewardingIntervalResponse, +}; use rand::prelude::SliceRandom; use rand_chacha::rand_core::SeedableRng; use rand_chacha::ChaCha20Rng; @@ -12,9 +17,10 @@ use rocket::fairing::AdHoc; use serde::Serialize; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::time::Duration; use tokio::sync::RwLock; use tokio::time; +use validator_api_requests::models::MixnodeStatus; use validator_client::nymd::hash::SHA256_HASH_SIZE; use validator_client::nymd::CosmWasmClient; @@ -42,28 +48,40 @@ struct ValidatorCacheInner { current_mixnode_rewarded_set_size: AtomicU32, current_mixnode_active_set_size: AtomicU32, + + current_reward_params: RwLock>, +} + +fn current_unix_timestamp() -> i64 { + let now = OffsetDateTime::now_utc(); + now.unix_timestamp() } #[derive(Default, Serialize, Clone)] pub struct Cache { value: T, - as_at: u64, + as_at: i64, } impl Cache { + fn new(value: T) -> Self { + Cache { + value, + as_at: current_unix_timestamp(), + } + } + fn set(&mut self, value: T) { self.value = value; - self.as_at = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() + self.as_at = current_unix_timestamp() } fn renew(&mut self) { - self.as_at = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() + self.as_at = current_unix_timestamp() + } + + pub fn timestamp(&self) -> i64 { + self.as_at } pub fn into_inner(self) -> T { @@ -102,6 +120,8 @@ impl ValidatorCacheRefresher { ) .await?; + let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?; + info!( "Updating validator cache. There are {} mixnodes and {} gateways", mixnodes.len(), @@ -115,6 +135,7 @@ impl ValidatorCacheRefresher { contract_settings, current_rewarding_interval, rewarding_block_hash, + epoch_rewarding_params, ) .await; @@ -225,6 +246,7 @@ impl ValidatorCache { state: ContractStateParams, rewarding_interval: RewardingIntervalResponse, rewarding_block_hash: Option<[u8; SHA256_HASH_SIZE]>, + epoch_rewarding_params: EpochRewardParams, ) { // if the rewarding is currently in progress, don't mess with the rewarded/active sets // as most likely will be changed next time this function is called @@ -270,6 +292,11 @@ impl ValidatorCache { self.inner.mixnodes.write().await.set(mixnodes); self.inner.gateways.write().await.set(gateways); + self.inner + .current_reward_params + .write() + .await + .set(epoch_rewarding_params); } pub async fn mixnodes(&self) -> Cache> { @@ -305,6 +332,58 @@ impl ValidatorCache { } } + pub(crate) async fn epoch_reward_params(&self) -> Cache { + self.inner.current_reward_params.read().await.clone() + } + + pub async fn mixnode_details( + &self, + identity: IdentityKeyRef<'_>, + ) -> (Option, MixnodeStatus) { + // it might not be the most optimal to possibly iterate the entire vector to find (or not) + // the relevant value. However, the vectors are relatively small (< 10_000 elements) and + // the implementation for active/rewarded sets might change soon so there's no point in premature optimisation + // with HashSets + let rewarded_mixnodes = &self.inner.rewarded_mixnodes.read().await.value; + let active_set_size = self + .inner + .current_mixnode_active_set_size + .load(Ordering::SeqCst) as usize; + + // see if node is in the top active_set_size of rewarded nodes, i.e. it's active + if let Some(bond) = rewarded_mixnodes + .iter() + .take(active_set_size) + .find(|mix| mix.mix_node.identity_key == identity) + { + (Some(bond.clone()), MixnodeStatus::Active) + // see if it's in the bottom part of the rewarded set, i.e. it's in standby + } else if let Some(bond) = rewarded_mixnodes + .iter() + .skip(active_set_size) + .find(|mix| mix.mix_node.identity_key == identity) + { + (Some(bond.clone()), MixnodeStatus::Standby) + // if it's not in the rewarded set see if its bonded at all + } else if let Some(bond) = self + .inner + .mixnodes + .read() + .await + .value + .iter() + .find(|mix| mix.mix_node.identity_key == identity) + { + (Some(bond.clone()), MixnodeStatus::Inactive) + } else { + (None, MixnodeStatus::NotFound) + } + } + + pub async fn mixnode_status(&self, identity: IdentityKey) -> MixnodeStatus { + self.mixnode_details(&identity).await.1 + } + pub fn initialised(&self) -> bool { self.inner.initialised.load(Ordering::Relaxed) } @@ -332,6 +411,7 @@ impl ValidatorCacheInner { rewarded_mixnodes: RwLock::new(Cache::default()), current_mixnode_rewarded_set_size: Default::default(), current_mixnode_active_set_size: Default::default(), + current_reward_params: RwLock::new(Cache::new(EpochRewardParams::new_empty())), } } } diff --git a/validator-api/src/cache/routes.rs b/validator-api/src/cache/routes.rs index cf59be469e..be902cd311 100644 --- a/validator-api/src/cache/routes.rs +++ b/validator-api/src/cache/routes.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::cache::ValidatorCache; -use mixnet_contract::{GatewayBond, MixNodeBond}; +use mixnet_contract_common::{GatewayBond, MixNodeBond}; use rocket::serde::json::Json; use rocket::State; diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index ace0c62851..cd50c7cdf8 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -62,7 +62,7 @@ fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> BlindedSignat .unwrap() } -#[post("/blind_sign", data = "")] +#[post("/blind-sign", data = "")] // Until we have serialization and deserialization traits we'll be using a crutch pub async fn post_blind_sign( blind_sign_request_body: Json, @@ -79,7 +79,7 @@ pub async fn post_blind_sign( Json(BlindedSignatureResponse::new(blinded_signature)) } -#[get("/verification_key")] +#[get("/verification-key")] pub async fn get_verification_key(key_pair: &State) -> Json { Json(VerificationKeyResponse::new(key_pair.verification_key())) } diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index c5513710ee..094a0f820e 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -36,7 +36,7 @@ const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1; const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000; const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3; -const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(5); +const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(30); const DEFAULT_MONITOR_THRESHOLD: u8 = 60; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index a74ef82f28..589b06d25f 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -399,6 +399,7 @@ fn expected_monitor_test_runs(config: &Config) -> usize { fn setup_rewarder( config: &Config, + first_epoch: Epoch, rocket: &Rocket, nymd_client: &Client, ) -> Option { @@ -407,10 +408,6 @@ fn setup_rewarder( let node_status_storage = rocket.state::().unwrap().clone(); let validator_cache = rocket.state::().unwrap().clone(); - let first_epoch = Epoch::new( - config.get_first_rewarding_epoch(), - config.get_epoch_length(), - ); Some(Rewarder::new( nymd_client.clone(), validator_cache, @@ -427,11 +424,16 @@ fn setup_rewarder( } } -async fn setup_rocket(config: &Config, liftoff_notify: Arc) -> Result> { +async fn setup_rocket( + config: &Config, + first_epoch: Epoch, + liftoff_notify: Arc, +) -> Result> { // let's build our rocket! let rocket = rocket::build() .attach(setup_cors()?) .attach(setup_liftoff_notify(liftoff_notify)) + .manage(first_epoch) .attach(ValidatorCache::stage()); #[cfg(feature = "coconut")] @@ -440,13 +442,17 @@ async fn setup_rocket(config: &Config, liftoff_notify: Arc) -> Result) -> Result<()> { .map_err(|err| err.into()); } + let first_epoch = Epoch::new( + config.get_first_rewarding_epoch(), + config.get_epoch_length(), + ); + let liftoff_notify = Arc::new(Notify::new()); // let's build our rocket! - let rocket = setup_rocket(&config, Arc::clone(&liftoff_notify)).await?; + let rocket = setup_rocket(&config, first_epoch, Arc::clone(&liftoff_notify)).await?; let monitor_builder = setup_network_monitor(&config, system_version, &rocket); let validator_cache = rocket.state::().unwrap().clone(); @@ -513,7 +524,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { let uptime_updater = HistoricalUptimeUpdater::new(storage); tokio::spawn(async move { uptime_updater.run().await }); - if let Some(rewarder) = setup_rewarder(&config, &rocket, &nymd_client) { + if let Some(rewarder) = setup_rewarder(&config, first_epoch, &rocket, &nymd_client) { info!("Periodic rewarding is starting..."); tokio::spawn(async move { rewarder.run().await }); } else { diff --git a/validator-api/src/network_monitor/monitor/preparer.rs b/validator-api/src/network_monitor/monitor/preparer.rs index 160e5e8eda..6fed3f643a 100644 --- a/validator-api/src/network_monitor/monitor/preparer.rs +++ b/validator-api/src/network_monitor/monitor/preparer.rs @@ -8,7 +8,7 @@ use crate::network_monitor::test_packet::{NodeType, TestPacket}; use crate::network_monitor::test_route::TestRoute; use crypto::asymmetric::{encryption, identity}; use log::info; -use mixnet_contract::{Addr, GatewayBond, Layer, MixNodeBond}; +use mixnet_contract_common::{Addr, GatewayBond, Layer, MixNodeBond}; use nymsphinx::addressing::clients::Recipient; use nymsphinx::forwarding::packet::MixPacket; use rand::seq::SliceRandom; diff --git a/validator-api/src/node_status_api/mod.rs b/validator-api/src/node_status_api/mod.rs index 3316aec6f4..697fca4403 100644 --- a/validator-api/src/node_status_api/mod.rs +++ b/validator-api/src/node_status_api/mod.rs @@ -1,9 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::storage; use rocket::fairing::AdHoc; -use std::path::PathBuf; use std::time::Duration; pub(crate) mod local_guard; @@ -16,20 +14,35 @@ pub(crate) const FIFTEEN_MINUTES: Duration = Duration::from_secs(900); pub(crate) const ONE_HOUR: Duration = Duration::from_secs(3600); pub(crate) const ONE_DAY: Duration = Duration::from_secs(86400); -pub(crate) fn stage(database_path: PathBuf) -> AdHoc { - AdHoc::on_ignite("SQLx Stage", |rocket| async { - rocket - .attach(storage::ValidatorApiStorage::stage(database_path)) - .mount( - "/v1/status", - routes![ - routes::mixnode_report, - routes::gateway_report, - routes::mixnode_uptime_history, - routes::gateway_uptime_history, - routes::mixnode_core_status_count, - routes::gateway_core_status_count, - ], - ) +pub(crate) fn stage_full() -> AdHoc { + AdHoc::on_ignite("Node Status API Stage", |rocket| async { + rocket.mount( + "/v1/status", + routes![ + routes::mixnode_report, + routes::gateway_report, + routes::mixnode_uptime_history, + routes::gateway_uptime_history, + routes::mixnode_core_status_count, + routes::gateway_core_status_count, + routes::get_mixnode_status, + routes::get_mixnode_reward_estimation, + routes::get_mixnode_stake_saturation, + ], + ) + }) +} + +// in the minimal variant we would not have access to endpoints relying on existence +// of the network monitor and the associated storage +pub(crate) fn stage_minimal() -> AdHoc { + AdHoc::on_ignite("Node Status API Stage", |rocket| async { + rocket.mount( + "/v1/status", + routes![ + routes::get_mixnode_status, + routes::get_mixnode_stake_saturation, + ], + ) }) } diff --git a/validator-api/src/node_status_api/models.rs b/validator-api/src/node_status_api/models.rs index a3fbe6ee63..4ad57950dd 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/validator-api/src/node_status_api/models.rs @@ -208,22 +208,24 @@ pub struct HistoricalUptime { } pub(crate) struct ErrorResponse { - error: ValidatorApiStorageError, + error_message: String, status: Status, } impl ErrorResponse { - pub(crate) fn new(error: ValidatorApiStorageError, status: Status) -> Self { - ErrorResponse { error, status } + pub(crate) fn new(error_message: impl Into, status: Status) -> Self { + ErrorResponse { + error_message: error_message.into(), + status, + } } } impl<'r, 'o: 'r> Responder<'r, 'o> for ErrorResponse { fn respond_to(self, _: &'r Request<'_>) -> response::Result<'o> { - let message = format!("{}", self.error); Response::build() .header(ContentType::Plain) - .sized_body(message.len(), Cursor::new(message)) + .sized_body(self.error_message.len(), Cursor::new(self.error_message)) .status(self.status) .ok() } @@ -269,9 +271,3 @@ impl Display for ValidatorApiStorageError { } } } - -#[derive(Serialize)] -pub struct CoreNodeStatus { - pub(crate) identity: String, - pub(crate) count: i32, -} diff --git a/validator-api/src/node_status_api/routes.rs b/validator-api/src/node_status_api/routes.rs index 7b732a5977..b4a9530566 100644 --- a/validator-api/src/node_status_api/routes.rs +++ b/validator-api/src/node_status_api/routes.rs @@ -2,92 +2,179 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node_status_api::models::{ - CoreNodeStatus, ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, + ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, }; use crate::storage::ValidatorApiStorage; +use crate::{Epoch, ValidatorCache}; use rocket::http::Status; use rocket::serde::json::Json; use rocket::State; +use time::OffsetDateTime; +use validator_api_requests::models::{ + CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, + StakeSaturationResponse, +}; -#[get("/mixnode//report")] +#[get("/mixnode//report")] pub(crate) async fn mixnode_report( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, ErrorResponse> { storage - .construct_mixnode_report(pubkey) + .construct_mixnode_report(identity) .await .map(Json) - .map_err(|err| ErrorResponse::new(err, Status::NotFound)) + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[get("/gateway//report")] +#[get("/gateway//report")] pub(crate) async fn gateway_report( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, ErrorResponse> { storage - .construct_gateway_report(pubkey) + .construct_gateway_report(identity) .await .map(Json) - .map_err(|err| ErrorResponse::new(err, Status::NotFound)) + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[get("/mixnode//history")] +#[get("/mixnode//history")] pub(crate) async fn mixnode_uptime_history( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, ErrorResponse> { storage - .get_mixnode_uptime_history(pubkey) + .get_mixnode_uptime_history(identity) .await .map(Json) - .map_err(|err| ErrorResponse::new(err, Status::NotFound)) + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[get("/gateway//history")] +#[get("/gateway//history")] pub(crate) async fn gateway_uptime_history( storage: &State, - pubkey: &str, + identity: &str, ) -> Result, ErrorResponse> { storage - .get_gateway_uptime_history(pubkey) + .get_gateway_uptime_history(identity) .await .map(Json) - .map_err(|err| ErrorResponse::new(err, Status::NotFound)) + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound)) } -#[get("/mixnode//core-status-count?")] +#[get("/mixnode//core-status-count?")] pub(crate) async fn mixnode_core_status_count( storage: &State, - pubkey: &str, + identity: &str, since: Option, -) -> Json { +) -> Json { let count = storage - .get_core_mixnode_status_count(pubkey, since) + .get_core_mixnode_status_count(identity, since) .await .unwrap_or_default(); - Json(CoreNodeStatus { - identity: pubkey.to_string(), + Json(CoreNodeStatusResponse { + identity: identity.to_string(), count, }) } -#[get("/gateway//core-status-count?")] +#[get("/gateway//core-status-count?")] pub(crate) async fn gateway_core_status_count( storage: &State, - pubkey: &str, + identity: &str, since: Option, -) -> Json { +) -> Json { let count = storage - .get_core_gateway_status_count(pubkey, since) + .get_core_gateway_status_count(identity, since) .await .unwrap_or_default(); - Json(CoreNodeStatus { - identity: pubkey.to_string(), + Json(CoreNodeStatusResponse { + identity: identity.to_string(), count, }) } + +#[get("/mixnode//status")] +pub(crate) async fn get_mixnode_status( + cache: &State, + identity: String, +) -> Json { + Json(MixnodeStatusResponse { + status: cache.mixnode_status(identity).await, + }) +} + +#[get("/mixnode//reward-estimation")] +pub(crate) async fn get_mixnode_reward_estimation( + cache: &State, + storage: &State, + first_epoch: &State, + identity: String, +) -> Result, ErrorResponse> { + let (bond, status) = cache.mixnode_details(&identity).await; + if let Some(bond) = bond { + let epoch_reward_params = cache.epoch_reward_params().await; + let as_at = epoch_reward_params.timestamp(); + let epoch_reward_params = epoch_reward_params.into_inner(); + + let current_epoch = first_epoch.current(OffsetDateTime::now_utc()); + let uptime = storage + .get_average_mixnode_uptime_in_interval( + &identity, + current_epoch.start_unix_timestamp(), + current_epoch.end_unix_timestamp(), + ) + .await + .map_err(|err| ErrorResponse::new(err.to_string(), Status::NotFound))?; + + let (estimated_total_node_reward, estimated_operator_reward, estimated_delegators_reward) = + epoch_reward_params.estimate_reward(&bond, uptime.u8(), status.is_active()); + + Ok(Json(RewardEstimationResponse { + estimated_total_node_reward, + estimated_operator_reward, + estimated_delegators_reward, + current_epoch_start: current_epoch.start_unix_timestamp(), + current_epoch_end: current_epoch.end_unix_timestamp(), + current_epoch_uptime: uptime.u8(), + as_at, + })) + } else { + Err(ErrorResponse::new( + "mixnode bond not found", + Status::NotFound, + )) + } +} + +#[get("/mixnode//stake-saturation")] +pub(crate) async fn get_mixnode_stake_saturation( + cache: &State, + identity: String, +) -> Result, ErrorResponse> { + let (bond, _) = cache.mixnode_details(&identity).await; + if let Some(bond) = bond { + let epoch_reward_params = cache.epoch_reward_params().await; + let as_at = epoch_reward_params.timestamp(); + let epoch_reward_params = epoch_reward_params.into_inner(); + + let saturation = bond.stake_saturation( + epoch_reward_params.circulating_supply, + epoch_reward_params.rewarded_set_size, + ); + + Ok(Json(StakeSaturationResponse { + saturation: saturation.to_num(), + as_at, + })) + } else { + Err(ErrorResponse::new( + "mixnode bond not found", + Status::NotFound, + )) + } +} diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index f38503efd7..1481ad2682 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::Config; -use crate::rewarding::{error::RewardingError, MixnodeToReward}; +use crate::rewarding::{error::RewardingError, EpochRewardParams, MixnodeToReward}; use config::defaults::DEFAULT_VALIDATOR_API_PORT; -use mixnet_contract::{ +use mixnet_contract_common::{ ContractStateParams, Delegation, ExecuteMsg, GatewayBond, IdentityKey, MixNodeBond, MixnodeRewardingStatusResponse, RewardingIntervalResponse, MIXNODE_DELEGATORS_PAGE_LIMIT, }; @@ -98,34 +98,6 @@ impl Client { Ok(time) } - pub(crate) async fn get_reward_pool(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.0.read().await.get_reward_pool().await?) - } - - pub(crate) async fn get_circulating_supply(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.0.read().await.get_circulating_supply().await?) - } - - pub(crate) async fn get_sybil_resistance_percent(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.0.read().await.get_sybil_resistance_percent().await?) - } - - pub(crate) async fn get_epoch_reward_percent(&self) -> Result - where - C: CosmWasmClient + Sync, - { - Ok(self.0.read().await.get_epoch_reward_percent().await?) - } - pub(crate) async fn get_mixnodes(&self) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, @@ -158,9 +130,34 @@ impl Client { self.0.read().await.get_current_rewarding_interval().await } + pub(crate) async fn get_current_epoch_reward_params( + &self, + ) -> Result + where + C: CosmWasmClient + Sync, + { + let this = self.0.read().await; + + let state = this.get_contract_settings().await?; + let reward_pool = this.get_reward_pool().await?; + let epoch_reward_percent = this.get_epoch_reward_percent().await?; + + let epoch_reward_params = EpochRewardParams { + reward_pool, + circulating_supply: this.get_circulating_supply().await?, + sybil_resistance_percent: this.get_sybil_resistance_percent().await?, + rewarded_set_size: state.mixnode_rewarded_set_size, + active_set_size: state.mixnode_active_set_size, + period_reward_pool: (reward_pool / 100) * epoch_reward_percent as u128, + active_set_work_factor: state.active_set_work_factor, + }; + + Ok(epoch_reward_params) + } + pub(crate) async fn get_rewarding_status( &self, - mix_identity: mixnet_contract::IdentityKey, + mix_identity: mixnet_contract_common::IdentityKey, rewarding_interval_nonce: u32, ) -> Result where diff --git a/validator-api/src/rewarding/mod.rs b/validator-api/src/rewarding/mod.rs index bccd83e4ee..74222bb16b 100644 --- a/validator-api/src/rewarding/mod.rs +++ b/validator-api/src/rewarding/mod.rs @@ -12,8 +12,10 @@ use crate::storage::models::{ use crate::storage::ValidatorApiStorage; use config::defaults::DENOM; use log::{error, info}; -use mixnet_contract::mixnode::NodeRewardParams; -use mixnet_contract::{ExecuteMsg, IdentityKey, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT}; +use mixnet_contract_common::mixnode::NodeRewardParams; +use mixnet_contract_common::{ + ExecuteMsg, IdentityKey, MixNodeBond, RewardingStatus, MIXNODE_DELEGATORS_PAGE_LIMIT, +}; use std::convert::TryInto; use std::time::Duration; use time::OffsetDateTime; @@ -23,14 +25,66 @@ use validator_client::nymd::SigningNymdClient; pub(crate) mod epoch; pub(crate) mod error; -struct EpochRewardParams { - reward_pool: u128, - circulating_supply: u128, - sybil_resistance_percent: u8, - rewarded_set_size: u32, - active_set_size: u32, - period_reward_pool: u128, - active_set_work_factor: u8, +#[derive(Copy, Clone)] +pub(crate) struct EpochRewardParams { + pub(crate) reward_pool: u128, + pub(crate) circulating_supply: u128, + pub(crate) sybil_resistance_percent: u8, + pub(crate) rewarded_set_size: u32, + pub(crate) active_set_size: u32, + pub(crate) period_reward_pool: u128, + pub(crate) active_set_work_factor: u8, +} + +impl EpochRewardParams { + // technically it's identical to what would have been derived with a Default implementation, + // however, I prefer to be explicit about it, as a `Default::default` value makes no sense + // apart from the `ValidatorCacheInner` context, where this value is not going to be touched anyway + // (it's guarded behind an `initialised` flag) + pub(crate) fn new_empty() -> Self { + EpochRewardParams { + reward_pool: 0, + circulating_supply: 0, + sybil_resistance_percent: 0, + rewarded_set_size: 0, + active_set_size: 0, + period_reward_pool: 0, + active_set_work_factor: 0, + } + } + + pub(crate) fn estimate_reward( + &self, + node: &MixNodeBond, + uptime: u8, + in_active_set: bool, + ) -> (u128, u128, u128) { + let node_reward_params = NodeRewardParams::new( + self.period_reward_pool, + self.rewarded_set_size.into(), + self.active_set_size.into(), + 0, + self.circulating_supply, + uptime.into(), + self.sybil_resistance_percent, + in_active_set, + self.active_set_work_factor, + ); + + let total_node_reward = node.reward(&node_reward_params); + let operator_reward = node.operator_reward(&node_reward_params); + let delegators_reward = + node.reward_delegation(node.total_delegation().amount, &node_reward_params); + + ( + total_node_reward + .reward() + .checked_to_num() + .unwrap_or_default(), + operator_reward, + delegators_reward, + ) + } } #[derive(Debug, Clone)] @@ -117,24 +171,6 @@ impl Rewarder { } } - async fn epoch_reward_params(&self) -> Result { - let state = self.nymd_client.get_contract_settings().await?; - let reward_pool = self.nymd_client.get_reward_pool().await?; - let epoch_reward_percent = self.nymd_client.get_epoch_reward_percent().await?; - - let epoch_reward_params = EpochRewardParams { - reward_pool, - circulating_supply: self.nymd_client.get_circulating_supply().await?, - sybil_resistance_percent: self.nymd_client.get_sybil_resistance_percent().await?, - rewarded_set_size: state.mixnode_rewarded_set_size, - active_set_size: state.mixnode_active_set_size, - period_reward_pool: (reward_pool / 100) * epoch_reward_percent as u128, - active_set_work_factor: state.active_set_work_factor, - }; - - Ok(epoch_reward_params) - } - /// Obtains the current number of delegators that have delegated their stake towards this particular mixnode. /// /// # Arguments @@ -170,7 +206,7 @@ impl Rewarder { // and the lack of port data / verloc data will eventually be balanced out anyway // by people hesitating to delegate to nodes without them and thus those nodes disappearing // from the active set (once introduced) - let epoch_reward_params = self.epoch_reward_params().await?; + let epoch_reward_params = self.nymd_client.get_current_epoch_reward_params().await?; info!("Rewarding pool stats"); info!( diff --git a/validator-api/validator-api-requests/Cargo.toml b/validator-api/validator-api-requests/Cargo.toml new file mode 100644 index 0000000000..946dd5703d --- /dev/null +++ b/validator-api/validator-api-requests/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "validator-api-requests" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = "1.0" +ts-rs = { version = "5.1", optional = true } + diff --git a/validator-api/validator-api-requests/src/lib.rs b/validator-api/validator-api-requests/src/lib.rs new file mode 100644 index 0000000000..9beea87f0d --- /dev/null +++ b/validator-api/validator-api-requests/src/lib.rs @@ -0,0 +1,4 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod models; diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs new file mode 100644 index 0000000000..fddd64d788 --- /dev/null +++ b/validator-api/validator-api-requests/src/models.rs @@ -0,0 +1,53 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +#[serde(rename_all = "snake_case")] +pub enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set, but is bonded + NotFound, // doesn't even exist in the bonded set +} + +impl MixnodeStatus { + pub fn is_active(&self) -> bool { + *self == MixnodeStatus::Active + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +pub struct CoreNodeStatusResponse { + pub identity: String, + pub count: i32, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +pub struct MixnodeStatusResponse { + pub status: MixnodeStatus, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +pub struct RewardEstimationResponse { + pub estimated_total_node_reward: u128, + pub estimated_operator_reward: u128, + pub estimated_delegators_reward: u128, + + pub current_epoch_start: i64, + pub current_epoch_end: i64, + pub current_epoch_uptime: u8, + pub as_at: i64, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))] +pub struct StakeSaturationResponse { + pub saturation: f32, + pub as_at: i64, +}