Compare commits

..

97 Commits

Author SHA1 Message Date
Jędrzej Stuczyński 7f9ac1286b fixed nym-api tests 2024-02-02 13:56:21 +00:00
Jędrzej Stuczyński 961775417f handle chunking on nym-api side 2024-02-02 11:53:53 +00:00
Jędrzej Stuczyński 044d94ad02 updated dkg client traits 2024-02-01 15:19:39 +00:00
Jędrzej Stuczyński 35d7525d1e contract changes 2024-02-01 12:14:43 +00:00
Jędrzej Stuczyński b393405db8 dealing metadata storage logic 2024-01-31 15:46:32 +00:00
Jędrzej Stuczyński 6974f3d785 resharing test + bugfixes 2024-01-30 18:33:58 +00:00
Jędrzej Stuczyński 85d96deded fixed bug in DKG to allow for different sets of dealers and receivers 2024-01-30 15:47:31 +00:00
Jędrzej Stuczyński 4fe7ce0f12 restored key derivation tests 2024-01-30 15:40:20 +00:00
Jędrzej Stuczyński 5886361dc5 restored dealings tests 2024-01-29 11:58:56 +00:00
Jędrzej Stuczyński 40c26b1326 updated contract schema 2024-01-26 10:59:40 +00:00
Jędrzej Stuczyński 46a482cfcb clippy fixes 2024-01-26 10:38:48 +00:00
Jędrzej Stuczyński 761b6c2cac test code compiles
but doesnt fully work yet
2024-01-26 09:52:19 +00:00
Jędrzej Stuczyński 0afdd7bc82 cleanup 2024-01-25 16:11:07 +00:00
Jędrzej Stuczyński 7b9fe3dc09 more completed key derivation 2024-01-25 16:10:35 +00:00
Jędrzej Stuczyński d734174f6e more completed key validation 2024-01-24 12:20:27 +00:00
Jędrzej Stuczyński 6187d94b68 more completed key finalization 2024-01-24 10:34:42 +00:00
Jędrzej Stuczyński a7dca2f07c happy path for key finalization 2024-01-23 17:45:56 +00:00
Jędrzej Stuczyński 2ed4449e0b happy path for key validation 2024-01-23 16:22:41 +00:00
Jędrzej Stuczyński 605176551b actually working happy path with a unit test 2024-01-22 16:55:04 +00:00
Jędrzej Stuczyński ac3830e677 happy path for key derivation 2024-01-22 14:07:26 +00:00
Jędrzej Stuczyński c00e4655f4 improved test fixture + dealing exchange test 2024-01-19 17:21:26 +00:00
Jędrzej Stuczyński 8fa84a28e6 [wip] dealing exchange 2024-01-19 13:42:40 +00:00
Jędrzej Stuczyński 94d3bf087a storing epoch id alongside coconut key 2024-01-17 14:15:25 +00:00
Jędrzej Stuczyński 7a9b989db9 [wip]: improving error recovery during key submission phase 2024-01-17 14:15:25 +00:00
Jędrzej Stuczyński 9f1b89616a more explicit errors in the controller outer loop 2024-01-17 14:15:25 +00:00
Jędrzej Stuczyński 89315f0c2a cleaned up key loading 2024-01-17 14:15:25 +00:00
Jędrzej Stuczyński 858fafb1a5 removed the dkg client retries 2024-01-17 14:15:25 +00:00
Jędrzej Stuczyński 169f8f2c1c cargo fmt 2024-01-17 14:12:41 +00:00
Jędrzej Stuczyński 8782fd7bb8 making nym-api aware of the changes 2024-01-17 14:12:39 +00:00
Jędrzej Stuczyński 146c3bd358 client support 2024-01-17 14:11:02 +00:00
Jędrzej Stuczyński e68ebdc2b8 schema 2024-01-17 14:11:02 +00:00
Jędrzej Stuczyński 397b03267a making dkg kick off when a start message is sent 2024-01-17 14:11:02 +00:00
Jędrzej Stuczyński 2a021b46ac fixed the return type of the query 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński c43cb657c6 added a query msg for the data 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński 6f66b377e2 added cw2 interface to dkg contract 2024-01-17 14:10:34 +00:00
Jędrzej Stuczyński 5ea67a9376 missing test fix 2024-01-17 14:10:08 +00:00
Jędrzej Stuczyński f566dffc5b fixed tests 2024-01-17 14:10:08 +00:00
Jędrzej Stuczyński 05f8beedad api support: submit ed25519 public key alongside the bte public key 2024-01-17 14:10:07 +00:00
Jędrzej Stuczyński 2fff051e28 submit ed25519 public key alongside the bte public key 2024-01-17 14:10:07 +00:00
Jędrzej Stuczyński 44bd70c546 reusing already generated dealings 2024-01-17 14:07:03 +00:00
Jędrzej Stuczyński 702354d127 client support 2024-01-17 14:07:02 +00:00
Jędrzej Stuczyński 56b1010d16 schema 2024-01-17 14:05:53 +00:00
Jędrzej Stuczyński 0632517f5d contract query for dealing status 2024-01-17 14:05:53 +00:00
Jędrzej Stuczyński bfcc5e9b41 fixed dealings query arguments 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 337aacd442 more clippy 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński da5b7302b5 updated dkg schema 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 7a53e86b40 clippy 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński 654dd07d19 removed old debug code 2024-01-17 13:59:06 +00:00
Jędrzej Stuczyński ef0765face ephemera contract fix 2024-01-17 13:59:05 +00:00
Jędrzej Stuczyński 3685b4681c fixes 2024-01-17 13:59:05 +00:00
Jędrzej Stuczyński 47e2af2caa reintroducing bug in deterministic_filter_dealers to make tests pass
yes, it's as bad as it sounds
2024-01-17 13:59:03 +00:00
Jędrzej Stuczyński 5be555d79f ability to query for dkg contract state 2024-01-17 13:58:26 +00:00
Jędrzej Stuczyński 8cc2b3167e client support 2024-01-17 13:56:33 +00:00
Jędrzej Stuczyński 4d95955961 renaming 2024-01-17 13:50:23 +00:00
Jędrzej Stuczyński e36ae4091f removed todos from commented tests 2024-01-17 13:50:23 +00:00
Jędrzej Stuczyński b566147f2f storage and query tests 2024-01-17 13:50:22 +00:00
Jędrzej Stuczyński 12242bb3c6 updated dealings queries 2024-01-17 13:50:22 +00:00
Jędrzej Stuczyński 0a0b0e80f4 storing dealings in new map 2024-01-17 13:50:21 +00:00
Tommy Verrall 3c3a34ec0f Merge pull request #4322 from nymtech/update-localnet-script
Check that localnet is running fromt the scripts dir
2024-01-15 13:43:27 +01:00
Tommy Verrall e9b442e634 Merge pull request #4303 from nymtech/dependabot/npm_and_yarn/follow-redirects-1.15.4
Bump follow-redirects from 1.15.3 to 1.15.4
2024-01-15 13:42:55 +01:00
Tommy Verrall ca02e2bce1 Merge pull request #4310 from nymtech/dependabot/npm_and_yarn/wasm/node-tester/internal-dev/follow-redirects-1.15.4
Bump follow-redirects from 1.15.2 to 1.15.4 in /wasm/node-tester/internal-dev
2024-01-15 13:42:36 +01:00
Tommy Verrall 985ab43fe9 Merge pull request #4306 from nymtech/dependabot/npm_and_yarn/clients/native/examples/js-examples/websocket/follow-redirects-1.15.4
Bump follow-redirects from 1.14.9 to 1.15.4 in /clients/native/examples/js-examples/websocket
2024-01-15 13:42:12 +01:00
dependabot[bot] f4dad37b14 Bump follow-redirects from 1.15.2 to 1.15.4 in /wasm/client/internal-dev (#4311)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-15 11:52:56 +01:00
durch 08a57fa8df Check that we are running fromt the scripts dir 2024-01-15 10:25:51 +01:00
Tommy Verrall 98e88e2f11 Merge pull request #4253 from nymtech/feature/validator-rewarder
Feature/validator rewarder
2024-01-15 09:25:26 +01:00
Tommy Verrall 2a589b049c Merge pull request #4316 from nymtech/feature/bump_vergen
Bump vergen version to latest
2024-01-12 14:44:09 +00:00
Tommy Verrall 9bcd56e254 Merge pull request #4312 from nymtech/dependabot/npm_and_yarn/wasm/mix-fetch/internal-dev/follow-redirects-1.15.4
Bump follow-redirects from 1.15.2 to 1.15.4 in /wasm/mix-fetch/internal-dev
2024-01-12 12:58:55 +00:00
Bogdan-Ștefan Neacşu 025ba2ec5f Bump vergen version to latest 2024-01-12 14:46:37 +02:00
Tommy Verrall 1a1d11c447 Merge pull request #4247 from nymtech/fix/test-route-construction
Only use good nodes for test route construction
2024-01-12 12:28:21 +00:00
serinko 958bc2ae9a [DOCs]: NymVPN alpha - pages for events (#4309)
* initialise new nymvpn guide pages

* docs: nymvpn guide, testing, troubleshooting and faq

* add faq

* remove todo points

* resolve review comments

* change landing page order

* incorporate huxis user feedback

* add binaries link

* change menu naming -> upper case

* final version for cryptotalk demo

* change naming convention client -> cli
2024-01-11 16:34:25 +00:00
dependabot[bot] cfb9f3d356 Bump follow-redirects in /wasm/mix-fetch/internal-dev
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 23:47:45 +00:00
dependabot[bot] ea386b6145 Bump follow-redirects in /wasm/node-tester/internal-dev
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 23:47:41 +00:00
dependabot[bot] d0fa1792e2 Bump follow-redirects in /clients/native/examples/js-examples/websocket
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.9 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.9...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 19:35:21 +00:00
Jędrzej Stuczyński 9b2d224e54 clippy 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 3f504d7500 fixed builds of other binaries 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 67701290d3 cargo fmt 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 22541f5a79 smoothing some rough edges 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński bd8f666405 config template 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński a3c1541660 actually sending the rewards 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 6c1d14a4bc clippy 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński defd148d73 cli arguments + balance check 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 162ff71814 more granual configs 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 5c864cb055 monitoring done 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński cfc13671a4 most of the issuance monitoring logic 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 668a255e0d issuance score calculation logic 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 31d8352621 persisting rewarding data 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 4f6fe88b4c starting on persistence 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 397ef8723d block signing related code 2024-01-09 11:03:05 +01:00
Jędrzej Stuczyński 2c2223947c wip 2024-01-09 11:03:04 +01:00
Jędrzej Stuczyński e1c0638f1e getting signing rewards 2024-01-09 11:02:41 +01:00
Jędrzej Stuczyński 13fa2119fc wip 2024-01-09 11:02:41 +01:00
Jędrzej Stuczyński 8f24e8f208 starting to integrate scraper into the rewarder 2024-01-09 11:02:40 +01:00
Jędrzej Stuczyński 37dd20ded1 wip2 2024-01-09 11:01:17 +01:00
Jędrzej Stuczyński b4b32bb907 wip 2024-01-09 10:59:52 +01:00
dependabot[bot] c1718154cb Bump follow-redirects from 1.15.3 to 1.15.4
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 09:32:28 +00:00
durch 3126053cbe Add three measurements grace period 2023-12-14 14:50:53 +01:00
durch 54266fd5df Only use good nodes for test route construction 2023-12-13 16:39:21 +01:00
204 changed files with 13963 additions and 4760 deletions
Generated
+154 -37
View File
@@ -1649,6 +1649,16 @@ dependencies = [
"tendermint-proto",
]
[[package]]
name = "cosmos-sdk-proto"
version = "0.20.0"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#41ed4631e146268b0300033c8bbb993d79a49f58"
dependencies = [
"prost 0.12.1",
"prost-types 0.12.1",
"tendermint-proto",
]
[[package]]
name = "cosmrs"
version = "0.15.0"
@@ -1656,7 +1666,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea"
dependencies = [
"bip32",
"cosmos-sdk-proto",
"cosmos-sdk-proto 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ecdsa 0.16.8",
"eyre",
"k256",
"rand_core 0.6.4",
"serde",
"serde_json",
"signature 2.1.0",
"subtle-encoding",
"tendermint",
"thiserror",
]
[[package]]
name = "cosmrs"
version = "0.15.0"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#41ed4631e146268b0300033c8bbb993d79a49f58"
dependencies = [
"bip32",
"cosmos-sdk-proto 0.20.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"ecdsa 0.16.8",
"eyre",
"k256",
@@ -2212,6 +2241,16 @@ dependencies = [
"darling_macro 0.14.4",
]
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core 0.20.3",
"darling_macro 0.20.3",
]
[[package]]
name = "darling_core"
version = "0.13.4"
@@ -2240,6 +2279,20 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
@@ -2262,6 +2315,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote",
"syn 2.0.38",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@@ -2771,26 +2835,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "enum-iterator"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -5870,6 +5914,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "nym-api"
version = "1.1.34"
@@ -5930,6 +5983,8 @@ dependencies = [
"pin-project",
"rand 0.7.3",
"rand 0.8.5",
"rand_chacha 0.2.2",
"rand_chacha 0.3.1",
"reqwest",
"rocket",
"rocket_cors",
@@ -5938,6 +5993,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"sha2 0.9.9",
"sqlx",
"tap",
"tempfile",
@@ -5956,7 +6012,7 @@ name = "nym-api-requests"
version = "0.1.0"
dependencies = [
"bs58 0.4.0",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"getset",
"nym-coconut-interface",
@@ -6014,7 +6070,7 @@ name = "nym-bity-integration"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"eyre",
"k256",
"nym-cli-commands",
@@ -6059,7 +6115,7 @@ dependencies = [
"cfg-if",
"clap 4.4.7",
"comfy-table",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"cw-utils",
"handlebars",
@@ -6256,6 +6312,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
@@ -6328,7 +6386,7 @@ name = "nym-credentials"
version = "0.1.0"
dependencies = [
"bls12_381",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"log",
"nym-api-requests",
"nym-coconut-interface",
@@ -6687,7 +6745,6 @@ dependencies = [
"nym-types",
"nym-validator-client",
"opentelemetry",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"serde_json",
@@ -7416,7 +7473,7 @@ name = "nym-types"
version = "1.0.0"
dependencies = [
"base64 0.21.4",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"eyre",
"hmac 0.12.1",
@@ -7450,7 +7507,7 @@ dependencies = [
"bip32",
"bip39",
"colored",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"cw-controllers",
"cw-utils",
@@ -7492,6 +7549,40 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-validator-rewarder"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"clap 4.4.7",
"cosmwasm-std",
"futures",
"humantime 2.1.0",
"humantime-serde",
"nym-bin-common",
"nym-coconut",
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
"nym-config",
"nym-credentials",
"nym-crypto",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"nyxd-scraper",
"serde",
"serde_with",
"sha2 0.10.8",
"sqlx",
"thiserror",
"time",
"tokio",
"tracing",
"url",
"zeroize",
]
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
@@ -7510,7 +7601,7 @@ dependencies = [
name = "nym-wallet-types"
version = "1.0.0"
dependencies = [
"cosmrs",
"cosmrs 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cosmwasm-std",
"hex-literal",
"nym-config",
@@ -7594,7 +7685,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"const_format",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"eyre",
"futures",
"nym-bin-common",
@@ -9818,6 +9909,35 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
dependencies = [
"base64 0.21.4",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.0.2",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
dependencies = [
"darling 0.20.3",
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "serde_yaml"
version = "0.9.25"
@@ -10702,6 +10822,8 @@ dependencies = [
"deranged",
"itoa",
"js-sys",
"libc",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -11652,18 +11774,13 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "7.4.3"
version = "8.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37"
checksum = "1290fd64cc4e7d3c9b07d7f333ce0ce0007253e32870e632624835cc80b83939"
dependencies = [
"anyhow",
"cfg-if",
"enum-iterator",
"getset",
"git2",
"rustc_version 0.4.0",
"rustversion",
"thiserror",
"time",
]
+6 -1
View File
@@ -102,6 +102,7 @@ members = [
"nym-node",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
"tools/nym-cli",
@@ -124,6 +125,7 @@ default-members = [
"nym-api",
"tools/nymvisor",
"explorer-api",
"nym-validator-rewarder",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
@@ -201,7 +203,10 @@ cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = "0.5.1"
cosmrs = "=0.15.0"
# temporarily using a fork again (yay.) because we need staking and slashing support
cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch ="nym-temp/all-validator-features" }
#cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch = "nym-temp/all-validator-features" } # unfortuntely we need a fork by yours truly to get the staking support
tendermint = "0.34" # same version as used by cosmrs
tendermint-rpc = "0.34" # same version as used by cosmrs
prost = "0.12"
@@ -1667,9 +1667,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true,
"funding": [
{
@@ -5800,9 +5800,9 @@
}
},
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true
},
"forwarded": {
+2 -1
View File
@@ -35,9 +35,10 @@ opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = [
vergen = { version = "=8.2.6", default-features = false, features = [
"build",
"git",
"gitcl",
"rustc",
"cargo",
] }
+8 -7
View File
@@ -1,13 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
use vergen::EmitBuilder;
fn main() {
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
EmitBuilder::builder()
.all_build()
.all_git()
.all_rustc()
.all_cargo()
.emit()
.expect("failed to extract build metadata");
}
+10 -10
View File
@@ -40,9 +40,9 @@ pub struct BinaryBuildInformation {
/// Provides the rustc channel that was used for the build, for example `nightly`.
pub rustc_channel: &'static str,
// VERGEN_CARGO_PROFILE
/// Provides the cargo profile that was used for the build, for example `debug`.
pub cargo_profile: &'static str,
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: &'static str,
}
impl BinaryBuildInformation {
@@ -57,7 +57,7 @@ impl BinaryBuildInformation {
commit_branch: env!("VERGEN_GIT_BRANCH"),
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
cargo_profile: env!("VERGEN_CARGO_PROFILE"),
cargo_debug: env!("VERGEN_CARGO_DEBUG"),
}
}
@@ -71,7 +71,7 @@ impl BinaryBuildInformation {
commit_branch: self.commit_branch.to_owned(),
rustc_version: self.rustc_version.to_owned(),
rustc_channel: self.rustc_channel.to_owned(),
cargo_profile: self.cargo_profile.to_owned(),
cargo_debug: self.cargo_debug.to_owned(),
}
}
@@ -115,9 +115,9 @@ pub struct BinaryBuildInformationOwned {
/// Provides the rustc channel that was used for the build, for example `nightly`.
pub rustc_channel: String,
// VERGEN_CARGO_PROFILE
/// Provides the cargo profile that was used for the build, for example `debug`.
pub cargo_profile: String,
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: String,
}
impl Display for BinaryBuildInformationOwned {
@@ -151,8 +151,8 @@ impl Display for BinaryBuildInformationOwned {
self.rustc_version,
"rustc Channel:",
self.rustc_channel,
"cargo Profile:",
self.cargo_profile,
"cargo Debug:",
self.cargo_debug,
)
}
}
@@ -42,6 +42,14 @@ pub struct Config {
nyxd_config: nyxd::Config,
}
impl TryFrom<NymNetworkDetails> for Config {
type Error = ValidatorClientError;
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
Config::try_from_nym_network_details(&value)
}
}
impl Config {
pub fn try_from_nym_network_details(
details: &NymNetworkDetails,
@@ -5,21 +5,24 @@ use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
pub use nym_api_requests::{
coconut::{
models::{
EpochCredentialsResponse, IssuedCredential, IssuedCredentialBody,
IssuedCredentialResponse, IssuedCredentialsResponse,
},
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
VerifyCredentialBody, VerifyCredentialResponse,
},
models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody,
VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_coconut_dkg_common::types::EpochId;
pub use nym_coconut_dkg_common::types::EpochId;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
@@ -8,6 +8,8 @@ use cosmwasm_std::{Fraction, Uint128};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Div;
use std::str::FromStr;
use thiserror::Error;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct MismatchedDenoms;
@@ -126,6 +128,37 @@ impl From<CosmWasmCoin> for Coin {
}
}
// unfortunately cosmwasm didn't re-export this correct so we just redefine its
#[derive(Error, Debug, PartialEq, Eq)]
pub enum CoinFromStrError {
#[error("Missing denominator")]
MissingDenom,
#[error("Missing amount or non-digit characters in amount")]
MissingAmount,
#[error("Invalid amount: {0}")]
InvalidAmount(#[from] std::num::ParseIntError),
}
impl FromStr for Coin {
type Err = CoinFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let pos = s
.find(|c: char| !c.is_ascii_digit())
.ok_or(CoinFromStrError::MissingDenom)?;
let (amount, denom) = s.split_at(pos);
if amount.is_empty() {
return Err(CoinFromStrError::MissingAmount);
}
Ok(Coin {
amount: amount.parse::<u128>()?,
denom: denom.to_string(),
})
}
}
pub trait CoinConverter {
type Target;
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::collect_paged;
@@ -7,14 +7,22 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::dealer::{
ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
};
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use nym_coconut_dkg_common::types::{DealerDetails, Epoch, EpochId, InitialReplacementData};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, PagedVKSharesResponse};
use nym_coconut_dkg_common::types::ChunkIndex;
use serde::Deserialize;
pub use nym_coconut_dkg_common::{
dealer::{DealerDetailsResponse, PagedDealerResponse},
dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatusResponse,
},
msg::QueryMsg as DkgQueryMsg,
types::{
DealerDetails, DealingIndex, Epoch, EpochId, EpochState, InitialReplacementData, State,
},
verification_key::{ContractVKShare, PagedVKSharesResponse, VkShareResponse},
};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait DkgQueryClient {
@@ -22,10 +30,16 @@ pub trait DkgQueryClient {
where
for<'a> T: Deserialize<'a>;
async fn get_state(&self) -> Result<State, NyxdError> {
let request = DkgQueryMsg::GetState {};
self.query_dkg_contract(request).await
}
async fn get_current_epoch(&self) -> Result<Epoch, NyxdError> {
let request = DkgQueryMsg::GetCurrentEpochState {};
self.query_dkg_contract(request).await
}
async fn get_current_epoch_threshold(&self) -> Result<Option<u64>, NyxdError> {
let request = DkgQueryMsg::GetCurrentEpochThreshold {};
self.query_dkg_contract(request).await
@@ -64,17 +78,86 @@ pub trait DkgQueryClient {
self.query_dkg_contract(request).await
}
async fn get_dealings_paged(
async fn get_dealings_metadata(
&self,
idx: u64,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedDealingsResponse, NyxdError> {
let request = DkgQueryMsg::GetDealing {
idx,
limit,
start_after,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingMetadataResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealer_dealings_status(
&self,
epoch_id: EpochId,
dealer: String,
) -> Result<DealerDealingsStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealerDealingsStatus { epoch_id, dealer };
self.query_dkg_contract(request).await
}
async fn get_dealing_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> Result<DealingStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealing_chunk_status(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Result<DealingChunkStatusResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
dealing_index,
chunk_index,
};
self.query_dkg_contract(request).await
}
async fn get_dealing_chunk(
&self,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Result<DealingChunkResponse, NyxdError> {
let request = DkgQueryMsg::GetDealingChunk {
epoch_id,
dealer,
dealing_index,
chunk_index,
};
self.query_dkg_contract(request).await
}
async fn get_vk_share(
&self,
epoch_id: EpochId,
owner: String,
) -> Result<VkShareResponse, NyxdError> {
let request = DkgQueryMsg::GetVerificationKey { epoch_id, owner };
self.query_dkg_contract(request).await
}
@@ -91,6 +174,11 @@ pub trait DkgQueryClient {
};
self.query_dkg_contract(request).await
}
async fn get_contract_cw2_version(&self) -> Result<cw2::ContractVersion, NyxdError> {
self.query_dkg_contract(DkgQueryMsg::GetCW2ContractVersion {})
.await
}
}
// extension trait to the query client to deal with the paged queries
@@ -106,10 +194,6 @@ pub trait PagedDkgQueryClient: DkgQueryClient {
collect_paged!(self, get_past_dealers_paged, dealers)
}
async fn get_all_epoch_dealings(&self, idx: u64) -> Result<Vec<ContractDealing>, NyxdError> {
collect_paged!(self, get_dealings_paged, dealings, idx)
}
async fn get_all_verification_key_shares(
&self,
epoch_id: EpochId,
@@ -143,6 +227,7 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::IgnoreValue;
use nym_coconut_dkg_common::msg::QueryMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -151,6 +236,7 @@ mod tests {
msg: DkgQueryMsg,
) {
match msg {
DkgQueryMsg::GetState {} => client.get_state().ignore(),
DkgQueryMsg::GetCurrentEpochState {} => client.get_current_epoch().ignore(),
DkgQueryMsg::GetCurrentEpochThreshold {} => {
client.get_current_epoch_threshold().ignore()
@@ -165,11 +251,42 @@ mod tests {
DkgQueryMsg::GetPastDealers { limit, start_after } => {
client.get_past_dealers_paged(start_after, limit).ignore()
}
DkgQueryMsg::GetDealing {
idx,
limit,
start_after,
} => client.get_dealings_paged(idx, start_after, limit).ignore(),
DkgQueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
} => client
.get_dealing_status(epoch_id, dealer, dealing_index)
.ignore(),
DkgQueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
} => client
.get_dealings_metadata(epoch_id, dealer, dealing_index)
.ignore(),
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
client.get_dealer_dealings_status(epoch_id, dealer).ignore()
}
DkgQueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => client
.get_dealing_chunk_status(epoch_id, dealer, dealing_index, chunk_index)
.ignore(),
DkgQueryMsg::GetDealingChunk {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => client
.get_dealing_chunk(epoch_id, dealer, dealing_index, chunk_index)
.ignore(),
DkgQueryMsg::GetVerificationKey { epoch_id, owner } => {
client.get_vk_share(epoch_id, owner).ignore()
}
DkgQueryMsg::GetVerificationKeys {
epoch_id,
limit,
@@ -177,6 +294,7 @@ mod tests {
} => client
.get_vk_shares_paged(epoch_id, start_after, limit)
.ignore(),
DkgQueryMsg::GetCW2ContractVersion {} => client.get_contract_cw2_version().ignore(),
};
}
}
@@ -8,11 +8,11 @@ use crate::nyxd::{Coin, Fee, SigningCosmWasmClient};
use crate::signing::signer::OfflineSigner;
use async_trait::async_trait;
use cosmrs::AccountId;
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealing::{DealingChunkInfo, PartialContractDealing};
use nym_coconut_dkg_common::msg::ExecuteMsg as DkgExecuteMsg;
use nym_coconut_dkg_common::types::EncodedBTEPublicKeyWithProof;
use nym_coconut_dkg_common::types::{DealingIndex, EncodedBTEPublicKeyWithProof};
use nym_coconut_dkg_common::verification_key::VerificationKeyShare;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_contracts_common::IdentityKey;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
@@ -25,6 +25,13 @@ pub trait DkgSigningClient {
funds: Vec<Coin>,
) -> Result<ExecuteResult, NyxdError>;
async fn initiate_dkg(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::InitiateDkg {};
self.execute_dkg_contract(fee, req, "initiating the DKG".to_string(), vec![])
.await
}
async fn advance_dkg_epoch_state(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::AdvanceEpochState {};
@@ -42,12 +49,14 @@ pub trait DkgSigningClient {
async fn register_dealer(
&self,
bte_key: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::RegisterDealer {
bte_key_with_proof: bte_key,
identity_key,
announce_address,
resharing,
};
@@ -56,18 +65,32 @@ pub trait DkgSigningClient {
.await
}
async fn submit_dealing_bytes(
async fn submit_dealing_metadata(
&self,
dealing_bytes: ContractSafeBytes,
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealing {
dealing_bytes,
let req = DkgExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
};
self.execute_dkg_contract(fee, req, "dealing commitment".to_string(), vec![])
self.execute_dkg_contract(fee, req, "dealing metadata commitment".to_string(), vec![])
.await
}
async fn submit_dealing_chunk(
&self,
chunk: PartialContractDealing,
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
let req = DkgExecuteMsg::CommitDealingsChunk { chunk, resharing };
self.execute_dkg_contract(fee, req, "dealing chunk commitment".to_string(), vec![])
.await
}
@@ -94,9 +117,10 @@ pub trait DkgSigningClient {
resharing: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
// the call to unchecked is fine as we're converting from pre-validated `AccountId`
let owner = Addr::unchecked(owner.to_string());
let req = DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let req = DkgExecuteMsg::VerifyVerificationKeyShare {
owner: owner.to_string(),
resharing,
};
self.execute_dkg_contract(
fee,
@@ -146,28 +170,36 @@ mod tests {
msg: DkgExecuteMsg,
) {
match msg {
DkgExecuteMsg::InitiateDkg {} => client.initiate_dkg(None).ignore(),
DkgExecuteMsg::RegisterDealer {
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => client
.register_dealer(bte_key_with_proof, announce_address, resharing, None)
.register_dealer(
bte_key_with_proof,
identity_key,
announce_address,
resharing,
None,
)
.ignore(),
DkgExecuteMsg::CommitDealing {
dealing_bytes,
DkgExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
} => client
.submit_dealing_bytes(dealing_bytes, resharing, None)
.submit_dealing_metadata(dealing_index, chunks, resharing, None)
.ignore(),
DkgExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
client.submit_dealing_chunk(chunk, resharing, None).ignore()
}
DkgExecuteMsg::CommitVerificationKeyShare { share, resharing } => client
.submit_verification_key_share(share, resharing, None)
.ignore(),
DkgExecuteMsg::VerifyVerificationKeyShare { owner, resharing } => client
.verify_verification_key_share(
&owner.into_string().parse().unwrap(),
resharing,
None,
)
.verify_verification_key_share(&owner.parse().unwrap(), resharing, None)
.ignore(),
DkgExecuteMsg::SurpassedThreshold {} => client.surpass_threshold(None).ignore(),
DkgExecuteMsg::AdvanceEpochState {} => client.advance_dkg_epoch_state(None).ignore(),
@@ -8,26 +8,26 @@ use std::str::FromStr;
// TODO: all of those could/should be derived via a macro
// query clients
mod coconut_bandwidth_query_client;
mod dkg_query_client;
mod ephemera_query_client;
mod group_query_client;
mod mixnet_query_client;
mod multisig_query_client;
mod name_service_query_client;
mod sp_directory_query_client;
mod vesting_query_client;
pub mod coconut_bandwidth_query_client;
pub mod dkg_query_client;
pub mod ephemera_query_client;
pub mod group_query_client;
pub mod mixnet_query_client;
pub mod multisig_query_client;
pub mod name_service_query_client;
pub mod sp_directory_query_client;
pub mod vesting_query_client;
// signing clients
mod coconut_bandwidth_signing_client;
mod dkg_signing_client;
mod ephemera_signing_client;
mod group_signing_client;
mod mixnet_signing_client;
mod multisig_signing_client;
mod name_service_signing_client;
mod sp_directory_signing_client;
mod vesting_signing_client;
pub mod coconut_bandwidth_signing_client;
pub mod dkg_signing_client;
pub mod ephemera_signing_client;
pub mod group_signing_client;
pub mod mixnet_signing_client;
pub mod multisig_signing_client;
pub mod name_service_signing_client;
pub mod sp_directory_signing_client;
pub mod vesting_signing_client;
// re-export query traits
pub use coconut_bandwidth_query_client::{
@@ -52,10 +52,6 @@ use wasmtimer::tokio::sleep;
pub const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
pub const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
#[cfg(feature = "http-client")]
#[async_trait]
impl CosmWasmClient for cosmrs::rpc::HttpClient {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CosmWasmClient: TendermintRpcClient {
@@ -522,3 +518,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
res.try_into()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T> CosmWasmClient for T where T: TendermintRpcClient {}
@@ -425,7 +425,7 @@ where
amount: amount.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned()))
.map_err(|_| NyxdError::SerializationError("MsgSend".to_owned()))
})
.collect::<Result<_, _>>()?;
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config, GasPrice, Hash, Height};
use crate::rpc::TendermintRpcClient;
@@ -26,6 +26,7 @@ use cosmrs::rpc::{HttpClient, HttpClientUrl};
pub mod client_traits;
mod helpers;
pub mod logs;
pub mod module_traits;
pub mod types;
#[derive(Debug)]
@@ -329,14 +330,6 @@ where
}
}
#[async_trait]
impl<C, S> CosmWasmClient for MaybeSigningClient<C, S>
where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
}
#[async_trait]
impl<C, S> SigningCosmWasmClient for MaybeSigningClient<C, S>
where
@@ -0,0 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod slashing;
pub mod staking;
pub use staking::query::StakingQueryClient;
// pub use slashing::query
@@ -0,0 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod query;
@@ -0,0 +1,2 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod query;
pub use cosmrs::staking::{
QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse, Validator,
};
@@ -0,0 +1,78 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse};
use crate::nyxd::error::NyxdError;
use crate::nyxd::{CosmWasmClient, PageRequest};
use async_trait::async_trait;
use cosmrs::proto::cosmos::staking::v1beta1::{
QueryHistoricalInfoRequest as ProtoQueryHistoricalInfoRequest,
QueryHistoricalInfoResponse as ProtoQueryHistoricalInfoResponse,
QueryValidatorRequest as ProtoQueryValidatorRequest,
QueryValidatorResponse as ProtoQueryValidatorResponse,
QueryValidatorsRequest as ProtoQueryValidatorsRequest,
QueryValidatorsResponse as ProtoQueryValidatorsResponse,
};
use cosmrs::staking::{QueryHistoricalInfoRequest, QueryValidatorRequest, QueryValidatorsRequest};
use cosmrs::AccountId;
// TODO: change trait restriction from `CosmWasmClient` to `TendermintRpcClient`
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait StakingQueryClient: CosmWasmClient {
async fn historical_info(&self, height: i64) -> Result<QueryHistoricalInfoResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/HistoricalInfo".to_owned());
let req = QueryHistoricalInfoRequest { height };
let res = self
.make_abci_query::<ProtoQueryHistoricalInfoRequest, ProtoQueryHistoricalInfoResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
async fn validator(
&self,
validator_addr: AccountId,
) -> Result<QueryValidatorResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/Validator".to_owned());
let req = QueryValidatorRequest { validator_addr };
let res = self
.make_abci_query::<ProtoQueryValidatorRequest, ProtoQueryValidatorResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
async fn validators(
&self,
status: String,
pagination: Option<PageRequest>,
) -> Result<QueryValidatorsResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/Validators".to_owned());
let req = QueryValidatorsRequest { status, pagination };
let res = self
.make_abci_query::<ProtoQueryValidatorsRequest, ProtoQueryValidatorsResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T> StakingQueryClient for T where T: CosmWasmClient {}
@@ -29,24 +29,30 @@ use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
pub use crate::nyxd::fee::Fee;
pub use crate::nyxd::{
cosmwasm_client::{
client_traits::{CosmWasmClient, SigningCosmWasmClient},
module_traits::{self, StakingQueryClient},
},
fee::Fee,
};
pub use crate::rpc::TendermintRpcClient;
pub use coin::Coin;
pub use cosmrs::bank::MsgSend;
pub use cosmrs::tendermint::abci::{
response::DeliverTx, types::ExecTxResult, Event, EventAttribute,
pub use cosmrs::{
bank::MsgSend,
bip32,
crypto::PublicKey,
query::{PageRequest, PageResponse},
tendermint::{
abci::{response::DeliverTx, types::ExecTxResult, Event, EventAttribute},
block::Height,
hash::{self, Algorithm, Hash},
validator::Info as TendermintValidatorInfo,
Time as TendermintTime,
},
tx::{self, Msg},
AccountId, Any, Coin as CosmosCoin, Denom, Gas,
};
pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash::{self, Algorithm, Hash};
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::Msg;
pub use cosmrs::tx::{self};
pub use cosmrs::Any;
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::Gas;
pub use cosmrs::{bip32, AccountId, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use cw2;
pub use cw3;
@@ -56,9 +62,8 @@ pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
Paging,
Paging, Request, Response, SimpleRequest,
};
pub use tendermint_rpc::{Request, Response, SimpleRequest};
#[cfg(feature = "http-client")]
use crate::http_client;
@@ -98,6 +103,14 @@ impl Config {
}
}
impl TryFrom<NymNetworkDetails> for Config {
type Error = NyxdError;
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
Config::try_from_nym_network_details(&value)
}
}
#[derive(Debug)]
pub struct NyxdClient<C, S = NoSigner> {
client: MaybeSigningClient<C, S>,
@@ -729,7 +742,7 @@ where
where
H: Into<Height> + Send,
{
self.client.validators(height, paging).await
TendermintRpcClient::validators(&self.client, height, paging).await
}
async fn latest_consensus_params(
@@ -804,14 +817,6 @@ where
}
}
#[async_trait]
impl<C, S> CosmWasmClient for NyxdClient<C, S>
where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
}
impl<C, S> OfflineSigner for NyxdClient<C, S>
where
S: OfflineSigner,
+1 -1
View File
@@ -14,7 +14,7 @@ pub use nym_coconut::{
aggregate_signature_shares, aggregate_verification_keys, blind_sign, hash_to_scalar,
prepare_blind_sign, prove_bandwidth_credential, Attribute, Base58, BlindSignRequest,
BlindedSignature, Bytable, CoconutError, KeyPair, Parameters, PrivateAttribute,
PublicAttribute, Signature, SignatureShare, Theta, VerificationKey,
PublicAttribute, SecretKey, Signature, SignatureShare, Theta, VerificationKey,
};
#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq, Eq)]
@@ -6,7 +6,7 @@ use log::{debug, info};
use std::str::FromStr;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::TimeConfiguration;
use nym_coconut_dkg_common::types::{TimeConfiguration, DEFAULT_DEALINGS};
use nym_validator_client::nyxd::AccountId;
#[derive(Debug, Parser)]
@@ -93,6 +93,7 @@ pub async fn generate(args: Args) {
multisig_addr: multisig_addr.to_string(),
time_configuration: Some(time_configuration),
mix_denom,
key_size: DEFAULT_DEALINGS as u32,
};
debug!("instantiate_msg: {:?}", instantiate_msg);
@@ -10,6 +10,8 @@ license.workspace = true
cosmwasm-schema = { workspace = true }
cosmwasm-std = { workspace = true }
cw-utils = { workspace = true }
cw2 = { workspace = true }
cw4 = { workspace = true }
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
nym-multisig-contract-common = { path = "../multisig-contract" }
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, NodeIndex};
use crate::types::{EncodedBTEPublicKeyWithProof, NodeIndex};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
@@ -9,6 +9,7 @@ use cosmwasm_std::Addr;
pub struct DealerDetails {
pub address: Addr,
pub bte_public_key_with_proof: EncodedBTEPublicKeyWithProof,
pub ed25519_identity: String,
pub announce_address: String,
pub assigned_index: NodeIndex,
}
@@ -64,38 +65,3 @@ impl PagedDealerResponse {
}
}
}
#[cw_serde]
pub struct ContractDealing {
pub dealing: ContractSafeBytes,
pub dealer: Addr,
}
impl ContractDealing {
pub fn new(dealing: ContractSafeBytes, dealer: Addr) -> Self {
ContractDealing { dealing, dealer }
}
}
#[cw_serde]
pub struct PagedDealingsResponse {
pub dealings: Vec<ContractDealing>,
pub per_page: usize,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<Addr>,
}
impl PagedDealingsResponse {
pub fn new(
dealings: Vec<ContractDealing>,
per_page: usize,
start_next_after: Option<Addr>,
) -> Self {
PagedDealingsResponse {
dealings,
per_page,
start_next_after,
}
}
}
@@ -0,0 +1,284 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ChunkIndex, DealingIndex, EpochId, PartialContractDealingData};
use contracts_common::dealings::ContractSafeBytes;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use std::collections::{BTreeMap, HashMap};
/// Defines the maximum size of a dealing chunk. Currently set to 2kB
pub const MAX_DEALING_CHUNK_SIZE: usize = 2048;
/// Defines the maximum size of a full dealing.
/// Currently set to 100kB (which is enough for a dealing created for 100 parties)
pub const MAX_DEALING_SIZE: usize = 102400;
pub const MAX_DEALING_CHUNKS: usize = MAX_DEALING_SIZE / MAX_DEALING_CHUNK_SIZE;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const DEFAULT_DEALINGS: usize = 2 + 2 + 1;
pub fn chunk_dealing(
dealing_index: DealingIndex,
dealing_bytes: Vec<u8>,
chunk_size: usize,
) -> HashMap<ChunkIndex, PartialContractDealing> {
let mut chunks = HashMap::new();
for (chunk_index, chunk) in dealing_bytes.chunks(chunk_size).enumerate() {
let chunk = PartialContractDealing {
dealing_index,
chunk_index: chunk_index as ChunkIndex,
data: ContractSafeBytes(chunk.to_vec()),
};
chunks.insert(chunk_index as ChunkIndex, chunk);
}
chunks
}
#[cw_serde]
#[derive(Copy)]
pub struct DealingChunkInfo {
pub size: usize,
}
impl DealingChunkInfo {
pub fn new(size: usize) -> Self {
DealingChunkInfo { size }
}
pub fn construct(dealing_len: usize, chunk_size: usize) -> Vec<Self> {
let (full_chunks, overflow) = (dealing_len / chunk_size, dealing_len % chunk_size);
let mut chunks = Vec::new();
for _ in 0..full_chunks {
chunks.push(DealingChunkInfo::new(chunk_size));
}
if overflow != 0 {
chunks.push(DealingChunkInfo::new(overflow));
}
chunks
}
}
#[cw_serde]
#[derive(Copy)]
pub struct SubmittedChunk {
pub info: DealingChunkInfo,
pub status: ChunkSubmissionStatus,
}
#[cw_serde]
#[derive(Default, Copy)]
pub struct ChunkSubmissionStatus {
// this field is updated by the contract itself to indicate when this particular chunk has been received
pub submission_height: Option<u64>,
}
impl ChunkSubmissionStatus {
pub fn submitted(&self) -> bool {
self.submission_height.is_some()
}
}
impl From<DealingChunkInfo> for SubmittedChunk {
fn from(value: DealingChunkInfo) -> Self {
SubmittedChunk::new(value)
}
}
impl SubmittedChunk {
pub fn new(info: DealingChunkInfo) -> Self {
SubmittedChunk {
info,
status: Default::default(),
}
}
pub fn submitted(&self) -> bool {
self.status.submitted()
}
}
#[cw_serde]
pub struct DealingMetadata {
pub dealing_index: DealingIndex,
pub submitted_chunks: BTreeMap<ChunkIndex, SubmittedChunk>,
}
impl DealingMetadata {
pub fn new(dealing_index: DealingIndex, chunks: Vec<DealingChunkInfo>) -> Self {
DealingMetadata {
dealing_index,
submitted_chunks: chunks
.into_iter()
.enumerate()
.map(|(id, chunk)| (id as ChunkIndex, chunk.into()))
.collect(),
}
}
pub fn is_complete(&self) -> bool {
self.submitted_chunks.values().all(|c| c.submitted())
}
pub fn total_size(&self) -> usize {
self.submitted_chunks.values().map(|c| c.info.size).sum()
}
pub fn submission_statuses(&self) -> BTreeMap<ChunkIndex, ChunkSubmissionStatus> {
self.submitted_chunks
.iter()
.map(|(id, c)| (*id, c.status))
.collect()
}
}
#[cw_serde]
pub struct PartialContractDealing {
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub data: PartialContractDealingData,
}
impl PartialContractDealing {
pub fn new(
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
data: PartialContractDealingData,
) -> Self {
PartialContractDealing {
dealing_index,
chunk_index,
data,
}
}
}
#[cw_serde]
pub struct DealingMetadataResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub metadata: Option<DealingMetadata>,
}
#[cw_serde]
pub struct DealingChunkResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub chunk: Option<PartialContractDealingData>,
}
#[cw_serde]
pub struct DealingChunkStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub chunk_index: ChunkIndex,
pub status: ChunkSubmissionStatus,
}
#[cw_serde]
pub struct DealingStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub dealing_index: DealingIndex,
pub status: DealingStatus,
}
#[cw_serde]
pub struct DealingStatus {
pub has_metadata: bool,
pub fully_submitted: bool,
pub chunk_submission_status: BTreeMap<ChunkIndex, ChunkSubmissionStatus>,
}
impl From<Option<DealingMetadata>> for DealingStatus {
fn from(metadata: Option<DealingMetadata>) -> Self {
DealingStatus {
has_metadata: metadata.is_some(),
fully_submitted: metadata
.as_ref()
.map(|m| m.is_complete())
.unwrap_or_default(),
chunk_submission_status: metadata
.map(|m| m.submission_statuses())
.unwrap_or_default(),
}
}
}
#[cw_serde]
pub struct DealerDealingsStatusResponse {
pub epoch_id: EpochId,
pub dealer: Addr,
pub all_dealings_fully_submitted: bool,
pub dealing_submission_status: BTreeMap<DealingIndex, DealingStatus>,
}
impl DealerDealingsStatusResponse {
pub fn full_dealings(&self) -> usize {
self.dealing_submission_status
.values()
.filter(|s| s.fully_submitted)
.count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunking_dealings() {
const CHUNK_SIZE: usize = 512;
let test_cases = [
(CHUNK_SIZE - 10, CHUNK_SIZE, 1),
(CHUNK_SIZE, CHUNK_SIZE, 1),
(CHUNK_SIZE + 10, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2, CHUNK_SIZE, 2),
(CHUNK_SIZE * 2 + 1, CHUNK_SIZE, 3),
(CHUNK_SIZE * 10 + 42, CHUNK_SIZE, 11),
];
for (dealing_len, chunk_size, expected_chunks) in test_cases {
let chunks = DealingChunkInfo::construct(dealing_len, chunk_size);
assert_eq!(expected_chunks, chunks.len());
assert_eq!(dealing_len, chunks.iter().map(|c| c.size).sum::<usize>());
let mut expected_last = dealing_len % chunk_size;
if expected_last == 0 {
expected_last = chunk_size;
}
assert_eq!(chunks.last().unwrap().size, expected_last);
}
}
}
@@ -1,4 +1,8 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod dealer;
pub mod dealing;
pub mod event_attributes;
pub mod msg;
pub mod types;
@@ -1,16 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::types::{ContractSafeBytes, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration};
use crate::dealing::{DealingChunkInfo, PartialContractDealing};
use crate::types::{
ChunkIndex, DealingIndex, EncodedBTEPublicKeyWithProof, EpochId, TimeConfiguration,
};
use crate::verification_key::VerificationKeyShare;
use contracts_common::IdentityKey;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
#[cfg(feature = "schema")]
use crate::{
dealer::{DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse},
types::{Epoch, InitialReplacementData},
verification_key::PagedVKSharesResponse,
dealer::{DealerDetailsResponse, PagedDealerResponse},
dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatusResponse,
},
types::{Epoch, InitialReplacementData, State},
verification_key::{PagedVKSharesResponse, VkShareResponse},
};
#[cfg(feature = "schema")]
use cosmwasm_schema::QueryResponses;
@@ -21,18 +28,31 @@ pub struct InstantiateMsg {
pub multisig_addr: String,
pub time_configuration: Option<TimeConfiguration>,
pub mix_denom: String,
/// Specifies the number of elements in the derived keys
pub key_size: u32,
}
#[cw_serde]
pub enum ExecuteMsg {
// we could have just re-used AdvanceEpochState, but imo an explicit message is better
InitiateDkg {},
RegisterDealer {
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: IdentityKey,
announce_address: String,
resharing: bool,
},
CommitDealing {
dealing_bytes: ContractSafeBytes,
CommitDealingsMetadata {
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
},
CommitDealingsChunk {
chunk: PartialContractDealing,
resharing: bool,
},
@@ -42,8 +62,7 @@ pub enum ExecuteMsg {
},
VerifyVerificationKeyShare {
// TODO: this should be using a String...
owner: Addr,
owner: String,
resharing: bool,
},
@@ -55,6 +74,9 @@ pub enum ExecuteMsg {
#[cw_serde]
#[cfg_attr(feature = "schema", derive(QueryResponses))]
pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(State))]
GetState {},
#[cfg_attr(feature = "schema", returns(Epoch))]
GetCurrentEpochState {},
@@ -79,19 +101,53 @@ pub enum QueryMsg {
start_after: Option<String>,
},
#[cfg_attr(feature = "schema", returns(PagedDealingsResponse))]
GetDealing {
idx: u64,
limit: Option<u32>,
start_after: Option<String>,
#[cfg_attr(feature = "schema", returns(DealingMetadataResponse))]
GetDealingsMetadata {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
},
#[cfg_attr(feature = "schema", returns(DealerDealingsStatusResponse))]
GetDealerDealingsStatus { epoch_id: EpochId, dealer: String },
#[cfg_attr(feature = "schema", returns(DealingStatusResponse))]
GetDealingStatus {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
},
#[cfg_attr(feature = "schema", returns(DealingChunkStatusResponse))]
GetDealingChunkStatus {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[cfg_attr(feature = "schema", returns(DealingChunkResponse))]
GetDealingChunk {
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[cfg_attr(feature = "schema", returns(VkShareResponse))]
GetVerificationKey { epoch_id: EpochId, owner: String },
#[cfg_attr(feature = "schema", returns(PagedVKSharesResponse))]
GetVerificationKeys {
epoch_id: EpochId,
limit: Option<u32>,
start_after: Option<String>,
},
/// Gets the stored contract version information that's required by the CW2 spec interface for migrations.
#[serde(rename = "get_cw2_contract_version")]
#[cfg_attr(feature = "schema", returns(cw2::ContractVersion))]
GetCW2ContractVersion {},
}
#[cw_serde]
@@ -8,14 +8,18 @@ use std::str::FromStr;
pub use crate::dealer::{DealerDetails, PagedDealerResponse};
pub use contracts_common::dealings::ContractSafeBytes;
pub use cosmwasm_std::{Addr, Coin, Timestamp};
pub use cw4::Cw4Contract;
pub type EncodedBTEPublicKeyWithProof = String;
pub type EncodedBTEPublicKeyWithProofRef<'a> = &'a str;
pub type NodeIndex = u64;
pub type EpochId = u64;
// 2 public attributes, 2 private attributes, 1 fixed for coconut credential
pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
pub type DealingIndex = u32;
// we really don't need to hold more data than that (even u8 would have been enough),
// but explicitly make it different type than `DealingIndex` so type system would detect any
// accidental misuses
pub type ChunkIndex = u16;
pub type PartialContractDealingData = ContractSafeBytes;
#[cw_serde]
pub struct InitialReplacementData {
@@ -73,13 +77,23 @@ impl Default for TimeConfiguration {
}
}
#[cw_serde]
pub struct State {
pub mix_denom: String,
pub multisig_addr: Addr,
pub group_addr: Cw4Contract,
/// Specifies the number of elements in the derived keys
pub key_size: u32,
}
#[cw_serde]
#[derive(Copy, Default)]
pub struct Epoch {
pub state: EpochState,
pub epoch_id: EpochId,
pub time_configuration: TimeConfiguration,
pub finish_timestamp: Timestamp,
pub finish_timestamp: Option<Timestamp>,
}
impl Epoch {
@@ -90,36 +104,40 @@ impl Epoch {
current_timestamp: Timestamp,
) -> Self {
let duration = match state {
EpochState::WaitingInitialisation => None,
EpochState::PublicKeySubmission { .. } => {
time_configuration.public_key_submission_time_secs
Some(time_configuration.public_key_submission_time_secs)
}
EpochState::DealingExchange { .. } => {
Some(time_configuration.dealing_exchange_time_secs)
}
EpochState::DealingExchange { .. } => time_configuration.dealing_exchange_time_secs,
EpochState::VerificationKeySubmission { .. } => {
time_configuration.verification_key_submission_time_secs
Some(time_configuration.verification_key_submission_time_secs)
}
EpochState::VerificationKeyValidation { .. } => {
time_configuration.verification_key_validation_time_secs
Some(time_configuration.verification_key_validation_time_secs)
}
EpochState::VerificationKeyFinalization { .. } => {
time_configuration.verification_key_finalization_time_secs
Some(time_configuration.verification_key_finalization_time_secs)
}
EpochState::InProgress => time_configuration.in_progress_time_secs,
EpochState::InProgress => Some(time_configuration.in_progress_time_secs),
};
Epoch {
state,
epoch_id,
time_configuration,
finish_timestamp: current_timestamp.plus_seconds(duration),
finish_timestamp: duration.map(|d| current_timestamp.plus_seconds(d)),
}
}
pub fn final_timestamp_secs(&self) -> u64 {
let mut finish = self.finish_timestamp.seconds();
pub fn final_timestamp_secs(&self) -> Option<u64> {
let mut finish = self.finish_timestamp?.seconds();
let time_configuration = self.time_configuration;
let mut curr_epoch_state = self.state;
while let Some(state) = curr_epoch_state.next() {
curr_epoch_state = state;
let adding = match curr_epoch_state {
EpochState::WaitingInitialisation => return None,
EpochState::PublicKeySubmission { .. } => {
time_configuration.public_key_submission_time_secs
}
@@ -137,12 +155,13 @@ impl Epoch {
};
finish += adding;
}
finish
Some(finish)
}
}
// currently (it is still extremely likely to change, we might be able to get rid of verification key-related complaints),
// the epoch can be in the following states (in order):
// 0. WaitingInitialisation -> the contract has been instantiated, but awaits for the admin to kick off the process (group members might still be getting added)
// 1. PublicKeySubmission -> potential dealers are submitting their BTE and ed25519 public keys to participate in dealing exchange
// 2. DealingExchange -> the actual (off-chain) dealing exchange is happening
// 3. ComplaintSubmission -> receivers submitting evidence of other dealers sending malformed data
@@ -156,6 +175,7 @@ impl Epoch {
#[cw_serde]
#[derive(Copy)]
pub enum EpochState {
WaitingInitialisation,
PublicKeySubmission { resharing: bool },
DealingExchange { resharing: bool },
VerificationKeySubmission { resharing: bool },
@@ -166,13 +186,14 @@ pub enum EpochState {
impl Default for EpochState {
fn default() -> Self {
Self::PublicKeySubmission { resharing: false }
Self::WaitingInitialisation
}
}
impl Display for EpochState {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
EpochState::WaitingInitialisation => write!(f, "Waiting for initialisation"),
EpochState::PublicKeySubmission { resharing } => {
write!(f, "PublicKeySubmission (resharing: {resharing})")
}
@@ -194,8 +215,13 @@ impl Display for EpochState {
}
impl EpochState {
pub fn first() -> Self {
EpochState::PublicKeySubmission { resharing: false }
}
pub fn next(self) -> Option<Self> {
match self {
EpochState::WaitingInitialisation => None,
EpochState::PublicKeySubmission { resharing } => {
Some(EpochState::DealingExchange { resharing })
}
@@ -20,6 +20,13 @@ pub struct ContractVKShare {
pub verified: bool,
}
#[cw_serde]
pub struct VkShareResponse {
pub owner: Addr,
pub epoch_id: EpochId,
pub share: Option<ContractVKShare>,
}
#[cw_serde]
pub struct PagedVKSharesResponse {
pub shares: Vec<ContractVKShare>,
@@ -36,7 +43,10 @@ pub fn to_cosmos_msg(
multisig_addr: String,
expiration_time: Timestamp,
) -> StdResult<CosmosMsg> {
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare { owner, resharing };
let verify_vk_share_req = ExecuteMsg::VerifyVerificationKeyShare {
owner: owner.to_string(),
resharing,
};
let verify_vk_share_msg = CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: coconut_dkg_addr,
msg: to_binary(&verify_vk_share_req)?,
@@ -57,7 +67,14 @@ pub fn to_cosmos_msg(
Ok(msg)
}
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<Addr> {
// DKG SAFETY:
// each legit verification proposal will only contain a single execute msg,
// if they have more than one, we can safely ignore it
pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option<String> {
if msgs.len() != 1 {
return None;
}
if let Some(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: _,
msg,
@@ -4,14 +4,14 @@
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::ops::Deref;
use std::ops::{Deref, DerefMut};
// some sane upper-bound size on byte sizes
// currently set to 128 bytes
pub const MAX_DISPLAY_SIZE: usize = 128;
// TODO: if we are to use this for different types, it might make sense to introduce something like
// CommitmentTypeId field on the below for distinguishing different ones. it would somehow become part of the trait
// helps to transfer bytes between contract boundary to decrease amount of data sent accross
// after it's put to `Binary`
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, JsonSchema)]
pub struct ContractSafeBytes(pub Vec<u8>);
@@ -23,6 +23,18 @@ impl Deref for ContractSafeBytes {
}
}
impl DerefMut for ContractSafeBytes {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl From<Vec<u8>> for ContractSafeBytes {
fn from(value: Vec<u8>) -> Self {
ContractSafeBytes(value)
}
}
impl Display for ContractSafeBytes {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if !self.0.is_empty() {
+19 -9
View File
@@ -5,7 +5,9 @@ use nym_bandwidth_controller::acquire::state::State;
use nym_client_core::config::disk_persistence::CommonClientPaths;
use nym_config::DEFAULT_DATA_DIR;
use nym_credential_storage::persistent_storage::PersistentStorage;
use nym_validator_client::nyxd::contract_traits::{CoconutBandwidthSigningClient, DkgQueryClient};
use nym_validator_client::nyxd::contract_traits::{
dkg_query_client::EpochState, CoconutBandwidthSigningClient, DkgQueryClient,
};
use nym_validator_client::nyxd::Coin;
use std::path::PathBuf;
use std::process::exit;
@@ -87,21 +89,29 @@ where
.duration_since(SystemTime::UNIX_EPOCH)
.expect("the system clock is set to 01/01/1970 (or earlier)")
.as_secs();
if epoch.state.is_final() {
if current_timestamp_secs + SAFETY_BUFFER_SECS >= epoch.finish_timestamp.seconds() {
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
exit(0);
if let Some(finish_timestamp) = epoch.finish_timestamp {
if current_timestamp_secs + SAFETY_BUFFER_SECS >= finish_timestamp.seconds() {
info!("In the next {} minute(s), a transition will take place in the coconut system. Deposits should be halted in this time for safety reasons.", SAFETY_BUFFER_SECS / 60);
exit(0);
}
}
break;
} else {
} else if let Some(final_timestamp) = epoch.final_timestamp_secs() {
// Use 1 additional second to not start the next iteration immediately and spam get_current_epoch queries
let secs_until_final = epoch
.final_timestamp_secs()
.saturating_sub(current_timestamp_secs)
+ 1;
let secs_until_final = final_timestamp.saturating_sub(current_timestamp_secs) + 1;
info!("Approximately {} seconds until coconut is available. Sleeping until then. You can safely kill the process at any moment.", secs_until_final);
tokio::time::sleep(Duration::from_secs(secs_until_final)).await;
} else if matches!(epoch.state, EpochState::WaitingInitialisation) {
info!("dkg hasn't been initialised yet and it is not known when it will be. Going to check again later");
tokio::time::sleep(Duration::from_secs(60 * 5)).await;
} else {
// this should never be the case since the only case where final timestamp is unknown is when it's waiting for initialisation,
// but let's guard ourselves against future changes
info!("it is unknown when coconut will be come available. Going to check again later");
tokio::time::sleep(Duration::from_secs(60 * 5)).await;
}
}
+1 -2
View File
@@ -14,8 +14,7 @@ use std::collections::HashMap;
use std::ops::Neg;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
+1 -2
View File
@@ -53,8 +53,7 @@ impl PublicKey {
}
}
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicKeyWithProof {
pub(crate) key: PublicKey,
pub(crate) proof: ProofOfDiscreteLog,
+1 -2
View File
@@ -67,8 +67,7 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProofOfChunking {
y0: G1Projective,
bb: Vec<G1Projective>,
+1 -2
View File
@@ -13,8 +13,7 @@ use zeroize::Zeroize;
const DISCRETE_LOG_DOMAIN: &[u8] =
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
#[derive(Clone, Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProofOfDiscreteLog {
pub(crate) rand_commitment: G1Projective,
pub(crate) response: Scalar,
+1 -2
View File
@@ -76,8 +76,7 @@ impl<'a> Instance<'a> {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq, Eq))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProofOfSecretSharing {
ff: G1Projective,
aa: G2Projective,
+100 -20
View File
@@ -82,8 +82,7 @@ impl RecoveredVerificationKeys {
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Dealing {
pub public_coefficients: PublicCoefficients,
pub ciphertexts: Ciphertexts,
@@ -321,9 +320,17 @@ impl<'a> TryFrom<&'a nym_contracts_common::dealings::ContractSafeBytes> for Deal
}
}
// this assumes all dealings have been verified
/// Attempt to run the `VkCombine` algorithm to obtain the public master verification key, `VK`
/// alongside shares of the verification key, `shvk_{1}`, `shvk_{2}`, ... `svhk_{n}`, where n is the number of receivers.
///
/// # Arguments
///
/// * `dealings`: map of dealer indices to dealings they generated
/// * `threshold`: explicit threshold value of the associated dealings
/// * `receivers`:map of receiver indices to their public keys
// note: this function assumes all dealings have already been verified
pub fn try_recover_verification_keys(
dealings: &[Dealing],
dealings: &BTreeMap<NodeIndex, Dealing>,
threshold: Threshold,
receivers: &BTreeMap<NodeIndex, PublicKey>,
) -> Result<RecoveredVerificationKeys, DkgError> {
@@ -331,24 +338,31 @@ pub fn try_recover_verification_keys(
return Err(DkgError::NoDealingsAvailable);
}
let threshold_usize = threshold as usize;
let threshold = threshold as usize;
if dealings.len() < threshold {
return Err(DkgError::NotEnoughDealingsAvailable {
available: dealings.len(),
required: threshold,
});
}
if !dealings
.iter()
.all(|dealing| dealing.public_coefficients.size() == threshold_usize)
.values()
.all(|dealing| dealing.public_coefficients.size() == threshold)
{
return Err(DkgError::MismatchedDealings);
}
let indices = receivers.keys().collect::<Vec<_>>();
let dealer_indices = dealings.keys().collect::<Vec<_>>();
// Compute A0, ..., A_{t-1}
let mut interpolated_coefficients = Vec::with_capacity(threshold_usize);
for k in 0..threshold_usize {
let mut samples = Vec::with_capacity(indices.len());
for (j, dealing) in dealings.iter().enumerate() {
let mut interpolated_coefficients = Vec::with_capacity(threshold);
for k in 0..threshold {
let mut samples = Vec::with_capacity(dealer_indices.len());
for (dealer_index, dealing) in dealings.iter() {
samples.push((
Scalar::from(*indices[j]),
Scalar::from(*dealer_index),
*dealing.public_coefficients.nth(k),
))
}
@@ -365,7 +379,7 @@ pub fn try_recover_verification_keys(
// shvk_j = A0^{j^0} * A1^{j^1} * ... * A_{t-1}^{j^{t-1}}
let verification_key_shares = receivers
.keys()
.map(|index| interpolated_coefficients.evaluate_at(&Scalar::from(*index)))
.map(|receiver_index| interpolated_coefficients.evaluate_at(&Scalar::from(*receiver_index)))
.collect();
Ok(RecoveredVerificationKeys {
@@ -457,14 +471,17 @@ mod tests {
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
derived_secrets.push(
@@ -513,9 +530,12 @@ mod tests {
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
let RecoveredVerificationKeys {
recovered_master,
@@ -531,6 +551,66 @@ mod tests {
.is_ok())
}
#[test]
#[ignore] // expensive test
fn verifying_partial_verification_keys_with_different_dealers_and_receivers() {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let dealer_indices = [1, 2, 3, 8];
let receiver_indices = [3, 4, 5, 6, 7];
let threshold = 3;
let mut receivers = BTreeMap::new();
let mut full_keys = Vec::new();
for index in &receiver_indices {
let (dk, pk) = keygen(&params, &mut rng);
receivers.insert(*index, *pk.public_key());
full_keys.push((dk, pk))
}
let dealings = dealer_indices
.iter()
.map(|&dealer_index| {
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<BTreeMap<_, _>>();
let RecoveredVerificationKeys {
recovered_master,
recovered_partials,
} = try_recover_verification_keys(&dealings, threshold, &receivers).unwrap();
let g2 = G2Projective::generator();
let mut derived_secrets = Vec::new();
for (i, (dk, _)) in full_keys.iter().enumerate() {
let shares = dealings
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
let recovered_secret = combine_shares(shares, &dealer_indices).unwrap();
// make sure it matches the associated vk
assert_eq!(recovered_partials[i], g2 * recovered_secret);
derived_secrets.push(recovered_secret)
}
assert!(verify_verification_keys(
&recovered_master,
&recovered_partials,
&receivers,
threshold
)
.is_ok())
}
#[test]
#[ignore] // expensive test
fn dealing_roundtrip() {
+1 -1
View File
@@ -3,7 +3,7 @@
use thiserror::Error;
#[derive(Debug, Error)]
#[derive(Debug, Error, Clone)]
pub enum DkgError {
#[error("Provided set of values contained duplicate coordinate")]
DuplicateCoordinate,
+1
View File
@@ -13,6 +13,7 @@ pub mod dealing;
pub(crate) mod share;
pub(crate) mod utils;
pub use bls12_381::{G2Projective, Scalar};
pub use dealing::*;
pub use share::*;
+51 -36
View File
@@ -52,7 +52,7 @@ fn single_sender() {
.unwrap();
// make sure each share is actually decryptable (even though proofs say they must be, perform this sanity check)
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let _recovered = decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap();
}
@@ -91,10 +91,13 @@ fn full_threshold_secret_sharing() {
let dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<Vec<_>>();
for dealing in dealings.iter() {
.collect::<BTreeMap<_, _>>();
for dealing in dealings.values() {
dealing
.verify(&params, threshold, &receivers, None)
.unwrap();
@@ -109,9 +112,9 @@ fn full_threshold_secret_sharing() {
let g2 = G2Projective::generator();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
@@ -169,9 +172,12 @@ fn full_threshold_secret_resharing() {
let first_dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
// recover verification keys
let RecoveredVerificationKeys {
@@ -180,9 +186,9 @@ fn full_threshold_secret_resharing() {
} = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = first_dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
@@ -203,19 +209,22 @@ fn full_threshold_secret_resharing() {
.iter()
.zip(derived_secrets.iter())
.map(|(&dealer_index, prior_secret)| {
Dealing::create(
&mut rng,
&params,
(
dealer_index,
threshold,
&receivers,
Some(*prior_secret),
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
&receivers,
Some(*prior_secret),
)
.0,
)
.0
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
for (reshared_dealing, prior_vk) in resharing_dealings.iter().zip(recovered_partials.iter()) {
for (reshared_dealing, prior_vk) in resharing_dealings.values().zip(recovered_partials.iter()) {
reshared_dealing
.verify(&params, threshold, &receivers, Some(*prior_vk))
.unwrap();
@@ -228,9 +237,9 @@ fn full_threshold_secret_resharing() {
} = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap();
let mut reshared_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = resharing_dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
@@ -279,9 +288,12 @@ fn full_threshold_secret_resharing_left_party() {
let first_dealings = node_indices
.iter()
.map(|&dealer_index| {
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0
(
dealer_index,
Dealing::create(&mut rng, &params, dealer_index, threshold, &receivers, None).0,
)
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
// recover verification keys
let RecoveredVerificationKeys {
@@ -290,9 +302,9 @@ fn full_threshold_secret_resharing_left_party() {
} = try_recover_verification_keys(&first_dealings, threshold, &receivers).unwrap();
let mut derived_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = first_dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
@@ -323,20 +335,23 @@ fn full_threshold_secret_resharing_left_party() {
.iter()
.zip(derived_secrets.iter().take(2))
.map(|(&dealer_index, prior_secret)| {
Dealing::create(
&mut rng,
&params,
(
dealer_index,
threshold,
&receivers,
Some(*prior_secret),
Dealing::create(
&mut rng,
&params,
dealer_index,
threshold,
&receivers,
Some(*prior_secret),
)
.0,
)
.0
})
.collect::<Vec<_>>();
.collect::<BTreeMap<_, _>>();
for (reshared_dealing, prior_vk) in resharing_dealings
.iter()
.values()
.zip(recovered_partials.iter().take(2))
{
reshared_dealing
@@ -351,9 +366,9 @@ fn full_threshold_secret_resharing_left_party() {
} = try_recover_verification_keys(&resharing_dealings, threshold, &receivers).unwrap();
let mut reshared_secrets = Vec::new();
for (i, (ref mut dk, _)) in full_keys.iter_mut().enumerate() {
for (i, (ref dk, _)) in full_keys.iter().enumerate() {
let shares = resharing_dealings
.iter()
.values()
.map(|dealing| decrypt_share(dk, i, &dealing.ciphertexts, None).unwrap())
.collect();
+13 -1
View File
@@ -111,6 +111,7 @@ impl NymNetworkDetails {
.with_additional_validator_endpoint(ValidatorDetails::new(
var(var_names::NYXD).expect("nyxd validator not set"),
Some(var(var_names::NYM_API).expect("nym api not set")),
get_optional_env(var_names::NYXD_WEBSOCKET),
))
.with_mixnet_contract(Some(
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
@@ -340,6 +341,9 @@ impl DenomDetailsOwned {
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file
pub nyxd_url: String,
//
pub websocket_url: Option<String>,
// Right now api_url is optional as we are not running the api reliably on all validators
// however, later on it should be a mandatory field
pub api_url: Option<String>,
@@ -347,9 +351,10 @@ pub struct ValidatorDetails {
}
impl ValidatorDetails {
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>) -> Self {
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>, websocket_url: Option<S>) -> Self {
ValidatorDetails {
nyxd_url: nyxd_url.into(),
websocket_url: websocket_url.map(Into::into),
api_url: api_url.map(Into::into),
}
}
@@ -357,6 +362,7 @@ impl ValidatorDetails {
pub fn new_nyxd_only<S: Into<String>>(nyxd_url: S) -> Self {
ValidatorDetails {
nyxd_url: nyxd_url.into(),
websocket_url: None,
api_url: None,
}
}
@@ -372,6 +378,12 @@ impl ValidatorDetails {
.as_ref()
.map(|url| url.parse().expect("the provided api url is invalid!"))
}
pub fn websocket_url(&self) -> Option<Url> {
self.websocket_url
.as_ref()
.map(|url| url.parse().expect("the provided websocket url is invalid!"))
}
}
fn fix_deprecated_environmental_variables() {
+8 -1
View File
@@ -26,6 +26,7 @@ pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
@@ -33,7 +34,11 @@ pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
vec![ValidatorDetails::new(
NYXD_URL,
Some(NYM_API),
Some(NYXD_WS),
)]
}
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
@@ -111,6 +116,7 @@ pub fn export_to_env() {
);
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
@@ -159,6 +165,7 @@ pub fn export_to_env_if_not_set() {
);
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
+1
View File
@@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
+1 -1
View File
@@ -255,7 +255,7 @@ fn bench_coconut(c: &mut Criterion) {
b.iter(|| {
verify_partial_blind_signature(
&params,
&blind_sign_request,
blind_sign_request.get_private_attributes_pedersen_commitments(),
&public_attributes,
random_blind_signature,
partial_verification_key,
+2
View File
@@ -43,3 +43,5 @@ mod utils;
pub type Attribute = bls12_381::Scalar;
pub type PrivateAttribute = Attribute;
pub type PublicAttribute = Attribute;
pub use bls12_381::G1Projective;
+7 -8
View File
@@ -362,12 +362,12 @@ pub fn blind_sign(
/// The function returns `true` if the partial blind signature is valid, and `false` otherwise.
pub fn verify_partial_blind_signature(
params: &Parameters,
blind_sign_request: &BlindSignRequest,
private_attribute_commitments: &[G1Projective],
public_attributes: &[&Attribute],
blind_sig: &BlindedSignature,
partial_verification_key: &VerificationKey,
) -> bool {
let num_private_attributes = blind_sign_request.private_attributes_commitments.len();
let num_private_attributes = private_attribute_commitments.len();
if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() {
return false;
}
@@ -388,8 +388,7 @@ pub fn verify_partial_blind_signature(
];
// for each private attribute, add (cm_i, beta_i) to the miller terms
for (private_attr_commit, beta_g2) in blind_sign_request
.private_attributes_commitments
for (private_attr_commit, beta_g2) in private_attribute_commitments
.iter()
.zip(&partial_verification_key.beta_g2)
{
@@ -422,7 +421,7 @@ pub fn verify_partial_blind_signature(
// is equivalent to checking e(a, b) • e(c, d)^{-1} == id
// and thus to e(a, b) • e(c^{-1}, d) == id
//
// compute e(c^1, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
// compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
multi_miller_loop(&terms_refs)
.final_exponentiation()
.is_identity()
@@ -519,7 +518,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator_keypair.verification_key()
@@ -539,7 +538,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&[],
&blind_sig,
validator_keypair.verification_key()
@@ -568,7 +567,7 @@ mod tests {
// this assertion should fail, as we try to verify with a wrong validator key
assert!(!verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator2_keypair.verification_key()
+3 -1
View File
@@ -99,7 +99,9 @@ impl SecretKey {
Self { x, ys }
}
pub fn into_raw(&self) -> (Scalar, Vec<Scalar>) {
/// Extract the Scalar copy of the underlying secrets.
/// The caller of this function must exercise extreme care to not misuse the data and ensuring it gets zeroized
pub fn hazmat_to_raw(&self) -> (Scalar, Vec<Scalar>) {
(self.x, self.ys.clone())
}
+6 -4
View File
@@ -228,10 +228,11 @@ pub fn check_vk_pairing(
// safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine
#[allow(clippy::unwrap_used)]
if &vk.alpha != *dkg_values.last().as_ref().unwrap() {
if &vk.alpha != *dkg_values.first().as_ref().unwrap() {
return false;
}
if dkg_values
let dkg_betas = &dkg_values[1..];
if dkg_betas
.iter()
.zip(vk.beta_g2.iter())
.any(|(dkg_beta, vk_beta)| dkg_beta != vk_beta)
@@ -329,8 +330,9 @@ mod tests {
let params = setup(2).unwrap();
let keypair = keygen(&params);
let vk = keypair.verification_key();
let mut dkg_values = vk.beta_g2.clone();
dkg_values.push(vk.alpha);
let mut dkg_values = vec![vk.alpha];
dkg_values.append(&mut vk.beta_g2.clone());
assert!(check_vk_pairing(&params, &dkg_values, vk));
}
+10 -1
View File
@@ -11,8 +11,10 @@ use crate::storage::{persist_block, ScraperStorage};
use futures::StreamExt;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::ops::{Add, Range};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
use tokio::sync::Notify;
use tokio::time::{interval_at, Instant};
use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_util::sync::CancellationToken;
@@ -39,6 +41,7 @@ impl PendingSync {
pub struct BlockProcessor {
cancel: CancellationToken,
synced: Arc<Notify>,
last_processed_height: u32,
last_processed_at: Instant,
pending_sync: PendingSync,
@@ -60,6 +63,7 @@ pub struct BlockProcessor {
impl BlockProcessor {
pub async fn new(
cancel: CancellationToken,
synced: Arc<Notify>,
incoming: UnboundedReceiver<BlockToProcess>,
block_requester: Sender<BlockRequest>,
storage: ScraperStorage,
@@ -69,6 +73,7 @@ impl BlockProcessor {
Ok(BlockProcessor {
cancel,
synced,
last_processed_height: last_processed.try_into().unwrap_or_default(),
last_processed_at: Instant::now(),
pending_sync: Default::default(),
@@ -243,6 +248,10 @@ impl BlockProcessor {
}
self.try_request_pending().await;
if self.pending_sync.is_empty() {
self.synced.notify_one();
}
}
async fn try_request_pending(&mut self) -> bool {
@@ -294,7 +303,7 @@ impl BlockProcessor {
error!("failed to perform startup sync: {err}");
self.cancel.cancel();
return;
}
};
loop {
tokio::select! {
+1
View File
@@ -16,3 +16,4 @@ pub mod storage;
pub use modules::{BlockModule, MsgModule, TxModule};
pub use scraper::{Config, NyxdScraper};
pub use storage::models;
+14 -1
View File
@@ -9,8 +9,10 @@ use crate::rpc_client::RpcClient;
use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber};
use crate::storage::ScraperStorage;
use std::path::PathBuf;
use std::sync::Arc;
use tendermint_rpc::WebSocketClientDriver;
use tokio::sync::mpsc::{channel, unbounded_channel};
use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use tracing::info;
@@ -54,6 +56,7 @@ impl NyxdScraperBuilder {
);
let mut block_processor = BlockProcessor::new(
scraper.cancel_token.clone(),
scraper.startup_sync.clone(),
processing_rx,
req_tx,
scraper.storage.clone(),
@@ -112,7 +115,8 @@ pub struct NyxdScraper {
task_tracker: TaskTracker,
cancel_token: CancellationToken,
storage: ScraperStorage,
startup_sync: Arc<Notify>,
pub storage: ScraperStorage,
}
impl NyxdScraper {
@@ -127,6 +131,7 @@ impl NyxdScraper {
config,
task_tracker: TaskTracker::new(),
cancel_token: CancellationToken::new(),
startup_sync: Arc::new(Default::default()),
storage,
})
}
@@ -164,6 +169,7 @@ impl NyxdScraper {
);
let block_processor = BlockProcessor::new(
self.cancel_token.clone(),
self.startup_sync.clone(),
processing_rx,
req_tx,
self.storage.clone(),
@@ -189,8 +195,15 @@ impl NyxdScraper {
Ok(())
}
pub async fn wait_for_startup_sync(&self) {
info!("awaiting startup chain sync");
self.startup_sync.notified().await
}
pub async fn stop(self) {
info!("stopping the chain scrapper");
assert!(self.task_tracker.is_closed());
self.cancel_token.cancel();
self.task_tracker.wait().await
}
+27 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::models::Validator;
use crate::storage::models::{CommitSignature, Validator};
use sqlx::types::time::OffsetDateTime;
use sqlx::{Executor, Sqlite};
use tracing::{instrument, trace};
@@ -84,8 +84,8 @@ impl StorageManager {
SELECT COUNT(*) as count FROM pre_commit
WHERE
validator_address == ?
AND height > ?
AND height < ?
AND height >= ?
AND height <= ?
"#,
consensus_address,
start_height,
@@ -98,6 +98,24 @@ impl StorageManager {
Ok(count)
}
pub(crate) async fn get_precommit(
&self,
consensus_address: &str,
height: i64,
) -> Result<Option<CommitSignature>, sqlx::Error> {
sqlx::query_as(
r#"
SELECT * FROM pre_commit
WHERE validator_address = ?
AND height = ?
"#,
)
.bind(consensus_address)
.bind(height)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn get_block_validators(
&self,
height: i64,
@@ -118,6 +136,12 @@ impl StorageManager {
.await
}
pub(crate) async fn get_validators(&self) -> Result<Vec<Validator>, sqlx::Error> {
sqlx::query_as("SELECT * FROM validator")
.fetch_all(&self.connection_pool)
.await
}
pub(crate) async fn get_last_processed_height(&self) -> Result<i64, sqlx::Error> {
let maybe_record = sqlx::query!(
r#"
+32 -4
View File
@@ -7,7 +7,7 @@ use crate::storage::manager::{
insert_block, insert_message, insert_precommit, insert_transaction, insert_validator,
update_last_processed, StorageManager,
};
use crate::storage::models::Validator;
use crate::storage::models::{CommitSignature, Validator};
use sqlx::types::time::OffsetDateTime;
use sqlx::{ConnectOptions, Sqlite, Transaction};
use std::fmt::Debug;
@@ -19,7 +19,7 @@ use tracing::{debug, error, info, instrument, trace, warn};
mod helpers;
mod manager;
mod models;
pub mod models;
pub type StorageTransaction = Transaction<'static, Sqlite>;
@@ -31,8 +31,6 @@ pub struct ScraperStorage {
impl ScraperStorage {
#[instrument]
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> {
// TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals?
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
@@ -91,6 +89,21 @@ impl ScraperStorage {
Ok(self.manager.get_last_block_height_before(time).await?)
}
pub async fn get_blocks_between(
&self,
start_time: OffsetDateTime,
end_time: OffsetDateTime,
) -> Result<i64, ScraperError> {
let Some(block_start) = self.get_first_block_height_after(start_time).await? else {
return Ok(0);
};
let Some(block_end) = self.get_last_block_height_before(end_time).await? else {
return Ok(0);
};
Ok(block_end - block_start)
}
pub async fn get_signed_between(
&self,
consensus_address: &str,
@@ -120,10 +133,25 @@ impl ScraperStorage {
.await
}
pub async fn get_precommit(
&self,
consensus_address: &str,
height: i64,
) -> Result<Option<CommitSignature>, ScraperError> {
Ok(self
.manager
.get_precommit(consensus_address, height)
.await?)
}
pub async fn get_block_signers(&self, height: i64) -> Result<Vec<Validator>, ScraperError> {
Ok(self.manager.get_block_validators(height).await?)
}
pub async fn get_all_known_validators(&self) -> Result<Vec<Validator>, ScraperError> {
Ok(self.manager.get_validators().await?)
}
pub async fn get_last_processed_height(&self) -> Result<i64, ScraperError> {
Ok(self.manager.get_last_processed_height().await?)
}
+1 -1
View File
@@ -4,7 +4,7 @@
use sqlx::types::time::OffsetDateTime;
use sqlx::FromRow;
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone, Eq, PartialEq, Hash, FromRow)]
pub struct Validator {
pub consensus_address: String,
pub consensus_pubkey: String,
+3 -3
View File
@@ -116,8 +116,8 @@ impl TaskManager {
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn catch_interrupt(mut self) -> Result<(), SentError> {
let res = crate::wait_for_signal_and_error(&mut self).await;
pub async fn catch_interrupt(&mut self) -> Result<(), SentError> {
let res = crate::wait_for_signal_and_error(self).await;
log::info!("Sending shutdown");
self.signal_shutdown().ok();
@@ -629,7 +629,7 @@ impl TaskHandle {
#[cfg(not(target_arch = "wasm32"))]
pub async fn wait_for_shutdown(self) -> Result<(), SentError> {
match self {
TaskHandle::Internal(task_manager) => task_manager.catch_interrupt().await,
TaskHandle::Internal(mut task_manager) => task_manager.catch_interrupt().await,
TaskHandle::External(mut task_client) => {
task_client.recv().await;
Ok(())
+6 -3
View File
@@ -1220,12 +1220,13 @@ dependencies = [
"cw-controllers",
"cw-multi-test",
"cw-storage-plus",
"cw2",
"cw4",
"cw4-group",
"lazy_static",
"nym-coconut-dkg-common",
"nym-group-contract-common",
"rusty-fork",
"semver",
"serde",
"thiserror",
]
@@ -1237,6 +1238,8 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-utils",
"cw2",
"cw4",
"nym-contracts-common",
"nym-multisig-contract-common",
]
@@ -1879,9 +1882,9 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.17"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0"
[[package]]
name = "serde"
+1
View File
@@ -48,5 +48,6 @@ cw3 = "=1.1.0"
cw3-fixed-multisig = "=1.1.0"
cw4 = "=1.1.0"
cw20 = "=1.1.0"
semver = "1.0.21"
thiserror = "1.0.48"
+2 -1
View File
@@ -20,15 +20,16 @@ cosmwasm-std = { workspace = true }
cosmwasm-storage = { workspace = true }
cw-storage-plus = { workspace = true }
cw-controllers = { workspace = true }
cw2 = { workspace = true }
cw4 = { workspace = true }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
semver = { workspace = true, default-features = false }
thiserror = { workspace = true }
[dev-dependencies]
cw-multi-test = { workspace = true }
cw4-group = { path = "../multisig/cw4-group" }
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
lazy_static = "1.4"
rusty-fork = "0.3"
[features]
+454 -42
View File
@@ -8,6 +8,7 @@
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
@@ -15,6 +16,12 @@
"group_addr": {
"type": "string"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
@@ -84,6 +91,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"oneOf": [
{
"type": "object",
"required": [
"initiate_dkg"
],
"properties": {
"initiate_dkg": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -95,6 +115,7 @@
"required": [
"announce_address",
"bte_key_with_proof",
"identity_key",
"resharing"
],
"properties": {
@@ -104,6 +125,9 @@
"bte_key_with_proof": {
"type": "string"
},
"identity_key": {
"type": "string"
},
"resharing": {
"type": "boolean"
}
@@ -122,12 +146,12 @@
"commit_dealing": {
"type": "object",
"required": [
"dealing_bytes",
"dealing",
"resharing"
],
"properties": {
"dealing_bytes": {
"$ref": "#/definitions/ContractSafeBytes"
"dealing": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
"type": "boolean"
@@ -227,6 +251,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
@@ -234,6 +276,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_state"
],
"properties": {
"get_state": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -352,6 +407,39 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealing_status"
],
"properties": {
"get_dealing_status": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -361,10 +449,47 @@
"get_dealing": {
"type": "object",
"required": [
"idx"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"idx": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings"
],
"properties": {
"get_dealings": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
@@ -379,9 +504,38 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_verification_key"
],
"properties": {
"get_verification_key": {
"type": "object",
"required": [
"epoch_id",
"owner"
],
"properties": {
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"type": "string"
}
},
"additionalProperties": false
@@ -425,6 +579,20 @@
}
},
"additionalProperties": false
},
{
"description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.",
"type": "object",
"required": [
"get_cw2_contract_version"
],
"properties": {
"get_cw2_contract_version": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
}
]
},
@@ -436,6 +604,26 @@
},
"sudo": null,
"responses": {
"get_c_w2_contract_version": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ContractVersion",
"type": "object",
"required": [
"contract",
"version"
],
"properties": {
"contract": {
"description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing",
"type": "string"
},
"version": {
"description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)",
"type": "string"
}
},
"additionalProperties": false
},
"get_current_dealers": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealerResponse",
@@ -480,7 +668,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -496,6 +685,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -508,7 +700,6 @@
"type": "object",
"required": [
"epoch_id",
"finish_timestamp",
"state",
"time_configuration"
],
@@ -519,7 +710,14 @@
"minimum": 0.0
},
"finish_timestamp": {
"$ref": "#/definitions/Timestamp"
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
},
"state": {
"$ref": "#/definitions/EpochState"
@@ -535,6 +733,7 @@
{
"type": "string",
"enum": [
"waiting_initialisation",
"in_progress"
]
},
@@ -744,7 +943,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -760,6 +960,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -776,34 +979,36 @@
},
"get_dealing": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"title": "DealingResponse",
"type": "object",
"required": [
"dealings",
"per_page"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/ContractDealing"
}
"dealer": {
"$ref": "#/definitions/Addr"
},
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"dealing": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
"$ref": "#/definitions/ContractSafeBytes"
},
{
"type": "null"
}
]
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
@@ -812,21 +1017,91 @@
"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"
},
"ContractDealing": {
"type": "object",
"required": [
"dealer",
"dealing"
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
},
"get_dealing_status": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingStatusResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"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"
}
}
},
"get_dealings": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"type": "object",
"required": [
"dealer",
"dealings",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/PartialContractDealing"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"integer",
"null"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing": {
"$ref": "#/definitions/ContractSafeBytes"
}
},
"additionalProperties": false
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false,
"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"
},
"ContractSafeBytes": {
"type": "array",
@@ -835,6 +1110,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
},
@@ -921,7 +1214,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -937,6 +1231,124 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
}
}
},
"get_state": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
"properties": {
"group_addr": {
"$ref": "#/definitions/Cw4Contract"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"$ref": "#/definitions/Addr"
}
},
"additionalProperties": false,
"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"
},
"Cw4Contract": {
"description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()",
"allOf": [
{
"$ref": "#/definitions/Addr"
}
]
}
}
},
"get_verification_key": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "VkShareResponse",
"type": "object",
"required": [
"epoch_id",
"owner"
],
"properties": {
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"$ref": "#/definitions/Addr"
},
"share": {
"anyOf": [
{
"$ref": "#/definitions/ContractVKShare"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"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"
},
"ContractVKShare": {
"type": "object",
"required": [
"announce_address",
"epoch_id",
"node_index",
"owner",
"share",
"verified"
],
"properties": {
"announce_address": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"node_index": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"$ref": "#/definitions/Addr"
},
"share": {
"type": "string"
},
"verified": {
"type": "boolean"
}
},
"additionalProperties": false
+38 -3
View File
@@ -2,6 +2,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"oneOf": [
{
"type": "object",
"required": [
"initiate_dkg"
],
"properties": {
"initiate_dkg": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -13,6 +26,7 @@
"required": [
"announce_address",
"bte_key_with_proof",
"identity_key",
"resharing"
],
"properties": {
@@ -22,6 +36,9 @@
"bte_key_with_proof": {
"type": "string"
},
"identity_key": {
"type": "string"
},
"resharing": {
"type": "boolean"
}
@@ -40,12 +57,12 @@
"commit_dealing": {
"type": "object",
"required": [
"dealing_bytes",
"dealing",
"resharing"
],
"properties": {
"dealing_bytes": {
"$ref": "#/definitions/ContractSafeBytes"
"dealing": {
"$ref": "#/definitions/PartialContractDealing"
},
"resharing": {
"type": "boolean"
@@ -145,6 +162,24 @@
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
@@ -4,6 +4,7 @@
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
@@ -11,6 +12,12 @@
"group_addr": {
"type": "string"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
+130 -4
View File
@@ -2,6 +2,19 @@
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"oneOf": [
{
"type": "object",
"required": [
"get_state"
],
"properties": {
"get_state": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -120,6 +133,39 @@
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealing_status"
],
"properties": {
"get_dealing_status": {
"type": "object",
"required": [
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
@@ -129,10 +175,47 @@
"get_dealing": {
"type": "object",
"required": [
"idx"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"idx": {
"dealer": {
"type": "string"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_dealings"
],
"properties": {
"get_dealings": {
"type": "object",
"required": [
"dealer",
"epoch_id"
],
"properties": {
"dealer": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
@@ -147,9 +230,38 @@
},
"start_after": {
"type": [
"string",
"integer",
"null"
]
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"get_verification_key"
],
"properties": {
"get_verification_key": {
"type": "object",
"required": [
"epoch_id",
"owner"
],
"properties": {
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"type": "string"
}
},
"additionalProperties": false
@@ -193,6 +305,20 @@
}
},
"additionalProperties": false
},
{
"description": "Gets the stored contract version information that's required by the CW2 spec interface for migrations.",
"type": "object",
"required": [
"get_cw2_contract_version"
],
"properties": {
"get_cw2_contract_version": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
}
]
}
@@ -0,0 +1,20 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ContractVersion",
"type": "object",
"required": [
"contract",
"version"
],
"properties": {
"contract": {
"description": "contract is the crate name of the implementing contract, eg. `crate:cw20-base` we will use other prefixes for other languages, and their standard global namespacing",
"type": "string"
},
"version": {
"description": "version is any string that this implementation knows. It may be simple counter \"1\", \"2\". or semantic version on release tags \"v0.7.0\", or some custom feature flag list. the only code that needs to understand the version parsing is code that knows how to migrate from the given contract (and is tied to it's implementation somehow)",
"type": "string"
}
},
"additionalProperties": false
}
@@ -42,7 +42,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -58,6 +59,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -4,7 +4,6 @@
"type": "object",
"required": [
"epoch_id",
"finish_timestamp",
"state",
"time_configuration"
],
@@ -15,7 +14,14 @@
"minimum": 0.0
},
"finish_timestamp": {
"$ref": "#/definitions/Timestamp"
"anyOf": [
{
"$ref": "#/definitions/Timestamp"
},
{
"type": "null"
}
]
},
"state": {
"$ref": "#/definitions/EpochState"
@@ -31,6 +37,7 @@
{
"type": "string",
"enum": [
"waiting_initialisation",
"in_progress"
]
},
@@ -32,7 +32,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -48,6 +49,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -1,33 +1,35 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"title": "DealingResponse",
"type": "object",
"required": [
"dealings",
"per_page"
"dealer",
"dealing_index",
"epoch_id"
],
"properties": {
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/ContractDealing"
}
"dealer": {
"$ref": "#/definitions/Addr"
},
"per_page": {
"type": "integer",
"format": "uint",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"dealing": {
"anyOf": [
{
"$ref": "#/definitions/Addr"
"$ref": "#/definitions/ContractSafeBytes"
},
{
"type": "null"
}
]
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
@@ -36,22 +38,6 @@
"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"
},
"ContractDealing": {
"type": "object",
"required": [
"dealer",
"dealing"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing": {
"$ref": "#/definitions/ContractSafeBytes"
}
},
"additionalProperties": false
},
"ContractSafeBytes": {
"type": "array",
"items": {
@@ -0,0 +1,36 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "DealingStatusResponse",
"type": "object",
"required": [
"dealer",
"dealing_index",
"dealing_submitted",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealing_index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"dealing_submitted": {
"type": "boolean"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
"additionalProperties": false,
"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"
}
}
}
@@ -0,0 +1,68 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "PagedDealingsResponse",
"type": "object",
"required": [
"dealer",
"dealings",
"epoch_id"
],
"properties": {
"dealer": {
"$ref": "#/definitions/Addr"
},
"dealings": {
"type": "array",
"items": {
"$ref": "#/definitions/PartialContractDealing"
}
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"start_next_after": {
"description": "Field indicating paging information for the following queries if the caller wishes to get further entries.",
"type": [
"integer",
"null"
],
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false,
"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"
},
"ContractSafeBytes": {
"type": "array",
"items": {
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
},
"PartialContractDealing": {
"type": "object",
"required": [
"data",
"index"
],
"properties": {
"data": {
"$ref": "#/definitions/ContractSafeBytes"
},
"index": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
}
}
@@ -42,7 +42,8 @@
"address",
"announce_address",
"assigned_index",
"bte_public_key_with_proof"
"bte_public_key_with_proof",
"ed25519_identity"
],
"properties": {
"address": {
@@ -58,6 +59,9 @@
},
"bte_public_key_with_proof": {
"type": "string"
},
"ed25519_identity": {
"type": "string"
}
},
"additionalProperties": false
@@ -0,0 +1,43 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "State",
"type": "object",
"required": [
"group_addr",
"key_size",
"mix_denom",
"multisig_addr"
],
"properties": {
"group_addr": {
"$ref": "#/definitions/Cw4Contract"
},
"key_size": {
"description": "Specifies the number of elements in the derived keys",
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"mix_denom": {
"type": "string"
},
"multisig_addr": {
"$ref": "#/definitions/Addr"
}
},
"additionalProperties": false,
"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"
},
"Cw4Contract": {
"description": "Cw4Contract is a wrapper around Addr that provides a lot of helpers for working with cw4 contracts\n\nIf you wish to persist this, convert to Cw4CanonicalContract via .canonical()",
"allOf": [
{
"$ref": "#/definitions/Addr"
}
]
}
}
}
@@ -0,0 +1,72 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "VkShareResponse",
"type": "object",
"required": [
"epoch_id",
"owner"
],
"properties": {
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"$ref": "#/definitions/Addr"
},
"share": {
"anyOf": [
{
"$ref": "#/definitions/ContractVKShare"
},
{
"type": "null"
}
]
}
},
"additionalProperties": false,
"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"
},
"ContractVKShare": {
"type": "object",
"required": [
"announce_address",
"epoch_id",
"node_index",
"owner",
"share",
"verified"
],
"properties": {
"announce_address": {
"type": "string"
},
"epoch_id": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"node_index": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
},
"owner": {
"$ref": "#/definitions/Addr"
},
"share": {
"type": "string"
},
"verified": {
"type": "boolean"
}
},
"additionalProperties": false
}
}
}
+131 -22
View File
@@ -1,20 +1,26 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::queries::{
query_current_dealers_paged, query_dealer_details, query_past_dealers_paged,
};
use crate::dealers::transactions::try_add_dealer;
use crate::dealings::queries::query_dealings_paged;
use crate::dealings::transactions::try_commit_dealings;
use crate::dealings::queries::{
query_dealer_dealings_status, query_dealing_chunk, query_dealing_chunk_status,
query_dealing_metadata, query_dealing_status,
};
use crate::dealings::transactions::{try_commit_dealings_chunk, try_submit_dealings_metadata};
use crate::epoch_state::queries::{
query_current_epoch, query_current_epoch_threshold, query_initial_dealers,
};
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::{advance_epoch_state, try_surpassed_threshold};
use crate::epoch_state::transactions::{
advance_epoch_state, try_initiate_dkg, try_surpassed_threshold,
};
use crate::error::ContractError;
use crate::state::{State, MULTISIG, STATE};
use crate::verification_key_shares::queries::query_vk_shares_paged;
use crate::state::queries::query_state;
use crate::state::storage::{DKG_ADMIN, MULTISIG, STATE};
use crate::verification_key_shares::queries::{query_vk_share, query_vk_shares_paged};
use crate::verification_key_shares::transactions::try_commit_verification_key_share;
use crate::verification_key_shares::transactions::try_verify_verification_key_share;
use cosmwasm_std::{
@@ -22,7 +28,11 @@ use cosmwasm_std::{
};
use cw4::Cw4Contract;
use nym_coconut_dkg_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
use nym_coconut_dkg_common::types::{Epoch, EpochState};
use nym_coconut_dkg_common::types::{Epoch, EpochState, State};
use semver::Version;
const CONTRACT_NAME: &str = "crate:nym-coconut-dkg";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
/// Instantiate the contract.
///
@@ -33,13 +43,15 @@ use nym_coconut_dkg_common::types::{Epoch, EpochState};
pub fn instantiate(
mut deps: DepsMut<'_>,
env: Env,
_info: MessageInfo,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
let multisig_addr = deps.api.addr_validate(&msg.multisig_addr)?;
MULTISIG.set(deps.branch(), Some(multisig_addr.clone()))?;
let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
DKG_ADMIN.set(deps.branch(), Some(info.sender))?;
let group_addr = Cw4Contract::new(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
ContractError::InvalidGroup {
addr: msg.group_addr.clone(),
}
@@ -49,19 +61,22 @@ pub fn instantiate(
group_addr,
multisig_addr,
mix_denom: msg.mix_denom,
key_size: msg.key_size,
};
STATE.save(deps.storage, &state)?;
CURRENT_EPOCH.save(
deps.storage,
&Epoch::new(
EpochState::default(),
EpochState::WaitingInitialisation,
0,
msg.time_configuration.unwrap_or_default(),
env.block.time,
),
)?;
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Response::default())
}
@@ -74,15 +89,28 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::InitiateDkg {} => try_initiate_dkg(deps, env, info),
ExecuteMsg::RegisterDealer {
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => try_add_dealer(deps, info, bte_key_with_proof, announce_address, resharing),
ExecuteMsg::CommitDealing {
dealing_bytes,
} => try_add_dealer(
deps,
info,
bte_key_with_proof,
identity_key,
announce_address,
resharing,
} => try_commit_dealings(deps, info, dealing_bytes, resharing),
),
ExecuteMsg::CommitDealingsMetadata {
dealing_index,
chunks,
resharing,
} => try_submit_dealings_metadata(deps, info, dealing_index, chunks, resharing),
ExecuteMsg::CommitDealingsChunk { chunk, resharing } => {
try_commit_dealings_chunk(deps, env, info, chunk, resharing)
}
ExecuteMsg::CommitVerificationKeyShare { share, resharing } => {
try_commit_verification_key_share(deps, env, info, share, resharing)
}
@@ -97,6 +125,7 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let response = match msg {
QueryMsg::GetState {} => to_binary(&query_state(deps.storage)?)?,
QueryMsg::GetCurrentEpochState {} => to_binary(&query_current_epoch(deps.storage)?)?,
QueryMsg::GetCurrentEpochThreshold {} => {
to_binary(&query_current_epoch_threshold(deps.storage)?)?
@@ -111,24 +140,90 @@ pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse,
QueryMsg::GetPastDealers { limit, start_after } => {
to_binary(&query_past_dealers_paged(deps, start_after, limit)?)?
}
QueryMsg::GetDealing {
idx,
limit,
start_after,
} => to_binary(&query_dealings_paged(deps, idx, start_after, limit)?)?,
QueryMsg::GetDealingsMetadata {
epoch_id,
dealer,
dealing_index,
} => to_binary(&query_dealing_metadata(
deps,
epoch_id,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealerDealingsStatus { epoch_id, dealer } => {
to_binary(&query_dealer_dealings_status(deps, epoch_id, dealer)?)?
}
QueryMsg::GetDealingStatus {
epoch_id,
dealer,
dealing_index,
} => to_binary(&query_dealing_status(
deps,
epoch_id,
dealer,
dealing_index,
)?)?,
QueryMsg::GetDealingChunkStatus {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => to_binary(&query_dealing_chunk_status(
deps,
epoch_id,
dealer,
dealing_index,
chunk_index,
)?)?,
QueryMsg::GetDealingChunk {
epoch_id,
dealer,
dealing_index,
chunk_index,
} => to_binary(&query_dealing_chunk(
deps,
epoch_id,
dealer,
dealing_index,
chunk_index,
)?)?,
QueryMsg::GetVerificationKey { owner, epoch_id } => {
to_binary(&query_vk_share(deps, owner, epoch_id)?)?
}
QueryMsg::GetVerificationKeys {
epoch_id,
limit,
start_after,
} => to_binary(&query_vk_shares_paged(deps, epoch_id, start_after, limit)?)?,
QueryMsg::GetCW2ContractVersion {} => to_binary(&cw2::get_contract_version(deps.storage)?)?,
};
Ok(response)
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
Ok(Default::default())
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
fn parse_semver(raw: &str) -> Result<Version, ContractError> {
raw.parse()
.map_err(|error: semver::Error| ContractError::SemVerFailure {
value: CONTRACT_VERSION.to_string(),
error_message: error.to_string(),
})
}
// Note: don't remove this particular bit of code as we have to ALWAYS check whether we have to
// update the stored version
let build_version: Version = parse_semver(CONTRACT_VERSION)?;
let stored_version: Version = parse_semver(&cw2::get_contract_version(deps.storage)?.version)?;
if stored_version < build_version {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
// If state structure changed in any contract version in the way migration is needed, it
// should occur here, for example anything from `crate::queued_migrations::`
}
Ok(Response::new())
}
#[cfg(test)]
@@ -140,7 +235,8 @@ mod tests {
use cosmwasm_std::{coins, Addr};
use cw4::Member;
use cw_multi_test::{App, AppBuilder, AppResponse, ContractWrapper, Executor};
use nym_coconut_dkg_common::msg::ExecuteMsg::RegisterDealer;
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use nym_coconut_dkg_common::msg::ExecuteMsg::{InitiateDkg, RegisterDealer};
use nym_coconut_dkg_common::types::NodeIndex;
use nym_group_contract_common::msg::InstantiateMsg as GroupInstantiateMsg;
@@ -178,6 +274,7 @@ mod tests {
multisig_addr: MULTISIG_CONTRACT.to_string(),
time_configuration: None,
mix_denom: TEST_MIX_DENOM.to_string(),
key_size: DEFAULT_DEALINGS as u32,
};
app.instantiate_contract(
coconut_dkg_code_id,
@@ -213,6 +310,7 @@ mod tests {
multisig_addr: "multisig_addr".to_string(),
time_configuration: None,
mix_denom: "nym".to_string(),
key_size: 5,
};
let info = mock_info("creator", &[]);
@@ -235,6 +333,14 @@ mod tests {
});
let coconut_dkg_contract_addr = instantiate_with_group(&mut app, &members);
app.execute_contract(
Addr::unchecked(ADMIN_ADDRESS),
coconut_dkg_contract_addr.clone(),
&InitiateDkg {},
&[],
)
.unwrap();
for (idx, member) in members.iter().enumerate() {
let res = app
.execute_contract(
@@ -242,6 +348,7 @@ mod tests {
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -256,6 +363,7 @@ mod tests {
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -272,6 +380,7 @@ mod tests {
coconut_dkg_contract_addr,
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
@@ -5,7 +5,7 @@ use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::state::storage::STATE;
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
use nym_coconut_dkg_common::types::{DealerDetails, EncodedBTEPublicKeyWithProof, EpochState};
@@ -34,10 +34,13 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
Ok(())
}
// future optimisation:
// for a recurring dealer just let it refresh the keys without having to do all the storage operations
pub fn try_add_dealer(
mut deps: DepsMut<'_>,
info: MessageInfo,
bte_key_with_proof: EncodedBTEPublicKeyWithProof,
identity_key: String,
announce_address: String,
resharing: bool,
) -> Result<Response, ContractError> {
@@ -65,6 +68,7 @@ pub fn try_add_dealer(
let dealer_details = DealerDetails {
address: info.sender.clone(),
bte_public_key_with_proof: bte_key_with_proof,
ed25519_identity: identity_key,
announce_address,
assigned_index: node_index,
};
@@ -77,10 +81,10 @@ pub fn try_add_dealer(
pub(crate) mod tests {
use super::*;
use crate::dealers::storage::current_dealers;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_fixture_dealer, GROUP_MEMBERS};
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, GROUP_MEMBERS};
use cosmwasm_std::testing::{mock_env, mock_info};
use cw4::Member;
use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
@@ -137,10 +141,13 @@ pub(crate) mod tests {
#[test]
fn invalid_state() {
let mut deps = helpers::init_contract();
let owner = Addr::unchecked("owner");
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = Addr::unchecked("owner");
let info = mock_info(owner.as_str(), &[]);
let bte_key_with_proof = String::from("bte_key_with_proof");
let identity = String::from("identity");
let announce_address = String::from("localhost:8000");
env.block.time = env
@@ -155,6 +162,7 @@ pub(crate) mod tests {
deps.as_mut(),
info,
bte_key_with_proof,
identity,
announce_address,
false,
)
@@ -163,7 +171,7 @@ pub(crate) mod tests {
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::DealingExchange { resharing: false }.to_string(),
expected_state: EpochState::default().to_string(),
expected_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
}
);
}
+342 -184
View File
@@ -1,195 +1,353 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealings::storage;
use crate::dealings::storage::DEALINGS_BYTES;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::dealer::{ContractDealing, PagedDealingsResponse};
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
use crate::dealings::storage::{StoredDealing, DEALINGS_METADATA};
use crate::state::storage::STATE;
use cosmwasm_std::{Deps, StdResult};
use nym_coconut_dkg_common::dealing::{
DealerDealingsStatusResponse, DealingChunkResponse, DealingChunkStatusResponse,
DealingMetadataResponse, DealingStatus, DealingStatusResponse,
};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId};
use std::collections::BTreeMap;
pub fn query_dealings_paged(
/// Get the metadata associated with the particular dealing
pub fn query_dealing_metadata(
deps: Deps<'_>,
idx: u64,
start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<PagedDealingsResponse> {
let limit = limit
.unwrap_or(storage::DEALINGS_PAGE_DEFAULT_LIMIT)
.min(storage::DEALINGS_PAGE_MAX_LIMIT) as usize;
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingMetadataResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
let idx = idx as usize;
if idx >= TOTAL_DEALINGS {
return Ok(PagedDealingsResponse::new(vec![], limit, None));
}
let addr = start_after
.map(|addr| deps.api.addr_validate(&addr))
.transpose()?;
let start = addr.as_ref().map(Bound::exclusive);
let dealings = DEALINGS_BYTES[idx]
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|res| res.map(|(dealer, dealing)| ContractDealing::new(dealing, dealer)))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = dealings.last().map(|dealing| dealing.dealer.clone());
Ok(PagedDealingsResponse::new(
dealings,
limit,
start_next_after,
))
Ok(DealingMetadataResponse {
epoch_id,
dealer,
dealing_index,
metadata,
})
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT};
use crate::support::tests::fixtures::dealing_bytes_fixture;
use crate::support::tests::helpers::init_contract;
use cosmwasm_std::{Addr, DepsMut};
/// Get the status of all dealings of particular dealer for given epoch.
pub fn query_dealer_dealings_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
) -> StdResult<DealerDealingsStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let state = STATE.load(deps.storage)?;
fn fill_dealings(deps: DepsMut<'_>, size: usize) {
for n in 0..size {
let dealing_share = dealing_bytes_fixture();
let sender = Addr::unchecked(format!("owner{}", n));
(0..TOTAL_DEALINGS).for_each(|idx| {
DEALINGS_BYTES[idx]
.save(deps.storage, &sender, &dealing_share)
.unwrap();
});
}
let mut dealing_submission_status: BTreeMap<DealingIndex, DealingStatus> = BTreeMap::new();
// Since our key size is in single digit range, querying all of this at once on chain is fine
for dealing_index in 0..state.key_size {
let metadata =
DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
dealing_submission_status.insert(dealing_index, metadata.into());
}
#[test]
fn empty_on_bad_idx() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
for idx in TOTAL_DEALINGS as u64..100 * TOTAL_DEALINGS as u64 {
let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap();
assert_eq!(0, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_empty_on_init() {
let deps = init_contract();
for idx in 0..TOTAL_DEALINGS as u64 {
let response = query_dealings_paged(deps.as_ref(), idx, None, Option::from(2)).unwrap();
assert_eq!(0, response.dealings.len());
}
}
#[test]
fn dealings_paged_retrieval_obeys_limits() {
let mut deps = init_contract();
let limit = 2;
fill_dealings(deps.as_mut(), 1000);
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(limit)).unwrap();
assert_eq!(limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_default_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
for idx in 0..TOTAL_DEALINGS as u64 {
// query without explicitly setting a limit
let page1 = query_dealings_paged(deps.as_ref(), idx, None, None).unwrap();
assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_paged_retrieval_has_max_limit() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1000);
// query with a crazily high limit in an attempt to use too many resources
let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT;
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(crazy_limit)).unwrap();
// we default to a decent sized upper bound instead
let expected_limit = DEALINGS_PAGE_MAX_LIMIT;
assert_eq!(expected_limit, page1.dealings.len() as u32);
}
}
#[test]
fn dealings_pagination_works() {
let mut deps = init_contract();
fill_dealings(deps.as_mut(), 1);
let per_page = 2;
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
// page should have 1 result on it
assert_eq!(1, page1.dealings.len());
}
// save another
fill_dealings(deps.as_mut(), 2);
for idx in 0..TOTAL_DEALINGS as u64 {
// page1 should have 2 results on it
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.dealings.len());
}
fill_dealings(deps.as_mut(), 3);
for idx in 0..TOTAL_DEALINGS as u64 {
// page1 still has 2 results
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
assert_eq!(2, page1.dealings.len());
// retrieving the next page should start after the last key on this page
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
idx,
Option::from(start_after.to_string()),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.dealings.len());
}
fill_dealings(deps.as_mut(), 4);
for idx in 0..TOTAL_DEALINGS as u64 {
let page1 =
query_dealings_paged(deps.as_ref(), idx, None, Option::from(per_page)).unwrap();
let start_after = page1.start_next_after.unwrap();
let page2 = query_dealings_paged(
deps.as_ref(),
idx,
Option::from(start_after.to_string()),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.dealings.len());
}
}
Ok(DealerDealingsStatusResponse {
epoch_id,
dealer,
all_dealings_fully_submitted: dealing_submission_status
.values()
.all(|d| d.fully_submitted),
dealing_submission_status,
})
}
/// Get the status of particular dealing, i.e. whether it has been fully submitted.
pub fn query_dealing_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
) -> StdResult<DealingStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
Ok(DealingStatusResponse {
epoch_id,
dealer,
dealing_index,
status: metadata.into(),
})
}
/// Get the status of particular chunk, i.e. whether (and when) it has been fully submitted.
pub fn query_dealing_chunk_status(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> StdResult<DealingChunkStatusResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let metadata = DEALINGS_METADATA.may_load(deps.storage, (epoch_id, &dealer, dealing_index))?;
let status = metadata
.as_ref()
.and_then(|m| m.submitted_chunks.get(&chunk_index))
.map(|&c| c.status)
.unwrap_or_default();
Ok(DealingChunkStatusResponse {
epoch_id,
dealer,
dealing_index,
chunk_index,
status,
})
}
/// Get the particular chunk of the dealing.
pub fn query_dealing_chunk(
deps: Deps<'_>,
epoch_id: EpochId,
dealer: String,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> StdResult<DealingChunkResponse> {
let dealer = deps.api.addr_validate(&dealer)?;
let chunk = StoredDealing::read(deps.storage, epoch_id, &dealer, dealing_index, chunk_index);
Ok(DealingChunkResponse {
epoch_id,
dealer,
dealing_index,
chunk_index,
chunk,
})
}
// #[cfg(test)]
// pub(crate) mod tests {
// use super::*;
// use crate::dealings::storage::{DEALINGS_PAGE_DEFAULT_LIMIT, DEALINGS_PAGE_MAX_LIMIT};
// use crate::support::tests::fixtures::{dealing_bytes_fixture, partial_dealing_fixture};
// use crate::support::tests::helpers::init_contract;
// use cosmwasm_std::{Addr, DepsMut};
// use nym_coconut_dkg_common::types::PartialContractDealing;
//
// fn fill_dealings(deps: DepsMut<'_>, epoch: EpochId, dealers: usize, key_size: u32) {
// for i in 0..dealers {
// let dealer = Addr::unchecked(format!("dealer{i}"));
// for dealing_index in 0..key_size {
// StoredDealing::save(
// deps.storage,
// epoch,
// &dealer,
// PartialContractDealing {
// dealing_index: dealing_index,
// data: dealing_bytes_fixture(),
// },
// )
// }
// }
// }
//
// #[test]
// fn test_query_dealing() {
// let mut deps = init_contract();
//
// let bad_address = "FOOMP".to_string();
// assert!(query_dealing(deps.as_ref(), 0, bad_address, 0).is_err());
//
// let empty = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(empty.epoch_id, 0);
// assert_eq!(empty.dealing_index, 0);
// assert_eq!(empty.dealer, Addr::unchecked("foo"));
// assert!(empty.dealing.is_none());
//
// // insert the dealing
// let dealing = partial_dealing_fixture();
// StoredDealing::save(
// deps.as_mut().storage,
// 0,
// &Addr::unchecked("foo"),
// dealing.clone(),
// );
//
// let retrieved = query_dealing(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(retrieved.epoch_id, 0);
// assert_eq!(retrieved.dealing_index, dealing.dealing_index);
// assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
// assert_eq!(retrieved.dealing.unwrap(), dealing.data);
// }
//
// #[test]
// fn test_query_dealing_status() {
// let mut deps = init_contract();
//
// let bad_address = "FOOMP".to_string();
// assert!(query_dealing_status(deps.as_ref(), 0, bad_address, 0).is_err());
//
// let empty = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(empty.epoch_id, 0);
// assert_eq!(empty.dealing_index, 0);
// assert_eq!(empty.dealer, Addr::unchecked("foo"));
// assert!(!empty.dealing_submitted);
//
// // insert the dealing
// let dealing = partial_dealing_fixture();
// StoredDealing::save(
// deps.as_mut().storage,
// 0,
// &Addr::unchecked("foo"),
// dealing.clone(),
// );
//
// let retrieved = query_dealing_status(deps.as_ref(), 0, "foo".to_string(), 0).unwrap();
// assert_eq!(retrieved.epoch_id, 0);
// assert_eq!(retrieved.dealing_index, dealing.dealing_index);
// assert_eq!(retrieved.dealer, Addr::unchecked("foo"));
// assert!(retrieved.dealing_submitted)
// }
//
// #[cfg(test)]
// mod query_dealings {
// use super::*;
// use nym_coconut_dkg_common::types::DEFAULT_DEALINGS;
//
// #[test]
// fn dealings_empty_on_init() {
// let deps = init_contract();
// let all_dealings = StoredDealing::unchecked_all_entries(&deps.storage);
// assert!(all_dealings.is_empty())
// }
//
// #[test]
// fn dealings_paged_retrieval_obeys_limits() {
// let mut deps = init_contract();
// let limit = 2;
// fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// let page1 =
// query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(limit))
// .unwrap();
// assert_eq!(limit, page1.dealings.len() as u32);
// }
// }
//
// #[test]
// fn dealings_paged_retrieval_has_default_limit() {
// let mut deps = init_contract();
// fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// // query without explicitly setting a limit
// let page1 = query_dealings_paged(deps.as_ref(), 0, dealer, None, None).unwrap();
//
// assert_eq!(DEALINGS_PAGE_DEFAULT_LIMIT, page1.dealings.len() as u32);
// }
// }
//
// #[test]
// fn dealings_paged_retrieval_has_max_limit() {
// let mut deps = init_contract();
// fill_dealings(deps.as_mut(), 0, 10, DEFAULT_DEALINGS as u32);
//
// // query with a crazily high limit in an attempt to use too many resources
// let crazy_limit = 1000 * DEALINGS_PAGE_MAX_LIMIT;
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// let page1 =
// query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(crazy_limit))
// .unwrap();
//
// // we default to a decent sized upper bound instead
// let expected_limit = DEALINGS_PAGE_MAX_LIMIT;
// assert_eq!(expected_limit, page1.dealings.len() as u32);
// }
// }
//
// #[test]
// fn dealings_pagination_works() {
// let mut deps = init_contract();
//
// fill_dealings(deps.as_mut(), 0, 10, 1);
// let per_page = 2;
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// let page1 =
// query_dealings_paged(deps.as_ref(), 0, dealer, None, Option::from(per_page))
// .unwrap();
//
// // page should have 1 result on it
// assert_eq!(1, page1.dealings.len());
// }
//
// // save another
// fill_dealings(deps.as_mut(), 1, 10, 2);
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// // page1 should have 2 results on it
// let page1 =
// query_dealings_paged(deps.as_ref(), 1, dealer, None, Option::from(per_page))
// .unwrap();
// assert_eq!(2, page1.dealings.len());
// }
//
// fill_dealings(deps.as_mut(), 3, 10, 3);
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// // page1 still has 2 results
// let page1 = query_dealings_paged(
// deps.as_ref(),
// 3,
// dealer.clone(),
// None,
// Option::from(per_page),
// )
// .unwrap();
// assert_eq!(2, page1.dealings.len());
//
// // retrieving the next page should start after the last key on this page
// let start_after = page1.start_next_after.unwrap();
// let page2 = query_dealings_paged(
// deps.as_ref(),
// 3,
// dealer,
// Option::from(start_after),
// Option::from(per_page),
// )
// .unwrap();
//
// assert_eq!(1, page2.dealings.len());
// }
//
// fill_dealings(deps.as_mut(), 4, 10, 4);
//
// for dealer in 0..10 {
// let dealer = format!("dealer{dealer}");
// let page1 = query_dealings_paged(
// deps.as_ref(),
// 4,
// dealer.clone(),
// None,
// Option::from(per_page),
// )
// .unwrap();
// let start_after = page1.start_next_after.unwrap();
// let page2 = query_dealings_paged(
// deps.as_ref(),
// 4,
// dealer,
// 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.dealings.len());
// }
// }
// }
// }
+415 -27
View File
@@ -1,33 +1,421 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw_storage_plus::Map;
use nym_coconut_dkg_common::types::{ContractSafeBytes, TOTAL_DEALINGS};
use crate::error::ContractError;
use cosmwasm_std::{Addr, Order, Record, StdResult, Storage};
use cw_storage_plus::{Bound, Key, KeyDeserialize, Map, Path, Prefix, Prefixer, PrimaryKey};
use nym_coconut_dkg_common::dealing::{DealingMetadata, PartialContractDealing};
use nym_coconut_dkg_common::types::{
ChunkIndex, ContractSafeBytes, DealingIndex, EpochId, PartialContractDealingData,
};
pub(crate) const DEALINGS_PAGE_MAX_LIMIT: u32 = 2;
pub(crate) const DEALINGS_PAGE_DEFAULT_LIMIT: u32 = 1;
type Dealer<'a> = &'a Addr;
type DealingKey<'a> = &'a Addr;
/// Metadata for a dealing for given `EpochId`, submitted by particular `Dealer` for given `DealingIndex`.
pub(crate) const DEALINGS_METADATA: Map<(EpochId, Dealer, DealingIndex), DealingMetadata> =
Map::new("dealings_metadata");
// Note to whoever is looking at this implementation and is thinking of using something similar
// for storing small commitments/hashes of data on chain:
// If there's a lot of entries you want to store thinking, "oh, this digest is only 32 bytes, it's not that much",
// the default cosmwasm' serializer will bloat it to around ~100B. So you really don't want to be using
// Buckets/Maps, etc. for that purpose. Instead you want to use `storage` directly (look into the actual implementation of
// `Map` or `Bucket` to see what I mean. Instead of using the `to_vec` method on serde_json_wasm, you'd
// provide your data directly yourself.
// but you must be extremely careful when doing so, as you might end up overwriting some existing data
// if you don't choose your prefixes wisely.
// I didn't have to do it here as I'm storing relatively little data and after just base58-encoding
// my bytes, I was fine with the json overhead.
pub(crate) fn metadata_exists(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> bool {
DEALINGS_METADATA.has(storage, (epoch_id, dealer, dealing_index))
}
// if TOTAL_DEALINGS is modified to anything other then current value (5), this part will also need
// to be modified
pub(crate) const DEALINGS_BYTES: [Map<'_, DealingKey<'_>, ContractSafeBytes>; TOTAL_DEALINGS] = [
Map::new("dbyt1"),
Map::new("dbyt2"),
Map::new("dbyt3"),
Map::new("dbyt4"),
Map::new("dbyt5"),
];
pub(crate) fn may_read_metadata(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Result<Option<DealingMetadata>, ContractError> {
Ok(DEALINGS_METADATA.may_load(storage, (epoch_id, dealer, dealing_index))?)
}
pub(crate) fn must_read_metadata(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
) -> Result<DealingMetadata, ContractError> {
DEALINGS_METADATA
.may_load(storage, (epoch_id, dealer, dealing_index))?
.ok_or_else(|| ContractError::UnavailableDealingMetadata {
epoch_id,
dealer: dealer.to_owned(),
dealing_index,
})
}
pub(crate) fn store_metadata(
storage: &mut dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
metadata: &DealingMetadata,
) -> Result<(), ContractError> {
Ok(DEALINGS_METADATA.save(storage, (epoch_id, dealer, dealing_index), metadata)?)
}
// dealings data is stored in a multilevel map with the following hierarchy:
// - epoch-id:
// - issuer-address:
// - dealing id:
// - chunk_id:
// - dealing content
// NOTE: we're storing raw bytes bypassing serialization, so we can't use the `Map` type,
// thus make sure you always use the below methods for using the storage!
pub(crate) struct StoredDealing;
impl StoredDealing {
const NAMESPACE: &'static [u8] = b"dealing";
fn deserialize_dealing_record(
kv: Record,
) -> StdResult<(ChunkIndex, PartialContractDealingData)> {
let (k, v) = kv;
let index = <ChunkIndex as KeyDeserialize>::from_vec(k)?;
let data = ContractSafeBytes(v);
Ok((index, data))
}
fn storage_key(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Path<Vec<u8>> {
// just replicate the behaviour from `Map::key`
// note: `PrimaryKey` trait is not implemented for tuple (T, U, V, W), only for up to (T, U, V)
// that's why we create a (T, U, (V, W)) tuple(s) instead
let key = (epoch_id, dealer, (dealing_index, chunk_index));
Path::new(
Self::NAMESPACE,
&key.key().iter().map(Key::as_ref).collect::<Vec<_>>(),
)
}
fn prefix(
prefix: (EpochId, Dealer, DealingIndex),
) -> Prefix<ChunkIndex, PartialContractDealingData, ChunkIndex> {
Prefix::with_deserialization_functions(
Self::NAMESPACE,
&prefix.prefix(),
&[],
// explicitly panic to make sure we're never attempting to call an unexpected deserializer on our data
|_, _, kv| Self::deserialize_dealing_record(kv),
|_, _, _| panic!("attempted to call custom de_fn_v"),
)
}
pub(crate) fn exists(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: &Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> StdResult<bool> {
// whenever the dealing is saved, the metadata is appropriately updated
// reading metadata is way cheaper than the dealing chunk itself
let Some(metadata) =
DEALINGS_METADATA.may_load(storage, (epoch_id, dealer, dealing_index))?
else {
return Ok(false);
};
let Some(chunk_info) = metadata.submitted_chunks.get(&chunk_index) else {
return Ok(false);
};
Ok(chunk_info.status.submitted())
// StoredDealing::storage_key(epoch_id, dealer, dealing_index).has(storage)
}
pub(crate) fn save(
storage: &mut dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealng_chunk: PartialContractDealing,
) {
// NOTE: we're storing bytes directly here!
let storage_key = Self::storage_key(
epoch_id,
dealer,
dealng_chunk.dealing_index,
dealng_chunk.chunk_index,
);
storage.set(&storage_key, dealng_chunk.data.as_slice());
}
pub(crate) fn read(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> Option<PartialContractDealingData> {
let storage_key = Self::storage_key(epoch_id, dealer, dealing_index, chunk_index);
storage.get(&storage_key).map(ContractSafeBytes)
}
pub(crate) fn prefix_range<'a>(
storage: &'a dyn Storage,
prefix: (EpochId, Dealer, DealingIndex),
start: Option<Bound<ChunkIndex>>,
) -> impl Iterator<Item = StdResult<PartialContractDealing>> + 'a {
let dealing_index = prefix.2;
Self::prefix(prefix)
.range(storage, start, None, Order::Ascending)
.map(move |maybe_record| {
maybe_record.map(|(chunk_index, data)| PartialContractDealing {
dealing_index,
chunk_index,
data,
})
})
}
// iterate over all values, only to be used in tests due to the amount of data being returned
#[cfg(test)]
#[allow(clippy::type_complexity)]
pub(crate) fn unchecked_all_entries(
storage: &dyn Storage,
) -> Vec<(
(EpochId, Addr, (DealingIndex, ChunkIndex)),
PartialContractDealingData,
)> {
type StorageKey<'a> = (EpochId, Dealer<'a>, (DealingIndex, ChunkIndex));
let empty_prefix: Prefix<StorageKey, PartialContractDealingData, StorageKey> =
Prefix::with_deserialization_functions(
Self::NAMESPACE,
&[],
&[],
|_, _, kv| StorageKey::from_vec(kv.0).map(|kt| (kt, ContractSafeBytes(kv.1))),
|_, _, _| unimplemented!(),
);
empty_prefix
.range(storage, None, None, Order::Ascending)
.collect::<StdResult<_>>()
.unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::support::tests::helpers::init_contract;
use std::collections::HashMap;
fn dealing_data(
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> PartialContractDealingData {
ContractSafeBytes(
format!("{epoch_id},{dealer},{dealing_index},{chunk_index}")
.as_bytes()
.to_vec(),
)
}
#[test]
fn saving_dealing_chunks() {
let mut deps = init_contract();
fn exists_in_storage(
storage: &dyn Storage,
epoch_id: EpochId,
dealer: Dealer,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
) -> bool {
StoredDealing::storage_key(epoch_id, dealer, dealing_index, chunk_index).has(storage)
}
// make sure to check all combinations of epoch id, dealer address and dealing index to ensure nothing overlaps
let epochs = [54, 423, 754];
let dealers = [
Addr::unchecked("dealer1"),
Addr::unchecked("dealer2"),
Addr::unchecked("dealer3"),
Addr::unchecked("dealer4"),
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
let chunk_indices = [0, 1, 2, 3, 4];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
for chunk_index in &chunk_indices {
assert!(!exists_in_storage(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index
));
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
dealing_index: *dealing_index,
chunk_index: *chunk_index,
data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index),
},
)
}
}
}
}
let all: HashMap<_, _> = StoredDealing::unchecked_all_entries(&deps.storage)
.into_iter()
.collect();
assert_eq!(
all.len(),
epochs.len() * dealers.len() * dealing_indices.len() * chunk_indices.len()
);
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
for chunk_index in &chunk_indices {
assert!(exists_in_storage(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index
));
let content = StoredDealing::read(
&deps.storage,
*epoch_id,
dealer,
*dealing_index,
*chunk_index,
)
.unwrap();
let expected =
dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index);
assert_eq!(expected, content);
assert_eq!(
&expected,
all.get(&(*epoch_id, dealer.clone(), (*dealing_index, *chunk_index)))
.unwrap()
);
}
}
}
}
}
#[test]
fn iterating_over_dealing_chunks() {
let mut deps = init_contract();
let epochs = [54, 423, 754];
let dealers = [
Addr::unchecked("dealer1"),
Addr::unchecked("dealer2"),
Addr::unchecked("dealer3"),
Addr::unchecked("dealer4"),
Addr::unchecked("dealer5"),
];
let dealing_indices = [0, 1, 2, 3, 4, 5, 6, 7];
let chunk_indices = [0, 1, 2, 3, 4];
for epoch_id in &epochs {
for dealer in &dealers {
for dealing_index in &dealing_indices {
for chunk_index in &chunk_indices {
StoredDealing::save(
deps.as_mut().storage,
*epoch_id,
dealer,
PartialContractDealing {
dealing_index: *dealing_index,
chunk_index: *chunk_index,
data: dealing_data(*epoch_id, dealer, *dealing_index, *chunk_index),
},
)
}
}
}
}
// remember, we're not testing the iterator implementation
// nothing under epoch 0
let dealings =
StoredDealing::prefix_range(&deps.storage, (0, &dealers[0], dealing_indices[0]), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
// nothing for dealer "foo"
let foo = Addr::unchecked("foo");
let dealings =
StoredDealing::prefix_range(&deps.storage, (epochs[0], &foo, dealing_indices[0]), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
// nothing for dealing index 99
let dealings =
StoredDealing::prefix_range(&deps.storage, (epochs[0], &dealers[0], 99), None)
.collect::<Vec<_>>();
assert!(dealings.is_empty());
let all = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0], dealing_indices[0]),
None,
)
.collect::<Vec<_>>();
assert_eq!(all.len(), chunk_indices.len());
for (i, dealing) in all.iter().enumerate() {
let expected =
dealing_data(epochs[0], &dealers[0], dealing_indices[0], chunk_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index);
}
// for sanity sake, check another dealer with different epoch and different dealing index
let all_other = StoredDealing::prefix_range(
&deps.storage,
(epochs[2], &dealers[3], dealing_indices[4]),
None,
)
.collect::<Vec<_>>();
assert_eq!(all_other.len(), chunk_indices.len());
for (i, dealing) in all_other.iter().enumerate() {
let expected =
dealing_data(epochs[2], &dealers[3], dealing_indices[4], chunk_indices[i]);
assert_eq!(expected, dealing.as_ref().unwrap().data);
assert_eq!(chunk_indices[i], dealing.as_ref().unwrap().chunk_index);
}
let without_first = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0], dealing_indices[0]),
Some(Bound::exclusive(chunk_indices[0])),
)
.collect::<Vec<_>>();
assert_eq!(&all[1..], without_first);
let mid = StoredDealing::prefix_range(
&deps.storage,
(epochs[0], &dealers[0], dealing_indices[0]),
Some(Bound::inclusive(chunk_indices[3])),
)
.collect::<Vec<_>>();
assert_eq!(&all[3..], mid);
}
}
+308 -104
View File
@@ -1,142 +1,346 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage as dealers_storage;
use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::INITIAL_REPLACEMENT_DATA;
use crate::dealings::storage::{
metadata_exists, must_read_metadata, store_metadata, StoredDealing,
};
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use nym_coconut_dkg_common::types::{ContractSafeBytes, EpochState};
use crate::state::storage::STATE;
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response, Storage};
use nym_coconut_dkg_common::dealing::{
DealingChunkInfo, DealingMetadata, PartialContractDealing, MAX_DEALING_CHUNKS,
};
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochState};
pub fn try_commit_dealings(
deps: DepsMut<'_>,
info: MessageInfo,
dealing_bytes: ContractSafeBytes,
// make sure the epoch is in the dealing exchange and the message sender is a valid dealer for this epoch
fn ensure_permission(
storage: &dyn Storage,
sender: &Addr,
resharing: bool,
) -> Result<Response, ContractError> {
check_epoch_state(deps.storage, EpochState::DealingExchange { resharing })?;
) -> Result<(), ContractError> {
check_epoch_state(storage, EpochState::DealingExchange { resharing })?;
// ensure the sender is a dealer
if dealers_storage::current_dealers()
.may_load(deps.storage, &info.sender)?
.may_load(storage, sender)?
.is_none()
{
return Err(ContractError::NotADealer);
}
if resharing
&& !INITIAL_REPLACEMENT_DATA
.load(deps.storage)?
.load(storage)?
.initial_dealers
.contains(&info.sender)
.contains(sender)
{
return Err(ContractError::NotAnInitialDealer);
}
Ok(())
}
// check if this dealer has already committed to all dealings
// (we don't want to allow overwriting anything)
for dealings in DEALINGS_BYTES {
if !dealings.has(deps.storage, &info.sender) {
dealings.save(deps.storage, &info.sender, &dealing_bytes)?;
return Ok(Response::default());
pub fn try_submit_dealings_metadata(
deps: DepsMut,
info: MessageInfo,
dealing_index: DealingIndex,
chunks: Vec<DealingChunkInfo>,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let state = STATE.load(deps.storage)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// don't allow overwriting existing metadata
if metadata_exists(deps.storage, epoch.epoch_id, &info.sender, dealing_index) {
return Err(ContractError::MetadataAlreadyExists {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
});
}
// make sure the dealing index is in the allowed range
// note: dealing indexing starts from 0
if dealing_index >= state.key_size {
return Err(ContractError::DealingOutOfRange {
epoch_id: epoch.epoch_id,
dealer: info.sender,
index: dealing_index,
key_size: state.key_size,
});
}
// make sure the metadata is not empty
if chunks.is_empty() {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
});
}
// make sure the chunks are non empty
if chunks.iter().any(|c| c.size == 0) {
return Err(ContractError::EmptyMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
});
}
// make sure the number of dealing chunks is in the allowed range
// to prevent somebody splitting their dealings into 10B chunks
if chunks.len() > MAX_DEALING_CHUNKS {
return Err(ContractError::TooFragmentedMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
chunks: chunks.len(),
});
}
// make sure all chunks, but the last one, have the same size
// SAFETY: we checked for whether `chunks` is empty and returned an error in that case
#[allow(clippy::unwrap_used)]
let first_chunk_size = chunks.first().unwrap().size;
for (chunk_index, chunk_info) in chunks.iter().enumerate().take(chunks.len() - 1) {
if chunk_info.size != first_chunk_size {
return Err(ContractError::UnevenChunkSplit {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index,
chunk_index: chunk_index as ChunkIndex,
first_chunk_size,
size: chunk_info.size,
});
}
}
Err(ContractError::AlreadyCommitted {
commitment: String::from("dealing"),
})
// finally, construct and store the metadata
let metadata = DealingMetadata::new(dealing_index, chunks);
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
dealing_index,
&metadata,
)?;
Ok(Response::new())
}
pub fn try_commit_dealings_chunk(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
chunk: PartialContractDealing,
resharing: bool,
) -> Result<Response, ContractError> {
ensure_permission(deps.storage, &info.sender, resharing)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
// read meta
let mut metadata = must_read_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
chunk.dealing_index,
)?;
// check if the received chunk is within the declared range
let Some(submission_status) = metadata.submitted_chunks.get_mut(&chunk.chunk_index) else {
return Err(ContractError::DealingChunkNotInMetadata {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
});
};
// check if this dealer has already committed this particular dealing chunk
if let Some(submission_height) = submission_status.status.submission_height {
return Err(ContractError::DealingChunkAlreadyCommitted {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
block_height: submission_height,
});
}
// check if the received chunk has the specified size
if submission_status.info.size != chunk.data.len() {
return Err(ContractError::InconsistentChunkLength {
epoch_id: epoch.epoch_id,
dealer: info.sender,
dealing_index: chunk.dealing_index,
chunk_index: chunk.chunk_index,
metadata_length: submission_status.info.size,
received: chunk.data.len(),
});
}
// update the metadata
submission_status.status.submission_height = Some(env.block.height);
store_metadata(
deps.storage,
epoch.epoch_id,
&info.sender,
chunk.dealing_index,
&metadata,
)?;
// store the dealing
StoredDealing::save(deps.storage, epoch.epoch_id, &info.sender, chunk);
Ok(Response::new())
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::support::tests::fixtures::{dealer_details_fixture, dealing_bytes_fixture};
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
use crate::support::tests::fixtures::{dealer_details_fixture, partial_dealing_fixture};
use crate::support::tests::helpers;
use crate::support::tests::helpers::add_fixture_dealer;
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{InitialReplacementData, TimeConfiguration};
use nym_coconut_dkg_common::types::{
ContractSafeBytes, InitialReplacementData, TimeConfiguration,
};
#[test]
fn invalid_commit_dealing() {
let mut deps = helpers::init_contract();
let owner = Addr::unchecked("owner1");
let mut env = mock_env();
let info = mock_info(owner.as_str(), &[]);
let dealing_bytes = dealing_bytes_fixture();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
expected_state: EpochState::DealingExchange { resharing: false }.to_string()
}
);
env.block.time = env
.block
.time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
add_fixture_dealer(deps.as_mut());
advance_epoch_state(deps.as_mut(), env).unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), false)
.unwrap_err();
assert_eq!(ret, ContractError::NotADealer);
let dealer_details = DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.unwrap();
// assume we're in resharing mode
CURRENT_EPOCH
.update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
epoch.state = EpochState::DealingExchange { resharing: true };
Ok(epoch)
})
.unwrap();
INITIAL_REPLACEMENT_DATA
.save(
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![],
initial_height: 1,
},
)
.unwrap();
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true)
.unwrap_err();
assert_eq!(ret, ContractError::NotAnInitialDealer);
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_dealers = vec![dealer_details_fixture(1).address];
Ok(data)
})
.unwrap();
for dealings in DEALINGS_BYTES {
assert!(!dealings.has(deps.as_mut().storage, &owner));
let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing_bytes.clone(), true);
assert!(ret.is_ok());
assert!(dealings.has(deps.as_mut().storage, &owner));
}
let ret = try_commit_dealings(deps.as_mut(), info, dealing_bytes, true).unwrap_err();
assert_eq!(
ret,
ContractError::AlreadyCommitted {
commitment: String::from("dealing"),
}
);
todo!()
// let mut deps = helpers::init_contract();
// let mut env = mock_env();
// try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
//
// let owner = Addr::unchecked("owner1");
// let info = mock_info(owner.as_str(), &[]);
// let dealing = partial_dealing_fixture();
//
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
// assert_eq!(
// ret,
// ContractError::IncorrectEpochState {
// current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
// expected_state: EpochState::DealingExchange { resharing: false }.to_string()
// }
// );
//
// env.block.time = env
// .block
// .time
// .plus_seconds(TimeConfiguration::default().public_key_submission_time_secs);
// add_fixture_dealer(deps.as_mut());
// advance_epoch_state(deps.as_mut(), env).unwrap();
//
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
// assert_eq!(ret, ContractError::NotADealer);
//
// let dealer_details = DealerDetails {
// address: owner.clone(),
// bte_public_key_with_proof: String::new(),
// ed25519_identity: String::new(),
// announce_address: String::new(),
// assigned_index: 1,
// };
// dealers_storage::current_dealers()
// .save(deps.as_mut().storage, &owner, &dealer_details)
// .unwrap();
//
// // assume we're in resharing mode
// CURRENT_EPOCH
// .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
// epoch.state = EpochState::DealingExchange { resharing: true };
// Ok(epoch)
// })
// .unwrap();
// INITIAL_REPLACEMENT_DATA
// .save(
// deps.as_mut().storage,
// &InitialReplacementData {
// initial_dealers: vec![],
// initial_height: 1,
// },
// )
// .unwrap();
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), true).unwrap_err();
// assert_eq!(ret, ContractError::NotAnInitialDealer);
//
// INITIAL_REPLACEMENT_DATA
// .update::<_, ContractError>(deps.as_mut().storage, |mut data| {
// data.initial_dealers = vec![dealer_details_fixture(1).address];
// Ok(data)
// })
// .unwrap();
//
// // back to 'normal' mode
// CURRENT_EPOCH
// .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
// epoch.state = EpochState::DealingExchange { resharing: false };
// Ok(epoch)
// })
// .unwrap();
//
// // dealing out of range
// let ret = try_commit_dealings(
// deps.as_mut(),
// info.clone(),
// PartialContractDealing {
// dealing_index: 42,
// data: ContractSafeBytes(vec![1, 2, 3]),
// },
// false,
// )
// .unwrap_err();
// assert_eq!(
// ret,
// ContractError::DealingOutOfRange {
// epoch_id: 0,
// dealer: info.sender.clone(),
// index: 42,
// key_size: DEFAULT_DEALINGS as u32,
// }
// );
//
// // 'good' dealing
// let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
// assert!(ret.is_ok());
//
// // duplicate dealing
// let ret =
// try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false).unwrap_err();
// assert_eq!(
// ret,
// ContractError::DealingAlreadyCommitted {
// epoch_id: 0,
// dealer: info.sender.clone(),
// index: 0,
// }
// );
//
// // same index, but next epoch
// CURRENT_EPOCH
// .update::<_, ContractError>(deps.as_mut().storage, |mut epoch| {
// epoch.epoch_id += 1;
// Ok(epoch)
// })
// .unwrap();
//
// let ret = try_commit_dealings(deps.as_mut(), info.clone(), dealing.clone(), false);
// assert!(ret.is_ok());
}
}
@@ -27,22 +27,29 @@ pub(crate) fn query_initial_dealers(
#[cfg(test)]
pub(crate) mod test {
use super::*;
use crate::support::tests::helpers::init_contract;
use cosmwasm_std::testing::mock_env;
use crate::epoch_state::transactions::try_initiate_dkg;
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS};
use cosmwasm_std::testing::{mock_env, mock_info};
use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration};
#[test]
fn query_state() {
let mut deps = init_contract();
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::WaitingInitialisation);
assert_eq!(epoch.finish_timestamp, None);
let env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let epoch = query_current_epoch(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
mock_env()
.block
epoch.finish_timestamp.unwrap(),
env.block
.time
.plus_seconds(TimeConfiguration::default().public_key_submission_time_secs)
);
@@ -1,17 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::dealers::storage::{current_dealers, past_dealers};
use crate::dealings::storage::DEALINGS_BYTES;
use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRESHOLD};
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::state::storage::{DKG_ADMIN, STATE};
use crate::verification_key_shares::storage::verified_dealers;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
use cosmwasm_std::{Addr, Deps, DepsMut, Env, MessageInfo, Order, Response, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
fn reset_dkg_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
THRESHOLD.remove(storage);
let dealers: Vec<_> = current_dealers()
.keys(storage, None, None, Order::Ascending)
@@ -19,15 +18,6 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
for dealer_addr in dealers {
let details = current_dealers().load(storage, &dealer_addr)?;
for dealings in DEALINGS_BYTES {
let dealing_keys: Vec<_> = dealings
.keys(storage, None, None, Order::Ascending)
.flatten()
.collect();
for key in dealing_keys {
dealings.remove(storage, &key);
}
}
current_dealers().remove(storage, &dealer_addr)?;
past_dealers().save(storage, &dealer_addr, &details)?;
}
@@ -80,18 +70,43 @@ fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractE
Ok(removed_dealer_count >= replacement_threshold)
}
pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
pub(crate) fn try_initiate_dkg(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
) -> Result<Response, ContractError> {
// only the admin is allowed to kick start the process
DKG_ADMIN.assert_admin(deps.as_ref(), &info.sender)?;
let epoch = CURRENT_EPOCH.load(deps.storage)?;
if epoch.finish_timestamp > env.block.time {
return Err(ContractError::EarlyEpochStateAdvancement(
epoch
.finish_timestamp
.minus_seconds(env.block.time.seconds())
.seconds(),
));
if !matches!(epoch.state, EpochState::WaitingInitialisation) {
return Err(ContractError::AlreadyInitialised);
}
// the first exchange won't involve resharing
let initial_state = EpochState::PublicKeySubmission { resharing: false };
let initial_epoch = Epoch::new(initial_state, 0, epoch.time_configuration, env.block.time);
CURRENT_EPOCH.save(deps.storage, &initial_epoch)?;
Ok(Response::default())
}
pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Response, ContractError> {
let current_epoch = CURRENT_EPOCH.load(deps.storage)?;
if current_epoch.state == EpochState::WaitingInitialisation {
return Err(ContractError::WaitingInitialisation);
}
if let Some(finish_timestamp) = current_epoch.finish_timestamp {
if finish_timestamp > env.block.time {
return Err(ContractError::EarlyEpochStateAdvancement(
finish_timestamp
.minus_seconds(env.block.time.seconds())
.seconds(),
));
}
}
let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process
let mut new_state = state;
@@ -123,6 +138,8 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
} else if dealers_eq_members(&deps)? {
// The dealer set hasn't changed, so we only extend the finish timestamp
// The epoch remains the same, as we use it as key for storing VKs
// TODO: change that behaviour in the following PR
Epoch::new(
current_epoch.state,
current_epoch.epoch_id,
@@ -139,6 +156,7 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
// ... in reshare mode
if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() {
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
// TODO: FIXME: for second reshare the added set of dealers won't be allowed to participate
data.initial_height = env.block.height;
Ok(data)
})?;
@@ -152,7 +170,7 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
EpochState::PublicKeySubmission { resharing: true }
};
reset_epoch_state(deps.storage)?;
reset_dkg_state(deps.storage)?;
Epoch::new(
state,
current_epoch.epoch_id + 1,
@@ -174,7 +192,7 @@ pub(crate) fn try_surpassed_threshold(
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = verified_dealers(deps.storage)?;
if dealers_still_active(&deps.as_ref(), dealers.into_iter())? < threshold as usize {
reset_epoch_state(deps.storage)?;
reset_dkg_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
EpochState::default(),
@@ -193,20 +211,19 @@ pub(crate) mod tests {
use super::*;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS};
use crate::support::tests::helpers::{init_contract, ADMIN_ADDRESS, GROUP_MEMBERS};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use cw4::Member;
use nym_coconut_dkg_common::types::{
ContractSafeBytes, DealerDetails, EpochState, TimeConfiguration,
};
use cw_controllers::AdminError;
use nym_coconut_dkg_common::types::{DealerDetails, EpochState, TimeConfiguration};
use rusty_fork::rusty_fork_test;
// Because of the global variable handling group, we need individual process for each test
rusty_fork_test! {
// Using values from the DKG document
// Using values from the DKG document
#[test]
fn threshold_surpassed() {
let mut deps = init_contract();
@@ -217,14 +234,18 @@ pub(crate) mod tests {
for n in [10, 25, 50, 100] {
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
let shares: Vec<_> = (0..n).map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)).collect();
let shares: Vec<_> = (0..n)
.map(|idx| vk_share_fixture(&format!("owner{}", idx), 0))
.collect();
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
let data = InitialReplacementData {
initial_dealers,
initial_height: 1,
};
for share in shares {
vk_shares().save(deps.as_mut().storage, (&share.owner, 0), &share).unwrap();
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), &share)
.unwrap();
}
for f in [two_thirds, three_fourths, ninty_pc] {
let threshold = f(n);
@@ -361,7 +382,8 @@ pub(crate) mod tests {
fn advance_state() {
let mut deps = init_contract();
let mut env = mock_env();
{
{
let mut group = GROUP_MEMBERS.lock().unwrap();
group.push((
@@ -394,13 +416,21 @@ pub(crate) mod tests {
));
}
// can't advance the state if dkg hasn't been initiated
assert_eq!(
advance_epoch_state(deps.as_mut(), env.clone()).unwrap_err(),
ContractError::WaitingInitialisation
);
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block
.time
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
@@ -424,7 +454,8 @@ pub(crate) mod tests {
);
// setup dealer details
let all_shares: [_; 4] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
let all_shares: [_; 4] =
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
@@ -441,7 +472,10 @@ pub(crate) mod tests {
.may_load(&deps.storage)
.unwrap()
.is_none());
env.block.time = env.block.time.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
env.block.time = env
.block
.time
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(
@@ -449,7 +483,7 @@ pub(crate) mod tests {
EpochState::DealingExchange { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block
.time
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
@@ -472,7 +506,7 @@ pub(crate) mod tests {
EpochState::VerificationKeySubmission { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block.time.plus_seconds(
epoch
.time_configuration
@@ -499,7 +533,7 @@ pub(crate) mod tests {
EpochState::VerificationKeyValidation { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block.time.plus_seconds(
epoch
.time_configuration
@@ -526,7 +560,7 @@ pub(crate) mod tests {
EpochState::VerificationKeyFinalization { resharing: false }
);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block.time.plus_seconds(
epoch
.time_configuration
@@ -548,7 +582,7 @@ pub(crate) mod tests {
let epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
assert_eq!(epoch.state, EpochState::InProgress);
assert_eq!(
epoch.finish_timestamp,
epoch.finish_timestamp.unwrap(),
env.block
.time
.plus_seconds(epoch.time_configuration.in_progress_time_secs)
@@ -614,7 +648,9 @@ pub(crate) mod tests {
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
for details in all_details.iter() {
past_dealers().remove(deps.as_mut().storage, &details.address).unwrap();
past_dealers()
.remove(deps.as_mut().storage, &details.address)
.unwrap();
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
@@ -622,9 +658,15 @@ pub(crate) mod tests {
for times in [
epoch.time_configuration.public_key_submission_time_secs,
epoch.time_configuration.dealing_exchange_time_secs,
epoch.time_configuration.verification_key_submission_time_secs,
epoch.time_configuration.verification_key_validation_time_secs,
epoch.time_configuration.verification_key_finalization_time_secs,
epoch
.time_configuration
.verification_key_submission_time_secs,
epoch
.time_configuration
.verification_key_validation_time_secs,
epoch
.time_configuration
.verification_key_finalization_time_secs,
] {
env.block.time = env.block.time.plus_seconds(times);
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
@@ -634,7 +676,7 @@ pub(crate) mod tests {
let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1);
share.verified = i % 2 == 0;
share
});
});
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
@@ -670,6 +712,8 @@ pub(crate) mod tests {
fn surpass_threshold() {
let mut deps = init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let time_configuration = TimeConfiguration::default();
{
let mut group = GROUP_MEMBERS.lock().unwrap();
@@ -701,13 +745,13 @@ pub(crate) mod tests {
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
expected_state: EpochState::InProgress.to_string()
}
);
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
let all_shares: [_; 3] =
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
@@ -719,7 +763,8 @@ pub(crate) mod tests {
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
let all_shares: [_; 3] =
std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
@@ -778,6 +823,46 @@ pub(crate) mod tests {
}
}
#[test]
fn initialising_dkg() {
let mut deps = init_contract();
let env = mock_env();
let initial_epoch_info = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert!(initial_epoch_info.finish_timestamp.is_none());
// can only be executed by the admin
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info("not an admin", &[]))
.unwrap_err();
assert_eq!(ContractError::Admin(AdminError::NotAdmin {}), res);
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]));
assert!(res.is_ok());
// can't be initialised more than once
let res = try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[]))
.unwrap_err();
assert_eq!(ContractError::AlreadyInitialised, res);
// sets the correct epoch data
let epoch = CURRENT_EPOCH.load(&deps.storage).unwrap();
assert_eq!(epoch.epoch_id, 0);
assert_eq!(
epoch.state,
EpochState::PublicKeySubmission { resharing: false }
);
assert_eq!(
epoch.time_configuration,
initial_epoch_info.time_configuration
);
assert_eq!(
epoch.finish_timestamp.unwrap(),
env.block
.time
.plus_seconds(epoch.time_configuration.public_key_submission_time_secs)
);
}
#[test]
fn reset_state() {
let mut deps = init_contract();
@@ -788,27 +873,12 @@ pub(crate) mod tests {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
for dealings in DEALINGS_BYTES {
dealings
.save(
deps.as_mut().storage,
&details.address,
&ContractSafeBytes(vec![1, 2, 3]),
)
.unwrap();
}
}
reset_epoch_state(deps.as_mut().storage).unwrap();
reset_dkg_state(deps.as_mut().storage).unwrap();
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
for details in all_details {
for dealings in DEALINGS_BYTES {
assert!(dealings
.may_load(&deps.storage, &details.address)
.unwrap()
.is_none());
}
assert!(current_dealers()
.may_load(deps.as_mut().storage, &details.address)
.unwrap()
@@ -826,6 +896,7 @@ pub(crate) mod tests {
fn verify_threshold() {
let mut deps = init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
assert!(THRESHOLD.may_load(deps.as_mut().storage).unwrap().is_none());
@@ -838,6 +909,7 @@ pub(crate) mod tests {
&DealerDetails {
address: address.clone(),
bte_public_key_with_proof: "bte_public_key_with_proof".to_string(),
ed25519_identity: "identity".to_string(),
announce_address: "127.0.0.1".to_string(),
assigned_index: i,
},
@@ -33,14 +33,14 @@ pub(crate) mod test {
let mut deps = init_contract();
let env = mock_env();
for fixed_state in EpochState::default().all_until(EpochState::InProgress) {
for fixed_state in EpochState::first().all_until(EpochState::InProgress) {
CURRENT_EPOCH
.save(
deps.as_mut().storage,
&Epoch::new(fixed_state, 0, TimeConfiguration::default(), env.block.time),
)
.unwrap();
for against_state in EpochState::default().all_until(EpochState::InProgress) {
for against_state in EpochState::first().all_until(EpochState::InProgress) {
let ret = check_epoch_state(deps.as_mut().storage, against_state);
if fixed_state == against_state {
assert!(ret.is_ok());
+90 -2
View File
@@ -1,8 +1,10 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::StdError;
use cosmwasm_std::{Addr, StdError};
use cw_controllers::AdminError;
use nym_coconut_dkg_common::dealing::MAX_DEALING_CHUNKS;
use nym_coconut_dkg_common::types::{ChunkIndex, DealingIndex, EpochId};
use thiserror::Error;
/// Custom errors for contract failure conditions.
@@ -14,6 +16,12 @@ pub enum ContractError {
#[error(transparent)]
Admin(#[from] AdminError),
#[error("Dkg hasn't been initialised yet")]
WaitingInitialisation,
#[error("Dkg has already been initialised")]
AlreadyInitialised,
#[error("Group contract invalid address '{addr}'")]
InvalidGroup { addr: String },
@@ -30,7 +38,7 @@ pub enum ContractError {
EpochNotInitialised,
#[error(
"Requested action needs state to be {expected_state}, currently in state {current_state}, "
"Requested action needs state to be {expected_state}, currently in state {current_state}"
)]
IncorrectEpochState {
current_state: String,
@@ -43,9 +51,89 @@ pub enum ContractError {
#[error("This sender is not a dealer for the current resharing epoch")]
NotAnInitialDealer,
#[error("Dealer {dealer} has already committed dealing chunk for epoch {epoch_id} with dealing index {dealing_index} and chunk index {chunk_index} at height {block_height}")]
DealingChunkAlreadyCommitted {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
block_height: u64,
},
#[error("dealer {dealer} tried to commit chunk {chunk_index} of dealing {dealing_index} for epoch {epoch_id}, but it hasn't been declared in the prior metadata")]
DealingChunkNotInMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
},
#[error("dealer {dealer} has attempted to commit dealing chunk for epoch {epoch_id} with dealing index {index} while the key size is set to {key_size}")]
DealingOutOfRange {
epoch_id: EpochId,
dealer: Addr,
index: DealingIndex,
key_size: u32,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} with {chunks} chunks while at most {} chunks are allowed", MAX_DEALING_CHUNKS)]
TooFragmentedMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunks: usize,
},
#[error("the declared chunk split for epoch {epoch_id} from dealer {dealer} for dealing index {dealing_index} is uneven. first chunk has size of {first_chunk_size} while chunk at index {chunk_index} has {size}")]
UnevenChunkSplit {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
first_chunk_size: usize,
size: usize,
},
#[error("the received chunk for epoch {epoch_id} from dealer {dealer} at dealing index {dealing_index} at chunk index {chunk_index} has inconsistent length. the metadata contains length of {metadata_length} while the received data is {received} bytes long")]
InconsistentChunkLength {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
chunk_index: ChunkIndex,
metadata_length: usize,
received: usize,
},
#[error("dealer {dealer} has attempted to commit dealing metadata for epoch {epoch_id} for dealing index {dealing_index} zero chunks")]
EmptyMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} does not exist")]
UnavailableDealingMetadata {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("metadata for dealing for epoch {epoch_id} from {dealer} at index {dealing_index} already exists")]
MetadataAlreadyExists {
epoch_id: EpochId,
dealer: Addr,
dealing_index: DealingIndex,
},
#[error("This dealer has already committed {commitment}")]
AlreadyCommitted { commitment: String },
#[error("No verification key committed for owner {owner}")]
NoCommitForOwner { owner: String },
#[error("failed to parse {value} into a valid SemVer version: {error_message}")]
SemVerFailure {
value: String,
error_message: String,
},
}
+3 -17
View File
@@ -1,19 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use cw4::Cw4Contract;
use cw_controllers::Admin;
use cw_storage_plus::Item;
use serde::{Deserialize, Serialize};
// unique items
pub const STATE: Item<State> = Item::new("state");
pub const MULTISIG: Admin = Admin::new("multisig");
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub struct State {
pub mix_denom: String,
pub multisig_addr: Addr,
pub group_addr: Cw4Contract,
}
pub mod queries;
pub mod storage;
@@ -0,0 +1,10 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::state::storage::STATE;
use cosmwasm_std::{StdResult, Storage};
use nym_coconut_dkg_common::types::State;
pub(crate) fn query_state(storage: &dyn Storage) -> StdResult<State> {
STATE.load(storage)
}
@@ -0,0 +1,13 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cw_controllers::Admin;
use cw_storage_plus::Item;
use nym_coconut_dkg_common::types::State;
// unique items
pub const DKG_ADMIN: Admin = Admin::new("dkg-admin");
pub const STATE: Item<State> = Item::new("state");
pub const MULTISIG: Admin = Admin::new("multisig");
@@ -1,8 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Addr;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::dealing::PartialContractDealing;
use nym_coconut_dkg_common::types::ContractSafeBytes;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
@@ -20,13 +21,22 @@ pub fn vk_share_fixture(owner: &str, index: u64) -> ContractVKShare {
}
pub fn dealing_bytes_fixture() -> ContractSafeBytes {
ContractSafeBytes(vec![])
ContractSafeBytes(vec![1, 2, 3])
}
pub fn partial_dealing_fixture() -> PartialContractDealing {
PartialContractDealing {
chunk_index: 0,
dealing_index: 0,
data: ContractSafeBytes(vec![1, 2, 3]),
}
}
pub fn dealer_details_fixture(assigned_index: u64) -> DealerDetails {
DealerDetails {
address: Addr::unchecked(format!("owner{}", assigned_index)),
bte_public_key_with_proof: "".to_string(),
ed25519_identity: "".to_string(),
announce_address: "".to_string(),
assigned_index,
}
@@ -9,7 +9,7 @@ use cosmwasm_std::{
QuerierResult, SystemResult, WasmQuery,
};
use cw4::{Cw4QueryMsg, Member, MemberListResponse, MemberResponse};
use lazy_static::lazy_static;
use nym_coconut_dkg_common::dealing::DEFAULT_DEALINGS;
use nym_coconut_dkg_common::msg::InstantiateMsg;
use nym_coconut_dkg_common::types::DealerDetails;
use std::sync::Mutex;
@@ -20,9 +20,7 @@ pub const ADMIN_ADDRESS: &str = "admin address";
pub const GROUP_CONTRACT: &str = "group contract address";
pub const MULTISIG_CONTRACT: &str = "multisig contract address";
lazy_static! {
pub static ref GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(vec![]);
}
pub(crate) static GROUP_MEMBERS: Mutex<Vec<(Member, u64)>> = Mutex::new(Vec::new());
pub fn add_fixture_dealer(deps: DepsMut<'_>) {
let owner = Addr::unchecked("owner");
@@ -33,6 +31,7 @@ pub fn add_fixture_dealer(deps: DepsMut<'_>) {
&DealerDetails {
address: owner.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 100,
},
@@ -87,6 +86,7 @@ pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>>
multisig_addr: String::from(MULTISIG_CONTRACT),
time_configuration: None,
mix_denom: TEST_MIX_DENOM.to_string(),
key_size: DEFAULT_DEALINGS as u32,
};
let env = mock_env();
let info = mock_info(ADMIN_ADDRESS, &[]);
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::verification_key_shares::storage;
@@ -6,7 +6,23 @@ use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::{Deps, Order, StdResult};
use cw_storage_plus::Bound;
use nym_coconut_dkg_common::types::EpochId;
use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse;
use nym_coconut_dkg_common::verification_key::{PagedVKSharesResponse, VkShareResponse};
// TODO: unit tests
pub fn query_vk_share(
deps: Deps<'_>,
owner: String,
epoch_id: EpochId,
) -> StdResult<VkShareResponse> {
let owner = deps.api.addr_validate(&owner)?;
let share = vk_shares().may_load(deps.storage, (&owner, epoch_id))?;
Ok(VkShareResponse {
owner,
epoch_id,
share,
})
}
pub fn query_vk_shares_paged(
deps: Deps<'_>,
@@ -1,7 +1,5 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
use crate::epoch_state::storage::CURRENT_EPOCH;
@@ -6,9 +6,9 @@ use crate::dealers::storage as dealers_storage;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::{MULTISIG, STATE};
use crate::state::storage::{MULTISIG, STATE};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::{Addr, DepsMut, Env, MessageInfo, Response};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use nym_coconut_dkg_common::types::EpochState;
use nym_coconut_dkg_common::verification_key::{
to_cosmos_msg, ContractVKShare, VerificationKeyShare,
@@ -54,6 +54,7 @@ pub fn try_commit_verification_key_share(
resharing,
env.contract.address.to_string(),
STATE.load(deps.storage)?.multisig_addr.to_string(),
// TODO: make this value configurable
env.block
.time
.plus_seconds(BLOCK_TIME_FOR_VERIFICATION_SECS),
@@ -65,9 +66,11 @@ pub fn try_commit_verification_key_share(
pub fn try_verify_verification_key_share(
deps: DepsMut<'_>,
info: MessageInfo,
owner: Addr,
owner: String,
resharing: bool,
) -> Result<Response, ContractError> {
let owner = deps.api.addr_validate(&owner)?;
check_epoch_state(
deps.storage,
EpochState::VerificationKeyFinalization { resharing },
@@ -91,10 +94,11 @@ pub fn try_verify_verification_key_share(
#[cfg(test)]
mod tests {
use super::*;
use crate::epoch_state::transactions::advance_epoch_state;
use crate::epoch_state::transactions::{advance_epoch_state, try_initiate_dkg};
use crate::support::tests::helpers;
use crate::support::tests::helpers::{add_fixture_dealer, MULTISIG_CONTRACT};
use crate::support::tests::helpers::{add_fixture_dealer, ADMIN_ADDRESS, MULTISIG_CONTRACT};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::Addr;
use cw_controllers::AdminError;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::{EpochState, TimeConfiguration};
@@ -103,6 +107,8 @@ mod tests {
fn current_epoch_id() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = mock_info("requester", &[]);
let share = "share".to_string();
@@ -122,6 +128,7 @@ mod tests {
let dealer_details = DealerDetails {
address: dealer.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: announce_address.clone(),
assigned_index: 1,
};
@@ -149,6 +156,8 @@ mod tests {
fn commit_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = mock_info("requester", &[]);
let share = "share".to_string();
@@ -163,7 +172,7 @@ mod tests {
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
expected_state: EpochState::VerificationKeySubmission { resharing: false }
.to_string()
}
@@ -193,6 +202,7 @@ mod tests {
let dealer_details = DealerDetails {
address: dealer.clone(),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
@@ -223,8 +233,10 @@ mod tests {
fn invalid_verify_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let info = mock_info("requester", &[]);
let owner = Addr::unchecked("owner");
let owner = "owner".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
let ret =
@@ -233,7 +245,7 @@ mod tests {
assert_eq!(
ret,
ContractError::IncorrectEpochState {
current_state: EpochState::default().to_string(),
current_state: EpochState::PublicKeySubmission { resharing: false }.to_string(),
expected_state: EpochState::VerificationKeyFinalization { resharing: false }
.to_string()
}
@@ -280,7 +292,9 @@ mod tests {
fn verify_vk_share() {
let mut deps = helpers::init_contract();
let mut env = mock_env();
let owner = Addr::unchecked("owner");
try_initiate_dkg(deps.as_mut(), env.clone(), mock_info(ADMIN_ADDRESS, &[])).unwrap();
let owner = "owner".to_string();
let info = mock_info(owner.as_ref(), &[]);
let share = "share".to_string();
let multisig_info = mock_info(MULTISIG_CONTRACT, &[]);
@@ -298,13 +312,18 @@ mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let dealer_details = DealerDetails {
address: owner.clone(),
address: Addr::unchecked(&owner),
bte_public_key_with_proof: String::new(),
ed25519_identity: String::new(),
announce_address: String::new(),
assigned_index: 1,
};
dealers_storage::current_dealers()
.save(deps.as_mut().storage, &owner, &dealer_details)
.save(
deps.as_mut().storage,
&Addr::unchecked(&owner),
&dealer_details,
)
.unwrap();
try_commit_verification_key_share(deps.as_mut(), env.clone(), info, share, false).unwrap();
@@ -12,7 +12,7 @@ use cw4::Member;
use cw_multi_test::Executor;
use cw_utils::{Duration, Threshold};
use nym_coconut_dkg_common::msg::ExecuteMsg::{
AdvanceEpochState, CommitVerificationKeyShare, RegisterDealer,
AdvanceEpochState, CommitVerificationKeyShare, InitiateDkg, RegisterDealer,
};
use nym_coconut_dkg_common::msg::InstantiateMsg as DkgInstantiateMsg;
use nym_coconut_dkg_common::msg::QueryMsg::GetVerificationKeys;
@@ -75,6 +75,7 @@ fn dkg_proposal() {
multisig_addr: multisig_contract_addr.to_string(),
time_configuration: None,
mix_denom: TEST_COIN_DENOM.to_string(),
key_size: 5,
};
let coconut_dkg_contract_addr = app
.instantiate_contract(
@@ -99,11 +100,20 @@ fn dkg_proposal() {
)
.unwrap();
app.execute_contract(
Addr::unchecked(OWNER),
coconut_dkg_contract_addr.clone(),
&InitiateDkg {},
&[],
)
.unwrap();
app.execute_contract(
Addr::unchecked(MEMBER1),
coconut_dkg_contract_addr.clone(),
&RegisterDealer {
bte_key_with_proof: "bte_key_with_proof".to_string(),
identity_key: "identity".to_string(),
announce_address: "127.0.0.1:8000".to_string(),
resharing: false,
},
+1 -1
View File
@@ -41,7 +41,7 @@ bs58 = "0.4.0"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
time = { version = "0.3", features = ["macros"] }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
[dev-dependencies]
rand_chacha = "0.2"
+1 -1
View File
@@ -20,7 +20,7 @@ cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-name-service-common = { path = "../../common/cosmwasm-smart-contracts/name-service" }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
@@ -20,7 +20,7 @@ cw-utils = { workspace = true }
cw2 = { workspace = true }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.5.0" }
nym-service-provider-directory-common = { path = "../../common/cosmwasm-smart-contracts/service-provider-directory" }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
serde = { version = "1.0.155", default-features = false, features = ["derive"] }
thiserror = { workspace = true }
+1 -1
View File
@@ -36,7 +36,7 @@ cw-storage-plus = { workspace = true, features = ["iterator"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
thiserror ={ workspace = true }
semver = { version = "1.0.16", default-features = false }
semver = { workspace = true, default-features = false }
[dev-dependencies]
rand_chacha = "0.3.1"
+10 -4
View File
@@ -24,7 +24,7 @@
- [NymConnect X Electrum](tutorials/electrum.md)
- [NymConnect X Firo wallet](tutorials/firo.md)
# Code Examples
# Code Examples
- [Custom Service Providers](examples/custom-services.md)
- [Apps Using Network Requesters](examples/using-nrs.md)
@@ -49,9 +49,9 @@
- [Preparing Your Service](tutorials/cosmos-service/service.md)
- [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md)
- [Querying the Chain](tutorials/cosmos-service/querying.md)
- [Typescript](tutorials/typescript.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
@@ -68,6 +68,12 @@
# Events
- [CryptoTalk](events/cryptotalk/nym-vpn.md)
- [NymVPN Linux Guide](events/cryptotalk/nym-vpn-linux.md)
- [NymVPN MacOS Guide](events/cryptotalk/nym-vpn-mac.md)
- [NymVPN Testing](events/cryptotalk/nym-vpn-testing.md)
- [NymVPN Troubleshooting](events/cryptotalk/nym-vpn-troubleshooting.md)
- [NymVPN FAQ](events/cryptotalk/nym-vpn-faq.md)
- [Web3Privacy Now](./events/web3-privacy.md)
- [HCPP23-serinko](./events/hcpp23-serinko.md)
- [HCPP23-max](./events/hcpp23-max.md)
@@ -83,7 +89,7 @@
- [Community Applications and Guides](community-resources/community-applications-and-guides.md)
- [Change Service Grantee Information](info-request.md)
---
---
# Misc.
- [Code of Conduct](coc.md)
- [Licensing](licensing.md)
@@ -1,457 +0,0 @@
# NymVPN alpha
Nym has announced [NymVPN](https://nymvpn.com/en) and presented the [NymVPN Litepaper](https://nymvpn.com/nymVPN-litepaper.pdf). At CCC 2023 we have the unique opportunity to do the first alpha public testing. This page provides a how to guide, explaining steps to install and run NymVPN CLI and GUI on the Nym Sandbox testnet.
NymVPN is a client that uses [Nym Mixnet](https://nymtech.net) to anonymise users entire internet traffic.
The default is to run in 5-hop mode:
```
┌─►mix──┐ mix mix
│ │
Entry │ │ Exit
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
│ │
│ │
mix └─►mix──┘ mix
```
Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateway's, and is not passed between Mix Nodes.
The client can optionally do the first connection to the entry gateway using Wireguard. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device.
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn.md#testing) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research at 37c3*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko).
```
## Goals
This alpha testing will help:
* Stabilise NymVPN client
* Understand NymVPN client behavior in various setups (OS, connectivity, etc.)
* Stabilize the VPN infrastructure and improve its reliability / speed / features (e.g. IPv6 support)
* Load test the network in Sandbox environment and identify / anticipate potential weaknesses
```admonish info
Our alpha testing round is done live with some participants at CCC 2023. This guide will not work for everyone, as the NymVPN binaries aren't publicly accessible yet. Note that this setup of Nym testnet Sandbox environment is limited for CCC 2023 event and some of the configurations will not work in the future.
```
> **If you commit to test NymVPN alpha, please start with the [user research form](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) where all the steps will be provided**.
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
We have CLI and UI binaries available for Linux (Debian based) and macOS.
![](images/image1.png)
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/ccc) to download the binary for your system.
* Open terminal in the same directory and make executable by running:
```sh
# for CLI
chmod +x ./nym-vpn-cli
# for GUI
chmod +x ./nym-vpn_0.0.0_amd64.AppImage
# make sure your path to package is correct and the package name as well
```
* If you prefer to use the `.deb` version for installation (Linux only), open terminal in the same directory and run:
```
sudo dpkg -i ./<PACKAGE_NAME>.deb
# or
sudo apt-get install -f ./<PACKAGE_NAME>.deb
```
* For CLI: Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below.
### GUI configuration
* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
```sh
# for Linux
mkdir $HOME/.config/nym-vpn/
# for macOS
mkdir $HOME/Library/Application Support/nym-vpn/
```
* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the directory `nym-vpn` you just created
* Create the main config file called `config.toml` in the same directory with this content:
```toml
# for Linux
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env" # change <USER> to your username
entry_node_location = "DE" # two letters country code
# for macOS
env_config_file = "$HOME/Library/Application Support/nym-vpn/"
entry_node_location = "DE" # two letters country code
```
## Running
***For NymVPN to work, all other VPNs must be switched off!***
**Note:** At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
* Get your private key for wireguard setup [here](https://nymvpn.com/en/37c3)
* See a JSON list of all Gateways [here](https://nymvpn.com/en/ccc/api/gateways)
* As NymVPN need a permission for network settings, it is run with `sudo` root command
### CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
```admonish info
The entire command syntax with all the needed arguments' values and your private key for testing purposes can be found on our event webpage [nymvpn.com/en/37c3](https://nymvpn.com/en/37c3).
```
Running a help command:
```sh
./nym-vpn-cli --help
```
~~~admonish example collapsible=true title="Console output"
```
Usage: nym-vpn-cli [OPTIONS]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
--mixnet-client-path <MIXNET_CLIENT_PATH>
Path to the data directory of a previously initialised mixnet client, where the keys reside
--entry-gateway-id <ENTRY_GATEWAY_ID>
Mixnet public ID of the entry gateway
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
Auto-select entry gateway by country ISO
--exit-router-address <EXIT_ROUTER_ADDRESS>
Mixnet recipient address
--exit-gateway-id <EXIT_GATEWAY_ID>
--exit-router-country <EXIT_ROUTER_COUNTRY>
Mixnet recipient address
--enable-wireguard
Enable the wireguard traffic between the client and the entry gateway
--private-key <PRIVATE_KEY>
Associated private key
--wg-ip <WG_IP>
The IP address of the wireguard interface
--ip <IP>
The IP address of the TUN device
--mtu <MTU>
The MTU of the TUN device
--disable-routing
Disable routing all traffic through the VPN TUN device
--enable-two-hop
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
--enable-poisson-rate
Enable Poisson process rate limiting of outbound traffic
-h, --help
Print help
-V, --version
Print version
```
~~~
To run the CLI a few things need to be specified:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
### Options Flags
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users:
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/ccc/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/ccc/api/gateways)
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/37c3)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/37c3)
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
**More advanced options**
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
### GUI
If you installed the `.deb` package you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
Make sure you went through the GUI configuration in the [preparation section](./nym-vpn.md#gui-configuration). Then open terminal in the same directory where you [installed](./nym-vpn.md#preparation) the binary run:
```sh
sudo -E ./nym-vpn_0.0.0_amd64.AppImage
# In case you use another binary, just add the correct name as:
sudo -E ./<FULL_BINARY_NAME>
```
In case of errors, see [troubleshooting section](./nym-vpn.md#macos-alert-on-nymvpn-ui-startup).
## Testing
One of the main aims of the demo is testing; your results will help us to make NymVPN robust and stabilise both the client and the network through provided measurements. Before you go into testing, make sure you went through [preparation steps](./nym-vpn.md#preparation) and the [CLI binary](https://github.com/nymtech/nym/releases/tag/ccc) for your system is ready to run.
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary and `sandbox.env` to that directory
2. Copy the [block below](./nym-vpn.md#testssh) and save it to the same folder as `tests.sh`
3. Open terminal in the same directory
4. Turn off any existing VPN's (including the NymVPN instances) and make the script executable by running `chmod +x ./tests.sh`
5. Run the tests: `sudo ./tests.sh`
6. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
7. The script will print a JSON view of existing Gateways and prompt you to:
- ***(Make sure to use two different Gateways for entry and exit!)***
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
- `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/37c3)
8. Note that the testing script doesn't print many logs, in case of doubts you can check logs in the log file `temp_log.txt` located in the same directory.
9. The script shall run the tests and generate a folder called `tests_<LONG_STRING>` and files `perf_test_results.log` or `two_hop_perf_test_results.log` as well as some temp files. This is how the directory structure will look like:
```sh
nym-vpn-tests
├── tests.sh
├── nym-vpn-cli
├── sandbox.env
├── perf_test_results.log
├── tests_<LONG_STRING>
│   ├── api_response_times.txt
│   ├── download_time.txt
│   └── ping_results.txt
├── timeout
└── two_hop_perf_test_results.log
```
10. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
11. When the tests are finished, remove the `nym-vpn-cli` binary from the folder and compress it as `nym-vpn-tests.zip`
12. Upload this compressed file to the [questionnaire](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) upload field when prompted.
#### tests.sh
```sh
#!/bin/bash
ENDPOINT="https://sandbox-nym-api1.nymtech.net/api/v1/gateways/described"
json_array=()
echo "🚀 🏎 - please be patient, i'm fetching you your entry points - 🚀 🏎 "
data=$(curl -s "$ENDPOINT" | jq -c '.[] | {host: .bond.gateway.host, hostname: .self_described.host_information.hostname, identity_key: .bond.gateway.identity_key, exitGateway: .self_described.ip_packet_router.address}')
while IFS= read -r entry; do
host=$(echo "$entry" | jq -r '.host')
hostname=$(echo "$entry" | jq -r '.hostname')
identity_key=$(echo "$entry" | jq -r '.identity_key')
exit_gateway_address=$(echo "$entry" | jq -r '.exitGateway // empty')
valid_ip=$(echo "$host")
if [ -n "$exit_gateway_address" ]; then
exit_gateway="{\"address\": \"${exit_gateway_address}\"}"
else
exit_gateway="{}"
fi
if [[ $valid_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
country_info=$(curl -s "http://ipinfo.io/${valid_ip}/country" | tr -d '\n')
country_info_escaped=$(echo "$country_info" | tr -d '\n' | jq -aRs . | tr -d '"')
else
country_info_escaped=""
fi
json_object="{\"hostname\": \"${hostname}\", \"identityKey\": \"${identity_key}\", \"exitGateway\": ${exit_gateway}, \"location\": \"${country_info_escaped}\"}"
json_array+=("$json_object")
done < <(echo "$data")
if [ $? -ne 0 ]; then
echo "error fetching data from endpoint"
exit 1
fi
download_file() {
local file_url=$1
local output_file=$2
local time_file=$3
echo "starting download speed test..."
local start_time=$(date +%s)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
wget -O $output_file $file_url
elif [[ "$OSTYPE" == "darwin"* ]]; then
curl -o $output_file $file_url
fi
local end_time=$(date +%s)
local elapsed_time=$((end_time - start_time))
echo "download speed test completed in $elapsed_time seconds." >"$time_file"
}
if ! command -v jq &>/dev/null; then
echo "jq is not installed. Please install jq to proceed."
exit 1
fi
temp_log_file="temp_log.txt"
perform_tests() {
local gateway_id=$1
local exit_address=$2
local test_directory="tests_${gateway_id}_${exit_address}"
local file_url="http://ipv4.download.thinkbroadband.com/2MB.zip"
mkdir -p "$test_directory"
local ping_results_file="${test_directory}/ping_results.txt"
local download_time_file="${test_directory}/download_time.txt"
local api_response_file="${test_directory}/api_response_times.txt"
# ping test
echo "starting ping test..."
for site in google.com youtube.com facebook.com baidu.com wikipedia.org amazon.com twitter.com instagram.com yahoo.com ebay.com netflix.com; do
ping -c 4 $site >>"$ping_results_file"
done
echo "ping test completed. Results saved in $ping_results_file"
# download speed test
download_file $file_url /dev/null "$download_time_file"
# api test
local api_endpoint="https://validator.nymtech.net/api/v1/mixnodes"
local iterations=10
>"$api_response_file"
for i in $(seq 1 $iterations); do
local start_time=$(date +%s)
local response=$(curl -s -o /dev/null -w '%{http_code}' $api_endpoint)
local end_time=$(date +%s)
local elapsed_seconds=$((end_time - start_time))
local hours=$((elapsed_seconds / 3600))
local minutes=$(((elapsed_seconds % 3600) / 60))
local seconds=$((elapsed_seconds % 60))
local human_readable_time=$(printf "%02dh:%02dm:%02ds" $hours $minutes $seconds)
echo "iteration $i: response Time = ${human_readable_time}, status code = $response" >>"$api_response_file"
done
echo "api response test completed. Results saved in $api_response_file."
}
printf "%s\n" "${json_array[@]}" | jq -s .
read -p "enter a gateway ID: " identity_key
read -p "enter an exit address: " exit_address
while true; do
read -p "enable WireGuard? (yes/no): " enable_wireguard
enable_wireguard=$(echo "$enable_wireguard" | tr '[:upper:]' '[:lower:]')
case "$enable_wireguard" in
"yes")
read -p "enter your WireGuard private key: " priv_key
read -p "enter your WireGuard IP: " wg_ip
wireguard_options="--enable-wireguard --private-key $priv_key --wg-ip $wg_ip"
break
;;
"no")
wireguard_options=""
break
;;
*)
echo "invalid response. please enter 'yes' or 'no'."
;;
esac
done
sudo ./nym-vpn-cli -c sandbox.env --entry-gateway-id ${identity_key} --exit-router-address ${exit_address} --enable-two-hop $wireguard_options >"$temp_log_file" 2>&1 &
timeout=15
start_time=$(date +%s)
while true; do
current_time=$(date +%s)
if grep -q "received plain" "$temp_log_file"; then
echo "successful configuration with identity_key: $identity_key and exit address: $exit_address" >>perf_test_results.log
perform_tests "$identity_key" "$exit_address"
break
fi
if ((current_time - start_time > timeout)); then
echo "failed to connect with identity_key: $identity_key using the exit address: $exit_address" >>perf_test_results.log
break
fi
sleep 1
done
echo "terminating nym-vpn-cli..."
pkill -f './nym-vpn-cli'
sleep 5
rm -f "$temp_log_file"
```
## Troubleshooting
Below are listed some points which may need to be addressed when testing NymVPN alpha. If you crashed into any errors which are not listed, please contact us at the event.
#### Thread `main` panicked
If you see a message like:
```sh
thread 'main' panicked at /Users/runner/.cargo/git/checkouts/mullvadvpn-app-a575cf705b5dfd76/ccfbaa2/talpid-routing/src/unix.rs:301:30:
```
Restart your wifi connection and start again.
#### macOS alert on NymVPN UI startup
If you are running NymVPN on mac OS for the first time, you may see this alert message:
![](images/image3.png)
1. Head to System Settings -> Privacy & Security and click `Allow anyway`
![](images/image5.png)
2. Confirm with your password or TouchID
3. Possibly you may have to confirm again upon running the application
#### Missing `jq` error
In case of missing `jq` on Linux (Debian) install it with:
```sh
# Linux (Debian)
sudo apt-get install jq
# macOS
brew install jq
```
On some Linux distributions however the [script](./nym-vpn.md#testssh) returns `jq` error even if your system claims that `jq is already the newest version`.
In that case, comment the `jq` check in the script as follows:
```sh
#if ! command -v jq &>/dev/null; then
# echo "jq is not installed. Please install jq to proceed."
# exit 1
#fi
```
#### Error current_time: not found
When running `sudo sh ./test.sh` you may see an error like: `93: current_time: not found`. This has something to do with the `current_time` setup of your system and on itself shall not have a negative impact on the test. It has nothing to do with the client at all as it only relates to the code in our testing script.
#### Not connecting to the endpoint
In case the automatic download of all the Gateways fail (and it shouldn't), you do an easy manual work around:
1. Open the list of Gateways created by API [here](https://nymvpn.com/en/ccc/api/gateways)
2. On top click on `JSON` option (shall be default view) and `Save`
3. Save it as `data.json` to the `nym-vpn-tests` folder
4. Replace line 3 in the [script `tests.sh`](./nym-vpn.md#testssh) with:
```sh
NEW_ENDPOINT="http://localhost:8000/data.json"
```
5. In a new terminal window run:
```sh
python3 -m http.server 8000
```
6. Continue with the steps listed in [testing section](./nym-vpn.md#testing)
@@ -1,14 +0,0 @@
# Chaos Communication Congress 2023
At [CCC 2019](https://constructiveproof.com/posts/2020-01-27-nym-testnet-up-and-running/) we announced the first demo of the [Nym Mixnet](https://github.com/nymtech/nym). Now, four years later we are honoured to present our new product: [NymVPN](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d). NymVPN is a client/application which captures the entire network stack of a user's device, sending this traffic through either a 2-hop VPN or through the 5-hop Mixnet. CCC 2023 is the first public demo of both the GUI and CLI versions of NymVPN.
At first glance Nym platform may seem pretty complex. To make the connection between Nym and the community of developers, operators and users participating on the event as smooth as possible, we created a segment of documentation tailored specifically for the conference. The following pages provide an easy introduction to the NymVPN alpha client. We recommend interested developers to begin with [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) pages.
**See pages below for more content:**
* [NymVPN alpha guide](nym-vpn.md)
* [FAQ @ 37c3](faq.md)
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/897010658?h=1f55870fe6&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="NYMVPN alpha demo 37C3"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

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