From 5f7b3db9a40487ee852d292f54922c72100a2e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 2 Jun 2022 16:54:59 +0300 Subject: [PATCH] Feature/spend coconut (#1261) * Add coconut verifier structure for coconut protocol in gateway * Add endpoint for validator-api cred verification * Remove unused signature field * Register new endpoint * Improve validator-api config handling * Aggregate verif result from all apis * Simplify aggregate functions * Verify cred on apis correctly * Introduced coconut bandwidth contract to validator client * Fix rebase double import * Fix clippy on non-coconut * Add multisig contract address to validator client * Refactor Credential struct * Do bincode magic in the coconut interface * Implement serialization for credential and remove bindcode * Fix clippy and don't remove dkg * Client release funds proposal * Add wrapper for blinded serial number Also compare theta with a blinded serial number (in base 58 form) for future double spend protection. * Only post blinded serial number to blockchain * Validator api propose credential spending * Fix wallet * Gateway calls proposal creation * Query for proposal in verify coconut * Remove db from git * Verify against proposal description * Validator apis vote based on verification of cred * Fix wallet fmt * Execute the release of funds * Fix translation between token and bytes * Update CHANGELOG --- CHANGELOG.md | 3 + Cargo.lock | 54 +++- Cargo.toml | 1 + clients/credential/Cargo.toml | 1 - clients/credential/credential.db | 1 - clients/credential/src/client.rs | 51 ++-- clients/credential/src/commands.rs | 2 +- .../client-libs/gateway-client/src/client.rs | 7 +- .../client-libs/validator-client/Cargo.toml | 3 + .../validator-client/src/client.rs | 146 ++++++----- .../validator-client/src/connection_tester.rs | 7 +- .../src/nymd/cosmwasm_client/logs.rs | 2 +- .../validator-client/src/nymd/error.rs | 6 + .../validator-client/src/nymd/mod.rs | 229 ++++++++++------- .../coconut_bandwidth_signing_client.rs | 49 ++++ .../validator-client/src/nymd/traits/mod.rs | 6 + .../src/nymd/traits/multisig_query_client.rs | 24 ++ .../nymd/traits/multisig_signing_client.rs | 116 +++++++++ .../src/nymd/traits/vesting_query_client.rs | 26 +- .../src/nymd/traits/vesting_signing_client.rs | 34 +-- .../validator-client/src/validator_api/mod.rs | 57 ++++- .../src/validator_api/routes.rs | 3 + common/coconut-interface/src/error.rs | 8 +- common/coconut-interface/src/lib.rs | 237 ++++++++++++++---- .../multisig-contract/Cargo.toml | 14 ++ .../multisig-contract/src/lib.rs | 1 + .../multisig-contract}/src/msg.rs | 4 + common/credentials/src/coconut/utils.rs | 19 +- common/credentials/src/error.rs | 6 +- common/network-defaults/src/all.rs | 8 + common/network-defaults/src/lib.rs | 12 +- common/network-defaults/src/mainnet.rs | 3 + common/network-defaults/src/qa.rs | 3 + common/network-defaults/src/sandbox.rs | 3 + common/nymcoconut/src/proofs/mod.rs | 3 +- common/nymcoconut/src/scheme/double_use.rs | 49 ++++ common/nymcoconut/src/scheme/mod.rs | 4 +- common/nymcoconut/src/scheme/verification.rs | 17 +- contracts/Cargo.lock | 13 + .../multisig/cw3-flex-multisig/Cargo.toml | 2 + .../cw3-flex-multisig/src/contract.rs | 4 +- .../multisig/cw3-flex-multisig/src/lib.rs | 1 - explorer-api/src/client.rs | 11 +- gateway/gateway-requests/Cargo.toml | 1 - .../src/registration/handshake/error.rs | 5 - gateway/gateway-requests/src/types.rs | 22 +- gateway/src/node/client_handling/bandwidth.rs | 18 +- .../connection_handler/authenticated.rs | 70 +++++- .../websocket/connection_handler/coconut.rs | 27 ++ .../connection_handler/eth_events.rs | 36 ++- .../websocket/connection_handler/fresh.rs | 14 +- .../websocket/connection_handler/mod.rs | 2 + .../client_handling/websocket/listener.rs | 11 +- gateway/src/node/mod.rs | 33 ++- mixnode/src/node/mod.rs | 10 +- nym-wallet/Cargo.lock | 60 +++++ nym-wallet/src-tauri/src/config/mod.rs | 15 +- .../src/operations/mixnet/account.rs | 18 +- .../src/operations/simulate/admin.rs | 2 +- .../src/operations/simulate/mixnet.rs | 14 +- .../src/operations/simulate/vesting.rs | 12 +- .../src/operations/vesting/delegate.rs | 2 +- nym-wallet/src/types/rust/gateway.ts | 11 +- nym-wallet/src/types/rust/mixnode.ts | 12 +- .../src/types/rust/rewardedsetnodestatus.ts | 3 +- validator-api/Cargo.toml | 1 + validator-api/src/coconut/client.rs | 14 +- validator-api/src/coconut/error.rs | 14 ++ validator-api/src/coconut/mod.rs | 109 +++++++- validator-api/src/coconut/tests.rs | 46 +++- validator-api/src/config/mod.rs | 75 ++++-- validator-api/src/config/template.rs | 35 ++- validator-api/src/main.rs | 77 +++--- validator-api/src/nymd_client.rs | 123 ++++++--- .../src/rewarded_set_updater/error.rs | 3 - 75 files changed, 1580 insertions(+), 565 deletions(-) delete mode 100644 clients/credential/credential.db create mode 100644 common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs create mode 100644 common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs create mode 100644 common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs create mode 100644 common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml create mode 100644 common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs rename {contracts/multisig/cw3-flex-multisig => common/cosmwasm-smart-contracts/multisig-contract}/src/msg.rs (94%) create mode 100644 common/nymcoconut/src/scheme/double_use.rs create mode 100644 gateway/src/node/client_handling/websocket/connection_handler/coconut.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fa72933a7..50dc0d6fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284]) - all: added network compilation target to `--help` (or `--version`) commands ([#1256]). - network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]). +- gateway: Added gateway coconut verifications and validator-api communication for double spending protection ([#1261]) +- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261]) ### Fixed @@ -37,6 +39,7 @@ [#1256]: https://github.com/nymtech/nym/pull/1256 [#1257]: https://github.com/nymtech/nym/pull/1257 [#1260]: https://github.com/nymtech/nym/pull/1260 +[#1261]: https://github.com/nymtech/nym/pull/1261 [#1265]: https://github.com/nymtech/nym/pull/1265 [#1267]: https://github.com/nymtech/nym/pull/1267 [#1275]: https://github.com/nymtech/nym/pull/1275 diff --git a/Cargo.lock b/Cargo.lock index ccc731321e..5df601a7b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -873,7 +873,6 @@ dependencies = [ "bip39", "cfg-if 0.1.10", "clap 3.1.8", - "coconut-bandwidth-contract-common", "coconut-interface", "credential-storage", "credentials", @@ -1166,6 +1165,42 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "darling" version = "0.13.4" @@ -1951,7 +1986,6 @@ dependencies = [ name = "gateway-requests" version = "0.1.0" dependencies = [ - "bincode", "bs58", "coconut-interface", "credentials", @@ -2910,6 +2944,18 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "native-tls" version = "0.2.10" @@ -3226,6 +3272,7 @@ dependencies = [ "humantime-serde", "log", "mixnet-contract-common", + "multisig-contract-common", "nymcoconut", "nymsphinx", "okapi", @@ -6203,16 +6250,19 @@ dependencies = [ "async-trait", "base64", "bip39", + "coconut-bandwidth-contract-common", "coconut-interface", "colored", "config", "cosmrs", "cosmwasm-std", + "cw3", "flate2", "futures", "itertools", "log", "mixnet-contract-common", + "multisig-contract-common", "network-defaults", "prost 0.10.3", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index ac68ef15a0..131fd2dbb4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "common/cosmwasm-smart-contracts/coconut-bandwidth-contract", "common/cosmwasm-smart-contracts/contracts-common", "common/cosmwasm-smart-contracts/mixnet-contract", + "common/cosmwasm-smart-contracts/multisig-contract", "common/cosmwasm-smart-contracts/vesting-contract", "common/mixnode-common", "common/network-defaults", diff --git a/clients/credential/Cargo.toml b/clients/credential/Cargo.toml index cfd7dc97f7..54fdb0ee21 100644 --- a/clients/credential/Cargo.toml +++ b/clients/credential/Cargo.toml @@ -17,7 +17,6 @@ thiserror = "1.0" url = "2.2" tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macros"] } # async runtime -coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" } coconut-interface = { path = "../../common/coconut-interface" } credentials = { path = "../../common/credentials" } credential-storage = { path = "../../common/credential-storage" } diff --git a/clients/credential/credential.db b/clients/credential/credential.db deleted file mode 100644 index cdc6e614b3..0000000000 --- a/clients/credential/credential.db +++ /dev/null @@ -1 +0,0 @@ -[{"1C703C0BF3FC559E09E3DFB9846A9194191915E55A03F999EDC9279319CEBDDB":"{\"amount\":1000000,\"tx_hash\":\"1C703C0BF3FC559E09E3DFB9846A9194191915E55A03F999EDC9279319CEBDDB\",\"signing_keypair\":{\"public_key\":\"CacF8UyTpcxN1eueg8MG4dF5Trf74ZX1joWdGyuCQSYw\",\"private_key\":\"F8rkWjiEeNqXGY2dApxMdF9j15mn6ErhsAfBnmX3AMnX\"},\"encryption_keypair\":{\"public_key\":\"2Gvhyib4CJx3AVb8bg1fThPJ1FdySoapjgW8AzFyqymC\",\"private_key\":\"7WmF61KHWxiauzR4r82fK7NR8V7t5dbp2FrafP9KfYQx\"},\"blind_request_data\":{\"serial_number\":[217,158,131,252,67,46,125,220,192,31,52,16,80,226,101,249,38,236,180,207,127,126,24,218,158,216,7,175,240,195,13,93],\"binding_number\":[190,14,43,219,219,238,38,109,64,129,247,215,26,111,122,225,142,158,111,115,187,225,250,248,43,100,216,89,87,75,229,40],\"first_attribute\":[108,111,200,242,190,67,6,60,28,121,105,77,36,198,104,18,146,110,173,47,3,55,92,195,251,51,155,86,30,2,181,90],\"second_attribute\":[26,54,139,25,107,59,204,245,146,62,178,206,95,198,20,95,44,70,32,212,88,167,204,255,51,158,246,232,121,69,16,51],\"blind_sign_req\":[185,251,71,165,120,153,76,190,174,242,198,198,141,177,212,57,39,196,3,42,196,120,132,7,53,141,132,255,228,157,26,20,99,212,42,191,44,17,36,61,240,224,34,224,94,78,90,138,162,198,53,118,137,234,10,52,230,147,204,10,109,217,71,227,216,255,39,7,208,11,37,176,177,195,165,12,99,177,188,217,201,54,220,215,217,191,251,37,37,24,71,45,140,66,222,236,2,0,0,0,0,0,0,0,175,191,107,206,203,123,5,142,154,151,146,69,203,158,133,118,60,247,48,204,14,167,114,15,129,190,148,183,252,35,82,22,126,151,42,197,43,177,192,193,84,156,68,244,122,228,14,190,145,147,5,150,242,184,228,43,251,20,165,25,97,236,201,237,40,47,157,235,165,92,9,192,34,83,204,136,91,226,97,11,245,8,219,50,22,85,192,169,35,31,118,4,144,9,80,70,222,110,208,77,182,13,80,85,95,165,150,127,48,181,210,180,74,240,173,43,118,215,55,146,218,157,130,108,202,139,154,15,201,88,253,134,208,203,164,215,91,243,24,70,28,129,137,249,47,131,77,91,75,32,37,192,154,13,95,110,91,241,219,113,2,0,0,0,0,0,0,0,73,102,227,42,178,212,22,215,219,83,51,155,135,226,79,50,158,48,34,141,160,239,129,217,207,157,209,74,167,156,90,88,215,245,3,108,147,132,224,225,40,189,35,225,253,180,135,247,45,67,64,217,83,75,160,14,207,75,116,210,220,192,189,3,2,0,0,0,0,0,0,0,18,196,183,38,67,8,219,14,183,60,138,123,31,55,31,18,151,74,184,0,61,84,35,173,192,178,50,117,229,225,148,6,104,98,54,139,122,40,129,216,190,185,184,135,121,241,170,207,59,190,203,94,212,222,34,27,160,152,233,52,241,175,84,67]},\"signature\":\"y59zgkBne96tAKREkDQ5swA4mCimpooVLyLNAL3jzdVXVHHd43SoS56pfnHULzs9yHP3AJnGCR6FLyHKEhBSXPrYfEGSLMdhy9VrjYiyhthmd6BsKWqS9rGAG6PbWt2PpDh\"}","90C9B03B448608B95D13128472789B27399100D18C46A49254CB13AD07A0BE88":"{\"amount\":1000000,\"tx_hash\":\"90C9B03B448608B95D13128472789B27399100D18C46A49254CB13AD07A0BE88\",\"signing_keypair\":{\"public_key\":\"EadiWGJPJozdxSMPK3r1sr8oHZEcVQU5qWUmqAn7jDNQ\",\"private_key\":\"ASQHYmsq4X1eMEuQvFwGiunXuZxEzm79PPydmY6CKWZt\"},\"encryption_keypair\":{\"public_key\":\"3d4jyDrdepHZPRQo6KzQFpR5kvPySqYqWC19H51cjhMh\",\"private_key\":\"HDMhDCTiTu5ChUxWJqZRCR7vZnYULyTu5Rx5Vh2VNxTm\"},\"blind_request_data\":{\"serial_number\":[153,108,141,81,89,232,4,244,22,216,186,112,32,142,11,42,87,74,40,216,191,253,127,49,67,243,183,188,162,46,221,75],\"binding_number\":[7,68,244,43,146,101,216,254,14,27,164,250,41,195,187,242,189,144,126,105,179,35,128,247,30,101,200,151,235,224,92,80],\"first_attribute\":[175,216,242,214,61,233,96,76,17,254,79,233,23,217,84,94,32,68,225,249,201,46,109,183,90,63,126,121,114,87,239,44],\"second_attribute\":[180,82,202,163,28,149,147,178,186,66,26,149,55,177,45,154,185,20,62,68,123,70,188,73,28,146,99,235,91,225,49,107],\"blind_sign_req\":[153,12,234,149,97,21,0,51,175,113,17,215,121,255,21,185,27,59,204,115,208,171,136,219,140,125,151,168,85,240,167,222,13,162,250,39,50,74,172,251,237,85,136,41,34,71,198,215,151,16,80,136,115,178,27,230,102,25,4,12,17,204,225,49,67,109,125,119,100,204,201,90,210,111,118,15,146,244,29,130,64,181,67,178,222,190,27,122,12,47,56,63,187,66,236,130,2,0,0,0,0,0,0,0,161,138,107,198,253,152,7,86,206,134,122,66,171,152,77,90,251,134,64,95,193,174,162,252,176,193,217,52,36,53,207,237,188,84,208,204,183,94,103,168,118,149,151,175,40,134,171,53,184,102,151,180,126,82,124,138,79,130,214,135,133,54,251,182,165,32,6,4,34,75,153,113,11,49,157,149,28,150,8,71,214,47,224,119,109,16,113,85,140,181,214,194,11,168,127,178,189,117,99,79,90,134,207,33,102,42,232,150,89,209,247,165,80,91,237,42,201,223,187,88,98,217,4,199,226,1,60,15,45,159,103,94,185,43,99,142,109,186,212,240,9,161,247,135,184,165,75,67,32,47,122,196,39,199,184,176,87,130,30,20,2,0,0,0,0,0,0,0,117,234,104,216,238,168,141,1,212,50,149,242,215,231,19,91,45,18,164,168,24,68,40,57,25,141,165,63,152,72,121,27,135,89,68,176,9,8,176,145,131,50,160,195,7,248,80,104,135,142,236,251,21,55,39,170,86,133,224,50,39,88,252,31,2,0,0,0,0,0,0,0,65,93,35,204,132,168,2,49,218,27,106,107,95,165,195,206,45,63,158,197,65,106,131,71,43,76,129,106,87,155,146,97,187,218,99,40,125,68,50,152,115,112,80,96,21,11,203,94,118,186,209,236,234,35,159,178,148,59,238,39,218,225,219,106]},\"signature\":\"u3BgWtmZZoNG9tSkC37Z6s15EZXLkY4PiYJX6HHA4iwHQKqKvNVuruPaRaP5ra9448G8iBDbybL94RSRfRYZGbeheMcnwaynvuUJbVh3KmdGLJR3Yr5UoNNjoLqEDLzP3jV\"}","04296C930C81F37096FD180CD93896A164CF5F999DF7D09C0124BE1DC75A5358":"{\"amount\":1000000,\"tx_hash\":\"04296C930C81F37096FD180CD93896A164CF5F999DF7D09C0124BE1DC75A5358\",\"signing_keypair\":{\"public_key\":\"9qnzPdvyUjgWFj7RPpvfbEkG7CbnUUdfj1Zi4nKA8B9b\",\"private_key\":\"ALa8vt73t6sjgbeHg5C6NuezvHbENMabZ3wzxv47TGyo\"},\"encryption_keypair\":{\"public_key\":\"E15DAJTRDvAj7JfZ7Rn5DUffcu72HVJyqYX5gm9WVPZu\",\"private_key\":\"4GLy6J8FVTDRdNzuKyDRg1BRQXNxA8gCALaTJKfYYkNR\"},\"blind_request_data\":{\"serial_number\":[67,28,114,203,240,92,183,145,79,253,109,235,240,221,156,199,216,66,67,175,124,158,226,171,141,131,244,120,187,230,146,62],\"binding_number\":[129,8,63,239,8,211,192,30,124,87,30,110,102,86,77,95,149,127,177,126,28,196,245,29,63,127,128,236,82,231,207,96],\"first_attribute\":[19,21,49,159,55,242,100,60,218,124,120,115,254,64,144,181,222,30,31,41,163,110,117,11,0,11,206,221,126,242,191,41],\"second_attribute\":[29,63,187,178,21,49,68,13,190,62,208,224,249,34,37,116,201,114,139,210,185,67,187,4,41,166,130,243,171,5,10,50],\"blind_sign_req\":[171,30,44,213,50,220,70,49,93,74,79,21,41,89,12,7,20,204,72,50,202,62,231,87,64,154,132,195,48,55,131,212,232,22,174,42,151,232,149,62,132,149,238,112,133,68,224,197,164,96,56,253,196,195,89,4,213,177,122,252,77,167,117,179,245,15,85,100,250,103,77,53,61,61,123,229,206,157,80,36,36,254,220,48,88,110,197,43,117,255,59,58,140,60,99,116,2,0,0,0,0,0,0,0,137,28,145,218,237,220,203,213,63,138,166,104,255,246,227,123,203,7,135,135,17,186,27,83,222,28,234,44,53,189,72,192,238,95,141,136,30,65,57,226,125,137,230,137,165,111,183,73,147,55,3,79,148,223,224,23,198,63,0,58,38,228,229,26,206,77,214,109,208,178,255,168,27,124,121,219,174,252,174,110,71,121,172,24,65,222,31,137,239,55,117,144,153,19,54,107,146,125,29,228,135,153,81,207,193,104,103,17,31,180,67,194,197,205,54,14,225,96,193,141,145,121,219,226,227,196,200,37,110,168,46,27,73,173,238,35,26,59,50,148,24,224,90,88,145,12,206,181,108,216,217,49,19,4,105,249,48,205,167,61,2,0,0,0,0,0,0,0,230,46,74,45,156,42,223,24,154,49,207,38,172,223,75,51,137,46,151,242,155,118,37,115,132,207,113,134,124,78,244,59,181,239,71,132,221,190,90,37,6,50,91,214,82,47,39,151,91,78,249,241,56,242,144,138,9,127,32,51,36,164,155,98,2,0,0,0,0,0,0,0,108,190,2,160,139,55,81,228,126,190,247,27,209,83,87,146,171,103,186,168,4,119,76,165,105,105,67,43,159,106,61,87,221,24,235,97,67,137,80,143,180,205,102,162,244,126,43,144,21,243,251,236,15,174,158,163,180,20,123,72,235,124,31,57]},\"signature\":\"yd9yDABbw8c3wgT8Pof7Z2jidAJs8RoL8KbYH2F1tmrMMCJwhUMzSjpyma6kS9WgaJsFBEbNXpwH1ip6LETfPgvfUJJo2Z9c5312TMAfR8qFr4Zc3X9aQyAo8kMvijCT7jA\"}","5E3CF2741F73091EA9B651B271D9925D6FBE3F67E9C5D3793303DA6D44F12FE5":"{\"amount\":1000000,\"tx_hash\":\"5E3CF2741F73091EA9B651B271D9925D6FBE3F67E9C5D3793303DA6D44F12FE5\",\"signing_keypair\":{\"public_key\":\"AEu3bhLcRZBxjoDVaTmpbNqPhyXRuiN5RQAhoRhcwjxt\",\"private_key\":\"DoEaH8EnoXef7SDyuSTuFt5CMWMdErWrBVfRrpnhmJYq\"},\"encryption_keypair\":{\"public_key\":\"HAW7XdJUMGV5fWAQLpZnHMX7nEnGJsRdgekVgYiqzwPL\",\"private_key\":\"G8wSowRi8weKD2dnM8YBvaGB94FEUrmsxLVQK7x6wz4k\"},\"blind_request_data\":{\"serial_number\":[184,233,37,105,38,174,157,246,96,46,155,173,169,113,202,128,121,35,166,191,146,13,251,46,141,104,88,140,102,165,124,33],\"binding_number\":[46,252,137,11,240,92,139,154,129,213,239,195,94,37,102,119,146,151,182,103,77,171,231,223,13,235,14,240,134,92,29,112],\"first_attribute\":[81,14,113,15,19,75,0,228,7,169,12,125,185,125,247,228,63,164,101,82,90,68,65,89,178,210,131,190,14,66,152,55],\"second_attribute\":[174,224,189,178,99,214,9,196,186,65,150,19,176,132,244,148,52,17,138,132,35,205,216,120,59,138,48,210,149,139,35,51],\"blind_sign_req\":[171,19,145,159,249,120,163,237,106,166,105,120,112,17,203,245,126,95,37,190,221,18,186,211,218,99,1,138,95,12,189,224,120,114,175,30,76,87,106,207,58,154,38,30,109,233,29,16,151,198,168,43,105,60,149,9,167,47,211,231,33,186,239,191,191,118,37,242,211,204,130,120,78,11,51,132,62,148,73,186,225,81,140,67,32,64,90,253,196,89,74,31,1,165,183,22,2,0,0,0,0,0,0,0,167,72,204,0,179,90,227,17,51,115,156,33,198,76,219,218,168,111,226,107,228,150,49,239,252,92,166,195,133,251,3,13,167,75,235,27,132,162,11,238,215,48,32,222,253,156,10,133,148,143,7,75,15,86,228,116,119,12,48,63,189,1,131,94,46,245,82,150,198,204,251,125,76,147,205,235,95,153,177,161,233,11,49,243,106,216,201,6,31,110,185,11,166,171,42,143,5,127,215,168,27,229,207,225,197,236,87,213,24,123,205,174,250,156,178,255,204,108,198,150,53,188,144,14,217,234,92,80,123,101,99,157,7,197,207,108,35,125,251,52,180,185,67,155,220,175,32,116,202,128,93,92,77,95,79,205,62,99,110,13,2,0,0,0,0,0,0,0,10,126,251,84,164,181,206,92,82,231,45,58,6,5,79,13,255,73,78,157,121,48,109,118,27,225,108,237,11,228,105,78,177,226,89,24,13,31,52,160,195,224,191,232,115,107,242,8,173,82,83,32,37,118,141,172,217,238,110,243,141,174,211,62,2,0,0,0,0,0,0,0,163,242,24,184,82,249,187,221,4,105,16,203,144,106,58,115,50,23,107,30,252,71,101,155,37,5,227,23,122,10,252,65,18,113,129,88,56,199,169,202,73,196,151,1,186,168,155,7,68,165,186,157,18,222,194,25,194,58,49,125,114,43,212,4]},\"signature\":\"uHR5LkaHQc3LQUXuzydXLj783vsJtp2s5FV1CNRQ3g8rjL29U2UM3wSEFWrxjoFVq5gS8sNUEDbvhGTehWM3pGPRMVY4U68gkPWzzR7dAMX1LHYjwUdUiwNgURmgVFrG7XV\"}","3A24DE7C3C1EF38F6CBEBB1BE893EB6819E55A9C8E8164C55A9B75CE42950536":"{\"amount\":1000000,\"tx_hash\":\"3A24DE7C3C1EF38F6CBEBB1BE893EB6819E55A9C8E8164C55A9B75CE42950536\",\"signing_keypair\":{\"public_key\":\"91wBGhrPnLAM7rc8C1WK693VwfZKj7d9K7Rpfcpxo3x7\",\"private_key\":\"FEh9rCDMZx29nZwTyjezxtcytRiZZrsxM2qWdyMyuEC7\"},\"encryption_keypair\":{\"public_key\":\"9Ay88XFrErtSLeeD3DQETeaqrAgAKNR1vNjoe2PvzyoD\",\"private_key\":\"HD92abV3ZPW1VKxh3bCcHVZ8oJ5XqRjrKspuZkQMvqkT\"},\"blind_request_data\":{\"serial_number\":[132,225,105,217,220,29,178,224,94,120,11,53,170,48,254,118,226,13,138,175,1,137,6,233,199,243,82,177,15,236,8,5],\"binding_number\":[214,221,36,34,246,89,38,85,207,68,157,111,72,61,241,253,70,30,206,120,80,185,10,217,210,162,9,77,57,112,79,112],\"first_attribute\":[213,53,84,155,222,190,233,7,127,153,97,181,71,169,88,180,33,255,144,158,221,62,119,61,11,183,140,136,198,152,186,21],\"second_attribute\":[231,35,35,8,13,210,132,121,142,177,211,196,174,183,10,161,8,232,101,123,107,10,144,104,58,212,95,243,216,98,91,66],\"blind_sign_req\":[131,90,160,125,41,86,193,108,153,195,118,191,72,114,19,168,89,70,211,33,230,221,202,233,172,194,91,92,168,79,223,65,217,200,186,142,16,136,216,83,40,22,53,249,50,29,234,219,174,135,50,180,67,16,30,73,89,232,51,155,170,105,83,240,116,155,50,214,28,98,241,211,27,202,201,186,231,151,9,249,149,50,246,24,204,81,141,77,51,32,90,254,63,150,35,228,2,0,0,0,0,0,0,0,141,71,143,23,11,175,34,130,24,40,235,202,85,74,58,208,132,184,176,43,159,38,159,138,118,16,141,70,20,77,59,12,153,184,153,255,181,243,192,131,239,253,207,58,228,82,207,244,143,126,159,156,127,116,223,200,175,191,130,91,218,19,169,222,95,36,151,19,18,14,236,135,21,30,69,130,146,236,252,85,152,68,107,154,102,48,200,246,88,205,96,74,12,203,10,2,88,70,108,249,72,221,172,76,212,26,105,127,1,214,165,143,133,241,142,106,52,190,152,116,101,26,181,213,208,59,117,33,128,70,252,146,203,142,173,98,252,135,68,44,197,243,225,52,170,142,92,167,250,23,211,161,7,85,102,108,15,164,238,94,2,0,0,0,0,0,0,0,4,48,16,6,220,119,137,9,19,83,33,21,156,10,94,247,114,65,199,182,184,76,83,122,118,46,29,9,111,242,244,4,195,174,63,91,223,161,105,250,195,94,75,226,206,80,226,190,37,203,200,184,173,164,170,226,27,244,43,100,95,65,40,45,2,0,0,0,0,0,0,0,136,46,57,4,42,1,115,237,142,99,92,81,193,81,203,189,226,255,137,255,109,205,71,147,74,191,253,120,93,154,61,18,139,163,156,182,203,137,253,197,11,225,221,81,59,38,23,43,168,53,214,23,140,227,39,174,93,191,83,150,228,30,41,12]},\"signature\":\"237zXmYcb2de47menhqKVUq75YUQYmDfcZ2Cnpqf7c69i1dpK4DetfzxUmsZxkoEw3pfBZkbkZdAUHbXpKJRaVG9RVy13gucaiZVpnmrdHmyqsfUJYqxB5XJy7PvnQufvW1p\"}","AB1DB6BCE0E5BB8093CD83CE8B979A38ABA549017F1E5FBCFC6665D8301F8B10":"{\"amount\":1000000,\"tx_hash\":\"AB1DB6BCE0E5BB8093CD83CE8B979A38ABA549017F1E5FBCFC6665D8301F8B10\",\"signing_keypair\":{\"public_key\":\"BsW9Zi5fr6MVXVD9C79DHazZngKYVU29Dd2FqiB3t6GC\",\"private_key\":\"5t6asn1vRZunc4tna9doZoURJJyVsugmkDwgaQe6fmZg\"},\"encryption_keypair\":{\"public_key\":\"Gx3p6FJxBs7uEAQPWFmVwiysPBeRseC5XvUixiNFFeAt\",\"private_key\":\"CKV5eAdhsmyBijmnt1UM7wLTmxnLxbV6h2WY6JFZmYVm\"},\"blind_request_data\":{\"serial_number\":[98,22,102,53,67,142,112,200,210,202,159,104,167,22,128,165,252,131,183,212,4,150,175,203,141,146,137,193,136,219,228,67],\"binding_number\":[3,214,193,92,3,51,114,82,60,25,195,249,143,244,252,169,19,84,168,119,227,141,186,35,187,120,96,205,72,61,189,48],\"first_attribute\":[132,126,154,107,2,30,197,238,4,205,66,148,113,208,48,233,37,53,187,160,213,151,49,171,64,57,139,90,63,87,188,115],\"second_attribute\":[106,78,124,199,142,38,90,198,100,219,16,74,9,109,47,212,166,30,185,234,204,112,42,38,193,74,135,219,223,179,30,19],\"blind_sign_req\":[145,90,96,73,107,35,232,107,92,90,101,110,229,20,12,197,185,112,19,110,4,212,236,222,33,252,156,12,164,253,198,80,41,254,246,26,247,150,242,56,2,250,155,142,20,132,108,172,178,146,141,193,156,246,79,110,32,254,166,217,57,83,8,70,58,42,48,48,137,96,13,193,214,125,89,122,85,97,13,112,29,193,119,165,251,204,221,81,138,206,108,3,4,88,182,27,2,0,0,0,0,0,0,0,131,68,160,172,144,26,48,222,7,103,95,220,238,3,125,119,15,254,236,186,107,240,69,183,43,182,191,139,208,201,100,128,55,146,133,119,213,155,40,224,106,149,217,30,147,95,52,7,146,147,130,119,30,74,157,239,231,11,199,90,188,64,201,48,27,156,69,63,33,107,61,254,128,200,127,207,97,179,198,142,223,95,167,197,54,54,54,28,122,22,99,197,213,105,56,211,204,241,133,3,184,107,81,82,10,67,188,3,221,93,229,232,125,173,124,242,248,183,14,124,52,120,234,79,175,133,191,33,45,79,152,252,100,22,220,193,244,169,43,29,202,104,2,96,164,95,116,46,5,23,50,139,39,23,73,151,94,229,153,21,2,0,0,0,0,0,0,0,90,175,157,204,225,13,7,24,166,246,95,121,101,58,127,194,67,222,14,175,13,126,221,162,91,86,130,91,249,183,86,48,164,151,67,190,88,250,83,103,236,57,114,53,155,50,92,181,58,239,141,200,153,16,44,83,110,125,190,133,195,145,165,98,2,0,0,0,0,0,0,0,45,195,137,247,231,86,230,71,21,193,215,245,38,74,249,49,81,90,142,237,9,123,34,252,140,107,120,2,118,147,121,107,65,0,99,209,123,64,124,244,142,136,130,186,77,32,148,12,216,71,76,245,179,241,249,177,44,191,107,95,159,123,125,52]},\"signature\":\"24WoBrFcv1mF8fL2MH1YHnbZq69G44ZqSzbdA8ciEWY8JXTFn1G6yyKGEUkxcmLoL7r5j9fPJD4mTZUdiRQ87pLMZPCkp6M9bLZMwD7b4u2Cg8CNmVUpaZ4VbNdTf4zhZid5\"}","90B33BF08C84575E88C6ACFFE6F44DFC652BF107CDC2D541BFCA8BAC931E5BA8":"{\"amount\":1000000,\"tx_hash\":\"90B33BF08C84575E88C6ACFFE6F44DFC652BF107CDC2D541BFCA8BAC931E5BA8\",\"signing_keypair\":{\"public_key\":\"4o64HhsgHq6bCWERouYe8wLGdrBW6Zc87uRFsRyQ6KhX\",\"private_key\":\"9pcuX5cpKxGde6ZiPbsMbSKBx8qcnbSD5VkV68VuNQB3\"},\"encryption_keypair\":{\"public_key\":\"2cF4745DbeYYxymmPJYTu9R88wRVSHnC7rs4TPvLkNxm\",\"private_key\":\"6wyfg3FEwbtWJ4pAfwEVvVuo4gEJV6cc4Kj6LV3qy2WV\"},\"blind_request_data\":{\"serial_number\":[95,129,65,207,163,115,235,106,18,228,138,56,255,52,168,5,190,226,124,190,211,158,70,124,38,235,132,6,25,222,157,31],\"binding_number\":[147,54,129,176,80,3,106,255,93,179,219,233,158,175,141,145,180,245,103,169,36,3,99,138,7,139,108,186,186,193,152,17],\"first_attribute\":[61,240,7,131,88,85,251,196,132,143,227,154,197,40,194,163,27,24,53,182,97,31,61,238,241,172,115,236,99,2,240,15],\"second_attribute\":[203,202,64,166,201,186,160,157,155,156,127,116,239,44,158,213,167,57,152,228,39,153,194,20,46,222,44,246,87,151,164,61],\"blind_sign_req\":[146,176,228,40,252,11,192,170,3,84,188,29,87,64,49,134,204,118,180,111,166,23,44,91,115,7,5,41,143,230,192,139,183,60,135,118,68,33,28,56,245,7,61,128,246,183,224,155,149,180,159,139,44,6,86,185,32,243,35,50,208,167,105,51,10,199,17,25,202,242,82,185,180,93,181,71,9,21,198,3,53,27,228,247,70,247,15,231,35,239,171,168,12,39,9,103,2,0,0,0,0,0,0,0,149,10,49,53,253,27,20,83,77,16,209,183,26,202,173,245,48,2,98,214,50,111,205,61,143,205,180,12,60,80,204,8,225,176,250,242,51,147,6,172,49,20,234,128,58,157,77,9,173,73,60,198,152,161,17,123,43,108,134,157,145,182,38,23,7,244,179,16,244,222,206,4,241,95,210,165,18,216,116,249,134,68,231,240,47,49,30,18,236,98,51,218,81,213,208,120,166,39,74,65,151,62,195,247,123,40,224,232,120,182,234,214,63,185,102,225,19,65,65,28,233,91,168,201,249,193,43,59,2,192,145,29,106,92,124,57,210,121,76,30,81,115,226,84,155,68,97,46,82,34,160,51,76,110,123,248,55,124,137,102,2,0,0,0,0,0,0,0,237,8,254,97,90,184,163,156,221,82,133,94,177,40,166,132,230,14,241,234,176,194,121,0,157,148,70,12,157,15,160,28,212,94,219,169,220,163,101,233,97,95,106,244,208,115,209,2,3,150,129,201,126,49,98,125,26,68,122,103,142,111,23,21,2,0,0,0,0,0,0,0,93,65,229,251,193,25,208,246,197,112,140,170,138,227,58,92,23,231,190,23,95,49,20,247,18,65,168,78,40,181,74,69,7,14,136,243,70,224,141,79,202,202,127,255,64,28,8,79,69,17,47,130,129,224,103,10,50,89,97,99,93,242,119,14]},\"signature\":\"ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZLZR9j\"}"},{}] \ No newline at end of file diff --git a/clients/credential/src/client.rs b/clients/credential/src/client.rs index 40f035c6b9..dae2d9e91f 100644 --- a/clients/credential/src/client.rs +++ b/clients/credential/src/client.rs @@ -1,60 +1,49 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::error::Result; -use crate::{CONTRACT_ADDRESS, MNEMONIC, NYMD_URL}; use bip39::Mnemonic; -use coconut_bandwidth_contract_common::deposit::DepositData; -use coconut_bandwidth_contract_common::msg::ExecuteMsg; -use network_defaults::DEFAULT_NETWORK; use std::str::FromStr; use url::Url; -use validator_client::nymd::{AccountId, Coin, Denom, NymdClient, SigningNymdClient}; + +use crate::error::Result; +use crate::{MNEMONIC, NYMD_URL}; + +use network_defaults::{DEFAULT_NETWORK, DENOM, VOUCHER_INFO}; +use validator_client::nymd::traits::CoconutBandwidthSigningClient; +use validator_client::nymd::{Coin, Fee, NymdClient, SigningNymdClient}; pub(crate) struct Client { nymd_client: NymdClient, - denom: Denom, - contract_address: AccountId, } impl Client { pub fn new() -> Self { let nymd_url = Url::from_str(NYMD_URL).unwrap(); let mnemonic = Mnemonic::from_str(MNEMONIC).unwrap(); - let nymd_client = NymdClient::connect_with_mnemonic( - DEFAULT_NETWORK, - nymd_url.as_ref(), - None, - None, - None, - mnemonic, - None, - ) - .unwrap(); - let denom = Denom::from_str(network_defaults::DENOM).unwrap(); - let contract_address = AccountId::from_str(CONTRACT_ADDRESS).unwrap(); + let nymd_client = + NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None) + .unwrap(); - Client { - nymd_client, - denom, - contract_address, - } + Client { nymd_client } } pub async fn deposit( &self, amount: u64, - info: &str, verification_key: String, encryption_key: String, + fee: Option, ) -> Result { - let req = ExecuteMsg::DepositFunds { - data: DepositData::new(info.to_string(), verification_key, encryption_key), - }; - let funds = vec![Coin::new(amount as u128, self.denom.to_string())]; + let amount = Coin::new(amount as u128, DENOM.to_string()); Ok(self .nymd_client - .execute(&self.contract_address, &req, Default::default(), "", funds) + .deposit( + amount, + String::from(VOUCHER_INFO), + verification_key, + encryption_key, + fee, + ) .await? .transaction_hash .to_string()) diff --git a/clients/credential/src/commands.rs b/clients/credential/src/commands.rs index 62ab440fd2..f95c476120 100644 --- a/clients/credential/src/commands.rs +++ b/clients/credential/src/commands.rs @@ -55,9 +55,9 @@ impl Execute for Deposit { let tx_hash = client .deposit( self.amount, - VOUCHER_INFO, signing_keypair.public_key.clone(), encryption_keypair.public_key.clone(), + None, ) .await?; diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 0435c6cab4..4ead7b4597 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -83,7 +83,7 @@ impl GatewayClient { ) -> Self { GatewayClient { authenticated: false, - disabled_credentials_mode: true, + disabled_credentials_mode: false, bandwidth_remaining: 0, gateway_address, gateway_identity, @@ -100,8 +100,8 @@ impl GatewayClient { } } - pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) { - self.disabled_credentials_mode = disabled_credentials_mode + pub fn set_disabled_credentials_mode(&mut self, _disabled_credentials_mode: bool) { + self.disabled_credentials_mode = false; } // TODO: later convert into proper builder methods @@ -496,7 +496,6 @@ impl GatewayClient { self.shared_key.as_ref().unwrap(), iv, ) - .ok_or(GatewayClientError::SerializeCredential)? .into(); self.bandwidth_remaining = match self.send_websocket_message(msg).await? { ServerResponse::Bandwidth { available_total } => Ok(available_total), diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index 3ea58f4212..560dcb4788 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -10,8 +10,11 @@ rust-version = "1.56" [dependencies] base64 = "0.13" colored = "2.0" +cw3 = "0.13.1" mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" } vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" } +coconut-bandwidth-contract-common = { path= "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" } +multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" } vesting-contract = { path = "../../../contracts/vesting" } serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index f3f4842013..b28f4e33aa 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -2,7 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{validator_api, ValidatorClientError}; -use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use coconut_interface::{ + BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody, + ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + VerifyCredentialBody, VerifyCredentialResponse, +}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use url::Url; @@ -29,8 +33,6 @@ use mixnet_contract_common::{ }; #[cfg(feature = "nymd-client")] use std::collections::{HashMap, HashSet}; -#[cfg(feature = "nymd-client")] -use std::str::FromStr; #[cfg(feature = "nymd-client")] #[must_use] @@ -39,9 +41,9 @@ pub struct Config { network: network_defaults::all::Network, api_url: Url, nymd_url: Url, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: cosmrs::AccountId, + vesting_contract_address: cosmrs::AccountId, + bandwidth_claim_contract_address: cosmrs::AccountId, mixnode_page_limit: Option, gateway_page_limit: Option, @@ -51,20 +53,22 @@ pub struct Config { #[cfg(feature = "nymd-client")] impl Config { - pub fn new( - network: network_defaults::all::Network, - nymd_url: Url, - api_url: Url, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, - ) -> Self { + pub fn new(network: network_defaults::all::Network, nymd_url: Url, api_url: Url) -> Self { Config { network, nymd_url, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), api_url, mixnode_page_limit: None, gateway_page_limit: None, @@ -73,6 +77,21 @@ impl Config { } } + pub fn with_mixnode_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.mixnet_contract_address = address; + self + } + + pub fn with_vesting_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.vesting_contract_address = address; + self + } + + pub fn with_bandwidth_claim_contract_address(mut self, address: cosmrs::AccountId) -> Self { + self.bandwidth_claim_contract_address = address; + self + } + pub fn with_mixnode_page_limit(mut self, limit: Option) -> Config { self.mixnode_page_limit = limit; self @@ -97,9 +116,9 @@ impl Config { #[cfg(feature = "nymd-client")] pub struct Client { pub network: network_defaults::all::Network, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: cosmrs::AccountId, + vesting_contract_address: cosmrs::AccountId, + bandwidth_claim_contract_address: cosmrs::AccountId, mnemonic: Option, mixnode_page_limit: Option, @@ -122,18 +141,18 @@ impl Client { let nymd_client = NymdClient::connect_with_mnemonic( config.network, config.nymd_url.as_str(), - config.mixnet_contract_address.clone(), - config.vesting_contract_address.clone(), - config.erc20_bridge_contract_address.clone(), mnemonic.clone(), None, - )?; + )? + .with_mixnet_contract_address(config.mixnet_contract_address.clone()) + .with_vesting_contract_address(config.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(config.bandwidth_claim_contract_address.clone()); Ok(Client { network: config.network, mixnet_contract_address: config.mixnet_contract_address, vesting_contract_address: config.vesting_contract_address, - erc20_bridge_contract_address: config.erc20_bridge_contract_address, + bandwidth_claim_contract_address: config.bandwidth_claim_contract_address, mnemonic: Some(mnemonic), mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, @@ -148,12 +167,12 @@ impl Client { self.nymd = NymdClient::connect_with_mnemonic( self.network, new_endpoint.as_ref(), - self.mixnet_contract_address.clone(), - self.vesting_contract_address.clone(), - self.erc20_bridge_contract_address.clone(), self.mnemonic.clone().unwrap(), None, - )?; + )? + .with_mixnet_contract_address(self.mixnet_contract_address.clone()) + .with_vesting_contract_address(self.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone()); Ok(()) } @@ -166,32 +185,15 @@ impl Client { impl Client { pub fn new_query(config: Config) -> Result, ValidatorClientError> { let validator_api_client = validator_api::Client::new(config.api_url.clone()); - let nymd_client = NymdClient::connect( - config.nymd_url.as_str(), - Some(config.mixnet_contract_address.clone().unwrap_or_else(|| { - cosmrs::AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).unwrap() - })), - Some(config.vesting_contract_address.clone().unwrap_or_else(|| { - cosmrs::AccountId::from_str(DEFAULT_NETWORK.vesting_contract_address()).unwrap() - })), - Some( - config - .erc20_bridge_contract_address - .clone() - .unwrap_or_else(|| { - cosmrs::AccountId::from_str( - DEFAULT_NETWORK.bandwidth_claim_contract_address(), - ) - .unwrap() - }), - ), - )?; + let nymd_client = NymdClient::connect(config.nymd_url.as_str())? + .with_mixnet_contract_address(config.mixnet_contract_address.clone()) + .with_vesting_contract_address(config.vesting_contract_address.clone()); Ok(Client { network: config.network, mixnet_contract_address: config.mixnet_contract_address, vesting_contract_address: config.vesting_contract_address, - erc20_bridge_contract_address: config.erc20_bridge_contract_address, + bandwidth_claim_contract_address: config.bandwidth_claim_contract_address, mnemonic: None, mixnode_page_limit: config.mixnode_page_limit, gateway_page_limit: config.gateway_page_limit, @@ -203,12 +205,10 @@ impl Client { } pub fn change_nymd(&mut self, new_endpoint: Url) -> Result<(), ValidatorClientError> { - self.nymd = NymdClient::connect( - new_endpoint.as_ref(), - self.mixnet_contract_address.clone(), - self.vesting_contract_address.clone(), - self.erc20_bridge_contract_address.clone(), - )?; + self.nymd = NymdClient::connect(new_endpoint.as_ref())? + .with_mixnet_contract_address(self.mixnet_contract_address.clone()) + .with_vesting_contract_address(self.vesting_contract_address.clone()) + .with_bandwidth_claim_contract_address(self.bandwidth_claim_contract_address.clone()); Ok(()) } } @@ -222,10 +222,10 @@ impl Client { // use case: somebody initialised client without a contract in order to upload and initialise one // and now they want to actually use it without making new client pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) { - self.mixnet_contract_address = Some(mixnet_contract_address) + self.mixnet_contract_address = mixnet_contract_address } - pub fn get_mixnet_contract_address(&self) -> Option { + pub fn get_mixnet_contract_address(&self) -> cosmrs::AccountId { self.mixnet_contract_address.clone() } @@ -733,4 +733,34 @@ impl ApiClient { ) -> Result { Ok(self.validator_api.get_coconut_verification_key().await?) } + + pub async fn verify_bandwidth_credential( + &self, + request_body: &VerifyCredentialBody, + ) -> Result { + Ok(self + .validator_api + .verify_bandwidth_credential(request_body) + .await?) + } + + pub async fn propose_release_funds( + &self, + request_body: &ProposeReleaseFundsRequestBody, + ) -> Result { + Ok(self + .validator_api + .propose_release_funds(request_body) + .await?) + } + + pub async fn execute_release_funds( + &self, + request_body: &ExecuteReleaseFundsRequestBody, + ) -> Result<(), ValidatorClientError> { + Ok(self + .validator_api + .execute_release_funds(request_body) + .await?) + } } diff --git a/common/client-libs/validator-client/src/connection_tester.rs b/common/client-libs/validator-client/src/connection_tester.rs index 1a8154cd80..9e41f91989 100644 --- a/common/client-libs/validator-client/src/connection_tester.rs +++ b/common/client-libs/validator-client/src/connection_tester.rs @@ -19,7 +19,7 @@ const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2; pub async fn run_validator_connection_test( nymd_urls: impl Iterator, api_urls: impl Iterator, - mixnet_contract_address: HashMap, H>, + mixnet_contract_address: HashMap, ) -> ( HashMap>, HashMap>, @@ -47,14 +47,15 @@ pub async fn run_validator_connection_test( fn setup_connection_tests( nymd_urls: impl Iterator, api_urls: impl Iterator, - mixnet_contract_address: HashMap, H>, + mixnet_contract_address: HashMap, ) -> impl Iterator { let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| { let address = mixnet_contract_address .get(&network) .expect("No configured contract address") .clone(); - NymdClient::::connect(url.as_str(), address, None, None) + NymdClient::::connect(url.as_str()) + .map(|client| client.with_mixnet_contract_address(address)) .map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client))) .ok() }); diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 961deffadc..5bf493fbc9 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -22,7 +22,7 @@ pub struct Log { /// Searches in logs for the first event of the given event type and in that event /// for the first attribute with the given attribute key. -pub(crate) fn find_attribute<'a>( +pub fn find_attribute<'a>( logs: &'a [Log], event_type: &str, attribute_key: &str, diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nymd/error.rs index ea3620ec35..8ccd41c09e 100644 --- a/common/client-libs/validator-client/src/nymd/error.rs +++ b/common/client-libs/validator-client/src/nymd/error.rs @@ -124,6 +124,12 @@ pub enum NymdError { #[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())] BroadcastTimeout { hash: tx::Hash, timeout: Duration }, + + #[error("Cosmwasm std error: {0}")] + CosmwasmStdError(#[from] cosmwasm_std::StdError), + + #[error("Coconut interface error: {0}")] + CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), } impl NymdError { diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 19d17f390a..1851ce155d 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -23,6 +23,7 @@ use mixnet_contract_common::{ PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, PagedRewardedSetResponse, QueryMsg, RewardedSetUpdateDetails, }; +use network_defaults::DEFAULT_NETWORK; use serde::Serialize; use std::convert::TryInto; @@ -58,30 +59,64 @@ pub mod wallet; #[derive(Debug)] pub struct NymdClient { client: C, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, + mixnet_contract_address: AccountId, + vesting_contract_address: AccountId, + bandwidth_claim_contract_address: AccountId, + coconut_bandwidth_contract_address: AccountId, + multisig_contract_address: AccountId, client_address: Option>, simulated_gas_multiplier: f32, } +impl NymdClient { + pub fn with_mixnet_contract_address(mut self, address: AccountId) -> Self { + self.mixnet_contract_address = address; + self + } + pub fn with_vesting_contract_address(mut self, address: AccountId) -> Self { + self.vesting_contract_address = address; + self + } + pub fn with_bandwidth_claim_contract_address(mut self, address: AccountId) -> Self { + self.bandwidth_claim_contract_address = address; + self + } + pub fn with_coconut_bandwidth_contract_address(mut self, address: AccountId) -> Self { + self.coconut_bandwidth_contract_address = address; + self + } + pub fn with_multisig_contract_address(mut self, address: AccountId) -> Self { + self.multisig_contract_address = address; + self + } +} + impl NymdClient { - pub fn connect( - endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, - ) -> Result, NymdError> + pub fn connect(endpoint: U) -> Result, NymdError> where U: TryInto, { Ok(NymdClient { client: QueryNymdClient::new(endpoint)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: None, simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } } @@ -91,9 +126,6 @@ impl NymdClient { pub fn connect_with_signer( network: config::defaults::all::Network, endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, signer: DirectSecp256k1HdWallet, gas_price: Option, ) -> Result, NymdError> @@ -110,20 +142,31 @@ impl NymdClient { Ok(NymdClient { client: SigningNymdClient::connect_with_signer(endpoint, signer, gas_price)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } pub fn connect_with_mnemonic( network: config::defaults::all::Network, endpoint: U, - mixnet_contract_address: Option, - vesting_contract_address: Option, - erc20_bridge_contract_address: Option, mnemonic: bip39::Mnemonic, gas_price: Option, ) -> Result, NymdError> @@ -142,32 +185,48 @@ impl NymdClient { Ok(NymdClient { client: SigningNymdClient::connect_with_signer(endpoint, wallet, gas_price)?, - mixnet_contract_address, - vesting_contract_address, - erc20_bridge_contract_address, client_address: Some(client_address), simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER, + mixnet_contract_address: DEFAULT_NETWORK + .mixnet_contract_address() + .parse() + .expect("Error parsing mixnet contract address"), + vesting_contract_address: DEFAULT_NETWORK + .vesting_contract_address() + .parse() + .expect("Error parsing vesting contract address"), + bandwidth_claim_contract_address: DEFAULT_NETWORK + .bandwidth_claim_contract_address() + .parse() + .expect("Error parsing bandwidth claim contract address"), + coconut_bandwidth_contract_address: DEFAULT_NETWORK + .coconut_bandwidth_contract_address() + .parse() + .unwrap(), + multisig_contract_address: DEFAULT_NETWORK.multisig_contract_address().parse().unwrap(), }) } } impl NymdClient { - pub fn mixnet_contract_address(&self) -> Result<&AccountId, NymdError> { - self.mixnet_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn mixnet_contract_address(&self) -> &AccountId { + &self.mixnet_contract_address } - pub fn vesting_contract_address(&self) -> Result<&AccountId, NymdError> { - self.vesting_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn vesting_contract_address(&self) -> &AccountId { + &self.vesting_contract_address } - pub fn erc20_bridge_contract_address(&self) -> Result<&AccountId, NymdError> { - self.erc20_bridge_contract_address - .as_ref() - .ok_or(NymdError::NoContractAddressAvailable) + pub fn bandwidth_claim_contract_address(&self) -> &AccountId { + &self.bandwidth_claim_contract_address + } + + pub fn coconut_bandwidth_contract_address(&self) -> &AccountId { + &self.coconut_bandwidth_contract_address + } + + pub fn multisig_contract_address(&self) -> &AccountId { + &self.multisig_contract_address } pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) { @@ -295,7 +354,7 @@ impl NymdClient { { let request = QueryMsg::StateParams {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -305,7 +364,7 @@ impl NymdClient { { let request = QueryMsg::QueryOperatorReward { address }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -324,7 +383,7 @@ impl NymdClient { proxy, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -341,7 +400,7 @@ impl NymdClient { proxy_address, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -351,7 +410,7 @@ impl NymdClient { { let request = QueryMsg::GetCurrentEpoch {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -361,7 +420,7 @@ impl NymdClient { { let request = QueryMsg::GetContractVersion {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -378,7 +437,7 @@ impl NymdClient { interval_id, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -388,7 +447,7 @@ impl NymdClient { { let request = QueryMsg::GetCurrentRewardedSetHeight {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -400,7 +459,7 @@ impl NymdClient { { let request = QueryMsg::GetRewardedSetUpdateDetails {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -420,7 +479,7 @@ impl NymdClient { }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -430,7 +489,7 @@ impl NymdClient { { let request = QueryMsg::LayerDistribution {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -440,7 +499,7 @@ impl NymdClient { { let request = QueryMsg::GetRewardPool {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -450,7 +509,7 @@ impl NymdClient { { let request = QueryMsg::GetCirculatingSupply {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -460,7 +519,7 @@ impl NymdClient { { let request = QueryMsg::GetSybilResistancePercent {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -470,7 +529,7 @@ impl NymdClient { { let request = QueryMsg::GetActiveSetWorkFactor {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -480,7 +539,7 @@ impl NymdClient { { let request = QueryMsg::GetIntervalRewardPercent {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -490,7 +549,7 @@ impl NymdClient { { let request = QueryMsg::GetEpochsInInterval {}; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -504,7 +563,7 @@ impl NymdClient { }; let response: MixOwnershipResponse = self .client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await?; Ok(response.mixnode) } @@ -519,7 +578,7 @@ impl NymdClient { }; let response: GatewayOwnershipResponse = self .client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await?; Ok(response.gateway) } @@ -537,7 +596,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -554,7 +613,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -574,7 +633,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -594,7 +653,7 @@ impl NymdClient { limit: page_limit, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -614,7 +673,7 @@ impl NymdClient { proxy, }; self.client - .query_contract_smart(self.mixnet_contract_address()?, &request) + .query_contract_smart(self.mixnet_contract_address(), &request) .await } @@ -801,7 +860,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::CompoundOperatorReward", @@ -819,7 +878,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::ClaimOperatorReward", @@ -841,7 +900,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::CompoundDelegatorReward", @@ -863,7 +922,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "MixnetContract::ClaimDelegatorReward", @@ -892,7 +951,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding mixnode from rust!", @@ -923,7 +982,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding mixnode on behalf from rust!", @@ -960,7 +1019,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Bonding multiple mixnodes on behalf from rust!", @@ -979,7 +1038,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding mixnode from rust!", @@ -1003,7 +1062,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding mixnode on behalf from rust!", @@ -1029,7 +1088,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Updating mixnode configuration from rust!", @@ -1056,7 +1115,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Delegating to mixnode from rust!", @@ -1086,7 +1145,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Delegating to mixnode on behalf from rust!", @@ -1122,7 +1181,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Delegating to multiple mixnodes on behalf from rust!", @@ -1147,7 +1206,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Removing mixnode delegation from rust!", @@ -1175,7 +1234,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Removing mixnode delegation on behalf from rust!", @@ -1204,7 +1263,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding gateway from rust!", @@ -1235,7 +1294,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Bonding gateway on behalf from rust!", @@ -1272,7 +1331,7 @@ impl NymdClient { self.client .execute_multiple( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), reqs, fee, "Bonding multiple gateways on behalf from rust!", @@ -1291,7 +1350,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding gateway from rust!", @@ -1316,7 +1375,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Unbonding gateway on behalf from rust!", @@ -1339,7 +1398,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Updating contract state from rust!", @@ -1358,7 +1417,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Advance current epoch", @@ -1377,7 +1436,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Reconciling delegation events", @@ -1396,7 +1455,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Snapshotting mixnodes", @@ -1423,7 +1482,7 @@ impl NymdClient { self.client .execute( self.address(), - self.mixnet_contract_address()?, + self.mixnet_contract_address(), &req, fee, "Writing rewarded set", diff --git a/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs new file mode 100644 index 0000000000..352ee6df26 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/coconut_bandwidth_signing_client.rs @@ -0,0 +1,49 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nymd::cosmwasm_client::types::ExecuteResult; +use crate::nymd::error::NymdError; +use crate::nymd::{Coin, Fee, NymdClient}; +use coconut_bandwidth_contract_common::{deposit::DepositData, msg::ExecuteMsg}; + +use async_trait::async_trait; + +#[async_trait] +pub trait CoconutBandwidthSigningClient { + async fn deposit( + &self, + amount: Coin, + info: String, + verification_key: String, + encryption_key: String, + fee: Option, + ) -> Result; +} + +#[async_trait] +impl CoconutBandwidthSigningClient for NymdClient { + async fn deposit( + &self, + amount: Coin, + info: String, + verification_key: String, + encryption_key: String, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::DepositFunds { + data: DepositData::new(info.to_string(), verification_key, encryption_key), + }; + self.client + .execute( + self.address(), + self.coconut_bandwidth_contract_address(), + &req, + fee, + "CoconutBandwidth::Deposit", + vec![amount], + ) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/mod.rs b/common/client-libs/validator-client/src/nymd/traits/mod.rs index 20834c9f86..9198522d2d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mod.rs +++ b/common/client-libs/validator-client/src/nymd/traits/mod.rs @@ -1,8 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +mod coconut_bandwidth_signing_client; +mod multisig_query_client; +mod multisig_signing_client; mod vesting_query_client; mod vesting_signing_client; +pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient; +pub use multisig_query_client::QueryClient; +pub use multisig_signing_client::MultisigSigningClient; pub use vesting_query_client::VestingQueryClient; pub use vesting_signing_client::VestingSigningClient; diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs new file mode 100644 index 0000000000..5869ada26a --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_query_client.rs @@ -0,0 +1,24 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nymd::error::NymdError; +use crate::nymd::{CosmWasmClient, NymdClient}; + +use multisig_contract_common::msg::{ProposalResponse, QueryMsg}; + +use async_trait::async_trait; + +#[async_trait] +pub trait QueryClient { + async fn get_proposal(&self, proposal_id: u64) -> Result; +} + +#[async_trait] +impl QueryClient for NymdClient { + async fn get_proposal(&self, proposal_id: u64) -> Result { + let request = QueryMsg::Proposal { proposal_id }; + self.client + .query_contract_smart(self.multisig_contract_address(), &request) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs new file mode 100644 index 0000000000..0d9fca57e3 --- /dev/null +++ b/common/client-libs/validator-client/src/nymd/traits/multisig_signing_client.rs @@ -0,0 +1,116 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; +use crate::nymd::cosmwasm_client::types::ExecuteResult; +use crate::nymd::error::NymdError; +use crate::nymd::{Fee, NymdClient}; + +use coconut_bandwidth_contract_common::msg::ExecuteMsg as CoconutBandwidthExecuteMsg; +use multisig_contract_common::msg::ExecuteMsg; + +use async_trait::async_trait; +use cosmwasm_std::{to_binary, Coin, CosmosMsg, WasmMsg}; +use cw3::Vote; +use network_defaults::DEFAULT_NETWORK; + +#[async_trait] +pub trait MultisigSigningClient { + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result; + + async fn vote_proposal( + &self, + proposal_id: u64, + yes: bool, + fee: Option, + ) -> Result; + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result; +} + +#[async_trait] +impl MultisigSigningClient for NymdClient { + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let release_funds_req = CoconutBandwidthExecuteMsg::ReleaseFunds { + funds: Coin::new(voucher_value, DEFAULT_NETWORK.denom()), + }; + let release_funds_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.coconut_bandwidth_contract_address().to_string(), + msg: to_binary(&release_funds_req)?, + funds: vec![], + }); + let req = ExecuteMsg::Propose { + title, + description: blinded_serial_number, + msgs: vec![release_funds_msg], + latest: None, + }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Propose::Execute::ReleaseFunds", + vec![], + ) + .await + } + + async fn vote_proposal( + &self, + proposal_id: u64, + vote_yes: bool, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let vote = if vote_yes { Vote::Yes } else { Vote::No }; + let req = ExecuteMsg::Vote { proposal_id, vote }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Vote", + vec![], + ) + .await + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result { + let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier))); + let req = ExecuteMsg::Execute { proposal_id }; + self.client + .execute( + self.address(), + self.multisig_contract_address(), + &req, + fee, + "Multisig::Execute", + vec![], + ) + .await + } +} diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs index a020dea2ea..ea20cb6654 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_query_client.rs @@ -84,7 +84,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -99,7 +99,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -113,7 +113,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -127,7 +127,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -140,7 +140,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -152,7 +152,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -164,7 +164,7 @@ impl VestingQueryClient for NymdClient { vesting_account_address: vesting_account_address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -178,7 +178,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -193,7 +193,7 @@ impl VestingQueryClient for NymdClient { block_time, }; self.client - .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request) + .query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address(), &request) .await .map(Into::into) } @@ -203,7 +203,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } async fn get_mixnode_pledge(&self, address: &str) -> Result, NymdError> { @@ -211,7 +211,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } async fn get_gateway_pledge(&self, address: &str) -> Result, NymdError> { @@ -219,7 +219,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } @@ -228,7 +228,7 @@ impl VestingQueryClient for NymdClient { address: address.to_string(), }; self.client - .query_contract_smart(self.vesting_contract_address()?, &request) + .query_contract_smart(self.vesting_contract_address(), &request) .await } } diff --git a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs index 163b6957db..e24473e81d 100644 --- a/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/vesting_signing_client.rs @@ -129,7 +129,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UpdateMixnetConfig", @@ -150,7 +150,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UpdateMixnetAddress", @@ -175,7 +175,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::BondGateway", @@ -190,7 +190,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UnbondGateway", @@ -213,7 +213,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUnbondGateway", @@ -238,7 +238,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::BondMixnode", @@ -253,7 +253,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UnbondMixnode", @@ -276,7 +276,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUnbondMixnode", @@ -296,7 +296,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::WithdrawVested", @@ -320,7 +320,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::TrackUndelegation", @@ -342,7 +342,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::DelegateToMixnode", @@ -363,7 +363,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::UndelegateFromMixnode", @@ -389,7 +389,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CreatePeriodicVestingAccount", @@ -407,7 +407,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::ClaimOperatorReward", @@ -425,7 +425,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CompoundOperatorReward", @@ -444,7 +444,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::ClaimDelegatorReward", @@ -463,7 +463,7 @@ impl VestingSigningClient for NymdClient self.client .execute( self.address(), - self.vesting_contract_address()?, + self.vesting_contract_address(), &req, fee, "VestingContract::CompoundDelegatorReward", diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 66c86f55b6..be0ad92727 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -3,7 +3,11 @@ use crate::validator_api::error::ValidatorAPIError; use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; -use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; +use coconut_interface::{ + BlindSignRequestBody, BlindedSignatureResponse, ExecuteReleaseFundsRequestBody, + ProposeReleaseFundsRequestBody, ProposeReleaseFundsResponse, VerificationKeyResponse, + VerifyCredentialBody, VerifyCredentialResponse, +}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -371,6 +375,57 @@ impl Client { ) .await } + + pub async fn verify_bandwidth_credential( + &self, + request_body: &VerifyCredentialBody, + ) -> Result { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, + ], + NO_PARAMS, + request_body, + ) + .await + } + + pub async fn propose_release_funds( + &self, + request_body: &ProposeReleaseFundsRequestBody, + ) -> Result { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_PROPOSE_RELEASE_FUNDS, + ], + NO_PARAMS, + request_body, + ) + .await + } + + pub async fn execute_release_funds( + &self, + request_body: &ExecuteReleaseFundsRequestBody, + ) -> Result<(), ValidatorAPIError> { + self.post_validator_api( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_EXECUTE_RELEASE_FUNDS, + ], + NO_PARAMS, + request_body, + ) + .await + } } // utility function that should solve the double slash problem in validator API forever. diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 61c4e309e8..b6939d18ab 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -17,6 +17,9 @@ pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL: &str = "partial-bandwidth-credential"; pub const COCONUT_VERIFICATION_KEY: &str = "verification-key"; +pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; +pub const COCONUT_PROPOSE_RELEASE_FUNDS: &str = "propose-release-funds"; +pub const COCONUT_EXECUTE_RELEASE_FUNDS: &str = "execute-release-funds"; pub const STATUS_ROUTES: &str = "status"; pub const MIXNODE: &str = "mixnode"; diff --git a/common/coconut-interface/src/error.rs b/common/coconut-interface/src/error.rs index 95ba31db88..51b047d0d5 100644 --- a/common/coconut-interface/src/error.rs +++ b/common/coconut-interface/src/error.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use nymcoconut::CoconutError; use thiserror::Error; #[derive(Debug, Error)] @@ -11,9 +12,6 @@ pub enum CoconutInterfaceError { #[error("Could not decode base 58 string - {0}")] MalformedString(#[from] bs58::decode::Error), - #[error("Not enough public attributes were specified")] - NotEnoughPublicAttributes, - - #[error("Could not recover bandwidth value")] - InvalidBandwidth, + #[error("Coconut error - {0}")] + CoconutError(#[from] CoconutError), } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 1e8d726f48..8cb345dbf5 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -5,96 +5,158 @@ pub mod error; use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use std::str::FromStr; use error::CoconutInterfaceError; pub use nymcoconut::*; -#[derive(Serialize, Deserialize, Getters, CopyGetters, Clone)] +#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters, Clone, PartialEq)] pub struct Credential { #[getset(get = "pub")] n_params: u32, #[getset(get = "pub")] theta: Theta, - public_attributes: Vec>, - #[getset(get = "pub")] - signature: Signature, + voucher_value: u64, + voucher_info: String, } impl Credential { pub fn new( n_params: u32, theta: Theta, - voucher_value: String, + voucher_value: u64, voucher_info: String, - signature: &Signature, ) -> Credential { - let public_attributes = vec![voucher_value.into_bytes(), voucher_info.into_bytes()]; Credential { n_params, theta, - public_attributes, - signature: *signature, + voucher_value, + voucher_info, } } - pub fn voucher_value(&self) -> Result { - let bandwidth_vec = self - .public_attributes - .get(0) - .ok_or(CoconutInterfaceError::NotEnoughPublicAttributes)? - .to_owned(); - let bandwidth_str = String::from_utf8(bandwidth_vec) - .map_err(|_| CoconutInterfaceError::InvalidBandwidth)?; - let value = - u64::from_str(&bandwidth_str).map_err(|_| CoconutInterfaceError::InvalidBandwidth)?; + pub fn blinded_serial_number(&self) -> String { + self.theta.blinded_serial_number_bs58() + } - Ok(value) + pub fn has_blinded_serial_number( + &self, + blinded_serial_number_bs58: &str, + ) -> Result { + Ok(self + .theta + .has_blinded_serial_number(blinded_serial_number_bs58)?) + } + + pub fn voucher_value(&self) -> u64 { + self.voucher_value } pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - let public_attributes = self - .public_attributes - .iter() - .map(hash_to_scalar) - .collect::>(); + let public_attributes = vec![ + self.voucher_value.to_string().as_bytes(), + self.voucher_info.as_bytes(), + ] + .iter() + .map(hash_to_scalar) + .collect::>(); nymcoconut::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) } + + pub fn as_bytes(&self) -> Vec { + let n_params_bytes = self.n_params.to_be_bytes(); + let theta_bytes = self.theta.to_bytes(); + let theta_bytes_len = theta_bytes.len(); + let voucher_value_bytes = self.voucher_value.to_be_bytes(); + let voucher_info_bytes = self.voucher_info.as_bytes(); + let voucher_info_len = voucher_info_bytes.len(); + + let mut bytes = Vec::with_capacity(28 + theta_bytes_len + voucher_info_len); + bytes.extend_from_slice(&n_params_bytes); + bytes.extend_from_slice(&(theta_bytes_len as u64).to_be_bytes()); + bytes.extend_from_slice(&theta_bytes); + bytes.extend_from_slice(&voucher_value_bytes); + bytes.extend_from_slice(voucher_info_bytes); + + bytes + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() < 28 { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let mut four_byte = [0u8; 4]; + let mut eight_byte = [0u8; 8]; + + four_byte.copy_from_slice(&bytes[..4]); + let n_params = u32::from_be_bytes(four_byte); + eight_byte.copy_from_slice(&bytes[4..12]); + let theta_len = u64::from_be_bytes(eight_byte); + if bytes.len() < 28 + theta_len as usize { + return Err(CoconutError::Deserialization(String::from( + "To few bytes in credential", + ))); + } + let theta = Theta::from_bytes(&bytes[12..12 + theta_len as usize]) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + eight_byte.copy_from_slice(&bytes[12 + theta_len as usize..20 + theta_len as usize]); + let voucher_value = u64::from_be_bytes(eight_byte); + let voucher_info = String::from_utf8(bytes[20 + theta_len as usize..].to_vec()) + .map_err(|e| CoconutError::Deserialization(e.to_string()))?; + + Ok(Credential { + n_params, + theta, + voucher_value, + voucher_info, + }) + } } -#[derive(Serialize, Deserialize, Debug, Getters, CopyGetters)] +impl Bytable for Credential { + fn to_byte_vec(&self) -> Vec { + self.as_bytes() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + Credential::from_bytes(slice) + } +} + +impl Base58 for Credential {} + +#[derive(Serialize, Deserialize, Getters, CopyGetters)] pub struct VerifyCredentialBody { #[getset(get = "pub")] - n_params: u32, + credential: Credential, #[getset(get = "pub")] - theta: Theta, - public_attributes: Vec, + proposal_id: u64, } impl VerifyCredentialBody { - pub fn new( - n_params: u32, - theta: &Theta, - public_attributes: &[Attribute], - ) -> VerifyCredentialBody { + pub fn new(credential: Credential, proposal_id: u64) -> VerifyCredentialBody { VerifyCredentialBody { - n_params, - theta: theta.clone(), - public_attributes: public_attributes - .iter() - .map(|attr| attr.to_bs58()) - .collect(), + credential, + proposal_id, } } +} - pub fn public_attributes(&self) -> Vec { - self.public_attributes - .iter() - .map(|x| Attribute::try_from_bs58(x).unwrap()) - .collect() +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifyCredentialResponse { + pub verification_result: bool, +} + +impl VerifyCredentialResponse { + pub fn new(verification_result: bool) -> Self { + VerifyCredentialResponse { + verification_result, + } } } + // All strings are base58 encoded representations of structs #[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)] pub struct BlindSignRequestBody { @@ -194,3 +256,86 @@ impl VerificationKeyResponse { VerificationKeyResponse { key } } } + +#[derive(Serialize, Deserialize, Getters, CopyGetters)] +pub struct ProposeReleaseFundsRequestBody { + #[getset(get = "pub")] + credential: Credential, +} + +impl ProposeReleaseFundsRequestBody { + pub fn new(credential: Credential) -> Self { + ProposeReleaseFundsRequestBody { credential } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ProposeReleaseFundsResponse { + pub proposal_id: u64, +} + +impl ProposeReleaseFundsResponse { + pub fn new(proposal_id: u64) -> Self { + ProposeReleaseFundsResponse { proposal_id } + } +} + +#[derive(Debug, Serialize, Deserialize, Getters, CopyGetters)] +pub struct ExecuteReleaseFundsRequestBody { + #[getset(get = "pub")] + proposal_id: u64, +} + +impl ExecuteReleaseFundsRequestBody { + pub fn new(proposal_id: u64) -> Self { + ExecuteReleaseFundsRequestBody { proposal_id } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serde_coconut_credential() { + let voucher_value = 1000000u64; + let voucher_info = String::from("BandwidthVoucher"); + let serial_number = + Attribute::try_from_bs58("7Rp3imcuNX3w9se9wm5th8gSvc2czsnMrGsdt5HsrycA").unwrap(); + let binding_number = + Attribute::try_from_bs58("Auf8yVEgyEAWNHaXUZmimS4n9g5YiYnNYqp6F9BtBe9E").unwrap(); + let signature = Signature::try_from_bs58( + "ta3pM9ffj5T6YGbwjSBp2W118rcwyP9PXStc\ + 7ssb91g5GQYMQHhuTNajbdZcjxUFBFL5rhED8EHpRzE8r432ss3qbPBfpNev4CdkfMkQ3wepyM7hy7q1W6Rn9WmFoZL\ + ZR9j", + ) + .unwrap(); + let params = Parameters::new(4).unwrap(); + let verification_key = VerificationKey::try_from_bs58("8CFtVVXdwLy4WHMQPE4\ + woe89q3DRHoNxBSchftrEjSBPWA4r4xZv4Y9qSvS5x5bMmFtp7BX6ikECAnuXr5EjXWSsgjirZJmpS5XDUynVfht1cD\ + FWGDvy2XFrRCuoCMotNXi3PoF6wYqdTR9Rqcfoj3i2H5Nid422WBaLtVoC9QNobvpvaqq6vX5PbsSyPayvU8HCXFxM6\ + JjScYpbRTxQtdwefWLrk3LmXyJQBWi7c2VAhSxu9msp7VTBycqdwQNgxHETStZuwXsozxaGQ2KssVUCaaoYPR4g2RqK\ + UAvtWwA7pMiAQNcbkXcbsjCgVjWaCpMWC37XA31cLcFf3zbjHD9e5tXjAcqa4M89fbFhuvvSXxowSAZ5NoWrN32kd5d\ + wxJm1JW3Tt2h6yDDBe84oMy71462dZn7N78DVk2mFNGwBCibrZWA7oUzRBMfYxiQrksoFcou7QfLLd58zoNYmPQPt84\ + 1VpQopEBfdQ7Nf9zoXxBt3zMy7g5NsFGvzh7KTbDUyeeXrdkKJPQBs6dqaizr9sS8CPPmR4uk96vDTRh8CJ5FbSsmb8\ + nP71dRvvwRZJHGzwYirMo6SXS3ZYxFuiA3mkxYuqDHCwkTWDuRCcAaztrDYRZg7VCMo4Q446AaEso5eqpeWpHZQt53E\ + ZRpqmNYKASGwMhTeEHPSLgSmtoAAUcaRWpGRzYfd6kzEma8tdGLwyP4rLXgvSvtDLP37dU7YgF3LEXbGAz57U9ATy46\ + 6sroLpHPdaCWB8RF11wvB6Tu196JnJd2KyQBP1iUWP3rtZs3GhAF1QVcxquh8BqDZzAcpQ6wCS1P9c5GxKgww77FVF5\ + Kp83XtoxSrw3GaYVyKTGxNh3vcKPR31txCjTxPaN2fg7TaPLhoQJX4YaAroFSXqrqbbRsisuHhhCeUP2YwDjHedes9y") + .unwrap(); + let theta = prove_bandwidth_credential( + ¶ms, + &verification_key, + &signature, + serial_number, + binding_number, + ) + .unwrap(); + let credential = Credential::new(4, theta, voucher_value, voucher_info); + + let serialized_credential = credential.as_bytes(); + let deserialized_credential = Credential::from_bytes(&serialized_credential).unwrap(); + + assert_eq!(credential, deserialized_credential); + } +} diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml new file mode 100644 index 0000000000..c7437e4a6d --- /dev/null +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "multisig-contract-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cw-utils = { version = "0.13.1" } +cw3 = { version = "0.13.1" } +cw4 = { version = "0.13.1" } +cosmwasm-std = "1.0.0-beta6" +schemars = "0.8" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } \ No newline at end of file diff --git a/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs new file mode 100644 index 0000000000..d0e87a0d30 --- /dev/null +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/lib.rs @@ -0,0 +1 @@ +pub mod msg; diff --git a/contracts/multisig/cw3-flex-multisig/src/msg.rs b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs similarity index 94% rename from contracts/multisig/cw3-flex-multisig/src/msg.rs rename to common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs index 8d72cd37b0..19b440fa76 100644 --- a/contracts/multisig/cw3-flex-multisig/src/msg.rs +++ b/common/cosmwasm-smart-contracts/multisig-contract/src/msg.rs @@ -1,7 +1,11 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use cosmwasm_std::{CosmosMsg, Empty}; +pub use cw3::ProposalResponse; use cw3::Vote; use cw4::MemberChangedHookMsg; use cw_utils::{Duration, Expiration, Threshold}; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index 282614238e..0ffee7abdc 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -51,12 +51,7 @@ pub async fn obtain_aggregate_verification_key( let mut shares = Vec::with_capacity(validators.len()); let mut client = validator_client::ApiClient::new(validators[0].clone()); - let response = client.get_coconut_verification_key().await?; - - indices.push(1); - shares.push(response.key); - - for (id, validator_url) in validators.iter().enumerate().skip(1) { + for (id, validator_url) in validators.iter().enumerate() { client.change_validator_api(validator_url.clone()); let response = client.get_coconut_verification_key().await?; indices.push((id + 1) as u64); @@ -135,14 +130,7 @@ pub async fn obtain_aggregate_signature( let mut validators_partial_vks: Vec = Vec::with_capacity(validators.len()); let mut client = validator_client::ApiClient::new(validators[0].clone()); - let validator_partial_vk = client.get_coconut_verification_key().await?; - validators_partial_vks.push(validator_partial_vk.key.clone()); - - let first = - obtain_partial_credential(params, attributes, &client, &validator_partial_vk.key).await?; - shares.push(SignatureShare::new(first, 1)); - - for (id, validator_url) in validators.iter().enumerate().skip(1) { + for (id, validator_url) in validators.iter().enumerate() { client.change_validator_api(validator_url.clone()); let validator_partial_vk = client.get_coconut_verification_key().await?; validators_partial_vks.push(validator_partial_vk.key.clone()); @@ -193,8 +181,7 @@ pub fn prepare_credential_for_spending( Ok(Credential::new( PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES, theta, - voucher_value.to_string(), + voucher_value, voucher_info, - signature, )) } diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 8f628ac863..86b9abc140 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(feature = "coconut")] -use coconut_interface::{error::CoconutInterfaceError, CoconutError}; +use coconut_interface::CoconutError; use crypto::asymmetric::encryption::KeyRecoveryError; use validator_client::ValidatorClientError; @@ -20,10 +20,6 @@ pub enum Error { #[error("Ran into a coconut error - {0}")] CoconutError(#[from] CoconutError), - #[cfg(feature = "coconut")] - #[error("Ran into a coconut interface error - {0}")] - CoconutInterfaceError(#[from] CoconutInterfaceError), - #[error("Ran into a validator client error - {0}")] ValidatorClientError(#[from] ValidatorClientError), diff --git a/common/network-defaults/src/all.rs b/common/network-defaults/src/all.rs index a7e4ea58d8..bdc62fe952 100644 --- a/common/network-defaults/src/all.rs +++ b/common/network-defaults/src/all.rs @@ -52,6 +52,14 @@ impl Network { self.details().bandwidth_claim_contract_address } + pub fn coconut_bandwidth_contract_address(&self) -> &str { + self.details().coconut_bandwidth_contract_address + } + + pub fn multisig_contract_address(&self) -> &str { + self.details().multisig_contract_address + } + pub fn rewarding_validator_address(&self) -> &str { self.details().rewarding_validator_address } diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index a992d5bed6..66c8621843 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -51,6 +51,8 @@ pub struct DefaultNetworkDetails<'a> { mixnet_contract_address: &'a str, vesting_contract_address: &'a str, bandwidth_claim_contract_address: &'a str, + coconut_bandwidth_contract_address: &'a str, + multisig_contract_address: &'a str, rewarding_validator_address: &'a str, validators: Vec, } @@ -62,6 +64,8 @@ static MAINNET_DEFAULTS: Lazy> = mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: mainnet::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS, validators: mainnet::validators(), }); @@ -73,6 +77,8 @@ static SANDBOX_DEFAULTS: Lazy> = mixnet_contract_address: sandbox::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: sandbox::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: sandbox::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS, validators: sandbox::validators(), }); @@ -83,6 +89,8 @@ static QA_DEFAULTS: Lazy> = Lazy::new(|| DefaultN mixnet_contract_address: qa::MIXNET_CONTRACT_ADDRESS, vesting_contract_address: qa::VESTING_CONTRACT_ADDRESS, bandwidth_claim_contract_address: qa::BANDWIDTH_CLAIM_CONTRACT_ADDRESS, + coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS, + multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS, rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS, validators: qa::validators(), }); @@ -146,7 +154,7 @@ pub const ETH_ERC20_APPROVE_FUNCTION_NAME: &str = "approve"; // Ethereum constants used for token bridge /// How much bandwidth (in bytes) one token can buy -const BYTES_PER_TOKEN: u64 = 1024 * 1024 * 1024; +pub const BYTES_PER_UTOKEN: u64 = 1024; /// Threshold for claiming more bandwidth: 1 MB pub const REMAINING_BANDWIDTH_THRESHOLD: i64 = 1024 * 1024; @@ -155,7 +163,7 @@ pub const TOKENS_TO_BURN: u64 = 1; /// How many ERC20 utokens should be burned to buy bandwidth pub const UTOKENS_TO_BURN: u64 = TOKENS_TO_BURN * 1000000; /// Default bandwidth (in bytes) that we try to buy -pub const BANDWIDTH_VALUE: u64 = TOKENS_TO_BURN * BYTES_PER_TOKEN; +pub const BANDWIDTH_VALUE: u64 = UTOKENS_TO_BURN * BYTES_PER_UTOKEN; pub const VOUCHER_INFO: &str = "BandwidthVoucher"; diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 69f34c92a2..0e9f16eb05 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -13,6 +13,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/network-defaults/src/qa.rs b/common/network-defaults/src/qa.rs index 38ada60537..c782e089ab 100644 --- a/common/network-defaults/src/qa.rs +++ b/common/network-defaults/src/qa.rs @@ -13,6 +13,9 @@ pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("0000000000000000000000000000000000000000"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/network-defaults/src/sandbox.rs b/common/network-defaults/src/sandbox.rs index 3ec1f746a9..a6cfc39514 100644 --- a/common/network-defaults/src/sandbox.rs +++ b/common/network-defaults/src/sandbox.rs @@ -11,6 +11,9 @@ pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt1ghd753shjuwexxywmgs4xz7x2 pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt14ejqjyq8um4p3xfqj74yld5waqljf88fn549lh"; pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str = "nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv"; +pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = + "nymt1nz0r0au8aj6dc00wmm3ufy4g4k86rjzlgq608r"; +pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg"; pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] = hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133"); pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] = diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs index fb43a1d850..d38f5a05cf 100644 --- a/common/nymcoconut/src/proofs/mod.rs +++ b/common/nymcoconut/src/proofs/mod.rs @@ -327,8 +327,7 @@ impl ProofCmCs { } } -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, PartialEq)] pub struct ProofKappaZeta { // c challenge: Scalar, diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs new file mode 100644 index 0000000000..f952938a72 --- /dev/null +++ b/common/nymcoconut/src/scheme/double_use.rs @@ -0,0 +1,49 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use bls12_381::G2Projective; +use group::Curve; +use std::convert::TryFrom; +use std::convert::TryInto; + +use crate::error::{CoconutError, Result}; +use crate::traits::{Base58, Bytable}; +use crate::utils::try_deserialize_g2_projective; + +pub struct BlindedSerialNumber { + pub(crate) inner: G2Projective, +} + +impl TryFrom<&[u8]> for BlindedSerialNumber { + type Error = CoconutError; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != 96 { + return Err( + CoconutError::Deserialization( + format!("Tried to deserialize blinded serial number with incorrect number of bytes, expected 96, got {}", bytes.len()), + )); + } + + let inner = try_deserialize_g2_projective( + &bytes.try_into().unwrap(), + CoconutError::Deserialization( + "failed to deserialize the blinded serial number (zeta)".to_string(), + ), + )?; + + Ok(BlindedSerialNumber { inner }) + } +} + +impl Bytable for BlindedSerialNumber { + fn to_byte_vec(&self) -> Vec { + self.inner.to_affine().to_compressed().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + Self::try_from(slice) + } +} + +impl Base58 for BlindedSerialNumber {} diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs index e85830cada..881ac0ec53 100644 --- a/common/nymcoconut/src/scheme/mod.rs +++ b/common/nymcoconut/src/scheme/mod.rs @@ -19,6 +19,7 @@ use crate::utils::try_deserialize_g1_projective; use crate::Attribute; pub mod aggregation; +pub mod double_use; pub mod issuance; pub mod keygen; pub mod setup; @@ -27,8 +28,7 @@ pub mod verification; pub type SignerIndex = u64; // (h, s) -#[derive(Debug, Clone, Copy)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct Signature(pub(crate) G1Projective, pub(crate) G1Projective); pub type PartialSignature = Signature; diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index c2ccd2da12..18e639828a 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -10,6 +10,7 @@ use group::{Curve, Group}; use crate::error::{CoconutError, Result}; use crate::proofs::ProofKappaZeta; +use crate::scheme::double_use::BlindedSerialNumber; use crate::scheme::setup::Parameters; use crate::scheme::Signature; use crate::scheme::VerificationKey; @@ -19,8 +20,7 @@ use crate::Attribute; // TODO NAMING: this whole thing // Theta -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] +#[derive(Debug, PartialEq)] pub struct Theta { // blinded_message (kappa) pub blinded_message: G2Projective, @@ -81,6 +81,12 @@ impl Theta { ) } + pub fn has_blinded_serial_number(&self, blinded_serial_number_bs58: &str) -> Result { + let blinded_serial_number = BlindedSerialNumber::try_from_bs58(blinded_serial_number_bs58)?; + let ret = self.blinded_serial_number.eq(&blinded_serial_number.inner); + Ok(ret) + } + // blinded message (kappa) || blinded serial number (zeta) || credential || pi_v pub fn to_bytes(&self) -> Vec { let blinded_message_bytes = self.blinded_message.to_affine().to_compressed(); @@ -100,6 +106,13 @@ impl Theta { pub fn from_bytes(bytes: &[u8]) -> Result { Theta::try_from(bytes) } + + pub fn blinded_serial_number_bs58(&self) -> String { + let blinded_serial_nuumber = BlindedSerialNumber { + inner: self.blinded_serial_number, + }; + blinded_serial_nuumber.to_bs58() + } } impl Bytable for Theta { diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 0ace19f985..055efa78b3 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -502,6 +502,7 @@ dependencies = [ "cw3-fixed-multisig", "cw4", "cw4-group", + "multisig-contract-common", "schemars", "serde", "thiserror", @@ -1042,6 +1043,18 @@ dependencies = [ "time 0.3.6", ] +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "network-defaults" version = "0.1.0" diff --git a/contracts/multisig/cw3-flex-multisig/Cargo.toml b/contracts/multisig/cw3-flex-multisig/Cargo.toml index 86ae1f6450..9c83f0fc21 100644 --- a/contracts/multisig/cw3-flex-multisig/Cargo.toml +++ b/contracts/multisig/cw3-flex-multisig/Cargo.toml @@ -28,6 +28,8 @@ schemars = "0.8.1" serde = { version = "1.0.103", default-features = false, features = ["derive"] } thiserror = { version = "1.0.23" } +multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" } + [dev-dependencies] cosmwasm-schema = { version = "1.0.0-beta6" } cw4-group = { path = "../cw4-group", version = "0.13.1" } diff --git a/contracts/multisig/cw3-flex-multisig/src/contract.rs b/contracts/multisig/cw3-flex-multisig/src/contract.rs index 5616cba7dd..0af5001f12 100644 --- a/contracts/multisig/cw3-flex-multisig/src/contract.rs +++ b/contracts/multisig/cw3-flex-multisig/src/contract.rs @@ -18,8 +18,8 @@ use cw_storage_plus::Bound; use cw_utils::{maybe_addr, Expiration, ThresholdResponse}; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{Config, CONFIG}; +use multisig_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw3-flex-multisig"; @@ -499,7 +499,7 @@ mod tests { max_voting_period: Duration, ) -> Addr { let flex_id = app.store_code(contract_flex()); - let msg = crate::msg::InstantiateMsg { + let msg = InstantiateMsg { group_addr: group.to_string(), threshold, max_voting_period, diff --git a/contracts/multisig/cw3-flex-multisig/src/lib.rs b/contracts/multisig/cw3-flex-multisig/src/lib.rs index e5ff7237ef..e544aa55c8 100644 --- a/contracts/multisig/cw3-flex-multisig/src/lib.rs +++ b/contracts/multisig/cw3-flex-multisig/src/lib.rs @@ -1,6 +1,5 @@ pub mod contract; pub mod error; -pub mod msg; pub mod state; pub use crate::error::ContractError; diff --git a/explorer-api/src/client.rs b/explorer-api/src/client.rs index 8688058fef..b7c628b256 100644 --- a/explorer-api/src/client.rs +++ b/explorer-api/src/client.rs @@ -23,19 +23,10 @@ impl ThreadsafeValidatorClient { } pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient { - let network = DEFAULT_NETWORK; - let mixnet_contract = network.mixnet_contract_address().to_string(); let nymd_url = default_nymd_endpoints()[0].clone(); let api_url = default_api_endpoints()[0].clone(); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract.parse().unwrap()), - None, - None, - ); + let client_config = validator_client::Config::new(DEFAULT_NETWORK, nymd_url, api_url); ThreadsafeValidatorClient(Arc::new( validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"), diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index 2d0b46dd88..9501755306 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -18,7 +18,6 @@ rand = { version = "0.7.3", features = ["wasm-bindgen"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -bincode = "1.3" crypto = { path = "../../common/crypto" } pemstore = { path = "../../common/pemstore" } diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/gateway/gateway-requests/src/registration/handshake/error.rs index 0bf672d2b5..81b2c2d0c7 100644 --- a/gateway/gateway-requests/src/registration/handshake/error.rs +++ b/gateway/gateway-requests/src/registration/handshake/error.rs @@ -25,9 +25,4 @@ pub enum HandshakeError { MalformedRequest, #[error("sent request was malformed")] HandshakeFailure, - #[error("could not deserialize from slice: {source}")] - DeserializationError { - #[from] - source: bincode::Error, - }, } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 1043a6075e..5206f444a8 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -146,18 +146,13 @@ impl ClientControlRequest { credential: &Credential, shared_key: &SharedKeys, iv: IV, - ) -> Option { - match bincode::serialize(credential) { - Ok(serialized_credential) => { - let enc_credential = - shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); + ) -> Self { + let serialized_credential = credential.as_bytes(); + let enc_credential = shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); - Some(ClientControlRequest::BandwidthCredential { - enc_credential, - iv: iv.to_bytes(), - }) - } - _ => None, + ClientControlRequest::BandwidthCredential { + enc_credential, + iv: iv.to_bytes(), } } @@ -167,8 +162,9 @@ impl ClientControlRequest { shared_key: &SharedKeys, iv: IV, ) -> Result { - let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; - bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption) + let credential_bytes = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; + Credential::from_bytes(&credential_bytes) + .map_err(|_| GatewayRequestsError::MalformedEncryption) } #[cfg(not(feature = "coconut"))] diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 28a41568b4..e88b531f83 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,13 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -#[cfg(feature = "coconut")] -use std::convert::TryFrom; - #[cfg(feature = "coconut")] use coconut_interface::Credential; -#[cfg(feature = "coconut")] -use credentials::error::Error; #[cfg(not(feature = "coconut"))] use credentials::token::bandwidth::TokenCredential; @@ -22,12 +17,13 @@ impl Bandwidth { } #[cfg(feature = "coconut")] -impl TryFrom for Bandwidth { - type Error = Error; - - fn try_from(credential: Credential) -> Result { - let value = credential.voucher_value()?; - Ok(Self { value }) +impl From for Bandwidth { + fn from(credential: Credential) -> Self { + let token_value = credential.voucher_value(); + let bandwidth_bytes = token_value * network_defaults::BYTES_PER_UTOKEN; + Bandwidth { + value: bandwidth_bytes, + } } } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index e636c7a98a..c6ea692b33 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -42,8 +42,8 @@ pub(crate) enum RequestHandlingError { #[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)] UnsupportedBandwidthValue(u64), - #[error("Provided bandwidth credential did not verify correctly")] - InvalidBandwidthCredential, + #[error("Provided bandwidth credential did not verify correctly on {0}")] + InvalidBandwidthCredential(String), #[error("This gateway is not running in the disabled credentials mode")] NotInDisabledCredentialsMode, @@ -65,8 +65,12 @@ pub(crate) enum RequestHandlingError { NymdError(#[from] validator_client::nymd::error::NymdError), #[cfg(feature = "coconut")] - #[error("Provided coconut bandwidth credential did not have expected structure - {0}")] - CoconutBandwidthCredentialError(#[from] credentials::error::Error), + #[error("Validator API error")] + APIError(#[from] validator_client::ValidatorClientError), + + #[cfg(feature = "coconut")] + #[error("Not enough validator API endpoints provided. Needed {needed}, received {received}")] + NotEnoughValidatorAPIs { received: usize, needed: usize }, } impl RequestHandlingError { @@ -208,11 +212,55 @@ where iv, )?; - if !credential.verify(&self.inner.aggregated_verification_key) { - return Err(RequestHandlingError::InvalidBandwidthCredential); + if !credential.verify( + self.inner + .coconut_verifier + .as_ref() + .aggregated_verification_key(), + ) { + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("credential failed to verify on gateway"), + )); } - let bandwidth = Bandwidth::try_from(credential)?; + let req = coconut_interface::ProposeReleaseFundsRequestBody::new(credential.clone()); + let proposal_id = self + .inner + .coconut_verifier + .api_clients() + .get(0) + .ok_or(RequestHandlingError::NotEnoughValidatorAPIs { + needed: 1, + received: 0, + })? + .propose_release_funds(&req) + .await? + .proposal_id; + + let req = coconut_interface::VerifyCredentialBody::new(credential.clone(), proposal_id); + for client in self.inner.coconut_verifier.api_clients().iter().skip(1) { + if !client + .verify_bandwidth_credential(&req) + .await? + .verification_result + { + debug!("Validator {} didn't accept the credential. It will probably vote No on the spending proposal", client.validator_api.current_url()); + } + } + + let req = coconut_interface::ExecuteReleaseFundsRequestBody::new(proposal_id); + self.inner + .coconut_verifier + .api_clients() + .get(0) + .ok_or(RequestHandlingError::NotEnoughValidatorAPIs { + needed: 1, + received: 0, + })? + .execute_release_funds(&req) + .await?; + + let bandwidth = Bandwidth::from(credential); let bandwidth_value = bandwidth.value(); if bandwidth_value > i64::MAX as u64 { @@ -254,11 +302,15 @@ where .inner .check_local_identity(&credential.gateway_identity()) { - return Err(RequestHandlingError::InvalidBandwidthCredential); + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )); } if !credential.verify_signature() { - return Err(RequestHandlingError::InvalidBandwidthCredential); + return Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )); } debug!("Verifying Ethereum for token burn..."); let gateway_owner = self diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs new file mode 100644 index 0000000000..deae5479da --- /dev/null +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -0,0 +1,27 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use coconut_interface::VerificationKey; +use validator_client::ApiClient; + +pub struct CoconutVerifier { + api_clients: Vec, + aggregated_verification_key: VerificationKey, +} + +impl CoconutVerifier { + pub fn new(api_clients: Vec, aggregated_verification_key: VerificationKey) -> Self { + CoconutVerifier { + api_clients, + aggregated_verification_key, + } + } + + pub fn api_clients(&self) -> &Vec { + &self.api_clients + } + + pub fn aggregated_verification_key(&self) -> &VerificationKey { + &self.aggregated_verification_key + } +} diff --git a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs index 2bb81aefcd..d090a496ce 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs @@ -39,16 +39,9 @@ impl ERC20Bridge { .expect("The list of validators is empty"); let mnemonic = Mnemonic::from_str(&cosmos_mnemonic).expect("Invalid Cosmos mnemonic provided"); - let nymd_client = NymdClient::connect_with_mnemonic( - DEFAULT_NETWORK, - nymd_url.as_ref(), - AccountId::from_str(DEFAULT_NETWORK.mixnet_contract_address()).ok(), - None, - AccountId::from_str(DEFAULT_NETWORK.bandwidth_claim_contract_address()).ok(), - mnemonic, - None, - ) - .expect("Could not create nymd client"); + let nymd_client = + NymdClient::connect_with_mnemonic(DEFAULT_NETWORK, nymd_url.as_ref(), mnemonic, None) + .expect("Could not create nymd client"); ERC20Bridge { contract: eth_contract(web3.clone()), @@ -95,7 +88,9 @@ impl ERC20Bridge { } } - Err(RequestHandlingError::InvalidBandwidthCredential) + Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )) } pub(crate) async fn verify_gateway_owner( @@ -103,17 +98,22 @@ impl ERC20Bridge { gateway_owner: String, gateway_identity: &PublicKey, ) -> Result<(), RequestHandlingError> { - let owner_address = AccountId::from_str(&gateway_owner) - .map_err(|_| RequestHandlingError::InvalidBandwidthCredential)?; + let owner_address = AccountId::from_str(&gateway_owner).map_err(|_| { + RequestHandlingError::InvalidBandwidthCredential(String::from("gateway")) + })?; let gateway_bond = self .nymd_client .owns_gateway(&owner_address) .await? - .ok_or(RequestHandlingError::InvalidBandwidthCredential)?; + .ok_or_else(|| { + RequestHandlingError::InvalidBandwidthCredential(String::from("gateway")) + })?; if gateway_bond.gateway.identity_key == gateway_identity.to_base58_string() { Ok(()) } else { - Err(RequestHandlingError::InvalidBandwidthCredential) + Err(RequestHandlingError::InvalidBandwidthCredential( + String::from("gateway"), + )) } } @@ -121,9 +121,7 @@ impl ERC20Bridge { &self, credential: &TokenCredential, ) -> Result<(), RequestHandlingError> { - // It's ok to unwrap here, as the cosmos contract is set correctly - let erc20_bridge_contract_address = - self.nymd_client.erc20_bridge_contract_address().unwrap(); + let bandwidth_claim_contract_address = self.nymd_client.bandwidth_claim_contract_address(); let req = ExecuteMsg::LinkPayment { data: LinkPaymentData::new( credential.verification_key().to_bytes(), @@ -134,7 +132,7 @@ impl ERC20Bridge { }; self.nymd_client .execute( - erc20_bridge_contract_address, + bandwidth_claim_contract_address, &req, Default::default(), "Linking payment", diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index 2fc83e9e24..8b33a807d2 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::node::client_handling::active_clients::ActiveClientsStore; +#[cfg(feature = "coconut")] +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; use crate::node::client_handling::websocket::connection_handler::{ AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream, }; @@ -27,9 +29,6 @@ use thiserror::Error; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError}; -#[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; - #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; @@ -77,11 +76,10 @@ pub(crate) struct FreshHandler { pub(crate) socket_connection: SocketStream, pub(crate) storage: St, - #[cfg(feature = "coconut")] - pub(crate) aggregated_verification_key: VerificationKey, - #[cfg(not(feature = "coconut"))] pub(crate) erc20_bridge: Arc, + #[cfg(feature = "coconut")] + pub(crate) coconut_verifier: Arc, } impl FreshHandler @@ -102,7 +100,7 @@ where local_identity: Arc, storage: St, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc, ) -> Self { FreshHandler { @@ -114,7 +112,7 @@ where local_identity, storage, #[cfg(feature = "coconut")] - aggregated_verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge, } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index fdd90e3aea..9172f9288f 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -15,6 +15,8 @@ pub(crate) use self::authenticated::AuthenticatedHandler; pub(crate) use self::fresh::FreshHandler; mod authenticated; +#[cfg(feature = "coconut")] +pub(crate) mod coconut; #[cfg(not(feature = "coconut"))] pub(crate) mod eth_events; mod fresh; diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 3669882cb6..08bf22267a 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -14,7 +14,7 @@ use std::sync::Arc; use tokio::task::JoinHandle; #[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; @@ -25,8 +25,7 @@ pub(crate) struct Listener { disabled_credentials_mode: bool, #[cfg(feature = "coconut")] - aggregated_verification_key: VerificationKey, - + pub(crate) coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc, } @@ -36,7 +35,7 @@ impl Listener { address: SocketAddr, local_identity: Arc, disabled_credentials_mode: bool, - #[cfg(feature = "coconut")] aggregated_verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge, ) -> Self { Listener { @@ -44,7 +43,7 @@ impl Listener { local_identity, disabled_credentials_mode, #[cfg(feature = "coconut")] - aggregated_verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge: Arc::new(erc20_bridge), } @@ -84,7 +83,7 @@ impl Listener { storage.clone(), active_clients_store.clone(), #[cfg(feature = "coconut")] - self.aggregated_verification_key.clone(), + Arc::clone(&self.coconut_verifier), #[cfg(not(feature = "coconut"))] Arc::clone(&self.erc20_bridge), ); diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index a14dc90074..5b33bdb319 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -18,11 +18,11 @@ use std::process; use std::sync::Arc; use crate::config::persistence::pathfinder::GatewayPathfinder; +#[cfg(feature = "coconut")] +use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; #[cfg(not(feature = "coconut"))] use crate::node::client_handling::websocket::connection_handler::eth_events::ERC20Bridge; #[cfg(feature = "coconut")] -use coconut_interface::VerificationKey; -#[cfg(feature = "coconut")] use credentials::obtain_aggregate_verification_key; use self::storage::PersistentStorage; @@ -175,7 +175,7 @@ where &self, forwarding_channel: MixForwardingSender, active_clients_store: ActiveClientsStore, - #[cfg(feature = "coconut")] verification_key: VerificationKey, + #[cfg(feature = "coconut")] coconut_verifier: Arc, #[cfg(not(feature = "coconut"))] erc20_bridge: ERC20Bridge, ) { info!("Starting client [web]socket listener..."); @@ -190,7 +190,7 @@ where Arc::clone(&self.identity_keypair), self.config.get_disabled_credentials_mode(), #[cfg(feature = "coconut")] - verification_key, + coconut_verifier, #[cfg(not(feature = "coconut"))] erc20_bridge, ) @@ -227,13 +227,27 @@ where ); } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_gateway_exists(&self) -> Option { + fn random_api_client(&self) -> validator_client::ApiClient { let endpoints = self.config.get_validator_api_endpoints(); let validator_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - let validator_client = validator_client::ApiClient::new(validator_api.clone()); + + validator_client::ApiClient::new(validator_api.clone()) + } + + #[cfg(feature = "coconut")] + fn all_api_clients(&self) -> Vec { + self.config + .get_validator_api_endpoints() + .into_iter() + .map(validator_client::ApiClient::new) + .collect() + } + + // TODO: ask DH whether this function still makes sense in ^0.10 + async fn check_if_same_ip_gateway_exists(&self) -> Option { + let validator_client = self.random_api_client(); let existing_gateways = match validator_client.get_cached_gateways().await { Ok(gateways) => gateways, @@ -271,6 +285,9 @@ where obtain_aggregate_verification_key(&self.config.get_validator_api_endpoints()) .await .expect("failed to contact validators to obtain their verification keys"); + #[cfg(feature = "coconut")] + let coconut_verifier = + CoconutVerifier::new(self.all_api_clients(), validators_verification_key); #[cfg(not(feature = "coconut"))] let erc20_bridge = ERC20Bridge::new( @@ -291,7 +308,7 @@ where mix_forwarding_channel, active_clients_store, #[cfg(feature = "coconut")] - validators_verification_key, + Arc::new(coconut_verifier), #[cfg(not(feature = "coconut"))] erc20_bridge, ); diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index 5e88cc8d69..27851e34bf 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -244,14 +244,18 @@ impl MixNode { atomic_verloc_results } - // TODO: ask DH whether this function still makes sense in ^0.10 - async fn check_if_same_ip_node_exists(&mut self) -> Option { + fn random_api_client(&self) -> validator_client::ApiClient { let endpoints = self.config.get_validator_api_endpoints(); let validator_api = endpoints .choose(&mut thread_rng()) .expect("The list of validator apis is empty"); - let validator_client = validator_client::ApiClient::new(validator_api.clone()); + validator_client::ApiClient::new(validator_api.clone()) + } + + // TODO: ask DH whether this function still makes sense in ^0.10 + async fn check_if_same_ip_node_exists(&mut self) -> Option { + let validator_client = self.random_api_client(); let existing_nodes = match validator_client.get_cached_mixnodes().await { Ok(nodes) => nodes, Err(err) => { diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 0429c1a093..72377c7237 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -709,6 +709,15 @@ dependencies = [ "objc", ] +[[package]] +name = "coconut-bandwidth-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "coconut-interface" version = "0.1.0" @@ -1096,6 +1105,42 @@ dependencies = [ "serde", ] +[[package]] +name = "cw-utils" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "babd2c090f39d07ce5bf2556962305e795daa048ce20a93709eb591476e4a29e" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw3" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f871854338a54c7bb094d16ffe17212b93b146d9659dbce4c9402a9b77e240ef" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw4" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4476d6a7c13c46ed9ff260bd0e1cf648dc37b13f483822e1ff2a431f0f6ee52" +dependencies = [ + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + [[package]] name = "darling" version = "0.10.2" @@ -2800,6 +2845,18 @@ dependencies = [ "time 0.3.7", ] +[[package]] +name = "multisig-contract-common" +version = "0.1.0" +dependencies = [ + "cosmwasm-std", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", +] + [[package]] name = "native-tls" version = "0.2.8" @@ -5478,16 +5535,19 @@ dependencies = [ "async-trait", "base64", "bip39", + "coconut-bandwidth-contract-common", "coconut-interface", "colored", "config", "cosmrs", "cosmwasm-std", + "cw3", "flate2", "futures", "itertools", "log", "mixnet-contract-common", + "multisig-contract-common", "network-defaults", "prost", "reqwest", diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index cf8cde54fe..3184cab8c9 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -211,34 +211,31 @@ impl Config { .flat_map(|c| c.validators().cloned()) } - pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option { + pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .mixnet_contract_address(network.into()) .expect("No mixnet contract address found in config") .parse() - .ok() + .expect("Wrong format for mixnet contract address") } - pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> Option { + pub fn get_vesting_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .vesting_contract_address(network.into()) .expect("No vesting contract address found in config") .parse() - .ok() + .expect("Wrong format for vesting contract address") } - pub fn get_bandwidth_claim_contract_address( - &self, - network: WalletNetwork, - ) -> Option { + pub fn get_bandwidth_claim_contract_address(&self, network: WalletNetwork) -> CosmosAccountId { self.base .networks .bandwidth_claim_contract_address(network.into()) .expect("No bandwidth claim contract address found in config") .parse() - .ok() + .expect("Wrong format for bandwidth claim contract address") } pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index f48cffe515..5e0215cf71 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -133,7 +133,7 @@ pub async fn switch_network( let denom = network.denom(); Account::new( - client.nymd.mixnet_contract_address()?.to_string(), + client.nymd.mixnet_contract_address().to_string(), client.nymd.address().to_string(), denom.parse()?, ) @@ -216,7 +216,7 @@ async fn _connect_with_mnemonic( .find(|client| WalletNetwork::from(client.network) == default_network); let account_for_default_network = match client_for_default_network { Some(client) => Ok(Account::new( - client.nymd.mixnet_contract_address()?.to_string(), + client.nymd.mixnet_contract_address().to_string(), client.nymd.address().to_string(), default_network.denom().parse()?, )), @@ -309,14 +309,12 @@ fn create_clients( log::info!("Connecting to: api_url: {api_url} for {network}"); let mut client = validator_client::Client::new_signing( - validator_client::Config::new( - network.into(), - nymd_url, - api_url, - config.get_mixnet_contract_address(network), - config.get_vesting_contract_address(network), - config.get_bandwidth_claim_contract_address(network), - ), + validator_client::Config::new(network.into(), nymd_url, api_url) + .with_mixnode_contract_address(config.get_mixnet_contract_address(network)) + .with_vesting_contract_address(config.get_vesting_contract_address(network)) + .with_bandwidth_claim_contract_address( + config.get_bandwidth_claim_contract_address(network), + ), mnemonic.clone(), )?; client.set_nymd_simulated_gas_multiplier(CUSTOM_SIMULATED_GAS_MULTIPLIER); diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index cbfa8e39f9..01aac84999 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -18,7 +18,7 @@ pub async fn simulate_update_contract_settings( let mixnet_contract_settings_params: ContractStateParams = params.try_into()?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 37d48859aa..a833f934b1 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -20,7 +20,7 @@ pub async fn simulate_bond_gateway( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); // TODO: I'm still not 100% convinced whether this should be exposed here or handled somewhere else in the client code @@ -44,7 +44,7 @@ pub async fn simulate_unbond_gateway( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -68,7 +68,7 @@ pub async fn simulate_bond_mixnode( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -91,7 +91,7 @@ pub async fn simulate_unbond_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -112,7 +112,7 @@ pub async fn simulate_update_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -137,7 +137,7 @@ pub async fn simulate_delegate_to_mixnode( let delegation = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -160,7 +160,7 @@ pub async fn simulate_undelegate_from_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let mixnet_contract = client.nymd.mixnet_contract_address()?; + let mixnet_contract = client.nymd.mixnet_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index c72b7be820..fe095c5717 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -21,7 +21,7 @@ pub async fn simulate_vesting_bond_gateway( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -45,7 +45,7 @@ pub async fn simulate_vesting_unbond_gateway( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -69,7 +69,7 @@ pub async fn simulate_vesting_bond_mixnode( let pledge = pledge.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -93,7 +93,7 @@ pub async fn simulate_vesting_unbond_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -114,7 +114,7 @@ pub async fn simulate_vesting_update_mixnode( let guard = state.read().await; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( @@ -138,7 +138,7 @@ pub async fn simulate_withdraw_vested_coins( let amount = amount.into_backend_coin(guard.current_network().denom())?; let client = guard.current_client()?; - let vesting_contract = client.nymd.vesting_contract_address()?; + let vesting_contract = client.nymd.vesting_contract_address(); let gas_price = client.nymd.gas_price().clone(); let msg = client.nymd.wrap_contract_execute_message( diff --git a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs index a212afe7f1..e6bb223787 100644 --- a/nym-wallet/src-tauri/src/operations/vesting/delegate.rs +++ b/nym-wallet/src-tauri/src/operations/vesting/delegate.rs @@ -14,7 +14,7 @@ pub async fn get_pending_vesting_delegation_events( ) -> Result, BackendError> { let guard = state.read().await; let client = &guard.current_client()?.nymd; - let vesting_contract = client.vesting_contract_address()?; + let vesting_contract = client.vesting_contract_address(); Ok(client .get_pending_delegation_events( diff --git a/nym-wallet/src/types/rust/gateway.ts b/nym-wallet/src/types/rust/gateway.ts index 1dbba0e5ed..24b3b7eb58 100644 --- a/nym-wallet/src/types/rust/gateway.ts +++ b/nym-wallet/src/types/rust/gateway.ts @@ -1,9 +1,2 @@ -export interface Gateway { - host: string; - mix_port: number; - clients_port: number; - location: string; - sphinx_key: string; - identity_key: string; - version: string; -} + +export interface Gateway { host: string, mix_port: number, clients_port: number, location: string, sphinx_key: string, identity_key: string, version: string, } \ No newline at end of file diff --git a/nym-wallet/src/types/rust/mixnode.ts b/nym-wallet/src/types/rust/mixnode.ts index d1c3a9574b..6b6d783dda 100644 --- a/nym-wallet/src/types/rust/mixnode.ts +++ b/nym-wallet/src/types/rust/mixnode.ts @@ -1,10 +1,2 @@ -export interface MixNode { - host: string; - mix_port: number; - verloc_port: number; - http_api_port: number; - sphinx_key: string; - identity_key: string; - version: string; - profit_margin_percent: number; -} + +export interface MixNode { host: string, mix_port: number, verloc_port: number, http_api_port: number, sphinx_key: string, identity_key: string, version: string, profit_margin_percent: number, } \ No newline at end of file diff --git a/nym-wallet/src/types/rust/rewardedsetnodestatus.ts b/nym-wallet/src/types/rust/rewardedsetnodestatus.ts index 8ddd13d047..3f0bb8e3e6 100644 --- a/nym-wallet/src/types/rust/rewardedsetnodestatus.ts +++ b/nym-wallet/src/types/rust/rewardedsetnodestatus.ts @@ -1 +1,2 @@ -export type RewardedSetNodeStatus = 'Active' | 'Standby'; + +export type RewardedSetNodeStatus = "Active" | "Standby"; \ No newline at end of file diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index ed31e57d8d..c33e43b55d 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -54,6 +54,7 @@ cosmwasm-std = "1.0.0-beta8" crypto = { path="../common/crypto" } gateway-client = { path="../common/client-libs/gateway-client" } mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" } +multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nymsphinx = { path="../common/nymsphinx" } topology = { path="../common/topology" } validator-api-requests = { path = "validator-api-requests" } diff --git a/validator-api/src/coconut/client.rs b/validator-api/src/coconut/client.rs index 34042e9610..c606152cc9 100644 --- a/validator-api/src/coconut/client.rs +++ b/validator-api/src/coconut/client.rs @@ -2,9 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use crate::coconut::error::Result; -use validator_client::nymd::TxResponse; +use multisig_contract_common::msg::ProposalResponse; +use validator_client::nymd::{Fee, TxResponse}; #[async_trait] pub trait Client { async fn get_tx(&self, tx_hash: &str) -> Result; + async fn get_proposal(&self, proposal_id: u64) -> Result; + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result; + async fn vote_proposal(&self, proposal_id: u64, vote_yes: bool, fee: Option) + -> Result<()>; + async fn execute_proposal(&self, proposal_id: u64, fee: Option) -> Result<()>; } diff --git a/validator-api/src/coconut/error.rs b/validator-api/src/coconut/error.rs index fb2ff73419..78f9b08e43 100644 --- a/validator-api/src/coconut/error.rs +++ b/validator-api/src/coconut/error.rs @@ -63,8 +63,22 @@ pub enum CoconutError { #[error("Error in coconut interface - {0}")] CoconutInterfaceError(#[from] coconut_interface::error::CoconutInterfaceError), + #[error("Could not create proposal for spending credential")] + CreateProposalError, + #[error("Storage error - {0}")] StorageError(#[from] ValidatorApiStorageError), + + #[error("Credentials error - {0}")] + CredentialsError(#[from] credentials::error::Error), + + #[error( + "Incorrect credential proposal description. Expected blinded serial number in base 58" + )] + IncorrectProposal, + + #[error("Internal error: {0}")] + InternalError(String), } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/validator-api/src/coconut/mod.rs b/validator-api/src/coconut/mod.rs index ed08605100..8d98cf977f 100644 --- a/validator-api/src/coconut/mod.rs +++ b/validator-api/src/coconut/mod.rs @@ -14,15 +14,19 @@ use crate::ValidatorApiStorage; use coconut_interface::{ Attribute, BlindSignRequest, BlindSignRequestBody, BlindedSignature, BlindedSignatureResponse, - KeyPair, Parameters, VerificationKeyResponse, + ExecuteReleaseFundsRequestBody, KeyPair, Parameters, ProposeReleaseFundsRequestBody, + ProposeReleaseFundsResponse, VerificationKey, VerificationKeyResponse, VerifyCredentialBody, + VerifyCredentialResponse, }; use config::defaults::VALIDATOR_API_VERSION; use credentials::coconut::params::{ ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm, }; +use credentials::obtain_aggregate_verification_key; use crypto::asymmetric::encryption; use crypto::shared_key::new_ephemeral_shared_key; use crypto::symmetric::stream_cipher; +use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; use getset::{CopyGetters, Getters}; use rand_07::rngs::OsRng; @@ -30,26 +34,33 @@ use rocket::fairing::AdHoc; use rocket::serde::json::Json; use rocket::State as RocketState; use std::sync::Arc; -use tokio::sync::{Mutex, RwLock}; -use validator_client::validator_api::routes::{BANDWIDTH, COCONUT_ROUTES}; +use tokio::sync::Mutex; +use url::Url; pub struct State { - client: Arc>, + client: Arc, key_pair: KeyPair, + validator_apis: Vec, storage: ValidatorApiStorage, rng: Arc>, } impl State { - pub(crate) fn new(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> Self + pub(crate) fn new( + client: C, + key_pair: KeyPair, + validator_apis: Vec, + storage: ValidatorApiStorage, + ) -> Self where C: LocalClient + Send + Sync + 'static, { - let client = Arc::new(RwLock::new(client)); + let client = Arc::new(client); let rng = Arc::new(Mutex::new(OsRng)); Self { client, key_pair, + validator_apis, storage, rng, } @@ -109,6 +120,10 @@ impl State { Ok(response) } } + + pub async fn verification_key(&self) -> Result { + Ok(obtain_aggregate_verification_key(&self.validator_apis).await?) + } } #[derive(Getters, CopyGetters, Debug)] @@ -135,11 +150,16 @@ impl InternalSignRequest { } } - pub fn stage(client: C, key_pair: KeyPair, storage: ValidatorApiStorage) -> AdHoc + pub fn stage( + client: C, + key_pair: KeyPair, + validator_apis: Vec, + storage: ValidatorApiStorage, + ) -> AdHoc where C: LocalClient + Send + Sync + 'static, { - let state = State::new(client, key_pair, storage); + let state = State::new(client, key_pair, validator_apis, storage); AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { rocket.manage(state).mount( // this format! is so ugly... @@ -150,7 +170,10 @@ impl InternalSignRequest { routes![ post_blind_sign, get_verification_key, - post_partial_bandwidth_credential + post_partial_bandwidth_credential, + verify_bandwidth_credential, + post_propose_release_funds, + post_execute_release_funds ], ) }) @@ -183,8 +206,6 @@ pub async fn post_blind_sign( } let tx = state .client - .read() - .await .get_tx(blind_sign_request_body.tx_hash()) .await?; let encryption_key = extract_encryption_key(&blind_sign_request_body, tx).await?; @@ -226,3 +247,69 @@ pub async fn get_verification_key( state.key_pair.verification_key(), ))) } + +#[post("/verify-bandwidth-credential", data = "")] +pub async fn verify_bandwidth_credential( + verify_credential_body: Json, + state: &RocketState, +) -> Result> { + let proposal_id = *verify_credential_body.0.proposal_id(); + let proposal = state.client.get_proposal(proposal_id).await?; + // Proposal description is the blinded serial number + if !verify_credential_body + .0 + .credential() + .has_blinded_serial_number(&proposal.description)? + { + return Err(CoconutError::IncorrectProposal); + } + let verification_key = state.verification_key().await?; + let verification_result = verify_credential_body + .0 + .credential() + .verify(&verification_key); + + // Vote yes or no on the proposal based on the verification result + state + .client + .vote_proposal(proposal_id, verification_result, None) + .await?; + + Ok(Json(VerifyCredentialResponse::new(verification_result))) +} + +#[post("/propose-release-funds", data = "")] +pub async fn post_propose_release_funds( + propose_release_funds: Json, + state: &RocketState, +) -> Result> { + let verification_key = state.verification_key().await?; + if !propose_release_funds + .0 + .credential() + .verify(&verification_key) + { + return Err(CoconutError::CreateProposalError); + } + + let title = String::from("Create proposal to spend a coconut credential"); + let blinded_serial_number = propose_release_funds.0.credential().blinded_serial_number(); + let voucher_value = propose_release_funds.0.credential().voucher_value() as u128; + let proposal_id = state + .client + .propose_release_funds(title, blinded_serial_number, voucher_value, None) + .await?; + + Ok(Json(ProposeReleaseFundsResponse::new(proposal_id))) +} + +#[post("/execute-release-funds", data = "")] +pub async fn post_execute_release_funds( + execute_release_funds: Json, + state: &RocketState, +) -> Result> { + let proposal_id = *execute_release_funds.0.proposal_id(); + state.client.execute_proposal(proposal_id, None).await?; + + Ok(Json(())) +} diff --git a/validator-api/src/coconut/tests.rs b/validator-api/src/coconut/tests.rs index 5f0c745a69..5e34f3962c 100644 --- a/validator-api/src/coconut/tests.rs +++ b/validator-api/src/coconut/tests.rs @@ -15,10 +15,11 @@ use credentials::coconut::params::{ }; use crypto::shared_key::recompute_shared_key; use crypto::symmetric::stream_cipher; +use multisig_contract_common::msg::ProposalResponse; use nymcoconut::{ prepare_blind_sign, ttp_keygen, Base58, BlindSignRequest, BlindedSignature, KeyPair, Parameters, }; -use validator_client::nymd::{tx::Hash, DeliverTx, Event, Tag, TxResponse}; +use validator_client::nymd::{tx::Hash, DeliverTx, Event, Fee, Tag, TxResponse}; use validator_client::validator_api::routes::{ API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_PARTIAL_BANDWIDTH_CREDENTIAL, COCONUT_ROUTES, COCONUT_VERIFICATION_KEY, @@ -56,6 +57,37 @@ impl super::client::Client for DummyClient { .cloned() .ok_or(CoconutError::TxHashParseError) } + + async fn get_proposal(&self, _proposal_id: u64) -> Result { + todo!() + } + + async fn propose_release_funds( + &self, + _title: String, + _blinded_serial_number: String, + _voucher_value: u128, + _fee: Option, + ) -> Result { + todo!() + } + + async fn vote_proposal( + &self, + _proposal_id: u64, + _vote_yes: bool, + _fee: Option, + ) -> Result<()> { + todo!() + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> crate::coconut::error::Result<()> { + todo!() + } } pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse { @@ -87,7 +119,12 @@ async fn check_signer_verif_key(key_pair: KeyPair) { let nymd_db = Arc::new(RwLock::new(HashMap::new())); let nymd_client = DummyClient::new(&nymd_db); - let rocket = rocket::build().attach(InternalSignRequest::stage(nymd_client, key_pair, storage)); + let rocket = rocket::build().attach(InternalSignRequest::stage( + nymd_client, + key_pair, + vec![], + storage, + )); let client = Client::tracked(rocket) .await @@ -172,6 +209,7 @@ async fn signed_before() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) @@ -231,7 +269,7 @@ async fn state_functions() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = ValidatorApiStorage::init(db_dir).await.unwrap(); - let state = State::new(nymd_client, key_pair, storage.clone()); + let state = State::new(nymd_client, key_pair, vec![], storage.clone()); let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E"); assert!(state.signed_before(&tx_hash).await.unwrap().is_none()); @@ -393,6 +431,7 @@ async fn blind_sign_correct() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) @@ -465,6 +504,7 @@ async fn signature_test() { let rocket = rocket::build().attach(InternalSignRequest::stage( nymd_client, key_pair, + vec![], storage.clone(), )); let client = Client::tracked(rocket) diff --git a/validator-api/src/config/mod.rs b/validator-api/src/config/mod.rs index c3a07d145a..7cf81b296e 100644 --- a/validator-api/src/config/mod.rs +++ b/validator-api/src/config/mod.rs @@ -2,16 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use crate::config::template::config_template; -use config::defaults::{default_api_endpoints, DEFAULT_NETWORK}; +use config::defaults::DEFAULT_NETWORK; use config::NymConfig; use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::time::Duration; use url::Url; -#[cfg(feature = "coconut")] -use coconut_interface::{Base58, KeyPair}; - mod template; pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; @@ -82,17 +79,20 @@ impl NymConfig for Config { } fn config_directory(&self) -> PathBuf { - self.root_directory().join("config") + self.root_directory().join(self.get_id()).join("config") } fn data_directory(&self) -> PathBuf { - self.root_directory().join("data") + self.root_directory().join(self.get_id()).join("data") } } #[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] pub struct Base { + /// ID specifies the human readable ID of this particular validator-api. + id: String, + local_validator: Url, /// Address of the validator contract managing the network @@ -105,6 +105,7 @@ pub struct Base { impl Default for Base { fn default() -> Self { Base { + id: String::default(), local_validator: DEFAULT_LOCAL_VALIDATOR .parse() .expect("default local validator is malformed!"), @@ -128,11 +129,6 @@ pub struct NetworkMonitor { #[serde(default)] disabled_credentials_mode: bool, - /// Specifies list of all validators on the network issuing coconut credentials. - /// A special care must be taken to ensure they are in correct order. - /// The list must also contain THIS validator that is running the test - all_validator_apis: Vec, - /// Specifies the interval at which the network monitor sends the test packets. #[serde(with = "humantime_serde")] run_interval: Duration, @@ -189,8 +185,10 @@ pub struct NetworkMonitor { } impl NetworkMonitor { + pub const DB_FILE: &'static str = "credentials_database.db"; + fn default_credentials_database_path() -> PathBuf { - Config::default_data_directory(None).join("credentials_database.db") + Config::default_data_directory(None).join(Self::DB_FILE) } } @@ -201,7 +199,6 @@ impl Default for NetworkMonitor { min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, enabled: false, disabled_credentials_mode: true, - all_validator_apis: default_api_endpoints(), run_interval: DEFAULT_MONITOR_RUN_INTERVAL, gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, @@ -230,8 +227,10 @@ pub struct NodeStatusAPI { } impl NodeStatusAPI { + pub const DB_FILE: &'static str = "db.sqlite"; + fn default_database_path() -> PathBuf { - Config::default_data_directory(None).join("db.sqlite") + Config::default_data_directory(None).join(Self::DB_FILE) } } @@ -279,15 +278,31 @@ impl Default for Rewarding { } } -#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +#[derive(Debug, Deserialize, PartialEq, Serialize)] #[serde(default)] #[cfg(feature = "coconut")] pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. enabled: bool, - /// Base58 encoded signing keypair - keypair_bs58: String, + /// Path to the signing keypair + keypair_path: PathBuf, + + /// Specifies list of all validators on the network issuing coconut credentials. + /// A special care must be taken to ensure they are in correct order. + /// The list must also contain THIS validator that is running the test + all_validator_apis: Vec, +} + +#[cfg(feature = "coconut")] +impl Default for CoconutSigner { + fn default() -> Self { + CoconutSigner { + enabled: false, + keypair_path: PathBuf::default(), + all_validator_apis: config::defaults::default_api_endpoints(), + } + } } impl Config { @@ -295,9 +310,18 @@ impl Config { Config::default() } + pub fn with_id(mut self, id: &str) -> Self { + self.base.id = id.to_string(); + self.node_status_api.database_path = + Config::default_data_directory(Some(id)).join(NodeStatusAPI::DB_FILE); + self.network_monitor.credentials_database_path = + Config::default_data_directory(Some(id)).join(NetworkMonitor::DB_FILE); + self + } + #[cfg(feature = "coconut")] - pub fn keypair(&self) -> KeyPair { - KeyPair::try_from_bs58(self.coconut_signer.keypair_bs58.clone()).unwrap() + pub fn keypair_path(&self) -> PathBuf { + self.coconut_signer.keypair_path.clone() } pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self { @@ -337,13 +361,14 @@ impl Config { } #[cfg(feature = "coconut")] - pub fn with_keypair>(mut self, keypair_bs58: S) -> Self { - self.coconut_signer.keypair_bs58 = keypair_bs58.into(); + pub fn with_keypair_path(mut self, keypair_path: PathBuf) -> Self { + self.coconut_signer.keypair_path = keypair_path; self } + #[cfg(feature = "coconut")] pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec) -> Self { - self.network_monitor.all_validator_apis = validator_api_urls; + self.coconut_signer.all_validator_apis = validator_api_urls; self } @@ -374,6 +399,10 @@ impl Config { self } + pub fn get_id(&self) -> String { + self.base.id.clone() + } + pub fn get_network_monitor_enabled(&self) -> bool { self.network_monitor.enabled } @@ -474,7 +503,7 @@ impl Config { // fix dead code warnings as this method is only ever used with coconut feature #[cfg(feature = "coconut")] pub fn get_all_validator_api_endpoints(&self) -> Vec { - self.network_monitor.all_validator_apis.clone() + self.coconut_signer.all_validator_apis.clone() } // TODO: Remove if still unused diff --git a/validator-api/src/config/template.rs b/validator-api/src/config/template.rs index e5edd71c4e..74eb8ca10b 100644 --- a/validator-api/src/config/template.rs +++ b/validator-api/src/config/template.rs @@ -10,12 +10,18 @@ pub(crate) fn config_template() -> &'static str { [base] +# ID specifies the human readable ID of this particular validator-api. +id = '{{ base.id }}' + # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' # Address of the validator contract managing the network. mixnet_contract_address = '{{ base.mixnet_contract_address }}' +# Mnemonic used for rewarding and validator interaction +mnemonic = '{{ base.mnemonic }}' + ##### network monitor config options ##### [network_monitor] @@ -26,15 +32,6 @@ enabled = {{ network_monitor.enabled }} # to claim bandwidth without presenting bandwidth credentials. disabled_credentials_mode = {{ network_monitor.disabled_credentials_mode }} -# Specifies list of all validators on the network issuing coconut credentials. -# A special care must be taken to ensure they are in correct order. -# The list must also contain THIS validator that is running the test -all_validator_apis = [ - {{#each network_monitor.all_validator_apis }} - '{{this}}', - {{/each}} -] - # Specifies the interval at which the network monitor sends the test packets. run_interval = '{{ network_monitor.run_interval }}' @@ -92,13 +89,27 @@ database_path = '{{ node_status_api.database_path }}' # Specifies whether rewarding service is enabled in this process. enabled = {{ rewarding.enabled }} -# Mnemonic (currently of the network monitor) used for rewarding -mnemonic = '{{ rewarding.mnemonic }}' - # Specifies the minimum percentage of monitor test run data present in order to # distribute rewards for given interval. # Note, only values in range 0-100 are valid minimum_interval_monitor_threshold = {{ rewarding.minimum_interval_monitor_threshold }} +[coconut_signer] + +# Specifies whether rewarding service is enabled in this process. +enabled = {{ coconut_signer.enabled }} + +# Path to the signing keypair +keypair_path = '{{ coconut_signer.keypair_path }}' + +# Specifies list of all validators on the network issuing coconut credentials. +# A special care must be taken to ensure they are in correct order. +# The list must also contain THIS validator that is running the test +all_validator_apis = [ + {{#each coconut_signer.all_validator_apis }} + '{{this}}', + {{/each}} +] + "# } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 26cd59bd1c..e51a9fd847 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -23,17 +23,18 @@ use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use rocket_okapi::mount_endpoints_and_merged_docs; use rocket_okapi::swagger_ui::make_swagger_ui; -use std::process; use std::sync::Arc; use std::time::Duration; +use std::{fs, process}; use tokio::sync::Notify; -use url::Url; // use validator_client::nymd::SigningNymdClient; // use validator_client::ValidatorClientError; use crate::rewarded_set_updater::RewardedSetUpdater; #[cfg(feature = "coconut")] use coconut::InternalSignRequest; +#[cfg(feature = "coconut")] +use coconut_interface::{Base58, KeyPair}; use validator_client::nymd::SigningNymdClient; pub(crate) mod config; @@ -48,18 +49,19 @@ mod swagger; #[cfg(feature = "coconut")] mod coconut; +const ID: &str = "id"; const MONITORING_ENABLED: &str = "enable-monitor"; const REWARDING_ENABLED: &str = "enable-rewarding"; const MIXNET_CONTRACT_ARG: &str = "mixnet-contract"; const MNEMONIC_ARG: &str = "mnemonic"; const WRITE_CONFIG_ARG: &str = "save-config"; const NYMD_VALIDATOR_ARG: &str = "nymd-validator"; -const API_VALIDATORS_ARG: &str = "api-validators"; const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode"; +#[cfg(feature = "coconut")] +const API_VALIDATORS_ARG: &str = "api-validators"; #[cfg(feature = "coconut")] const KEYPAIR_ARG: &str = "keypair"; - #[cfg(feature = "coconut")] const COCONUT_ENABLED: &str = "enable-coconut"; @@ -73,7 +75,8 @@ const REWARDING_MONITOR_THRESHOLD_ARG: &str = "monitor-threshold"; const MIN_MIXNODE_RELIABILITY_ARG: &str = "min_mixnode_reliability"; const MIN_GATEWAY_RELIABILITY_ARG: &str = "min_gateway_reliability"; -fn parse_validators(raw: &str) -> Vec { +#[cfg(feature = "coconut")] +fn parse_validators(raw: &str) -> Vec { raw.split(',') .map(|raw_validator| { raw_validator @@ -124,6 +127,12 @@ fn parse_args<'a>() -> ArgMatches<'a> { .version(crate_version!()) .long_version(&*build_details) .author("Nymtech") + .arg( + Arg::with_name(ID) + .help("Id of the validator-api we want to run") + .long(ID) + .takes_value(true) + ) .arg( Arg::with_name(MONITORING_ENABLED) .help("specifies whether a network monitoring is enabled on this API") @@ -159,12 +168,6 @@ fn parse_args<'a>() -> ArgMatches<'a> { .long(WRITE_CONFIG_ARG) .short("w") ) - .arg( - Arg::with_name(API_VALIDATORS_ARG) - .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") - .long(API_VALIDATORS_ARG) - .takes_value(true) - ) .arg( Arg::with_name(REWARDING_MONITOR_THRESHOLD_ARG) .help("Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval.") @@ -185,10 +188,16 @@ fn parse_args<'a>() -> ArgMatches<'a> { .takes_value(true) .long(KEYPAIR_ARG), ) + .arg( + Arg::with_name(API_VALIDATORS_ARG) + .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") + .long(API_VALIDATORS_ARG) + .takes_value(true) + ) .arg( Arg::with_name(COCONUT_ENABLED) .help("Flag to indicate whether coconut signer authority is enabled on this API") - .requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG]) + .requires_all(&[KEYPAIR_ARG, MNEMONIC_ARG, API_VALIDATORS_ARG]) .long(COCONUT_ENABLED), ); @@ -236,12 +245,18 @@ fn setup_logging() { .filter_module("sled", log::LevelFilter::Warn) .filter_module("tungstenite", log::LevelFilter::Warn) .filter_module("tokio_tungstenite", log::LevelFilter::Warn) - .filter_module("_", log::LevelFilter::Warn) - .filter_module("rocket::server", log::LevelFilter::Warn) .init(); } fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { + if let Some(id) = matches.value_of(ID) { + fs::create_dir_all(Config::default_config_directory(Some(id))) + .expect("Could not create config directory"); + fs::create_dir_all(Config::default_data_directory(Some(id))) + .expect("Could not create data directory"); + config = config.with_id(id); + } + if matches.is_present(MONITORING_ENABLED) { config = config.with_network_monitor_enabled(true) } @@ -255,6 +270,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { config = config.with_coconut_signer_enabled(true) } + #[cfg(feature = "coconut")] if let Some(raw_validators) = matches.value_of(API_VALIDATORS_ARG) { config = config.with_custom_validator_apis(parse_validators(raw_validators)); } @@ -311,11 +327,7 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config { #[cfg(feature = "coconut")] if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) { - let keypair_bs58 = std::fs::read_to_string(keypair_path) - .unwrap() - .trim() - .to_string(); - config = config.with_keypair(keypair_bs58) + config = config.with_keypair_path(keypair_path.into()) } #[cfg(not(feature = "coconut"))] @@ -402,7 +414,7 @@ fn expected_monitor_test_runs(config: &Config, interval_length: Duration) -> usi async fn setup_rocket( config: &Config, liftoff_notify: Arc, - _nymd_client: Option>, + _nymd_client: Client, ) -> Result> { let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); let mut rocket = rocket::build(); @@ -434,9 +446,14 @@ async fn setup_rocket( #[cfg(feature = "coconut")] let rocket = if config.get_coconut_signer_enabled() { + let keypair_bs58 = fs::read_to_string(config.keypair_path())? + .trim() + .to_string(); + let keypair = KeyPair::try_from_bs58(keypair_bs58)?; rocket.attach(InternalSignRequest::stage( - _nymd_client.expect("Should have a signing client here"), - config.keypair(), + _nymd_client, + keypair, + config.get_all_validator_api_endpoints(), storage.clone().unwrap(), )) } else { @@ -486,10 +503,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { let system_version = env!("CARGO_PKG_VERSION"); // try to load config from the file, if it doesn't exist, use default values - let config = match Config::load_from_file(None) { + let id = matches.value_of(ID); + let config = match Config::load_from_file(id) { Ok(cfg) => cfg, Err(_) => { - let config_path = Config::default_config_file_path(None) + let config_path = Config::default_config_file_path(id) .into_os_string() .into_string() .unwrap(); @@ -507,11 +525,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { return Ok(()); } - let signing_nymd_client = if matches.is_present(MNEMONIC_ARG) { - Some(Client::new_signing(&config)) - } else { - None - }; + let signing_nymd_client = Client::new_signing(&config); let liftoff_notify = Arc::new(Notify::new()); @@ -529,9 +543,8 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { // if network monitor is disabled, we're not going to be sending any rewarding hence // we're not starting signing client if config.get_network_monitor_enabled() { - let nymd_client = signing_nymd_client.expect("We should have a signing client here"); let validator_cache_refresher = ValidatorCacheRefresher::new( - nymd_client.clone(), + signing_nymd_client.clone(), config.get_caching_interval(), validator_cache.clone(), ); @@ -546,7 +559,7 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { tokio::spawn(async move { uptime_updater.run().await }); let mut rewarded_set_updater = - RewardedSetUpdater::new(nymd_client, validator_cache.clone(), storage).await?; + RewardedSetUpdater::new(signing_nymd_client, validator_cache.clone(), storage).await?; // spawn rewarded set updater tokio::spawn(async move { rewarded_set_updater.run().await.unwrap() }); diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 3e32d40bef..b2b43b179d 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -1,21 +1,28 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::config::Config; -use crate::rewarded_set_updater::error::RewardingError; #[cfg(feature = "coconut")] use async_trait::async_trait; -use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; -use mixnet_contract_common::Interval; -use mixnet_contract_common::{ - reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, - IdentityKey, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus, -}; use serde::Serialize; +#[cfg(feature = "coconut")] +use std::str::FromStr; use std::sync::Arc; use std::time::Duration; use tokio::sync::RwLock; use tokio::time::sleep; + +use config::defaults::{DEFAULT_NETWORK, DEFAULT_VALIDATOR_API_PORT}; +use mixnet_contract_common::{ + reward_params::EpochRewardParams, ContractStateParams, Delegation, ExecuteMsg, GatewayBond, + IdentityKey, Interval, MixNodeBond, MixnodeRewardingStatusResponse, RewardedSetNodeStatus, +}; +#[cfg(feature = "coconut")] +use multisig_contract_common::msg::ProposalResponse; +#[cfg(feature = "coconut")] +use validator_client::nymd::{ + cosmwasm_client::logs::find_attribute, + traits::{MultisigSigningClient, QueryClient}, +}; use validator_client::nymd::{ hash::{Hash, SHA256_HASH_SIZE}, Coin, CosmWasmClient, Fee, QueryNymdClient, SigningCosmWasmClient, SigningNymdClient, @@ -23,6 +30,11 @@ use validator_client::nymd::{ }; use validator_client::ValidatorClientError; +#[cfg(feature = "coconut")] +use crate::coconut::error::CoconutError; +use crate::config::Config; +use crate::rewarded_set_updater::error::RewardingError; + pub(crate) struct Client(pub(crate) Arc>>); impl Clone for Client { @@ -46,14 +58,8 @@ impl Client { .parse() .expect("the mixnet contract address is invalid!"); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract), - None, - None, - ); + let client_config = validator_client::Config::new(network, nymd_url, api_url) + .with_mixnode_contract_address(mixnet_contract); let inner = validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"); @@ -80,14 +86,8 @@ impl Client { .parse() .expect("the mnemonic is invalid!"); - let client_config = validator_client::Config::new( - network, - nymd_url, - api_url, - Some(mixnet_contract), - None, - None, - ); + let client_config = validator_client::Config::new(network, nymd_url, api_url) + .with_mixnode_contract_address(mixnet_contract); let inner = validator_client::Client::new_signing(client_config, mnemonic) .expect("Failed to connect to nymd!"); @@ -366,12 +366,7 @@ impl Client { C: SigningCosmWasmClient + Sync, M: Serialize + Clone + Send, { - let contract = self - .0 - .read() - .await - .get_mixnet_contract_address() - .ok_or(RewardingError::UnspecifiedContractAddress)?; + let contract = self.0.read().await.get_mixnet_contract_address(); // grab the write lock here so we're sure nothing else is executing anything on the contract // in the meantime @@ -420,7 +415,7 @@ impl Client { #[cfg(feature = "coconut")] impl crate::coconut::client::Client for Client where - C: CosmWasmClient + Sync + Send, + C: SigningCosmWasmClient + Sync + Send, { async fn get_tx( &self, @@ -428,7 +423,71 @@ where ) -> crate::coconut::error::Result { let tx_hash = tx_hash .parse::() - .map_err(|_| crate::coconut::error::CoconutError::TxHashParseError)?; + .map_err(|_| CoconutError::TxHashParseError)?; Ok(self.0.read().await.nymd.get_tx(tx_hash).await?) } + + async fn get_proposal( + &self, + proposal_id: u64, + ) -> crate::coconut::error::Result { + Ok(self.0.read().await.nymd.get_proposal(proposal_id).await?) + } + + async fn propose_release_funds( + &self, + title: String, + blinded_serial_number: String, + voucher_value: u128, + fee: Option, + ) -> Result { + let res = self + .0 + .read() + .await + .nymd + .propose_release_funds(title, blinded_serial_number, voucher_value, fee) + .await?; + let proposal_id = u64::from_str( + &find_attribute(&res.logs, "wasm", "proposal_id") + .ok_or_else(|| { + CoconutError::InternalError("No attribute with proposal_id as key".to_string()) + })? + .value, + ) + .map_err(|_| { + CoconutError::InternalError("proposal_id could not be parsed to u64".to_string()) + })?; + + Ok(proposal_id) + } + + async fn vote_proposal( + &self, + proposal_id: u64, + vote_yes: bool, + fee: Option, + ) -> Result<(), CoconutError> { + self.0 + .read() + .await + .nymd + .vote_proposal(proposal_id, vote_yes, fee) + .await?; + Ok(()) + } + + async fn execute_proposal( + &self, + proposal_id: u64, + fee: Option, + ) -> Result<(), CoconutError> { + self.0 + .read() + .await + .nymd + .execute_proposal(proposal_id, fee) + .await?; + Ok(()) + } } diff --git a/validator-api/src/rewarded_set_updater/error.rs b/validator-api/src/rewarded_set_updater/error.rs index 3c866a9c98..20f0edc277 100644 --- a/validator-api/src/rewarded_set_updater/error.rs +++ b/validator-api/src/rewarded_set_updater/error.rs @@ -8,9 +8,6 @@ use validator_client::ValidatorClientError; #[derive(Debug, Error)] pub enum RewardingError { - #[error("Could not distribute rewards as the contract address was unspecified")] - UnspecifiedContractAddress, - // #[error("There were no mixnodes to reward (network is dead)")] // NoMixnodesToReward, #[error("Failed to execute the smart contract - {0}")]