Compare commits
11 Commits
create
...
test_ephemera
| Author | SHA1 | Date | |
|---|---|---|---|
| 28e330821e | |||
| f46ab0dc68 | |||
| 7e0d016375 | |||
| 13c6296c3f | |||
| 2f29e33f1e | |||
| 8325001e8c | |||
| 666c11cc28 | |||
| 04f910fbf3 | |||
| 315652b26a | |||
| 56a5d847c4 | |||
| 29f95febe9 |
Generated
+2971
-116
File diff suppressed because it is too large
Load Diff
+14
-11
@@ -34,6 +34,7 @@ members = [
|
||||
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
|
||||
"common/cosmwasm-smart-contracts/coconut-dkg",
|
||||
"common/cosmwasm-smart-contracts/contracts-common",
|
||||
"common/cosmwasm-smart-contracts/ephemera",
|
||||
"common/cosmwasm-smart-contracts/group-contract",
|
||||
"common/cosmwasm-smart-contracts/mixnet-contract",
|
||||
"common/cosmwasm-smart-contracts/multisig-contract",
|
||||
@@ -117,19 +118,21 @@ anyhow = "1.0.71"
|
||||
async-trait = "0.1.64"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-controllers = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw-utils = "=0.13.4"
|
||||
cw2 = { version = "=0.13.4" }
|
||||
cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
cw4 = { version = "=0.13.4" }
|
||||
cosmwasm-derive = "=1.2.5"
|
||||
cosmwasm-schema = "=1.2.5"
|
||||
cosmwasm-std = "=1.2.5"
|
||||
cosmwasm-storage = "=1.2.5"
|
||||
cosmrs = "=0.8.0"
|
||||
cw-utils = "=1.0.1"
|
||||
cw-storage-plus = "=1.0.1"
|
||||
cw2 = { version = "=1.0.1" }
|
||||
cw3 = { version = "=1.0.1" }
|
||||
cw3-fixed-multisig = { version = "=1.0.1" }
|
||||
cw4 = { version = "=1.0.1" }
|
||||
cw-controllers = { version = "=1.0.1" }
|
||||
dotenvy = "0.15.6"
|
||||
generic-array = "0.14.7"
|
||||
k256 = "0.11"
|
||||
getrandom = "0.2.10"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
|
||||
Generated
+195
-85
@@ -237,18 +237,18 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bip32"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "873faa4363bfc54c36a48321da034c92a0645a363eed34d948683ffc1706e37f"
|
||||
checksum = "b30ed1d6f8437a487a266c8293aeb95b61a23261273e3e02912cdb8b68bf798b"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"hmac 0.11.0",
|
||||
"hmac 0.12.1",
|
||||
"k256",
|
||||
"once_cell",
|
||||
"pbkdf2",
|
||||
"rand_core 0.6.4",
|
||||
"ripemd160",
|
||||
"sha2 0.9.9",
|
||||
"ripemd",
|
||||
"sha2 0.10.6",
|
||||
"subtle 2.4.1",
|
||||
"zeroize",
|
||||
]
|
||||
@@ -332,7 +332,6 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"generic-array 0.14.7",
|
||||
]
|
||||
|
||||
@@ -345,12 +344,6 @@ dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.5.0"
|
||||
@@ -577,9 +570,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.7.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
@@ -605,8 +598,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
|
||||
|
||||
[[package]]
|
||||
name = "cosmos-sdk-proto"
|
||||
version = "0.12.3"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8bb61f3d2224c90ea78e1fa7444787761a549170c46b6b0ed09b93f9b7e4076a"
|
||||
dependencies = [
|
||||
"prost",
|
||||
"prost-types",
|
||||
@@ -615,8 +609,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmrs"
|
||||
version = "0.7.1"
|
||||
source = "git+https://github.com/neacsu/cosmos-rust?branch=neacsu/feegrant_support#f63ded63ec13e753ebe8bdafe9dc503df265d67d"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20d5890dac07a62337e5841adb8f2074a66a962a098a48df9460f64d483beaf4"
|
||||
dependencies = [
|
||||
"bip32",
|
||||
"cosmos-sdk-proto",
|
||||
@@ -637,11 +632,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.1"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "970d1d705862179b5d7a233ae36f02f21c4ec1b8075fe60c77fc5b43471331fa"
|
||||
checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.6",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.6.4",
|
||||
@@ -650,26 +645,53 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4"
|
||||
checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773"
|
||||
dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0"
|
||||
name = "cosmwasm-schema"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195"
|
||||
checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc"
|
||||
dependencies = [
|
||||
"cosmwasm-schema-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema-derive"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"derivative",
|
||||
"forward_ref",
|
||||
"hex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde-json-wasm 0.5.1",
|
||||
"sha2 0.10.6",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
@@ -783,9 +805,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.3.2"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
|
||||
checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
|
||||
dependencies = [
|
||||
"generic-array 0.14.7",
|
||||
"rand_core 0.6.4",
|
||||
@@ -867,10 +889,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-controllers"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
|
||||
checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
@@ -881,9 +904,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a"
|
||||
checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
@@ -892,22 +915,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-utils"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b"
|
||||
checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
"schemars",
|
||||
"semver 1.0.17",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw2"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1"
|
||||
checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
@@ -915,11 +942,12 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw3"
|
||||
version = "0.13.4"
|
||||
name = "cw20"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b"
|
||||
checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"schemars",
|
||||
@@ -927,11 +955,27 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw4"
|
||||
version = "0.13.4"
|
||||
name = "cw3"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd"
|
||||
checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"cw20",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
@@ -953,11 +997,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
|
||||
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derivative"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1050,9 +1106,9 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.13.4"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9"
|
||||
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
@@ -1108,16 +1164,18 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.11.12"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6"
|
||||
checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"der",
|
||||
"ff 0.11.1",
|
||||
"digest 0.10.6",
|
||||
"ff 0.12.1",
|
||||
"generic-array 0.14.7",
|
||||
"group 0.11.0",
|
||||
"group 0.12.1",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle 2.4.1",
|
||||
@@ -1232,6 +1290,16 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.26"
|
||||
@@ -1524,6 +1592,17 @@ dependencies = [
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
|
||||
dependencies = [
|
||||
"ff 0.12.1",
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.19"
|
||||
@@ -1978,15 +2057,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.10.4"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
|
||||
checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sec1",
|
||||
"sha2 0.9.9",
|
||||
"sha2 0.10.6",
|
||||
"sha3",
|
||||
]
|
||||
|
||||
@@ -2484,6 +2562,17 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway-client"
|
||||
version = "0.1.0"
|
||||
@@ -2540,6 +2629,8 @@ dependencies = [
|
||||
name = "nym-group-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cw-controllers",
|
||||
"cw4",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -2556,7 +2647,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde-json-wasm 0.4.1",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"time",
|
||||
@@ -2566,7 +2657,9 @@ dependencies = [
|
||||
name = "nym-multisig-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw3",
|
||||
"cw4",
|
||||
@@ -2879,6 +2972,7 @@ dependencies = [
|
||||
"nym-coconut-interface",
|
||||
"nym-config",
|
||||
"nym-contracts-common",
|
||||
"nym-ephemera-common",
|
||||
"nym-group-contract-common",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
@@ -3081,11 +3175,11 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.9.0"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05894bce6a1ba4be299d0c5f29563e08af2bc18bb7d48313113bed71e904739"
|
||||
checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
|
||||
dependencies = [
|
||||
"crypto-mac 0.11.1",
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3210,13 +3304,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0"
|
||||
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3545,12 +3638,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.1.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525"
|
||||
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"hmac 0.11.0",
|
||||
"hmac 0.12.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3569,6 +3662,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ripemd"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ripemd160"
|
||||
version = "0.9.1"
|
||||
@@ -3738,10 +3840,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
|
||||
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array 0.14.7",
|
||||
"pkcs8",
|
||||
@@ -3814,6 +3917,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-wasm-bindgen"
|
||||
version = "0.5.0"
|
||||
@@ -3949,14 +4061,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.9.1"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.6",
|
||||
"keccak",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3970,11 +4080,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.4.0"
|
||||
version = "1.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788"
|
||||
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.6",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -4044,9 +4154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27"
|
||||
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
@@ -4331,9 +4441,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint"
|
||||
version = "0.23.7"
|
||||
version = "0.23.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949"
|
||||
checksum = "a199518e0366ba0aeb0d0e0a59dbd99ea0bdc14280f414ecc758c9228a454ad8"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -4362,9 +4472,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-config"
|
||||
version = "0.23.7"
|
||||
version = "0.23.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6c56ee93f4e9b7e7daba86d171f44572e91b741084384d0ae00df7991873dfd"
|
||||
checksum = "c6d8f6a64ae3b59ea3c73efad727271ee085b544b817d7f46901817ca6bb1773"
|
||||
dependencies = [
|
||||
"flex-error",
|
||||
"serde",
|
||||
@@ -4376,9 +4486,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-proto"
|
||||
version = "0.23.7"
|
||||
version = "0.23.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874"
|
||||
checksum = "1b303d6387aaea38cea7ef924476d1f798573044e7b4f6ddd1166ac5184b2281"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"flex-error",
|
||||
@@ -4394,9 +4504,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tendermint-rpc"
|
||||
version = "0.23.7"
|
||||
version = "0.23.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13e63f57ee05a1e927887191c76d1b139de9fa40c180b9f8727ee44377242a6"
|
||||
checksum = "3036f0b65baa11e767dabd22a0663e842b595b0a1903f149b7b8b1e09b2b443d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
||||
@@ -13,6 +13,7 @@ colored = "2.0"
|
||||
|
||||
nym-coconut-dkg-common = { path = "../../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-contracts-common = { path = "../../cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-ephemera-common = { path = "../../cosmwasm-smart-contracts/ephemera" }
|
||||
nym-mixnet-contract-common = { path = "../../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
@@ -41,7 +42,7 @@ nym-api-requests = { path = "../../../nym-api/nym-api-requests" }
|
||||
async-trait = { workspace = true, optional = true }
|
||||
bip39 = { workspace = true, features = ["rand"], optional = true }
|
||||
nym-config = { path = "../../config", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
cosmrs = { workspace = true, features = ["rpc", "bip32", "cosmwasm"], optional = true }
|
||||
# note that this has the same version as used by cosmrs
|
||||
eyre = { version = "0.6", optional = true }
|
||||
cw3 = { workspace = true, optional = true }
|
||||
@@ -55,7 +56,7 @@ cosmwasm-std = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
bip39 = { workspace = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32"] }
|
||||
cosmrs = { workspace = true, features = ["rpc", "bip32"] }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ pub use nym_mixnet_contract_common::{
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use crate::nyxd::traits::{DkgQueryClient, MixnetQueryClient};
|
||||
use crate::nyxd::traits::{DkgQueryClient, EphemeraQueryClient, MixnetQueryClient};
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use crate::nyxd::{self, CosmWasmClient, NyxdClient, QueryNyxdClient, SigningNyxdClient};
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
@@ -29,6 +29,8 @@ use nym_coconut_dkg_common::{types::EpochId, verification_key::ContractVKShare};
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use nym_coconut_interface::Base58;
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
#[cfg(feature = "nyxd-client")]
|
||||
use nym_mixnet_contract_common::{
|
||||
families::{Family, FamilyHead},
|
||||
mixnode::MixNodeBond,
|
||||
@@ -569,6 +571,26 @@ impl<C> Client<C> {
|
||||
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
pub async fn get_all_ephemera_peers(&self) -> Result<Vec<JsonPeerInfo>, ValidatorClientError>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
let mut peers = Vec::new();
|
||||
let mut start_after = None;
|
||||
loop {
|
||||
let mut paged_response = self.get_peers_paged(start_after.take(), None).await?;
|
||||
peers.append(&mut paged_response.peers);
|
||||
|
||||
if let Some(start_after_res) = paged_response.start_next_after {
|
||||
start_after = Some(start_after_res.to_string())
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(peers)
|
||||
}
|
||||
}
|
||||
|
||||
// validator-api wrappers
|
||||
|
||||
@@ -127,7 +127,7 @@ impl GasAdjustable for Gas {
|
||||
mod sealed {
|
||||
use cosmrs::tx::{self, Gas};
|
||||
use cosmrs::Coin as CosmosCoin;
|
||||
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
|
||||
use cosmrs::{AccountId, Denom as CosmosDenom};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
|
||||
@@ -144,29 +144,11 @@ mod sealed {
|
||||
}
|
||||
}
|
||||
|
||||
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
|
||||
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
|
||||
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
|
||||
val.to_string().parse().unwrap()
|
||||
}
|
||||
|
||||
// at the time of writing it the current cosmrs' Decimal is extremely limited...
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "CosmosDecimal")]
|
||||
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
|
||||
|
||||
impl From<Decimal> for CosmosDecimal {
|
||||
fn from(val: Decimal) -> Self {
|
||||
val.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
struct Coin {
|
||||
#[serde(with = "Denom")]
|
||||
denom: CosmosDenom,
|
||||
#[serde(with = "Decimal")]
|
||||
amount: CosmosDecimal,
|
||||
amount: u128,
|
||||
}
|
||||
|
||||
impl From<Coin> for CosmosCoin {
|
||||
|
||||
@@ -39,7 +39,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::{self, Gas};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
|
||||
pub use cosmrs::{bip32, AccountId, Denom};
|
||||
use cosmwasm_std::Addr;
|
||||
pub use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
@@ -67,6 +67,7 @@ pub struct Config {
|
||||
pub(crate) group_contract_address: Option<AccountId>,
|
||||
pub(crate) multisig_contract_address: Option<AccountId>,
|
||||
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
|
||||
pub(crate) ephemera_contract_address: Option<AccountId>,
|
||||
pub(crate) service_provider_contract_address: Option<AccountId>,
|
||||
pub(crate) name_service_contract_address: Option<AccountId>,
|
||||
// TODO: add this in later commits
|
||||
@@ -133,6 +134,10 @@ impl Config {
|
||||
details.contracts.coconut_dkg_contract_address.as_ref(),
|
||||
prefix,
|
||||
)?,
|
||||
ephemera_contract_address: Self::parse_optional_account(
|
||||
details.contracts.ephemera_contract_address.as_ref(),
|
||||
prefix,
|
||||
)?,
|
||||
service_provider_contract_address: Self::parse_optional_account(
|
||||
details
|
||||
.contracts
|
||||
@@ -263,6 +268,10 @@ impl<C> NyxdClient<C> {
|
||||
self.config.service_provider_contract_address = Some(address);
|
||||
}
|
||||
|
||||
pub fn set_ephemera_contract_address(&mut self, address: AccountId) {
|
||||
self.config.ephemera_contract_address = Some(address);
|
||||
}
|
||||
|
||||
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
|
||||
// note: what unwrap is doing here is just moving a failure that would have normally
|
||||
// occurred in `connect` when attempting to parse an empty address,
|
||||
@@ -321,6 +330,14 @@ impl<C> NyxdClient<C> {
|
||||
self.config.coconut_dkg_contract_address.as_ref().unwrap()
|
||||
}
|
||||
|
||||
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
|
||||
// note: what unwrap is doing here is just moving a failure that would have normally
|
||||
// occurred in `connect` when attempting to parse an empty address,
|
||||
// so it's not introducing new source of failure (just moves it)
|
||||
pub fn ephemera_contract_address(&self) -> &AccountId {
|
||||
self.config.ephemera_contract_address.as_ref().unwrap()
|
||||
}
|
||||
|
||||
// The service provider directory contract is optional, so we return an Option not a Result
|
||||
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config.service_provider_contract_address.as_ref()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{CosmWasmClient, NyxdClient};
|
||||
use async_trait::async_trait;
|
||||
use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg;
|
||||
use nym_ephemera_common::peers::PagedPeerResponse;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[async_trait]
|
||||
pub trait EphemeraQueryClient {
|
||||
async fn query_ephemera_contract<T>(&self, query: EphemeraQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_peers_paged(
|
||||
&self,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedPeerResponse, NyxdError> {
|
||||
let request = EphemeraQueryMsg::GetPeers { start_after, limit };
|
||||
self.query_ephemera_contract(request).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> EphemeraQueryClient for NyxdClient<C>
|
||||
where
|
||||
C: CosmWasmClient + Send + Sync,
|
||||
{
|
||||
async fn query_ephemera_contract<T>(&self, query: EphemeraQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.client
|
||||
.query_contract_smart(self.ephemera_contract_address(), &query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> EphemeraQueryClient for crate::Client<C>
|
||||
where
|
||||
C: CosmWasmClient + Sync + Send,
|
||||
{
|
||||
async fn query_ephemera_contract<T>(&self, query: EphemeraQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.nyxd.query_ephemera_contract(query).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::cosmwasm_client::types::ExecuteResult;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
|
||||
use async_trait::async_trait;
|
||||
use nym_ephemera_common::msg::ExecuteMsg as EphemeraExecuteMsg;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
#[async_trait]
|
||||
pub trait EphemeraSigningClient {
|
||||
async fn register_as_peer(
|
||||
&self,
|
||||
peer_info: JsonPeerInfo,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> EphemeraSigningClient for NyxdClient<C>
|
||||
where
|
||||
C: SigningCosmWasmClient + Send + Sync,
|
||||
{
|
||||
async fn register_as_peer(
|
||||
&self,
|
||||
peer_info: JsonPeerInfo,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let req = EphemeraExecuteMsg::RegisterPeer { peer_info };
|
||||
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.ephemera_contract_address(),
|
||||
&req,
|
||||
fee.unwrap_or_default(),
|
||||
format!("registering {} as an ephemera peer", self.address()),
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
mod coconut_bandwidth_query_client;
|
||||
mod dkg_query_client;
|
||||
mod ephemera_query_client;
|
||||
mod ephemera_signing_client;
|
||||
mod group_query_client;
|
||||
mod mixnet_query_client;
|
||||
mod multisig_query_client;
|
||||
@@ -24,6 +26,7 @@ mod name_service_signing_client;
|
||||
|
||||
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
|
||||
pub use dkg_query_client::DkgQueryClient;
|
||||
pub use ephemera_query_client::EphemeraQueryClient;
|
||||
pub use group_query_client::GroupQueryClient;
|
||||
pub use mixnet_query_client::MixnetQueryClient;
|
||||
pub use multisig_query_client::MultisigQueryClient;
|
||||
@@ -33,6 +36,7 @@ pub use vesting_query_client::VestingQueryClient;
|
||||
|
||||
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
|
||||
pub use dkg_signing_client::DkgSigningClient;
|
||||
pub use ephemera_signing_client::EphemeraSigningClient;
|
||||
pub use mixnet_signing_client::MixnetSigningClient;
|
||||
pub use multisig_signing_client::MultisigSigningClient;
|
||||
pub use name_service_signing_client::NameServiceSigningClient;
|
||||
|
||||
@@ -14,7 +14,7 @@ clap = { version = "4.0", features = ["derive"] }
|
||||
cw-utils = { workspace = true }
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
k256 = { workspace = true, features = ["ecdsa", "sha256"] }
|
||||
log = { workspace = true }
|
||||
rand = {version = "0.6", features = ["std"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@@ -25,7 +25,7 @@ toml = "0.5.6"
|
||||
url = "2.2"
|
||||
tap = "1"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
|
||||
|
||||
@@ -56,6 +56,8 @@ pub async fn generate(args: Args) {
|
||||
.expect("threshold can't be converted to Decimal"),
|
||||
},
|
||||
max_voting_period: Duration::Time(args.max_voting_period),
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
coconut_bandwidth_contract_address: coconut_bandwidth_contract_address.to_string(),
|
||||
coconut_dkg_contract_address: coconut_dkg_contract_address.to_string(),
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ pub mod helpers;
|
||||
pub mod legacy_helpers;
|
||||
|
||||
pub const NYM_DIR: &str = ".nym";
|
||||
pub const DEFAULT_NYM_APIS_DIR: &str = "nym-api";
|
||||
pub const DEFAULT_CONFIG_DIR: &str = "config";
|
||||
pub const DEFAULT_DATA_DIR: &str = "data";
|
||||
pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml";
|
||||
|
||||
@@ -14,7 +14,7 @@ pub struct InstantiateMsg {
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DepositFunds { data: DepositData },
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::msg::ExecuteMsg;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct SpendCredentialData {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
@@ -43,7 +43,7 @@ pub enum SpendCredentialStatus {
|
||||
Spent,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredential {
|
||||
funds: Coin,
|
||||
blinded_serial_number: String,
|
||||
@@ -74,7 +74,7 @@ impl SpendCredential {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedSpendCredentialResponse {
|
||||
pub spend_credentials: Vec<SpendCredential>,
|
||||
pub per_page: usize,
|
||||
@@ -95,7 +95,7 @@ impl PagedSpendCredentialResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct SpendCredentialResponse {
|
||||
pub spend_credential: Option<SpendCredential>,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub type Nonce = u32;
|
||||
|
||||
// define this type explicitly for [hopefully] better usability
|
||||
// (so you wouldn't need to worry about whether you should use bytes, bs58, etc.)
|
||||
#[derive(Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct MessageSignature(Vec<u8>);
|
||||
|
||||
impl MessageSignature {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common" }
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod msg;
|
||||
pub mod peers;
|
||||
pub mod types;
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::JsonPeerInfo;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct InstantiateMsg {
|
||||
pub group_addr: String,
|
||||
pub mix_denom: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
RegisterPeer { peer_info: JsonPeerInfo },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetPeers {
|
||||
limit: Option<u32>,
|
||||
start_after: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct MigrateMsg {}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::types::JsonPeerInfo;
|
||||
use cosmwasm_std::Addr;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PagedPeerResponse {
|
||||
pub peers: Vec<JsonPeerInfo>,
|
||||
pub per_page: usize,
|
||||
pub start_next_after: Option<Addr>,
|
||||
}
|
||||
|
||||
impl PagedPeerResponse {
|
||||
pub fn new(peers: Vec<JsonPeerInfo>, per_page: usize, start_next_after: Option<Addr>) -> Self {
|
||||
PagedPeerResponse {
|
||||
peers,
|
||||
per_page,
|
||||
start_next_after,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema)]
|
||||
pub struct JsonPeerInfo {
|
||||
/// The cosmos address of the peer, used in interacting with the chain.
|
||||
pub cosmos_address: Addr,
|
||||
/// The TCP/IP address of the peer.
|
||||
/// Expected formats:
|
||||
/// 1. `<IP>:<PORT>`
|
||||
/// 2. `/ip4/<IP>/tcp/<PORT>` - this is the format used by libp2p multiaddr
|
||||
pub ip_address: String,
|
||||
///Serialized public key.
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
impl JsonPeerInfo {
|
||||
#[must_use]
|
||||
pub fn new(cosmos_address: Addr, ip_address: String, public_key: String) -> Self {
|
||||
Self {
|
||||
cosmos_address,
|
||||
ip_address,
|
||||
public_key,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cw4::Member;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
/// The admin is the only account that can update the group state.
|
||||
/// Omit it to make the group immutable.
|
||||
@@ -15,8 +9,7 @@ pub struct InstantiateMsg {
|
||||
pub members: Vec<Member>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
/// Change the admin
|
||||
UpdateAdmin { admin: Option<String> },
|
||||
@@ -32,23 +25,24 @@ pub enum ExecuteMsg {
|
||||
RemoveHook { addr: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
/// Return AdminResponse
|
||||
#[returns(cw_controllers::AdminResponse)]
|
||||
Admin {},
|
||||
/// Return TotalWeightResponse
|
||||
TotalWeight {},
|
||||
/// Returns MembersListResponse
|
||||
#[returns(cw4::TotalWeightResponse)]
|
||||
TotalWeight { at_height: Option<u64> },
|
||||
#[returns(cw4::MemberListResponse)]
|
||||
ListMembers {
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns MemberResponse
|
||||
#[returns(cw4::MemberResponse)]
|
||||
Member {
|
||||
addr: String,
|
||||
at_height: Option<u64>,
|
||||
},
|
||||
/// Shows all registered hooks. Returns HooksResponse.
|
||||
/// Shows all registered hooks.
|
||||
#[returns(cw_controllers::HooksResponse)]
|
||||
Hooks {},
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ pub fn generate_owner_storage_subkey(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct Delegation {
|
||||
/// Address of the owner of this delegation.
|
||||
pub owner: Addr,
|
||||
@@ -114,7 +114,7 @@ impl Delegation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixNodeDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<OwnerProxySubKey>,
|
||||
@@ -129,7 +129,7 @@ impl PagedMixNodeDelegationsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedDelegatorDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
|
||||
@@ -147,7 +147,7 @@ impl PagedDelegatorDelegationsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeDelegationResponse {
|
||||
pub delegation: Option<Delegation>,
|
||||
pub mixnode_still_bonded: bool,
|
||||
@@ -162,7 +162,7 @@ impl MixNodeDelegationResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedAllDelegationsResponse {
|
||||
pub delegations: Vec<Delegation>,
|
||||
pub start_next_after: Option<StorageKey>,
|
||||
|
||||
@@ -23,7 +23,7 @@ pub struct Gateway {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBond {
|
||||
pub pledge_amount: Coin,
|
||||
pub owner: Addr,
|
||||
@@ -132,7 +132,7 @@ impl GatewayConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedGatewayResponse {
|
||||
pub nodes: Vec<GatewayBond>,
|
||||
pub per_page: usize,
|
||||
@@ -153,13 +153,13 @@ impl PagedGatewayResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub gateway: Option<GatewayBond>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct GatewayBondResponse {
|
||||
pub identity: IdentityKey,
|
||||
pub gateway: Option<GatewayBond>,
|
||||
|
||||
@@ -489,7 +489,7 @@ impl CurrentIntervalResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PendingEpochEventsResponse {
|
||||
pub seconds_until_executable: i64,
|
||||
pub events: Vec<PendingEpochEvent>,
|
||||
@@ -510,7 +510,7 @@ impl PendingEpochEventsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct PendingIntervalEventsResponse {
|
||||
pub seconds_until_executable: i64,
|
||||
pub events: Vec<PendingIntervalEvent>,
|
||||
|
||||
@@ -33,7 +33,7 @@ impl RewardedSetNodeStatus {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeDetails {
|
||||
pub bond_information: MixNodeBond,
|
||||
pub rewarding_details: MixNodeRewarding,
|
||||
@@ -86,7 +86,7 @@ impl MixNodeDetails {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeRewarding {
|
||||
/// Information provided by the operator that influence the cost function.
|
||||
pub cost_params: MixNodeCostParams,
|
||||
@@ -465,7 +465,7 @@ impl MixNodeRewarding {
|
||||
}
|
||||
|
||||
// operator information + data assigned by the contract(s)
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeBond {
|
||||
/// Unique id assigned to the bonded mixnode.
|
||||
pub mix_id: MixId,
|
||||
@@ -559,7 +559,7 @@ pub struct MixNode {
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixNodeCostParams {
|
||||
pub profit_margin_percent: Percent,
|
||||
|
||||
@@ -686,7 +686,7 @@ impl MixNodeConfigUpdate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixnodeBondsResponse {
|
||||
pub nodes: Vec<MixNodeBond>,
|
||||
pub per_page: usize,
|
||||
@@ -703,7 +703,7 @@ impl PagedMixnodeBondsResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct PagedMixnodesDetailsResponse {
|
||||
pub nodes: Vec<MixNodeDetails>,
|
||||
pub per_page: usize,
|
||||
@@ -745,19 +745,19 @@ impl PagedUnbondedMixnodesResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixOwnershipResponse {
|
||||
pub address: Addr,
|
||||
pub mixnode_details: Option<MixNodeDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeDetailsResponse {
|
||||
pub mix_id: MixId,
|
||||
pub mixnode_details: Option<MixNodeDetails>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
|
||||
pub struct MixnodeRewardingDetailsResponse {
|
||||
pub mix_id: MixId,
|
||||
pub rewarding_details: Option<MixNodeRewarding>,
|
||||
|
||||
@@ -7,19 +7,19 @@ use crate::{BlockHeight, EpochEventId, IntervalEventId, MixId};
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingEpochEvent {
|
||||
pub id: EpochEventId,
|
||||
pub event: PendingEpochEventData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingEpochEventData {
|
||||
pub created_at: BlockHeight,
|
||||
pub kind: PendingEpochEventKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PendingEpochEventKind {
|
||||
// can't just pass the `Delegation` struct here as it's impossible to determine
|
||||
// `cumulative_reward_ratio` ahead of time
|
||||
@@ -68,19 +68,19 @@ impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingIntervalEvent {
|
||||
pub id: IntervalEventId,
|
||||
pub event: PendingIntervalEventData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PendingIntervalEventData {
|
||||
pub created_at: BlockHeight,
|
||||
pub kind: PendingIntervalEventKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum PendingIntervalEventKind {
|
||||
ChangeMixCostParams {
|
||||
mix_id: MixId,
|
||||
|
||||
@@ -35,7 +35,7 @@ pub struct RewardDistribution {
|
||||
pub delegates: Decimal,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct PendingRewardResponse {
|
||||
pub amount_staked: Option<Coin>,
|
||||
pub amount_earned: Option<Coin>,
|
||||
@@ -46,7 +46,7 @@ pub struct PendingRewardResponse {
|
||||
pub mixnode_still_fully_bonded: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct EstimatedCurrentEpochRewardResponse {
|
||||
pub original_stake: Option<Coin>,
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ pub type EpochEventId = u32;
|
||||
pub type IntervalEventId = u32;
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq, Eq)]
|
||||
pub struct LayerAssignment {
|
||||
mix_id: MixId,
|
||||
layer: Layer,
|
||||
@@ -118,7 +118,7 @@ impl Index<Layer> for LayerDistribution {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContractState {
|
||||
pub owner: Addr, // only the owner account can update state
|
||||
pub rewarding_validator_address: Addr,
|
||||
@@ -130,7 +130,7 @@ pub struct ContractState {
|
||||
pub params: ContractStateParams,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct ContractStateParams {
|
||||
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
|
||||
pub minimum_mixnode_delegation: Option<Coin>,
|
||||
|
||||
@@ -9,6 +9,8 @@ edition = "2021"
|
||||
cw-utils = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw4 = { workspace= true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use cw_utils::ThresholdError;
|
||||
use cw3::DepositError;
|
||||
use cw_utils::{PaymentError, ThresholdError};
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -17,9 +18,6 @@ pub enum ContractError {
|
||||
#[error("Group contract invalid address '{addr}'")]
|
||||
InvalidGroup { addr: String },
|
||||
|
||||
#[error("Coconut bandwidth contract address not found")]
|
||||
InvalidCoconutBandwidth {},
|
||||
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized {},
|
||||
|
||||
@@ -43,4 +41,10 @@ pub enum ContractError {
|
||||
|
||||
#[error("Cannot close completed or passed proposals")]
|
||||
WrongCloseStatus {},
|
||||
|
||||
#[error("{0}")]
|
||||
Payment(#[from] PaymentError),
|
||||
|
||||
#[error("{0}")]
|
||||
Deposit(#[from] DepositError),
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod error;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cosmwasm_std::{CosmosMsg, Empty};
|
||||
use cw3::Vote;
|
||||
use cw3::{UncheckedDepositInfo, Vote};
|
||||
use cw4::MemberChangedHookMsg;
|
||||
use cw_utils::{Duration, Expiration, Threshold};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
use crate::state::Executor;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg {
|
||||
// this is the group contract that contains the member list
|
||||
pub group_addr: String,
|
||||
@@ -17,11 +17,15 @@ pub struct InstantiateMsg {
|
||||
pub coconut_dkg_contract_address: String,
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
// who is able to execute passed proposals
|
||||
// None means that anyone can execute
|
||||
pub executor: Option<Executor>,
|
||||
/// The cost of creating a proposal (if any).
|
||||
pub proposal_deposit: Option<UncheckedDepositInfo>,
|
||||
}
|
||||
|
||||
// TODO: add some T variants? Maybe good enough as fixed Empty for now
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
Propose {
|
||||
title: String,
|
||||
@@ -45,41 +49,44 @@ pub enum ExecuteMsg {
|
||||
}
|
||||
|
||||
// We can also add this as a cw3 extension
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
/// Return ThresholdResponse
|
||||
#[returns(cw_utils::ThresholdResponse)]
|
||||
Threshold {},
|
||||
/// Returns ProposalResponse
|
||||
#[returns(cw3::ProposalResponse)]
|
||||
Proposal { proposal_id: u64 },
|
||||
/// Returns ProposalListResponse
|
||||
#[returns(cw3::ProposalListResponse)]
|
||||
ListProposals {
|
||||
start_after: Option<u64>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns ProposalListResponse
|
||||
#[returns(cw3::ProposalListResponse)]
|
||||
ReverseProposals {
|
||||
start_before: Option<u64>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns VoteResponse
|
||||
#[returns(cw3::VoteResponse)]
|
||||
Vote { proposal_id: u64, voter: String },
|
||||
/// Returns VoteListResponse
|
||||
#[returns(cw3::VoteListResponse)]
|
||||
ListVotes {
|
||||
proposal_id: u64,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Returns VoterInfo
|
||||
#[returns(cw3::VoterResponse)]
|
||||
Voter { address: String },
|
||||
/// Returns VoterListResponse
|
||||
#[returns(cw3::VoterListResponse)]
|
||||
ListVoters {
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
},
|
||||
/// Gets the current configuration.
|
||||
#[returns(crate::state::Config)]
|
||||
Config {},
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
|
||||
#[cw_serde]
|
||||
pub struct MigrateMsg {
|
||||
pub coconut_bandwidth_address: String,
|
||||
pub coconut_dkg_address: String,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, QuerierWrapper};
|
||||
use cw3::DepositInfo;
|
||||
use cw4::Cw4Contract;
|
||||
use cw_storage_plus::Item;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
|
||||
use crate::error::ContractError;
|
||||
|
||||
/// Defines who is able to execute proposals once passed
|
||||
#[cw_serde]
|
||||
pub enum Executor {
|
||||
/// Any member of the voting group, even with 0 points
|
||||
Member,
|
||||
/// Only the given address
|
||||
Only(Addr),
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
pub struct Config {
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
// Total weight and voters are queried from this contract
|
||||
pub group_addr: Cw4Contract,
|
||||
pub coconut_bandwidth_addr: Addr,
|
||||
pub coconut_dkg_addr: Addr,
|
||||
// who is able to execute passed proposals
|
||||
// None means that anyone can execute
|
||||
pub executor: Option<Executor>,
|
||||
/// The price, if any, of creating a new proposal.
|
||||
pub proposal_deposit: Option<DepositInfo>,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
// Executor can be set in 3 ways:
|
||||
// - Member: any member of the voting group is authorized
|
||||
// - Only: only passed address is authorized
|
||||
// - None: Everyone are authorized
|
||||
pub fn authorize(&self, querier: &QuerierWrapper, sender: &Addr) -> Result<(), ContractError> {
|
||||
if let Some(executor) = &self.executor {
|
||||
match executor {
|
||||
Executor::Member => {
|
||||
self.group_addr
|
||||
.is_member(querier, sender, None)?
|
||||
.ok_or(ContractError::Unauthorized {})?;
|
||||
}
|
||||
Executor::Only(addr) => {
|
||||
if addr != sender {
|
||||
return Err(ContractError::Unauthorized {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// unique items
|
||||
pub const CONFIG: Item<Config> = Item::new("config");
|
||||
@@ -28,7 +28,7 @@ pub enum Period {
|
||||
After,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct PledgeData {
|
||||
pub amount: Coin,
|
||||
pub block_time: Timestamp,
|
||||
@@ -49,7 +49,7 @@ impl PledgeData {
|
||||
}
|
||||
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub enum PledgeCap {
|
||||
Percent(Percent),
|
||||
Absolute(Uint128), // This has to be in unym
|
||||
@@ -77,7 +77,7 @@ impl Default for PledgeCap {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct OriginalVestingResponse {
|
||||
pub amount: Coin,
|
||||
pub number_of_periods: usize,
|
||||
|
||||
@@ -55,7 +55,7 @@ impl VestingSpecification {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
// Families
|
||||
|
||||
@@ -7,7 +7,7 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = { workspace = true }
|
||||
thiserror = "1.0"
|
||||
|
||||
# I guess temporarily until we get serde support in coconut up and running
|
||||
|
||||
@@ -27,7 +27,7 @@ thiserror = "1.0.37"
|
||||
zeroize = { workspace = true, optional = true, features = ["zeroize_derive"] }
|
||||
|
||||
# internal
|
||||
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
|
||||
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0", default-features = false }
|
||||
nym-pemstore = { path = "../../common/pemstore", version = "0.3.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
@@ -38,3 +38,4 @@ serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serd
|
||||
asymmetric = ["x25519-dalek", "ed25519-dalek", "zeroize"]
|
||||
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
|
||||
symmetric = ["aes", "ctr", "cipher", "generic-array"]
|
||||
wasm-contract = ["nym-sphinx-types/wasm-contract"]
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bip32 = "0.3.0"
|
||||
k256 = "0.10.4"
|
||||
bip32 = "0.4.0"
|
||||
k256 = { workspace = true }
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
thiserror = "1"
|
||||
@@ -30,6 +30,7 @@ pub struct NymContracts {
|
||||
pub group_contract_address: Option<String>,
|
||||
pub multisig_contract_address: Option<String>,
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
pub ephemera_contract_address: Option<String>,
|
||||
pub service_provider_directory_contract_address: Option<String>,
|
||||
pub name_service_contract_address: Option<String>,
|
||||
}
|
||||
@@ -129,6 +130,9 @@ impl NymNetworkDetails {
|
||||
.with_coconut_dkg_contract(Some(
|
||||
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
|
||||
))
|
||||
.with_ephemera_contract(Some(
|
||||
var(var_names::EPHEMERA_CONTRACT_ADDRESS).expect("ephemera contract not set"),
|
||||
))
|
||||
.with_service_provider_directory_contract(get_optional_env(
|
||||
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
))
|
||||
@@ -162,6 +166,7 @@ impl NymNetworkDetails {
|
||||
coconut_dkg_contract_address: parse_optional_str(
|
||||
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
),
|
||||
ephemera_contract_address: parse_optional_str(mainnet::EPHEMERA_CONTRACT_ADDRESS),
|
||||
service_provider_directory_contract_address: None,
|
||||
name_service_contract_address: None,
|
||||
},
|
||||
@@ -246,6 +251,12 @@ impl NymNetworkDetails {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_ephemera_contract<S: Into<String>>(mut self, contract: Option<S>) -> Self {
|
||||
self.contracts.ephemera_contract_address = contract.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_service_provider_directory_contract<S: Into<String>>(
|
||||
mut self,
|
||||
|
||||
@@ -20,6 +20,7 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
|
||||
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
|
||||
@@ -87,6 +88,10 @@ pub fn export_to_env() {
|
||||
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_to_default(
|
||||
var_names::EPHEMERA_CONTRACT_ADDRESS,
|
||||
EPHEMERA_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_to_default(
|
||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||
REWARDING_VALIDATOR_ADDRESS,
|
||||
@@ -132,6 +137,10 @@ pub fn export_to_env_if_not_set() {
|
||||
var_names::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::EPHEMERA_CONTRACT_ADDRESS,
|
||||
EPHEMERA_CONTRACT_ADDRESS,
|
||||
);
|
||||
set_var_conditionally_to_default(
|
||||
var_names::REWARDING_VALIDATOR_ADDRESS,
|
||||
REWARDING_VALIDATOR_ADDRESS,
|
||||
|
||||
@@ -17,6 +17,7 @@ pub const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str = "COCONUT_BANDWIDTH_CONTRACT
|
||||
pub const GROUP_CONTRACT_ADDRESS: &str = "GROUP_CONTRACT_ADDRESS";
|
||||
pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
||||
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
|
||||
pub const EPHEMERA_CONTRACT_ADDRESS: &str = "EPHEMERA_CONTRACT_ADDRESS";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
|
||||
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
|
||||
@@ -9,5 +9,9 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
sphinx-packet = { version = "0.1.0" }
|
||||
nym-outfox = { path = "../../../nym-outfox" }
|
||||
nym-outfox = { path = "../../../nym-outfox", optional = true }
|
||||
thiserror = "1"
|
||||
|
||||
[features]
|
||||
default = ["nym-outfox"]
|
||||
wasm-contract = []
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(not(feature = "wasm-contract"))]
|
||||
pub use nym_outfox::{
|
||||
constants::MIN_PACKET_SIZE, constants::MIX_PARAMS_LEN, constants::OUTFOX_PACKET_OVERHEAD,
|
||||
error::OutfoxError,
|
||||
};
|
||||
// re-exporting types and constants available in sphinx
|
||||
#[cfg(not(feature = "wasm-contract"))]
|
||||
use nym_outfox::packet::{OutfoxPacket, OutfoxProcessedPacket};
|
||||
pub use sphinx_packet::{
|
||||
constants::{
|
||||
@@ -30,6 +32,7 @@ pub enum NymPacketError {
|
||||
Sphinx(#[from] sphinx_packet::Error),
|
||||
|
||||
#[error("Outfox error: {0}")]
|
||||
#[cfg(not(feature = "wasm-contract"))]
|
||||
Outfox(#[from] nym_outfox::error::OutfoxError),
|
||||
|
||||
#[error("{0}")]
|
||||
@@ -39,11 +42,13 @@ pub enum NymPacketError {
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum NymPacket {
|
||||
Sphinx(SphinxPacket),
|
||||
#[cfg(not(feature = "wasm-contract"))]
|
||||
Outfox(OutfoxPacket),
|
||||
}
|
||||
|
||||
pub enum NymProcessedPacket {
|
||||
Sphinx(ProcessedPacket),
|
||||
#[cfg(not(feature = "wasm-contract"))]
|
||||
Outfox(OutfoxProcessedPacket),
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmrs = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
"nyxd-client",
|
||||
|
||||
Generated
+200
-297
@@ -2,16 +2,6 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.7.5"
|
||||
@@ -19,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.3.0",
|
||||
"cipher",
|
||||
"cpufeatures",
|
||||
"ctr",
|
||||
"opaque-debug 0.3.0",
|
||||
@@ -48,12 +38,6 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -102,20 +86,6 @@ dependencies = [
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
@@ -189,30 +159,6 @@ dependencies = [
|
||||
"keystream",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20poly1305"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"chacha20",
|
||||
"cipher 0.4.4",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@@ -222,17 +168,6 @@ dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coconut-test"
|
||||
version = "0.1.0"
|
||||
@@ -260,23 +195,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.7.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd"
|
||||
checksum = "75836a10cb9654c54e77ee56da94d592923092a10b369cdb0dbd56acefc16340"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"digest 0.10.7",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.6.4",
|
||||
@@ -285,45 +214,62 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4"
|
||||
checksum = "1c9f7f0e51bfc7295f7b2664fe8513c966428642aa765dad8a74acdab5e0c773"
|
||||
dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da"
|
||||
checksum = "0f00b363610218eea83f24bbab09e1a7c3920b79f068334fdfcc62f6129ef9fc"
|
||||
dependencies = [
|
||||
"cosmwasm-schema-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-schema-derive"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae38f909b2822d32b275c9e2db9728497aa33ffe67dd463bc67c6a3b7092785c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195"
|
||||
checksum = "a49b85345e811c8e80ec55d0d091e4fcb4f00f97ab058f9be5f614c444a730cb"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"derivative",
|
||||
"forward_ref",
|
||||
"hex",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde-json-wasm 0.5.1",
|
||||
"sha2 0.10.6",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "1.0.0"
|
||||
version = "1.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5"
|
||||
checksum = "a3737a3aac48f5ed883b5b73bfb731e77feebd8fc6b43419844ec2971072164d"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
@@ -338,49 +284,6 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
@@ -389,9 +292,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.3.2"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21"
|
||||
checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
"rand_core 0.6.4",
|
||||
@@ -406,7 +309,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -436,7 +338,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
|
||||
dependencies = [
|
||||
"cipher 0.3.0",
|
||||
"cipher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -454,10 +356,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-controllers"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa"
|
||||
checksum = "91440ce8ec4f0642798bc8c8cb6b9b53c1926c6dadaf0eed267a5145cd529071"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
@@ -468,17 +371,17 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-multi-test"
|
||||
version = "0.13.4"
|
||||
version = "0.16.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e"
|
||||
checksum = "2a18afd2e201221c6d72a57f0886ef2a22151bbc9e6db7af276fde8a91081042"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"derivative",
|
||||
"itertools",
|
||||
"k256",
|
||||
"prost",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -487,9 +390,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-storage-plus"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a"
|
||||
checksum = "053a5083c258acd68386734f428a5a171b29f7d733151ae83090c6fcc9417ffa"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
@@ -498,22 +401,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw-utils"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b"
|
||||
checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw2",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw2"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1"
|
||||
checksum = "8fb70cee2cf0b4a8ff7253e6bc6647107905e8eb37208f87d54f67810faa62f8"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
@@ -521,11 +428,12 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw3"
|
||||
version = "0.13.4"
|
||||
name = "cw20"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b"
|
||||
checksum = "91666da6c7b40c8dd5ff94df655a28114efc10c79b70b4d06f13c31e37d60609"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"schemars",
|
||||
@@ -533,11 +441,45 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw3-fixed-multisig"
|
||||
version = "0.13.4"
|
||||
name = "cw20-base"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706"
|
||||
checksum = "afcd279230b08ed8afd8be5828221622bd5b9ce25d0b01d58bad626c6ce0169c"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw2",
|
||||
"cw20",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw3"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fe0b587008aa221cd2a2579a21990a28c4347dc53ad43167c68ad765f5b6efa"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"cw20",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw3-fixed-multisig"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9e2415adb201e5e89dab34edf59d7dc166bc558526de009a49ae66276c9119a"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
@@ -550,14 +492,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw3-flex-multisig"
|
||||
version = "0.13.1"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw2",
|
||||
"cw20",
|
||||
"cw20-base",
|
||||
"cw3",
|
||||
"cw3-fixed-multisig",
|
||||
"cw4",
|
||||
@@ -565,15 +508,15 @@ dependencies = [
|
||||
"nym-group-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cw4"
|
||||
version = "0.13.4"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd"
|
||||
checksum = "2c236e0bae02ce97e89235a681dd0e07d099524b369f1ef908d704db3e6b049b"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"schemars",
|
||||
@@ -582,7 +525,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cw4-group"
|
||||
version = "0.13.4"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
@@ -599,11 +542,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.5.1"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
|
||||
checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -654,9 +598,9 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.13.4"
|
||||
version = "0.14.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9"
|
||||
checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
@@ -683,7 +627,7 @@ dependencies = [
|
||||
"ed25519",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"sha2",
|
||||
"sha2 0.9.9",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -698,7 +642,7 @@ dependencies = [
|
||||
"hex",
|
||||
"rand_core 0.6.4",
|
||||
"serde",
|
||||
"sha2",
|
||||
"sha2 0.9.9",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -710,16 +654,18 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.11.12"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6"
|
||||
checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"crypto-bigint",
|
||||
"der",
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
"generic-array 0.14.6",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.4",
|
||||
"sec1",
|
||||
"subtle 2.4.1",
|
||||
@@ -778,9 +724,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.11.1"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924"
|
||||
checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.4.1",
|
||||
@@ -941,10 +887,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -974,9 +918,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.11.0"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89"
|
||||
checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.4",
|
||||
@@ -992,15 +936,6 @@ dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
@@ -1020,7 +955,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"hmac",
|
||||
"hmac 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1033,6 +968,15 @@ dependencies = [
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
@@ -1059,15 +1003,6 @@ dependencies = [
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -1083,7 +1018,7 @@ version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@@ -1123,15 +1058,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.10.4"
|
||||
version = "0.11.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d"
|
||||
checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sec1",
|
||||
"sha2",
|
||||
"sha2 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1215,15 +1149,6 @@ version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-vesting-integration-tests"
|
||||
version = "0.1.0"
|
||||
@@ -1250,16 +1175,6 @@ dependencies = [
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.2.6",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-coconut-bandwidth"
|
||||
version = "0.1.0"
|
||||
@@ -1343,10 +1258,43 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"cw-controllers",
|
||||
"cw-multi-test",
|
||||
"cw-storage-plus",
|
||||
"cw4",
|
||||
"cw4-group",
|
||||
"lazy_static",
|
||||
"nym-ephemera-common",
|
||||
"nym-group-contract-common",
|
||||
"rusty-fork",
|
||||
"schemars",
|
||||
"serde",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-ephemera-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cw-utils",
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-group-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cw-controllers",
|
||||
"cw4",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -1387,7 +1335,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"serde-json-wasm 0.4.1",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
"time",
|
||||
@@ -1397,7 +1345,9 @@ dependencies = [
|
||||
name = "nym-multisig-contract-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-schema",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus",
|
||||
"cw-utils",
|
||||
"cw3",
|
||||
"cw4",
|
||||
@@ -1436,23 +1386,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-outfox"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
"curve25519-dalek",
|
||||
"getrandom 0.2.10",
|
||||
"log",
|
||||
"rand 0.7.3",
|
||||
"rayon",
|
||||
"sphinx-packet",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
@@ -1500,7 +1433,6 @@ dependencies = [
|
||||
name = "nym-sphinx-types"
|
||||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"nym-outfox",
|
||||
"sphinx-packet",
|
||||
"thiserror",
|
||||
]
|
||||
@@ -1588,13 +1520,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0"
|
||||
checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1603,17 +1534,6 @@ version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
"opaque-debug 0.3.0",
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
@@ -1772,28 +1692,6 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
@@ -1820,12 +1718,12 @@ checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
|
||||
[[package]]
|
||||
name = "rfc6979"
|
||||
version = "0.1.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525"
|
||||
checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"hmac",
|
||||
"hmac 0.12.1",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -1926,18 +1824,13 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sec1"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1"
|
||||
checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928"
|
||||
dependencies = [
|
||||
"base16ct",
|
||||
"der",
|
||||
"generic-array 0.14.6",
|
||||
"pkcs8",
|
||||
@@ -1969,6 +1862,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16a62a1fad1e1828b24acac8f2b468971dade7b8c3c2e672bcadefefb1f8c137"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.160"
|
||||
@@ -2027,12 +1929,23 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.4.0"
|
||||
name = "sha2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788"
|
||||
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
@@ -2060,20 +1973,20 @@ dependencies = [
|
||||
"curve25519-dalek",
|
||||
"digest 0.9.0",
|
||||
"hkdf",
|
||||
"hmac",
|
||||
"hmac 0.11.0",
|
||||
"lioness",
|
||||
"log",
|
||||
"rand 0.7.3",
|
||||
"rand_distr",
|
||||
"sha2",
|
||||
"sha2 0.9.9",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27"
|
||||
checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der",
|
||||
@@ -2242,16 +2155,6 @@ dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.3.1"
|
||||
|
||||
+15
-13
@@ -3,6 +3,7 @@ members = [
|
||||
"coconut-bandwidth",
|
||||
"coconut-dkg",
|
||||
"coconut-test",
|
||||
"ephemera",
|
||||
"mixnet",
|
||||
"mixnet-vesting-integration-tests",
|
||||
"multisig/cw3-flex-multisig",
|
||||
@@ -32,16 +33,17 @@ incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[workspace.dependencies]
|
||||
cosmwasm-crypto = "=1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-controllers = "=0.13.4"
|
||||
cw-multi-test = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw-utils = "=0.13.4"
|
||||
cw2 = "=0.13.4"
|
||||
cw3 = "=0.13.4"
|
||||
cw3-fixed-multisig = "=0.13.4"
|
||||
cw4 = "=0.13.4"
|
||||
cosmwasm-crypto = "=1.2.5"
|
||||
cosmwasm-derive = "=1.2.5"
|
||||
cosmwasm-schema = "=1.2.5"
|
||||
cosmwasm-std = "=1.2.5"
|
||||
cosmwasm-storage = "=1.2.5"
|
||||
cw-controllers = "=1.0.1"
|
||||
cw-multi-test = "=0.16.4"
|
||||
cw-storage-plus = "=1.0.1"
|
||||
cw-utils = "=1.0.1"
|
||||
cw2 = "=1.0.1"
|
||||
cw3 = "=1.0.1"
|
||||
cw3-fixed-multisig = "=1.0.1"
|
||||
cw4 = "=1.0.1"
|
||||
cw20 = "=1.0.1"
|
||||
|
||||
@@ -30,7 +30,7 @@ impl<'a> IndexList<ContractVKShare> for VkShareIndex<'a> {
|
||||
pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare, VkShareIndex<'a>> {
|
||||
let indexes = VkShareIndex {
|
||||
epoch_id: MultiIndex::new(
|
||||
|d| d.epoch_id,
|
||||
|_pk, d| d.epoch_id,
|
||||
VK_SHARES_PK_NAMESPACE,
|
||||
VK_SHARES_EPOCH_ID_IDX_NAMESPACE,
|
||||
),
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{entry_point, Addr, Coin, DepsMut, Empty, Env, Response};
|
||||
use cw3_flex_multisig::state::CONFIG;
|
||||
use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper};
|
||||
use nym_multisig_contract_common::error::ContractError;
|
||||
use nym_multisig_contract_common::state::CONFIG;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ fn spend_credential_creates_proposal() {
|
||||
threshold: Threshold::AbsolutePercentage {
|
||||
percentage: Decimal::from_ratio(2u128, 3u128),
|
||||
},
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
max_voting_period: Duration::Height(1000),
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(),
|
||||
|
||||
@@ -52,6 +52,8 @@ fn dkg_proposal() {
|
||||
threshold: Threshold::AbsolutePercentage {
|
||||
percentage: Decimal::from_ratio(1u128, 1u128),
|
||||
},
|
||||
executor: None,
|
||||
proposal_deposit: None,
|
||||
max_voting_period: Duration::Time(1000),
|
||||
coconut_bandwidth_contract_address: TEST_COCONUT_BANDWIDTH_CONTRACT_ADDRESS.to_string(),
|
||||
coconut_dkg_contract_address: TEST_COCONUT_DKG_CONTRACT_ADDRESS.to_string(),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "nym-ephemera"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
nym-ephemera-common = { path = "../../common/cosmwasm-smart-contracts/ephemera" }
|
||||
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
lazy_static = "1.4"
|
||||
rusty-fork = "0.3"
|
||||
@@ -0,0 +1,68 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::peers::queries::query_peers_paged;
|
||||
use crate::peers::transactions::try_register_peer;
|
||||
use crate::state::{State, STATE};
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
};
|
||||
use cw4::Cw4Contract;
|
||||
use nym_ephemera_common::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
/// `deps` contains Storage, API and Querier
|
||||
/// `env` contains block, message and contract info
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
let group_addr = Cw4Contract(deps.api.addr_validate(&msg.group_addr).map_err(|_| {
|
||||
ContractError::InvalidGroup {
|
||||
addr: msg.group_addr.clone(),
|
||||
}
|
||||
})?);
|
||||
|
||||
let state = State {
|
||||
group_addr,
|
||||
mix_denom: msg.mix_denom,
|
||||
};
|
||||
STATE.save(deps.storage, &state)?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Handle an incoming message
|
||||
#[entry_point]
|
||||
pub fn execute(
|
||||
deps: DepsMut<'_>,
|
||||
_env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::RegisterPeer { peer_info } => try_register_peer(deps, info, peer_info),
|
||||
}
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps<'_>, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let response = match msg {
|
||||
QueryMsg::GetPeers { limit, start_after } => {
|
||||
to_binary(&query_peers_paged(deps, start_after, limit)?)?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::StdError;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Custom errors for contract failure conditions.
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error(transparent)]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("Group contract invalid address '{addr}'")]
|
||||
InvalidGroup { addr: String },
|
||||
|
||||
#[error("This sender is already registered")]
|
||||
AlreadyRegistered,
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
mod peers;
|
||||
mod state;
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod queries;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::peers::storage::{PEERS, PEERS_PAGE_DEFAULT_LIMIT, PEERS_PAGE_MAX_LIMIT};
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
use nym_ephemera_common::peers::PagedPeerResponse;
|
||||
|
||||
pub fn query_peers_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
) -> StdResult<PagedPeerResponse> {
|
||||
let limit = limit
|
||||
.unwrap_or(PEERS_PAGE_DEFAULT_LIMIT)
|
||||
.min(PEERS_PAGE_MAX_LIMIT) as usize;
|
||||
|
||||
let addr = start_after
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.transpose()?;
|
||||
|
||||
let start = addr.map(Bound::exclusive);
|
||||
|
||||
let peers = PEERS
|
||||
.range(deps.storage, start, None, Order::Ascending)
|
||||
.take(limit)
|
||||
.map(|res| res.map(|item| item.1))
|
||||
.collect::<StdResult<Vec<_>>>()?;
|
||||
|
||||
let start_next_after = peers
|
||||
.last()
|
||||
.map(|peer_info| peer_info.cosmos_address.clone());
|
||||
|
||||
Ok(PagedPeerResponse::new(peers, limit, start_next_after))
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Addr;
|
||||
use cw_storage_plus::Map;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
pub(crate) const PEERS_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const PEERS_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
|
||||
pub(crate) const PEERS: Map<'_, Addr, JsonPeerInfo> = Map::new("prs");
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::peers::storage::PEERS;
|
||||
use cosmwasm_std::{DepsMut, MessageInfo, Response};
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
|
||||
pub fn try_register_peer(
|
||||
deps: DepsMut<'_>,
|
||||
info: MessageInfo,
|
||||
peer_info: JsonPeerInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
if PEERS.may_load(deps.storage, info.sender.clone())?.is_none() {
|
||||
PEERS.save(deps.storage, info.sender, &peer_info)?;
|
||||
} else {
|
||||
return Err(ContractError::AlreadyRegistered);
|
||||
}
|
||||
Ok(Default::default())
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cw4::Cw4Contract;
|
||||
use cw_storage_plus::Item;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// unique items
|
||||
pub const STATE: Item<State> = Item::new("state");
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
pub struct State {
|
||||
pub mix_denom: String,
|
||||
pub group_addr: Cw4Contract,
|
||||
}
|
||||
@@ -22,7 +22,7 @@ nym-mixnet-contract = { path = "../mixnet" }
|
||||
nym-vesting-contract = { path = "../vesting" }
|
||||
|
||||
# other local dependencies
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "wasm-contract"] }
|
||||
|
||||
# external dependencies
|
||||
rand_chacha = "0.2"
|
||||
|
||||
@@ -42,7 +42,7 @@ semver = { version = "1.0.16", default-features = false }
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
rand_chacha = "0.2"
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "wasm-contract"] }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
|
||||
|
||||
@@ -520,7 +520,7 @@ mod tests {
|
||||
.delegations
|
||||
.iter()
|
||||
.filter(|d| d.proxy.is_some())
|
||||
.all(|d| d.proxy.as_ref().unwrap() == &vesting_contract));
|
||||
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
|
||||
|
||||
// now make sure that if we do it in paged manner, we'll get exactly the same result
|
||||
let per_page = Some(15);
|
||||
|
||||
@@ -27,12 +27,12 @@ impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
|
||||
pub(crate) fn delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
|
||||
let indexes = DelegationIndex {
|
||||
owner: MultiIndex::new(
|
||||
|d| d.owner.clone(),
|
||||
|_pk, d| d.owner.clone(),
|
||||
DELEGATION_PK_NAMESPACE,
|
||||
DELEGATION_OWNER_IDX_NAMESPACE,
|
||||
),
|
||||
mixnode: MultiIndex::new(
|
||||
|d| d.mix_id,
|
||||
|_pk, d| d.mix_id,
|
||||
DELEGATION_PK_NAMESPACE,
|
||||
DELEGATION_MIXNODE_IDX_NAMESPACE,
|
||||
),
|
||||
|
||||
@@ -39,12 +39,12 @@ pub(crate) fn unbonded_mixnodes<'a>(
|
||||
) -> IndexedMap<'a, MixId, UnbondedMixnode, UnbondedMixnodeIndex<'a>> {
|
||||
let indexes = UnbondedMixnodeIndex {
|
||||
owner: MultiIndex::new(
|
||||
|d| d.owner.clone(),
|
||||
|_pk, d| d.owner.clone(),
|
||||
UNBONDED_MIXNODES_PK_NAMESPACE,
|
||||
UNBONDED_MIXNODES_OWNER_IDX_NAMESPACE,
|
||||
),
|
||||
identity_key: MultiIndex::new(
|
||||
|d| d.identity_key.clone(),
|
||||
|_pk, d| d.identity_key.clone(),
|
||||
UNBONDED_MIXNODES_PK_NAMESPACE,
|
||||
UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE,
|
||||
),
|
||||
|
||||
@@ -185,7 +185,7 @@ pub(crate) fn _try_withdraw_operator_reward(
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == &vesting_contract {
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
@@ -271,7 +271,7 @@ pub(crate) fn _try_withdraw_delegator_reward(
|
||||
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
|
||||
// otherwise, we don't care
|
||||
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
|
||||
if proxy == &vesting_contract {
|
||||
if proxy == vesting_contract {
|
||||
let msg = VestingContractExecuteMsg::TrackReward {
|
||||
amount: reward.clone(),
|
||||
address: owner.clone().into_string(),
|
||||
|
||||
@@ -298,7 +298,7 @@ pub(crate) fn ensure_is_authorized(
|
||||
sender: &Addr,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
if sender != &crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
|
||||
if sender != crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)? {
|
||||
return Err(MixnetContractError::Unauthorized);
|
||||
}
|
||||
Ok(())
|
||||
@@ -309,7 +309,7 @@ pub(crate) fn ensure_can_advance_epoch(
|
||||
storage: &dyn Storage,
|
||||
) -> Result<EpochStatus, MixnetContractError> {
|
||||
let epoch_status = crate::interval::storage::current_epoch_status(storage)?;
|
||||
if sender != &epoch_status.being_advanced_by {
|
||||
if sender != epoch_status.being_advanced_by {
|
||||
// well, we know we're going to throw an error now,
|
||||
// but we might as well also check if we're even a validator
|
||||
// to return a possibly better error message
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "cw3-flex-multisig"
|
||||
version = "0.13.1"
|
||||
version = "1.0.0"
|
||||
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Implementing cw3 with multiple voting patterns and dynamic groups"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/CosmWasm/cw-plus"
|
||||
@@ -13,6 +13,7 @@ documentation = "https://docs.cosmwasm.com"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
|
||||
@@ -22,15 +23,15 @@ cw2 = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace = true }
|
||||
cw20 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { version = "1.0.0" }
|
||||
cw4-group = { path = "../cw4-group", version = "0.13.4" }
|
||||
cw4-group = { path = "../cw4-group", version = "1.0.0" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw20-base = "1.0.0"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +1,25 @@
|
||||
/*!
|
||||
This builds on [`cw3_fixed_multisig`] with a more
|
||||
powerful implementation of the [cw3 spec](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw3/README.md).
|
||||
It is a multisig contract that is backed by a
|
||||
[cw4 (group)](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw4/README.md) contract, which independently
|
||||
maintains the voter set.
|
||||
|
||||
This provides 2 main advantages:
|
||||
|
||||
* You can create two different multisigs with different voting thresholds
|
||||
backed by the same group. Thus, you can have a 50% vote, and a 67% vote
|
||||
that always use the same voter set, but can take other actions.
|
||||
* TODO: It allows dynamic multisig groups.
|
||||
|
||||
|
||||
In addition to the dynamic voting set, the main difference with the native
|
||||
Cosmos SDK multisig, is that it aggregates the signatures on chain, with
|
||||
visible proposals (like `x/gov` in the Cosmos SDK), rather than requiring
|
||||
signers to share signatures off chain.
|
||||
|
||||
For more information on this contract, please check out the
|
||||
[README](https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw3-flex-multisig/README.md).
|
||||
*/
|
||||
|
||||
pub mod contract;
|
||||
pub mod state;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cw4::Cw4Contract;
|
||||
use cw_storage_plus::Item;
|
||||
use cw_utils::{Duration, Threshold};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, JsonSchema, Debug)]
|
||||
pub struct Config {
|
||||
pub threshold: Threshold,
|
||||
pub max_voting_period: Duration,
|
||||
// Total weight and voters are queried from this contract
|
||||
pub group_addr: Cw4Contract,
|
||||
pub coconut_bandwidth_addr: Addr,
|
||||
pub coconut_dkg_addr: Addr,
|
||||
}
|
||||
|
||||
// unique items
|
||||
pub const CONFIG: Item<Config> = Item::new("config");
|
||||
@@ -1,5 +1,5 @@
|
||||
[alias]
|
||||
wasm = "build --release --target wasm32-unknown-unknown"
|
||||
wasm-debug = "build --target wasm32-unknown-unknown"
|
||||
wasm = "build --release --lib --target wasm32-unknown-unknown"
|
||||
wasm-debug = "build --lib --target wasm32-unknown-unknown"
|
||||
unit-test = "test --lib"
|
||||
schema = "run --example schema"
|
||||
schema = "run --bin schema"
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "cw4-group"
|
||||
version = "0.13.4"
|
||||
version = "1.0.0"
|
||||
authors = ["Ethan Frey <ethanfrey@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
description = "Simple cw4 implementation of group membership controlled by admin "
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/CosmWasm/cw-plus"
|
||||
@@ -20,6 +20,8 @@ exclude = [
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
# use library feature to disable all instantiate/execute/query exports
|
||||
library = []
|
||||
|
||||
@@ -31,10 +33,8 @@ cw2 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { workspace = true }
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use cosmwasm_std::entry_point;
|
||||
use cosmwasm_std::{
|
||||
attr, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Order, Response, StdResult,
|
||||
SubMsg,
|
||||
SubMsg, Uint64,
|
||||
};
|
||||
use cw2::set_contract_version;
|
||||
use cw4::{
|
||||
@@ -13,6 +13,7 @@ use cw_storage_plus::Bound;
|
||||
use cw_utils::maybe_addr;
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::validate_unique_members;
|
||||
use crate::state::{ADMIN, HOOKS, MEMBERS, TOTAL};
|
||||
use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
|
||||
|
||||
@@ -39,21 +40,25 @@ pub fn instantiate(
|
||||
pub fn create(
|
||||
mut deps: DepsMut,
|
||||
admin: Option<String>,
|
||||
members: Vec<Member>,
|
||||
mut members: Vec<Member>,
|
||||
height: u64,
|
||||
) -> Result<(), ContractError> {
|
||||
validate_unique_members(&mut members)?;
|
||||
let members = members; // let go of mutability
|
||||
|
||||
let admin_addr = admin
|
||||
.map(|admin| deps.api.addr_validate(&admin))
|
||||
.transpose()?;
|
||||
ADMIN.set(deps.branch(), admin_addr)?;
|
||||
|
||||
let mut total = 0u64;
|
||||
let mut total = Uint64::zero();
|
||||
for member in members.into_iter() {
|
||||
total += member.weight;
|
||||
let member_weight = Uint64::from(member.weight);
|
||||
total = total.checked_add(member_weight)?;
|
||||
let member_addr = deps.api.addr_validate(&member.addr)?;
|
||||
MEMBERS.save(deps.storage, &member_addr, &member.weight, height)?;
|
||||
MEMBERS.save(deps.storage, &member_addr, &member_weight.u64(), height)?;
|
||||
}
|
||||
TOTAL.save(deps.storage, &total)?;
|
||||
TOTAL.save(deps.storage, &total.u64(), height)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -115,20 +120,23 @@ pub fn update_members(
|
||||
deps: DepsMut,
|
||||
height: u64,
|
||||
sender: Addr,
|
||||
to_add: Vec<Member>,
|
||||
mut to_add: Vec<Member>,
|
||||
to_remove: Vec<String>,
|
||||
) -> Result<MemberChangedHookMsg, ContractError> {
|
||||
validate_unique_members(&mut to_add)?;
|
||||
let to_add = to_add; // let go of mutability
|
||||
|
||||
ADMIN.assert_admin(deps.as_ref(), &sender)?;
|
||||
|
||||
let mut total = TOTAL.load(deps.storage)?;
|
||||
let mut total = Uint64::from(TOTAL.load(deps.storage)?);
|
||||
let mut diffs: Vec<MemberDiff> = vec![];
|
||||
|
||||
// add all new members and update total
|
||||
for add in to_add.into_iter() {
|
||||
let add_addr = deps.api.addr_validate(&add.addr)?;
|
||||
MEMBERS.update(deps.storage, &add_addr, height, |old| -> StdResult<_> {
|
||||
total -= old.unwrap_or_default();
|
||||
total += add.weight;
|
||||
total = total.checked_sub(Uint64::from(old.unwrap_or_default()))?;
|
||||
total = total.checked_add(Uint64::from(add.weight))?;
|
||||
diffs.push(MemberDiff::new(add.addr, old, Some(add.weight)));
|
||||
Ok(add.weight)
|
||||
})?;
|
||||
@@ -140,12 +148,12 @@ pub fn update_members(
|
||||
// Only process this if they were actually in the list before
|
||||
if let Some(weight) = old {
|
||||
diffs.push(MemberDiff::new(remove, Some(weight), None));
|
||||
total -= weight;
|
||||
total = total.checked_sub(Uint64::from(weight))?;
|
||||
MEMBERS.remove(deps.storage, &remove_addr, height)?;
|
||||
}
|
||||
}
|
||||
|
||||
TOTAL.save(deps.storage, &total)?;
|
||||
TOTAL.save(deps.storage, &total.u64(), height)?;
|
||||
Ok(MemberChangedHookMsg { diffs })
|
||||
}
|
||||
|
||||
@@ -157,20 +165,26 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||
at_height: height,
|
||||
} => to_binary(&query_member(deps, addr, height)?),
|
||||
QueryMsg::ListMembers { start_after, limit } => {
|
||||
to_binary(&list_members(deps, start_after, limit)?)
|
||||
to_binary(&query_list_members(deps, start_after, limit)?)
|
||||
}
|
||||
QueryMsg::TotalWeight { at_height: height } => {
|
||||
to_binary(&query_total_weight(deps, height)?)
|
||||
}
|
||||
QueryMsg::TotalWeight {} => to_binary(&query_total_weight(deps)?),
|
||||
QueryMsg::Admin {} => to_binary(&ADMIN.query_admin(deps)?),
|
||||
QueryMsg::Hooks {} => to_binary(&HOOKS.query_hooks(deps)?),
|
||||
}
|
||||
}
|
||||
|
||||
fn query_total_weight(deps: Deps) -> StdResult<TotalWeightResponse> {
|
||||
let weight = TOTAL.load(deps.storage)?;
|
||||
pub fn query_total_weight(deps: Deps, height: Option<u64>) -> StdResult<TotalWeightResponse> {
|
||||
let weight = match height {
|
||||
Some(h) => TOTAL.may_load_at_height(deps.storage, h),
|
||||
None => TOTAL.may_load(deps.storage),
|
||||
}?
|
||||
.unwrap_or_default();
|
||||
Ok(TotalWeightResponse { weight })
|
||||
}
|
||||
|
||||
fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<MemberResponse> {
|
||||
pub fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<MemberResponse> {
|
||||
let addr = deps.api.addr_validate(&addr)?;
|
||||
let weight = match height {
|
||||
Some(h) => MEMBERS.may_load_at_height(deps.storage, &addr, h),
|
||||
@@ -183,7 +197,7 @@ fn query_member(deps: Deps, addr: String, height: Option<u64>) -> StdResult<Memb
|
||||
const MAX_LIMIT: u32 = 30;
|
||||
const DEFAULT_LIMIT: u32 = 10;
|
||||
|
||||
fn list_members(
|
||||
pub fn query_list_members(
|
||||
deps: Deps,
|
||||
start_after: Option<String>,
|
||||
limit: Option<u32>,
|
||||
@@ -205,352 +219,3 @@ fn list_members(
|
||||
|
||||
Ok(MemberListResponse { members })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{from_slice, Api, OwnedDeps, Querier, Storage};
|
||||
use cw4::{member_key, TOTAL_KEY};
|
||||
use cw_controllers::{AdminError, HookError};
|
||||
|
||||
const INIT_ADMIN: &str = "juan";
|
||||
const USER1: &str = "somebody";
|
||||
const USER2: &str = "else";
|
||||
const USER3: &str = "funny";
|
||||
|
||||
fn do_instantiate(deps: DepsMut) {
|
||||
let msg = InstantiateMsg {
|
||||
admin: Some(INIT_ADMIN.into()),
|
||||
members: vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 11,
|
||||
},
|
||||
Member {
|
||||
addr: USER2.into(),
|
||||
weight: 6,
|
||||
},
|
||||
],
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps, mock_env(), info, msg).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proper_instantiation() {
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
// it worked, let's query the state
|
||||
let res = ADMIN.query_admin(deps.as_ref()).unwrap();
|
||||
assert_eq!(Some(INIT_ADMIN.into()), res.admin);
|
||||
|
||||
let res = query_total_weight(deps.as_ref()).unwrap();
|
||||
assert_eq!(17, res.weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_member_queries() {
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
let member1 = query_member(deps.as_ref(), USER1.into(), None).unwrap();
|
||||
assert_eq!(member1.weight, Some(11));
|
||||
|
||||
let member2 = query_member(deps.as_ref(), USER2.into(), None).unwrap();
|
||||
assert_eq!(member2.weight, Some(6));
|
||||
|
||||
let member3 = query_member(deps.as_ref(), USER3.into(), None).unwrap();
|
||||
assert_eq!(member3.weight, None);
|
||||
|
||||
let members = list_members(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(members.members.len(), 2);
|
||||
// TODO: assert the set is proper
|
||||
}
|
||||
|
||||
fn assert_users<S: Storage, A: Api, Q: Querier>(
|
||||
deps: &OwnedDeps<S, A, Q>,
|
||||
user1_weight: Option<u64>,
|
||||
user2_weight: Option<u64>,
|
||||
user3_weight: Option<u64>,
|
||||
height: Option<u64>,
|
||||
) {
|
||||
let member1 = query_member(deps.as_ref(), USER1.into(), height).unwrap();
|
||||
assert_eq!(member1.weight, user1_weight);
|
||||
|
||||
let member2 = query_member(deps.as_ref(), USER2.into(), height).unwrap();
|
||||
assert_eq!(member2.weight, user2_weight);
|
||||
|
||||
let member3 = query_member(deps.as_ref(), USER3.into(), height).unwrap();
|
||||
assert_eq!(member3.weight, user3_weight);
|
||||
|
||||
// this is only valid if we are not doing a historical query
|
||||
if height.is_none() {
|
||||
// compute expected metrics
|
||||
let weights = vec![user1_weight, user2_weight, user3_weight];
|
||||
let sum: u64 = weights.iter().map(|x| x.unwrap_or_default()).sum();
|
||||
let count = weights.iter().filter(|x| x.is_some()).count();
|
||||
|
||||
// TODO: more detailed compare?
|
||||
let members = list_members(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(count, members.members.len());
|
||||
|
||||
let total = query_total_weight(deps.as_ref()).unwrap();
|
||||
assert_eq!(sum, total.weight); // 17 - 11 + 15 = 21
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_new_remove_old_member() {
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
// add a new one and remove existing one
|
||||
let add = vec![Member {
|
||||
addr: USER3.into(),
|
||||
weight: 15,
|
||||
}];
|
||||
let remove = vec![USER1.into()];
|
||||
|
||||
// non-admin cannot update
|
||||
let height = mock_env().block.height;
|
||||
let err = update_members(
|
||||
deps.as_mut(),
|
||||
height + 5,
|
||||
Addr::unchecked(USER1),
|
||||
add.clone(),
|
||||
remove.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, AdminError::NotAdmin {}.into());
|
||||
|
||||
// Test the values from instantiate
|
||||
assert_users(&deps, Some(11), Some(6), None, None);
|
||||
// Note all values were set at height, the beginning of that block was all None
|
||||
assert_users(&deps, None, None, None, Some(height));
|
||||
// This will get us the values at the start of the block after instantiate (expected initial values)
|
||||
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
|
||||
|
||||
// admin updates properly
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height + 10,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// updated properly
|
||||
assert_users(&deps, None, Some(6), Some(15), None);
|
||||
|
||||
// snapshot still shows old value
|
||||
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_old_remove_new_member() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
// add a new one and remove existing one
|
||||
let add = vec![Member {
|
||||
addr: USER1.into(),
|
||||
weight: 4,
|
||||
}];
|
||||
let remove = vec![USER3.into()];
|
||||
|
||||
// admin updates properly
|
||||
let height = mock_env().block.height;
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
assert_users(&deps, Some(4), Some(6), None, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_and_remove_same_member() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
// USER1 is updated and remove in the same call, we should remove this an add member3
|
||||
let add = vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 20,
|
||||
},
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 5,
|
||||
},
|
||||
];
|
||||
let remove = vec![USER1.into()];
|
||||
|
||||
// admin updates properly
|
||||
let height = mock_env().block.height;
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
assert_users(&deps, None, Some(6), Some(5), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_remove_hooks() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert!(hooks.hooks.is_empty());
|
||||
|
||||
let contract1 = String::from("hook1");
|
||||
let contract2 = String::from("hook2");
|
||||
|
||||
let add_msg = ExecuteMsg::AddHook {
|
||||
addr: contract1.clone(),
|
||||
};
|
||||
|
||||
// non-admin cannot add hook
|
||||
let user_info = mock_info(USER1, &[]);
|
||||
let err = execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
user_info.clone(),
|
||||
add_msg.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
|
||||
|
||||
// admin can add it, and it appears in the query
|
||||
let admin_info = mock_info(INIT_ADMIN, &[]);
|
||||
let _ = execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
admin_info.clone(),
|
||||
add_msg.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract1.clone()]);
|
||||
|
||||
// cannot remove a non-registered contract
|
||||
let remove_msg = ExecuteMsg::RemoveHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), remove_msg).unwrap_err();
|
||||
assert_eq!(err, HookError::HookNotRegistered {}.into());
|
||||
|
||||
// add second contract
|
||||
let add_msg2 = ExecuteMsg::AddHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg2).unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract1.clone(), contract2.clone()]);
|
||||
|
||||
// cannot re-add an existing contract
|
||||
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg).unwrap_err();
|
||||
assert_eq!(err, HookError::HookAlreadyRegistered {}.into());
|
||||
|
||||
// non-admin cannot remove
|
||||
let remove_msg = ExecuteMsg::RemoveHook { addr: contract1 };
|
||||
let err = execute(deps.as_mut(), mock_env(), user_info, remove_msg.clone()).unwrap_err();
|
||||
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
|
||||
|
||||
// remove the original
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info, remove_msg).unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hooks_fire() {
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert!(hooks.hooks.is_empty());
|
||||
|
||||
let contract1 = String::from("hook1");
|
||||
let contract2 = String::from("hook2");
|
||||
|
||||
// register 2 hooks
|
||||
let admin_info = mock_info(INIT_ADMIN, &[]);
|
||||
let add_msg = ExecuteMsg::AddHook {
|
||||
addr: contract1.clone(),
|
||||
};
|
||||
let add_msg2 = ExecuteMsg::AddHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
for msg in vec![add_msg, add_msg2] {
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), msg).unwrap();
|
||||
}
|
||||
|
||||
// make some changes - add 3, remove 2, and update 1
|
||||
// USER1 is updated and remove in the same call, we should remove this an add member3
|
||||
let add = vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 20,
|
||||
},
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 5,
|
||||
},
|
||||
];
|
||||
let remove = vec![USER2.into()];
|
||||
let msg = ExecuteMsg::UpdateMembers { remove, add };
|
||||
|
||||
// admin updates properly
|
||||
assert_users(&deps, Some(11), Some(6), None, None);
|
||||
let res = execute(deps.as_mut(), mock_env(), admin_info, msg).unwrap();
|
||||
assert_users(&deps, Some(20), None, Some(5), None);
|
||||
|
||||
// ensure 2 messages for the 2 hooks
|
||||
assert_eq!(res.messages.len(), 2);
|
||||
// same order as in the message (adds first, then remove)
|
||||
let diffs = vec![
|
||||
MemberDiff::new(USER1, Some(11), Some(20)),
|
||||
MemberDiff::new(USER3, None, Some(5)),
|
||||
MemberDiff::new(USER2, Some(6), None),
|
||||
];
|
||||
let hook_msg = MemberChangedHookMsg { diffs };
|
||||
let msg1 = SubMsg::new(hook_msg.clone().into_cosmos_msg(contract1).unwrap());
|
||||
let msg2 = SubMsg::new(hook_msg.into_cosmos_msg(contract2).unwrap());
|
||||
assert_eq!(res.messages, vec![msg1, msg2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_queries_work() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
do_instantiate(deps.as_mut());
|
||||
|
||||
// get total from raw key
|
||||
let total_raw = deps.storage.get(TOTAL_KEY.as_bytes()).unwrap();
|
||||
let total: u64 = from_slice(&total_raw).unwrap();
|
||||
assert_eq!(17, total);
|
||||
|
||||
// get member votes from raw key
|
||||
let member2_raw = deps.storage.get(&member_key(USER2)).unwrap();
|
||||
let member2: u64 = from_slice(&member2_raw).unwrap();
|
||||
assert_eq!(6, member2);
|
||||
|
||||
// and execute misses
|
||||
let member3_raw = deps.storage.get(&member_key(USER3));
|
||||
assert_eq!(None, member3_raw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
use cosmwasm_std::StdError;
|
||||
use cosmwasm_std::{OverflowError, StdError};
|
||||
use thiserror::Error;
|
||||
|
||||
use cw_controllers::{AdminError, HookError};
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error(transparent)]
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("{0}")]
|
||||
Hook(#[from] HookError),
|
||||
|
||||
#[error(transparent)]
|
||||
#[error("{0}")]
|
||||
Admin(#[from] AdminError),
|
||||
|
||||
#[error("{0}")]
|
||||
Overflow(#[from] OverflowError),
|
||||
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized {},
|
||||
|
||||
#[error("Message contained duplicate member: {member}")]
|
||||
DuplicateMember { member: String },
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
|
||||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{to_binary, Addr, CosmosMsg, StdResult, WasmMsg};
|
||||
use cw4::{Cw4Contract, Member};
|
||||
|
||||
use crate::ContractError;
|
||||
use nym_group_contract_common::msg::ExecuteMsg;
|
||||
|
||||
/// Cw4GroupContract is a wrapper around Cw4Contract that provides a lot of helpers
|
||||
/// for working with cw4-group contracts.
|
||||
///
|
||||
/// It extends Cw4Contract to add the extra calls from cw4-group.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[cw_serde]
|
||||
pub struct Cw4GroupContract(pub Cw4Contract);
|
||||
|
||||
impl Deref for Cw4GroupContract {
|
||||
@@ -41,3 +41,17 @@ impl Cw4GroupContract {
|
||||
self.encode_msg(msg)
|
||||
}
|
||||
}
|
||||
|
||||
/// Sorts the slice and verifies all member addresses are unique.
|
||||
pub fn validate_unique_members(members: &mut [Member]) -> Result<(), ContractError> {
|
||||
members.sort_by(|a, b| a.addr.cmp(&b.addr));
|
||||
for (a, b) in members.iter().zip(members.iter().skip(1)) {
|
||||
if a.addr == b.addr {
|
||||
return Err(ContractError::DuplicateMember {
|
||||
member: a.addr.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,25 @@
|
||||
/*!
|
||||
This is a basic implementation of the [cw4 spec](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw4/README.md).
|
||||
It fulfills all elements of the spec, including the raw query lookups,
|
||||
and it designed to be used as a backing storage for
|
||||
[cw3 compliant contracts](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw3/README.md).
|
||||
|
||||
It stores a set of members along with an admin, and allows the admin to
|
||||
update the state. Raw queries (intended for cross-contract queries)
|
||||
can check a given member address and the total weight. Smart queries (designed
|
||||
for client API) can do the same, and also query the admin address as well as
|
||||
paginate over all members.
|
||||
|
||||
For more information on this contract, please check out the
|
||||
[README](https://github.com/CosmWasm/cw-plus/blob/main/contracts/cw4-group/README.md).
|
||||
*/
|
||||
|
||||
pub mod contract;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod state;
|
||||
|
||||
pub use crate::error::ContractError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
use cosmwasm_std::Addr;
|
||||
use cw4::TOTAL_KEY;
|
||||
use cw4::{
|
||||
MEMBERS_CHANGELOG, MEMBERS_CHECKPOINTS, MEMBERS_KEY, TOTAL_KEY, TOTAL_KEY_CHANGELOG,
|
||||
TOTAL_KEY_CHECKPOINTS,
|
||||
};
|
||||
use cw_controllers::{Admin, Hooks};
|
||||
use cw_storage_plus::{Item, SnapshotMap, Strategy};
|
||||
use cw_storage_plus::{SnapshotItem, SnapshotMap, Strategy};
|
||||
|
||||
pub const ADMIN: Admin = Admin::new("admin");
|
||||
pub const HOOKS: Hooks = Hooks::new("cw4-hooks");
|
||||
|
||||
pub const TOTAL: Item<u64> = Item::new(TOTAL_KEY);
|
||||
|
||||
pub const MEMBERS: SnapshotMap<&Addr, u64> = SnapshotMap::new(
|
||||
cw4::MEMBERS_KEY,
|
||||
cw4::MEMBERS_CHECKPOINTS,
|
||||
cw4::MEMBERS_CHANGELOG,
|
||||
pub const TOTAL: SnapshotItem<u64> = SnapshotItem::new(
|
||||
TOTAL_KEY,
|
||||
TOTAL_KEY_CHECKPOINTS,
|
||||
TOTAL_KEY_CHANGELOG,
|
||||
Strategy::EveryBlock,
|
||||
);
|
||||
|
||||
pub const MEMBERS: SnapshotMap<&Addr, u64> = SnapshotMap::new(
|
||||
MEMBERS_KEY,
|
||||
MEMBERS_CHECKPOINTS,
|
||||
MEMBERS_CHANGELOG,
|
||||
Strategy::EveryBlock,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,439 @@
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{from_slice, Addr, Api, DepsMut, OwnedDeps, Querier, Storage, SubMsg};
|
||||
use cw4::{member_key, Member, MemberChangedHookMsg, MemberDiff, TOTAL_KEY};
|
||||
use cw_controllers::{AdminError, HookError};
|
||||
|
||||
use crate::contract::{
|
||||
execute, instantiate, query_list_members, query_member, query_total_weight, update_members,
|
||||
};
|
||||
use crate::state::{ADMIN, HOOKS};
|
||||
use crate::ContractError;
|
||||
use nym_group_contract_common::msg::{ExecuteMsg, InstantiateMsg};
|
||||
|
||||
const INIT_ADMIN: &str = "juan";
|
||||
const USER1: &str = "somebody";
|
||||
const USER2: &str = "else";
|
||||
const USER3: &str = "funny";
|
||||
|
||||
fn set_up(deps: DepsMut) {
|
||||
let msg = InstantiateMsg {
|
||||
admin: Some(INIT_ADMIN.into()),
|
||||
members: vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 11,
|
||||
},
|
||||
Member {
|
||||
addr: USER2.into(),
|
||||
weight: 6,
|
||||
},
|
||||
],
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps, mock_env(), info, msg).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proper_instantiation() {
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
// it worked, let's query the state
|
||||
let res = ADMIN.query_admin(deps.as_ref()).unwrap();
|
||||
assert_eq!(Some(INIT_ADMIN.into()), res.admin);
|
||||
|
||||
let res = query_total_weight(deps.as_ref(), None).unwrap();
|
||||
assert_eq!(17, res.weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_member_queries() {
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
let member1 = query_member(deps.as_ref(), USER1.into(), None).unwrap();
|
||||
assert_eq!(member1.weight, Some(11));
|
||||
|
||||
let member2 = query_member(deps.as_ref(), USER2.into(), None).unwrap();
|
||||
assert_eq!(member2.weight, Some(6));
|
||||
|
||||
let member3 = query_member(deps.as_ref(), USER3.into(), None).unwrap();
|
||||
assert_eq!(member3.weight, None);
|
||||
|
||||
let members = query_list_members(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(members.members.len(), 2);
|
||||
// TODO: assert the set is proper
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_members_instantiation() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
||||
let msg = InstantiateMsg {
|
||||
admin: Some(INIT_ADMIN.into()),
|
||||
members: vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 5,
|
||||
},
|
||||
Member {
|
||||
addr: USER2.into(),
|
||||
weight: 6,
|
||||
},
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 6,
|
||||
},
|
||||
],
|
||||
};
|
||||
let info = mock_info("creator", &[]);
|
||||
let err = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
ContractError::DuplicateMember {
|
||||
member: USER1.to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_members_execution() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
||||
set_up(deps.as_mut());
|
||||
|
||||
let add = vec![
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 15,
|
||||
},
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 11,
|
||||
},
|
||||
];
|
||||
|
||||
let height = mock_env().block.height;
|
||||
let err = update_members(
|
||||
deps.as_mut(),
|
||||
height + 5,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
vec![],
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err,
|
||||
ContractError::DuplicateMember {
|
||||
member: USER3.to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_users<S: Storage, A: Api, Q: Querier>(
|
||||
deps: &OwnedDeps<S, A, Q>,
|
||||
user1_weight: Option<u64>,
|
||||
user2_weight: Option<u64>,
|
||||
user3_weight: Option<u64>,
|
||||
height: Option<u64>,
|
||||
) {
|
||||
let member1 = query_member(deps.as_ref(), USER1.into(), height).unwrap();
|
||||
assert_eq!(member1.weight, user1_weight);
|
||||
|
||||
let member2 = query_member(deps.as_ref(), USER2.into(), height).unwrap();
|
||||
assert_eq!(member2.weight, user2_weight);
|
||||
|
||||
let member3 = query_member(deps.as_ref(), USER3.into(), height).unwrap();
|
||||
assert_eq!(member3.weight, user3_weight);
|
||||
|
||||
// this is only valid if we are not doing a historical query
|
||||
if height.is_none() {
|
||||
// compute expected metrics
|
||||
let weights = vec![user1_weight, user2_weight, user3_weight];
|
||||
let sum: u64 = weights.iter().map(|x| x.unwrap_or_default()).sum();
|
||||
let count = weights.iter().filter(|x| x.is_some()).count();
|
||||
|
||||
// TODO: more detailed compare?
|
||||
let members = query_list_members(deps.as_ref(), None, None).unwrap();
|
||||
assert_eq!(count, members.members.len());
|
||||
|
||||
let total = query_total_weight(deps.as_ref(), None).unwrap();
|
||||
assert_eq!(sum, total.weight); // 17 - 11 + 15 = 21
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_new_remove_old_member() {
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
// add a new one and remove existing one
|
||||
let add = vec![Member {
|
||||
addr: USER3.into(),
|
||||
weight: 15,
|
||||
}];
|
||||
let remove = vec![USER1.into()];
|
||||
|
||||
// non-admin cannot update
|
||||
let height = mock_env().block.height;
|
||||
let err = update_members(
|
||||
deps.as_mut(),
|
||||
height + 5,
|
||||
Addr::unchecked(USER1),
|
||||
add.clone(),
|
||||
remove.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, AdminError::NotAdmin {}.into());
|
||||
|
||||
// Test the values from instantiate
|
||||
assert_users(&deps, Some(11), Some(6), None, None);
|
||||
// Note all values were set at height, the beginning of that block was all None
|
||||
assert_users(&deps, None, None, None, Some(height));
|
||||
// This will get us the values at the start of the block after instantiate (expected initial values)
|
||||
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
|
||||
|
||||
// admin updates properly
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height + 10,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// updated properly
|
||||
assert_users(&deps, None, Some(6), Some(15), None);
|
||||
|
||||
// snapshot still shows old value
|
||||
assert_users(&deps, Some(11), Some(6), None, Some(height + 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_old_remove_new_member() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
// add a new one and remove existing one
|
||||
let add = vec![Member {
|
||||
addr: USER1.into(),
|
||||
weight: 4,
|
||||
}];
|
||||
let remove = vec![USER3.into()];
|
||||
|
||||
// admin updates properly
|
||||
let height = mock_env().block.height;
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
assert_users(&deps, Some(4), Some(6), None, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_and_remove_same_member() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
// USER1 is updated and remove in the same call, we should remove this an add member3
|
||||
let add = vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 20,
|
||||
},
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 5,
|
||||
},
|
||||
];
|
||||
let remove = vec![USER1.into()];
|
||||
|
||||
// admin updates properly
|
||||
let height = mock_env().block.height;
|
||||
update_members(
|
||||
deps.as_mut(),
|
||||
height,
|
||||
Addr::unchecked(INIT_ADMIN),
|
||||
add,
|
||||
remove,
|
||||
)
|
||||
.unwrap();
|
||||
assert_users(&deps, None, Some(6), Some(5), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_remove_hooks() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert!(hooks.hooks.is_empty());
|
||||
|
||||
let contract1 = String::from("hook1");
|
||||
let contract2 = String::from("hook2");
|
||||
|
||||
let add_msg = ExecuteMsg::AddHook {
|
||||
addr: contract1.clone(),
|
||||
};
|
||||
|
||||
// non-admin cannot add hook
|
||||
let user_info = mock_info(USER1, &[]);
|
||||
let err = execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
user_info.clone(),
|
||||
add_msg.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
|
||||
|
||||
// admin can add it, and it appears in the query
|
||||
let admin_info = mock_info(INIT_ADMIN, &[]);
|
||||
let _ = execute(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
admin_info.clone(),
|
||||
add_msg.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract1.clone()]);
|
||||
|
||||
// cannot remove a non-registered contract
|
||||
let remove_msg = ExecuteMsg::RemoveHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), remove_msg).unwrap_err();
|
||||
assert_eq!(err, HookError::HookNotRegistered {}.into());
|
||||
|
||||
// add second contract
|
||||
let add_msg2 = ExecuteMsg::AddHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg2).unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract1.clone(), contract2.clone()]);
|
||||
|
||||
// cannot re-add an existing contract
|
||||
let err = execute(deps.as_mut(), mock_env(), admin_info.clone(), add_msg).unwrap_err();
|
||||
assert_eq!(err, HookError::HookAlreadyRegistered {}.into());
|
||||
|
||||
// non-admin cannot remove
|
||||
let remove_msg = ExecuteMsg::RemoveHook { addr: contract1 };
|
||||
let err = execute(deps.as_mut(), mock_env(), user_info, remove_msg.clone()).unwrap_err();
|
||||
assert_eq!(err, HookError::Admin(AdminError::NotAdmin {}).into());
|
||||
|
||||
// remove the original
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info, remove_msg).unwrap();
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert_eq!(hooks.hooks, vec![contract2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hooks_fire() {
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
let hooks = HOOKS.query_hooks(deps.as_ref()).unwrap();
|
||||
assert!(hooks.hooks.is_empty());
|
||||
|
||||
let contract1 = String::from("hook1");
|
||||
let contract2 = String::from("hook2");
|
||||
|
||||
// register 2 hooks
|
||||
let admin_info = mock_info(INIT_ADMIN, &[]);
|
||||
let add_msg = ExecuteMsg::AddHook {
|
||||
addr: contract1.clone(),
|
||||
};
|
||||
let add_msg2 = ExecuteMsg::AddHook {
|
||||
addr: contract2.clone(),
|
||||
};
|
||||
for msg in vec![add_msg, add_msg2] {
|
||||
let _ = execute(deps.as_mut(), mock_env(), admin_info.clone(), msg).unwrap();
|
||||
}
|
||||
|
||||
// make some changes - add 3, remove 2, and update 1
|
||||
// USER1 is updated and remove in the same call, we should remove this an add member3
|
||||
let add = vec![
|
||||
Member {
|
||||
addr: USER1.into(),
|
||||
weight: 20,
|
||||
},
|
||||
Member {
|
||||
addr: USER3.into(),
|
||||
weight: 5,
|
||||
},
|
||||
];
|
||||
let remove = vec![USER2.into()];
|
||||
let msg = ExecuteMsg::UpdateMembers { remove, add };
|
||||
|
||||
// admin updates properly
|
||||
assert_users(&deps, Some(11), Some(6), None, None);
|
||||
let res = execute(deps.as_mut(), mock_env(), admin_info, msg).unwrap();
|
||||
assert_users(&deps, Some(20), None, Some(5), None);
|
||||
|
||||
// ensure 2 messages for the 2 hooks
|
||||
assert_eq!(res.messages.len(), 2);
|
||||
// same order as in the message (adds first, then remove)
|
||||
// order of added users is not guaranteed to be preserved
|
||||
let diffs = vec![
|
||||
MemberDiff::new(USER3, None, Some(5)),
|
||||
MemberDiff::new(USER1, Some(11), Some(20)),
|
||||
MemberDiff::new(USER2, Some(6), None),
|
||||
];
|
||||
let hook_msg = MemberChangedHookMsg { diffs };
|
||||
let msg1 = SubMsg::new(hook_msg.clone().into_cosmos_msg(contract1).unwrap());
|
||||
let msg2 = SubMsg::new(hook_msg.into_cosmos_msg(contract2).unwrap());
|
||||
dbg!(&res.messages);
|
||||
dbg!(&msg1);
|
||||
dbg!(&msg2);
|
||||
assert_eq!(res.messages, vec![msg1, msg2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_queries_work() {
|
||||
// add will over-write and remove have no effect
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
// get total from raw key
|
||||
let total_raw = deps.storage.get(TOTAL_KEY.as_bytes()).unwrap();
|
||||
let total: u64 = from_slice(&total_raw).unwrap();
|
||||
assert_eq!(17, total);
|
||||
|
||||
// get member votes from raw key
|
||||
let member2_raw = deps.storage.get(&member_key(USER2)).unwrap();
|
||||
let member2: u64 = from_slice(&member2_raw).unwrap();
|
||||
assert_eq!(6, member2);
|
||||
|
||||
// and execute misses
|
||||
let member3_raw = deps.storage.get(&member_key(USER3));
|
||||
assert_eq!(None, member3_raw);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_at_height() {
|
||||
let mut deps = mock_dependencies();
|
||||
set_up(deps.as_mut());
|
||||
|
||||
let height = mock_env().block.height;
|
||||
|
||||
// Test the values from instantiate
|
||||
let total = query_total_weight(deps.as_ref(), None).unwrap();
|
||||
assert_eq!(17, total.weight);
|
||||
// Note all values were set at height, the beginning of that block was all None
|
||||
let total = query_total_weight(deps.as_ref(), Some(height)).unwrap();
|
||||
assert_eq!(0, total.weight);
|
||||
// This will get us the values at the start of the block after instantiate (expected initial values)
|
||||
let total = query_total_weight(deps.as_ref(), Some(height + 1)).unwrap();
|
||||
assert_eq!(17, total.weight);
|
||||
}
|
||||
@@ -30,12 +30,12 @@ fn names<'a>() -> IndexedMap<'a, NameId, RegisteredName, NameIndex<'a>> {
|
||||
let indexes = NameIndex {
|
||||
name: UniqueIndex::new(|d| d.name.to_string(), NAMES_NAME_IDX_NAMESPACE),
|
||||
address: MultiIndex::new(
|
||||
|d| d.address.to_string(),
|
||||
|_pk, d| d.address.to_string(),
|
||||
NAMES_PK_NAMESPACE,
|
||||
NAMES_ADDRESS_IDX_NAMESPACE,
|
||||
),
|
||||
owner: MultiIndex::new(
|
||||
|d| d.owner.clone(),
|
||||
|_pk, d| d.owner.clone(),
|
||||
NAMES_PK_NAMESPACE,
|
||||
NAMES_OWNER_IDX_NAMESPACE,
|
||||
),
|
||||
|
||||
@@ -25,6 +25,6 @@ vergen = { version = "=7.4.3", default-features = false, features = ["build", "g
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.40"
|
||||
cw-multi-test = { workspace = true }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand", "wasm-contract"] }
|
||||
rand_chacha = "0.2"
|
||||
rstest = "0.17.0"
|
||||
|
||||
@@ -26,12 +26,12 @@ impl<'a> IndexList<Service> for ServiceIndex<'a> {
|
||||
fn services<'a>() -> IndexedMap<'a, ServiceId, Service, ServiceIndex<'a>> {
|
||||
let indexes = ServiceIndex {
|
||||
nym_address: MultiIndex::new(
|
||||
|d| d.service.nym_address.to_string(),
|
||||
|_pk, d| d.service.nym_address.to_string(),
|
||||
SERVICES_PK_NAMESPACE,
|
||||
SERVICES_NYM_ADDRESS_IDX_NAMESPACE,
|
||||
),
|
||||
announcer: MultiIndex::new(
|
||||
|d| d.announcer.clone(),
|
||||
|_pk, d| d.announcer.clone(),
|
||||
SERVICES_PK_NAMESPACE,
|
||||
SERVICES_ANNOUNCER_IDX_NAMESPACE,
|
||||
),
|
||||
|
||||
@@ -25,7 +25,7 @@ fn generate_storage_key(storage: &mut dyn Storage) -> Result<AccountStorageKey,
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
|
||||
pub struct Account {
|
||||
pub owner_address: Addr,
|
||||
pub staking_address: Option<Addr>,
|
||||
|
||||
@@ -16,6 +16,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1lp5zex6685kd22agzskhqsylpnssxnweyuvsz4edr4p
|
||||
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
|
||||
MULTISIG_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p
|
||||
EPHEMERA_CONTRACT_ADDRESS=n1vwtgazgpancsfel04y7syc95ausmat47cjtldqzkdmx6phyrwx2qvkv32p
|
||||
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
NYXD="http://127.0.0.1:26657"
|
||||
|
||||
@@ -16,6 +16,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
|
||||
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
EPHEMERA_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYXD="https://rpc.nymtech.net"
|
||||
|
||||
@@ -16,6 +16,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19d2nwj7fdhxqmyvgy8lf3ad49a6vmww4shryhrkj2mq
|
||||
GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm28l5
|
||||
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
|
||||
EPHEMERA_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1qsn2655eflc0nx2uwqtwyv5kad5dwm4c0gn72yr4q4de5r3jaz2slvqjgt
|
||||
NAME_SERVICE_CONTRACT_ADDRESS=n1cm2u5vfjd3zalfw0p65xyh4tcrw3hjlm0960gzhewga449h4mgas77mjkl
|
||||
|
||||
@@ -16,6 +16,7 @@ COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ghd753shjuwexxywmgs4xz7x2q732vcn7ty4yw
|
||||
GROUP_CONTRACT_ADDRESS=n1rw8fw2mpcpzzq3jpa4e52ufawnmj5a4u68p35umvgskewuw0nlzsaa5w4m
|
||||
MULTISIG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
|
||||
EPHEMERA_CONTRACT_ADDRESS=n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs
|
||||
REWARDING_VALIDATOR_ADDRESS=n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
NYXD="https://qa-validator.nymtech.net"
|
||||
|
||||
@@ -17,6 +17,7 @@ BANDWIDTH_CLAIM_CONTRACT_ADDRESS="nymt1rhmk9udessnv3r8f3eh2s03f45svnjaczpmcqz"
|
||||
MULTISIG_CONTRACT_ADDRESS="nymt142dkm8xe9f0ytyarp7ww4kvclva65705jphxsk9exn3nqdsm8jkqnp06ac"
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS="nymt1ty0frysegskh6ndm3v96z5xdq66qzcu0aw7xcxlgp54jg0mjwlgqplc6v0"
|
||||
COCONUT_DKG_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfusu5qk4k5z4"
|
||||
EPHEMERA_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62kfusu5qk4k5z4"
|
||||
GROUP_CONTRACT_ADDRESS="nymt14ry36mwauycz08v8ndcujghxz4hmua5epxcn0mamlr3suqe0l2qsqx5ya2"
|
||||
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
name = "ephemera"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "ephemera"
|
||||
path = "bin/main.rs"
|
||||
|
||||
[dependencies]
|
||||
## internal
|
||||
nym-task = { path = "../common/task" }
|
||||
|
||||
actix-web = "4"
|
||||
anyhow = { version = "1.0.66", features = ["backtrace"] }
|
||||
array-bytes = "6.0.0"
|
||||
async-trait = "0.1.59"
|
||||
asynchronous-codec = "0.6.1"
|
||||
blake2 = "0.10.6"
|
||||
bs58 = "0.4.0"
|
||||
bytes = "1.3.0"
|
||||
cfg-if = "1.0.0"
|
||||
chrono = { version = "0.4.24", default-features = false, features = ["clock"] }
|
||||
clap = { version = "4.0.32", features = ["derive"] }
|
||||
config = { version = "0.13", default-features = false, features = ["toml"] }
|
||||
digest = "0.10.6"
|
||||
dirs = "5.0.0"
|
||||
futures = "0.3.18"
|
||||
futures-util = "0.3.25"
|
||||
lazy_static = "1.4.0"
|
||||
libp2p = { version = "0.51.3", default-features = false, features = ["dns", "gossipsub", "kad", "macros", "noise", "request-response", "serde", "tcp", "tokio", "yamux"] }
|
||||
libp2p-identity = "0.1.0"
|
||||
log = "0.4.14"
|
||||
lru = "0.10.0"
|
||||
nym-config = { path = "../common/config" }
|
||||
nym-ephemera-common = { path = "../common/cosmwasm-smart-contracts/ephemera" }
|
||||
pretty_env_logger = "0.4"
|
||||
refinery = { version = "0.8.7", features = ["rusqlite"], optional = true }
|
||||
reqwest = { version = "0.11.6", features = ["json"] }
|
||||
rocksdb = { version = "0.20.1", optional = true }
|
||||
rusqlite = { version = "0.27.0", features = ["bundled"], optional = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_derive = "1.0.149"
|
||||
serde_json = "1.0.91"
|
||||
thiserror = "1.0.37"
|
||||
tokio = { version = "1", features = ["macros", "net","rt-multi-thread"] }
|
||||
tokio-tungstenite = "0.18.0"
|
||||
tokio-util = { version = "0.7.4", features = ["full"] }
|
||||
toml = "0.7.0"
|
||||
unsigned-varint = "0.7.1"
|
||||
utoipa = { version = "3.0.1", features = ["actix_extras"] }
|
||||
utoipa-swagger-ui = { version = "3.0.2", features = ["actix-web"] }
|
||||
uuid = { version = "1.2.2", features = ["v4"] }
|
||||
|
||||
# Temporary fix to https://github.com/bluejekyll/trust-dns/issues/1946
|
||||
enum-as-inner = "=0.5.1"
|
||||
|
||||
[dev-dependencies]
|
||||
assert_matches = "1.5.0"
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
||||
default = ["sqlite_storage"]
|
||||
rocksdb_storage = ["rocksdb"]
|
||||
sqlite_storage = ["rusqlite", "refinery"]
|
||||
@@ -0,0 +1,142 @@
|
||||
# Ephemera - reliable broadcast protocol implementation
|
||||
|
||||
Ephemera does reliable broadcast for blocks.
|
||||
|
||||
## Short Overview
|
||||
|
||||
All Ephemera nodes accept messages submitted by clients. Node then gossips these to other nodes in the cluster. After certain interval,
|
||||
a node collects messages and produces a block. Then it does reliable broadcast for the block with other nodes in the cluster.
|
||||
|
||||
Ephemera doesn't have the concept of (decentralised) leader at the moment. It's up to an _Application_ to decide which block to use.
|
||||
For example in case of Nym-Api, it is the first block submitted to a "Smart Contract".
|
||||
|
||||
At the same time, the purpose of blocks is to reach consensus about which messages are included. It's just that Ephemera doesn't make the final decision,
|
||||
instead it leaves that to an _Application_.
|
||||
|
||||
## Main concepts
|
||||
|
||||
- **Node** - a single instance of Ephemera.
|
||||
- **Cluster** - a set of nodes participating in reliable broadcast.
|
||||
- **EphemeraMessage** - a message submitted by a client.
|
||||
- **Block** - a set of messages collected by a node.
|
||||
- **Application(ABCI)** - a trait which Ephemera users implement to accept messages and blocks.
|
||||
- check_tx
|
||||
- check_block
|
||||
- accept_block
|
||||
|
||||
## How to run
|
||||
|
||||
[README](../scripts/README.md)
|
||||
|
||||
## HTTP API
|
||||
|
||||
See [Rust](src/api/http/mod.rs)
|
||||
|
||||
### Endpoints
|
||||
|
||||
**NODE**
|
||||
- `/ephemera/node/health`
|
||||
- `/ephemera/node/config`
|
||||
|
||||
**BLOCKS**
|
||||
- `/ephemera/broadcast/block/{hash}`
|
||||
- `/ephemera/broadcast/block/height/{height}`
|
||||
- `/ephemera/broadcast/blocks/last`
|
||||
- `/ephemera/broadcast/block/certificates/{hash}`
|
||||
- `/ephemera/broadcast/block/broadcast_info/{hash}`
|
||||
|
||||
**GROUP**
|
||||
- `/ephemera/broadcast/group/info`
|
||||
|
||||
**MESSAGES**
|
||||
- `/ephemera/broadcast/submit_message`
|
||||
|
||||
**DHT**
|
||||
- `/ephemera/dht/query/{key}`
|
||||
- `/ephemera/dht/store`
|
||||
|
||||
## Rust API
|
||||
|
||||
Almost identical to HTTP API.
|
||||
|
||||
See [Rust](src/api/mod.rs)
|
||||
|
||||
## Application(Ephemera ABCI)
|
||||
|
||||
Cosmos style ABCI application hook
|
||||
- `check_tx`
|
||||
- `check_block`
|
||||
- `deliver_block`
|
||||
|
||||
See [Rust](src/api/application.rs)
|
||||
|
||||
## Examples
|
||||
|
||||
### Ephemera HTTP and WS external interfaces example/tests
|
||||
|
||||
See [README.md](../examples/http-ws-sync/README.md)
|
||||
|
||||
### Nym Api simulation
|
||||
|
||||
See [README.md](../examples/nym-api/README.md)
|
||||
|
||||
### http API example/tests
|
||||
|
||||
See [README.md](../examples/cluster-http-api/README.md)
|
||||
|
||||
### Membership over HTTP API example/tests
|
||||
|
||||
See [README.md](../examples/members_provider_http/README.md)
|
||||
|
||||
## About reliable broadcast and consensus
|
||||
|
||||
In blockchain technology blocks have two main purposes:
|
||||
1. To maintain chain of blocks, so that the validity of each block can be cryptographically verified by the previous blocks
|
||||
2. As a unit of consensus, each block contains a set of transactions/messages/actions that are agreed upon by
|
||||
the network. This set of transactions is chosen from the global set of all possible transactions that are pending.
|
||||
We call the set of transactions in a block consensus because the set of nodes trying to achieve global shared state
|
||||
agreed on this particular set of transactions.
|
||||
|
||||
Ephemera is not a blockchain. But it uses blocks to agree on the set of transactions in a block.
|
||||
But at the same time it doesn't behave like a blockchain consensus algorithm.
|
||||
We may say that it allows each application that uses Ephemera to "propose" something what can be
|
||||
afterwards to be used to achieve consensus.
|
||||
|
||||
### In Summary
|
||||
|
||||
1. Ephemera provides functionality to reach agreement on a single value between a set of nodes.
|
||||
2. Ephemera also provides the concept of a block, which application can take advantage of to reach consensus externally.
|
||||
|
||||
### Reliable broadcast, consensus and blocks
|
||||
|
||||
In distributed systems(including byzantine), we try to solve the problem of reaching to a commons state between a set of nodes.
|
||||
|
||||
One way to define this problem is using the following properties:
|
||||
1.
|
||||
1) Agreement: All nodes agree on the same value.(TODO clarify)
|
||||
2) Consensus: All nodes agree on the same value.(TODO clarify)
|
||||
2. Validity: All nodes agree on a value that is valid.
|
||||
3. Termination: All nodes eventually agree on a value.
|
||||
|
||||
Reliable broadcast ensures the properties of 1.1 and 1.2. It's left to a particular consensus algorithm to ensure the termination property.
|
||||
|
||||
One important feature of consensus in blockchain is that it guarantees total ordering of transactions.
|
||||
Reliable broadcast with blocks helps to ensure this total ordering.
|
||||
|
||||
### Ephemera specific properties
|
||||
|
||||
Because Ephemera doesn't use the idea of leader, we can say that it solves consensus partially.
|
||||
It allows each instance to create a block. And then it's up to an application to decide which block to use.
|
||||
|
||||
Also, as it doesn't implement a full consensus algorithm, it doesn't ensure the termination.
|
||||
There's no algorithm in place what tries to reach a consensus about a single block globally and sequentially
|
||||
in time.
|
||||
|
||||
When a block contains a single message, then it's semantically equivalent to a reliable broadcast.
|
||||
|
||||
But when a block contains multiple messages, then it can be part of a consensus process. Except that in Ephemera each node
|
||||
can create a block. To achieve consensus in a more traditional sense, it needs an application help if more strict
|
||||
consensus is required.
|
||||
|
||||
For example, Nym-Api allows each node to create a block but uses external coordinator(a smart contract)
|
||||
to decide which block to use.
|
||||
@@ -0,0 +1,12 @@
|
||||
use clap::Parser;
|
||||
|
||||
use ephemera::cli::Cli;
|
||||
use ephemera::logging;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
logging::init();
|
||||
|
||||
Cli::parse().execute().await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
CREATE TABLE IF NOT EXISTS blocks (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
block_hash TEXT NOT NULL UNIQUE,
|
||||
height TEXT NOT NULL UNIQUE,
|
||||
block BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS block_certificates (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
block_hash TEXT NOT NULL UNIQUE,
|
||||
certificates BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS block_broadcast_group (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
block_hash TEXT NOT NULL UNIQUE,
|
||||
members BLOB NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS block_merkle_tree (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
block_hash TEXT NOT NULL UNIQUE,
|
||||
merkle_tree BLOB NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,104 @@
|
||||
use log::trace;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::api::types::{ApiBlock, ApiEphemeraMessage};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum RemoveMessages {
|
||||
/// Remove all messages from the mempool
|
||||
All,
|
||||
/// Remove only inclued messages from the mempool
|
||||
Selected(Vec<ApiEphemeraMessage>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CheckBlockResult {
|
||||
/// Accept the block
|
||||
Accept,
|
||||
/// Reject the block with a reason.
|
||||
Reject,
|
||||
/// Reject the block and remove messages from the mempool
|
||||
RejectAndRemoveMessages(RemoveMessages),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct CheckBlockResponse {
|
||||
pub accept: bool,
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
//Just a placeholder for now
|
||||
#[error("ApplicationError: {0}")]
|
||||
Application(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Cosmos style ABCI application hook
|
||||
///
|
||||
/// These functions should be relatively fast, as they are called synchronously by Ephemera main loop.
|
||||
pub trait Application {
|
||||
/// It's called when receiving a new message from network before adding it to the mempool.
|
||||
/// It's up to the application to decide whether the message is valid or not.
|
||||
/// Basic check could be for example signature verification.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `message` - message to be checked
|
||||
///
|
||||
/// # Returns
|
||||
/// * `true` - if the message is valid
|
||||
/// * `false` - if the message is invalid
|
||||
///
|
||||
/// # Errors
|
||||
/// * `Error::General` - if there was an error during validation
|
||||
fn check_tx(&self, message: ApiEphemeraMessage) -> Result<bool>;
|
||||
|
||||
/// Ephemera produces new blocks with configured interval.
|
||||
/// Application can decide whether to accept the block or not.
|
||||
/// For example, if the block doesn't contain any transactions, it can be rejected.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block` - block to be checked
|
||||
///
|
||||
/// # Returns
|
||||
/// * `CheckBlockResult::Accept` - if the block is valid
|
||||
/// * `CheckBlockResult::Reject` - if the block is invalid
|
||||
/// * `CheckBlockResult::RejectAndRemoveMessages` - if the block is invalid and some messages should be removed from the mempool
|
||||
///
|
||||
/// # Errors
|
||||
/// * `Error::General` - if there was an error during validation
|
||||
fn check_block(&self, block: &ApiBlock) -> Result<CheckBlockResult>;
|
||||
|
||||
/// Deliver Block is called after block is confirmed by Ephemera and persisted to the storage.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block` - block to be delivered
|
||||
///
|
||||
/// # Errors
|
||||
/// * `Error::General` - if there was an error during validation
|
||||
fn deliver_block(&self, block: ApiBlock) -> Result<()>;
|
||||
}
|
||||
|
||||
/// Dummy application which doesn't do any validation.
|
||||
/// Might be useful for testing.
|
||||
#[derive(Default)]
|
||||
pub struct Dummy;
|
||||
|
||||
impl Application for Dummy {
|
||||
fn check_tx(&self, tx: ApiEphemeraMessage) -> Result<bool> {
|
||||
trace!("check_tx: {tx:?}");
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn check_block(&self, block: &ApiBlock) -> Result<CheckBlockResult> {
|
||||
trace!("accept_block: {block:?}");
|
||||
Ok(CheckBlockResult::Accept)
|
||||
}
|
||||
|
||||
fn deliver_block(&self, block: ApiBlock) -> Result<()> {
|
||||
trace!("deliver_block: {block:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::api::types::{ApiBlockBroadcastInfo, ApiBroadcastInfo, ApiHealth};
|
||||
use crate::ephemera_api::{
|
||||
ApiBlock, ApiCertificate, ApiDhtQueryRequest, ApiDhtQueryResponse, ApiDhtStoreRequest,
|
||||
ApiEphemeraConfig, ApiEphemeraMessage, ApiVerifyMessageInBlock,
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
Internal(#[from] reqwest::Error),
|
||||
#[error("Unexpected response: {status} {body}")]
|
||||
UnexpectedResponse {
|
||||
status: reqwest::StatusCode,
|
||||
body: String,
|
||||
},
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Client to interact with the node over HTTP api.
|
||||
pub struct Client {
|
||||
pub(crate) client: reqwest::Client,
|
||||
pub(crate) url: String,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
/// Create a http new client.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `url` - The url of the node api endpoint.
|
||||
#[must_use]
|
||||
pub fn new(url: String) -> Self {
|
||||
let client = reqwest::Client::new();
|
||||
Self { client, url }
|
||||
}
|
||||
|
||||
/// Create a new client.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `url` - The url of the node api endpoint.
|
||||
/// * `timeout_sec` - Request timeout in seconds.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the client cannot be created.
|
||||
#[must_use]
|
||||
pub fn new_with_timeout(url: String, timeout_sec: u64) -> Self {
|
||||
let client = reqwest::ClientBuilder::new()
|
||||
.timeout(Duration::from_secs(timeout_sec))
|
||||
.build()
|
||||
.unwrap();
|
||||
Self { client, url }
|
||||
}
|
||||
|
||||
/// Get the health of the node.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{Client, ApiHealth};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
///async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000".to_string());
|
||||
/// let health = client.health().await.unwrap();
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
/// * [`ApiHealth`] - The health of the node.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn health(&self) -> Result<ApiHealth> {
|
||||
self.query("ephemera/node/health").await
|
||||
}
|
||||
|
||||
/// Get the block by hash.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiBlock, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
///async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000".to_string());
|
||||
/// let block = client.get_block_by_hash("9D2LaY17rbnxfgKUbvcsJ5cB2BRHEd8fPJwsBnDHNGBX").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
/// * Option<[`ApiBlock`]> - The block.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_block_by_hash(&self, hash: &str) -> Result<Option<ApiBlock>> {
|
||||
let url = format!("ephemera/broadcast/block/{hash}",);
|
||||
self.query_optional(&url).await
|
||||
}
|
||||
|
||||
/// Get the block certificates by hash.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiCertificate, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000".to_string());
|
||||
/// let certificates = client.get_block_certificates("9D2LaY17rbnxfgKUbvcsJ5cB2BRHEd8fPJwsBnDHNGBX").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `hash` - The hash of the block.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Option<Vec<[`ApiCertificate`]>> - The block certificates.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_block_certificates(&self, hash: &str) -> Result<Option<Vec<ApiCertificate>>> {
|
||||
let url = format!("ephemera/broadcast/block/certificates/{hash}",);
|
||||
self.query_optional(&url).await
|
||||
}
|
||||
|
||||
/// Get the block by height.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiBlock, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let block = client.get_block_by_height(1).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `height` - The height of the block.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Option<[`ApiBlock`]> - The block.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_block_by_height(&self, height: u64) -> Result<Option<ApiBlock>> {
|
||||
let url = format!("ephemera/broadcast/block/height/{height}",);
|
||||
self.query_optional(&url).await
|
||||
}
|
||||
|
||||
/// Get the last block.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiBlock, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let block = client.get_last_block().await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
/// * [`ApiBlock`] - The last block.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_last_block(&self) -> Result<ApiBlock> {
|
||||
self.query("ephemera/broadcast/blocks/last").await
|
||||
}
|
||||
|
||||
/// Get the node configuration.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiEphemeraConfig, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let config = client.get_ephemera_config().await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
/// * [`ApiEphemeraConfig`] - The node configuration.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_ephemera_config(&self) -> Result<ApiEphemeraConfig> {
|
||||
self.query("ephemera/node/config").await
|
||||
}
|
||||
|
||||
/// Submit a message to the node.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::{ApiEphemeraMessage, Client};
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let message = unimplemented!("See how to create a ApiEphemeraMessage");
|
||||
/// client.submit_message(message).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `message` - The message to submit.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn submit_message(&self, message: ApiEphemeraMessage) -> Result<()> {
|
||||
let url = format!("{}/{}", self.url, "ephemera/broadcast/submit_message");
|
||||
let response = self.client.post(&url).json(&message).send().await?;
|
||||
if response.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::UnexpectedResponse {
|
||||
status: response.status(),
|
||||
body: response.text().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
///Store Key Value pair in the DHT.
|
||||
///
|
||||
/// # Example
|
||||
///```no_run
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let request = unimplemented!("See how to create a ApiDhtStoreRequest");
|
||||
/// client.store_in_dht(request).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request` - Key Value pair to store.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn store_in_dht(&self, request: ApiDhtStoreRequest) -> Result<()> {
|
||||
let url = format!("{}/{}", self.url, "ephemera/dht/store");
|
||||
let response = self.client.post(&url).json(&request).send().await?;
|
||||
if response.status().is_success() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::UnexpectedResponse {
|
||||
status: response.status(),
|
||||
body: response.text().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
///Store Key Value pair in the DHT.
|
||||
///
|
||||
/// # Example
|
||||
///```no_run
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let key = &[1, 2, 3];
|
||||
/// let value = &[4, 5, 6];
|
||||
/// client.store_in_dht_key_value(key, value).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
/// # Arguments
|
||||
/// * `key` - Key to use to store the value.
|
||||
/// * `value` - Value to store.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn store_in_dht_key_value(&self, key: &[u8], value: &[u8]) -> Result<()> {
|
||||
let request = ApiDhtStoreRequest::new(key, value);
|
||||
self.store_in_dht(request).await
|
||||
}
|
||||
|
||||
/// Query the DHT for a given key.
|
||||
///
|
||||
/// # Example
|
||||
///```no_run
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use ephemera::ephemera_api::{ApiDhtQueryRequest, Client};
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let request = ApiDhtQueryRequest::new(&[1, 2, 3]);
|
||||
/// let response = client.query_dht(request).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request` - Key to query.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Option<[`ApiDhtQueryResponse`]> - The value stored in the DHT for the given key.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn query_dht(
|
||||
&self,
|
||||
request: ApiDhtQueryRequest,
|
||||
) -> Result<Option<ApiDhtQueryResponse>> {
|
||||
let url = format!("ephemera/dht/query/{}", request.key_encoded());
|
||||
self.query_optional(&url).await
|
||||
}
|
||||
|
||||
/// Query the DHT for a given key.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let key = &[1, 2, 3];
|
||||
/// let response = client.query_dht_key(key).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - Key to query.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Option<[`ApiDhtQueryResponse`]> - The value stored in the DHT for the given key.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn query_dht_key(&self, key: &[u8]) -> Result<Option<ApiDhtQueryResponse>> {
|
||||
let request = ApiDhtQueryRequest::new(key);
|
||||
self.query_dht(request).await
|
||||
}
|
||||
|
||||
/// Get broadcast group info.
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let info = client.broadcast_info().await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Returns
|
||||
/// * [`ApiBroadcastInfo`] - The broadcast group info.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn broadcast_info(&self) -> Result<ApiBroadcastInfo> {
|
||||
self.query("ephemera/broadcast/group/info").await
|
||||
}
|
||||
|
||||
/// Get block broadcast info
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let info = client.get_block_broadcast_info("hash").await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
///```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `hash` - Hash of the block to query.
|
||||
///
|
||||
/// # Returns
|
||||
/// * Some([`ApiBlockBroadcastInfo`]) - The block broadcast info.
|
||||
/// * None - If the block is not found.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn get_block_broadcast_info(
|
||||
&self,
|
||||
hash: &str,
|
||||
) -> Result<Option<ApiBlockBroadcastInfo>> {
|
||||
let url = format!("ephemera/broadcast/block/broadcast_info/{hash}",);
|
||||
self.query_optional(&url).await
|
||||
}
|
||||
|
||||
/// Verifies if given message is in block identified by block hash
|
||||
///
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use ephemera::ephemera_api::Client;
|
||||
///
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// let client = Client::new("http://localhost:7000/".to_string());
|
||||
/// let is_in_block = client.verify_message_in_block("block_hash", "message_hash", 0).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block_hash` - Hash of the block to query.
|
||||
/// * `message_hash` - Hash of the message to query.
|
||||
/// * `message_index` - Index of the message in the block.
|
||||
///
|
||||
/// # Returns
|
||||
/// * bool - True if the message is in the block, false otherwise.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the request fails.
|
||||
pub async fn verify_message_in_block(
|
||||
&self,
|
||||
block_hash: &str,
|
||||
message_hash: &str,
|
||||
message_index: usize,
|
||||
) -> Result<bool> {
|
||||
let request = ApiVerifyMessageInBlock::new(
|
||||
block_hash.to_string(),
|
||||
message_hash.to_string(),
|
||||
message_index,
|
||||
);
|
||||
|
||||
let url = format!("{}/{}", self.url, "ephemera/messages/verify");
|
||||
let response = self.client.post(&url).json(&request).send().await?;
|
||||
if response.status().is_success() {
|
||||
let body = response.json::<bool>().await?;
|
||||
Ok(body)
|
||||
} else {
|
||||
Err(Error::UnexpectedResponse {
|
||||
status: response.status(),
|
||||
body: response.text().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn query_optional<T: for<'de> serde::Deserialize<'de>>(
|
||||
&self,
|
||||
path: &str,
|
||||
) -> Result<Option<T>> {
|
||||
let url = format!("{}/{}", self.url, path);
|
||||
match self.client.get(&url).send().await {
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
let body = response.json::<T>().await?;
|
||||
Ok(Some(body))
|
||||
} else if response.status() == reqwest::StatusCode::NOT_FOUND {
|
||||
Ok(None)
|
||||
} else {
|
||||
return Err(Error::UnexpectedResponse {
|
||||
status: response.status(),
|
||||
body: response.text().await?,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn query<T: for<'de> serde::Deserialize<'de>>(&self, path: &str) -> Result<T> {
|
||||
let url = format!("{}/{}", self.url, path);
|
||||
match self.client.get(&url).send().await {
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
let body = response.json::<T>().await?;
|
||||
Ok(body)
|
||||
} else {
|
||||
Err(Error::UnexpectedResponse {
|
||||
status: response.status(),
|
||||
body: response.text().await?,
|
||||
})
|
||||
}
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use actix_web::{dev::Server, http::KeepAlive, web::Data, App, HttpServer};
|
||||
use log::info;
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::api::CommandExecutor;
|
||||
use crate::core::builder::NodeInfo;
|
||||
|
||||
pub(crate) mod client;
|
||||
pub(crate) mod query;
|
||||
pub(crate) mod submit;
|
||||
|
||||
/// Starts the HTTP server.
|
||||
pub(crate) fn init(node_info: &NodeInfo, api: CommandExecutor) -> anyhow::Result<Server> {
|
||||
print_startup_messages(node_info);
|
||||
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.app_data(Data::new(api.clone()))
|
||||
.service(query::health)
|
||||
.service(query::block_by_hash)
|
||||
.service(query::block_certificates)
|
||||
.service(query::block_by_height)
|
||||
.service(query::block_broadcast_group)
|
||||
.service(query::last_block)
|
||||
.service(query::node_config)
|
||||
.service(query::query_dht)
|
||||
.service(query::broadcast_info)
|
||||
.service(submit::submit_message)
|
||||
.service(submit::store_in_dht)
|
||||
.service(submit::verify_message_in_block)
|
||||
.service(swagger_ui())
|
||||
})
|
||||
.keep_alive(KeepAlive::Os)
|
||||
.bind((node_info.ip.as_str(), node_info.initial_config.http.port))?
|
||||
.run();
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
/// Builds the Swagger UI.
|
||||
///
|
||||
/// Note that all routes you want Swagger docs for must be in the `paths` annotation.
|
||||
fn swagger_ui() -> SwaggerUi {
|
||||
use crate::api::types;
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
query::health,
|
||||
query::block_by_hash,
|
||||
query::block_certificates,
|
||||
query::block_by_height,
|
||||
query::last_block,
|
||||
query::block_broadcast_group,
|
||||
query::node_config,
|
||||
query::query_dht,
|
||||
query::broadcast_info,
|
||||
submit::submit_message,
|
||||
submit::store_in_dht,
|
||||
submit::verify_message_in_block
|
||||
),
|
||||
components(schemas(
|
||||
types::ApiBlock,
|
||||
types::ApiEphemeraMessage,
|
||||
types::ApiCertificate,
|
||||
types::ApiSignature,
|
||||
types::ApiPublicKey,
|
||||
types::ApiHealth,
|
||||
types::HealthStatus,
|
||||
types::ApiEphemeraConfig,
|
||||
types::ApiDhtStoreRequest,
|
||||
types::ApiDhtQueryRequest,
|
||||
types::ApiDhtQueryResponse,
|
||||
types::ApiBroadcastInfo,
|
||||
types::ApiVerifyMessageInBlock,
|
||||
))
|
||||
)]
|
||||
struct ApiDoc;
|
||||
SwaggerUi::new("/swagger-ui/{_:.*}").url("/api-doc/openapi.json", ApiDoc::openapi())
|
||||
}
|
||||
|
||||
/// Prints messages saying which ports HTTP is running on, and some helpful pointers
|
||||
/// `OpenAPI` and `Swagger UI` endpoints.
|
||||
fn print_startup_messages(info: &NodeInfo) {
|
||||
let http_root = info.api_address_http();
|
||||
info!("Server running on {}", http_root);
|
||||
info!("Swagger UI: {}/swagger-ui/", http_root);
|
||||
info!("OpenAPI spec is at: {}/api-doc/openapi.json", http_root);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use log::error;
|
||||
|
||||
use crate::{
|
||||
api::{types::ApiHealth, types::HealthStatus::Healthy, CommandExecutor},
|
||||
ephemera_api::{ApiDhtQueryRequest, ApiDhtQueryResponse},
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Endpoint to check if the server is running")),
|
||||
)]
|
||||
#[get("/ephemera/node/health")]
|
||||
#[allow(clippy::unused_async)]
|
||||
pub(crate) async fn health() -> impl Responder {
|
||||
HttpResponse::Ok().json(ApiHealth { status: Healthy })
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get current broadcast group"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
)]
|
||||
#[get("/ephemera/broadcast/group/info")]
|
||||
pub(crate) async fn broadcast_info(api: web::Data<CommandExecutor>) -> impl Responder {
|
||||
match api.get_broadcast_info().await {
|
||||
Ok(group) => HttpResponse::Ok().json(group),
|
||||
Err(err) => {
|
||||
error!("Failed to get current broadcast group: {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "GET block by hash"),
|
||||
(status = 404, description = "Block not found"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("hash", description = "Block hash")),
|
||||
)]
|
||||
#[get("/ephemera/broadcast/block/{hash}")]
|
||||
pub(crate) async fn block_by_hash(
|
||||
hash: web::Path<String>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> impl Responder {
|
||||
match api.get_block_by_id(hash.into_inner()).await {
|
||||
Ok(Some(block)) => HttpResponse::Ok().json(block),
|
||||
Ok(_) => HttpResponse::NotFound().json("Block not found"),
|
||||
Err(err) => {
|
||||
error!("Failed to get block by hash: {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get block signatures"),
|
||||
(status = 404, description = "Certificates not found"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("hash", description = "Block hash")),
|
||||
)]
|
||||
#[get("/ephemera/broadcast/block/certificates/{hash}")]
|
||||
pub(crate) async fn block_certificates(
|
||||
hash: web::Path<String>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> impl Responder {
|
||||
let id = hash.into_inner();
|
||||
match api.get_block_certificates(id.clone()).await {
|
||||
Ok(Some(signatures)) => HttpResponse::Ok().json(signatures),
|
||||
Ok(_) => HttpResponse::NotFound().json("Certificates not found"),
|
||||
Err(err) => {
|
||||
error!("Failed to get signatures {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get block by height"),
|
||||
(status = 404, description = "Block not found"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("height", description = "Block height")),
|
||||
)]
|
||||
#[get("/ephemera/broadcast/block/height/{height}")]
|
||||
pub(crate) async fn block_by_height(
|
||||
height: web::Path<u64>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> impl Responder {
|
||||
match api.get_block_by_height(height.into_inner()).await {
|
||||
Ok(Some(block)) => HttpResponse::Ok().json(block),
|
||||
Ok(_) => HttpResponse::NotFound().json("Block not found"),
|
||||
Err(err) => {
|
||||
error!("Failed to get block {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get last block"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
)]
|
||||
//Need to use plural(blocks), otherwise overlaps with block_by_id route
|
||||
#[get("/ephemera/broadcast/blocks/last")]
|
||||
pub(crate) async fn last_block(api: web::Data<CommandExecutor>) -> impl Responder {
|
||||
match api.get_last_block().await {
|
||||
Ok(block) => HttpResponse::Ok().json(block),
|
||||
Err(err) => {
|
||||
error!("Failed to get block {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get block broadcast group"),
|
||||
(status = 404, description = "Block not found"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("hash", description = "Block hash")),
|
||||
)]
|
||||
#[get("/ephemera/broadcast/block/broadcast_info/{hash}")]
|
||||
pub(crate) async fn block_broadcast_group(
|
||||
hash: web::Path<String>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> impl Responder {
|
||||
let hash = hash.into_inner();
|
||||
match api.get_block_broadcast_info(hash).await {
|
||||
Ok(Some(group)) => HttpResponse::Ok().json(group),
|
||||
Ok(_) => HttpResponse::NotFound().json("Block not found"),
|
||||
Err(err) => {
|
||||
error!("Failed to get block broadcast group {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Get node config"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
)]
|
||||
#[get("/ephemera/node/config")]
|
||||
pub(crate) async fn node_config(api: web::Data<CommandExecutor>) -> impl Responder {
|
||||
match api.get_node_config().await {
|
||||
Ok(config) => HttpResponse::Ok().json(config),
|
||||
Err(err) => {
|
||||
error!("Failed to get node config {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
responses(
|
||||
(status = 200, description = "Query dht"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("query", description = "Dht query")),
|
||||
)]
|
||||
#[get("/ephemera/dht/query/{key}")]
|
||||
pub(crate) async fn query_dht(
|
||||
api: web::Data<CommandExecutor>,
|
||||
key: web::Path<String>,
|
||||
) -> impl Responder {
|
||||
let key = ApiDhtQueryRequest::parse_key(key.into_inner().as_str());
|
||||
|
||||
match api.query_dht(key).await {
|
||||
Ok(Some((key, value))) => {
|
||||
let response = ApiDhtQueryResponse::new(key, value);
|
||||
HttpResponse::Ok().json(response)
|
||||
}
|
||||
Ok(_) => HttpResponse::NotFound().json("Not found"),
|
||||
Err(err) => {
|
||||
error!("Failed to query dht {err}",);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use actix_web::{post, web, HttpResponse};
|
||||
use log::{debug, error};
|
||||
|
||||
use crate::api::types::ApiVerifyMessageInBlock;
|
||||
use crate::api::{
|
||||
types::{ApiDhtStoreRequest, ApiEphemeraMessage},
|
||||
ApiError, CommandExecutor,
|
||||
};
|
||||
|
||||
#[utoipa::path(
|
||||
request_body = ApiEphemeraMessage,
|
||||
responses(
|
||||
(status = 200, description = "Send a message to an Ephemera node which will be broadcast to the network"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(("message", description = "Message to send"))
|
||||
)]
|
||||
#[post("/ephemera/broadcast/submit_message")]
|
||||
pub(crate) async fn submit_message(
|
||||
message: web::Json<ApiEphemeraMessage>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> HttpResponse {
|
||||
match api.send_ephemera_message(message.into_inner()).await {
|
||||
Ok(_) => HttpResponse::Ok().json("Message submitted"),
|
||||
Err(err) => {
|
||||
if let ApiError::DuplicateMessage = err {
|
||||
debug!("Message already submitted {err:?}");
|
||||
HttpResponse::BadRequest().json("Message already submitted")
|
||||
} else {
|
||||
error!("Error submitting message: {}", err);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
request_body = ApiDhtStoreRequest,
|
||||
responses(
|
||||
(status = 200, description = "Request to store a value in the DHT"),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(
|
||||
("request", description = "Dht store request")
|
||||
)
|
||||
)]
|
||||
#[post("/ephemera/dht/store")]
|
||||
pub(crate) async fn store_in_dht(
|
||||
request: web::Json<ApiDhtStoreRequest>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> HttpResponse {
|
||||
let request = request.into_inner();
|
||||
|
||||
let key = request.key();
|
||||
let value = request.value();
|
||||
|
||||
match api.store_in_dht(key, value).await {
|
||||
Ok(_) => HttpResponse::Ok().json("Store request submitted"),
|
||||
Err(err) => {
|
||||
error!("Error storing in dht: {}", err);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
request_body = ApiVerifyMessageInBlock,
|
||||
responses(
|
||||
(status = 200, description = "Verifies if given message is in block identified by block hash.\
|
||||
Returns true if message is in block, false otherwise. False can also mean that block or message \
|
||||
does not exist in that block."),
|
||||
(status = 500, description = "Server failed to process request")),
|
||||
params(
|
||||
("request", description = "Verify message request")
|
||||
)
|
||||
)]
|
||||
#[post("/ephemera/messages/verify")]
|
||||
pub(crate) async fn verify_message_in_block(
|
||||
request: web::Json<ApiVerifyMessageInBlock>,
|
||||
api: web::Data<CommandExecutor>,
|
||||
) -> HttpResponse {
|
||||
let request = request.into_inner();
|
||||
match api.verify_message_in_block(request).await {
|
||||
Ok(valid) => HttpResponse::Ok().json(valid),
|
||||
Err(err) => {
|
||||
error!("Error verifying message: {}", err);
|
||||
HttpResponse::InternalServerError().json("Server failed to process request")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
//! # Ephemera API
|
||||
//!
|
||||
//! This module contains all the types and functions available as part of Ephemera public API.
|
||||
//!
|
||||
//! This API is also available over HTTP.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use log::{error, trace};
|
||||
use tokio::sync::{
|
||||
mpsc::{channel, Receiver, Sender},
|
||||
oneshot,
|
||||
};
|
||||
|
||||
use crate::api::types::{
|
||||
ApiBlock, ApiBlockBroadcastInfo, ApiBroadcastInfo, ApiCertificate, ApiEphemeraConfig,
|
||||
ApiEphemeraMessage, ApiError, ApiVerifyMessageInBlock,
|
||||
};
|
||||
|
||||
pub(crate) mod application;
|
||||
pub(crate) mod http;
|
||||
pub(crate) mod types;
|
||||
|
||||
/// Kademlia DHT key
|
||||
pub(crate) type DhtKey = Vec<u8>;
|
||||
|
||||
/// Kademlia DHT value
|
||||
pub(crate) type DhtValue = Vec<u8>;
|
||||
|
||||
/// Kademlia DHT key/value pair
|
||||
pub(crate) type DhtKV = (DhtKey, DhtValue);
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, ApiError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ToEphemeraApiCmd {
|
||||
SubmitEphemeraMessage(Box<ApiEphemeraMessage>, oneshot::Sender<Result<()>>),
|
||||
QueryBlockByHeight(u64, oneshot::Sender<Result<Option<ApiBlock>>>),
|
||||
QueryBlockByHash(String, oneshot::Sender<Result<Option<ApiBlock>>>),
|
||||
QueryLastBlock(oneshot::Sender<Result<ApiBlock>>),
|
||||
QueryBlockCertificates(String, oneshot::Sender<Result<Option<Vec<ApiCertificate>>>>),
|
||||
QueryDht(DhtKey, oneshot::Sender<Result<Option<DhtKV>>>),
|
||||
StoreInDht(DhtKey, DhtValue, oneshot::Sender<Result<()>>),
|
||||
QueryEphemeraConfig(oneshot::Sender<Result<ApiEphemeraConfig>>),
|
||||
QueryBroadcastGroup(oneshot::Sender<Result<ApiBroadcastInfo>>),
|
||||
QueryBlockBroadcastInfo(
|
||||
String,
|
||||
oneshot::Sender<Result<Option<ApiBlockBroadcastInfo>>>,
|
||||
),
|
||||
VerifyMessageInBlock(String, String, usize, oneshot::Sender<Result<bool>>),
|
||||
}
|
||||
|
||||
impl Display for ToEphemeraApiCmd {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ToEphemeraApiCmd::SubmitEphemeraMessage(message, _) => {
|
||||
write!(f, "SubmitEphemeraMessage({message})",)
|
||||
}
|
||||
ToEphemeraApiCmd::QueryBlockByHeight(height, _) => {
|
||||
write!(f, "QueryBlockByHeight({height})",)
|
||||
}
|
||||
ToEphemeraApiCmd::QueryBlockByHash(hash, _) => write!(f, "QueryBlockByHash({hash})",),
|
||||
ToEphemeraApiCmd::QueryLastBlock(_) => write!(f, "QueryLastBlock"),
|
||||
ToEphemeraApiCmd::QueryBlockCertificates(id, _) => {
|
||||
write!(f, "QueryBlockSignatures{id}")
|
||||
}
|
||||
ToEphemeraApiCmd::QueryDht(_, _) => {
|
||||
write!(f, "QueryDht")
|
||||
}
|
||||
ToEphemeraApiCmd::StoreInDht(_, _, _) => {
|
||||
write!(f, "StoreInDht")
|
||||
}
|
||||
ToEphemeraApiCmd::QueryEphemeraConfig(_) => {
|
||||
write!(f, "EphemeraConfig")
|
||||
}
|
||||
ToEphemeraApiCmd::QueryBroadcastGroup(_) => {
|
||||
write!(f, "BroadcastGroup")
|
||||
}
|
||||
ToEphemeraApiCmd::QueryBlockBroadcastInfo(hash, ..) => {
|
||||
write!(f, "BlockBroadcastInfo({hash})")
|
||||
}
|
||||
ToEphemeraApiCmd::VerifyMessageInBlock(block_id, message_id, height, _) => {
|
||||
write!(
|
||||
f,
|
||||
"VerifyMessageInBlock({block_id}, {message_id}, {height})",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ApiListener {
|
||||
pub(crate) messages_rcv: Receiver<ToEphemeraApiCmd>,
|
||||
}
|
||||
|
||||
impl ApiListener {
|
||||
pub(crate) fn new(messages_rcv: Receiver<ToEphemeraApiCmd>) -> Self {
|
||||
Self { messages_rcv }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CommandExecutor {
|
||||
pub(crate) commands_channel: Sender<ToEphemeraApiCmd>,
|
||||
}
|
||||
|
||||
impl CommandExecutor {
|
||||
pub(crate) fn new() -> (CommandExecutor, ApiListener) {
|
||||
let (commands_channel, signed_messages_rcv) = channel(100);
|
||||
let api_listener = ApiListener::new(signed_messages_rcv);
|
||||
let api = CommandExecutor { commands_channel };
|
||||
(api, api_listener)
|
||||
}
|
||||
|
||||
/// Returns block with given id if it exists
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block_id` - Block id
|
||||
///
|
||||
/// # Returns
|
||||
/// * `ApiBlock` - Block
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_block_by_id(&self, block_id: String) -> Result<Option<ApiBlock>> {
|
||||
trace!("get_block_by_id({:?})", block_id);
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockByHash(block_id, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns block with given height if it exists
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `height` - Block height
|
||||
///
|
||||
/// # Returns
|
||||
/// * `ApiBlock` - Block
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_block_by_height(&self, height: u64) -> Result<Option<ApiBlock>> {
|
||||
trace!("get_block_by_height({:?})", height);
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockByHeight(height, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns last block. Which has maximum height and is stored in database
|
||||
///
|
||||
/// # Returns
|
||||
/// * `ApiBlock` - Last block
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_last_block(&self) -> Result<ApiBlock> {
|
||||
trace!("get_last_block()");
|
||||
self.send_and_wait_response(ToEphemeraApiCmd::QueryLastBlock)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns signatures for given block id
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block_hash` - Block id
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Vec<ApiCertificate>` - Certificates
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_block_certificates(
|
||||
&self,
|
||||
block_hash: String,
|
||||
) -> Result<Option<Vec<ApiCertificate>>> {
|
||||
trace!("get_block_certificates({block_hash:?})",);
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockCertificates(block_hash, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Queries DHT for given key
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - DHT key
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some((key, value))` - If key is found
|
||||
/// * `None` - If key is not found
|
||||
pub async fn query_dht(&self, key: DhtKey) -> Result<Option<(DhtKey, DhtValue)>> {
|
||||
trace!("get_dht({key:?})");
|
||||
//TODO: this needs timeout(somewhere around dht query functionality)
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryDht(key, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Stores given key-value pair in DHT
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `key` - DHT key
|
||||
/// * `value` - DHT value
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn store_in_dht(&self, key: DhtKey, value: DhtValue) -> Result<()> {
|
||||
trace!("store_in_dht({key:?}, {value:?})");
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::StoreInDht(key, value, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns node configuration
|
||||
///
|
||||
/// # Returns
|
||||
/// * `ApiEphemeraConfig` - Node configuration
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_node_config(&self) -> Result<ApiEphemeraConfig> {
|
||||
trace!("get_node_config()");
|
||||
self.send_and_wait_response(ToEphemeraApiCmd::QueryEphemeraConfig)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns broadcast group
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
///
|
||||
/// # Return
|
||||
/// * `ApiBroadcastInfo` - Broadcast group
|
||||
pub async fn get_broadcast_info(&self) -> Result<ApiBroadcastInfo> {
|
||||
trace!("get_broadcast_group()");
|
||||
self.send_and_wait_response(ToEphemeraApiCmd::QueryBroadcastGroup)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Returns block broadcast info.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block_hash` - Block hash
|
||||
///
|
||||
/// # Return
|
||||
/// * `ApiBlockBroadcastInfo` - Block broadcast info
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn get_block_broadcast_info(
|
||||
&self,
|
||||
block_hash: String,
|
||||
) -> Result<Option<ApiBlockBroadcastInfo>> {
|
||||
trace!("get_broadcast_group()");
|
||||
self.send_and_wait_response(|tx| ToEphemeraApiCmd::QueryBlockBroadcastInfo(block_hash, tx))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Send a message to Ephemera which should then be included in mempool and broadcast to all peers
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `message` - Message to be sent
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn send_ephemera_message(&self, message: ApiEphemeraMessage) -> Result<()> {
|
||||
trace!("send_ephemera_message({message})",);
|
||||
self.send_and_wait_response(|tx| {
|
||||
ToEphemeraApiCmd::SubmitEphemeraMessage(message.into(), tx)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Verifies if given message is in block identified by block hash
|
||||
/// Returns true if message is in block, false otherwise. False can also mean that block or message
|
||||
/// does not exist.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `request` - Message and block hash
|
||||
///
|
||||
/// # Errors
|
||||
/// * `ApiError::InternalError` - If there is an internal error
|
||||
pub async fn verify_message_in_block(&self, request: ApiVerifyMessageInBlock) -> Result<bool> {
|
||||
trace!("verify_message_in_block({request})",);
|
||||
let block_hash = request.block_hash;
|
||||
let message_hash = request.message_hash;
|
||||
let index = request.message_index;
|
||||
self.send_and_wait_response(|tx| {
|
||||
ToEphemeraApiCmd::VerifyMessageInBlock(block_hash, message_hash, index, tx)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn send_and_wait_response<F, R>(&self, f: F) -> Result<R>
|
||||
where
|
||||
F: FnOnce(oneshot::Sender<Result<R>>) -> ToEphemeraApiCmd,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let (tx, rcv) = oneshot::channel();
|
||||
let cmd = f(tx);
|
||||
if let Err(e) = self.commands_channel.send(cmd).await {
|
||||
error!("Failed to send command to Ephemera: {e:?}",);
|
||||
return Err(ApiError::Internal(
|
||||
"Failed to receive response from Ephemera".to_string(),
|
||||
));
|
||||
}
|
||||
rcv.await.map_err(|e| {
|
||||
error!("Failed to receive response from Ephemera: {e:?}",);
|
||||
ApiError::Internal("Failed to receive response from Ephemera".to_string())
|
||||
})?
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,659 @@
|
||||
//! This module contains all the types that are used in the API.
|
||||
//!
|
||||
//! - `ApiEphemeraMessage`
|
||||
//! - `RawApiEphemeraMessage`
|
||||
//! - `ApiBlock`
|
||||
//! - `ApiCertificate`
|
||||
//! - `Health`
|
||||
//! - `ApiError`
|
||||
//! - `ApiEphemeraConfig`
|
||||
//! - `ApiDhtQueryRequest`
|
||||
//! - `ApiDhtQueryResponse`
|
||||
//! - `ApiDhtStoreRequest`
|
||||
//! - `ApiBroadcastInfo`
|
||||
//! - `ApiBlockBroadcastInfo`
|
||||
//! - `ApiVerifyMessageInBlock`
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Display;
|
||||
|
||||
use array_bytes::{bytes2hex, hex2bytes};
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::peer::{PeerId, ToPeerId};
|
||||
use crate::utilities::codec::{Codec, DecodingError, EncodingError, EphemeraCodec};
|
||||
use crate::{
|
||||
block::types::{block::Block, block::BlockHeader, message::EphemeraMessage},
|
||||
codec::{Decode, Encode},
|
||||
crypto::{Keypair, PublicKey},
|
||||
ephemera_api,
|
||||
utilities::{
|
||||
crypto::{Certificate, Signature},
|
||||
time::EphemeraTime,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ApiError {
|
||||
#[error("Application rejected ephemera message")]
|
||||
ApplicationRejectedMessage,
|
||||
#[error("Duplicate message")]
|
||||
DuplicateMessage,
|
||||
#[error("Invalid hash: {0}")]
|
||||
InvalidHash(String),
|
||||
#[error("ApplicationError: {0}")]
|
||||
Application(#[from] ephemera_api::ApplicationError),
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
/// # Ephemera message.
|
||||
///
|
||||
/// A message submitted to an Ephemera node will be gossiped to other nodes.
|
||||
/// And will be eventually included in a Ephemera block.
|
||||
///
|
||||
/// It needs to signed by the sender. The signature is included in the certificate.
|
||||
///
|
||||
/// The fields of the message what are signed:
|
||||
/// - timestamp
|
||||
/// - label
|
||||
/// - data
|
||||
///
|
||||
/// Currently it's up provided [`ephemera_api::application::Application`] to verify the signature.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiEphemeraMessage {
|
||||
/// The timestamp of the message.
|
||||
pub timestamp: u64,
|
||||
/// The label of the message. It can be used to identify the type of a message for example.
|
||||
pub label: String,
|
||||
/// The data of the message. It is application specific.
|
||||
pub data: Vec<u8>,
|
||||
/// The certificate of the message. All messages are required to be signed.
|
||||
pub certificate: ApiCertificate,
|
||||
}
|
||||
|
||||
impl ApiEphemeraMessage {
|
||||
#[must_use]
|
||||
pub fn new(raw_message: RawApiEphemeraMessage, certificate: ApiCertificate) -> Self {
|
||||
Self {
|
||||
timestamp: raw_message.timestamp,
|
||||
label: raw_message.label,
|
||||
data: raw_message.data,
|
||||
certificate,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the message hash.
|
||||
///
|
||||
/// # Errors
|
||||
/// - If internal hash function fails.
|
||||
pub fn hash(&self) -> anyhow::Result<String> {
|
||||
let em = EphemeraMessage::from(self.clone());
|
||||
let hash = em.hash_with_default_hasher()?.to_string();
|
||||
Ok(hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// `RawApiEphemeraMessage` contains the fields of the `ApiEphemeraMessage` that are signed.
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct RawApiEphemeraMessage {
|
||||
/// The timestamp of the message. It's initialized when the message is created.
|
||||
/// It uses UTC time.
|
||||
pub timestamp: u64,
|
||||
/// The label of the message. It can be used to identify the type of a message without decoding full data.
|
||||
pub label: String,
|
||||
/// The data of the message. It is application specific.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl RawApiEphemeraMessage {
|
||||
#[must_use]
|
||||
pub fn new(label: String, data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
timestamp: EphemeraTime::now(),
|
||||
label,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
/// Signs the message with the given keypair.
|
||||
///
|
||||
/// # Signing example
|
||||
///
|
||||
/// ```
|
||||
/// use ephemera::codec::Encode;
|
||||
/// use ephemera::crypto::{EphemeraKeypair, EphemeraPublicKey, Keypair};
|
||||
/// use ephemera::ephemera_api::{ApiEphemeraMessage, RawApiEphemeraMessage};
|
||||
///
|
||||
/// let keypair = Keypair::generate(None);
|
||||
/// let raw_message = RawApiEphemeraMessage::new("test".to_string(), vec![]);
|
||||
///
|
||||
/// let signed_message:ApiEphemeraMessage = raw_message.sign(&keypair).unwrap();
|
||||
///
|
||||
/// assert_eq!(signed_message.certificate.public_key, keypair.public_key().into());
|
||||
///
|
||||
/// let bytes = raw_message.encode().unwrap();
|
||||
/// assert!(keypair.public_key().verify(&bytes, &signed_message.certificate.signature.into()));
|
||||
/// ```
|
||||
///
|
||||
/// # Errors
|
||||
/// - If the message can't be encoded.
|
||||
/// - If the message can't be signed.
|
||||
pub fn sign(&self, keypair: &Keypair) -> anyhow::Result<ApiEphemeraMessage> {
|
||||
let certificate = Certificate::prepare(keypair, &self)?;
|
||||
let message = ApiEphemeraMessage::new(self.clone(), certificate.into());
|
||||
Ok(message)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ApiBlockHeader {
|
||||
/// The timestamp of the block. It's initialized when the block is created.
|
||||
/// It uses UTC time.
|
||||
pub timestamp: u64,
|
||||
/// The PeerId of the block producer instance.
|
||||
pub creator: PeerId,
|
||||
/// The height of the block.
|
||||
pub height: u64,
|
||||
/// The hash of the current block.
|
||||
pub hash: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiBlock {
|
||||
pub header: ApiBlockHeader,
|
||||
pub messages: Vec<ApiEphemeraMessage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct ApiRawBlock {
|
||||
pub(crate) header: ApiBlockHeader,
|
||||
pub(crate) messages: Vec<ApiEphemeraMessage>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiCertificate {
|
||||
pub signature: ApiSignature,
|
||||
pub public_key: ApiPublicKey,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiSignature(pub(crate) Signature);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiPublicKey(pub(crate) PublicKey);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiEphemeraConfig {
|
||||
/// The address of the node. It's the address what Ephemera instance uses to communicate with other nodes.
|
||||
pub protocol_address: String,
|
||||
/// The HTTP API address of the node.
|
||||
pub api_address: String,
|
||||
/// The WebSocket address of the node.
|
||||
pub websocket_address: String,
|
||||
/// Node's public key.
|
||||
///
|
||||
/// # Converting to string and back example
|
||||
/// ```
|
||||
/// use ephemera::crypto::{EphemeraKeypair, Keypair, PublicKey};
|
||||
///
|
||||
/// let keypair = Keypair::generate(None);
|
||||
/// let public_key = keypair.public_key().to_string();
|
||||
///
|
||||
/// let from_str = public_key.parse::<PublicKey>().unwrap();
|
||||
///
|
||||
/// assert_eq!(keypair.public_key(), from_str);
|
||||
/// ```
|
||||
pub public_key: String,
|
||||
/// True if the node is a block producer. It's a configuration option.
|
||||
pub block_producer: bool,
|
||||
/// The interval of block creation in seconds. It's a configuration option.
|
||||
pub block_creation_interval_sec: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiDhtQueryRequest {
|
||||
/// The key to query for in hex format.
|
||||
key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiDhtQueryResponse {
|
||||
/// The key that was queried for in hex format.
|
||||
key: String,
|
||||
/// The value that was stored under the queried key in hex format.
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub enum HealthStatus {
|
||||
Healthy,
|
||||
Unhealthy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiHealth {
|
||||
pub(crate) status: HealthStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiDhtStoreRequest {
|
||||
/// The key to store the value under in hex format.
|
||||
key: String,
|
||||
/// The value to store in hex format.
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiBroadcastInfo {
|
||||
/// The PeerId of the local node.
|
||||
pub local_peer_id: PeerId,
|
||||
/// The list of the current members of the network.
|
||||
pub current_members: HashSet<PeerId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiBlockBroadcastInfo {
|
||||
pub local_peer_id: PeerId,
|
||||
pub broadcast_group: Vec<PeerId>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, ToSchema)]
|
||||
pub struct ApiVerifyMessageInBlock {
|
||||
pub block_hash: String,
|
||||
pub message_hash: String,
|
||||
pub message_index: usize,
|
||||
}
|
||||
|
||||
impl ApiVerifyMessageInBlock {
|
||||
#[must_use]
|
||||
pub fn new(block_hash: String, message_hash: String, message_index: usize) -> Self {
|
||||
Self {
|
||||
block_hash,
|
||||
message_hash,
|
||||
message_index,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiVerifyMessageInBlock {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{{ block_hash: {}, message_hash: {} }}",
|
||||
self.block_hash, self.message_hash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiBlockBroadcastInfo {
|
||||
pub(crate) fn new(local_peer_id: PeerId, broadcast_group: Vec<PeerId>) -> Self {
|
||||
Self {
|
||||
local_peer_id,
|
||||
broadcast_group,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiBroadcastInfo {
|
||||
pub(crate) fn new(current_members: HashSet<PeerId>, local_peer_id: PeerId) -> Self {
|
||||
Self {
|
||||
local_peer_id,
|
||||
current_members,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiBroadcastInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let current_members = self.current_members.iter().map(ToString::to_string);
|
||||
write!(
|
||||
f,
|
||||
"{{ local_peer_id: {}, current_members: {current_members:?} }}",
|
||||
self.local_peer_id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiEphemeraMessage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ApiEphemeraMessage(timestamp: {}, label: {})",
|
||||
self.timestamp, self.label,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiEphemeraMessage> for RawApiEphemeraMessage {
|
||||
fn from(message: ApiEphemeraMessage) -> Self {
|
||||
RawApiEphemeraMessage {
|
||||
timestamp: message.timestamp,
|
||||
label: message.label,
|
||||
data: message.data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiEphemeraMessage> for EphemeraMessage {
|
||||
fn from(message: ApiEphemeraMessage) -> Self {
|
||||
Self {
|
||||
timestamp: message.timestamp,
|
||||
label: message.label,
|
||||
data: message.data,
|
||||
certificate: message.certificate.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for RawApiEphemeraMessage {
|
||||
type Output = Self;
|
||||
|
||||
fn decode(bytes: &[u8]) -> Result<Self::Output, DecodingError> {
|
||||
Codec::decode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for RawApiEphemeraMessage {
|
||||
fn encode(&self) -> Result<Vec<u8>, EncodingError> {
|
||||
Codec::encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for &RawApiEphemeraMessage {
|
||||
fn encode(&self) -> Result<Vec<u8>, EncodingError> {
|
||||
Codec::encode(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiBlockHeader {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ApiBlockHeader(timestamp: {}, creator: {}, height: {}, hash: {})",
|
||||
self.timestamp, self.creator, self.height, self.hash,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiBlock {
|
||||
#[must_use]
|
||||
pub fn as_raw_block(&self) -> ApiRawBlock {
|
||||
ApiRawBlock {
|
||||
header: self.header.clone(),
|
||||
messages: self.messages.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn message_count(&self) -> usize {
|
||||
self.messages.len()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn hash(&self) -> String {
|
||||
self.header.hash.clone()
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// - If the block is invalid.
|
||||
/// - If the block's certificate is invalid.
|
||||
/// - If the block's certificate is not signed by the block's creator.
|
||||
pub fn verify(&self, certificate: &ApiCertificate) -> Result<bool, ApiError> {
|
||||
let block: Block = self.clone().try_into()?;
|
||||
let valid = block.verify(&(certificate.clone()).into()).map_err(|e| {
|
||||
error!("Failed to verify block: {}", e);
|
||||
ApiError::Internal("Failed to verify block certificate".to_string())
|
||||
})?;
|
||||
Ok(valid)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ApiBlock {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ApiBlock(header: {}, message_count: {})",
|
||||
self.header,
|
||||
self.message_count()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiRawBlock {
|
||||
pub fn new(header: ApiBlockHeader, messages: Vec<ApiEphemeraMessage>) -> Self {
|
||||
Self { header, messages }
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiCertificate {
|
||||
/// # Errors
|
||||
/// - `EncodingError` if the message cannot be encoded.
|
||||
/// - `KeyPairError` if the message cannot be signed.
|
||||
pub fn prepare<D: Encode>(key_pair: &Keypair, data: &D) -> anyhow::Result<Self> {
|
||||
Certificate::prepare(key_pair, data).map(Into::into)
|
||||
}
|
||||
|
||||
/// # Errors
|
||||
/// -`EncodingError` if the message cannot be encoded.
|
||||
pub fn verify<D: Encode>(&self, data: &D) -> anyhow::Result<bool> {
|
||||
let certificate: Certificate = (self.clone()).into();
|
||||
Certificate::verify(&certificate, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EphemeraMessage> for ApiEphemeraMessage {
|
||||
fn from(ephemera_message: EphemeraMessage) -> Self {
|
||||
Self {
|
||||
timestamp: ephemera_message.timestamp,
|
||||
label: ephemera_message.label,
|
||||
data: ephemera_message.data,
|
||||
certificate: ApiCertificate {
|
||||
signature: ephemera_message.certificate.signature.into(),
|
||||
public_key: ephemera_message.certificate.public_key.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Certificate> for ApiCertificate {
|
||||
fn from(signature: Certificate) -> Self {
|
||||
Self {
|
||||
signature: signature.signature.into(),
|
||||
public_key: signature.public_key.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiCertificate> for Certificate {
|
||||
fn from(value: ApiCertificate) -> Self {
|
||||
Certificate {
|
||||
signature: value.signature.into(),
|
||||
public_key: value.public_key.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Block> for &ApiBlock {
|
||||
fn from(block: &Block) -> Self {
|
||||
let api_block: ApiBlock = block.clone().into();
|
||||
Box::leak(Box::new(api_block))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Signature> for ApiSignature {
|
||||
fn from(signature: Signature) -> Self {
|
||||
Self(signature)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiSignature> for Signature {
|
||||
fn from(signature: ApiSignature) -> Self {
|
||||
signature.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiPublicKey {
|
||||
pub fn peer_id(&self) -> String {
|
||||
self.0.peer_id().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PublicKey> for ApiPublicKey {
|
||||
fn from(public_key: PublicKey) -> Self {
|
||||
Self(public_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ApiPublicKey> for PublicKey {
|
||||
fn from(public_key: ApiPublicKey) -> Self {
|
||||
public_key.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Block> for ApiBlock {
|
||||
fn from(block: Block) -> Self {
|
||||
Self {
|
||||
header: ApiBlockHeader {
|
||||
timestamp: block.header.timestamp,
|
||||
creator: block.header.creator,
|
||||
height: block.header.height,
|
||||
hash: block.header.hash.to_string(),
|
||||
},
|
||||
messages: block.messages.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<ApiBlock> for Block {
|
||||
type Error = ApiError;
|
||||
|
||||
fn try_from(api_block: ApiBlock) -> Result<Self, ApiError> {
|
||||
let messages: Vec<EphemeraMessage> = api_block
|
||||
.messages
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect::<Vec<EphemeraMessage>>();
|
||||
Ok(Self {
|
||||
header: BlockHeader {
|
||||
timestamp: api_block.header.timestamp,
|
||||
creator: api_block.header.creator,
|
||||
height: api_block.header.height,
|
||||
hash: api_block.header.hash.parse().map_err(|e| {
|
||||
error!("Failed to parse block hash: {}", e);
|
||||
ApiError::Internal("Failed to parse block hash".to_string())
|
||||
})?,
|
||||
},
|
||||
messages,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiDhtStoreRequest {
|
||||
#[must_use]
|
||||
pub fn new(key: &[u8], value: &[u8]) -> Self {
|
||||
let key = bytes2hex("0x", key);
|
||||
let value = bytes2hex("0x", value);
|
||||
Self { key, value }
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
#[must_use]
|
||||
pub fn key(&self) -> Vec<u8> {
|
||||
//We can unwrap here because the key is always valid.
|
||||
hex2bytes(&self.key).unwrap()
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
#[must_use]
|
||||
pub fn value(&self) -> Vec<u8> {
|
||||
//We can unwrap here because the value is always valid.
|
||||
hex2bytes(&self.value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiDhtQueryRequest {
|
||||
#[must_use]
|
||||
pub fn new(key: &[u8]) -> Self {
|
||||
let key = bytes2hex("0x", key);
|
||||
Self { key }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn key_encoded(&self) -> String {
|
||||
self.key.clone()
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
#[must_use]
|
||||
pub fn key(&self) -> Vec<u8> {
|
||||
//We can unwrap here because the value is always valid.
|
||||
hex2bytes(&self.key).unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn parse_key(key: &str) -> Vec<u8> {
|
||||
hex2bytes(key).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiDhtQueryResponse {
|
||||
pub(crate) fn new(key: Vec<u8>, value: Vec<u8>) -> Self {
|
||||
let key = bytes2hex("0x", key);
|
||||
let value = bytes2hex("0x", value);
|
||||
Self { key, value }
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
#[must_use]
|
||||
pub fn key(&self) -> Vec<u8> {
|
||||
//We can unwrap here because the key is always valid.
|
||||
hex2bytes(&self.key).unwrap()
|
||||
}
|
||||
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
#[must_use]
|
||||
pub fn value(&self) -> Vec<u8> {
|
||||
//We can unwrap here because the value is always valid.
|
||||
hex2bytes(&self.value).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::crypto::EphemeraKeypair;
|
||||
use crate::crypto::Keypair;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_message_sign_ok() {
|
||||
let message_signing_keypair = Keypair::generate(None);
|
||||
|
||||
let message = RawApiEphemeraMessage::new("test".to_string(), vec![1, 2, 3]);
|
||||
let signed_message = message
|
||||
.sign(&message_signing_keypair)
|
||||
.expect("Failed to sign message");
|
||||
|
||||
let certificate = signed_message.certificate;
|
||||
|
||||
assert!(certificate.verify(&message).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_sign_fail() {
|
||||
let message_signing_keypair = Keypair::generate(None);
|
||||
|
||||
let message = RawApiEphemeraMessage::new("test1".to_string(), vec![1, 2, 3]);
|
||||
let signed_message = message
|
||||
.sign(&message_signing_keypair)
|
||||
.expect("Failed to sign message");
|
||||
|
||||
let certificate = signed_message.certificate;
|
||||
|
||||
let modified_message = RawApiEphemeraMessage::new("test2".to_string(), vec![1, 2, 3]);
|
||||
assert!(!certificate.verify(&modified_message).unwrap());
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user