Merge branch 'develop' into tauri-wallet

This commit is contained in:
Drazen Urch
2021-09-16 17:27:27 +02:00
89 changed files with 4273 additions and 1492 deletions
Generated
+78 -81
View File
@@ -1,7 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
# Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
# SPDX-License-Identifier: Apache-2.0
version = 3
[[package]]
@@ -120,9 +118,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.43"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
[[package]]
name = "app"
@@ -460,6 +458,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397"
dependencies = [
"digest 0.9.0",
"ff",
"group",
"pairing",
@@ -729,13 +728,12 @@ dependencies = [
"coconut-rs",
"getset",
"serde",
"sha2",
]
[[package]]
name = "coconut-rs"
version = "0.4.0"
source = "git+https://github.com/nymtech/coconut.git?branch=0.4.0#183cff805aa73a908b681ac3fcdff7d084c4f42b"
version = "0.5.0"
source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f"
dependencies = [
"bls12_381",
"bs58",
@@ -748,8 +746,6 @@ dependencies = [
"serde",
"serde_derive",
"sha2",
"sha3",
"subtle 2.4.1",
"thiserror",
]
@@ -819,9 +815,9 @@ dependencies = [
[[package]]
name = "const-oid"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c32f031ea41b4291d695026c023b95d59db2d8a2c7640800ed56bc8f510f22"
checksum = "fdab415d6744056100f40250a66bc430c1a46f7a02e20bc11c94c79a0f0464df"
[[package]]
name = "const_fn"
@@ -936,8 +932,9 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.6.0"
source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e"
dependencies = [
"prost",
"prost-types",
@@ -945,9 +942,10 @@ dependencies = [
]
[[package]]
name = "cosmos_sdk"
version = "0.2.0"
source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30"
name = "cosmrs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -1124,9 +1122,9 @@ dependencies = [
[[package]]
name = "crypto-bigint"
version = "0.2.6"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e49339137316df1914fdb54a5eae75a73f45068fd0d2178fe235b11d93238a6e"
checksum = "43b0ca41e2a75a407a44812f110ecd40e1efacb8993f612746491e12d5b929fe"
dependencies = [
"generic-array 0.14.4",
"rand_core 0.6.3",
@@ -1343,9 +1341,9 @@ dependencies = [
[[package]]
name = "der"
version = "0.4.1"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31e21d2d0f22cde6e88694108429775c0219760a07779bf96503b434a03d7412"
checksum = "2adca118c71ecd9ae094d4b68257b3fdfcb711a612b9eec7b5a0d27a5a70a5b4"
dependencies = [
"const-oid",
]
@@ -1768,9 +1766,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80edafed416a46fb378521624fab1cfa2eb514784fd8921adbe8a8d8321da811"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if 1.0.0",
"crc32fast",
@@ -2019,6 +2017,7 @@ dependencies = [
"bincode",
"bs58",
"coconut-interface",
"credentials",
"crypto",
"futures",
"log",
@@ -2167,9 +2166,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
[[package]]
name = "gio"
version = "0.14.5"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81a4c12fcba7a6402ae843a0085ec16d3658a87901763b6a7f0a7c5d60e555a5"
checksum = "f3a29d8062af72045518271a2cd98b4e1617ce43f5b4223ad0fb9a0eff8f718c"
dependencies = [
"bitflags",
"futures-channel",
@@ -2235,7 +2234,7 @@ checksum = "2aad66361f66796bfc73f530c51ef123970eb895ffba991a234fcf7bea89e518"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate 1.0.0",
"proc-macro-crate 1.1.0",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -2364,7 +2363,7 @@ checksum = "21de1da96dc117443fb03c2e270b2d34b7de98d0a79a19bbb689476173745b79"
dependencies = [
"anyhow",
"heck",
"proc-macro-crate 1.0.0",
"proc-macro-crate 1.1.0",
"proc-macro-error",
"proc-macro2",
"quote",
@@ -2891,9 +2890,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.54"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254"
checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
dependencies = [
"wasm-bindgen",
]
@@ -2943,9 +2942,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.101"
version = "0.2.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
[[package]]
name = "libm"
@@ -3389,7 +3388,7 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "486ea01961c4a818096de679a8b740b26d9033146ac5291b1c98557658f8cdd9"
dependencies = [
"proc-macro-crate 1.0.0",
"proc-macro-crate 1.1.0",
"proc-macro2",
"quote",
"syn",
@@ -4137,9 +4136,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkcs8"
version = "0.7.5"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee84ed13e44dd82689fa18348a49934fa79cc774a344c42fc9b301c71b140a"
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
dependencies = [
"der",
"spki",
@@ -4219,9 +4218,9 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fdbd1df62156fbc5945f4762632564d7d038153091c3fcf1067f6aef7cff92"
checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83"
dependencies = [
"thiserror",
"toml",
@@ -5194,9 +5193,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.67"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950"
checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
dependencies = [
"itoa",
"ryu",
@@ -5359,9 +5358,9 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[package]]
name = "sled"
version = "0.34.6"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d0132f3e393bcb7390c60bb45769498cf4550bcb7a21d7f95c02b69f6362cdc"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
@@ -5411,9 +5410,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765f090f0e423d2b55843402a07915add955e7d60657db13707a159727326cad"
checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
dependencies = [
"libc",
"winapi",
@@ -5479,23 +5478,21 @@ checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5"
[[package]]
name = "spki"
version = "0.4.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "987637c5ae6b3121aba9d513f869bd2bff11c4cc086c22473befd6649c0bd521"
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
dependencies = [
"der",
]
[[package]]
name = "sqlformat"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684001e7985ec1a9a66963b77ed151ef22a7876b3fdd7e37a57ec774f54b7d96"
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
dependencies = [
"lazy_static",
"maplit",
"itertools 0.10.1",
"nom",
"regex",
"unicode_categories",
]
@@ -6525,9 +6522,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.26"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d"
checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@@ -6537,9 +6534,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77"
dependencies = [
"proc-macro2",
"quote",
@@ -6548,9 +6545,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.19"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8"
checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
dependencies = [
"lazy_static",
]
@@ -6764,7 +6761,7 @@ dependencies = [
"bip39",
"coconut-interface",
"config",
"cosmos_sdk",
"cosmrs",
"cosmwasm-std",
"flate2",
"itertools 0.10.1",
@@ -6859,9 +6856,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c"
checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
dependencies = [
"cfg-if 1.0.0",
"serde",
@@ -6871,9 +6868,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523"
checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
dependencies = [
"bumpalo",
"lazy_static",
@@ -6886,9 +6883,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.27"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a87d738d4abc4cf22f6eb142f5b9a81301331ee3c767f2fef2fda4e325492060"
checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -6898,9 +6895,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8"
checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -6908,9 +6905,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92"
checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
dependencies = [
"proc-macro2",
"quote",
@@ -6921,15 +6918,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.77"
version = "0.2.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4"
checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
[[package]]
name = "wasm-bindgen-test"
version = "0.3.27"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02129da5406bad6e3b2042d0e19f963c03b84f0b05908599fc7f0980afb3158b"
checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e"
dependencies = [
"console_error_panic_hook",
"js-sys",
@@ -6941,9 +6938,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-test-macro"
version = "0.3.27"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7848675a1de39f7470ba747c4aa689f2977e190ab9589cdce6bd8a77c5743bd4"
checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688"
dependencies = [
"proc-macro2",
"quote",
@@ -6963,9 +6960,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.54"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a"
checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -7047,9 +7044,9 @@ dependencies = [
[[package]]
name = "webview2"
version = "0.1.1"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fab1ccfdabb098b047293c8d496c1914d1c654b68fdaa3bb77cfa47c4bca2c7"
checksum = "1b132bb76313456e93b17037262a030d54c9fe0f11838ef1593f845e3807ef8a"
dependencies = [
"com",
"once_cell",
@@ -7060,9 +7057,9 @@ dependencies = [
[[package]]
name = "webview2-sys"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5288cef1e0cbcf7a0b961e6271e33589b8989c80b2e11078504e989b5346ff"
checksum = "24b7889e893ac4c50d7346356be3ce13a85e56512c38b8fde0526559b8012a4c"
dependencies = [
"com",
"winapi",
@@ -7195,9 +7192,9 @@ dependencies = [
[[package]]
name = "x25519-dalek"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f"
checksum = "2392b6b94a576b4e2bf3c5b2757d63f10ada8020a2e4d08ac849ebcf6ea8e077"
dependencies = [
"curve25519-dalek",
"rand_core 0.5.1",
@@ -7221,9 +7218,9 @@ checksum = "9fc79f4a1e39857fc00c3f662cbf2651c771f00e9c15fe2abc341806bd46bd71"
[[package]]
name = "zeroize"
version = "1.4.1"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd"
checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd"
dependencies = [
"zeroize_derive",
]
@@ -61,8 +61,8 @@ impl KeyManager {
// I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute
#[allow(dead_code)]
/// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`].
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: SharedKeys) {
self.gateway_shared_key = Some(Arc::new(gateway_shared_key))
pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc<SharedKeys>) {
self.gateway_shared_key = Some(gateway_shared_key)
}
/// Loads previously stored keys from the disk.
+1 -1
View File
@@ -20,7 +20,7 @@ version = '{{ client.version }}'
id = '{{ client.id }}'
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_rest_urls = [
validator_api_urls = [
{{#each client.validator_api_urls }}
'{{this}}',
{{/each}}
+2 -2
View File
@@ -79,7 +79,7 @@ async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
validator_urls: Vec<Url>,
) -> SharedKeys {
) -> Arc<SharedKeys> {
let timeout = Duration::from_millis(1500);
let coconut_credential =
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
@@ -95,7 +95,7 @@ async fn register_with_gateway(
.await
.expect("failed to establish connection with the gateway!");
gateway_client
.register()
.perform_initial_authentication()
.await
.expect("failed to register with the gateway!")
}
+1 -1
View File
@@ -20,7 +20,7 @@ version = '{{ client.version }}'
id = '{{ client.id }}'
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_rest_urls = [
validator_api_urls = [
{{#each client.validator_api_urls }}
'{{this}}',
{{/each}}
+2 -2
View File
@@ -79,7 +79,7 @@ async fn register_with_gateway(
gateway: &gateway::Node,
our_identity: Arc<identity::KeyPair>,
validator_urls: Vec<Url>,
) -> SharedKeys {
) -> Arc<SharedKeys> {
let timeout = Duration::from_millis(1500);
let coconut_credential =
prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await;
@@ -95,7 +95,7 @@ async fn register_with_gateway(
.await
.expect("failed to establish connection with the gateway!");
gateway_client
.register()
.perform_initial_authentication()
.await
.expect("failed to register with the gateway!")
}
+16
View File
@@ -1,3 +1,19 @@
## Prerequisites
On Ubuntu-ish systems (not tested on Debian but may work):
```
sudo apt update && sudo apt install libwebkit2gtk-4.0-dev \
build-essential \
curl \
wget \
libssl-dev \
libgtk-3-dev \
libappindicator3-dev \
patchelf \
librsvg2-dev```
## Getting started
+17 -7
View File
@@ -4,7 +4,7 @@
)]
use coconut_interface::{
self, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey,
};
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use std::sync::Arc;
@@ -15,19 +15,29 @@ struct State {
signatures: Vec<Signature>,
n_attributes: u32,
params: Parameters,
public_attributes_bytes: Vec<Vec<u8>>,
public_attributes: Vec<Attribute>,
private_attributes: Vec<Attribute>,
aggregated_verification_key: Option<VerificationKey>,
}
impl State {
fn init(public_attributes: Vec<Attribute>, private_attributes: Vec<Attribute>) -> State {
let n_attributes = (public_attributes.len() + private_attributes.len()) as u32;
fn init(public_attributes_bytes: Vec<Vec<u8>>, private_attributes_bytes: Vec<Vec<u8>>) -> State {
let n_attributes = (public_attributes_bytes.len() + private_attributes_bytes.len()) as u32;
let params = Parameters::new(n_attributes).unwrap();
let public_attributes = public_attributes_bytes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
let private_attributes = private_attributes_bytes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
State {
signatures: Vec::new(),
n_attributes,
params,
public_attributes_bytes,
public_attributes,
private_attributes,
aggregated_verification_key: None,
@@ -137,14 +147,14 @@ async fn verify_credential(
let credential = Credential::new(
state.n_attributes,
theta,
&state.public_attributes,
state.public_attributes_bytes.clone(),
state
.signatures
.get(idx)
.ok_or("Got invalid signature idx")?,
);
Ok(credential.verify(&verification_key).await)
Ok(credential.verify(&verification_key))
}
#[tauri::command]
@@ -170,8 +180,8 @@ async fn get_credential(
}
fn main() {
let public_attributes = vec![coconut_interface::hash_to_scalar("public_key")];
let private_attributes = vec![coconut_interface::hash_to_scalar("private_key")];
let public_attributes = vec![b"public_key".to_vec()];
let private_attributes = vec![b"private_key".to_vec()];
tauri::Builder::default()
.manage(Arc::new(RwLock::new(State::init(
public_attributes,
+59 -16
View File
@@ -12,7 +12,7 @@ use coconut_interface::Credential;
use crypto::asymmetric::identity;
use futures::{FutureExt, SinkExt, StreamExt};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::authentication::iv::AuthenticationIV;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{client_handshake, SharedKeys};
use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse};
use log::*;
@@ -36,6 +36,8 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
// TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth
has_bandwidth: bool,
gateway_address: String,
gateway_identity: identity::PublicKey,
local_identity: Arc<identity::KeyPair>,
@@ -70,6 +72,7 @@ impl GatewayClient {
) -> Self {
GatewayClient {
authenticated: false,
has_bandwidth: false,
gateway_address,
gateway_identity,
local_identity,
@@ -114,6 +117,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
has_bandwidth: false,
gateway_address,
gateway_identity,
local_identity,
@@ -362,7 +366,7 @@ impl GatewayClient {
}
}
pub async fn register(&mut self) -> Result<SharedKeys, GatewayClientError> {
async fn register(&mut self) -> Result<(), GatewayClientError> {
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
@@ -379,21 +383,26 @@ impl GatewayClient {
ws_stream,
self.local_identity.as_ref(),
self.gateway_identity,
self.coconut_credential.clone(),
)
.await
.map_err(GatewayClientError::RegistrationFailure),
_ => unreachable!(),
}?;
self.authenticated = true;
Ok(shared_key)
self.authenticated = match self.read_control_response().await? {
ServerResponse::Register { status } => Ok(status),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
if self.authenticated {
self.shared_key = Some(Arc::new(shared_key));
}
Ok(())
}
pub async fn authenticate(
async fn authenticate(
&mut self,
shared_key: Option<SharedKeys>,
) -> Result<bool, GatewayClientError> {
) -> Result<(), GatewayClientError> {
if shared_key.is_none() && self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
@@ -409,7 +418,7 @@ impl GatewayClient {
let shared_key = shared_key
.as_ref()
.unwrap_or_else(|| self.shared_key.as_ref().unwrap());
let iv = AuthenticationIV::new_random(&mut rng);
let iv = IV::new_random(&mut rng);
let self_address = self
.local_identity
.as_ref()
@@ -420,15 +429,14 @@ impl GatewayClient {
let msg =
ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into();
let authenticated = match self.send_websocket_message(msg).await? {
match self.send_websocket_message(msg).await? {
ServerResponse::Authenticate { status } => {
self.authenticated = status;
Ok(status)
Ok(())
}
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => unreachable!(),
}?;
Ok(authenticated)
_ => Err(GatewayClientError::UnexpectedResponse),
}
}
/// Helper method to either call register or authenticate based on self.shared_key value
@@ -438,8 +446,7 @@ impl GatewayClient {
if self.shared_key.is_some() {
self.authenticate(None).await?;
} else {
let shared_key = self.register().await?;
self.shared_key = Some(Arc::new(shared_key));
self.register().await?;
}
if self.authenticated {
// if we are authenticated it means we MUST have an associated shared_key
@@ -449,6 +456,32 @@ impl GatewayClient {
}
}
pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
let mut rng = OsRng;
let iv = IV::new_random(&mut rng);
let msg = ClientControlRequest::new_enc_bandwidth_credential(
&self.coconut_credential,
self.shared_key.as_ref().unwrap(),
iv,
)
.ok_or(GatewayClientError::SerializeCredential)?
.into();
self.has_bandwidth = match self.send_websocket_message(msg).await? {
ServerResponse::Bandwidth { status } => Ok(status),
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
_ => Err(GatewayClientError::UnexpectedResponse),
}?;
Ok(())
}
pub async fn batch_send_mix_packets(
&mut self,
packets: Vec<MixPacket>,
@@ -456,6 +489,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
@@ -514,6 +550,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if !self.connection.is_established() {
return Err(GatewayClientError::ConnectionNotEstablished);
}
@@ -559,6 +598,9 @@ impl GatewayClient {
if !self.authenticated {
return Err(GatewayClientError::NotAuthenticated);
}
if !self.has_bandwidth {
return Err(GatewayClientError::NotEnoughBandwidth);
}
if self.connection.is_partially_delegated() {
return Ok(());
}
@@ -591,6 +633,7 @@ impl GatewayClient {
self.establish_connection().await?;
}
let shared_key = self.perform_initial_authentication().await?;
self.claim_bandwidth().await?;
// this call is NON-blocking
self.start_listening_for_mixnet_messages()?;
@@ -21,7 +21,10 @@ pub enum GatewayClientError {
NoSharedKeyAvailable,
ConnectionAbruptlyClosed,
MalformedResponse,
SerializeCredential,
NotAuthenticated,
NotEnoughBandwidth,
UnexpectedResponse,
ConnectionInInvalidState,
RegistrationFailure(HandshakeError),
AuthenticationFailure,
@@ -98,6 +101,13 @@ impl fmt::Display for GatewayClientError {
GatewayClientError::GatewayError(err) => {
write!(f, "gateway returned an error response - {}", err)
}
GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"),
GatewayClientError::NotEnoughBandwidth => {
write!(f, "client does not have enough bandwidth")
}
GatewayClientError::SerializeCredential => {
write!(f, "credential could not be serialized")
}
}
}
}
@@ -24,8 +24,8 @@ network-defaults = { path = "../../network-defaults" }
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true}
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true }
prost = { version = "0.7", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
@@ -34,4 +34,4 @@ cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-up
ts-rs = "3.0"
[features]
nymd-client = ["async-trait", "bip39", "config", "cosmos_sdk", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"]
@@ -14,7 +14,7 @@ use url::Url;
pub struct Config {
api_url: Url,
nymd_url: Url,
mixnet_contract_address: Option<cosmos_sdk::AccountId>,
mixnet_contract_address: Option<cosmrs::AccountId>,
mixnode_page_limit: Option<u32>,
gateway_page_limit: Option<u32>,
@@ -27,7 +27,7 @@ impl Config {
pub fn new(
nymd_url: Url,
api_url: Url,
mixnet_contract_address: Option<cosmos_sdk::AccountId>,
mixnet_contract_address: Option<cosmrs::AccountId>,
) -> Self {
Config {
nymd_url,
@@ -63,7 +63,7 @@ impl Config {
#[cfg(feature = "nymd-client")]
pub struct Client<C> {
mixnet_contract_address: Option<cosmos_sdk::AccountId>,
mixnet_contract_address: Option<cosmrs::AccountId>,
mnemonic: Option<bip39::Mnemonic>,
mixnode_page_limit: Option<u32>,
@@ -154,7 +154,7 @@ impl<C> Client<C> {
// use case: somebody initialised client without a contract in order to upload and initialise one
// and now they want to actually use it without making new client
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmos_sdk::AccountId) {
pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) {
self.mixnet_contract_address = Some(mixnet_contract_address)
}
@@ -243,6 +243,58 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_reverse_mixnode_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_reverse_mix_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_mixnode_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_mixnode_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_mix_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations(
&self,
identity: mixnet_contract::IdentityKey,
@@ -273,6 +325,58 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_all_nymd_reverse_gateway_delegations(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::IdentityKey>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = self
.nymd
.get_reverse_gateway_delegations_paged(
mixnet_contract::Addr::unchecked(delegation_owner.as_ref()),
start_after.take(),
self.mixnode_delegations_page_limit,
)
.await?;
delegations.append(&mut paged_response.delegated_nodes);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
Ok(delegations)
}
pub async fn get_all_nymd_gateway_delegations_of_owner(
&self,
delegation_owner: &cosmrs::AccountId,
) -> Result<Vec<mixnet_contract::Delegation>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
let mut delegations = Vec::new();
for node_identity in self
.get_all_nymd_reverse_gateway_delegations(delegation_owner)
.await?
{
let delegation = self
.nymd
.get_gateway_delegation(node_identity, delegation_owner)
.await?;
delegations.push(delegation);
}
Ok(delegations)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -8,21 +8,21 @@ use crate::nymd::cosmwasm_client::types::{
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
use cosmos_sdk::proto::cosmos::auth::v1beta1::{
use cosmrs::proto::cosmos::auth::v1beta1::{
BaseAccount, QueryAccountRequest, QueryAccountResponse,
};
use cosmos_sdk::proto::cosmos::bank::v1beta1::{
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
};
use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::*;
use cosmos_sdk::rpc::endpoint::block::Response as BlockResponse;
use cosmos_sdk::rpc::endpoint::broadcast;
use cosmos_sdk::rpc::endpoint::tx::Response as TxResponse;
use cosmos_sdk::rpc::query::Query;
use cosmos_sdk::rpc::{self, HttpClient, Order};
use cosmos_sdk::tendermint::abci::Transaction;
use cosmos_sdk::tendermint::{abci, block, chain};
use cosmos_sdk::{AccountId, Coin, Denom};
use cosmrs::proto::cosmwasm::wasm::v1beta1::*;
use cosmrs::rpc::endpoint::block::Response as BlockResponse;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::endpoint::tx::Response as TxResponse;
use cosmrs::rpc::query::Query;
use cosmrs::rpc::{self, HttpClient, Order};
use cosmrs::tendermint::abci::Transaction;
use cosmrs::tendermint::{abci, block, chain};
use cosmrs::{AccountId, Coin, Denom};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use cosmos_sdk::proto::cosmos::base::query::v1beta1::PageRequest;
use cosmos_sdk::rpc::endpoint::broadcast;
use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest;
use cosmrs::rpc::endpoint::broadcast;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::error::NymdError;
use cosmos_sdk::tendermint::abci;
use cosmrs::tendermint::abci;
use itertools::Itertools;
use serde::Deserialize;
@@ -3,7 +3,7 @@
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl};
use std::convert::TryInto;
pub mod client;
@@ -8,13 +8,13 @@ use crate::nymd::cosmwasm_client::types::*;
use crate::nymd::error::NymdError;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use async_trait::async_trait;
use cosmos_sdk::bank::MsgSend;
use cosmos_sdk::distribution::MsgWithdrawDelegatorReward;
use cosmos_sdk::rpc::endpoint::broadcast;
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmos_sdk::staking::{MsgDelegate, MsgUndelegate};
use cosmos_sdk::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
use cosmos_sdk::{cosmwasm, rpc, tx, AccountId, Coin};
use cosmrs::bank::MsgSend;
use cosmrs::distribution::MsgWithdrawDelegatorReward;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest};
use cosmrs::staking::{MsgDelegate, MsgUndelegate};
use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo};
use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin};
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
@@ -287,7 +287,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let delegate_msg = MsgDelegate {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
amount: Some(amount),
amount,
}
.to_msg()
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
@@ -407,7 +407,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
let auth_info = signer_info.auth_info(fee);
// ideally I'd prefer to have the entire error put into the NymdError::SigningFailure
// but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error
// but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error
let sign_doc = SignDoc::new(
&tx_body,
&auth_info,
@@ -5,15 +5,15 @@
use crate::nymd::cosmwasm_client::logs::Log;
use crate::nymd::error::NymdError;
use cosmos_sdk::crypto::PublicKey;
use cosmos_sdk::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::{
use cosmrs::crypto::PublicKey;
use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount;
use cosmrs::proto::cosmwasm::wasm::v1beta1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmos_sdk::tendermint::chain;
use cosmos_sdk::tx::{AccountNumber, SequenceNumber};
use cosmos_sdk::{tx, AccountId, Coin};
use cosmrs::tendermint::chain;
use cosmrs::tx::{AccountNumber, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use serde::Serialize;
use std::convert::TryFrom;
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmos_sdk::tendermint::block;
use cosmos_sdk::{bip32, rpc, tx, AccountId};
use cosmrs::tendermint::block;
use cosmrs::{bip32, rpc, tx, AccountId};
use std::io;
use thiserror::Error;
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::GasPrice;
use cosmos_sdk::tx::{Fee, Gas};
use cosmos_sdk::Coin;
use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use cosmwasm_std::Uint128;
use serde::{Deserialize, Serialize};
use std::fmt;
@@ -3,7 +3,7 @@
use crate::nymd::error::NymdError;
use config::defaults;
use cosmos_sdk::Denom;
use cosmrs::Denom;
use cosmwasm_std::Decimal;
use std::str::FromStr;
@@ -9,16 +9,17 @@ use crate::nymd::cosmwasm_client::types::{
use crate::nymd::error::NymdError;
use crate::nymd::fee_helpers::Operation;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmos_sdk::rpc::endpoint::broadcast;
use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmos_sdk::tx::{Fee, Gas};
use cosmos_sdk::Coin as CosmosCoin;
use cosmos_sdk::{AccountId, Denom};
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl};
use cosmrs::tx::{Fee, Gas};
use cosmwasm_std::Coin;
use mixnet_contract::{
Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey,
LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, QueryMsg, StateParams,
PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse,
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg,
StateParams,
};
use serde::Serialize;
use std::collections::HashMap;
@@ -27,7 +28,9 @@ use std::convert::TryInto;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::gas_price::GasPrice;
pub use cosmos_sdk::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{AccountId, Denom};
pub use signing_client::Client as SigningNymdClient;
pub mod cosmwasm_client;
@@ -268,6 +271,26 @@ impl<C> NymdClient<C> {
.await
}
/// Gets list of all the mixnodes on which a particular address delegated.
pub async fn get_reverse_mix_delegations_paged(
&self,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
page_limit: Option<u32>,
) -> Result<PagedReverseMixDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetReverseMixDelegations {
delegation_owner,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks value of delegation of given client towards particular mixnode.
pub async fn get_mix_delegation(
&self,
@@ -306,6 +329,26 @@ impl<C> NymdClient<C> {
.await
}
/// Gets list of all the gateways on which a particular address delegated.
pub async fn get_reverse_gateway_delegations_paged(
&self,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
page_limit: Option<u32>,
) -> Result<PagedReverseGatewayDelegationsResponse, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetReverseGatewayDelegations {
delegation_owner,
start_after,
limit: page_limit,
};
self.client
.query_contract_smart(self.contract_address()?, &request)
.await
}
/// Checks value of delegation of given client towards particular gateway.
pub async fn get_gateway_delegation(
&self,
@@ -3,11 +3,11 @@
use crate::nymd::error::NymdError;
use config::defaults;
use cosmos_sdk::bip32::{DerivationPath, XPrv};
use cosmos_sdk::crypto::secp256k1::SigningKey;
use cosmos_sdk::crypto::PublicKey;
use cosmos_sdk::tx::SignDoc;
use cosmos_sdk::{tx, AccountId};
use cosmrs::bip32::{DerivationPath, XPrv};
use cosmrs::crypto::secp256k1::SigningKey;
use cosmrs::crypto::PublicKey;
use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug)]
@@ -97,7 +97,7 @@ impl DirectSecp256k1HdWallet {
sign_doc: SignDoc,
) -> Result<tx::Raw, NymdError> {
// ideally I'd prefer to have the entire error put into the NymdError::SigningFailure
// but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error
// but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error
sign_doc
.sign(&signer.private_key)
.map_err(|_| NymdError::SigningFailure)
+1 -2
View File
@@ -6,7 +6,6 @@ description = "Crutch library until there is proper SerDe support for coconut st
[dependencies]
serde = { version = "1.0", features = ["derive"] }
sha2 = "0.9.3"
getset = "0.1.1"
coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.4.0" }
coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" }
+12 -40
View File
@@ -3,10 +3,6 @@
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
use sha2::{
digest::{generic_array::typenum::Unsigned, Digest},
Sha256,
};
pub use coconut_rs::*;
@@ -16,7 +12,7 @@ pub struct Credential {
n_params: u32,
#[getset(get = "pub")]
theta: Theta,
public_attributes: Vec<String>,
public_attributes: Vec<Vec<u8>>,
#[getset(get = "pub")]
signature: Signature,
}
@@ -24,35 +20,29 @@ impl Credential {
pub fn new(
n_params: u32,
theta: Theta,
public_attributes: &[Attribute],
public_attributes: Vec<Vec<u8>>,
signature: &Signature,
) -> Credential {
Credential {
n_params,
theta,
public_attributes: public_attributes
.iter()
.map(|attr| attr.to_bs58())
.collect(),
public_attributes,
signature: *signature,
}
}
pub fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes
.iter()
.map(|x| Attribute::try_from_bs58(x).unwrap())
.collect()
pub fn public_attributes(&self) -> Vec<Vec<u8>> {
self.public_attributes.clone()
}
pub async fn verify(&self, verification_key: &VerificationKey) -> bool {
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
let params = Parameters::new(self.n_params).unwrap();
coconut_rs::verify_credential(
&params,
verification_key,
&self.theta,
&self.public_attributes(),
)
let public_attributes = self
.public_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
coconut_rs::verify_credential(&params, verification_key, &self.theta, &public_attributes)
}
}
@@ -147,21 +137,3 @@ impl VerificationKeyResponse {
VerificationKeyResponse { key }
}
}
pub fn hash_to_scalar<M>(msg: M) -> Attribute
where
M: AsRef<[u8]>,
{
let mut h = Sha256::new();
h.update(msg);
let digest = h.finalize();
let mut bytes = [0u8; 64];
let pad_size = 64usize
.checked_sub(<Sha256 as Digest>::OutputSize::to_usize())
.unwrap_or_default();
bytes[pad_size..].copy_from_slice(&digest);
Attribute::from_bytes_wide(&bytes)
}
+9 -8
View File
@@ -6,12 +6,13 @@
// right now this has no double-spending protection, spender binding, etc
// it's the simplest possible case
use url::Url;
use crate::error::Error;
use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending};
use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey};
use url::Url;
const BANDWIDTH_VALUE: &str = "Bandwidth: infinite (for now)";
const BANDWIDTH_VALUE: u64 = 1024 * 1024; // 1 MB
pub const PUBLIC_ATTRIBUTES: u32 = 1;
pub const PRIVATE_ATTRIBUTES: u32 = 1;
@@ -19,8 +20,8 @@ pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES;
// TODO: this definitely has to be moved somewhere else. It's just a temporary solution
pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result<Signature, Error> {
let public_attributes = vec![hash_to_scalar(raw_identity)];
let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)];
let public_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes())];
let private_attributes = vec![hash_to_scalar(raw_identity)];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
@@ -32,15 +33,15 @@ pub fn prepare_for_spending(
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![hash_to_scalar(raw_identity)];
let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)];
let public_attributes = vec![BANDWIDTH_VALUE.to_be_bytes().to_vec()];
let private_attributes = vec![raw_identity.to_vec()];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
&public_attributes,
&private_attributes,
public_attributes,
private_attributes,
signature,
verification_key,
)
+12
View File
@@ -18,4 +18,16 @@ pub enum Error {
#[error("Run into a validato client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Not enough public attributes were specified")]
NotEnoughPublicAttributes,
#[error("Bandwidth is expected to be represented on 8 bytes")]
InvalidBandwidthSize,
#[error("Bandwidth operation overflowed. {0}")]
BandwidthOverflow(String),
#[error("There is not associated bandwidth for the given client")]
MissingBandwidth,
}
+10 -6
View File
@@ -3,9 +3,9 @@
use crate::error::Error;
use coconut_interface::{
aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign, prove_credential,
Attribute, BlindSignRequestBody, Credential, Parameters, Signature, SignatureShare,
VerificationKey,
aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign,
prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature,
SignatureShare, VerificationKey,
};
use url::Url;
@@ -118,12 +118,16 @@ pub async fn obtain_aggregate_signature(
// TODO: better type flow
pub fn prepare_credential_for_spending(
params: &Parameters,
public_attributes: &[Attribute],
private_attributes: &[Attribute],
public_attributes: Vec<Vec<u8>>,
private_attributes: Vec<Vec<u8>>,
signature: &Signature,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let theta = prove_credential(params, verification_key, signature, private_attributes)?;
let private_attributes = private_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<Attribute>>();
let theta = prove_credential(params, verification_key, signature, &private_attributes)?;
Ok(Credential::new(
(public_attributes.len() + private_attributes.len()) as u32,
+71 -5
View File
@@ -2,20 +2,40 @@
#![allow(clippy::field_reassign_with_default)]
use crate::{Addr, IdentityKey};
use cosmwasm_std::Coin;
use cosmwasm_std::{Coin, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct RawDelegationData {
pub amount: Uint128,
pub block_height: u64,
}
impl RawDelegationData {
pub fn new(amount: Uint128, block_height: u64) -> Self {
RawDelegationData {
amount,
block_height,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct Delegation {
owner: Addr,
amount: Coin,
block_height: u64,
}
impl Delegation {
pub fn new(owner: Addr, amount: Coin) -> Self {
Delegation { owner, amount }
pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self {
Delegation {
owner,
amount,
block_height,
}
}
pub fn amount(&self) -> &Coin {
@@ -25,14 +45,18 @@ impl Delegation {
pub fn owner(&self) -> Addr {
self.owner.clone()
}
pub fn block_height(&self) -> u64 {
self.block_height
}
}
impl Display for Delegation {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{} {} delegated by {}",
self.amount.amount, self.amount.denom, self.owner
"{} {} delegated by {} at block {}",
self.amount.amount, self.amount.denom, self.owner, self.block_height
)
}
}
@@ -58,6 +82,27 @@ impl PagedMixDelegationsResponse {
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedReverseMixDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseMixDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseMixDelegationsResponse {
delegation_owner,
delegated_nodes,
start_next_after,
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedGatewayDelegationsResponse {
pub node_identity: IdentityKey,
@@ -78,3 +123,24 @@ impl PagedGatewayDelegationsResponse {
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedReverseGatewayDelegationsResponse {
pub delegation_owner: Addr,
pub delegated_nodes: Vec<IdentityKey>,
pub start_next_after: Option<IdentityKey>,
}
impl PagedReverseGatewayDelegationsResponse {
pub fn new(
delegation_owner: Addr,
delegated_nodes: Vec<IdentityKey>,
start_next_after: Option<IdentityKey>,
) -> Self {
PagedReverseGatewayDelegationsResponse {
delegation_owner,
delegated_nodes,
start_next_after,
}
}
}
+6 -1
View File
@@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct Gateway {
pub host: String,
@@ -25,15 +27,18 @@ pub struct GatewayBond {
pub bond_amount: Coin,
pub total_delegation: Coin,
pub owner: Addr,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub gateway: Gateway,
}
impl GatewayBond {
pub fn new(bond_amount: Coin, owner: Addr, gateway: Gateway) -> Self {
pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
GatewayBond {
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
owner,
block_height,
gateway,
}
}
+10 -2
View File
@@ -8,10 +8,18 @@ mod msg;
mod types;
pub use cosmwasm_std::{Addr, Coin};
pub use delegation::{Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse};
pub use delegation::{
Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse,
PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData,
};
pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse};
pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse};
pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams};
pub use types::default_delegation_reward;
use std::sync::atomic::{AtomicU64, Ordering};
pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0);
pub fn current_block_height() -> u64 {
CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed)
}
+12 -1
View File
@@ -9,6 +9,8 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt::Display;
use ts_rs::TS;
use crate::current_block_height;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema, TS)]
pub struct MixNode {
pub host: String,
@@ -36,16 +38,25 @@ pub struct MixNodeBond {
pub total_delegation: Coin,
pub owner: Addr,
pub layer: Layer,
#[serde(default = "current_block_height")]
pub block_height: u64,
pub mix_node: MixNode,
}
impl MixNodeBond {
pub fn new(bond_amount: Coin, owner: Addr, layer: Layer, mix_node: MixNode) -> Self {
pub fn new(
bond_amount: Coin,
owner: Addr,
layer: Layer,
block_height: u64,
mix_node: MixNode,
) -> Self {
MixNodeBond {
total_delegation: coin(0, &bond_amount.denom),
bond_amount,
owner,
layer,
block_height,
mix_node,
}
}
+10
View File
@@ -75,6 +75,11 @@ pub enum QueryMsg {
start_after: Option<Addr>,
limit: Option<u32>,
},
GetReverseMixDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetMixDelegation {
mix_identity: IdentityKey,
address: Addr,
@@ -84,6 +89,11 @@ pub enum QueryMsg {
start_after: Option<Addr>,
limit: Option<u32>,
},
GetReverseGatewayDelegations {
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
},
GetGatewayDelegation {
gateway_identity: IdentityKey,
address: Addr,
-6
View File
@@ -26,10 +26,6 @@ impl LayerDistribution {
}
}
pub fn default_delegation_reward() -> Decimal {
Decimal::percent(110)
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StateParams {
pub epoch_length: u32, // length of an epoch, expressed in hours
@@ -38,9 +34,7 @@ pub struct StateParams {
pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system
pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
#[serde(default = "default_delegation_reward")]
pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
#[serde(default = "default_delegation_reward")]
pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25
pub mixnode_active_set_size: u32,
}
+2 -2
View File
@@ -727,9 +727,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.60"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
dependencies = [
"proc-macro2",
"quote",
+349
View File
@@ -0,0 +1,349 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"anyOf": [
{
"type": "object",
"required": [
"bond_mixnode"
],
"properties": {
"bond_mixnode": {
"type": "object",
"required": [
"mix_node"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_mixnode"
],
"properties": {
"unbond_mixnode": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"bond_gateway"
],
"properties": {
"bond_gateway": {
"type": "object",
"required": [
"gateway"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"unbond_gateway"
],
"properties": {
"unbond_gateway": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"update_state_params"
],
"properties": {
"update_state_params": {
"$ref": "#/definitions/StateParams"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"delegate_to_mixnode"
],
"properties": {
"delegate_to_mixnode": {
"type": "object",
"required": [
"mix_identity"
],
"properties": {
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"undelegate_from_mixnode"
],
"properties": {
"undelegate_from_mixnode": {
"type": "object",
"required": [
"mix_identity"
],
"properties": {
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"delegate_to_gateway"
],
"properties": {
"delegate_to_gateway": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"undelegate_from_gateway"
],
"properties": {
"undelegate_from_gateway": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"reward_mixnode"
],
"properties": {
"reward_mixnode": {
"type": "object",
"required": [
"identity",
"uptime"
],
"properties": {
"identity": {
"type": "string"
},
"uptime": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"reward_gateway"
],
"properties": {
"reward_gateway": {
"type": "object",
"required": [
"identity",
"uptime"
],
"properties": {
"identity": {
"type": "string"
},
"uptime": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
}
}
},
"additionalProperties": false
}
],
"definitions": {
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"Gateway": {
"type": "object",
"required": [
"clients_port",
"host",
"identity_key",
"location",
"mix_port",
"sphinx_key",
"version"
],
"properties": {
"clients_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"host": {
"type": "string"
},
"identity_key": {
"description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients",
"type": "string"
},
"location": {
"type": "string"
},
"mix_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"MixNode": {
"type": "object",
"required": [
"host",
"http_api_port",
"identity_key",
"mix_port",
"sphinx_key",
"verloc_port",
"version"
],
"properties": {
"host": {
"type": "string"
},
"http_api_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"identity_key": {
"description": "Base58 encoded ed25519 EdDSA public key.",
"type": "string"
},
"mix_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"sphinx_key": {
"type": "string"
},
"verloc_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"version": {
"type": "string"
}
}
},
"StateParams": {
"type": "object",
"required": [
"epoch_length",
"gateway_bond_reward_rate",
"gateway_delegation_reward_rate",
"minimum_gateway_bond",
"minimum_mixnode_bond",
"mixnode_active_set_size",
"mixnode_bond_reward_rate",
"mixnode_delegation_reward_rate"
],
"properties": {
"epoch_length": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"gateway_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"gateway_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"minimum_gateway_bond": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_bond": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"mixnode_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
}
}
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
}
-134
View File
@@ -1,134 +0,0 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "HandleMsg",
"anyOf": [
{
"type": "object",
"required": [
"register_mixnode"
],
"properties": {
"register_mixnode": {
"type": "object",
"required": [
"mix_node"
],
"properties": {
"mix_node": {
"$ref": "#/definitions/MixNode"
}
}
}
}
},
{
"type": "object",
"required": [
"un_register_mixnode"
],
"properties": {
"un_register_mixnode": {
"type": "object"
}
}
},
{
"type": "object",
"required": [
"bond_gateway"
],
"properties": {
"bond_gateway": {
"type": "object",
"required": [
"gateway"
],
"properties": {
"gateway": {
"$ref": "#/definitions/Gateway"
}
}
}
}
},
{
"type": "object",
"required": [
"unbond_gateway"
],
"properties": {
"unbond_gateway": {
"type": "object"
}
}
}
],
"definitions": {
"Gateway": {
"type": "object",
"required": [
"clients_host",
"identity_key",
"location",
"mix_host",
"sphinx_key",
"version"
],
"properties": {
"clients_host": {
"type": "string"
},
"identity_key": {
"description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients",
"type": "string"
},
"location": {
"type": "string"
},
"mix_host": {
"type": "string"
},
"sphinx_key": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"MixNode": {
"type": "object",
"required": [
"host",
"identity_key",
"layer",
"location",
"sphinx_key",
"version"
],
"properties": {
"host": {
"type": "string"
},
"identity_key": {
"description": "Base58 encoded ed25519 EdDSA public key.",
"type": "string"
},
"layer": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"location": {
"type": "string"
},
"sphinx_key": {
"type": "string"
},
"version": {
"type": "string"
}
}
}
}
}
@@ -1,5 +1,5 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InitMsg",
"title": "InstantiateMsg",
"type": "object"
}
+47 -17
View File
@@ -3,25 +3,40 @@
"title": "MixNodeBond",
"type": "object",
"required": [
"amount",
"bond_amount",
"layer",
"mix_node",
"owner"
"owner",
"total_delegation"
],
"properties": {
"amount": {
"type": "array",
"items": {
"$ref": "#/definitions/Coin"
}
"block_height": {
"default": 0,
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"bond_amount": {
"$ref": "#/definitions/Coin"
},
"layer": {
"$ref": "#/definitions/Layer"
},
"mix_node": {
"$ref": "#/definitions/MixNode"
},
"owner": {
"$ref": "#/definitions/HumanAddr"
"$ref": "#/definitions/Addr"
},
"total_delegation": {
"$ref": "#/definitions/Coin"
}
},
"definitions": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Coin": {
"type": "object",
"required": [
@@ -37,44 +52,59 @@
}
}
},
"HumanAddr": {
"type": "string"
"Layer": {
"type": "string",
"enum": [
"Gateway",
"One",
"Two",
"Three"
]
},
"MixNode": {
"type": "object",
"required": [
"host",
"http_api_port",
"identity_key",
"layer",
"location",
"mix_port",
"sphinx_key",
"verloc_port",
"version"
],
"properties": {
"host": {
"type": "string"
},
"http_api_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"identity_key": {
"description": "Base58 encoded ed25519 EdDSA public key.",
"type": "string"
},
"layer": {
"mix_port": {
"type": "integer",
"format": "uint64",
"format": "uint16",
"minimum": 0.0
},
"location": {
"type": "string"
},
"sphinx_key": {
"type": "string"
},
"verloc_port": {
"type": "integer",
"format": "uint16",
"minimum": 0.0
},
"version": {
"type": "string"
}
}
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
+262 -11
View File
@@ -20,18 +20,15 @@
"minimum": 0.0
},
"start_after": {
"anyOf": [
{
"$ref": "#/definitions/HumanAddr"
},
{
"type": "null"
}
"type": [
"string",
"null"
]
}
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
@@ -50,10 +47,96 @@
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"owns_mixnode"
],
"properties": {
"owns_mixnode": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"owns_gateway"
],
"properties": {
"owns_gateway": {
"type": "object",
"required": [
"address"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"state_params"
],
"properties": {
"state_params": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_mix_delegations"
],
"properties": {
"get_mix_delegations": {
"type": "object",
"required": [
"mix_identity"
],
"properties": {
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"mix_identity": {
"type": "string"
},
"start_after": {
"anyOf": [
{
"$ref": "#/definitions/HumanAddr"
"$ref": "#/definitions/Addr"
},
{
"type": "null"
@@ -62,11 +145,179 @@
}
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_reverse_mix_delegations"
],
"properties": {
"get_reverse_mix_delegations": {
"type": "object",
"required": [
"delegation_owner"
],
"properties": {
"delegation_owner": {
"$ref": "#/definitions/Addr"
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_mix_delegation"
],
"properties": {
"get_mix_delegation": {
"type": "object",
"required": [
"address",
"mix_identity"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
},
"mix_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_gateway_delegations"
],
"properties": {
"get_gateway_delegations": {
"type": "object",
"required": [
"gateway_identity"
],
"properties": {
"gateway_identity": {
"type": "string"
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
},
{
"type": "null"
}
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_reverse_gateway_delegations"
],
"properties": {
"get_reverse_gateway_delegations": {
"type": "object",
"required": [
"delegation_owner"
],
"properties": {
"delegation_owner": {
"$ref": "#/definitions/Addr"
},
"limit": {
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
},
"start_after": {
"type": [
"string",
"null"
]
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_gateway_delegation"
],
"properties": {
"get_gateway_delegation": {
"type": "object",
"required": [
"address",
"gateway_identity"
],
"properties": {
"address": {
"$ref": "#/definitions/Addr"
},
"gateway_identity": {
"type": "string"
}
}
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"layer_distribution"
],
"properties": {
"layer_distribution": {
"type": "object"
}
},
"additionalProperties": false
}
],
"definitions": {
"HumanAddr": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
}
}
+79 -3
View File
@@ -3,15 +3,91 @@
"title": "State",
"type": "object",
"required": [
"owner"
"gateway_epoch_bond_reward",
"gateway_epoch_delegation_reward",
"mixnode_epoch_bond_reward",
"mixnode_epoch_delegation_reward",
"network_monitor_address",
"owner",
"params"
],
"properties": {
"gateway_epoch_bond_reward": {
"$ref": "#/definitions/Decimal"
},
"gateway_epoch_delegation_reward": {
"$ref": "#/definitions/Decimal"
},
"mixnode_epoch_bond_reward": {
"$ref": "#/definitions/Decimal"
},
"mixnode_epoch_delegation_reward": {
"$ref": "#/definitions/Decimal"
},
"network_monitor_address": {
"$ref": "#/definitions/Addr"
},
"owner": {
"$ref": "#/definitions/HumanAddr"
"$ref": "#/definitions/Addr"
},
"params": {
"$ref": "#/definitions/StateParams"
}
},
"definitions": {
"HumanAddr": {
"Addr": {
"description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.",
"type": "string"
},
"Decimal": {
"description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)",
"type": "string"
},
"StateParams": {
"type": "object",
"required": [
"epoch_length",
"gateway_bond_reward_rate",
"gateway_delegation_reward_rate",
"minimum_gateway_bond",
"minimum_mixnode_bond",
"mixnode_active_set_size",
"mixnode_bond_reward_rate",
"mixnode_delegation_reward_rate"
],
"properties": {
"epoch_length": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"gateway_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"gateway_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"minimum_gateway_bond": {
"$ref": "#/definitions/Uint128"
},
"minimum_mixnode_bond": {
"$ref": "#/definitions/Uint128"
},
"mixnode_active_set_size": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mixnode_bond_reward_rate": {
"$ref": "#/definitions/Decimal"
},
"mixnode_delegation_reward_rate": {
"$ref": "#/definitions/Decimal"
}
}
},
"Uint128": {
"description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```",
"type": "string"
}
}
+99 -12
View File
@@ -3,14 +3,16 @@
use crate::helpers::calculate_epoch_reward_rate;
use crate::state::State;
use crate::storage::{config, config_read, layer_distribution};
use crate::storage::{config, layer_distribution};
use crate::{error::ContractError, queries, transactions};
use config::defaults::NETWORK_MONITOR_ADDRESS;
use cosmwasm_std::{
entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse,
Response, Uint128,
};
use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams};
use mixnet_contract::{
ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RawDelegationData, StateParams,
};
pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2;
@@ -89,32 +91,36 @@ pub fn instantiate(
#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::BondMixnode { mix_node } => transactions::try_add_mixnode(deps, info, mix_node),
ExecuteMsg::BondMixnode { mix_node } => {
transactions::try_add_mixnode(deps, env, info, mix_node)
}
ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info),
ExecuteMsg::BondGateway { gateway } => transactions::try_add_gateway(deps, info, gateway),
ExecuteMsg::BondGateway { gateway } => {
transactions::try_add_gateway(deps, env, info, gateway)
}
ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info),
ExecuteMsg::UpdateStateParams(params) => {
transactions::try_update_state_params(deps, info, params)
}
ExecuteMsg::RewardMixnode { identity, uptime } => {
transactions::try_reward_mixnode(deps, info, identity, uptime)
transactions::try_reward_mixnode(deps, env, info, identity, uptime)
}
ExecuteMsg::RewardGateway { identity, uptime } => {
transactions::try_reward_gateway(deps, info, identity, uptime)
transactions::try_reward_gateway(deps, env, info, identity, uptime)
}
ExecuteMsg::DelegateToMixnode { mix_identity } => {
transactions::try_delegate_to_mixnode(deps, info, mix_identity)
transactions::try_delegate_to_mixnode(deps, env, info, mix_identity)
}
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity)
}
ExecuteMsg::DelegateToGateway { gateway_identity } => {
transactions::try_delegate_to_gateway(deps, info, gateway_identity)
transactions::try_delegate_to_gateway(deps, env, info, gateway_identity)
}
ExecuteMsg::UndelegateFromGateway { gateway_identity } => {
transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity)
@@ -149,6 +155,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
start_after,
limit,
)?),
QueryMsg::GetReverseMixDelegations {
delegation_owner,
start_after,
limit,
} => to_binary(&queries::query_reverse_mixnode_delegations_paged(
deps,
delegation_owner,
start_after,
limit,
)?),
QueryMsg::GetMixDelegation {
mix_identity,
address,
@@ -167,6 +183,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
start_after,
limit,
)?),
QueryMsg::GetReverseGatewayDelegations {
delegation_owner,
start_after,
limit,
} => to_binary(&queries::query_reverse_gateway_delegations_paged(
deps,
delegation_owner,
start_after,
limit,
)?),
QueryMsg::GetGatewayDelegation {
gateway_identity,
address,
@@ -181,9 +207,70 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, Cont
}
#[entry_point]
pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
let state = config_read(deps.storage).load().unwrap();
config(deps.storage).save(&state)?;
pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
use crate::storage::{
gateway_delegations, gateway_delegations_read, gateway_delegations_read_old, gateways_read,
mix_delegations, mix_delegations_read, mix_delegations_read_old, mixnodes_read,
};
use crate::transactions::delegations;
use cosmwasm_std::{Order, StdResult};
use mixnet_contract::{GatewayBond, MixNodeBond};
// Read existing delegations data, drop invalid values, and rewrite delegations data with valid data only
fn overwrite_mixnode_delegations_data(
identity: &str,
deps: &mut DepsMut,
) -> Result<(), ContractError> {
let delegations_bucket = mix_delegations_read(deps.storage, identity);
let old_delegations_bucket = mix_delegations_read_old(deps.storage, identity);
let mut delegations_vec = delegations(delegations_bucket)?;
let old_delegations = delegations::<Uint128>(old_delegations_bucket)?;
for delegation in old_delegations {
delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1)))
}
for (key, delegation) in delegations_vec {
mix_delegations(deps.storage, identity).save(&key, &delegation)?;
}
Ok(())
}
fn overwrite_gateway_delegations_data(
identity: &str,
deps: &mut DepsMut,
) -> Result<(), ContractError> {
let delegations_bucket = gateway_delegations_read(deps.storage, identity);
let old_delegations_bucket = gateway_delegations_read_old(deps.storage, identity);
let mut delegations_vec = delegations(delegations_bucket)?;
let old_delegations = delegations::<Uint128>(old_delegations_bucket)?;
for delegation in old_delegations {
delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1)))
}
for (key, delegation) in delegations_vec {
gateway_delegations(deps.storage, identity).save(&key, &delegation)?;
}
Ok(())
}
let mixnet_bonds = mixnodes_read(deps.storage)
.range(None, None, Order::Ascending)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<MixNodeBond>>>()?;
for bond in mixnet_bonds {
overwrite_mixnode_delegations_data(bond.identity(), &mut deps)?;
}
let gateway_bonds = gateways_read(deps.storage)
.range(None, None, Order::Ascending)
.map(|res| res.map(|item| item.1))
.collect::<StdResult<Vec<GatewayBond>>>()?;
for bond in gateway_bonds {
overwrite_gateway_delegations_data(bond.identity(), &mut deps)?;
}
Ok(Default::default())
}
+467 -40
View File
@@ -5,6 +5,7 @@ use crate::error::ContractError;
use crate::storage::{
gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read,
mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params,
reverse_gateway_delegations_read, reverse_mix_delegations_read,
};
use config::defaults::DENOM;
use cosmwasm_std::Deps;
@@ -14,7 +15,8 @@ use cosmwasm_std::{coin, Addr};
use mixnet_contract::{
Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond,
MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse,
PagedMixDelegationsResponse, PagedMixnodeResponse, StateParams,
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse,
PagedReverseMixDelegationsResponse, StateParams,
};
const BOND_PAGE_MAX_LIMIT: u32 = 100;
@@ -123,8 +125,11 @@ pub(crate) fn query_mixnode_delegations_paged(
.map(|res| {
res.map(|entry| {
Delegation::new(
Addr::unchecked(String::from_utf8(entry.0).unwrap()),
coin(entry.1.u128(), DENOM),
Addr::unchecked(String::from_utf8(entry.0).expect(
"Non-UTF8 address used as key in bucket. The storage is corrupted!",
)),
coin(entry.1.amount.u128(), DENOM),
entry.1.block_height,
)
})
})
@@ -139,6 +144,37 @@ pub(crate) fn query_mixnode_delegations_paged(
))
}
pub(crate) fn query_reverse_mixnode_delegations_paged(
deps: Deps,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedReverseMixDelegationsResponse> {
let limit = limit
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let delegations = reverse_mix_delegations_read(deps.storage, &delegation_owner)
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|entry| {
String::from_utf8(entry.0)
.expect("Non-UTF8 address used as key in bucket. The storage is corrupted!")
})
})
.collect::<StdResult<Vec<IdentityKey>>>()?;
let start_next_after = delegations.last().cloned();
Ok(PagedReverseMixDelegationsResponse::new(
delegation_owner,
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
deps: Deps,
@@ -148,7 +184,8 @@ pub(crate) fn query_mixnode_delegation(
match mix_delegations_read(deps.storage, &mix_identity).may_load(address.as_bytes())? {
Some(delegation_value) => Ok(Delegation::new(
address,
coin(delegation_value.u128(), DENOM),
coin(delegation_value.amount.u128(), DENOM),
delegation_value.block_height,
)),
None => Err(ContractError::NoMixnodeDelegationFound {
identity: mix_identity,
@@ -174,8 +211,11 @@ pub(crate) fn query_gateway_delegations_paged(
.map(|res| {
res.map(|entry| {
Delegation::new(
Addr::unchecked(String::from_utf8(entry.0).unwrap()),
coin(entry.1.u128(), DENOM),
Addr::unchecked(String::from_utf8(entry.0).expect(
"Non-UTF8 address used as key in bucket. The storage is corrupted!",
)),
coin(entry.1.amount.u128(), DENOM),
entry.1.block_height,
)
})
})
@@ -190,6 +230,37 @@ pub(crate) fn query_gateway_delegations_paged(
))
}
pub(crate) fn query_reverse_gateway_delegations_paged(
deps: Deps,
delegation_owner: Addr,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> StdResult<PagedReverseGatewayDelegationsResponse> {
let limit = limit
.unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT)
.min(DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = calculate_start_value(start_after);
let delegations = reverse_gateway_delegations_read(deps.storage, &delegation_owner)
.range(start.as_deref(), None, Order::Ascending)
.take(limit)
.map(|res| {
res.map(|entry| {
String::from_utf8(entry.0)
.expect("Non-UTF8 address used as key in bucket. The storage is corrupted!")
})
})
.collect::<StdResult<Vec<IdentityKey>>>()?;
let start_next_after = delegations.last().cloned();
Ok(PagedReverseGatewayDelegationsResponse::new(
delegation_owner,
delegations,
start_next_after,
))
}
// queries for delegation value of given address for particular node
pub(crate) fn query_gateway_delegation(
deps: Deps,
@@ -199,7 +270,8 @@ pub(crate) fn query_gateway_delegation(
match gateway_delegations_read(deps.storage, &gateway_identity).may_load(address.as_bytes())? {
Some(delegation_value) => Ok(Delegation::new(
address,
coin(delegation_value.u128(), DENOM),
coin(delegation_value.amount.u128(), DENOM),
delegation_value.block_height,
)),
None => Err(ContractError::NoGatewayDelegationFound {
identity: gateway_identity,
@@ -214,11 +286,13 @@ mod tests {
use crate::state::State;
use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes};
use crate::support::tests::helpers;
use crate::support::tests::helpers::{good_gateway_bond, good_mixnode_bond};
use crate::support::tests::helpers::{
good_gateway_bond, good_mixnode_bond, raw_delegation_fixture,
};
use crate::transactions;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Addr, Storage, Uint128};
use mixnet_contract::{Gateway, MixNode};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{Addr, Storage};
use mixnet_contract::{Gateway, MixNode, RawDelegationData};
#[test]
fn mixnodes_empty_on_init() {
@@ -472,8 +546,13 @@ mod tests {
identity_key: "bobsnode".into(),
..helpers::mix_node_fixture()
};
transactions::try_add_mixnode(deps.as_mut(), mock_info("bob", &good_mixnode_bond()), node)
.unwrap();
transactions::try_add_mixnode(
deps.as_mut(),
mock_env(),
mock_info("bob", &good_mixnode_bond()),
node,
)
.unwrap();
let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap();
assert!(!res.has_node);
@@ -483,8 +562,13 @@ mod tests {
identity_key: "fredsnode".into(),
..helpers::mix_node_fixture()
};
transactions::try_add_mixnode(deps.as_mut(), mock_info("fred", &good_mixnode_bond()), node)
.unwrap();
transactions::try_add_mixnode(
deps.as_mut(),
mock_env(),
mock_info("fred", &good_mixnode_bond()),
node,
)
.unwrap();
let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap();
assert!(res.has_node);
@@ -509,8 +593,13 @@ mod tests {
identity_key: "bobsnode".into(),
..helpers::gateway_fixture()
};
transactions::try_add_gateway(deps.as_mut(), mock_info("bob", &good_gateway_bond()), node)
.unwrap();
transactions::try_add_gateway(
deps.as_mut(),
mock_env(),
mock_info("bob", &good_gateway_bond()),
node,
)
.unwrap();
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
assert!(!res.has_gateway);
@@ -520,8 +609,13 @@ mod tests {
identity_key: "fredsnode".into(),
..helpers::gateway_fixture()
};
transactions::try_add_gateway(deps.as_mut(), mock_info("fred", &good_gateway_bond()), node)
.unwrap();
transactions::try_add_gateway(
deps.as_mut(),
mock_env(),
mock_info("fred", &good_gateway_bond()),
node,
)
.unwrap();
let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap();
assert!(res.has_gateway);
@@ -564,14 +658,12 @@ mod tests {
#[cfg(test)]
mod querying_for_mixnode_delegations_paged {
use super::*;
use crate::storage::mix_delegations;
use cosmwasm_std::Uint128;
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
for i in 0..n {
let address = format!("address{}", i);
mix_delegations(storage, node_identity)
.save(address.as_bytes(), &Uint128(42))
.save(address.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
}
}
@@ -643,7 +735,7 @@ mod tests {
let node_identity: IdentityKey = "foo".into();
mix_delegations(&mut deps.storage, &node_identity)
.save("1".as_bytes(), &Uint128(42))
.save("1".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
let per_page = 2;
@@ -660,7 +752,7 @@ mod tests {
// save another
mix_delegations(&mut deps.storage, &node_identity)
.save("2".as_bytes(), &Uint128(42))
.save("2".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
// page1 should have 2 results on it
@@ -674,7 +766,7 @@ mod tests {
assert_eq!(2, page1.delegations.len());
mix_delegations(&mut deps.storage, &node_identity)
.save("3".as_bytes(), &Uint128(42))
.save("3".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
// page1 still has 2 results
@@ -701,7 +793,7 @@ mod tests {
// save another one
mix_delegations(&mut deps.storage, &node_identity)
.save("4".as_bytes(), &Uint128(42))
.save("4".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
let start_after = Addr::unchecked("2");
@@ -725,11 +817,18 @@ mod tests {
let delegation_owner = Addr::unchecked("bar");
mix_delegations(&mut deps.storage, &node_identity)
.save(delegation_owner.as_bytes(), &Uint128(42))
.save(
delegation_owner.as_bytes(),
&RawDelegationData::new(42u128.into(), 12_345),
)
.unwrap();
assert_eq!(
Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))),
Ok(Delegation::new(
delegation_owner.clone(),
coin(42, DENOM),
12_345
)),
query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner)
)
}
@@ -757,7 +856,7 @@ mod tests {
// add delegation from a different address
mix_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner2.as_bytes(), &Uint128(42))
.save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
assert_eq!(
@@ -774,7 +873,7 @@ mod tests {
// add delegation for a different node
mix_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner1.as_bytes(), &Uint128(42))
.save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
assert_eq!(
@@ -786,17 +885,177 @@ mod tests {
)
}
#[cfg(test)]
mod querying_for_reverse_mixnode_delegations_paged {
use super::*;
use crate::storage::reverse_mix_delegations;
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) {
for i in 0..n {
let node_identity = format!("node{}", i);
reverse_mix_delegations(storage, delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = helpers::init_contract();
let limit = 2;
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegated_nodes.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query without explicitly setting a limit
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
None,
)
.unwrap();
assert_eq!(
DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegated_nodes.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query with a crazy high limit in an attempt to use too many resources
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegated_nodes.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("bar");
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save("1".as_bytes(), &())
.unwrap();
let per_page = 2;
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegated_nodes.len());
// save another
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save("2".as_bytes(), &())
.unwrap();
// page1 should have 2 results on it
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegated_nodes.len());
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save("3".as_bytes(), &())
.unwrap();
// page1 still has 2 results
let page1 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegated_nodes.len());
// retrieving the next page should start after the last key on this page
let start_after: IdentityKey = String::from("2");
let page2 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegated_nodes.len());
// save another one
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save("4".as_bytes(), &())
.unwrap();
let start_after = String::from("2");
let page2 = query_reverse_mixnode_delegations_paged(
deps.as_ref(),
delegation_owner,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegated_nodes.len());
}
}
#[cfg(test)]
mod querying_for_gateway_delegations_paged {
use super::*;
use crate::storage::gateway_delegations;
use cosmwasm_std::Uint128;
fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) {
for i in 0..n {
let address = format!("address{}", i);
gateway_delegations(storage, node_identity)
.save(address.as_bytes(), &Uint128(42))
.save(address.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
}
}
@@ -868,7 +1127,7 @@ mod tests {
let node_identity: IdentityKey = "foo".into();
gateway_delegations(&mut deps.storage, &node_identity)
.save("1".as_bytes(), &Uint128(42))
.save("1".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
let per_page = 2;
@@ -885,7 +1144,7 @@ mod tests {
// save another
gateway_delegations(&mut deps.storage, &node_identity)
.save("2".as_bytes(), &Uint128(42))
.save("2".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
// page1 should have 2 results on it
@@ -899,7 +1158,7 @@ mod tests {
assert_eq!(2, page1.delegations.len());
gateway_delegations(&mut deps.storage, &node_identity)
.save("3".as_bytes(), &Uint128(42))
.save("3".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
// page1 still has 2 results
@@ -926,7 +1185,7 @@ mod tests {
// save another one
gateway_delegations(&mut deps.storage, &node_identity)
.save("4".as_bytes(), &Uint128(42))
.save("4".as_bytes(), &raw_delegation_fixture(42))
.unwrap();
let start_after = Addr::unchecked("2");
@@ -950,11 +1209,18 @@ mod tests {
let delegation_owner = Addr::unchecked("bar");
gateway_delegations(&mut deps.storage, &node_identity)
.save(delegation_owner.as_bytes(), &Uint128(42))
.save(
delegation_owner.as_bytes(),
&&RawDelegationData::new(42u128.into(), 12_345),
)
.unwrap();
assert_eq!(
Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))),
Ok(Delegation::new(
delegation_owner.clone(),
coin(42, DENOM),
12_345
)),
query_gateway_delegation(deps.as_ref(), node_identity, delegation_owner)
)
}
@@ -982,7 +1248,7 @@ mod tests {
// add delegation from a different address
gateway_delegations(&mut deps.storage, &node_identity1)
.save(delegation_owner2.as_bytes(), &Uint128(42))
.save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
assert_eq!(
@@ -999,7 +1265,7 @@ mod tests {
// add delegation for a different node
gateway_delegations(&mut deps.storage, &node_identity2)
.save(delegation_owner1.as_bytes(), &Uint128(42))
.save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42))
.unwrap();
assert_eq!(
@@ -1010,4 +1276,165 @@ mod tests {
query_gateway_delegation(deps.as_ref(), node_identity1, delegation_owner1)
)
}
#[cfg(test)]
mod querying_for_reverse_gateway_delegations_paged {
use super::*;
use crate::storage::reverse_gateway_delegations;
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) {
for i in 0..n {
let node_identity = format!("node{}", i);
reverse_gateway_delegations(storage, delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
}
}
#[test]
fn retrieval_obeys_limits() {
let mut deps = helpers::init_contract();
let limit = 2;
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner);
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(limit),
)
.unwrap();
assert_eq!(limit, page1.delegated_nodes.len() as u32);
}
#[test]
fn retrieval_has_default_limit() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query without explicitly setting a limit
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
None,
)
.unwrap();
assert_eq!(
DELEGATION_PAGE_DEFAULT_LIMIT,
page1.delegated_nodes.len() as u32
);
}
#[test]
fn retrieval_has_max_limit() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("foo");
store_n_reverse_delegations(
DELEGATION_PAGE_DEFAULT_LIMIT * 10,
&mut deps.storage,
&delegation_owner,
);
// query with a crazy high limit in an attempt to use too many resources
let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT;
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner,
None,
Option::from(crazy_limit),
)
.unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DELEGATION_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.delegated_nodes.len() as u32);
}
#[test]
fn pagination_works() {
let mut deps = helpers::init_contract();
let delegation_owner = Addr::unchecked("bar");
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
.save("1".as_bytes(), &())
.unwrap();
let per_page = 2;
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegated_nodes.len());
// save another
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
.save("2".as_bytes(), &())
.unwrap();
// page1 should have 2 results on it
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegated_nodes.len());
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
.save("3".as_bytes(), &())
.unwrap();
// page1 still has 2 results
let page1 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegated_nodes.len());
// retrieving the next page should start after the last key on this page
let start_after: IdentityKey = String::from("2");
let page2 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegated_nodes.len());
// save another one
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
.save("4".as_bytes(), &())
.unwrap();
let start_after = String::from("2");
let page2 = query_reverse_gateway_delegations_paged(
deps.as_ref(),
delegation_owner,
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegated_nodes.len());
}
}
}
-4
View File
@@ -6,8 +6,6 @@ use mixnet_contract::StateParams;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use mixnet_contract::default_delegation_reward;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct State {
pub owner: Addr, // only the owner account can update state
@@ -17,8 +15,6 @@ pub struct State {
// helper values to avoid having to recalculate them on every single payment operation
pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
pub gateway_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
#[serde(default = "default_delegation_reward")]
pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
#[serde(default = "default_delegation_reward")]
pub gateway_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05
}
+394 -37
View File
@@ -3,13 +3,15 @@
use crate::queries;
use crate::state::State;
use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING;
use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128};
use cosmwasm_storage::{
bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton,
Singleton,
};
use mixnet_contract::{
GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, StateParams,
Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond,
RawDelegationData, StateParams,
};
// storage prefixes
@@ -29,9 +31,13 @@ const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go";
const PREFIX_MIX_DELEGATION: &[u8] = b"md";
const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd";
const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm";
const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg";
// Contract-level stuff
// TODO Unify bucket and mixnode storage functions
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
singleton(storage, CONFIG_KEY)
}
@@ -161,6 +167,7 @@ pub(crate) fn increase_mix_delegated_stakes(
storage: &mut dyn Storage,
mix_identity: IdentityKeyRef,
scaled_reward_rate: Decimal,
reward_blockstamp: u64,
) -> StdResult<Uint128> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
@@ -190,11 +197,15 @@ pub(crate) fn increase_mix_delegated_stakes(
);
// and for each of them increase the stake proportionally to the reward
for (delegator_address, amount) in delegations_chunk.into_iter() {
let reward = amount * scaled_reward_rate;
let new_amount = amount + reward;
total_rewarded += reward;
mix_delegations(storage, mix_identity).save(&delegator_address, &new_amount)?;
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
// since they delegated
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
let reward = delegation.amount * scaled_reward_rate;
delegation.amount += reward;
total_rewarded += reward;
mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?;
}
}
}
@@ -205,6 +216,7 @@ pub(crate) fn increase_gateway_delegated_stakes(
storage: &mut dyn Storage,
gateway_identity: IdentityKeyRef,
scaled_reward_rate: Decimal,
reward_blockstamp: u64,
) -> StdResult<Uint128> {
let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize;
@@ -234,11 +246,16 @@ pub(crate) fn increase_gateway_delegated_stakes(
);
// and for each of them increase the stake proportionally to the reward
for (delegator_address, amount) in delegations_chunk.into_iter() {
let reward = amount * scaled_reward_rate;
let new_amount = amount + reward;
total_rewarded += reward;
gateway_delegations(storage, gateway_identity).save(&delegator_address, &new_amount)?;
// if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created
// since they delegated
for (delegator_address, mut delegation) in delegations_chunk.into_iter() {
if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp {
let reward = delegation.amount * scaled_reward_rate;
delegation.amount += reward;
total_rewarded += reward;
gateway_delegations(storage, gateway_identity)
.save(&delegator_address, &delegation)?;
}
}
}
@@ -290,21 +307,40 @@ pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket<IdentityKey
pub fn mix_delegations<'a>(
storage: &'a mut dyn Storage,
mix_identity: IdentityKeyRef,
) -> Bucket<'a, Uint128> {
) -> Bucket<'a, RawDelegationData> {
Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
pub fn mix_delegations_read<'a>(
storage: &'a dyn Storage,
mix_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, RawDelegationData> {
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
// https://github.com/nymtech/nym/blob/122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7/contracts/mixnet/src/storage.rs
pub fn mix_delegations_read_old<'a>(
storage: &'a dyn Storage,
mix_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()])
}
pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> {
Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
}
pub fn reverse_mix_delegations_read<'a>(
storage: &'a dyn Storage,
owner: &Addr,
) -> ReadonlyBucket<'a, ()> {
ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()])
}
pub fn gateway_delegations<'a>(
storage: &'a mut dyn Storage,
gateway_identity: IdentityKeyRef,
) -> Bucket<'a, Uint128> {
) -> Bucket<'a, RawDelegationData> {
Bucket::multilevel(
storage,
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
@@ -314,6 +350,16 @@ pub fn gateway_delegations<'a>(
pub fn gateway_delegations_read<'a>(
storage: &'a dyn Storage,
gateway_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, RawDelegationData> {
ReadonlyBucket::multilevel(
storage,
&[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()],
)
}
pub fn gateway_delegations_read_old<'a>(
storage: &'a dyn Storage,
gateway_identity: IdentityKeyRef,
) -> ReadonlyBucket<'a, Uint128> {
ReadonlyBucket::multilevel(
storage,
@@ -321,6 +367,26 @@ pub fn gateway_delegations_read<'a>(
)
}
pub fn reverse_gateway_delegations<'a>(
storage: &'a mut dyn Storage,
owner: &Addr,
) -> Bucket<'a, ()> {
Bucket::multilevel(
storage,
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
)
}
pub fn reverse_gateway_delegations_read<'a>(
storage: &'a dyn Storage,
owner: &Addr,
) -> ReadonlyBucket<'a, ()> {
ReadonlyBucket::multilevel(
storage,
&[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()],
)
}
// currently not used outside tests
#[cfg(test)]
pub(crate) fn read_gateway_bond(
@@ -348,6 +414,7 @@ mod tests {
use super::*;
use crate::support::tests::helpers::{
gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture,
raw_delegation_fixture,
};
use config::defaults::DENOM;
use cosmwasm_std::testing::MockStorage;
@@ -400,6 +467,7 @@ mod tests {
total_delegation: coin(0, DENOM),
owner: node_owner.clone(),
layer: Layer::One,
block_height: 12_345,
mix_node: MixNode {
identity_key: node_identity.clone(),
..mix_node_fixture()
@@ -433,6 +501,7 @@ mod tests {
bond_amount: coin(bond_value, DENOM),
total_delegation: coin(0, DENOM),
owner: node_owner.clone(),
block_height: 12_345,
gateway: Gateway {
identity_key: node_identity.clone(),
..gateway_fixture()
@@ -463,9 +532,13 @@ mod tests {
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let total_increase =
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
42,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
@@ -483,23 +556,88 @@ mod tests {
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase =
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(1), total_increase);
// amount is incremented, block height remains the same
assert_eq!(
Uint128(1001),
RawDelegationData::new(1001u128.into(), 42),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
mix_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// amount is not incremented
assert_eq!(
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
);
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
// there is an increase now, that the lock period has passed
assert_eq!(Uint128(1), total_increase);
// amount is incremented
assert_eq!(
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -510,6 +648,7 @@ mod tests {
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
@@ -517,20 +656,27 @@ mod tests {
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase =
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(100), total_increase);
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
Uint128(1001),
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -542,6 +688,7 @@ mod tests {
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
@@ -549,13 +696,20 @@ mod tests {
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
mix_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
let total_increase =
increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward)
.unwrap();
let total_increase = increase_mix_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(
Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10),
@@ -565,7 +719,7 @@ mod tests {
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
Uint128(1001),
raw_delegation_fixture(1001),
mix_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -574,6 +728,71 @@ mod tests {
}
}
#[cfg(test)]
mod reverse_mix_delegations {
use super::*;
use crate::support::tests::helpers;
#[test]
fn reverse_mix_delegation_exists() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
reverse_mix_delegations(&mut deps.storage, &delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner)
.may_load(node_identity.as_bytes())
.unwrap()
.is_some(),
);
}
#[test]
fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation for a different node
reverse_mix_delegations(&mut deps.storage, &delegation_owner1)
.save(node_identity2.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation from a different owner
reverse_mix_delegations(&mut deps.storage, &delegation_owner2)
.save(node_identity1.as_bytes(), &())
.unwrap();
assert!(
reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
}
}
#[cfg(test)]
mod increasing_gateway_delegated_stakes {
use super::*;
@@ -592,6 +811,7 @@ mod tests {
&mut deps.storage,
node_identity.as_ref(),
reward,
42,
)
.unwrap();
@@ -611,26 +831,88 @@ mod tests {
fn when_there_is_a_single_delegation() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
gateway_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_gateway_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
assert_eq!(Uint128(1), total_increase);
// amount is incremented, block height remains the same
assert_eq!(
Uint128(1001),
RawDelegationData::new(1001u128.into(), 42),
gateway_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
)
}
#[test]
fn when_there_is_a_single_delegation_depending_on_blockstamp() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
let delegator_address = Addr::unchecked("bob");
gateway_delegations(&mut deps.storage, &node_identity)
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
let total_increase = increase_gateway_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1,
)
.unwrap();
// there was no increase
assert!(total_increase.is_zero());
// amount is not incremented
assert_eq!(
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
gateway_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
);
let total_increase = increase_gateway_delegated_stakes(
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
// there is an increase now, that the lock period has passed
assert_eq!(Uint128(1), total_increase);
// amount is incremented
assert_eq!(
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
gateway_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -641,6 +923,7 @@ mod tests {
fn when_there_are_multiple_delegations() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
@@ -648,7 +931,10 @@ mod tests {
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
gateway_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
@@ -656,6 +942,7 @@ mod tests {
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
@@ -664,7 +951,7 @@ mod tests {
for i in 0..100 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
Uint128(1001),
raw_delegation_fixture(1001),
gateway_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -676,6 +963,7 @@ mod tests {
fn when_there_are_more_delegations_than_page_size() {
let mut deps = mock_dependencies(&[]);
let node_identity: IdentityKey = "nodeidentity".into();
let delegation_blockstamp = 42;
// 0.001
let reward = Decimal::from_ratio(1u128, 1000u128);
@@ -683,7 +971,10 @@ mod tests {
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
gateway_delegations(&mut deps.storage, &node_identity)
.save(delegator_address.as_bytes(), &Uint128(1000))
.save(
delegator_address.as_bytes(),
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
)
.unwrap();
}
@@ -691,6 +982,7 @@ mod tests {
&mut deps.storage,
node_identity.as_ref(),
reward,
delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING,
)
.unwrap();
@@ -702,7 +994,7 @@ mod tests {
for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 {
let delegator_address = Addr::unchecked(format!("address{}", i));
assert_eq!(
Uint128(1001),
raw_delegation_fixture(1001),
gateway_delegations_read(&mut deps.storage, &node_identity)
.load(delegator_address.as_bytes())
.unwrap()
@@ -710,4 +1002,69 @@ mod tests {
}
}
}
#[cfg(test)]
mod reverse_gateway_delegations {
use super::*;
use crate::support::tests::helpers;
#[test]
fn reverse_gateway_delegation_exists() {
let mut deps = helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let delegation_owner = Addr::unchecked("bar");
reverse_gateway_delegations(&mut deps.storage, &delegation_owner)
.save(node_identity.as_bytes(), &())
.unwrap();
assert!(
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner)
.may_load(node_identity.as_bytes())
.unwrap()
.is_some(),
);
}
#[test]
fn reverse_gateway_delegation_returns_none_if_delegation_doesnt_exist() {
let mut deps = helpers::init_contract();
let node_identity1: IdentityKey = "foo1".into();
let node_identity2: IdentityKey = "foo2".into();
let delegation_owner1 = Addr::unchecked("bar");
let delegation_owner2 = Addr::unchecked("bar2");
assert!(
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation for a different node
reverse_gateway_delegations(&mut deps.storage, &delegation_owner1)
.save(node_identity2.as_bytes(), &())
.unwrap();
assert!(
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
// add delegation from a different owner
reverse_gateway_delegations(&mut deps.storage, &delegation_owner2)
.save(node_identity1.as_bytes(), &())
.unwrap();
assert!(
reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1)
.may_load(node_identity1.as_bytes())
.unwrap()
.is_none()
);
}
}
}
+10 -3
View File
@@ -5,7 +5,6 @@ pub mod helpers {
use crate::contract::{instantiate, INITIAL_MIXNODE_BOND};
use crate::transactions::{try_add_gateway, try_add_mixnode};
use config::defaults::DENOM;
use cosmwasm_std::coin;
use cosmwasm_std::from_binary;
use cosmwasm_std::testing::mock_dependencies;
use cosmwasm_std::testing::mock_env;
@@ -16,10 +15,11 @@ pub mod helpers {
use cosmwasm_std::Addr;
use cosmwasm_std::Coin;
use cosmwasm_std::OwnedDeps;
use cosmwasm_std::{coin, Uint128};
use cosmwasm_std::{Empty, MemoryStorage};
use mixnet_contract::{
Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse,
PagedMixnodeResponse, QueryMsg,
PagedMixnodeResponse, QueryMsg, RawDelegationData,
};
pub fn add_mixnode(
@@ -31,6 +31,7 @@ pub mod helpers {
let key = format!("{}mixnode", sender);
try_add_mixnode(
deps.as_mut(),
mock_env(),
info,
MixNode {
identity_key: key.clone(),
@@ -67,6 +68,7 @@ pub mod helpers {
let key = format!("{}gateway", sender);
try_add_gateway(
deps.as_mut(),
mock_env(),
info,
Gateway {
identity_key: key.clone(),
@@ -129,6 +131,7 @@ pub mod helpers {
coin(50, DENOM),
Addr::unchecked("foo"),
Layer::One,
12_345,
mix_node,
)
}
@@ -156,7 +159,11 @@ pub mod helpers {
identity_key: "identity".to_string(),
version: "0.10.0".to_string(),
};
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), gateway)
GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway)
}
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
RawDelegationData::new(Uint128(amount), 42)
}
pub fn query_contract_balance(
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -23,4 +23,4 @@ pretty_env_logger = "0.4.0"
mixnet-contract = { path = "../common/mixnet-contract" }
network-defaults = { path = "../common/network-defaults" }
validator-client = { path = "../common/client-libs/validator-client" }
validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] }
+8 -2
View File
@@ -6,11 +6,11 @@ use serde::Serialize;
use mixnet_contract::{Addr, Coin, Layer, MixNode};
use crate::mix_node::models::{NodeDescription, NodeStats};
use crate::mix_nodes::Location;
use crate::mix_nodes::{get_mixnode_delegations, Location};
use crate::state::ExplorerApiStateContext;
pub fn mix_node_make_default_routes() -> Vec<Route> {
routes_with_openapi![get_description, get_stats, list]
routes_with_openapi![get_delegations, get_description, get_stats, list]
}
#[derive(Clone, Debug, Serialize, JsonSchema)]
@@ -51,6 +51,12 @@ pub(crate) async fn list(
)
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/delegations")]
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<mixnet_contract::Delegation>> {
Json(get_mixnode_delegations(pubkey).await)
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/description")]
pub(crate) async fn get_description(
+31 -2
View File
@@ -8,8 +8,11 @@ use rocket::tokio::sync::RwLock;
use serde::{Deserialize, Serialize};
use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code;
use mixnet_contract::MixNodeBond;
use network_defaults::default_api_endpoints;
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<String, Location>;
@@ -163,6 +166,32 @@ pub(crate) async fn retrieve_mixnodes() -> Vec<MixNodeBond> {
bonds
}
pub(crate) async fn get_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
let client = new_nymd_client();
let delegates = match client
.get_all_nymd_mixnode_delegations(pubkey.to_string())
.await
{
Ok(result) => result,
Err(e) => {
error!("Could not get delegations for mix node {}: {:?}", pubkey, e);
vec![]
}
};
delegates
}
fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
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()));
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())
+92 -28
View File
@@ -1,13 +1,14 @@
use std::collections::HashMap;
use std::net::ToSocketAddrs;
use std::net::{SocketAddr, ToSocketAddrs};
use std::time::Duration;
use rocket::serde::json::Json;
use rocket::{Route, State};
use mixnet_contract::MixNodeBond;
use crate::ping::models::PingResponse;
use crate::state::ExplorerApiStateContext;
use mixnet_contract::MixNodeBond;
const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10);
@@ -80,32 +81,95 @@ async fn port_check(bond: &MixNodeBond) -> HashMap<u16, bool> {
ports
}
async fn do_port_check(host: &str, port: u16) -> bool {
let addr = format!("{}:{}", host, port)
.to_socket_addrs()
.unwrap()
.next()
.unwrap();
match tokio::time::timeout(
CONNECTION_TIMEOUT_SECONDS,
tokio::net::TcpStream::connect(addr),
)
.await
{
Ok(Ok(_stream)) => {
// didn't timeout and tcp stream is open
trace!("Successfully pinged {}", addr);
true
}
Ok(Err(_stream_err)) => {
error!("{} ping failed {:}", addr, _stream_err);
// didn't timeout but couldn't open tcp stream
false
}
Err(_timeout) => {
// timed out
error!("{} timed out {:}", addr, _timeout);
false
fn sanitize_and_resolve_host(host: &str, port: u16) -> Option<SocketAddr> {
// trim the host
let trimmed_host = host.trim();
// host must be at least one non-whitespace character
if trimmed_host.is_empty() {
return None;
}
// the host string should hopefully parse and resolve into a valid socket address
let parsed_host = format!("{}:{}", trimmed_host, port);
match parsed_host.to_socket_addrs() {
Ok(mut addrs) => addrs.next(),
Err(e) => {
warn!(
"Failed to resolve {}:{} -> {}. Error: {}",
host, port, parsed_host, e
);
None
}
}
}
async fn do_port_check(host: &str, port: u16) -> bool {
match sanitize_and_resolve_host(host, port) {
Some(addr) => match tokio::time::timeout(
CONNECTION_TIMEOUT_SECONDS,
tokio::net::TcpStream::connect(addr),
)
.await
{
Ok(Ok(_stream)) => {
// didn't timeout and tcp stream is open
trace!("Successfully pinged {}", addr);
true
}
Ok(Err(_stream_err)) => {
warn!("{} ping failed {:}", addr, _stream_err);
// didn't timeout but couldn't open tcp stream
false
}
Err(_timeout) => {
// timed out
warn!("{} timed out {:}", addr, _timeout);
false
}
},
None => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolve_host_with_valid_ip_address_returns_some() {
assert!(sanitize_and_resolve_host("8.8.8.8", 1234).is_some());
assert!(sanitize_and_resolve_host("2001:4860:4860::8888", 1234).is_some());
}
#[test]
fn resolve_host_with_valid_hostname_returns_some() {
assert!(sanitize_and_resolve_host("nymtech.net", 1234).is_some());
}
#[test]
fn resolve_host_with_malformed_ip_address_returns_none() {
// these are invalid ip addresses
assert!(sanitize_and_resolve_host("192.168.1.999", 1234).is_none());
assert!(sanitize_and_resolve_host("10.999.999.999", 1234).is_none());
}
#[test]
fn resolve_host_with_unknown_hostname_returns_none() {
assert!(sanitize_and_resolve_host(
"some-unknown-hostname-that-will-never-resolve.nymtech.net",
1234
)
.is_none());
}
#[test]
fn resolve_host_with_bad_strings_return_none() {
assert!(sanitize_and_resolve_host("", 1234).is_none());
assert!(sanitize_and_resolve_host(" ", 1234).is_none());
assert!(sanitize_and_resolve_host(" 🤘 ", 1234).is_none());
assert!(sanitize_and_resolve_host("🤘", 1234).is_none());
assert!(sanitize_and_resolve_host("@", 1234).is_none());
assert!(sanitize_and_resolve_host("*", 1234).is_none());
}
}
+1
View File
@@ -24,6 +24,7 @@ crypto = { path = "../../common/crypto" }
pemstore = { path = "../../common/pemstore" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials"}
[dependencies.tungstenite]
version = "0.13.0"
@@ -1,7 +1,7 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::authentication::iv::AuthenticationIV;
use crate::iv::IV;
use crate::registration::handshake::shared_key::SharedKeys;
use crypto::symmetric::stream_cipher;
use nymsphinx::params::GatewayEncryptionAlgorithm;
@@ -23,7 +23,7 @@ pub enum EncryptedAddressConversionError {
}
impl EncryptedAddressBytes {
pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &AuthenticationIV) -> Self {
pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> Self {
let ciphertext = stream_cipher::encrypt::<GatewayEncryptionAlgorithm>(
key.encryption_key(),
iv.inner(),
@@ -35,12 +35,7 @@ impl EncryptedAddressBytes {
EncryptedAddressBytes(enc_address)
}
pub fn verify(
&self,
address: &DestinationAddressBytes,
key: &SharedKeys,
iv: &AuthenticationIV,
) -> bool {
pub fn verify(&self, address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> bool {
self == &Self::new(address, key, iv)
}
@@ -2,4 +2,3 @@
// SPDX-License-Identifier: Apache-2.0
pub mod encrypted_address;
pub mod iv;
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crypto::generic_array::{typenum::Unsigned, GenericArray};
use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV};
use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV as CryptoIV};
use nymsphinx::params::GatewayEncryptionAlgorithm;
use rand::{CryptoRng, RngCore};
@@ -10,7 +10,7 @@ type NonceSize = <GatewayEncryptionAlgorithm as NewStreamCipher>::NonceSize;
// I think 'IV' looks better than 'Iv', feel free to change that.
#[allow(clippy::upper_case_acronyms)]
pub struct AuthenticationIV(IV<GatewayEncryptionAlgorithm>);
pub struct IV(CryptoIV<GatewayEncryptionAlgorithm>);
#[derive(Debug)]
// I think 'IV' looks better than 'Iv', feel free to change that.
@@ -21,9 +21,9 @@ pub enum IVConversionError {
StringOfInvalidLengthError,
}
impl AuthenticationIV {
impl IV {
pub fn new_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
AuthenticationIV(random_iv::<GatewayEncryptionAlgorithm, _>(rng))
IV(random_iv::<GatewayEncryptionAlgorithm, _>(rng))
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, IVConversionError> {
@@ -31,7 +31,7 @@ impl AuthenticationIV {
return Err(IVConversionError::BytesOfInvalidLengthError);
}
Ok(AuthenticationIV(GenericArray::clone_from_slice(bytes)))
Ok(IV(GenericArray::clone_from_slice(bytes)))
}
pub fn to_bytes(&self) -> Vec<u8> {
@@ -42,7 +42,7 @@ impl AuthenticationIV {
self.0.as_ref()
}
pub fn inner(&self) -> &IV<GatewayEncryptionAlgorithm> {
pub fn inner(&self) -> &CryptoIV<GatewayEncryptionAlgorithm> {
&self.0
}
@@ -56,8 +56,8 @@ impl AuthenticationIV {
return Err(IVConversionError::StringOfInvalidLengthError);
}
Ok(AuthenticationIV(
GenericArray::from_exact_iter(decoded).expect("Invalid vector length!"),
Ok(IV(
GenericArray::from_exact_iter(decoded).expect("Invalid vector length!")
))
}
@@ -66,8 +66,8 @@ impl AuthenticationIV {
}
}
impl From<AuthenticationIV> for String {
fn from(iv: AuthenticationIV) -> Self {
impl From<IV> for String {
fn from(iv: IV) -> Self {
iv.to_base58_string()
}
}
+3 -3
View File
@@ -1,19 +1,19 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub use crypto::generic_array;
use crypto::hmac::{hmac::Mac, HmacOutput};
use nymsphinx::params::GatewayIntegrityHmacAlgorithm;
pub use types::*;
pub mod authentication;
pub mod iv;
pub mod registration;
pub mod types;
pub const DUMMY_MESSAGE_CONTENT: &[u8] =
b"[DUMMY MESSAGE] Wanting something does not give you the right to have it.";
pub use crypto::generic_array;
pub use types::*;
pub type GatewayMac = HmacOutput<GatewayIntegrityHmacAlgorithm>;
// TODO: could using `Mac` trait here for OutputSize backfire?
@@ -24,18 +24,11 @@ impl<'a> ClientHandshake<'a> {
ws_stream: &'a mut S,
identity: &'a crypto::asymmetric::identity::KeyPair,
gateway_pubkey: identity::PublicKey,
coconut_credential: coconut_interface::Credential,
) -> Self
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
{
let mut state = State::new(
rng,
ws_stream,
identity,
Some(gateway_pubkey),
Some(coconut_credential),
);
let mut state = State::new(rng, ws_stream, identity, Some(gateway_pubkey));
ClientHandshake {
handshake_future: Box::pin(async move {
@@ -25,8 +25,6 @@ pub enum HandshakeError {
MalformedRequest,
#[error("sent request was malformed")]
HandshakeFailure,
#[error("could not verify Coconut Credential")]
InvalidCoconutCredential,
#[error("could not deserialize from slice: {source}")]
DeserializationError {
#[from]
@@ -4,7 +4,6 @@
use crate::registration::handshake::shared_key::SharedKeys;
use crate::registration::handshake::state::State;
use crate::registration::handshake::{error::HandshakeError, WsItem};
use coconut_interface::VerificationKey;
use crypto::asymmetric::encryption;
use futures::future::BoxFuture;
use futures::task::{Context, Poll};
@@ -23,12 +22,11 @@ impl<'a> GatewayHandshake<'a> {
ws_stream: &'a mut S,
identity: &'a crypto::asymmetric::identity::KeyPair,
received_init_payload: Vec<u8>,
verification_key: &'a VerificationKey,
) -> Self
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
{
let mut state = State::new(rng, ws_stream, identity, None, None);
let mut state = State::new(rng, ws_stream, identity, None);
GatewayHandshake {
handshake_future: Box::pin(async move {
// If any step along the way failed (that are non-network related),
@@ -50,27 +48,13 @@ impl<'a> GatewayHandshake<'a> {
}
}
// init: <- pub_key || g^x || credential
// init: <- pub_key || g^x
let init_message = check_processing_error(
State::<S>::parse_init_message(received_init_payload),
&mut state,
)
.await?;
let credential = init_message.credential();
check_processing_error(
{
if !credential.verify(verification_key).await {
Err(HandshakeError::InvalidCoconutCredential)
} else {
Ok(())
}
},
&mut state,
)
.await?;
let remote_identity = init_message.local_id_pubkey();
let remote_ephemeral_key = init_message.ephemeral_key();
state.update_remote_identity(remote_identity);
@@ -6,8 +6,6 @@ use self::error::HandshakeError;
#[cfg(not(target_arch = "wasm32"))]
use self::gateway::GatewayHandshake;
pub use self::shared_key::{SharedKeySize, SharedKeys};
#[cfg(not(target_arch = "wasm32"))]
use coconut_interface::VerificationKey;
use crypto::asymmetric::identity;
use futures::{Sink, Stream};
use rand::{CryptoRng, RngCore};
@@ -32,12 +30,11 @@ pub async fn client_handshake<'a, S>(
ws_stream: &'a mut S,
identity: &'a identity::KeyPair,
gateway_pubkey: identity::PublicKey,
coconut_credential: coconut_interface::Credential,
) -> Result<SharedKeys, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
{
ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey, coconut_credential).await
ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey).await
}
#[cfg(not(target_arch = "wasm32"))]
@@ -46,19 +43,11 @@ pub async fn gateway_handshake<'a, S>(
ws_stream: &'a mut S,
identity: &'a identity::KeyPair,
received_init_payload: Vec<u8>,
verification_key: &VerificationKey,
) -> Result<SharedKeys, HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
{
GatewayHandshake::new(
rng,
ws_stream,
identity,
received_init_payload,
verification_key,
)
.await
GatewayHandshake::new(rng, ws_stream, identity, received_init_payload).await
}
/*
@@ -1,11 +1,12 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{GatewayMacSize, GatewayRequestsError};
use crypto::generic_array::{
typenum::{Sum, Unsigned, U16},
GenericArray,
};
use crypto::hmac::compute_keyed_hmac;
use crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag};
use crypto::symmetric::stream_cipher::{self, Key, NewStreamCipher, IV};
use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm};
use pemstore::traits::PemStorableKey;
@@ -102,6 +103,41 @@ impl SharedKeys {
.collect()
}
pub fn decrypt_tagged(
&self,
enc_data: &[u8],
iv: Option<&IV<GatewayEncryptionAlgorithm>>,
) -> Result<Vec<u8>, GatewayRequestsError> {
let mac_size = GatewayMacSize::to_usize();
if enc_data.len() < mac_size {
return Err(GatewayRequestsError::TooShortRequest);
}
let mac_tag = &enc_data[..mac_size];
let message_bytes = &enc_data[mac_size..];
if !recompute_keyed_hmac_and_verify_tag::<GatewayIntegrityHmacAlgorithm>(
self.mac_key(),
message_bytes,
mac_tag,
) {
return Err(GatewayRequestsError::InvalidMac);
}
// couldn't have made the first borrow mutable as you can't have an immutable borrow
// together with a mutable one
let mut message_bytes_mut = &mut enc_data.to_vec()[mac_size..];
let zero_iv = stream_cipher::zero_iv::<GatewayEncryptionAlgorithm>();
let iv = iv.unwrap_or(&zero_iv);
stream_cipher::decrypt_in_place::<GatewayEncryptionAlgorithm>(
self.encryption_key(),
iv,
&mut message_bytes_mut,
);
Ok(message_bytes_mut.to_vec())
}
pub fn encryption_key(&self) -> &Key<GatewayEncryptionAlgorithm> {
&self.encryption_key
}
@@ -5,7 +5,6 @@ use crate::registration::handshake::error::HandshakeError;
use crate::registration::handshake::shared_key::{SharedKeySize, SharedKeys};
use crate::registration::handshake::WsItem;
use crate::types;
use coconut_interface::Credential;
use crypto::{
asymmetric::{encryption, identity},
generic_array::typenum::Unsigned,
@@ -24,19 +23,13 @@ use tungstenite::Message as WsMessage;
pub struct InitMessage {
local_id_pubkey: [u8; identity::PUBLIC_KEY_LENGTH],
ephemeral_key: [u8; identity::PUBLIC_KEY_LENGTH],
credential: Option<Credential>,
}
impl InitMessage {
fn new(
local_id_pubkey: &identity::PublicKey,
ephemeral_key: &encryption::PublicKey,
credential: Credential,
) -> Self {
fn new(local_id_pubkey: &identity::PublicKey, ephemeral_key: &encryption::PublicKey) -> Self {
InitMessage {
local_id_pubkey: local_id_pubkey.to_bytes(),
ephemeral_key: ephemeral_key.to_bytes(),
credential: Some(credential),
}
}
@@ -50,11 +43,6 @@ impl InitMessage {
encryption::PublicKey::from_bytes(&self.ephemeral_key).unwrap()
}
#[cfg(not(target_arch = "wasm32"))]
pub fn credential(&self) -> Credential {
self.credential.clone().unwrap()
}
fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap()
}
@@ -85,7 +73,6 @@ pub(crate) struct State<'a, S> {
/// The known or received public identity key of the remote.
/// Ideally it would always be known before the handshake was initiated.
remote_pubkey: Option<identity::PublicKey>,
coconut_credential: Option<Credential>,
}
impl<'a, S> State<'a, S> {
@@ -94,7 +81,6 @@ impl<'a, S> State<'a, S> {
ws_stream: &'a mut S,
identity: &'a identity::KeyPair,
remote_pubkey: Option<identity::PublicKey>,
credential: Option<Credential>,
) -> Self {
let ephemeral_keypair = encryption::KeyPair::new(rng);
State {
@@ -103,7 +89,6 @@ impl<'a, S> State<'a, S> {
identity,
remote_pubkey,
derived_shared_keys: None,
coconut_credential: credential,
}
}
@@ -119,7 +104,6 @@ impl<'a, S> State<'a, S> {
InitMessage::new(
self.identity.public_key(),
self.ephemeral_keypair.public_key(),
self.coconut_credential.clone().unwrap(),
)
.to_bytes()
}
+40 -30
View File
@@ -2,9 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::authentication::encrypted_address::EncryptedAddressBytes;
use crate::authentication::iv::AuthenticationIV;
use crate::iv::IV;
use crate::registration::handshake::SharedKeys;
use crate::GatewayMacSize;
use coconut_interface::Credential;
use crypto::generic_array::typenum::Unsigned;
use crypto::hmac::recompute_keyed_hmac_and_verify_tag;
use crypto::symmetric::stream_cipher;
@@ -112,13 +113,17 @@ pub enum ClientControlRequest {
},
#[serde(alias = "handshakePayload")]
RegisterHandshakeInitRequest { data: Vec<u8> },
BandwidthCredential {
enc_credential: Vec<u8>,
iv: Vec<u8>,
},
}
impl ClientControlRequest {
pub fn new_authenticate(
address: DestinationAddressBytes,
enc_address: EncryptedAddressBytes,
iv: AuthenticationIV,
iv: IV,
) -> Self {
ClientControlRequest::Authenticate {
address: address.as_base58_string(),
@@ -126,6 +131,34 @@ impl ClientControlRequest {
iv: iv.to_base58_string(),
}
}
pub fn new_enc_bandwidth_credential(
credential: &Credential,
shared_key: &SharedKeys,
iv: IV,
) -> Option<Self> {
match bincode::serialize(credential) {
Ok(serialized_credential) => {
let enc_credential =
shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner()));
Some(ClientControlRequest::BandwidthCredential {
enc_credential,
iv: iv.to_bytes(),
})
}
_ => None,
}
}
pub fn try_from_enc_bandwidth_credential(
enc_credential: Vec<u8>,
shared_key: &SharedKeys,
iv: IV,
) -> Result<Credential, GatewayRequestsError> {
let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?;
bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption)
}
}
impl From<ClientControlRequest> for Message {
@@ -158,6 +191,8 @@ impl TryInto<String> for ClientControlRequest {
pub enum ServerResponse {
Authenticate { status: bool },
Register { status: bool },
// Maybe we could return the remaining bandwidth?
Bandwidth { status: bool },
Send { status: bool },
Error { message: String },
}
@@ -210,39 +245,14 @@ pub enum BinaryRequest {
// would work there.
impl BinaryRequest {
pub fn try_from_encrypted_tagged_bytes(
mut raw_req: Vec<u8>,
raw_req: Vec<u8>,
shared_keys: &SharedKeys,
) -> Result<Self, GatewayRequestsError> {
let mac_size = GatewayMacSize::to_usize();
if raw_req.len() < mac_size {
return Err(GatewayRequestsError::TooShortRequest);
}
let mac_tag = &raw_req[..mac_size];
let message_bytes = &raw_req[mac_size..];
if !recompute_keyed_hmac_and_verify_tag::<GatewayIntegrityHmacAlgorithm>(
shared_keys.mac_key(),
message_bytes,
mac_tag,
) {
return Err(GatewayRequestsError::InvalidMac);
}
// couldn't have made the first borrow mutable as you can't have an immutable borrow
// together with a mutable one
let mut message_bytes_mut = &mut raw_req[mac_size..];
let zero_iv = stream_cipher::zero_iv::<GatewayEncryptionAlgorithm>();
stream_cipher::decrypt_in_place::<GatewayEncryptionAlgorithm>(
shared_keys.encryption_key(),
&zero_iv,
&mut message_bytes_mut,
);
let message_bytes = &shared_keys.decrypt_tagged(&raw_req, None)?;
// right now there's only a single option possible which significantly simplifies the logic
// if we decided to allow for more 'binary' messages, the API wouldn't need to change.
let mix_packet = MixPacket::try_from_bytes(message_bytes_mut)?;
let mix_packet = MixPacket::try_from_bytes(message_bytes)?;
Ok(BinaryRequest::ForwardSphinx(mix_packet))
}
@@ -0,0 +1,85 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::collections::HashMap;
use std::convert::TryFrom;
use std::sync::Arc;
use tokio::sync::RwLock;
use coconut_interface::Credential;
use credentials::error::Error;
use nymsphinx::DestinationAddressBytes;
const BANDWIDTH_INDEX: usize = 0;
pub type BandwidthDatabase = Arc<RwLock<HashMap<DestinationAddressBytes, u64>>>;
pub fn empty_bandwidth_database() -> BandwidthDatabase {
Arc::new(RwLock::new(HashMap::new()))
}
pub struct Bandwidth {
value: u64,
}
impl Bandwidth {
pub fn value(&self) -> u64 {
self.value
}
pub async fn consume_bandwidth(
bandwidths: &BandwidthDatabase,
remote_address: &DestinationAddressBytes,
consumed: u64,
) -> Result<(), Error> {
if let Some(bandwidth) = bandwidths.write().await.get_mut(remote_address) {
if let Some(res) = bandwidth.checked_sub(consumed) {
*bandwidth = res;
Ok(())
} else {
Err(Error::BandwidthOverflow(String::from(
"Allocate more bandwidth for consumption",
)))
}
} else {
Err(Error::MissingBandwidth)
}
}
pub async fn increase_bandwidth(
bandwidths: &BandwidthDatabase,
remote_address: &DestinationAddressBytes,
increase: u64,
) -> Result<(), Error> {
let mut db = bandwidths.write().await;
if let Some(bandwidth) = db.get_mut(remote_address) {
if let Some(new_bandwidth) = bandwidth.checked_add(increase) {
*bandwidth = new_bandwidth;
} else {
return Err(Error::BandwidthOverflow(String::from(
"Use some of the already allocated bandwidth",
)));
}
} else {
db.insert(*remote_address, increase);
}
Ok(())
}
}
impl TryFrom<Credential> for Bandwidth {
type Error = Error;
fn try_from(credential: Credential) -> Result<Self, Self::Error> {
match credential.public_attributes().get(BANDWIDTH_INDEX) {
None => Err(Error::NotEnoughPublicAttributes),
Some(attr) => match <[u8; 8]>::try_from(attr.as_slice()) {
Ok(bandwidth_bytes) => {
let value = u64::from_be_bytes(bandwidth_bytes);
Ok(Self { value })
}
Err(_) => Err(Error::InvalidBandwidthSize),
},
}
}
}
@@ -10,7 +10,7 @@ use futures::{
StreamExt,
};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::authentication::iv::AuthenticationIV;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::SharedKeys;
use log::*;
use nymsphinx::DestinationAddressBytes;
@@ -34,7 +34,7 @@ pub(crate) enum ClientsHandlerRequest {
Authenticate(
DestinationAddressBytes,
EncryptedAddressBytes,
AuthenticationIV,
IV,
MixMessageSender,
ClientsHandlerResponseSender,
),
@@ -193,7 +193,7 @@ impl ClientsHandler {
&mut self,
address: DestinationAddressBytes,
encrypted_address: EncryptedAddressBytes,
iv: AuthenticationIV,
iv: IV,
comm_channel: MixMessageSender,
res_channel: ClientsHandlerResponseSender,
) {
+1
View File
@@ -1,5 +1,6 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod bandwidth;
pub(crate) mod clients_handler;
pub(crate) mod websocket;
@@ -1,6 +1,7 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::bandwidth::{Bandwidth, BandwidthDatabase};
use crate::node::client_handling::clients_handler::{
ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse,
};
@@ -14,7 +15,7 @@ use futures::{
SinkExt, StreamExt,
};
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::authentication::iv::AuthenticationIV;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::error::HandshakeError;
use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys};
use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse};
@@ -24,6 +25,7 @@ use mixnet_client::forwarder::MixForwardingSender;
use nymsphinx::DestinationAddressBytes;
use rand::{CryptoRng, Rng};
use std::convert::TryFrom;
use std::mem;
use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::{
@@ -59,6 +61,7 @@ pub(crate) struct Handle<R, S> {
local_identity: Arc<identity::KeyPair>,
aggregated_verification_key: VerificationKey,
bandwidths: BandwidthDatabase,
}
impl<R, S> Handle<R, S>
@@ -74,6 +77,7 @@ where
outbound_mix_sender: MixForwardingSender,
local_identity: Arc<identity::KeyPair>,
aggregated_verification_key: VerificationKey,
bandwidths: BandwidthDatabase,
) -> Self {
Handle {
rng,
@@ -84,6 +88,7 @@ where
socket_connection: SocketStream::RawTcp(conn),
local_identity,
aggregated_verification_key,
bandwidths,
}
}
@@ -119,7 +124,6 @@ where
ws_stream,
self.local_identity.as_ref(),
init_msg,
&self.aggregated_verification_key,
)
.await
}
@@ -205,8 +209,19 @@ where
Ok(request) => match request {
// currently only a single type exists
BinaryRequest::ForwardSphinx(mix_packet) => {
self.outbound_mix_sender.unbounded_send(mix_packet).unwrap();
ServerResponse::Send { status: true }
let consumed_bandwidth = mem::size_of_val(&mix_packet) as u64;
if let Err(e) = Bandwidth::consume_bandwidth(
&self.bandwidths,
&self.remote_address.unwrap(),
consumed_bandwidth,
)
.await
{
ServerResponse::new_error(format!("{:?}", e))
} else {
self.outbound_mix_sender.unbounded_send(mix_packet).unwrap();
ServerResponse::Send { status: true }
}
}
},
}
@@ -236,7 +251,7 @@ where
}
};
let iv = match AuthenticationIV::try_from_base58_string(iv) {
let iv = match IV::try_from_base58_string(iv) {
Ok(iv) => iv,
Err(e) => {
trace!("failed to parse received IV {:?}", e);
@@ -342,12 +357,68 @@ where
}
}
// currently there are no valid control messages you can send after authentication
async fn handle_text(&mut self, _: String) -> Message {
trace!("Handling text message (presumably control message)");
async fn handle_bandwidth(&mut self, enc_credential: Vec<u8>, iv: Vec<u8>) -> ServerResponse {
if self.shared_key.is_none() {
return ServerResponse::new_error("No shared key has been exchanged with the gateway");
}
if self.remote_address.is_none() {
return ServerResponse::new_error("No remote address has been set");
}
let iv = match IV::try_from_bytes(&iv) {
Ok(iv) => iv,
Err(e) => {
trace!("failed to parse received IV {:?}", e);
return ServerResponse::new_error("malformed iv");
}
};
let credential = match ClientControlRequest::try_from_enc_bandwidth_credential(
enc_credential,
self.shared_key.as_ref().unwrap(),
iv,
) {
Ok(c) => c,
Err(e) => {
return ServerResponse::new_error(e.to_string());
}
};
if credential.verify(&self.aggregated_verification_key) {
match Bandwidth::try_from(credential) {
Ok(bandwidth) => {
if let Err(e) = Bandwidth::increase_bandwidth(
&self.bandwidths,
&self.remote_address.unwrap(),
bandwidth.value(),
)
.await
{
return ServerResponse::Error {
message: format!("{:?}", e),
};
}
ServerResponse::Bandwidth { status: true }
}
Err(e) => ServerResponse::Error {
message: format!("{:?}", e),
},
}
} else {
ServerResponse::Bandwidth { status: false }
}
}
error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!");
ServerResponse::new_error("invalid request").into()
// currently the bandwidth credential request is the only one we can receive after
// authentication
async fn handle_text(&mut self, raw_request: String) -> Message {
if let Ok(request) = ClientControlRequest::try_from(raw_request) {
match request {
ClientControlRequest::BandwidthCredential { enc_credential, iv } => {
self.handle_bandwidth(enc_credential, iv).await.into()
}
_ => ServerResponse::new_error("invalid request").into(),
}
} else {
ServerResponse::new_error("malformed request").into()
}
}
async fn handle_request(&mut self, raw_request: Message) -> Option<Message> {
@@ -384,6 +455,7 @@ where
ClientControlRequest::RegisterHandshakeInitRequest { data } => {
self.handle_register(data, mix_sender).await
}
_ => ServerResponse::new_error("invalid request"),
}
} else {
// TODO: is this a malformed request or rather a network error and
@@ -1,6 +1,7 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::node::client_handling::bandwidth::empty_bandwidth_database;
use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender;
use crate::node::client_handling::websocket::connection_handler::Handle;
use coconut_interface::VerificationKey;
@@ -46,6 +47,8 @@ impl Listener {
}
};
let bandwidths = empty_bandwidth_database();
loop {
match tcp_listener.accept().await {
Ok((socket, remote_addr)) => {
@@ -59,6 +62,7 @@ impl Listener {
outbound_mix_sender.clone(),
Arc::clone(&self.local_identity),
self.aggregated_verification_key.clone(),
Arc::clone(&bandwidths),
);
tokio::spawn(async move { handle.start_handling().await });
}
@@ -1,8 +1,8 @@
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) use listener::Listener;
pub(crate) mod connection_handler;
pub(crate) mod listener;
pub(crate) mod message_receiver;
pub(crate) use listener::Listener;
+2 -2
View File
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes;
use gateway_requests::authentication::iv::AuthenticationIV;
use gateway_requests::generic_array::typenum::Unsigned;
use gateway_requests::iv::IV;
use gateway_requests::registration::handshake::{SharedKeySize, SharedKeys};
use log::*;
use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH};
@@ -79,7 +79,7 @@ impl ClientLedger {
&self,
client_address: &DestinationAddressBytes,
encrypted_address: &EncryptedAddressBytes,
iv: &AuthenticationIV,
iv: &IV,
) -> Result<bool, ClientLedgerError> {
match self.db.get(client_address.as_bytes_ref()) {
Err(e) => Err(ClientLedgerError::Read(e)),
+12 -12
View File
@@ -283,6 +283,7 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397"
dependencies = [
"digest 0.9.0",
"ff",
"group",
"pairing",
@@ -484,13 +485,12 @@ dependencies = [
"coconut-rs",
"getset",
"serde",
"sha2",
]
[[package]]
name = "coconut-rs"
version = "0.4.0"
source = "git+https://github.com/nymtech/coconut.git?branch=0.4.0#183cff805aa73a908b681ac3fcdff7d084c4f42b"
version = "0.5.0"
source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f"
dependencies = [
"bls12_381",
"bs58",
@@ -503,8 +503,6 @@ dependencies = [
"serde",
"serde_derive",
"sha2",
"sha3",
"subtle",
"thiserror",
]
@@ -653,8 +651,9 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.6.0"
source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e"
dependencies = [
"prost",
"prost-types",
@@ -662,9 +661,10 @@ dependencies = [
]
[[package]]
name = "cosmos_sdk"
version = "0.2.0"
source = "git+https://github.com/cosmos/cosmos-rust/?rev=ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30"
name = "cosmrs"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -2563,7 +2563,7 @@ dependencies = [
"bip39",
"coconut-interface",
"config",
"cosmos_sdk",
"cosmrs",
"cosmwasm-std",
"credentials",
"dirs",
@@ -4793,7 +4793,7 @@ dependencies = [
"bip39",
"coconut-interface",
"config",
"cosmos_sdk",
"cosmrs",
"cosmwasm-std",
"flate2",
"itertools 0.10.1",
+1 -5
View File
@@ -28,11 +28,7 @@ ts-rs = "3.0"
url = "2.0"
rand = "0.6.5"
cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", rev = "ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = [
"rpc",
"bip32",
"cosmwasm",
] }
cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"] }
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch = "0.14.1-updatedk256" }
validator-client = { path = "../../common/client-libs/validator-client", features = [
-140
View File
@@ -1,140 +0,0 @@
use crate::state::State;
use coconut_interface::{self, Credential, Signature, Theta, VerificationKey};
use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key};
use std::sync::Arc;
use tokio::sync::RwLock;
use url::Url;
#[tauri::command]
pub async fn randomise_credential(
idx: usize,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
let signature = state.signatures.remove(idx);
let new = signature.randomise(state.params()?);
state.signatures.insert(idx, new);
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn delete_credential(
idx: usize,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let mut state = state.write().await;
state.signatures.remove(idx);
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn list_credentials(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let state = state.read().await;
Ok(state.signatures.clone())
}
#[tauri::command]
pub async fn verify_credential(
idx: usize,
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<bool, String> {
// the API needs to be improved but at least it should compile (in theory)
let verification_key =
get_aggregated_verification_key(validator_urls.clone(), state.clone()).await?;
let theta = prove_credential(idx, validator_urls, state.clone()).await?;
let state = state.read().await;
let credential = Credential::new(
state.n_attributes(),
theta,
&state.public_attributes(),
state
.signatures
.get(idx)
.ok_or("Got invalid signature idx")?,
);
Ok(credential.verify(&verification_key).await)
}
async fn prove_credential(
idx: usize,
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Theta, String> {
let verification_key = get_aggregated_verification_key(validator_urls, state.clone()).await?;
let state = state.read().await;
if let Some(signature) = state.signatures.get(idx) {
match coconut_interface::prove_credential(
state.params()?,
&verification_key,
signature,
&state.private_attributes(),
) {
Ok(theta) => Ok(theta),
Err(e) => Err(format!("{}", e)),
}
} else {
Err("Got invalid Signature idx".to_string())
}
}
async fn get_aggregated_verification_key(
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<VerificationKey, String> {
if let Some(verification_key) = &state.read().await.aggregated_verification_key {
return Ok(verification_key.clone());
}
let parsed_urls = parse_url_validators(&validator_urls)?;
let key = obtain_aggregate_verification_key(&parsed_urls)
.await
.map_err(|err| format!("failed to obtain aggregate verification key - {:?}", err))?;
state
.write()
.await
.aggregated_verification_key
.replace(key.clone());
Ok(key)
}
fn parse_url_validators(raw: &[String]) -> Result<Vec<Url>, String> {
let mut parsed_urls = Vec::with_capacity(raw.len());
for url in raw {
let parsed_url: Url = url
.parse()
.map_err(|err| format!("one of validator urls is malformed - {}", err))?;
parsed_urls.push(parsed_url)
}
Ok(parsed_urls)
}
#[tauri::command]
pub async fn get_credential(
validator_urls: Vec<String>,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Vec<Signature>, String> {
let guard = state.read().await;
let parsed_urls = parse_url_validators(&validator_urls)?;
let signature = obtain_aggregate_signature(
guard.params()?,
&guard.public_attributes(),
&guard.private_attributes(),
&parsed_urls,
)
.await
.map_err(|err| format!("failed to obtain aggregate signature - {:?}", err))?;
let mut state = state.write().await;
state.signatures.push(signature);
Ok(state.signatures.clone())
}
+6 -7
View File
@@ -1,9 +1,8 @@
// This should be moved out of the wallet, and used as a primary coin type throughout the codebase
use ::config::defaults::DENOM;
use cosmos_sdk::Coin as CosmosCoin;
use cosmos_sdk::Decimal;
use cosmos_sdk::Denom as CosmosDenom;
use cosmrs::Decimal;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use cosmwasm_std::Uint128;
use serde::{Deserialize, Serialize};
@@ -12,7 +11,7 @@ use std::fmt;
use std::ops::{Add, Sub};
use std::str::FromStr;
use ts_rs::TS;
use validator_client::nymd::GasPrice;
use validator_client::nymd::{GasPrice, CosmosCoin};
use crate::format_err;
@@ -214,9 +213,9 @@ impl From<CosmWasmCoin> for Coin {
#[cfg(test)]
mod test {
use crate::{Coin, Denom};
use cosmos_sdk::Coin as CosmosCoin;
use cosmos_sdk::Decimal;
use cosmos_sdk::Denom as CosmosDenom;
use cosmrs::Coin as CosmosCoin;
use cosmrs::Decimal;
use cosmrs::Denom as CosmosDenom;
use cosmwasm_std::Coin as CosmWasmCoin;
use serde_json::json;
use std::convert::{TryFrom, TryInto};
+1 -12
View File
@@ -4,8 +4,6 @@
)]
use bip39::{Language, Mnemonic};
use cosmos_sdk::AccountId;
use cosmos_sdk::Coin as CosmosCoin;
use cosmwasm_std::Coin as CosmWasmCoin;
use error::BackendError;
use mixnet_contract::{Gateway, MixNode};
@@ -18,17 +16,13 @@ use tendermint_rpc::endpoint::broadcast::tx_commit::Response;
use tokio::sync::RwLock;
use ts_rs::{export, TS};
use validator_client::nymd::fee_helpers::Operation;
use validator_client::nymd::{NymdClient, SigningNymdClient};
use validator_client::nymd::{NymdClient, SigningNymdClient, AccountId, CosmosCoin};
mod coconut;
mod coin;
mod config;
mod error;
mod state;
use crate::coconut::{
delete_credential, get_credential, list_credentials, randomise_credential, verify_credential,
};
use crate::state::State;
use crate::coin::{Coin, Denom};
@@ -400,11 +394,6 @@ fn main() {
delegate_to_gateway,
undelegate_from_gateway,
send,
get_credential,
randomise_credential,
delete_credential,
list_credentials,
verify_credential,
create_new_account,
get_fee
])
-28
View File
@@ -1,19 +1,10 @@
use crate::config::Config;
use crate::format_err;
use coconut_interface::{self, Attribute, Parameters, Signature, VerificationKey};
use validator_client::nymd::{NymdClient, SigningNymdClient};
#[derive(Default)]
pub struct State {
config: Config,
signing_client: Option<NymdClient<SigningNymdClient>>,
// Coconut stuff
pub signatures: Vec<Signature>,
n_attributes: u32,
params: Option<Parameters>,
public_attributes: Vec<Attribute>,
private_attributes: Vec<Attribute>,
pub aggregated_verification_key: Option<VerificationKey>,
}
impl State {
@@ -30,23 +21,4 @@ impl State {
pub fn set_client(&mut self, signing_client: NymdClient<SigningNymdClient>) {
self.signing_client = Some(signing_client)
}
pub fn params(&self) -> Result<&Parameters, String> {
self
.params
.as_ref()
.ok_or_else(|| format_err!("Parameters are not set!"))
}
pub fn private_attributes(&self) -> Vec<Attribute> {
self.private_attributes.clone()
}
pub fn public_attributes(&self) -> Vec<Attribute> {
self.public_attributes.clone()
}
pub fn n_attributes(&self) -> u32 {
self.n_attributes
}
}
@@ -0,0 +1,7 @@
-- keeping track of all monitor runs that have happened will help to
-- solve an issue of mixnode being online only for a single check and yet being assigned 100% uptime
CREATE TABLE monitor_run
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
timestamp INTEGER NOT NULL
)
+44 -25
View File
@@ -7,7 +7,7 @@ extern crate rocket;
use crate::cache::ValidatorCacheRefresher;
use crate::config::Config;
use crate::network_monitor::tested_network::good_topology::parse_topology_file;
use crate::network_monitor::{new_monitor_runnables, NetworkMonitorRunnables};
use crate::network_monitor::NetworkMonitorBuilder;
use crate::nymd_client::Client;
use crate::storage::NodeStatusStorage;
use ::config::{defaults::DEFAULT_VALIDATOR_API_PORT, NymConfig};
@@ -16,10 +16,13 @@ use cache::ValidatorCache;
use clap::{App, Arg, ArgMatches};
use coconut::InternalSignRequest;
use log::info;
use rocket::fairing::AdHoc;
use rocket::http::Method;
use rocket::{Ignite, Rocket};
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
use std::process;
use std::sync::Arc;
use tokio::sync::Notify;
use url::Url;
pub(crate) mod cache;
@@ -79,6 +82,12 @@ fn parse_args<'a>() -> ArgMatches<'a> {
.long(NYMD_VALIDATOR_ARG)
.takes_value(true)
)
.arg(
Arg::with_name(API_VALIDATORS_ARG)
.help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered")
.long(API_VALIDATORS_ARG)
.takes_value(true)
)
.arg(Arg::with_name(MIXNET_CONTRACT_ARG)
.long(MIXNET_CONTRACT_ARG)
.help("Address of the validator contract managing the network")
@@ -146,7 +155,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config {
config = config.with_v4_good_topology(v4_topology_path)
}
if let Some(v6_topology_path) = matches.value_of(V4_TOPOLOGY_ARG) {
if let Some(v6_topology_path) = matches.value_of(V6_TOPOLOGY_ARG) {
config = config.with_v6_good_topology(v6_topology_path)
}
@@ -214,10 +223,16 @@ fn setup_cors() -> Result<Cors> {
Ok(cors)
}
async fn setup_network_monitor(
config: &Config,
fn setup_liftoff_notify(notify: Arc<Notify>) -> AdHoc {
AdHoc::on_liftoff("Liftoff notifier", |_| {
Box::pin(async move { notify.notify_one() })
})
}
fn setup_network_monitor<'a>(
config: &'a Config,
rocket: &Rocket<Ignite>,
) -> Option<NetworkMonitorRunnables> {
) -> Option<NetworkMonitorBuilder<'a>> {
if !config.get_network_monitor_enabled() {
return None;
}
@@ -230,19 +245,16 @@ async fn setup_network_monitor(
let v6_topology = parse_topology_file(config.get_v6_good_topology_file());
network_monitor::check_if_up_to_date(&v4_topology, &v6_topology);
Some(
new_monitor_runnables(
config,
v4_topology,
v6_topology,
node_status_storage,
validator_cache,
)
.await,
)
Some(NetworkMonitorBuilder::new(
config,
v4_topology,
v6_topology,
node_status_storage,
validator_cache,
))
}
async fn setup_rocket(config: &Config) -> Result<Rocket<Ignite>> {
async fn setup_rocket(config: &Config, liftoff_notify: Arc<Notify>) -> Result<Rocket<Ignite>> {
// let's build our rocket!
let rocket_config = rocket::config::Config {
// TODO: probably the port should be configurable?
@@ -251,6 +263,7 @@ async fn setup_rocket(config: &Config) -> Result<Rocket<Ignite>> {
};
let rocket = rocket::custom(rocket_config)
.attach(setup_cors()?)
.attach(setup_liftoff_notify(liftoff_notify))
.attach(ValidatorCache::stage())
.attach(InternalSignRequest::stage(config.keypair()));
@@ -290,10 +303,11 @@ async fn main() -> Result<()> {
let matches = parse_args();
let config = override_config(config, &matches);
let liftoff_notify = Arc::new(Notify::new());
// let's build our rocket!
let rocket = setup_rocket(&config).await?;
let monitor_runnables = setup_network_monitor(&config, &rocket).await;
let rocket = setup_rocket(&config, Arc::clone(&liftoff_notify)).await?;
let monitor_builder = setup_network_monitor(&config, &rocket);
let validator_cache = rocket.state::<ValidatorCache>().unwrap().clone();
@@ -321,19 +335,24 @@ async fn main() -> Result<()> {
tokio::spawn(async move { validator_cache_refresher.run().await });
}
if let Some(runnables) = monitor_runnables {
// launch the rocket!
let shutdown_handle = rocket.shutdown();
tokio::spawn(rocket.launch());
// to finish building our monitor, we need to have rocket up and running so that we could
// obtain our bandwidth credential
if let Some(monitor_builder) = monitor_builder {
info!("Starting network monitor...");
// spawn network monitor!
// wait for rocket's liftoff stage
liftoff_notify.notified().await;
// we're ready to go! spawn the network monitor!
let runnables = monitor_builder.build().await;
runnables.spawn_tasks();
} else {
info!("Network monitoring is disabled.");
}
// and launch the rocket
let shutdown_handle = rocket.shutdown();
tokio::spawn(rocket.launch());
wait_for_interrupt().await;
shutdown_handle.notify();
+97 -79
View File
@@ -31,6 +31,103 @@ pub(crate) mod monitor;
pub(crate) mod test_packet;
pub(crate) mod tested_network;
pub(crate) struct NetworkMonitorBuilder<'a> {
config: &'a Config,
tested_network: TestedNetwork,
node_status_storage: NodeStatusStorage,
validator_cache: ValidatorCache,
}
impl<'a> NetworkMonitorBuilder<'a> {
pub(crate) fn new(
config: &'a Config,
v4_topology: NymTopology,
v6_topology: NymTopology,
node_status_storage: NodeStatusStorage,
validator_cache: ValidatorCache,
) -> Self {
let tested_network = TestedNetwork::new_good(v4_topology, v6_topology);
NetworkMonitorBuilder {
config,
tested_network,
node_status_storage,
validator_cache,
}
}
pub(crate) async fn build(self) -> NetworkMonitorRunnables {
// TODO: in the future I guess this should somehow change to distribute the load
let tested_mix_gateway = self.tested_network.main_v4_gateway().clone();
info!(
"* gateway for testing mixnodes: {}",
tested_mix_gateway.identity_key.to_base58_string()
);
// TODO: those keys change constant throughout the whole execution of the monitor.
// and on top of that, they are used with ALL the gateways -> presumably this should change
// in the future
let mut rng = rand::rngs::OsRng;
let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng));
let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng));
let test_mixnode_sender = Recipient::new(
*identity_keypair.public_key(),
*encryption_keypair.public_key(),
tested_mix_gateway.identity_key,
);
let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded();
let (received_processor_sender_channel, received_processor_receiver_channel) =
mpsc::unbounded();
let packet_preparer = new_packet_preparer(
self.validator_cache,
self.tested_network.clone(),
test_mixnode_sender,
*identity_keypair.public_key(),
*encryption_keypair.public_key(),
);
let bandwidth_credential =
TEMPORARY_obtain_bandwidth_credential(self.config, identity_keypair.public_key()).await;
let packet_sender = new_packet_sender(
self.config,
gateway_status_update_sender,
Arc::clone(&identity_keypair),
bandwidth_credential,
self.config.get_gateway_sending_rate(),
);
let received_processor = new_received_processor(
received_processor_receiver_channel,
Arc::clone(&encryption_keypair),
);
let summary_producer = new_summary_producer(self.config.get_detailed_report());
let packet_receiver = new_packet_receiver(
gateway_status_update_receiver,
received_processor_sender_channel,
);
let monitor = monitor::Monitor::new(
self.config,
packet_preparer,
packet_sender,
received_processor,
summary_producer,
self.node_status_storage,
self.tested_network,
);
NetworkMonitorRunnables {
monitor,
packet_receiver,
}
}
}
pub(crate) struct NetworkMonitorRunnables {
monitor: Monitor,
packet_receiver: PacketReceiver,
@@ -48,85 +145,6 @@ impl NetworkMonitorRunnables {
}
}
pub(crate) async fn new_monitor_runnables(
config: &Config,
v4_topology: NymTopology,
v6_topology: NymTopology,
node_status_storage: NodeStatusStorage,
validator_cache: ValidatorCache,
) -> NetworkMonitorRunnables {
// TODO: in the future I guess this should somehow change to distribute the load
let tested_mix_gateway = v4_topology.gateways()[0].clone();
info!(
"* gateway for testing mixnodes: {}",
tested_mix_gateway.identity_key.to_base58_string()
);
let tested_network = TestedNetwork::new_good(v4_topology, v6_topology);
// TODO: those keys change constant throughout the whole execution of the monitor.
// and on top of that, they are used with ALL the gateways -> presumably this should change
// in the future
let mut rng = rand::rngs::OsRng;
let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng));
let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng));
let test_mixnode_sender = Recipient::new(
*identity_keypair.public_key(),
*encryption_keypair.public_key(),
tested_mix_gateway.identity_key,
);
let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded();
let (received_processor_sender_channel, received_processor_receiver_channel) =
mpsc::unbounded();
let packet_preparer = new_packet_preparer(
validator_cache,
tested_network.clone(),
test_mixnode_sender,
*identity_keypair.public_key(),
*encryption_keypair.public_key(),
);
let bandwidth_credential =
TEMPORARY_obtain_bandwidth_credential(config, identity_keypair.public_key()).await;
let packet_sender = new_packet_sender(
config,
gateway_status_update_sender,
Arc::clone(&identity_keypair),
bandwidth_credential,
config.get_gateway_sending_rate(),
);
let received_processor = new_received_processor(
received_processor_receiver_channel,
Arc::clone(&encryption_keypair),
);
let summary_producer = new_summary_producer(config.get_detailed_report());
let packet_receiver = new_packet_receiver(
gateway_status_update_receiver,
received_processor_sender_channel,
);
let monitor = monitor::Monitor::new(
config,
packet_preparer,
packet_sender,
received_processor,
summary_producer,
node_status_storage,
tested_network,
);
NetworkMonitorRunnables {
monitor,
packet_receiver,
}
}
fn new_packet_preparer(
validator_cache: ValidatorCache,
tested_network: TestedNetwork,
@@ -77,6 +77,18 @@ impl Monitor {
// TODO: slightly more graceful shutdown here
process::exit(1);
}
// indicate our run has completed successfully and should be used in any future
// uptime calculations
if let Err(err) = self.node_status_storage.insert_monitor_run().await {
error!(
"Failed to submit monitor run information to the database - {}",
err
);
// TODO: slightly more graceful shutdown here
process::exit(1);
}
}
// checking it this way with a TestReport is rather suboptimal but given the fact we're only
+21 -4
View File
@@ -7,6 +7,7 @@ use rocket::http::{ContentType, Status};
use rocket::response::{self, Responder, Response};
use rocket::Request;
use serde::{Deserialize, Serialize};
use sqlx::types::time::OffsetDateTime;
use std::convert::TryFrom;
use std::fmt::{self, Display, Formatter};
use std::io::Cursor;
@@ -90,13 +91,21 @@ pub struct MixnodeStatusReport {
impl MixnodeStatusReport {
pub(crate) fn construct_from_last_day_reports(
report_time: OffsetDateTime,
identity: String,
owner: String,
last_day_ipv4: Vec<NodeStatus>,
last_day_ipv6: Vec<NodeStatus>,
last_hour_test_runs: usize,
last_day_test_runs: usize,
) -> Self {
let node_uptimes =
NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6);
let node_uptimes = NodeUptimes::calculate_from_last_day_reports(
report_time,
last_day_ipv4,
last_day_ipv6,
last_hour_test_runs,
last_day_test_runs,
);
MixnodeStatusReport {
identity,
@@ -128,13 +137,21 @@ pub struct GatewayStatusReport {
impl GatewayStatusReport {
pub(crate) fn construct_from_last_day_reports(
report_time: OffsetDateTime,
identity: String,
owner: String,
last_day_ipv4: Vec<NodeStatus>,
last_day_ipv6: Vec<NodeStatus>,
last_hour_test_runs: usize,
last_day_test_runs: usize,
) -> Self {
let node_uptimes =
NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6);
let node_uptimes = NodeUptimes::calculate_from_last_day_reports(
report_time,
last_day_ipv4,
last_day_ipv6,
last_hour_test_runs,
last_day_test_runs,
);
GatewayStatusReport {
identity,
+44 -25
View File
@@ -4,7 +4,9 @@
use crate::node_status_api::models::Uptime;
use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR};
use crate::storage::models::NodeStatus;
use log::warn;
use sqlx::types::time::OffsetDateTime;
use std::cmp::min;
// A temporary helper struct used to produce reports for active nodes.
pub(crate) struct ActiveNodeDayStatuses {
@@ -30,33 +32,23 @@ pub(crate) struct NodeUptimes {
impl NodeUptimes {
pub(crate) fn calculate_from_last_day_reports(
report_time: OffsetDateTime,
last_day_ipv4: Vec<NodeStatus>,
last_day_ipv6: Vec<NodeStatus>,
last_hour_test_runs: usize,
last_day_test_runs: usize,
) -> Self {
let now = OffsetDateTime::now_utc();
let hour_ago = (now - ONE_HOUR).unix_timestamp();
let fifteen_minutes_ago = (now - FIFTEEN_MINUTES).unix_timestamp();
let hour_ago = (report_time - ONE_HOUR).unix_timestamp();
let fifteen_minutes_ago = (report_time - FIFTEEN_MINUTES).unix_timestamp();
let ipv4_day_total = last_day_ipv4.len();
let ipv6_day_total = last_day_ipv6.len();
let mut ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count();
let mut ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count();
let ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count();
let ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count();
let ipv4_hour_total = last_day_ipv4
.iter()
.filter(|report| report.timestamp >= hour_ago)
.count();
let ipv6_hour_total = last_day_ipv6
.iter()
.filter(|report| report.timestamp >= hour_ago)
.count();
let ipv4_hour_up = last_day_ipv4
let mut ipv4_hour_up = last_day_ipv4
.iter()
.filter(|report| report.up && report.timestamp >= hour_ago)
.count();
let ipv6_hour_up = last_day_ipv6
let mut ipv6_hour_up = last_day_ipv6
.iter()
.filter(|report| report.up && report.timestamp >= hour_ago)
.count();
@@ -73,15 +65,42 @@ impl NodeUptimes {
.map(|status| status.timestamp >= fifteen_minutes_ago && status.up) // make sure its within last 15min
.unwrap_or_default();
// the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results than all results in total
// because both of those values originate from the same vector
// If somehow we have more "up" reports than the actual test runs it means something weird is going on
// (or we just started running this code on old data, so if it appears for first 24h, it's fine and actually expected
// as we would not have any run information from the past)
// Either way, bound the the number of "up" reports by number of test runs and log warnings
// if that happens
if ipv4_hour_up > last_hour_test_runs || ipv6_hour_up > last_hour_test_runs {
warn!(
"We have more 'up' reports than the actual number of test runs in last hour! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)",
ipv4_hour_up,
ipv6_hour_up,
last_hour_test_runs,
);
ipv4_hour_up = min(ipv4_hour_up, last_hour_test_runs);
ipv6_hour_up = min(ipv6_hour_up, last_hour_test_runs);
}
if ipv4_day_up > last_day_test_runs || ipv6_day_up > last_day_test_runs {
warn!(
"We have more 'up' reports than the actual number of test runs in last day! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)",
ipv4_day_up,
ipv6_day_up,
last_day_test_runs,
);
ipv4_day_up = min(ipv4_day_up, last_day_test_runs);
ipv6_day_up = min(ipv6_day_up, last_day_test_runs);
}
// the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results
// than total test runs as we just bounded them
NodeUptimes {
most_recent_ipv4,
most_recent_ipv6,
last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, ipv4_hour_total).unwrap(),
last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, ipv6_hour_total).unwrap(),
last_day_ipv4: Uptime::from_ratio(ipv4_day_up, ipv4_day_total).unwrap(),
last_day_ipv6: Uptime::from_ratio(ipv6_day_up, ipv6_day_total).unwrap(),
last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, last_hour_test_runs).unwrap(),
last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, last_hour_test_runs).unwrap(),
last_day_ipv4: Uptime::from_ratio(ipv4_day_up, last_day_test_runs).unwrap(),
last_day_ipv6: Uptime::from_ratio(ipv6_day_up, last_day_test_runs).unwrap(),
}
}
}
+45 -14
View File
@@ -4,10 +4,8 @@
use crate::network_monitor::monitor::summary_producer::NodeResult;
use crate::node_status_api::models::{HistoricalUptime, Uptime};
use crate::node_status_api::utils::ActiveNodeDayStatuses;
use crate::node_status_api::ONE_DAY;
use crate::storage::models::{ActiveNode, NodeStatus};
use crate::storage::UnixTimestamp;
use sqlx::types::time::OffsetDateTime;
use std::convert::TryFrom;
#[derive(Clone)]
@@ -463,6 +461,43 @@ impl StorageManager {
Ok(())
}
/// Creates a database entry for a finished network monitor test run.
///
/// # Arguments
///
/// * `timestamp`: unix timestamp at which the monitor test run has occurred
pub(crate) async fn insert_monitor_run(
&self,
timestamp: UnixTimestamp,
) -> Result<(), sqlx::Error> {
sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Obtains number of network monitor test runs that have occurred within the specified interval.
///
/// # Arguments
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_monitor_runs_count(
&self,
since: UnixTimestamp,
until: UnixTimestamp,
) -> Result<i32, sqlx::Error> {
let count = sqlx::query!(
"SELECT COUNT(*) as count FROM monitor_run WHERE timestamp > ? AND timestamp < ?",
since,
until,
)
.fetch_one(&self.connection_pool)
.await?
.count;
Ok(count)
}
pub(crate) async fn purge_old_mixnode_ipv4_statuses(
&self,
timestamp: UnixTimestamp,
@@ -579,19 +614,17 @@ impl StorageManager {
// since technically it doesn't touch any SQL directly
pub(crate) async fn get_all_active_mixnodes_statuses(
&self,
since: UnixTimestamp,
) -> Result<Vec<ActiveNodeDayStatuses>, sqlx::Error> {
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let active_nodes = self.get_all_active_mixnodes(day_ago).await?;
let active_nodes = self.get_all_active_mixnodes(since).await?;
let mut active_day_statuses = Vec::with_capacity(active_nodes.len());
for active_node in active_nodes.into_iter() {
let ipv4_statuses = self
.get_mixnode_ipv4_statuses_since_by_id(active_node.id, day_ago)
.get_mixnode_ipv4_statuses_since_by_id(active_node.id, since)
.await?;
let ipv6_statuses = self
.get_mixnode_ipv6_statuses_since_by_id(active_node.id, day_ago)
.get_mixnode_ipv6_statuses_since_by_id(active_node.id, since)
.await?;
let statuses = ActiveNodeDayStatuses {
@@ -614,19 +647,17 @@ impl StorageManager {
// since technically it doesn't touch any SQL directly
pub(crate) async fn get_all_active_gateways_statuses(
&self,
since: UnixTimestamp,
) -> Result<Vec<ActiveNodeDayStatuses>, sqlx::Error> {
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let active_nodes = self.get_all_active_gateways(day_ago).await?;
let active_nodes = self.get_all_active_gateways(since).await?;
let mut active_day_statuses = Vec::with_capacity(active_nodes.len());
for active_node in active_nodes.into_iter() {
let ipv4_statuses = self
.get_gateway_ipv4_statuses_since_by_id(active_node.id, day_ago)
.get_gateway_ipv4_statuses_since_by_id(active_node.id, since)
.await?;
let ipv6_statuses = self
.get_gateway_ipv6_statuses_since_by_id(active_node.id, day_ago)
.get_gateway_ipv6_statuses_since_by_id(active_node.id, since)
.await?;
let statuses = ActiveNodeDayStatuses {
+131 -21
View File
@@ -6,7 +6,7 @@ use crate::node_status_api::models::{
GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory,
NodeStatusApiError, Uptime,
};
use crate::node_status_api::ONE_DAY;
use crate::node_status_api::{ONE_DAY, ONE_HOUR};
use crate::storage::manager::StorageManager;
use crate::storage::models::NodeStatus;
use rocket::fairing::{self, AdHoc};
@@ -67,46 +67,58 @@ impl NodeStatusStorage {
})
}
/// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted in last 24h.
async fn get_mixnode_daily_statuses(
/// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted
/// since the provided timestamp.
///
/// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses.
///
/// # Arguments
///
/// * `identity`: identity key of the mixnode to query.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
async fn get_mixnode_statuses(
&self,
identity: &str,
since: UnixTimestamp,
) -> Result<(Vec<NodeStatus>, Vec<NodeStatus>), NodeStatusApiError> {
let now = OffsetDateTime::now_utc();
let day_ago = now - ONE_DAY;
let ipv4_statuses = self
.manager
.get_mixnode_ipv4_statuses_since(identity, day_ago.unix_timestamp())
.get_mixnode_ipv4_statuses_since(identity, since)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
let ipv6_statuses = self
.manager
.get_mixnode_ipv6_statuses_since(identity, day_ago.unix_timestamp())
.get_mixnode_ipv6_statuses_since(identity, since)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
Ok((ipv4_statuses, ipv6_statuses))
}
/// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted in last 24h.
async fn get_gateway_daily_statuses(
/// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted
/// since the provided timestamp.
///
/// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses.
///
/// # Arguments
///
/// * `identity`: identity key of the gateway to query.
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
async fn get_gateway_statuses(
&self,
identity: &str,
since: UnixTimestamp,
) -> Result<(Vec<NodeStatus>, Vec<NodeStatus>), NodeStatusApiError> {
let now = OffsetDateTime::now_utc();
let day_ago = now - ONE_DAY;
let ipv4_statuses = self
.manager
.get_gateway_ipv4_statuses_since(identity, day_ago.unix_timestamp())
.get_gateway_ipv4_statuses_since(identity, since)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
let ipv6_statuses = self
.manager
.get_gateway_ipv6_statuses_since(identity, day_ago.unix_timestamp())
.get_gateway_ipv6_statuses_since(identity, since)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
@@ -118,7 +130,11 @@ impl NodeStatusStorage {
&self,
identity: &str,
) -> Result<MixnodeStatusReport, NodeStatusApiError> {
let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_daily_statuses(identity).await?;
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let hour_ago = (now - ONE_HOUR).unix_timestamp();
let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_statuses(identity, day_ago).await?;
// if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report
if ipv4_statuses.is_empty() {
@@ -127,6 +143,14 @@ impl NodeStatusStorage {
));
}
// determine the number of runs the mixnode should have been online for
let last_hour_runs_count = self
.get_monitor_runs_count(hour_ago, now.unix_timestamp())
.await?;
let last_day_runs_count = self
.get_monitor_runs_count(day_ago, now.unix_timestamp())
.await?;
// now, technically this is not a critical error, but this should have NEVER happened in the first place
// so something super weird is going on
if ipv4_statuses.len() != ipv6_statuses.len() {
@@ -145,10 +169,13 @@ impl NodeStatusStorage {
.expect("The node doesn't have an owner even though we have status information on it!");
Ok(MixnodeStatusReport::construct_from_last_day_reports(
now,
identity.to_owned(),
mixnode_owner,
ipv4_statuses,
ipv6_statuses,
last_hour_runs_count,
last_day_runs_count,
))
}
@@ -156,7 +183,11 @@ impl NodeStatusStorage {
&self,
identity: &str,
) -> Result<GatewayStatusReport, NodeStatusApiError> {
let (ipv4_statuses, ipv6_statuses) = self.get_gateway_daily_statuses(identity).await?;
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let hour_ago = (now - ONE_HOUR).unix_timestamp();
let (ipv4_statuses, ipv6_statuses) = self.get_gateway_statuses(identity, day_ago).await?;
// if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report
if ipv4_statuses.is_empty() {
@@ -165,6 +196,14 @@ impl NodeStatusStorage {
));
}
// determine the number of runs the gateway should have been online for
let last_hour_runs_count = self
.get_monitor_runs_count(hour_ago, now.unix_timestamp())
.await?;
let last_day_runs_count = self
.get_monitor_runs_count(day_ago, now.unix_timestamp())
.await?;
// now, technically this is not a critical error, but this should have NEVER happened in the first place
// so something super weird is going on
if ipv4_statuses.len() != ipv6_statuses.len() {
@@ -185,10 +224,13 @@ impl NodeStatusStorage {
);
Ok(GatewayStatusReport::construct_from_last_day_reports(
now,
identity.to_owned(),
gateway_owner,
ipv4_statuses,
ipv6_statuses,
last_hour_runs_count,
last_day_runs_count,
))
}
@@ -257,18 +299,33 @@ impl NodeStatusStorage {
pub(crate) async fn get_all_mixnode_reports(
&self,
) -> Result<Vec<MixnodeStatusReport>, NodeStatusApiError> {
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let hour_ago = (now - ONE_HOUR).unix_timestamp();
// determine the number of runs the mixnodes should have been online for
let last_hour_runs_count = self
.get_monitor_runs_count(hour_ago, now.unix_timestamp())
.await?;
let last_day_runs_count = self
.get_monitor_runs_count(day_ago, now.unix_timestamp())
.await?;
let reports = self
.manager
.get_all_active_mixnodes_statuses()
.get_all_active_mixnodes_statuses(day_ago)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?
.into_iter()
.map(|statuses| {
MixnodeStatusReport::construct_from_last_day_reports(
now,
statuses.identity,
statuses.owner,
statuses.ipv4_statuses,
statuses.ipv6_statuses,
last_hour_runs_count,
last_day_runs_count,
)
})
.collect();
@@ -281,18 +338,33 @@ impl NodeStatusStorage {
pub(crate) async fn get_all_gateway_reports(
&self,
) -> Result<Vec<GatewayStatusReport>, NodeStatusApiError> {
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
let hour_ago = (now - ONE_HOUR).unix_timestamp();
// determine the number of runs the gateways should have been online for
let last_hour_runs_count = self
.get_monitor_runs_count(hour_ago, now.unix_timestamp())
.await?;
let last_day_runs_count = self
.get_monitor_runs_count(day_ago, now.unix_timestamp())
.await?;
let reports = self
.manager
.get_all_active_gateways_statuses()
.get_all_active_gateways_statuses(day_ago)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?
.into_iter()
.map(|statuses| {
GatewayStatusReport::construct_from_last_day_reports(
now,
statuses.identity,
statuses.owner,
statuses.ipv4_statuses,
statuses.ipv6_statuses,
last_hour_runs_count,
last_day_runs_count,
)
})
.collect();
@@ -321,15 +393,53 @@ impl NodeStatusStorage {
.map_err(|_| NodeStatusApiError::InternalDatabaseError)
}
/// Inserts an entry to the database with the network monitor test run information
/// that has occurred at this instant.
pub(crate) async fn insert_monitor_run(&self) -> Result<(), NodeStatusApiError> {
let now = OffsetDateTime::now_utc().unix_timestamp();
self.manager
.insert_monitor_run(now)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)
}
/// Obtains number of network monitor test runs that have occurred within the specified interval.
///
/// # Arguments
///
/// * `since`: unix timestamp indicating the lower bound interval of the selection.
/// * `until`: unix timestamp indicating the upper bound interval of the selection.
pub(crate) async fn get_monitor_runs_count(
&self,
since: UnixTimestamp,
until: UnixTimestamp,
) -> Result<usize, NodeStatusApiError> {
let run_count = self
.manager
.get_monitor_runs_count(since, until)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
if run_count < 0 {
// I don't think it's ever possible for SQL to return a negative value from COUNT?
return Err(NodeStatusApiError::InternalDatabaseError);
}
Ok(run_count as usize)
}
// Called on timer/reward script
async fn update_historical_uptimes(
&self,
today_iso_8601: &str,
) -> Result<(), NodeStatusApiError> {
let now = OffsetDateTime::now_utc();
let day_ago = (now - ONE_DAY).unix_timestamp();
// get statuses for all active mixnodes...
let active_mixnodes_statuses = self
.manager
.get_all_active_mixnodes_statuses()
.get_all_active_mixnodes_statuses(day_ago)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
@@ -368,7 +478,7 @@ impl NodeStatusStorage {
// get statuses for all active gateways...
let active_gateways_statuses = self
.manager
.get_all_active_gateways_statuses()
.get_all_active_gateways_statuses(day_ago)
.await
.map_err(|_| NodeStatusApiError::InternalDatabaseError)?;
+30
View File
@@ -0,0 +1,30 @@
import { IconButton } from '@material-ui/core'
import { Close } from '@material-ui/icons'
import { Alert, AlertProps, AlertTitle } from '@material-ui/lab'
import React, { useState } from 'react'
export const AppAlert = ({
message,
severity = 'info',
title,
}: {
message: string
severity?: AlertProps['severity']
title?: string
}) => {
const [showAlert, setShowAlert] = useState(true)
return showAlert ? (
<Alert
severity={severity}
action={
<IconButton size="small" onClick={() => setShowAlert(false)}>
<Close />
</IconButton>
}
>
<AlertTitle>{title}</AlertTitle>
{message}
</Alert>
) : null
}
+188 -207
View File
@@ -11,10 +11,10 @@
"@material-ui/core": "^4.11.3",
"@material-ui/icons": "^4.11.2",
"@material-ui/lab": "^4.0.0-alpha.57",
"@nymproject/nym-validator-client": "0.16.0",
"@nymproject/nym-validator-client": "0.17.0",
"@types/react-dom": "^17.0.3",
"bs58": "^4.0.1",
"next": "11.1.0",
"next": "11.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"semver": "^7.3.5"
@@ -1785,11 +1785,14 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"dependencies": {
"regenerator-runtime": "^0.13.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
@@ -1906,14 +1909,6 @@
"@cosmjs/utils": "^0.25.5"
}
},
"node_modules/@cosmjs/cosmwasm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz",
"integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==",
"dependencies": {
"@cosmjs/cosmwasm-launchpad": "^0.25.5"
}
},
"node_modules/@cosmjs/cosmwasm-launchpad": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz",
@@ -1996,33 +1991,6 @@
"fast-deep-equal": "^3.1.3"
}
},
"node_modules/@cosmjs/launchpad/node_modules/axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"dependencies": {
"follow-redirects": "^1.10.0"
}
},
"node_modules/@cosmjs/launchpad/node_modules/follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/@cosmjs/math": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.25.5.tgz",
@@ -2093,33 +2061,6 @@
"xstream": "^11.14.0"
}
},
"node_modules/@cosmjs/tendermint-rpc/node_modules/axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"dependencies": {
"follow-redirects": "^1.10.0"
}
},
"node_modules/@cosmjs/tendermint-rpc/node_modules/follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/@cosmjs/utils": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.25.5.tgz",
@@ -3011,19 +2952,19 @@
"integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA=="
},
"node_modules/@next/env": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz",
"integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz",
"integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag=="
},
"node_modules/@next/polyfill-module": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz",
"integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz",
"integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg=="
},
"node_modules/@next/react-dev-overlay": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz",
"integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz",
"integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==",
"dependencies": {
"@babel/code-frame": "7.12.11",
"anser": "1.4.9",
@@ -3107,9 +3048,9 @@
}
},
"node_modules/@next/react-refresh-utils": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz",
"integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz",
"integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==",
"peerDependencies": {
"react-refresh": "0.8.3",
"webpack": "^4 || ^5"
@@ -3120,6 +3061,66 @@
}
}
},
"node_modules/@next/swc-darwin-arm64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz",
"integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz",
"integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz",
"integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz",
"integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@node-rs/helper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz",
@@ -3129,17 +3130,15 @@
}
},
"node_modules/@nymproject/nym-validator-client": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz",
"integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz",
"integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==",
"dependencies": {
"@cosmjs/cosmwasm": "^0.25.5",
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/crypto": "^0.25.5",
"@cosmjs/launchpad": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5",
"axios": "^0.19.2"
"@cosmjs/stargate": "^0.25.5",
"axios": "^0.21.1"
}
},
"node_modules/@protobufjs/aspromise": {
@@ -3746,12 +3745,11 @@
}
},
"node_modules/axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"dependencies": {
"follow-redirects": "1.5.10"
"follow-redirects": "^1.10.0"
}
},
"node_modules/babel-jest": {
@@ -4696,14 +4694,6 @@
"node": ">=10"
}
},
"node_modules/debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/decimal.js": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
@@ -5176,14 +5166,22 @@
}
},
"node_modules/follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"dependencies": {
"debug": "=3.1.0"
},
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreach": {
@@ -8319,16 +8317,16 @@
"dev": true
},
"node_modules/next": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz",
"integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz",
"integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==",
"dependencies": {
"@babel/runtime": "7.12.5",
"@babel/runtime": "7.15.3",
"@hapi/accept": "5.0.2",
"@next/env": "11.1.0",
"@next/polyfill-module": "11.1.0",
"@next/react-dev-overlay": "11.1.0",
"@next/react-refresh-utils": "11.1.0",
"@next/env": "11.1.1",
"@next/polyfill-module": "11.1.1",
"@next/react-dev-overlay": "11.1.1",
"@next/react-refresh-utils": "11.1.1",
"@node-rs/helper": "1.2.1",
"assert": "2.0.0",
"ast-types": "0.13.2",
@@ -8370,7 +8368,7 @@
"timers-browserify": "2.0.12",
"tty-browserify": "0.0.1",
"use-subscription": "1.5.1",
"util": "0.12.3",
"util": "0.12.4",
"vm-browserify": "1.1.2",
"watchpack": "2.1.1"
},
@@ -8380,6 +8378,12 @@
"engines": {
"node": ">=12.0.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "11.1.1",
"@next/swc-darwin-x64": "11.1.1",
"@next/swc-linux-x64-gnu": "11.1.1",
"@next/swc-win32-x64-msvc": "11.1.1"
},
"peerDependencies": {
"fibers": ">= 3.1.0",
"node-sass": "^4.0.0 || ^5.0.0",
@@ -10083,9 +10087,9 @@
}
},
"node_modules/util": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz",
"integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==",
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
@@ -11650,9 +11654,9 @@
}
},
"@babel/runtime": {
"version": "7.12.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz",
"integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==",
"version": "7.15.3",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz",
"integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
@@ -11752,14 +11756,6 @@
"@cosmjs/utils": "^0.25.5"
}
},
"@cosmjs/cosmwasm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz",
"integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==",
"requires": {
"@cosmjs/cosmwasm-launchpad": "^0.25.5"
}
},
"@cosmjs/cosmwasm-launchpad": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz",
@@ -11840,21 +11836,6 @@
"@cosmjs/utils": "^0.25.5",
"axios": "^0.21.1",
"fast-deep-equal": "^3.1.3"
},
"dependencies": {
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
}
}
},
"@cosmjs/math": {
@@ -11925,21 +11906,6 @@
"axios": "^0.21.1",
"readonly-date": "^1.0.0",
"xstream": "^11.14.0"
},
"dependencies": {
"axios": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "^1.10.0"
}
},
"follow-redirects": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz",
"integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA=="
}
}
},
"@cosmjs/utils": {
@@ -12576,19 +12542,19 @@
"integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA=="
},
"@next/env": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz",
"integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz",
"integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag=="
},
"@next/polyfill-module": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz",
"integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ=="
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz",
"integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg=="
},
"@next/react-dev-overlay": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz",
"integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz",
"integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==",
"requires": {
"@babel/code-frame": "7.12.11",
"anser": "1.4.9",
@@ -12649,11 +12615,35 @@
}
},
"@next/react-refresh-utils": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz",
"integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz",
"integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==",
"requires": {}
},
"@next/swc-darwin-arm64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz",
"integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==",
"optional": true
},
"@next/swc-darwin-x64": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz",
"integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==",
"optional": true
},
"@next/swc-linux-x64-gnu": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz",
"integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==",
"optional": true
},
"@next/swc-win32-x64-msvc": {
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz",
"integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==",
"optional": true
},
"@node-rs/helper": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz",
@@ -12663,17 +12653,15 @@
}
},
"@nymproject/nym-validator-client": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz",
"integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==",
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz",
"integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==",
"requires": {
"@cosmjs/cosmwasm": "^0.25.5",
"@cosmjs/cosmwasm-stargate": "^0.25.5",
"@cosmjs/crypto": "^0.25.5",
"@cosmjs/launchpad": "^0.25.5",
"@cosmjs/math": "^0.25.5",
"@cosmjs/proto-signing": "^0.25.5",
"axios": "^0.19.2"
"@cosmjs/stargate": "^0.25.5",
"axios": "^0.21.1"
}
},
"@protobufjs/aspromise": {
@@ -13195,11 +13183,11 @@
}
},
"axios": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
"integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
"requires": {
"follow-redirects": "1.5.10"
"follow-redirects": "^1.10.0"
}
},
"babel-jest": {
@@ -13985,14 +13973,6 @@
}
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"decimal.js": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz",
@@ -14355,12 +14335,9 @@
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz",
"integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA=="
},
"foreach": {
"version": "2.0.5",
@@ -16702,16 +16679,20 @@
"dev": true
},
"next": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz",
"integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==",
"version": "11.1.1",
"resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz",
"integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==",
"requires": {
"@babel/runtime": "7.12.5",
"@babel/runtime": "7.15.3",
"@hapi/accept": "5.0.2",
"@next/env": "11.1.0",
"@next/polyfill-module": "11.1.0",
"@next/react-dev-overlay": "11.1.0",
"@next/react-refresh-utils": "11.1.0",
"@next/env": "11.1.1",
"@next/polyfill-module": "11.1.1",
"@next/react-dev-overlay": "11.1.1",
"@next/react-refresh-utils": "11.1.1",
"@next/swc-darwin-arm64": "11.1.1",
"@next/swc-darwin-x64": "11.1.1",
"@next/swc-linux-x64-gnu": "11.1.1",
"@next/swc-win32-x64-msvc": "11.1.1",
"@node-rs/helper": "1.2.1",
"assert": "2.0.0",
"ast-types": "0.13.2",
@@ -16753,7 +16734,7 @@
"timers-browserify": "2.0.12",
"tty-browserify": "0.0.1",
"use-subscription": "1.5.1",
"util": "0.12.3",
"util": "0.12.4",
"vm-browserify": "1.1.2",
"watchpack": "2.1.1"
},
@@ -18105,9 +18086,9 @@
}
},
"util": {
"version": "0.12.3",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz",
"integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==",
"version": "0.12.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
"integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==",
"requires": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
+1 -1
View File
@@ -18,7 +18,7 @@
"@nymproject/nym-validator-client": "0.17.0",
"@types/react-dom": "^17.0.3",
"bs58": "^4.0.1",
"next": "11.1.0",
"next": "11.1.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"semver": "^7.3.5"
+44 -41
View File
@@ -1,49 +1,52 @@
import React, { useState } from 'react';
import Head from 'next/head';
import { ThemeProvider } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import { theme } from '../lib/theme';
import type { AppProps } from 'next/app';
import { ValidatorClientContext } from "../contexts/ValidatorClient";
import React, { useState } from 'react'
import Head from 'next/head'
import { ThemeProvider } from '@material-ui/core/styles'
import CssBaseline from '@material-ui/core/CssBaseline'
import { theme } from '../lib/theme'
import type { AppProps } from 'next/app'
import { ValidatorClientContext } from '../contexts/ValidatorClient'
// TODO: should it perhaps be pulled from some config or also user provided?
export const BONDING_CONTRACT_ADDRESS: string = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen";
export const BONDING_CONTRACT_ADDRESS: string =
'punk10pyejy66429refv3g35g2t7am0was7yalwrzen'
export const VALIDATOR_URLS: string[] = [
"https://testnet-milhon-validator1.nymtech.net",
"https://testnet-milhon-validator2.nymtech.net",
];
export const ADDRESS_LENGTH: number = 43;
export const ADMIN_ADDRESS: string = "punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0"
export const DENOM: string = "punk"; // used everywhere else
export const KEY_LENGTH: number = 32;
export const UDENOM: string = "upunk"; // required for client and coin construction
'https://testnet-milhon-validator1.nymtech.net',
'https://testnet-milhon-validator2.nymtech.net',
]
export const ADDRESS_LENGTH: number = 43
export const ADMIN_ADDRESS: string =
'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0'
export const DENOM: string = 'punk' // used everywhere else
export const KEY_LENGTH: number = 32
export const UDENOM: string = 'upunk' // required for client and coin construction
export default function Application(props: AppProps) {
const { Component, pageProps } = props;
const { Component, pageProps } = props
const [client, setClient] = useState(null)
const [client, setClient] = useState(null)
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
React.useEffect(() => {
const jssStyles = document.querySelector('#jss-server-side')
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles)
}
}, [])
return (
<React.Fragment>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<title>Nym</title>
</Head>
<ValidatorClientContext.Provider value={{ client, setClient }}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</ValidatorClientContext.Provider>
</React.Fragment>
);
return (
<React.Fragment>
<Head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="minimum-scale=1, initial-scale=1, width=device-width"
/>
<title>Nym</title>
</Head>
<ValidatorClientContext.Provider value={{ client, setClient }}>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</ValidatorClientContext.Provider>
</React.Fragment>
)
}
+53 -58
View File
@@ -821,9 +821,9 @@
"@babel/helper-validator-option" "^7.14.5"
"@babel/plugin-transform-typescript" "^7.14.5"
"@babel/runtime@7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.12.5"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz"
"@babel/runtime@7.15.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.15.3"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b"
dependencies:
regenerator-runtime "^0.13.4"
@@ -920,12 +920,6 @@
pako "^2.0.2"
protobufjs "~6.10.2"
"@cosmjs/cosmwasm@^0.25.5":
version "0.25.5"
resolved "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz"
dependencies:
"@cosmjs/cosmwasm-launchpad" "^0.25.5"
"@cosmjs/crypto@^0.25.5":
version "0.25.5"
resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.25.5.tgz"
@@ -1312,17 +1306,17 @@
version "1.0.3"
resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c"
"@next/env@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.0.tgz#cae83d8e0a65aa9f2af3368f8269ffd9d911746a"
"@next/env@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.1.tgz#d403282accbe8795aa2341f0e02c2e8bfc92bfb0"
"@next/polyfill-module@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.0.tgz#ee6b9117a1f9bb137479dfa51d5a9e38e066a62f"
"@next/polyfill-module@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.1.tgz#89d5a70685a52a0fad79f05a1f97a6b15cc727aa"
"@next/react-dev-overlay@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz#8d4e8020a4cbdacbca431a0bf40c4d28187083af"
"@next/react-dev-overlay@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz#3cd99202a85412bada8ba9c8e3f4cf7c19294b24"
dependencies:
"@babel/code-frame" "7.12.11"
anser "1.4.9"
@@ -1336,9 +1330,25 @@
stacktrace-parser "0.1.10"
strip-ansi "6.0.0"
"@next/react-refresh-utils@11.1.0":
version "11.1.0"
resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz#60c3c7b127a5dab8b0a2889a7dcf8a90d2c4e592"
"@next/react-refresh-utils@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz#8d1a5432a53c9f987503d5ab07d3241230afb33f"
"@next/swc-darwin-arm64@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz#ea9a76bcff00945df29a81bc43b3b22dd0a6cb53"
"@next/swc-darwin-x64@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz#95838e9116897ae734d02fdbbfa601b6f52adaf3"
"@next/swc-linux-x64-gnu@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz#42c4973213a880977ebdfad01474217d7d71e8c2"
"@next/swc-win32-x64-msvc@11.1.1":
version "11.1.1"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz#1ffcbd01a0155fa8558f7aefffea1066e9bebe74"
"@node-rs/helper@1.2.1":
version "1.2.1"
@@ -1346,17 +1356,15 @@
dependencies:
"@napi-rs/triples" "^1.0.3"
"@nymproject/nym-validator-client@0.16.0":
version "0.16.0"
resolved "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz"
"@nymproject/nym-validator-client@0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz#b03af24295cbd2f1b94ffd0c38c5a493e994d111"
dependencies:
"@cosmjs/cosmwasm" "^0.25.5"
"@cosmjs/cosmwasm-stargate" "^0.25.5"
"@cosmjs/crypto" "^0.25.5"
"@cosmjs/launchpad" "^0.25.5"
"@cosmjs/math" "^0.25.5"
"@cosmjs/proto-signing" "^0.25.5"
axios "^0.19.2"
"@cosmjs/stargate" "^0.25.5"
axios "^0.21.1"
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
@@ -1658,12 +1666,6 @@ available-typed-arrays@^1.0.2:
dependencies:
array-filter "^1.0.0"
axios@^0.19.2:
version "0.19.2"
resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz"
dependencies:
follow-redirects "1.5.10"
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz"
@@ -2221,12 +2223,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1:
dependencies:
ms "2.1.2"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz"
dependencies:
ms "2.0.0"
decimal.js@^10.2.1:
version "10.3.1"
resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz"
@@ -2494,12 +2490,6 @@ find-up@^4.0.0, find-up@^4.1.0:
locate-path "^5.0.0"
path-exists "^4.0.0"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz"
dependencies:
debug "=3.1.0"
follow-redirects@^1.10.0:
version "1.13.3"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz"
@@ -3617,16 +3607,16 @@ natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
next@11.1.0:
version "11.1.0"
resolved "https://registry.yarnpkg.com/next/-/next-11.1.0.tgz#767d4c4fa0b9b0c768cdbd6c9f03dd86b5d701c0"
next@11.1.1:
version "11.1.1"
resolved "https://registry.yarnpkg.com/next/-/next-11.1.1.tgz#ca15c6d6b4b4bf8c3e859f7fc4f9657ce59bcb63"
dependencies:
"@babel/runtime" "7.12.5"
"@babel/runtime" "7.15.3"
"@hapi/accept" "5.0.2"
"@next/env" "11.1.0"
"@next/polyfill-module" "11.1.0"
"@next/react-dev-overlay" "11.1.0"
"@next/react-refresh-utils" "11.1.0"
"@next/env" "11.1.1"
"@next/polyfill-module" "11.1.1"
"@next/react-dev-overlay" "11.1.1"
"@next/react-refresh-utils" "11.1.1"
"@node-rs/helper" "1.2.1"
assert "2.0.0"
ast-types "0.13.2"
@@ -3668,9 +3658,14 @@ next@11.1.0:
timers-browserify "2.0.12"
tty-browserify "0.0.1"
use-subscription "1.5.1"
util "0.12.3"
util "0.12.4"
vm-browserify "1.1.2"
watchpack "2.1.1"
optionalDependencies:
"@next/swc-darwin-arm64" "11.1.1"
"@next/swc-darwin-x64" "11.1.1"
"@next/swc-linux-x64-gnu" "11.1.1"
"@next/swc-win32-x64-msvc" "11.1.1"
node-fetch@2.6.1:
version "2.6.1"
@@ -4637,9 +4632,9 @@ util@0.10.3:
dependencies:
inherits "2.0.1"
util@0.12.3, util@^0.12.0:
version "0.12.3"
resolved "https://registry.npmjs.org/util/-/util-0.12.3.tgz"
util@0.12.4, util@^0.12.0:
version "0.12.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253"
dependencies:
inherits "^2.0.3"
is-arguments "^1.0.4"