Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d3d5cc3424 | |||
| a834bb17f8 | |||
| cee6d8c308 | |||
| 142eaf533b | |||
| 42365769f8 | |||
| 9fc822298f | |||
| 75221cfd3e | |||
| 50f71a21e0 | |||
| cfa9ecfcc4 | |||
| 549b33cd91 | |||
| be46da9906 | |||
| 66d123312f | |||
| a29f3db5fb | |||
| f452d97979 | |||
| 3eb7710a12 | |||
| d1c9251904 | |||
| 62894e2b40 | |||
| bd7fd1a61c | |||
| 28f118c73b | |||
| 65699736ee | |||
| caba594c95 | |||
| 746d52d017 | |||
| e323c05b33 | |||
| abdf071448 | |||
| a6f2b0e8c8 | |||
| f78b4a1742 | |||
| d4fde7b788 | |||
| d5a2952ef9 | |||
| 206b6ba742 | |||
| 67449b1c19 | |||
| 8d6e5d4fff | |||
| f448355b35 | |||
| cf78af6b98 | |||
| d041cfe5c5 | |||
| 85b0b6d73d | |||
| 685019884f | |||
| cd9d4eebd3 | |||
| 78610c7e28 | |||
| 496870b5f6 | |||
| 7eac5e3529 | |||
| 4ad4072709 | |||
| bd3711892a | |||
| b5926def85 | |||
| 50cc8bd0bf | |||
| fb1b58b5fb | |||
| c3a9ceae52 | |||
| c92a7e3e35 | |||
| e152c9a99e | |||
| 78cce00adf | |||
| 55e00a9a38 | |||
| 6d1b26daeb | |||
| 2d9e34cc81 | |||
| 7232fd83d1 | |||
| 3816142479 | |||
| 112ecc2e4c | |||
| 2f0fbd5ebd | |||
| f45a803139 | |||
| 7e16932c4a | |||
| a0a44509af | |||
| 852ed78e5d | |||
| 0d52800569 | |||
| a1dd9e656d | |||
| 1ea78e8e97 | |||
| 46d68e5448 | |||
| 2c820ca0ea | |||
| 10ff165c18 | |||
| eec3cc4c47 | |||
| 7407872b71 | |||
| 8d9387d3ac | |||
| e8bc2c7a01 | |||
| 0486cd2e63 | |||
| 604098844a | |||
| 65e35bd2b0 |
@@ -17,6 +17,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: install yarn in root
|
||||
run: cd ../.. yarn install
|
||||
|
||||
- name: Install npm
|
||||
run: npm install
|
||||
|
||||
|
||||
Generated
+86
-172
@@ -661,6 +661,22 @@ dependencies = [
|
||||
"syn 2.0.38",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-tungstenite"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc"
|
||||
dependencies = [
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"log",
|
||||
"pin-project-lite 0.2.13",
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asynchronous-codec"
|
||||
version = "0.6.2"
|
||||
@@ -674,15 +690,6 @@ dependencies = [
|
||||
"pin-project-lite 0.2.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "1.0.0"
|
||||
@@ -1552,6 +1559,26 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.0"
|
||||
@@ -1747,30 +1774,15 @@ dependencies = [
|
||||
"toml 0.5.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23"
|
||||
dependencies = [
|
||||
"crc-catalog 1.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe"
|
||||
dependencies = [
|
||||
"crc-catalog 2.2.0",
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.2.0"
|
||||
@@ -2564,12 +2576,6 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
@@ -2981,9 +2987,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.8"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
|
||||
checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
@@ -5932,7 +5938,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"sqlx 0.6.3",
|
||||
"sqlx",
|
||||
"tap",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@@ -6160,7 +6166,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.8",
|
||||
"sqlx 0.6.3",
|
||||
"sqlx",
|
||||
"tap",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@@ -6297,7 +6303,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"log",
|
||||
"sqlx 0.5.13",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
@@ -6484,7 +6490,7 @@ dependencies = [
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx 0.5.13",
|
||||
"sqlx",
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
@@ -6806,7 +6812,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx 0.6.3",
|
||||
"sqlx",
|
||||
"tap",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
@@ -6827,7 +6833,7 @@ dependencies = [
|
||||
"pretty_env_logger",
|
||||
"rocket",
|
||||
"serde",
|
||||
"sqlx 0.5.13",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
@@ -7335,7 +7341,7 @@ dependencies = [
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx 0.5.13",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
@@ -7582,6 +7588,28 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nyxd-scraper"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"const_format",
|
||||
"cosmrs",
|
||||
"eyre",
|
||||
"futures",
|
||||
"nym-bin-common",
|
||||
"sha2 0.10.8",
|
||||
"sqlx",
|
||||
"tendermint",
|
||||
"tendermint-rpc",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.1"
|
||||
@@ -7647,9 +7675,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.57"
|
||||
version = "0.10.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c"
|
||||
checksum = "8cde4d2d9200ad5909f8dac647e29482e07c3a35de8a13fce7c9c7747ad9f671"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
@@ -7688,9 +7716,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.93"
|
||||
version = "0.9.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d"
|
||||
checksum = "c1665caf8ab2dc9aef43d1c0023bd904633a6a05cb30b0ad59bec2ae986e57a7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
@@ -10108,17 +10136,6 @@ dependencies = [
|
||||
"der 0.7.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4"
|
||||
dependencies = [
|
||||
"itertools 0.10.5",
|
||||
"nom",
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.2.2"
|
||||
@@ -10130,70 +10147,14 @@ dependencies = [
|
||||
"unicode_categories",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b"
|
||||
dependencies = [
|
||||
"sqlx-core 0.5.13",
|
||||
"sqlx-macros 0.5.13",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188"
|
||||
dependencies = [
|
||||
"sqlx-core 0.6.3",
|
||||
"sqlx-macros 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"atoi 0.4.0",
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc 2.1.0",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"event-listener",
|
||||
"flume",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-intrusive",
|
||||
"futures-util",
|
||||
"hashlink 0.7.0",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"itoa",
|
||||
"libc",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rustls 0.19.1",
|
||||
"sha2 0.10.8",
|
||||
"smallvec",
|
||||
"sqlformat 0.1.8",
|
||||
"sqlx-rt 0.5.13",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"webpki 0.21.4",
|
||||
"webpki-roots 0.21.1",
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10203,12 +10164,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"atoi 1.0.0",
|
||||
"atoi",
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc 3.0.1",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"dotenvy",
|
||||
"either",
|
||||
@@ -10234,34 +10195,16 @@ dependencies = [
|
||||
"rustls-pemfile",
|
||||
"sha2 0.10.8",
|
||||
"smallvec",
|
||||
"sqlformat 0.2.2",
|
||||
"sqlx-rt 0.6.3",
|
||||
"sqlformat",
|
||||
"sqlx-rt",
|
||||
"stringprep",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"webpki-roots 0.22.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1"
|
||||
dependencies = [
|
||||
"dotenv",
|
||||
"either",
|
||||
"heck 0.4.1",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sha2 0.10.8",
|
||||
"sqlx-core 0.5.13",
|
||||
"sqlx-rt 0.5.13",
|
||||
"syn 1.0.109",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.6.3"
|
||||
@@ -10275,23 +10218,12 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sha2 0.10.8",
|
||||
"sqlx-core 0.6.3",
|
||||
"sqlx-rt 0.6.3",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
"syn 1.0.109",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt"
|
||||
version = "0.5.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tokio-rustls 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-rt"
|
||||
version = "0.6.3"
|
||||
@@ -10424,7 +10356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25"
|
||||
dependencies = [
|
||||
"base64 0.13.1",
|
||||
"crc 3.0.1",
|
||||
"crc",
|
||||
"lazy_static",
|
||||
"md-5",
|
||||
"rand 0.8.5",
|
||||
@@ -10658,6 +10590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"async-tungstenite",
|
||||
"bytes",
|
||||
"flex-error",
|
||||
"futures",
|
||||
@@ -10866,17 +10799,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
||||
dependencies = [
|
||||
"rustls 0.19.1",
|
||||
"tokio",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.4"
|
||||
@@ -11412,6 +11334,7 @@ dependencies = [
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.21.7",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
@@ -12026,15 +11949,6 @@ dependencies = [
|
||||
"untrusted 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.6"
|
||||
@@ -12154,7 +12068,7 @@ checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"crc 3.0.1",
|
||||
"crc",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
@@ -12205,7 +12119,7 @@ dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"crc 3.0.1",
|
||||
"crc",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
|
||||
+3
-1
@@ -67,6 +67,7 @@ members = [
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/routing",
|
||||
"common/nymsphinx/types",
|
||||
"common/nyxd-scraper",
|
||||
"common/pemstore",
|
||||
"common/socks5-client-core",
|
||||
"common/socks5/proxy-helpers",
|
||||
@@ -159,6 +160,7 @@ reqwest = "0.11.22"
|
||||
schemars = "0.8.1"
|
||||
serde = "1.0.152"
|
||||
serde_json = "1.0.91"
|
||||
sqlx = "0.6.3"
|
||||
tap = "1.0.1"
|
||||
time = "0.3.30"
|
||||
thiserror = "1.0.48"
|
||||
@@ -200,8 +202,8 @@ cw-controllers = { version = "=1.1.0" }
|
||||
# cosmrs-related
|
||||
bip32 = "0.5.1"
|
||||
cosmrs = "=0.15.0"
|
||||
tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
tendermint = "0.34" # same version as used by cosmrs
|
||||
tendermint-rpc = "0.34" # same version as used by cosmrs
|
||||
prost = "0.12"
|
||||
|
||||
# wasm-related dependencies
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: skyblue;
|
||||
}
|
||||
}
|
||||
.container {
|
||||
font-family: sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.intro {
|
||||
text-align: center;
|
||||
}
|
||||
.licenses-list {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.license-used-by {
|
||||
margin-top: -10px;
|
||||
}
|
||||
.license-text {
|
||||
max-height: 200px;
|
||||
overflow-y: scroll;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main class="container">
|
||||
<div class="intro">
|
||||
<h1>Third Party Licenses</h1>
|
||||
<p>This page lists the licenses of the projects used in cargo-about.</p>
|
||||
</div>
|
||||
|
||||
<h2>Overview of licenses:</h2>
|
||||
<ul class="licenses-overview">
|
||||
{{#each overview}}
|
||||
<li><a href="#{{id}}">{{name}}</a> ({{count}})</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
|
||||
<h2>All license text:</h2>
|
||||
<ul class="licenses-list">
|
||||
{{#each licenses}}
|
||||
<li class="license">
|
||||
<h3 id="{{id}}">{{name}}</h3>
|
||||
<h4>Used by:</h4>
|
||||
<ul class="license-used-by">
|
||||
{{#each used_by}}
|
||||
<li><a href="{{#if crate.repository}} {{crate.repository}} {{else}} https://crates.io/crates/{{crate.name}} {{/if}}">{{crate.name}} {{crate.version}}</a></li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<pre class="license-text">{{text}}</pre>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
private = { ignore = true }
|
||||
|
||||
accepted = [
|
||||
"0BSD",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"CC0-1.0",
|
||||
"ISC",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"Unicode-DFS-2016",
|
||||
"OpenSSL",
|
||||
]
|
||||
|
||||
workarounds = [
|
||||
"ring",
|
||||
"rustls",
|
||||
]
|
||||
@@ -15,4 +15,4 @@ prod:
|
||||
mixnode_identity: 3pMCJswCyA19MGYWGDWT5fBk2M8ybSZGXttyAoNY5gBB
|
||||
gateway_identity: 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
log_level: error
|
||||
time_zone: utc
|
||||
time_zone: utc
|
||||
+19
-4
@@ -1,4 +1,6 @@
|
||||
import { dir } from "console";
|
||||
import { readFileSync } from "fs";
|
||||
import { dirname } from "path";
|
||||
import { TLogLevelName } from "tslog";
|
||||
|
||||
import YAML from "yaml";
|
||||
@@ -10,9 +12,11 @@ class ConfigHandler {
|
||||
|
||||
public commonConfig: { request_headers: object };
|
||||
|
||||
private currentEnvironment: string;
|
||||
|
||||
public environment: string;
|
||||
|
||||
public environmnetConfig: {
|
||||
public environmentConfig: {
|
||||
log_level: TLogLevelName;
|
||||
time_zone: string;
|
||||
api_base_url: string;
|
||||
@@ -35,8 +39,9 @@ class ConfigHandler {
|
||||
|
||||
private setCommonConfig(): void {
|
||||
try {
|
||||
const baseWorkingDirectory = __dirname;
|
||||
this.commonConfig = YAML.parse(
|
||||
readFileSync("src/config/config.yaml", "utf8")
|
||||
readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"),
|
||||
).common;
|
||||
} catch (error) {
|
||||
throw Error(`Error reading common config: (${error})`);
|
||||
@@ -46,14 +51,24 @@ class ConfigHandler {
|
||||
private setEnvironmentConfig(environment: string): void {
|
||||
this.ensureEnvironmentIsValid(environment);
|
||||
try {
|
||||
this.environmnetConfig = YAML.parse(
|
||||
readFileSync("src/config/config.yaml", "utf8")
|
||||
const baseWorkingDirectory = __dirname;
|
||||
this.environmentConfig = YAML.parse(
|
||||
readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"),
|
||||
)[environment];
|
||||
} catch (error) {
|
||||
console.log("fadsfasdfasdfsdfsa")
|
||||
throw Error(`Error reading environment config: (${error})`);
|
||||
}
|
||||
}
|
||||
|
||||
public getEnvironmentConfig(environment: string): any {
|
||||
const baseWorkingDirectory = __dirname;
|
||||
return (
|
||||
this.environmentConfig ||
|
||||
YAML.parse(readFileSync(baseWorkingDirectory + "/config.yaml", "utf8"))[environment]
|
||||
);
|
||||
}
|
||||
|
||||
private ensureEnvironmentIsValid(environment: string): void {
|
||||
if (this.validEnvironments.indexOf(environment) === -1) {
|
||||
throw Error(`Config environment is not valid: "${environment}"`);
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
ConfigHandler: require('./config/configHandler.ts'),
|
||||
RestClient: require('./restClient/RestClient.ts')
|
||||
};
|
||||
+5
-5
@@ -13,9 +13,9 @@ import ConfigHandler from "../config/configHandler";
|
||||
|
||||
const config = ConfigHandler.getInstance();
|
||||
const log = new Logger({
|
||||
minLevel: config.environmnetConfig.log_level,
|
||||
minLevel: config.environmentConfig.log_level,
|
||||
dateTimeTimezone:
|
||||
config.environmnetConfig.time_zone ||
|
||||
config.environmentConfig.time_zone ||
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
});
|
||||
|
||||
@@ -24,7 +24,7 @@ function isSet(property): boolean {
|
||||
}
|
||||
|
||||
export class RestClient {
|
||||
private static authToken: string;
|
||||
public static authToken: string;
|
||||
|
||||
private axiosInstance: AxiosInstance;
|
||||
|
||||
@@ -83,7 +83,7 @@ export class RestClient {
|
||||
data,
|
||||
additionalConfigs,
|
||||
params,
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
await this.axiosInstance
|
||||
@@ -214,7 +214,7 @@ export class RestClient {
|
||||
|
||||
if (isSet(additionalConfigs)) {
|
||||
logRecord = `${logRecord}\nAdditional Configuration: ${stringify(
|
||||
additionalConfigs
|
||||
additionalConfigs,
|
||||
)}`;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ features = ["time"]
|
||||
version = "0.20.1"
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.6.2"
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
@@ -90,7 +90,7 @@ tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
pub use wasmtimer::{std::Instant, tokio::*};
|
||||
|
||||
@@ -43,6 +43,7 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
|
||||
pub use cosmrs::tendermint::Time as TendermintTime;
|
||||
pub use cosmrs::tx::Msg;
|
||||
pub use cosmrs::tx::{self};
|
||||
pub use cosmrs::Any;
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::Gas;
|
||||
pub use cosmrs::{bip32, AccountId, Denom};
|
||||
|
||||
@@ -73,7 +73,7 @@ where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let path = path.as_ref();
|
||||
log::debug!("trying to save config file to {}", path.display());
|
||||
log::info!("saving config file to {}", path.display());
|
||||
|
||||
if let Some(parent) = path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
|
||||
@@ -14,14 +14,14 @@ thiserror = { workspace = true }
|
||||
tokio = { version = "1.24.1", features = ["sync"]}
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx]
|
||||
version = "0.5"
|
||||
workspace = true
|
||||
features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = [ "rt-multi-thread", "net", "signal", "fs" ]
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
|
||||
@@ -81,6 +81,15 @@ ExitPolicy accept6 *6:119
|
||||
ExitPolicy accept *4:120
|
||||
ExitPolicy reject6 [FC00::]/7:*
|
||||
|
||||
# Portless
|
||||
ExitPolicy accept *:0
|
||||
ExitPolicy accept *4:0
|
||||
ExitPolicy accept *6:0
|
||||
|
||||
ExitPolicy reject *:0
|
||||
ExitPolicy reject *4:0
|
||||
ExitPolicy reject *6:0
|
||||
|
||||
#ExitPolicy accept *:8080 #and another comment here
|
||||
|
||||
ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
|
||||
@@ -184,6 +193,60 @@ ExitPolicy reject *:*
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *4:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V4Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy accept *6:0
|
||||
expected.push(
|
||||
Accept,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V6Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *4:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V4Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy reject *6:0
|
||||
expected.push(
|
||||
Reject,
|
||||
AddressPortPattern {
|
||||
ip_pattern: IpPattern::V6Star,
|
||||
ports: PortRange::new_zero(),
|
||||
},
|
||||
);
|
||||
|
||||
// ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:*
|
||||
expected.push(
|
||||
Reject,
|
||||
|
||||
@@ -264,7 +264,13 @@ mod stringified_ip_pattern {
|
||||
impl AddressPortPattern {
|
||||
/// Return true iff this pattern matches a given address and port.
|
||||
pub fn matches(&self, addr: &IpAddr, port: u16) -> bool {
|
||||
self.ip_pattern.matches(addr) && self.ports.contains(port)
|
||||
// For backward compatibility, we treat port 0 as a wildcard until all gateways have
|
||||
// upgraded, at which point we can add *:0 to the policy list.
|
||||
if port == 0 {
|
||||
self.ip_pattern.matches(addr)
|
||||
} else {
|
||||
self.ip_pattern.matches(addr) && self.ports.contains(port)
|
||||
}
|
||||
}
|
||||
|
||||
/// As matches, but accept a SocketAddr.
|
||||
@@ -395,19 +401,9 @@ fn parse_addr(s: &str) -> Result<IpAddr, PolicyError> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper: try to parse a port making sure it's non-zero
|
||||
fn parse_port(s: &str) -> Result<u16, PolicyError> {
|
||||
let port = s
|
||||
.parse::<u16>()
|
||||
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })?;
|
||||
|
||||
if port == 0 {
|
||||
Err(PolicyError::InvalidPort {
|
||||
raw: port.to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(port)
|
||||
}
|
||||
s.parse::<u16>()
|
||||
.map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })
|
||||
}
|
||||
|
||||
impl FromStr for IpPattern {
|
||||
@@ -494,6 +490,10 @@ impl PortRange {
|
||||
PortRange::new_unchecked(1, 65535)
|
||||
}
|
||||
|
||||
pub fn new_zero() -> Self {
|
||||
PortRange { start: 0, end: 0 }
|
||||
}
|
||||
|
||||
/// Create a new PortRange.
|
||||
///
|
||||
/// The Portrange contains all ports between `start` and `end` inclusive.
|
||||
@@ -574,6 +574,7 @@ mod test {
|
||||
|
||||
check("marzipan:80");
|
||||
check("1.2.3.4:90-80");
|
||||
check("1.2.3.4:0-80");
|
||||
check("1.2.3.4/100:8888");
|
||||
check("[1.2.3.4]/16:80");
|
||||
check("[::1]/130:8888");
|
||||
@@ -612,6 +613,22 @@ mod test {
|
||||
|
||||
check("0.0.0.0/0:*", &["127.0.0.1:80"], &["[f00b::]:80"]);
|
||||
check("[::]/0:*", &["[f00b::]:80"], &["127.0.0.1:80"]);
|
||||
|
||||
check(
|
||||
"*:0",
|
||||
&["1.2.3.4:0", "[::1]:0", "9.0.0.0:0"],
|
||||
&["1.2.3.4:443", "[::1]:500", "9.0.0.0:80", "[::1]:80"],
|
||||
);
|
||||
check(
|
||||
"*4:0",
|
||||
&["1.2.3.4:0", "9.0.0.0:0"],
|
||||
&["1.2.3.4:443", "9.0.0.0:80", "[::1]:0", "[::1]:80"],
|
||||
);
|
||||
check(
|
||||
"*6:0",
|
||||
&["[::1]:0"],
|
||||
&["[::1]:80", "1.2.3.4:0", "1.2.3.4:443"],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -620,6 +637,7 @@ mod test {
|
||||
policy.push(AddressPolicyAction::Accept, "*:443".parse()?);
|
||||
policy.push(AddressPolicyAction::Accept, "[::1]:80".parse()?);
|
||||
policy.push(AddressPolicyAction::Reject, "*:80".parse()?);
|
||||
policy.push(AddressPolicyAction::Accept, "*:0".parse()?);
|
||||
|
||||
let policy = policy; // drop mut
|
||||
assert!(policy
|
||||
@@ -640,6 +658,9 @@ mod test {
|
||||
assert!(policy
|
||||
.allows_sockaddr(&"127.0.0.1:66".parse().unwrap())
|
||||
.is_none());
|
||||
assert!(policy
|
||||
.allows_sockaddr(&"127.0.0.1:0".parse().unwrap())
|
||||
.unwrap());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -672,7 +693,6 @@ mod test {
|
||||
assert_eq!("*".parse::<PortRange>().unwrap(), PortRange::new_all());
|
||||
|
||||
assert!("hello".parse::<PortRange>().is_err());
|
||||
assert!("0".parse::<PortRange>().is_err());
|
||||
assert!("65536".parse::<PortRange>().is_err());
|
||||
assert!("65537".parse::<PortRange>().is_err());
|
||||
assert!("1-2-3".parse::<PortRange>().is_err());
|
||||
@@ -680,6 +700,9 @@ mod test {
|
||||
assert!("1-".parse::<PortRange>().is_err());
|
||||
assert!("-2".parse::<PortRange>().is_err());
|
||||
assert!("-".parse::<PortRange>().is_err());
|
||||
|
||||
assert_eq!("0".parse::<PortRange>().unwrap(), PortRange::new_zero(),);
|
||||
assert!("0-1".parse::<PortRange>().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::var_names;
|
||||
use crate::{DenomDetails, ValidatorDetails};
|
||||
use std::str::FromStr;
|
||||
|
||||
pub const NETWORK_NAME: &str = "mainnet";
|
||||
|
||||
@@ -60,6 +61,12 @@ pub fn read_var_if_not_default(var: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_parsed_var_if_not_default<T: FromStr>(var: &str) -> Option<Result<T, T::Err>> {
|
||||
read_var_if_not_default(var)
|
||||
.as_deref()
|
||||
.map(FromStr::from_str)
|
||||
}
|
||||
|
||||
pub fn export_to_env() {
|
||||
set_var_to_default(var_names::CONFIGURED, "true");
|
||||
set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME);
|
||||
|
||||
@@ -108,6 +108,17 @@ pub fn transpose_matrix<T: Debug>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! random_scalars_refs {
|
||||
( $x: ident, $params: expr, $n: expr ) => {
|
||||
let _vec = $params.n_random_scalars($n);
|
||||
#[allow(clippy::map_identity)]
|
||||
let $x = _vec.iter().collect::<Vec<_>>();
|
||||
};
|
||||
}
|
||||
|
||||
pub use random_scalars_refs;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
@@ -170,14 +181,3 @@ pub mod tests {
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! random_scalars_refs {
|
||||
( $x: ident, $params: expr, $n: expr ) => {
|
||||
let _vec = $params.n_random_scalars($n);
|
||||
#[allow(clippy::map_identity)]
|
||||
let $x = _vec.iter().collect::<Vec<_>>();
|
||||
};
|
||||
}
|
||||
|
||||
pub use random_scalars_refs;
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "nyxd-scraper"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait.workspace = true
|
||||
const_format = "0.2.32"
|
||||
cosmrs.workspace = true
|
||||
eyre = "0.6.9"
|
||||
futures.workspace = true
|
||||
sha2 = "0.10.8"
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] }
|
||||
tendermint.workspace = true
|
||||
tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] }
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-stream = "0.1.14"
|
||||
tokio-util = { version = "0.7.10", features = ["rt"]}
|
||||
tracing.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
|
||||
# TEMP
|
||||
nym-bin-common = { path = "../bin-common", features = ["basic_tracing"]}
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
use std::env;
|
||||
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let database_path = format!("{out_dir}/scraper-example.sqlite");
|
||||
|
||||
let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc"))
|
||||
.await
|
||||
.expect("Failed to create SQLx database connection");
|
||||
|
||||
sqlx::migrate!("./sql_migrations")
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("Failed to perform SQLx migrations");
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
|
||||
|
||||
#[cfg(target_family = "windows")]
|
||||
// for some strange reason we need to add a leading `/` to the windows path even though it's
|
||||
// not a valid windows path... but hey, it works...
|
||||
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
CREATE TABLE METADATA
|
||||
(
|
||||
id INTEGER PRIMARY KEY CHECK (id = 0),
|
||||
last_processed_height INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,76 @@
|
||||
CREATE TABLE validator
|
||||
(
|
||||
consensus_address TEXT NOT NULL PRIMARY KEY, /* Validator consensus address */
|
||||
consensus_pubkey TEXT NOT NULL UNIQUE /* Validator consensus public key */
|
||||
);
|
||||
|
||||
CREATE TABLE pre_commit
|
||||
(
|
||||
validator_address TEXT NOT NULL REFERENCES validator (consensus_address),
|
||||
height BIGINT NOT NULL,
|
||||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL,
|
||||
voting_power BIGINT NOT NULL,
|
||||
proposer_priority BIGINT NOT NULL,
|
||||
UNIQUE (validator_address, timestamp)
|
||||
);
|
||||
CREATE INDEX pre_commit_validator_address_index ON pre_commit (validator_address);
|
||||
CREATE INDEX pre_commit_height_index ON pre_commit (height);
|
||||
|
||||
CREATE TABLE block
|
||||
(
|
||||
height BIGINT UNIQUE PRIMARY KEY,
|
||||
hash TEXT NOT NULL UNIQUE,
|
||||
num_txs INTEGER DEFAULT 0,
|
||||
total_gas BIGINT DEFAULT 0,
|
||||
proposer_address TEXT REFERENCES validator (consensus_address),
|
||||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL
|
||||
);
|
||||
CREATE INDEX block_height_index ON block (height);
|
||||
CREATE INDEX block_hash_index ON block (hash);
|
||||
CREATE INDEX block_proposer_address_index ON block (proposer_address);
|
||||
|
||||
-- no JSONB in sqlite (yet) : )
|
||||
CREATE TABLE "transaction"
|
||||
(
|
||||
hash TEXT UNIQUE NOT NULL,
|
||||
height BIGINT NOT NULL REFERENCES block (height),
|
||||
"index" INTEGER NOT NULL,
|
||||
success BOOLEAN NOT NULL,
|
||||
|
||||
/* Body */
|
||||
num_messages INTEGER NOT NULL,
|
||||
-- messages JSONB NOT NULL,-- DEFAULT '[]'::JSONB,
|
||||
memo TEXT,
|
||||
-- signatures TEXT[] NOT NULL,
|
||||
|
||||
/* AuthInfo */
|
||||
-- signer_infos JSONB NOT NULL,-- DEFAULT '[]'::JSONB,
|
||||
-- fee JSONB NOT NULL,-- DEFAULT '{}'::JSONB,
|
||||
|
||||
/* Tx response */
|
||||
gas_wanted BIGINT DEFAULT 0,
|
||||
gas_used BIGINT DEFAULT 0,
|
||||
raw_log TEXT
|
||||
-- logs JSONB
|
||||
);
|
||||
CREATE INDEX transaction_hash_index ON "transaction" (hash);
|
||||
CREATE INDEX transaction_height_index ON "transaction" (height);
|
||||
|
||||
CREATE TABLE message
|
||||
(
|
||||
transaction_hash TEXT NOT NULL REFERENCES "transaction" (hash),
|
||||
"index" BIGINT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
-- value JSONB NOT NULL,
|
||||
-- involved_accounts_addresses TEXT[] NOT NULL,
|
||||
|
||||
height BIGINT NOT NULL,
|
||||
CONSTRAINT unique_message_per_tx UNIQUE (transaction_hash, "index")
|
||||
);
|
||||
CREATE INDEX message_transaction_hash_index ON message (transaction_hash);
|
||||
CREATE INDEX message_type_index ON message (type);
|
||||
|
||||
CREATE TABLE pruning
|
||||
(
|
||||
last_pruned_height BIGINT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::MAX_RANGE_SIZE;
|
||||
use std::cmp::min;
|
||||
use std::collections::VecDeque;
|
||||
use std::ops::Range;
|
||||
|
||||
pub(crate) fn split_request_range(request_range: Range<u32>) -> VecDeque<Range<u32>> {
|
||||
let mut requests = VecDeque::new();
|
||||
|
||||
let mut start = request_range.start;
|
||||
let mut end = min(request_range.end, start + MAX_RANGE_SIZE as u32);
|
||||
|
||||
loop {
|
||||
requests.push_back(start..end);
|
||||
start = min(start + MAX_RANGE_SIZE as u32, request_range.end);
|
||||
end = min(end + MAX_RANGE_SIZE as u32, request_range.end);
|
||||
|
||||
if start == end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
requests
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn splitting_request_range() {
|
||||
let range = 0..100;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
expected.push_back(30..60);
|
||||
expected.push_back(60..90);
|
||||
expected.push_back(90..100);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..30;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..60;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..30);
|
||||
expected.push_back(30..60);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 0..5;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(0..5);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
|
||||
let range = 123..200;
|
||||
let mut expected = VecDeque::new();
|
||||
expected.push_back(123..153);
|
||||
expected.push_back(153..183);
|
||||
expected.push_back(183..200);
|
||||
assert_eq!(expected, split_request_range(range));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::helpers::split_request_range;
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::block_requester::BlockRequest;
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
use crate::rpc_client::RpcClient;
|
||||
use crate::storage::{persist_block, ScraperStorage};
|
||||
use futures::StreamExt;
|
||||
use std::collections::{BTreeMap, HashSet, VecDeque};
|
||||
use std::ops::{Add, Range};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
|
||||
use tokio::time::{interval_at, Instant};
|
||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
mod helpers;
|
||||
pub(crate) mod types;
|
||||
|
||||
const MISSING_BLOCKS_CHECK_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const MAX_MISSING_BLOCKS_DELAY: Duration = Duration::from_secs(15);
|
||||
const MAX_RANGE_SIZE: usize = 30;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct PendingSync {
|
||||
request_in_flight: HashSet<u32>,
|
||||
queued_requests: VecDeque<Range<u32>>,
|
||||
}
|
||||
|
||||
impl PendingSync {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.request_in_flight.is_empty() && self.queued_requests.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BlockProcessor {
|
||||
cancel: CancellationToken,
|
||||
last_processed_height: u32,
|
||||
last_processed_at: Instant,
|
||||
pending_sync: PendingSync,
|
||||
queued_blocks: BTreeMap<u32, BlockToProcess>,
|
||||
|
||||
rpc_client: RpcClient,
|
||||
incoming: UnboundedReceiverStream<BlockToProcess>,
|
||||
block_requester: Sender<BlockRequest>,
|
||||
storage: ScraperStorage,
|
||||
|
||||
// future work: rather than sending each msg to every msg module,
|
||||
// let them subscribe based on `type_url` inside the message itself
|
||||
// (like "/cosmwasm.wasm.v1.MsgExecuteContract")
|
||||
block_modules: Vec<Box<dyn BlockModule + Send>>,
|
||||
tx_modules: Vec<Box<dyn TxModule + Send>>,
|
||||
msg_modules: Vec<Box<dyn MsgModule + Send>>,
|
||||
}
|
||||
|
||||
impl BlockProcessor {
|
||||
pub async fn new(
|
||||
cancel: CancellationToken,
|
||||
incoming: UnboundedReceiver<BlockToProcess>,
|
||||
block_requester: Sender<BlockRequest>,
|
||||
storage: ScraperStorage,
|
||||
rpc_client: RpcClient,
|
||||
) -> Result<Self, ScraperError> {
|
||||
let last_processed = storage.get_last_processed_height().await?;
|
||||
|
||||
Ok(BlockProcessor {
|
||||
cancel,
|
||||
last_processed_height: last_processed.try_into().unwrap_or_default(),
|
||||
last_processed_at: Instant::now(),
|
||||
pending_sync: Default::default(),
|
||||
queued_blocks: Default::default(),
|
||||
rpc_client,
|
||||
incoming: incoming.into(),
|
||||
block_requester,
|
||||
storage,
|
||||
block_modules: vec![],
|
||||
tx_modules: vec![],
|
||||
msg_modules: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_block(&mut self, block: BlockToProcess) -> Result<(), ScraperError> {
|
||||
info!("processing block at height {}", block.height);
|
||||
|
||||
let full_info = self.rpc_client.try_get_full_details(block).await?;
|
||||
|
||||
debug!(
|
||||
"this block has {} transaction(s)",
|
||||
full_info.transactions.len()
|
||||
);
|
||||
for tx in &full_info.transactions {
|
||||
debug!("{} has {} message(s)", tx.hash, tx.tx.body.messages.len());
|
||||
for (index, msg) in tx.tx.body.messages.iter().enumerate() {
|
||||
debug!("{index}: {:?}", msg.type_url)
|
||||
}
|
||||
}
|
||||
|
||||
// process the entire block as a transaction so that if anything fails,
|
||||
// we won't end up with a corrupted storage.
|
||||
let mut tx = self.storage.begin_processing_tx().await?;
|
||||
|
||||
persist_block(&full_info, &mut tx).await?;
|
||||
|
||||
// let the modules do whatever they want
|
||||
// the ones wanting the full block:
|
||||
for block_module in &mut self.block_modules {
|
||||
block_module.handle_block(&full_info, &mut tx).await?;
|
||||
}
|
||||
|
||||
// the ones wanting transactions:
|
||||
for block_tx in full_info.transactions {
|
||||
for tx_module in &mut self.tx_modules {
|
||||
tx_module.handle_tx(&block_tx, &mut tx).await?;
|
||||
}
|
||||
// the ones concerned with individual messages
|
||||
for (index, msg) in block_tx.tx.body.messages.iter().enumerate() {
|
||||
for msg_module in &mut self.msg_modules {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|source| ScraperError::StorageTxCommitFailure { source })?;
|
||||
|
||||
self.last_processed_height = full_info.block.header.height.value() as u32;
|
||||
self.last_processed_at = Instant::now();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_block_modules(&mut self, modules: Vec<Box<dyn BlockModule + Send>>) {
|
||||
self.block_modules = modules;
|
||||
}
|
||||
|
||||
pub fn set_tx_modules(&mut self, modules: Vec<Box<dyn TxModule + Send>>) {
|
||||
self.tx_modules = modules;
|
||||
}
|
||||
|
||||
pub fn set_msg_modules(&mut self, modules: Vec<Box<dyn MsgModule + Send>>) {
|
||||
self.msg_modules = modules;
|
||||
}
|
||||
|
||||
async fn maybe_request_missing_blocks(&mut self) -> Result<(), ScraperError> {
|
||||
// we're still processing, so we're good
|
||||
if self.last_processed_at.elapsed() < MAX_MISSING_BLOCKS_DELAY {
|
||||
debug!("no need to request missing blocks");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.try_request_pending().await {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// TODO: properly fill in the gaps later with BlockRequest::Specific,
|
||||
let request_range = if let Some((next_available, _)) = self.queued_blocks.first_key_value()
|
||||
{
|
||||
self.last_processed_height + 1..*next_available
|
||||
} else {
|
||||
let current_height = self.rpc_client.current_block_height().await? as u32;
|
||||
self.last_processed_height + 1..current_height + 1
|
||||
};
|
||||
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn request_missing_blocks(
|
||||
&mut self,
|
||||
request_range: Range<u32>,
|
||||
) -> Result<(), ScraperError> {
|
||||
let request_range = if request_range.len() > MAX_RANGE_SIZE {
|
||||
let mut split = split_request_range(request_range);
|
||||
|
||||
// SAFETY: we know that after the split of a non-empty range we have AT LEAST one value
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let first = split.pop_front().unwrap();
|
||||
self.pending_sync.queued_requests = split;
|
||||
self.pending_sync.request_in_flight = first.clone().collect();
|
||||
|
||||
first
|
||||
} else {
|
||||
request_range
|
||||
};
|
||||
|
||||
self.send_blocks_request(request_range).await
|
||||
}
|
||||
|
||||
// technically we're not mutating self here,
|
||||
// but we need it to help the compiler figure out the future is `Send`
|
||||
async fn send_blocks_request(&mut self, request_range: Range<u32>) -> Result<(), ScraperError> {
|
||||
debug!("requesting missing blocks: {request_range:?}");
|
||||
|
||||
self.block_requester
|
||||
.send(BlockRequest::Range(request_range))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn next_incoming(&mut self, block: BlockToProcess) {
|
||||
let height = block.height;
|
||||
|
||||
self.pending_sync.request_in_flight.remove(&height);
|
||||
|
||||
if self.last_processed_height == 0 {
|
||||
// this is the first time we've started up the process
|
||||
debug!("setting up initial processing height");
|
||||
self.last_processed_height = height - 1
|
||||
}
|
||||
|
||||
if height <= self.last_processed_height {
|
||||
warn!("we have already processed block for height {height}");
|
||||
return;
|
||||
}
|
||||
|
||||
if self.last_processed_height + 1 != height {
|
||||
if self.queued_blocks.insert(height, block).is_some() {
|
||||
warn!("we have already queued up block for height {height}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Err(err) = self.process_block(block).await {
|
||||
error!("failed to process block at height {height}: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
// process as much as we can from the queue
|
||||
let mut next = height + 1;
|
||||
while let Some(next_block) = self.queued_blocks.remove(&next) {
|
||||
if let Err(err) = self.process_block(next_block).await {
|
||||
error!("failed to process queued-up block at height {next}: {err}")
|
||||
}
|
||||
next += 1;
|
||||
}
|
||||
|
||||
self.try_request_pending().await;
|
||||
}
|
||||
|
||||
async fn try_request_pending(&mut self) -> bool {
|
||||
if self.pending_sync.request_in_flight.is_empty() {
|
||||
if let Some(next_sync) = self.pending_sync.queued_requests.pop_front() {
|
||||
debug!(
|
||||
"current request range has been resolved. requesting another bunch of blocks"
|
||||
);
|
||||
if let Err(err) = self.send_blocks_request(next_sync.clone()).await {
|
||||
error!("failed to request resync blocks: {err}");
|
||||
self.pending_sync.queued_requests.push_front(next_sync);
|
||||
} else {
|
||||
self.pending_sync.request_in_flight = next_sync.collect()
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
// technically we're not mutating self here,
|
||||
// but we need it to help the compiler figure out the future is `Send`
|
||||
async fn startup_resync(&mut self) -> Result<(), ScraperError> {
|
||||
assert!(self.pending_sync.is_empty());
|
||||
|
||||
let latest_block = self.rpc_client.current_block_height().await? as u32;
|
||||
if latest_block > self.last_processed_height && self.last_processed_height != 0 {
|
||||
let request_range = self.last_processed_height + 1..latest_block + 1;
|
||||
info!("we need to request {request_range:?} to resync");
|
||||
self.request_missing_blocks(request_range).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
info!("starting processing loop");
|
||||
|
||||
// sure, we could be more efficient and reset it on every processed block,
|
||||
// but the overhead is so minimal that it doesn't matter
|
||||
let mut missing_check_interval = interval_at(
|
||||
Instant::now().add(MISSING_BLOCKS_CHECK_INTERVAL),
|
||||
MISSING_BLOCKS_CHECK_INTERVAL,
|
||||
);
|
||||
|
||||
if let Err(err) = self.startup_resync().await {
|
||||
error!("failed to perform startup sync: {err}");
|
||||
self.cancel.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
}
|
||||
_ = missing_check_interval.tick() => {
|
||||
if let Err(err) = self.maybe_request_missing_blocks().await {
|
||||
error!("failed to request missing blocks: {err}")
|
||||
}
|
||||
}
|
||||
block = self.incoming.next() => {
|
||||
match block {
|
||||
Some(block) => self.next_incoming(block).await,
|
||||
None => {
|
||||
warn!("stopped receiving new blocks");
|
||||
self.cancel.cancel();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::ScraperError;
|
||||
use crate::helpers;
|
||||
use tendermint::{abci, block, tx, Block, Hash};
|
||||
use tendermint_rpc::endpoint::{block as block_endpoint, block_results, validators};
|
||||
use tendermint_rpc::event::{Event, EventData};
|
||||
|
||||
// just get all everything out of tx::Response, but parse raw `tx` bytes
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ParsedTransactionResponse {
|
||||
/// The hash of the transaction.
|
||||
///
|
||||
/// Deserialized from a hex-encoded string (there is a discrepancy between
|
||||
/// the format used for the request and the format used for the response in
|
||||
/// the Tendermint RPC).
|
||||
pub hash: Hash,
|
||||
|
||||
pub height: block::Height,
|
||||
|
||||
pub index: u32,
|
||||
|
||||
pub tx_result: abci::types::ExecTxResult,
|
||||
|
||||
pub tx: cosmrs::tx::Tx,
|
||||
|
||||
pub proof: Option<tx::Proof>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FullBlockInformation {
|
||||
/// Basic block information, including its signers.
|
||||
pub block: Block,
|
||||
|
||||
/// All of the emitted events alongside any tx results.
|
||||
pub results: block_results::Response,
|
||||
|
||||
/// Validator set for this particular block
|
||||
pub validators: validators::Response,
|
||||
|
||||
/// Transaction results from this particular block
|
||||
pub transactions: Vec<ParsedTransactionResponse>,
|
||||
}
|
||||
|
||||
impl FullBlockInformation {
|
||||
pub fn ensure_proposer(&self) -> Result<(), ScraperError> {
|
||||
let block_proposer = self.block.header.proposer_address;
|
||||
if !self
|
||||
.validators
|
||||
.validators
|
||||
.iter()
|
||||
.any(|v| v.address == block_proposer)
|
||||
{
|
||||
let proposer = helpers::validator_consensus_address(block_proposer)?;
|
||||
return Err(ScraperError::BlockProposerNotInValidatorSet {
|
||||
height: self.block.header.height.value() as u32,
|
||||
proposer: proposer.to_string(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BlockToProcess {
|
||||
pub(crate) height: u32,
|
||||
pub(crate) block: Block,
|
||||
}
|
||||
|
||||
impl From<Block> for BlockToProcess {
|
||||
fn from(block: Block) -> Self {
|
||||
BlockToProcess {
|
||||
height: block.header.height.value() as u32,
|
||||
block,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Event> for BlockToProcess {
|
||||
type Error = ScraperError;
|
||||
|
||||
fn try_from(event: Event) -> Result<Self, Self::Error> {
|
||||
let query = event.query.clone();
|
||||
|
||||
// TODO: we're losing `result_begin_block` and `result_end_block` here but maybe that's fine?
|
||||
let maybe_block = match event.data {
|
||||
// we don't care about `NewBlock` until CometBFT 0.38, i.e. until we upgrade to wasmd 0.50
|
||||
EventData::NewBlock { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "NewBlock".to_string(),
|
||||
})
|
||||
}
|
||||
EventData::LegacyNewBlock { block, .. } => block,
|
||||
EventData::Tx { .. } => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "Tx".to_string(),
|
||||
})
|
||||
}
|
||||
EventData::GenericJsonEvent(_) => {
|
||||
return Err(ScraperError::InvalidSubscriptionEvent {
|
||||
query,
|
||||
kind: "GenericJsonEvent".to_string(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let Some(block) = maybe_block else {
|
||||
return Err(ScraperError::EmptyBlockData { query });
|
||||
};
|
||||
|
||||
Ok((*block).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<block_endpoint::Response> for BlockToProcess {
|
||||
fn from(value: block_endpoint::Response) -> Self {
|
||||
value.block.into()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::error::ScraperError;
|
||||
use crate::rpc_client::RpcClient;
|
||||
use futures::StreamExt;
|
||||
use std::ops::Range;
|
||||
use tokio::sync::mpsc::{Receiver, UnboundedSender};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, instrument, warn};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BlockRequest {
|
||||
Range(Range<u32>),
|
||||
|
||||
// UNIMPLEMENTED:
|
||||
#[allow(dead_code)]
|
||||
Specific(Vec<u32>),
|
||||
}
|
||||
|
||||
pub(crate) struct BlockRequester {
|
||||
cancel: CancellationToken,
|
||||
rpc_client: RpcClient,
|
||||
requests: ReceiverStream<BlockRequest>,
|
||||
blocks: UnboundedSender<BlockToProcess>,
|
||||
}
|
||||
|
||||
impl BlockRequester {
|
||||
pub(crate) fn new(
|
||||
cancel: CancellationToken,
|
||||
rpc_client: RpcClient,
|
||||
requests: Receiver<BlockRequest>,
|
||||
blocks: UnboundedSender<BlockToProcess>,
|
||||
) -> Self {
|
||||
BlockRequester {
|
||||
cancel,
|
||||
rpc_client,
|
||||
requests: requests.into(),
|
||||
blocks,
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_and_send(&self, height: u32) -> Result<(), ScraperError> {
|
||||
let block = self.rpc_client.get_basic_block_details(height).await?;
|
||||
self.blocks.send(block.into())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn request_blocks<I: IntoIterator<Item = u32>>(&self, heights: I) {
|
||||
futures::stream::iter(heights)
|
||||
.for_each_concurrent(4, |height| async move {
|
||||
if let Err(err) = self.request_and_send(height).await {
|
||||
error!("failed to request block data: {err}")
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
async fn handle_blocks_request(&self, request: BlockRequest) {
|
||||
info!("received request for missed blocks");
|
||||
|
||||
match request {
|
||||
BlockRequest::Range(range) => self.request_blocks(range).await,
|
||||
BlockRequest::Specific(heights) => self.request_blocks(heights).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
}
|
||||
maybe_request = self.requests.next() => {
|
||||
match maybe_request {
|
||||
Some(request) => self.handle_blocks_request(request).await,
|
||||
None => {
|
||||
warn!("stopped receiving new requests");
|
||||
self.cancel.cancel();
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use const_format::concatcp;
|
||||
|
||||
// TODO: make those configurable via 'NymNetworkDetails'
|
||||
|
||||
// BECH32_PREFIX defines the main SDK Bech32 prefix of an account's address
|
||||
pub const BECH32_PREFIX: &str = "n";
|
||||
|
||||
// ACCOUNT_PREFIX is the prefix for account keys
|
||||
pub const ACCOUNT_PREFIX: &str = "acc";
|
||||
// VALIDATOR_PREFIX is the prefix for validator keys
|
||||
pub const VALIDATOR_PREFIX: &str = "val";
|
||||
// CONSENSUS_PREFIX is the prefix for consensus keys
|
||||
pub const CONSENSUS_PREFIX: &str = "cons";
|
||||
// PUBKEY_PREFIX is the prefix for public keys
|
||||
pub const PUBKEY_PREFIX: &str = "pub";
|
||||
// OPERATOR_PREFIX is the prefix for operator keys
|
||||
pub const OPERATOR_PREFIX: &str = "oper";
|
||||
// ADDRESS_PREFIX is the prefix for addresses
|
||||
pub const ADDRESS_PREFIX: &str = "addr";
|
||||
|
||||
// BECH32_ACCOUNT_ADDRESS_PREFIX defines the Bech32 prefix of an account's address
|
||||
pub const BECH32_ACCOUNT_ADDRESS_PREFIX: &str = BECH32_PREFIX;
|
||||
// BECH32_ACCOUNT_PUBKEY_PREFIX defines the Bech32 prefix of an account's public key
|
||||
pub const BECH32_ACCOUNT_PUBKEY_PREFIX: &str = concatcp!(BECH32_PREFIX, PUBKEY_PREFIX);
|
||||
// BECH32_VALIDATOR_ADDRESS_PREFIX defines the Bech32 prefix of a validator's operator address
|
||||
pub const BECH32_VALIDATOR_ADDRESS_PREFIX: &str =
|
||||
concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, OPERATOR_PREFIX);
|
||||
// BECH32_VALIDATOR_PUBKEY_PREFIX defines the Bech32 prefix of a validator's operator public key
|
||||
pub const BECH32_VALIDATOR_PUBKEY_PREFIX: &str = concatcp!(
|
||||
BECH32_PREFIX,
|
||||
VALIDATOR_PREFIX,
|
||||
OPERATOR_PREFIX,
|
||||
PUBKEY_PREFIX
|
||||
);
|
||||
// BECH32_CONSENSUS_ADDRESS_PREFIX defines the Bech32 prefix of a consensus node address
|
||||
pub const BECH32_CONSENSUS_ADDRESS_PREFIX: &str =
|
||||
concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, CONSENSUS_PREFIX);
|
||||
// BECH32_CONESNSUS_PUBKEY_PREFIX defines the Bech32 prefix of a consensus node public key
|
||||
pub const BECH32_CONESNSUS_PUBKEY_PREFIX: &str = concatcp!(
|
||||
BECH32_PREFIX,
|
||||
VALIDATOR_PREFIX,
|
||||
CONSENSUS_PREFIX,
|
||||
PUBKEY_PREFIX
|
||||
);
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use tendermint::Hash;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc::error::SendError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ScraperError {
|
||||
#[error("experienced internal database error: {0}")]
|
||||
InternalDatabaseError(#[from] sqlx::Error),
|
||||
|
||||
#[error("failed to perform startup SQL migration: {0}")]
|
||||
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
|
||||
|
||||
#[error("can't add any modules to the scraper as it's already running")]
|
||||
ScraperAlreadyRunning,
|
||||
|
||||
#[error("failed to establish websocket connection to {url}: {source}")]
|
||||
WebSocketConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("failed to establish rpc connection to {url}: {source}")]
|
||||
HttpConnectionFailure {
|
||||
url: String,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("failed to create chain subscription: {source}")]
|
||||
ChainSubscriptionFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain basic block information at height: {height}: {source}")]
|
||||
BlockQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain block results information at height: {height}: {source}")]
|
||||
BlockResultsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain validators information at height: {height}: {source}")]
|
||||
ValidatorsQueryFailure {
|
||||
height: u32,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain tx results for tx: {hash}: {source}")]
|
||||
TxResultsQueryFailure {
|
||||
hash: Hash,
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not obtain current abci info: {source}")]
|
||||
AbciInfoQueryFailure {
|
||||
#[source]
|
||||
source: tendermint_rpc::Error,
|
||||
},
|
||||
|
||||
#[error("could not parse tx {hash}: {source}")]
|
||||
TxParseFailure {
|
||||
hash: Hash,
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")]
|
||||
InvalidSubscriptionEvent { query: String, kind: String },
|
||||
|
||||
#[error("received block data was empty (query: '{query}')")]
|
||||
EmptyBlockData { query: String },
|
||||
|
||||
#[error("reached maximum number of allowed errors for subscription events")]
|
||||
MaximumSubscriptionFailures,
|
||||
|
||||
#[error("failed to begin storage tx: {source}")]
|
||||
StorageTxBeginFailure {
|
||||
#[source]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
|
||||
#[error("failed to commit storage tx: {source}")]
|
||||
StorageTxCommitFailure {
|
||||
#[source]
|
||||
source: sqlx::Error,
|
||||
},
|
||||
|
||||
#[error("failed to send on a closed channel")]
|
||||
ClosedChannelError,
|
||||
|
||||
#[error("failed to parse validator's address: {source}")]
|
||||
MalformedValidatorAddress {
|
||||
#[source]
|
||||
source: eyre::Report,
|
||||
},
|
||||
|
||||
#[error("failed to parse validator's address: {source}")]
|
||||
MalformedValidatorPubkey {
|
||||
#[source]
|
||||
source: eyre::Report,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"could not find the block proposer ('{proposer}') for height {height} in the validator set"
|
||||
)]
|
||||
BlockProposerNotInValidatorSet { height: u32, proposer: String },
|
||||
|
||||
#[error(
|
||||
"could not find validator information for {address}; the validator has signed a commit"
|
||||
)]
|
||||
MissingValidatorInfoCommitted { address: String },
|
||||
}
|
||||
|
||||
impl<T> From<SendError<T>> for ScraperError {
|
||||
fn from(_: SendError<T>) -> Self {
|
||||
ScraperError::ClosedChannelError
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::ParsedTransactionResponse;
|
||||
use crate::constants::{BECH32_CONESNSUS_PUBKEY_PREFIX, BECH32_CONSENSUS_ADDRESS_PREFIX};
|
||||
use crate::error::ScraperError;
|
||||
use cosmrs::AccountId;
|
||||
use sha2::{Digest, Sha256};
|
||||
use tendermint::{account, PublicKey};
|
||||
use tendermint::{validator, Hash};
|
||||
use tendermint_rpc::endpoint::validators;
|
||||
|
||||
pub(crate) fn tx_hash<M: AsRef<[u8]>>(raw_tx: M) -> Hash {
|
||||
Hash::Sha256(Sha256::digest(raw_tx).into())
|
||||
}
|
||||
|
||||
pub(crate) fn validator_pubkey_to_bech32(pubkey: PublicKey) -> Result<AccountId, ScraperError> {
|
||||
// TODO: this one seem to attach additional prefix to they pubkeys, is that what we want instead maybe?
|
||||
// Ok(pubkey.to_bech32(BECH32_CONESNSUS_PUBKEY_PREFIX))
|
||||
AccountId::new(BECH32_CONESNSUS_PUBKEY_PREFIX, &pubkey.to_bytes())
|
||||
.map_err(|source| ScraperError::MalformedValidatorPubkey { source })
|
||||
}
|
||||
|
||||
pub(crate) fn validator_consensus_address(id: account::Id) -> Result<AccountId, ScraperError> {
|
||||
AccountId::new(BECH32_CONSENSUS_ADDRESS_PREFIX, id.as_ref())
|
||||
.map_err(|source| ScraperError::MalformedValidatorAddress { source })
|
||||
}
|
||||
|
||||
pub(crate) fn tx_gas_sum(txs: &[ParsedTransactionResponse]) -> i64 {
|
||||
txs.iter().map(|tx| tx.tx_result.gas_used).sum()
|
||||
}
|
||||
|
||||
pub(crate) fn validator_info(
|
||||
id: account::Id,
|
||||
validators: &validators::Response,
|
||||
) -> Result<&validator::Info, ScraperError> {
|
||||
match validators.validators.iter().find(|v| v.address == id) {
|
||||
Some(info) => Ok(info),
|
||||
None => {
|
||||
let addr = validator_consensus_address(id)?;
|
||||
Err(ScraperError::MissingValidatorInfoCommitted {
|
||||
address: addr.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#![warn(clippy::expect_used)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
pub(crate) mod block_processor;
|
||||
pub(crate) mod block_requester;
|
||||
pub mod constants;
|
||||
pub mod error;
|
||||
pub(crate) mod helpers;
|
||||
pub mod modules;
|
||||
pub(crate) mod rpc_client;
|
||||
pub(crate) mod scraper;
|
||||
pub mod storage;
|
||||
|
||||
pub use modules::{BlockModule, MsgModule, TxModule};
|
||||
pub use scraper::{Config, NyxdScraper};
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::FullBlockInformation;
|
||||
use crate::error::ScraperError;
|
||||
use crate::storage::StorageTransaction;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait BlockModule {
|
||||
async fn handle_block(
|
||||
&mut self,
|
||||
block: &FullBlockInformation,
|
||||
storage_tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError>;
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
mod block_module;
|
||||
mod msg_module;
|
||||
mod tx_module;
|
||||
|
||||
pub use block_module::BlockModule;
|
||||
pub use msg_module::MsgModule;
|
||||
pub use tx_module::TxModule;
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::ParsedTransactionResponse;
|
||||
use crate::error::ScraperError;
|
||||
use crate::storage::StorageTransaction;
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::Any;
|
||||
|
||||
#[async_trait]
|
||||
pub trait MsgModule {
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
index: usize,
|
||||
msg: &Any,
|
||||
tx: &ParsedTransactionResponse,
|
||||
storage_tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError>;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::ParsedTransactionResponse;
|
||||
use crate::error::ScraperError;
|
||||
use crate::storage::StorageTransaction;
|
||||
use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait TxModule {
|
||||
async fn handle_tx(
|
||||
&mut self,
|
||||
tx: &ParsedTransactionResponse,
|
||||
storage_tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError>;
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::{
|
||||
BlockToProcess, FullBlockInformation, ParsedTransactionResponse,
|
||||
};
|
||||
use crate::error::ScraperError;
|
||||
use crate::helpers::tx_hash;
|
||||
use futures::future::join3;
|
||||
use futures::StreamExt;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use tendermint::Hash;
|
||||
use tendermint_rpc::endpoint::{block, block_results, tx, validators};
|
||||
use tendermint_rpc::{Client, HttpClient, Paging};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{debug, instrument};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RpcClient {
|
||||
// right now I don't care about anything nym specific, so a simple http client is sufficient,
|
||||
// once this is inadequate, we can switch to a NyxdClient
|
||||
inner: Arc<HttpClient>,
|
||||
}
|
||||
|
||||
impl RpcClient {
|
||||
pub fn new(url: &Url) -> Result<Self, ScraperError> {
|
||||
let http_client = HttpClient::new(url.as_str()).map_err(|source| {
|
||||
ScraperError::HttpConnectionFailure {
|
||||
url: url.to_string(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(RpcClient {
|
||||
inner: Arc::new(http_client),
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self, block), fields(height = block.height))]
|
||||
pub async fn try_get_full_details(
|
||||
&self,
|
||||
block: BlockToProcess,
|
||||
) -> Result<FullBlockInformation, ScraperError> {
|
||||
debug!("getting complete block details");
|
||||
let height = block.height;
|
||||
|
||||
// make all the http requests concurrently
|
||||
let (results, validators, raw_transactions) = join3(
|
||||
self.get_block_results(height),
|
||||
self.get_validators_details(height),
|
||||
self.get_transaction_results(&block.block.data),
|
||||
)
|
||||
.await;
|
||||
|
||||
let raw_transactions = raw_transactions?;
|
||||
let mut transactions = Vec::with_capacity(raw_transactions.len());
|
||||
for tx in raw_transactions {
|
||||
transactions.push(ParsedTransactionResponse {
|
||||
hash: tx.hash,
|
||||
height: tx.height,
|
||||
index: tx.index,
|
||||
tx_result: tx.tx_result,
|
||||
tx: cosmrs::Tx::from_bytes(&tx.tx).map_err(|source| {
|
||||
ScraperError::TxParseFailure {
|
||||
hash: tx.hash,
|
||||
source,
|
||||
}
|
||||
})?,
|
||||
proof: tx.proof,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(FullBlockInformation {
|
||||
block: block.block,
|
||||
results: results?,
|
||||
validators: validators?,
|
||||
transactions,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self), err(Display))]
|
||||
pub async fn get_basic_block_details(
|
||||
&self,
|
||||
height: u32,
|
||||
) -> Result<block::Response, ScraperError> {
|
||||
debug!("getting basic block details");
|
||||
|
||||
self.inner
|
||||
.block(height)
|
||||
.await
|
||||
.map_err(|source| ScraperError::BlockQueryFailure { height, source })
|
||||
}
|
||||
|
||||
#[instrument(skip(self), err(Display))]
|
||||
pub async fn get_block_results(
|
||||
&self,
|
||||
height: u32,
|
||||
) -> Result<block_results::Response, ScraperError> {
|
||||
debug!("getting block results");
|
||||
|
||||
self.inner
|
||||
.block_results(height)
|
||||
.await
|
||||
.map_err(|source| ScraperError::BlockResultsQueryFailure { height, source })
|
||||
}
|
||||
|
||||
pub(crate) async fn current_block_height(&self) -> Result<u64, ScraperError> {
|
||||
debug!("getting current block height");
|
||||
|
||||
let info = self
|
||||
.inner
|
||||
.abci_info()
|
||||
.await
|
||||
.map_err(|source| ScraperError::AbciInfoQueryFailure { source })?;
|
||||
Ok(info.last_block_height.value())
|
||||
}
|
||||
|
||||
async fn get_transaction_results(
|
||||
&self,
|
||||
raw: &[Vec<u8>],
|
||||
) -> Result<Vec<tx::Response>, ScraperError> {
|
||||
let ordered_results = Arc::new(Mutex::new(BTreeMap::new()));
|
||||
|
||||
// "Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays"
|
||||
// source: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#data
|
||||
//
|
||||
// I hate that zip as much as you, dear reader, but for some reason the compiler didn't let me remove the `move`
|
||||
futures::stream::iter(
|
||||
raw.iter()
|
||||
.map(tx_hash)
|
||||
.enumerate()
|
||||
.zip(std::iter::repeat(ordered_results.clone())),
|
||||
)
|
||||
.for_each_concurrent(4, |((id, tx_hash), ordered_results)| async move {
|
||||
let res = self.get_transaction_result(tx_hash).await;
|
||||
ordered_results.lock().await.insert(id, res);
|
||||
})
|
||||
.await;
|
||||
|
||||
// safety the futures have completed so we MUST have the only arc reference
|
||||
#[allow(clippy::unwrap_used)]
|
||||
let inner = Arc::into_inner(ordered_results).unwrap().into_inner();
|
||||
|
||||
// BTreeMap is ordered by its keys so we're guaranteed to get txs in correct order
|
||||
inner.into_values().collect()
|
||||
}
|
||||
|
||||
#[instrument(skip(self, tx_hash), fields(tx_hash = %tx_hash), err(Display))]
|
||||
async fn get_transaction_result(&self, tx_hash: Hash) -> Result<tx::Response, ScraperError> {
|
||||
debug!("getting tx results");
|
||||
|
||||
self.inner
|
||||
.tx(tx_hash, false)
|
||||
.await
|
||||
.map_err(|source| ScraperError::TxResultsQueryFailure {
|
||||
hash: tx_hash,
|
||||
source,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub async fn get_validators_details(
|
||||
&self,
|
||||
height: u32,
|
||||
) -> Result<validators::Response, ScraperError> {
|
||||
debug!("getting validators set");
|
||||
|
||||
self.inner
|
||||
.validators(height, Paging::All)
|
||||
.await
|
||||
.map_err(|source| ScraperError::ValidatorsQueryFailure { height, source })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::BlockProcessor;
|
||||
use crate::block_requester::BlockRequester;
|
||||
use crate::error::ScraperError;
|
||||
use crate::modules::{BlockModule, MsgModule, TxModule};
|
||||
use crate::rpc_client::RpcClient;
|
||||
use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber};
|
||||
use crate::storage::ScraperStorage;
|
||||
use std::path::PathBuf;
|
||||
use tendermint_rpc::WebSocketClientDriver;
|
||||
use tokio::sync::mpsc::{channel, unbounded_channel};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::task::TaskTracker;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
mod subscriber;
|
||||
|
||||
pub struct Config {
|
||||
/// Url to the websocket endpoint of a validator, for example `wss://rpc.nymtech.net/websocket`
|
||||
pub websocket_url: Url,
|
||||
|
||||
/// Url to the rpc endpoint of a validator, for example `https://rpc.nymtech.net/`
|
||||
pub rpc_url: Url,
|
||||
|
||||
pub database_path: PathBuf,
|
||||
}
|
||||
|
||||
pub struct NyxdScraperBuilder {
|
||||
config: Config,
|
||||
|
||||
block_modules: Vec<Box<dyn BlockModule + Send>>,
|
||||
tx_modules: Vec<Box<dyn TxModule + Send>>,
|
||||
msg_modules: Vec<Box<dyn MsgModule + Send>>,
|
||||
}
|
||||
|
||||
impl NyxdScraperBuilder {
|
||||
pub async fn build_and_start(self) -> Result<NyxdScraper, ScraperError> {
|
||||
let scraper = NyxdScraper::new(self.config).await?;
|
||||
|
||||
let (processing_tx, processing_rx) = unbounded_channel();
|
||||
let (req_tx, req_rx) = channel(5);
|
||||
|
||||
let rpc_client = RpcClient::new(&scraper.config.rpc_url)?;
|
||||
|
||||
// create the tasks
|
||||
let block_requester = BlockRequester::new(
|
||||
scraper.cancel_token.clone(),
|
||||
rpc_client.clone(),
|
||||
req_rx,
|
||||
processing_tx.clone(),
|
||||
);
|
||||
let mut block_processor = BlockProcessor::new(
|
||||
scraper.cancel_token.clone(),
|
||||
processing_rx,
|
||||
req_tx,
|
||||
scraper.storage.clone(),
|
||||
rpc_client,
|
||||
)
|
||||
.await?;
|
||||
block_processor.set_block_modules(self.block_modules);
|
||||
block_processor.set_tx_modules(self.tx_modules);
|
||||
block_processor.set_msg_modules(self.msg_modules);
|
||||
|
||||
let mut chain_subscriber = ChainSubscriber::new(
|
||||
&scraper.config.websocket_url,
|
||||
scraper.cancel_token.clone(),
|
||||
processing_tx,
|
||||
)
|
||||
.await?;
|
||||
let ws_driver = chain_subscriber.ws_driver();
|
||||
|
||||
scraper.start_tasks(
|
||||
block_requester,
|
||||
block_processor,
|
||||
chain_subscriber,
|
||||
ws_driver,
|
||||
);
|
||||
|
||||
Ok(scraper)
|
||||
}
|
||||
|
||||
pub fn new(config: Config) -> Self {
|
||||
NyxdScraperBuilder {
|
||||
config,
|
||||
block_modules: vec![],
|
||||
tx_modules: vec![],
|
||||
msg_modules: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_block_module<M: BlockModule + Send + 'static>(mut self, module: M) -> Self {
|
||||
self.block_modules.push(Box::new(module));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_tx_module<M: TxModule + Send + 'static>(mut self, module: M) -> Self {
|
||||
self.tx_modules.push(Box::new(module));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_msg_module<M: MsgModule + Send + 'static>(mut self, module: M) -> Self {
|
||||
self.msg_modules.push(Box::new(module));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NyxdScraper {
|
||||
config: Config,
|
||||
|
||||
task_tracker: TaskTracker,
|
||||
cancel_token: CancellationToken,
|
||||
storage: ScraperStorage,
|
||||
}
|
||||
|
||||
impl NyxdScraper {
|
||||
pub fn builder(config: Config) -> NyxdScraperBuilder {
|
||||
NyxdScraperBuilder::new(config)
|
||||
}
|
||||
|
||||
pub async fn new(config: Config) -> Result<Self, ScraperError> {
|
||||
let storage = ScraperStorage::init(&config.database_path).await?;
|
||||
|
||||
Ok(NyxdScraper {
|
||||
config,
|
||||
task_tracker: TaskTracker::new(),
|
||||
cancel_token: CancellationToken::new(),
|
||||
storage,
|
||||
})
|
||||
}
|
||||
|
||||
fn start_tasks(
|
||||
&self,
|
||||
mut block_requester: BlockRequester,
|
||||
mut block_processor: BlockProcessor,
|
||||
mut chain_subscriber: ChainSubscriber,
|
||||
ws_driver: WebSocketClientDriver,
|
||||
) {
|
||||
self.task_tracker
|
||||
.spawn(async move { block_requester.run().await });
|
||||
self.task_tracker
|
||||
.spawn(async move { block_processor.run().await });
|
||||
self.task_tracker
|
||||
.spawn(async move { chain_subscriber.run().await });
|
||||
self.task_tracker
|
||||
.spawn(run_websocket_driver(ws_driver, self.cancel_token.clone()));
|
||||
self.task_tracker.close();
|
||||
}
|
||||
|
||||
pub async fn start(&self) -> Result<(), ScraperError> {
|
||||
let (processing_tx, processing_rx) = unbounded_channel();
|
||||
let (req_tx, req_rx) = channel(5);
|
||||
|
||||
let rpc_client = RpcClient::new(&self.config.rpc_url)?;
|
||||
|
||||
// create the tasks
|
||||
let block_requester = BlockRequester::new(
|
||||
self.cancel_token.clone(),
|
||||
rpc_client.clone(),
|
||||
req_rx,
|
||||
processing_tx.clone(),
|
||||
);
|
||||
let block_processor = BlockProcessor::new(
|
||||
self.cancel_token.clone(),
|
||||
processing_rx,
|
||||
req_tx,
|
||||
self.storage.clone(),
|
||||
rpc_client,
|
||||
)
|
||||
.await?;
|
||||
let mut chain_subscriber = ChainSubscriber::new(
|
||||
&self.config.websocket_url,
|
||||
self.cancel_token.clone(),
|
||||
processing_tx,
|
||||
)
|
||||
.await?;
|
||||
let ws_driver = chain_subscriber.ws_driver();
|
||||
|
||||
// spawn them
|
||||
self.start_tasks(
|
||||
block_requester,
|
||||
block_processor,
|
||||
chain_subscriber,
|
||||
ws_driver,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn stop(self) {
|
||||
info!("stopping the chain scrapper");
|
||||
self.cancel_token.cancel();
|
||||
self.task_tracker.wait().await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::BlockToProcess;
|
||||
use crate::error::ScraperError;
|
||||
use tendermint_rpc::event::Event;
|
||||
use tendermint_rpc::query::EventType;
|
||||
use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientDriver};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, warn};
|
||||
use url::Url;
|
||||
|
||||
const MAX_FAILURES: usize = 10;
|
||||
|
||||
pub struct ChainSubscriber {
|
||||
cancel: CancellationToken,
|
||||
block_sender: UnboundedSender<BlockToProcess>,
|
||||
|
||||
websocket_client: WebSocketClient,
|
||||
websocket_driver: Option<WebSocketClientDriver>,
|
||||
}
|
||||
|
||||
impl ChainSubscriber {
|
||||
pub async fn new(
|
||||
websocket_endpoint: &Url,
|
||||
cancel: CancellationToken,
|
||||
block_sender: UnboundedSender<BlockToProcess>,
|
||||
) -> Result<Self, ScraperError> {
|
||||
// sure, we could have just used websocket client entirely, but let's keep the logic for
|
||||
// getting current blocks and historical blocks completely separate with the dual connection
|
||||
let (client, driver) = WebSocketClient::new(websocket_endpoint.as_str())
|
||||
.await
|
||||
.map_err(|source| ScraperError::WebSocketConnectionFailure {
|
||||
url: websocket_endpoint.to_string(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
Ok(ChainSubscriber {
|
||||
cancel,
|
||||
block_sender,
|
||||
websocket_client: client,
|
||||
websocket_driver: Some(driver),
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_new_event(&mut self, event: Event) -> Result<(), ScraperError> {
|
||||
if let Err(err) = self.block_sender.send(event.try_into()?) {
|
||||
// this error has nothing to do with the websocket or chain
|
||||
error!("failed to send block for processing: {err} - are we shutting down?")
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) -> Result<(), ScraperError> {
|
||||
let _drop_guard = self.cancel.clone().drop_guard();
|
||||
|
||||
info!("creating chain subscription");
|
||||
let mut subs = self
|
||||
.websocket_client
|
||||
.subscribe(EventType::NewBlock.into())
|
||||
.await
|
||||
.map_err(|source| ScraperError::ChainSubscriptionFailure { source })?;
|
||||
|
||||
let mut failures = 0;
|
||||
|
||||
info!("starting processing loop");
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.cancel.cancelled() => {
|
||||
info!("received cancellation token");
|
||||
break
|
||||
}
|
||||
maybe_event = subs.next() => {
|
||||
let Some(maybe_event) = maybe_event else {
|
||||
warn!("stopped receiving new events");
|
||||
break;
|
||||
};
|
||||
match maybe_event {
|
||||
Ok(event) => {
|
||||
if let Err(err) = self.handle_new_event(event) {
|
||||
error!("failed to process received block: {err}");
|
||||
failures += 1
|
||||
} else {
|
||||
failures = 0;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("failed to receive a valid subscription event: {err}");
|
||||
failures += 1
|
||||
}
|
||||
}
|
||||
if failures >= MAX_FAILURES {
|
||||
// note: the drop_guard will get dropped and thus cause a shutdown
|
||||
return Err(ScraperError::MaximumSubscriptionFailures);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn ws_driver(&mut self) -> WebSocketClientDriver {
|
||||
#[allow(clippy::expect_used)]
|
||||
self.websocket_driver
|
||||
.take()
|
||||
.expect("websocket driver has already been started!")
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_websocket_driver(driver: WebSocketClientDriver, cancel: CancellationToken) {
|
||||
info!("starting websocket driver");
|
||||
tokio::select! {
|
||||
_ = cancel.cancelled() => {
|
||||
info!("received cancellation token")
|
||||
}
|
||||
res = driver.run() => {
|
||||
match res {
|
||||
Ok(_) => info!("our websocket driver has finished execution"),
|
||||
Err(err) => {
|
||||
// TODO: in the future just attempt to reconnect
|
||||
error!("our websocket driver has errored out: {err}")
|
||||
}
|
||||
}
|
||||
cancel.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
@@ -0,0 +1,328 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::storage::models::Validator;
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use sqlx::{Executor, Sqlite};
|
||||
use tracing::{instrument, trace};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct StorageManager {
|
||||
pub(crate) connection_pool: sqlx::SqlitePool,
|
||||
}
|
||||
|
||||
impl StorageManager {
|
||||
pub(crate) async fn set_initial_metadata(&self) -> Result<(), sqlx::Error> {
|
||||
if sqlx::query("SELECT * from metadata")
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await?
|
||||
.is_none()
|
||||
{
|
||||
sqlx::query("INSERT INTO metadata (id, last_processed_height) VALUES (0, 0)")
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_first_block_height_after(
|
||||
&self,
|
||||
time: OffsetDateTime,
|
||||
) -> Result<Option<i64>, sqlx::Error> {
|
||||
let maybe_record = sqlx::query!(
|
||||
r#"
|
||||
SELECT height
|
||||
FROM block
|
||||
WHERE timestamp > ?
|
||||
ORDER BY timestamp
|
||||
LIMIT 1
|
||||
"#,
|
||||
time
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = maybe_record {
|
||||
Ok(row.height)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_last_block_height_before(
|
||||
&self,
|
||||
time: OffsetDateTime,
|
||||
) -> Result<Option<i64>, sqlx::Error> {
|
||||
let maybe_record = sqlx::query!(
|
||||
r#"
|
||||
SELECT height
|
||||
FROM block
|
||||
WHERE timestamp < ?
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT 1
|
||||
"#,
|
||||
time
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = maybe_record {
|
||||
Ok(row.height)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn get_signed_between(
|
||||
&self,
|
||||
consensus_address: &str,
|
||||
start_height: i64,
|
||||
end_height: i64,
|
||||
) -> Result<i32, sqlx::Error> {
|
||||
let count = sqlx::query!(
|
||||
r#"
|
||||
SELECT COUNT(*) as count FROM pre_commit
|
||||
WHERE
|
||||
validator_address == ?
|
||||
AND height > ?
|
||||
AND height < ?
|
||||
"#,
|
||||
consensus_address,
|
||||
start_height,
|
||||
end_height
|
||||
)
|
||||
.fetch_one(&self.connection_pool)
|
||||
.await?
|
||||
.count;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_block_validators(
|
||||
&self,
|
||||
height: i64,
|
||||
) -> Result<Vec<Validator>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Validator,
|
||||
r#"
|
||||
SELECT * FROM validator
|
||||
WHERE EXISTS (
|
||||
SELECT 1 FROM pre_commit
|
||||
WHERE height == ?
|
||||
AND pre_commit.validator_address = validator.consensus_address
|
||||
)
|
||||
"#,
|
||||
height
|
||||
)
|
||||
.fetch_all(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_last_processed_height(&self) -> Result<i64, sqlx::Error> {
|
||||
let maybe_record = sqlx::query!(
|
||||
r#"
|
||||
SELECT last_processed_height FROM metadata
|
||||
"#
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await?;
|
||||
|
||||
if let Some(row) = maybe_record {
|
||||
Ok(row.last_processed_height)
|
||||
} else {
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make those generic over executor so that they could be performed over connection pool and a tx
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
pub(crate) async fn insert_validator<'a, E>(
|
||||
consensus_address: String,
|
||||
consensus_pubkey: String,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("insert validator");
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO validator (consensus_address, consensus_pubkey)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
"#,
|
||||
consensus_address,
|
||||
consensus_pubkey
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
pub(crate) async fn insert_block<'a, E>(
|
||||
height: i64,
|
||||
hash: String,
|
||||
num_txs: u32,
|
||||
total_gas: i64,
|
||||
proposer_address: String,
|
||||
timestamp: OffsetDateTime,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("insert block");
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO block (height, hash, num_txs, total_gas, proposer_address, timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT DO NOTHING
|
||||
"#,
|
||||
height,
|
||||
hash,
|
||||
num_txs,
|
||||
total_gas,
|
||||
proposer_address,
|
||||
timestamp
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
pub(crate) async fn insert_precommit<'a, E>(
|
||||
validator_address: String,
|
||||
height: i64,
|
||||
timestamp: OffsetDateTime,
|
||||
voting_power: i64,
|
||||
proposer_priority: i64,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("insert precommit");
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO pre_commit (validator_address, height, timestamp, voting_power, proposer_priority)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT (validator_address, timestamp) DO NOTHING
|
||||
"#,
|
||||
validator_address,
|
||||
height,
|
||||
timestamp,
|
||||
voting_power,
|
||||
proposer_priority
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn insert_transaction<'a, E>(
|
||||
hash: String,
|
||||
height: i64,
|
||||
index: i64,
|
||||
success: bool,
|
||||
messages: i64,
|
||||
memo: String,
|
||||
gas_wanted: i64,
|
||||
gas_used: i64,
|
||||
raw_log: String,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("insert transaction");
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO "transaction" (hash, height, "index", success, num_messages, memo, gas_wanted, gas_used, raw_log)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT (hash) DO UPDATE
|
||||
SET height = excluded.height,
|
||||
"index" = excluded."index",
|
||||
success = excluded.success,
|
||||
num_messages = excluded.num_messages,
|
||||
memo = excluded.memo,
|
||||
gas_wanted = excluded.gas_wanted,
|
||||
gas_used = excluded.gas_used,
|
||||
raw_log = excluded.raw_log
|
||||
"#,
|
||||
hash,
|
||||
height,
|
||||
index,
|
||||
success,
|
||||
messages,
|
||||
memo,
|
||||
gas_wanted,
|
||||
gas_used,
|
||||
raw_log,
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
pub(crate) async fn insert_message<'a, E>(
|
||||
transaction_hash: String,
|
||||
index: i64,
|
||||
typ: String,
|
||||
height: i64,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("insert message");
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO message (transaction_hash, "index", type, height)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT (transaction_hash, "index") DO UPDATE
|
||||
SET height = excluded.height,
|
||||
type = excluded.type
|
||||
"#,
|
||||
transaction_hash,
|
||||
index,
|
||||
typ,
|
||||
height
|
||||
)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(skip(executor))]
|
||||
pub(crate) async fn update_last_processed<'a, E>(
|
||||
height: i64,
|
||||
executor: E,
|
||||
) -> Result<(), sqlx::Error>
|
||||
where
|
||||
E: Executor<'a, Database = Sqlite>,
|
||||
{
|
||||
trace!("update_last_processed");
|
||||
|
||||
sqlx::query!("UPDATE metadata SET last_processed_height = ?", height)
|
||||
.execute(executor)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::block_processor::types::{FullBlockInformation, ParsedTransactionResponse};
|
||||
use crate::error::ScraperError;
|
||||
use crate::storage::manager::{
|
||||
insert_block, insert_message, insert_precommit, insert_transaction, insert_validator,
|
||||
update_last_processed, StorageManager,
|
||||
};
|
||||
use crate::storage::models::Validator;
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use sqlx::{ConnectOptions, Sqlite, Transaction};
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
use tendermint::block::{Commit, CommitSig};
|
||||
use tendermint::Block;
|
||||
use tendermint_rpc::endpoint::validators;
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
mod helpers;
|
||||
mod manager;
|
||||
mod models;
|
||||
|
||||
pub type StorageTransaction = Transaction<'static, Sqlite>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScraperStorage {
|
||||
pub(crate) manager: StorageManager,
|
||||
}
|
||||
|
||||
impl ScraperStorage {
|
||||
#[instrument]
|
||||
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> {
|
||||
// TODO: we can inject here more stuff based on our nym-api global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.filename(database_path)
|
||||
.create_if_missing(true);
|
||||
|
||||
// TODO: do we want auto_vacuum ?
|
||||
|
||||
opts.disable_statement_logging();
|
||||
|
||||
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
||||
Ok(db) => db,
|
||||
Err(err) => {
|
||||
error!("Failed to connect to SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = sqlx::migrate!("./sql_migrations")
|
||||
.run(&connection_pool)
|
||||
.await
|
||||
{
|
||||
error!("Failed to initialize SQLx database: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
|
||||
info!("Database migration finished!");
|
||||
|
||||
let manager = StorageManager { connection_pool };
|
||||
manager.set_initial_metadata().await?;
|
||||
|
||||
let storage = ScraperStorage { manager };
|
||||
|
||||
Ok(storage)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn begin_processing_tx(&self) -> Result<StorageTransaction, ScraperError> {
|
||||
debug!("starting storage tx");
|
||||
self.manager
|
||||
.connection_pool
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|source| ScraperError::StorageTxBeginFailure { source })
|
||||
}
|
||||
|
||||
pub async fn get_first_block_height_after(
|
||||
&self,
|
||||
time: OffsetDateTime,
|
||||
) -> Result<Option<i64>, ScraperError> {
|
||||
Ok(self.manager.get_first_block_height_after(time).await?)
|
||||
}
|
||||
|
||||
pub async fn get_last_block_height_before(
|
||||
&self,
|
||||
time: OffsetDateTime,
|
||||
) -> Result<Option<i64>, ScraperError> {
|
||||
Ok(self.manager.get_last_block_height_before(time).await?)
|
||||
}
|
||||
|
||||
pub async fn get_signed_between(
|
||||
&self,
|
||||
consensus_address: &str,
|
||||
start_height: i64,
|
||||
end_height: i64,
|
||||
) -> Result<i32, ScraperError> {
|
||||
Ok(self
|
||||
.manager
|
||||
.get_signed_between(consensus_address, start_height, end_height)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_signed_between_times(
|
||||
&self,
|
||||
consensus_address: &str,
|
||||
start_time: OffsetDateTime,
|
||||
end_time: OffsetDateTime,
|
||||
) -> Result<i32, ScraperError> {
|
||||
let Some(block_start) = self.get_first_block_height_after(start_time).await? else {
|
||||
return Ok(0);
|
||||
};
|
||||
let Some(block_end) = self.get_last_block_height_before(end_time).await? else {
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
self.get_signed_between(consensus_address, block_start, block_end)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_block_signers(&self, height: i64) -> Result<Vec<Validator>, ScraperError> {
|
||||
Ok(self.manager.get_block_validators(height).await?)
|
||||
}
|
||||
|
||||
pub async fn get_last_processed_height(&self) -> Result<i64, ScraperError> {
|
||||
Ok(self.manager.get_last_processed_height().await?)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn persist_block(
|
||||
block: &FullBlockInformation,
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
let total_gas = crate::helpers::tx_gas_sum(&block.transactions);
|
||||
|
||||
// SANITY CHECK: make sure the block proposer is present in the validator set
|
||||
block.ensure_proposer()?;
|
||||
|
||||
// persist validators
|
||||
persist_validators(&block.validators, tx).await?;
|
||||
|
||||
// persist block data
|
||||
persist_block_data(&block.block, total_gas, tx).await?;
|
||||
|
||||
// persist commits
|
||||
if let Some(commit) = &block.block.last_commit {
|
||||
persist_commits(commit, &block.validators, tx).await?;
|
||||
} else {
|
||||
warn!("no commits for block {}", block.block.header.height)
|
||||
}
|
||||
|
||||
// persist txs
|
||||
persist_txs(&block.transactions, tx).await?;
|
||||
|
||||
// persist messages (inside the transactions)
|
||||
persist_messages(&block.transactions, tx).await?;
|
||||
|
||||
update_last_processed(block.block.header.height.into(), tx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_validators(
|
||||
validators: &validators::Response,
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
debug!("persisting {} validators", validators.total);
|
||||
for validator in &validators.validators {
|
||||
let consensus_address = crate::helpers::validator_consensus_address(validator.address)?;
|
||||
let consensus_pubkey = crate::helpers::validator_pubkey_to_bech32(validator.pub_key)?;
|
||||
|
||||
insert_validator(
|
||||
consensus_address.to_string(),
|
||||
consensus_pubkey.to_string(),
|
||||
&mut *tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_block_data(
|
||||
block: &Block,
|
||||
total_gas: i64,
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
let proposer_address =
|
||||
crate::helpers::validator_consensus_address(block.header.proposer_address)?.to_string();
|
||||
|
||||
insert_block(
|
||||
block.header.height.into(),
|
||||
block.header.hash().to_string(),
|
||||
block.data.len() as u32,
|
||||
total_gas,
|
||||
proposer_address,
|
||||
block.header.time.into(),
|
||||
tx,
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_commits(
|
||||
commits: &Commit,
|
||||
validators: &validators::Response,
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
debug!("persisting up to {} commits", commits.signatures.len());
|
||||
let height: i64 = commits.height.into();
|
||||
|
||||
for commit_sig in &commits.signatures {
|
||||
let (validator_id, timestamp, signature) = match commit_sig {
|
||||
CommitSig::BlockIdFlagAbsent => {
|
||||
trace!("absent signature");
|
||||
continue;
|
||||
}
|
||||
CommitSig::BlockIdFlagCommit {
|
||||
validator_address,
|
||||
timestamp,
|
||||
signature,
|
||||
} => (validator_address, timestamp, signature),
|
||||
CommitSig::BlockIdFlagNil {
|
||||
validator_address,
|
||||
timestamp,
|
||||
signature,
|
||||
} => (validator_address, timestamp, signature),
|
||||
};
|
||||
|
||||
let validator = crate::helpers::validator_info(*validator_id, validators)?;
|
||||
let validator_address = crate::helpers::validator_consensus_address(*validator_id)?;
|
||||
|
||||
if signature.is_none() {
|
||||
warn!("empty signature for {validator_address} at height {height}");
|
||||
continue;
|
||||
}
|
||||
|
||||
insert_precommit(
|
||||
validator_address.to_string(),
|
||||
height,
|
||||
(*timestamp).into(),
|
||||
validator.power.into(),
|
||||
validator.proposer_priority.value(),
|
||||
&mut *tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_txs(
|
||||
txs: &[ParsedTransactionResponse],
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
debug!("persisting {} txs", txs.len());
|
||||
|
||||
for chain_tx in txs {
|
||||
insert_transaction(
|
||||
chain_tx.hash.to_string(),
|
||||
chain_tx.height.into(),
|
||||
chain_tx.index as i64,
|
||||
chain_tx.tx_result.code.is_ok(),
|
||||
chain_tx.tx.body.messages.len() as i64,
|
||||
chain_tx.tx.body.memo.clone(),
|
||||
chain_tx.tx_result.gas_wanted,
|
||||
chain_tx.tx_result.gas_used,
|
||||
chain_tx.tx_result.log.clone(),
|
||||
&mut *tx,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn persist_messages(
|
||||
txs: &[ParsedTransactionResponse],
|
||||
tx: &mut StorageTransaction,
|
||||
) -> Result<(), ScraperError> {
|
||||
debug!("persisting messages");
|
||||
|
||||
for chain_tx in txs {
|
||||
for (index, msg) in chain_tx.tx.body.messages.iter().enumerate() {
|
||||
insert_message(
|
||||
chain_tx.hash.to_string(),
|
||||
index as i64,
|
||||
msg.type_url.clone(),
|
||||
chain_tx.height.into(),
|
||||
&mut *tx,
|
||||
)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use sqlx::types::time::OffsetDateTime;
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct Validator {
|
||||
pub consensus_address: String,
|
||||
pub consensus_pubkey: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct Block {
|
||||
pub height: i64,
|
||||
pub hash: String,
|
||||
pub num_txs: u32,
|
||||
pub total_gas: i64,
|
||||
pub proposer_address: String,
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct CommitSignature {
|
||||
pub height: i64,
|
||||
pub validator_address: String,
|
||||
pub voting_power: i64,
|
||||
pub proposer_priority: i64,
|
||||
pub timestamp: OffsetDateTime,
|
||||
}
|
||||
@@ -15,6 +15,6 @@ log = { workspace = true }
|
||||
reqwest = { workspace = true, features = ["json"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
|
||||
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "chrono"]}
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.24.1", features = [ "time" ] }
|
||||
|
||||
@@ -16,10 +16,12 @@ base64 = "0.21.3"
|
||||
# version mismatch with x25519-dalek/curve25519-dalek that is resolved in the
|
||||
# latest commit. So pick that for now.
|
||||
x25519-dalek = "2.0.0"
|
||||
defguard_wireguard_rs = { git = "https://github.com/neacsu/wireguard-rs.git", rev = "c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed" }
|
||||
ip_network = "0.4.1"
|
||||
log.workspace = true
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-task = { path = "../task" }
|
||||
nym-wireguard-types = { path = "../wireguard-types" }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
|
||||
|
||||
[target."cfg(target_os = \"linux\")".dependencies]
|
||||
defguard_wireguard_rs = { git = "https://github.com/neacsu/wireguard-rs.git", rev = "c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed" }
|
||||
|
||||
+13
-21
@@ -5,26 +5,20 @@
|
||||
|
||||
pub mod setup;
|
||||
|
||||
use nym_wireguard_types::registration::GatewayClientRegistry;
|
||||
use std::sync::Arc;
|
||||
|
||||
// Currently the module related to setting up the virtual network device is platform specific.
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::setup::{peer_allowed_ips, peer_static_public_key, PRIVATE_KEY};
|
||||
use defguard_wireguard_rs::WGApi;
|
||||
#[cfg(target_os = "linux")]
|
||||
use defguard_wireguard_rs::{
|
||||
host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WireguardInterfaceApi,
|
||||
};
|
||||
#[cfg(target_os = "linux")]
|
||||
use nym_network_defaults::{WG_PORT, WG_TUN_DEVICE_ADDRESS};
|
||||
|
||||
/// Start wireguard device
|
||||
#[cfg(target_os = "linux")]
|
||||
pub async fn start_wireguard(
|
||||
mut task_client: nym_task::TaskClient,
|
||||
_gateway_client_registry: Arc<GatewayClientRegistry>,
|
||||
) -> Result<WGApi, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
_gateway_client_registry: std::sync::Arc<
|
||||
nym_wireguard_types::registration::GatewayClientRegistry,
|
||||
>,
|
||||
) -> Result<defguard_wireguard_rs::WGApi, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
use crate::setup::{peer_allowed_ips, peer_static_public_key, PRIVATE_KEY};
|
||||
use defguard_wireguard_rs::{
|
||||
host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WGApi, WireguardInterfaceApi,
|
||||
};
|
||||
use nym_network_defaults::{WG_PORT, WG_TUN_DEVICE_ADDRESS};
|
||||
|
||||
let ifname = String::from("wg0");
|
||||
let wgapi = WGApi::new(ifname.clone(), false)?;
|
||||
wgapi.create_interface()?;
|
||||
@@ -48,10 +42,8 @@ pub async fn start_wireguard(
|
||||
|
||||
Ok(wgapi)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
pub async fn start_wireguard(
|
||||
_task_client: nym_task::TaskClient,
|
||||
_gateway_client_registry: Arc<GatewayClientRegistry>,
|
||||
) -> Result<WGApi, Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||
todo!("WireGuard is currently only supported on Linux")
|
||||
pub async fn start_wireguard() {
|
||||
todo!("WireGuard is currently only supported on Linux");
|
||||
}
|
||||
|
||||
@@ -55,6 +55,11 @@ wallet_release_version = "1.2.8"
|
||||
command = "mdbook-last-changed"
|
||||
renderer = ["html"]
|
||||
|
||||
# used for grabbing output of binary commands for automation
|
||||
# https://github.com/FauconFan/mdbook-cmdrun
|
||||
[preprocessor.cmdrun]
|
||||
|
||||
|
||||
#########
|
||||
# BUILD #
|
||||
#########
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Frequently Asked Questions @ 37C3
|
||||
|
||||
This is a FAQ page tailored for CCC 2023 event (37c3). If you interested to read more about Nym platform, you can have a look at [Nym general FAQ](https://nymtech.net/developers/faq/general-faq.html) and read through Nym's technical [documentation](https://nymtech.net/docs), [Developer Portal](https://nymtech.net/developers) and [Operators Guide](https://nymtech.net/operators).
|
||||
|
||||
## NymVPN
|
||||
|
||||
If this your first time hearing about NymVPN, make sure you read [NymVPN webpage](https://nymvpn.com/en), the official [support & FAQ page](https://nymvpn.com/en/support) and our guide on how to [install, run and test](./nym-vpn.md) the client.
|
||||
|
||||
Below are some extra FAQ's which came out during the event.
|
||||
|
||||
### What's the difference between 2-hops and 5-hops
|
||||
|
||||
The default is 5-hops (including Entry and Exit Gateways), which means that the traffic goes from the local client to Entry Gateway -> though 3 layes of Mix Nodes -> to Exit Gateway -> internet. this option uses all the Nym Mixnet features for maximum privacy.
|
||||
|
||||
```
|
||||
┌─►mix──┐ mix mix
|
||||
│ │
|
||||
Entry │ │ Exit
|
||||
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
|
||||
│ │
|
||||
│ │
|
||||
mix └─►mix──┘ mix
|
||||
```
|
||||
|
||||
The 2-hop option is going from the local client -> Entry Gateway -> directly to Exit Gateway -> internet. This option is good for operations demanding faster connection. Keep in mind that this setup by-passes the 3 layers of Mix Nodes.
|
||||
|
||||
```
|
||||
Entry Exit
|
||||
client ───► Gateway ────► Gateway ───► internet
|
||||
```
|
||||
|
||||
We highly recommend to read more about [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html).
|
||||
|
||||
### Why do I see different sizes of packets in my terminal log?
|
||||
|
||||
One of features of Nym Mixnet's clients is to break data into the same size packets called Sphinx, which is currently ~2kb. When running NymVPN, the data log shows payload sizes, which are the raw sizes of the IP packets, not Sphinx. The payload sizes will be capped by the configured MTU, which is set around 1500 bytes.
|
||||
|
||||
### What is 'poisson filter' about?
|
||||
|
||||
By default `--enable-poisson` is disabled and packets are sent from the local client to the Entry Gateway as quickly as possible. With the poisson process enabled the Nym client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
|
||||
Enabling the poisson filter is one of the key mechanisms to de-correlate input and output traffic to the Mixnet. The performance impact however is dramatic:
|
||||
1 packer per 20ms is 50 packets / sec so ballpark 100kb/s.
|
||||
For mobile clients that means constantly sending data eating up data allowance.
|
||||
|
||||
|
||||
## Nym Mixnet Architecture and Rewards
|
||||
|
||||
We have a list of questions related to Nym Nodes and the incentives behind running them under [FAQ pages](https://nymtech.net/operators/faq/mixnodes-faq.html) in our [Operators Guide](https://nymtech.net/operators). For better knowledge about Nym architecture we recommend to read [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) in our [technical documentation](https://nymtech.net/docs).
|
||||
|
||||
## Project Smoosh
|
||||
|
||||
Project Smoosh is a code name for a process in which different components of Nym Mixnet architecture get *smooshed* into one binary. Check out [Smoosh FAQ](https://nymtech.net/operators/faq/smoosh-faq.html) in Operators Guide to read more.
|
||||
|
||||
## Exit Gateway
|
||||
|
||||
Part of the the transition under code name [Project Smoosh](./faq.md#project-smoosh) is a creation of [Nym Exit Gateway](https://nymtech.net/operators/legal/exit-gateway.html) functionality. The operators running Gateways would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays. The main change will be to expand the original short [allowed.list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to a more permissive setup. An [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) will constrain the hosts that the users of the Nym VPN and Mixnet can connect to. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym VPN and Mixnet clients.
|
||||
|
||||
* Read more how the exit policy gets implemented [here](https://nymtech.net/operators/faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented)
|
||||
* Check out [Nym Operators Legal Forum](https://nymtech.net/operators/legal/exit-gateway.html)
|
||||
* Do reach out to us during 37c3 with any experiences you may have running Tor Exit relays or legal findings and suggestions for Nym Exit Gateway operators
|
||||
|
||||
## Nym Integrations and SDKs
|
||||
|
||||
If you are a dev who is interested to integrate Nym, have a look on our SDK tutorials:
|
||||
|
||||
* [Rust SDKs](https://nymtech.net/developers/tutorials/cosmos-service/intro.html)
|
||||
* [TypeScript SDKs](https://sdk.nymtech.net/)
|
||||
* [Integration FAQ](https://nymtech.net/developers/faq/integrations-faq.html)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 138 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
@@ -0,0 +1,457 @@
|
||||
# NymVPN alpha
|
||||
|
||||
Nym has announced [NymVPN](https://nymvpn.com/en) and presented the [NymVPN Litepaper](https://nymvpn.com/nymVPN-litepaper.pdf). At CCC 2023 we have the unique opportunity to do the first alpha public testing. This page provides a how to guide, explaining steps to install and run NymVPN CLI and GUI on the Nym Sandbox testnet.
|
||||
|
||||
NymVPN is a client that uses [Nym Mixnet](https://nymtech.net) to anonymise users entire internet traffic.
|
||||
|
||||
The default is to run in 5-hop mode:
|
||||
|
||||
```
|
||||
┌─►mix──┐ mix mix
|
||||
│ │
|
||||
Entry │ │ Exit
|
||||
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
|
||||
│ │
|
||||
│ │
|
||||
mix └─►mix──┘ mix
|
||||
```
|
||||
|
||||
Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateway's, and is not passed between Mix Nodes.
|
||||
|
||||
The client can optionally do the first connection to the entry gateway using Wireguard. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device.
|
||||
|
||||
```admonish warning
|
||||
NymVPN is an experimental software and it's for [testing](./nym-vpn.md#testing) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research at 37c3*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko).
|
||||
```
|
||||
|
||||
## Goals
|
||||
|
||||
This alpha testing will help:
|
||||
|
||||
* Stabilise NymVPN client
|
||||
* Understand NymVPN client behavior in various setups (OS, connectivity, etc.)
|
||||
* Stabilize the VPN infrastructure and improve its reliability / speed / features (e.g. IPv6 support)
|
||||
* Load test the network in Sandbox environment and identify / anticipate potential weaknesses
|
||||
|
||||
|
||||
```admonish info
|
||||
Our alpha testing round is done live with some participants at CCC 2023. This guide will not work for everyone, as the NymVPN binaries aren't publicly accessible yet. Note that this setup of Nym testnet Sandbox environment is limited for CCC 2023 event and some of the configurations will not work in the future.
|
||||
```
|
||||
|
||||
> **If you commit to test NymVPN alpha, please start with the [user research form](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) where all the steps will be provided**.
|
||||
|
||||
## Preparation
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
We have CLI and UI binaries available for Linux (Debian based) and macOS.
|
||||
|
||||

|
||||
|
||||
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/ccc) to download the binary for your system.
|
||||
* Open terminal in the same directory and make executable by running:
|
||||
|
||||
```sh
|
||||
# for CLI
|
||||
chmod +x ./nym-vpn-cli
|
||||
|
||||
# for GUI
|
||||
chmod +x ./nym-vpn_0.0.0_amd64.AppImage
|
||||
# make sure your path to package is correct and the package name as well
|
||||
```
|
||||
|
||||
* If you prefer to use the `.deb` version for installation (Linux only), open terminal in the same directory and run:
|
||||
```
|
||||
sudo dpkg -i ./<PACKAGE_NAME>.deb
|
||||
# or
|
||||
sudo apt-get install -f ./<PACKAGE_NAME>.deb
|
||||
```
|
||||
|
||||
* For CLI: Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below.
|
||||
|
||||
### GUI configuration
|
||||
|
||||
* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
|
||||
```sh
|
||||
# for Linux
|
||||
mkdir $HOME/.config/nym-vpn/
|
||||
|
||||
# for macOS
|
||||
mkdir $HOME/Library/Application Support/nym-vpn/
|
||||
```
|
||||
* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the directory `nym-vpn` you just created
|
||||
* Create the main config file called `config.toml` in the same directory with this content:
|
||||
```toml
|
||||
# for Linux
|
||||
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env" # change <USER> to your username
|
||||
entry_node_location = "DE" # two letters country code
|
||||
|
||||
# for macOS
|
||||
env_config_file = "$HOME/Library/Application Support/nym-vpn/"
|
||||
entry_node_location = "DE" # two letters country code
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
***For NymVPN to work, all other VPNs must be switched off!***
|
||||
|
||||
**Note:** At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
|
||||
|
||||
* Get your private key for wireguard setup [here](https://nymvpn.com/en/37c3)
|
||||
* See a JSON list of all Gateways [here](https://nymvpn.com/en/ccc/api/gateways)
|
||||
* As NymVPN need a permission for network settings, it is run with `sudo` root command
|
||||
|
||||
### CLI
|
||||
|
||||
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
|
||||
|
||||
```admonish info
|
||||
The entire command syntax with all the needed arguments' values and your private key for testing purposes can be found on our event webpage [nymvpn.com/en/37c3](https://nymvpn.com/en/37c3).
|
||||
```
|
||||
|
||||
Running a help command:
|
||||
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
Usage: nym-vpn-cli [OPTIONS]
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE>
|
||||
Path pointing to an env file describing the network
|
||||
--mixnet-client-path <MIXNET_CLIENT_PATH>
|
||||
Path to the data directory of a previously initialised mixnet client, where the keys reside
|
||||
--entry-gateway-id <ENTRY_GATEWAY_ID>
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
|
||||
--exit-router-country <EXIT_ROUTER_COUNTRY>
|
||||
Mixnet recipient address
|
||||
--enable-wireguard
|
||||
Enable the wireguard traffic between the client and the entry gateway
|
||||
--private-key <PRIVATE_KEY>
|
||||
Associated private key
|
||||
--wg-ip <WG_IP>
|
||||
The IP address of the wireguard interface
|
||||
--ip <IP>
|
||||
The IP address of the TUN device
|
||||
--mtu <MTU>
|
||||
The MTU of the TUN device
|
||||
--disable-routing
|
||||
Disable routing all traffic through the VPN TUN device
|
||||
--enable-two-hop
|
||||
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
|
||||
--enable-poisson-rate
|
||||
Enable Poisson process rate limiting of outbound traffic
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
|
||||
|
||||
```
|
||||
~~~
|
||||
|
||||
To run the CLI a few things need to be specified:
|
||||
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
|
||||
```
|
||||
|
||||
### Options Flags
|
||||
|
||||
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users:
|
||||
|
||||
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
|
||||
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/ccc/api/gateways)
|
||||
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/ccc/api/gateways)
|
||||
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
|
||||
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/37c3)
|
||||
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/37c3)
|
||||
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
|
||||
|
||||
**More advanced options**
|
||||
|
||||
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
|
||||
|
||||
### GUI
|
||||
|
||||
If you installed the `.deb` package you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
|
||||
|
||||
Make sure you went through the GUI configuration in the [preparation section](./nym-vpn.md#gui-configuration). Then open terminal in the same directory where you [installed](./nym-vpn.md#preparation) the binary run:
|
||||
|
||||
```sh
|
||||
sudo -E ./nym-vpn_0.0.0_amd64.AppImage
|
||||
|
||||
# In case you use another binary, just add the correct name as:
|
||||
sudo -E ./<FULL_BINARY_NAME>
|
||||
```
|
||||
|
||||
In case of errors, see [troubleshooting section](./nym-vpn.md#macos-alert-on-nymvpn-ui-startup).
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
One of the main aims of the demo is testing; your results will help us to make NymVPN robust and stabilise both the client and the network through provided measurements. Before you go into testing, make sure you went through [preparation steps](./nym-vpn.md#preparation) and the [CLI binary](https://github.com/nymtech/nym/releases/tag/ccc) for your system is ready to run.
|
||||
|
||||
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary and `sandbox.env` to that directory
|
||||
2. Copy the [block below](./nym-vpn.md#testssh) and save it to the same folder as `tests.sh`
|
||||
3. Open terminal in the same directory
|
||||
4. Turn off any existing VPN's (including the NymVPN instances) and make the script executable by running `chmod +x ./tests.sh`
|
||||
5. Run the tests: `sudo ./tests.sh`
|
||||
6. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
|
||||
7. The script will print a JSON view of existing Gateways and prompt you to:
|
||||
- ***(Make sure to use two different Gateways for entry and exit!)***
|
||||
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
|
||||
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
|
||||
- `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/37c3)
|
||||
8. Note that the testing script doesn't print many logs, in case of doubts you can check logs in the log file `temp_log.txt` located in the same directory.
|
||||
9. The script shall run the tests and generate a folder called `tests_<LONG_STRING>` and files `perf_test_results.log` or `two_hop_perf_test_results.log` as well as some temp files. This is how the directory structure will look like:
|
||||
```sh
|
||||
nym-vpn-tests
|
||||
├── tests.sh
|
||||
├── nym-vpn-cli
|
||||
├── sandbox.env
|
||||
├── perf_test_results.log
|
||||
├── tests_<LONG_STRING>
|
||||
│ ├── api_response_times.txt
|
||||
│ ├── download_time.txt
|
||||
│ └── ping_results.txt
|
||||
├── timeout
|
||||
└── two_hop_perf_test_results.log
|
||||
```
|
||||
10. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
|
||||
11. When the tests are finished, remove the `nym-vpn-cli` binary from the folder and compress it as `nym-vpn-tests.zip`
|
||||
12. Upload this compressed file to the [questionnaire](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) upload field when prompted.
|
||||
|
||||
#### tests.sh
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
|
||||
ENDPOINT="https://sandbox-nym-api1.nymtech.net/api/v1/gateways/described"
|
||||
json_array=()
|
||||
echo "🚀 🏎 - please be patient, i'm fetching you your entry points - 🚀 🏎 "
|
||||
|
||||
data=$(curl -s "$ENDPOINT" | jq -c '.[] | {host: .bond.gateway.host, hostname: .self_described.host_information.hostname, identity_key: .bond.gateway.identity_key, exitGateway: .self_described.ip_packet_router.address}')
|
||||
|
||||
while IFS= read -r entry; do
|
||||
host=$(echo "$entry" | jq -r '.host')
|
||||
hostname=$(echo "$entry" | jq -r '.hostname')
|
||||
identity_key=$(echo "$entry" | jq -r '.identity_key')
|
||||
exit_gateway_address=$(echo "$entry" | jq -r '.exitGateway // empty')
|
||||
valid_ip=$(echo "$host")
|
||||
|
||||
if [ -n "$exit_gateway_address" ]; then
|
||||
exit_gateway="{\"address\": \"${exit_gateway_address}\"}"
|
||||
else
|
||||
exit_gateway="{}"
|
||||
fi
|
||||
if [[ $valid_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
country_info=$(curl -s "http://ipinfo.io/${valid_ip}/country" | tr -d '\n')
|
||||
country_info_escaped=$(echo "$country_info" | tr -d '\n' | jq -aRs . | tr -d '"')
|
||||
else
|
||||
country_info_escaped=""
|
||||
fi
|
||||
json_object="{\"hostname\": \"${hostname}\", \"identityKey\": \"${identity_key}\", \"exitGateway\": ${exit_gateway}, \"location\": \"${country_info_escaped}\"}"
|
||||
json_array+=("$json_object")
|
||||
done < <(echo "$data")
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "error fetching data from endpoint"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
download_file() {
|
||||
local file_url=$1
|
||||
local output_file=$2
|
||||
local time_file=$3
|
||||
|
||||
echo "starting download speed test..."
|
||||
local start_time=$(date +%s)
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
wget -O $output_file $file_url
|
||||
elif [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
curl -o $output_file $file_url
|
||||
fi
|
||||
local end_time=$(date +%s)
|
||||
local elapsed_time=$((end_time - start_time))
|
||||
echo "download speed test completed in $elapsed_time seconds." >"$time_file"
|
||||
}
|
||||
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "jq is not installed. Please install jq to proceed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
temp_log_file="temp_log.txt"
|
||||
|
||||
perform_tests() {
|
||||
local gateway_id=$1
|
||||
local exit_address=$2
|
||||
local test_directory="tests_${gateway_id}_${exit_address}"
|
||||
local file_url="http://ipv4.download.thinkbroadband.com/2MB.zip"
|
||||
|
||||
mkdir -p "$test_directory"
|
||||
local ping_results_file="${test_directory}/ping_results.txt"
|
||||
local download_time_file="${test_directory}/download_time.txt"
|
||||
local api_response_file="${test_directory}/api_response_times.txt"
|
||||
|
||||
# ping test
|
||||
echo "starting ping test..."
|
||||
for site in google.com youtube.com facebook.com baidu.com wikipedia.org amazon.com twitter.com instagram.com yahoo.com ebay.com netflix.com; do
|
||||
ping -c 4 $site >>"$ping_results_file"
|
||||
done
|
||||
echo "ping test completed. Results saved in $ping_results_file"
|
||||
|
||||
# download speed test
|
||||
download_file $file_url /dev/null "$download_time_file"
|
||||
|
||||
# api test
|
||||
local api_endpoint="https://validator.nymtech.net/api/v1/mixnodes"
|
||||
local iterations=10
|
||||
>"$api_response_file"
|
||||
for i in $(seq 1 $iterations); do
|
||||
local start_time=$(date +%s)
|
||||
local response=$(curl -s -o /dev/null -w '%{http_code}' $api_endpoint)
|
||||
local end_time=$(date +%s)
|
||||
|
||||
local elapsed_seconds=$((end_time - start_time))
|
||||
local hours=$((elapsed_seconds / 3600))
|
||||
local minutes=$(((elapsed_seconds % 3600) / 60))
|
||||
local seconds=$((elapsed_seconds % 60))
|
||||
|
||||
local human_readable_time=$(printf "%02dh:%02dm:%02ds" $hours $minutes $seconds)
|
||||
echo "iteration $i: response Time = ${human_readable_time}, status code = $response" >>"$api_response_file"
|
||||
done
|
||||
echo "api response test completed. Results saved in $api_response_file."
|
||||
}
|
||||
|
||||
printf "%s\n" "${json_array[@]}" | jq -s .
|
||||
|
||||
read -p "enter a gateway ID: " identity_key
|
||||
read -p "enter an exit address: " exit_address
|
||||
|
||||
while true; do
|
||||
read -p "enable WireGuard? (yes/no): " enable_wireguard
|
||||
enable_wireguard=$(echo "$enable_wireguard" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$enable_wireguard" in
|
||||
"yes")
|
||||
read -p "enter your WireGuard private key: " priv_key
|
||||
read -p "enter your WireGuard IP: " wg_ip
|
||||
wireguard_options="--enable-wireguard --private-key $priv_key --wg-ip $wg_ip"
|
||||
break
|
||||
;;
|
||||
"no")
|
||||
wireguard_options=""
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo "invalid response. please enter 'yes' or 'no'."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
sudo ./nym-vpn-cli -c sandbox.env --entry-gateway-id ${identity_key} --exit-router-address ${exit_address} --enable-two-hop $wireguard_options >"$temp_log_file" 2>&1 &
|
||||
|
||||
timeout=15
|
||||
start_time=$(date +%s)
|
||||
while true; do
|
||||
current_time=$(date +%s)
|
||||
if grep -q "received plain" "$temp_log_file"; then
|
||||
echo "successful configuration with identity_key: $identity_key and exit address: $exit_address" >>perf_test_results.log
|
||||
perform_tests "$identity_key" "$exit_address"
|
||||
break
|
||||
fi
|
||||
if ((current_time - start_time > timeout)); then
|
||||
echo "failed to connect with identity_key: $identity_key using the exit address: $exit_address" >>perf_test_results.log
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "terminating nym-vpn-cli..."
|
||||
pkill -f './nym-vpn-cli'
|
||||
sleep 5
|
||||
rm -f "$temp_log_file"
|
||||
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Below are listed some points which may need to be addressed when testing NymVPN alpha. If you crashed into any errors which are not listed, please contact us at the event.
|
||||
|
||||
#### Thread `main` panicked
|
||||
|
||||
If you see a message like:
|
||||
```sh
|
||||
thread 'main' panicked at /Users/runner/.cargo/git/checkouts/mullvadvpn-app-a575cf705b5dfd76/ccfbaa2/talpid-routing/src/unix.rs:301:30:
|
||||
```
|
||||
Restart your wifi connection and start again.
|
||||
|
||||
#### macOS alert on NymVPN UI startup
|
||||
|
||||
If you are running NymVPN on mac OS for the first time, you may see this alert message:
|
||||
|
||||

|
||||
|
||||
1. Head to System Settings -> Privacy & Security and click `Allow anyway`
|
||||
|
||||

|
||||
|
||||
2. Confirm with your password or TouchID
|
||||
|
||||
3. Possibly you may have to confirm again upon running the application
|
||||
|
||||
#### Missing `jq` error
|
||||
|
||||
In case of missing `jq` on Linux (Debian) install it with:
|
||||
```sh
|
||||
# Linux (Debian)
|
||||
sudo apt-get install jq
|
||||
# macOS
|
||||
brew install jq
|
||||
```
|
||||
On some Linux distributions however the [script](./nym-vpn.md#testssh) returns `jq` error even if your system claims that `jq is already the newest version`.
|
||||
In that case, comment the `jq` check in the script as follows:
|
||||
```sh
|
||||
#if ! command -v jq &>/dev/null; then
|
||||
# echo "jq is not installed. Please install jq to proceed."
|
||||
# exit 1
|
||||
#fi
|
||||
```
|
||||
|
||||
#### Error current_time: not found
|
||||
|
||||
When running `sudo sh ./test.sh` you may see an error like: `93: current_time: not found`. This has something to do with the `current_time` setup of your system and on itself shall not have a negative impact on the test. It has nothing to do with the client at all as it only relates to the code in our testing script.
|
||||
|
||||
#### Not connecting to the endpoint
|
||||
|
||||
In case the automatic download of all the Gateways fail (and it shouldn't), you do an easy manual work around:
|
||||
|
||||
1. Open the list of Gateways created by API [here](https://nymvpn.com/en/ccc/api/gateways)
|
||||
2. On top click on `JSON` option (shall be default view) and `Save`
|
||||
3. Save it as `data.json` to the `nym-vpn-tests` folder
|
||||
4. Replace line 3 in the [script `tests.sh`](./nym-vpn.md#testssh) with:
|
||||
```sh
|
||||
NEW_ENDPOINT="http://localhost:8000/data.json"
|
||||
```
|
||||
5. In a new terminal window run:
|
||||
```sh
|
||||
python3 -m http.server 8000
|
||||
```
|
||||
6. Continue with the steps listed in [testing section](./nym-vpn.md#testing)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# Chaos Communication Congress 2023
|
||||
|
||||
At [CCC 2019](https://constructiveproof.com/posts/2020-01-27-nym-testnet-up-and-running/) we announced the first demo of the [Nym Mixnet](https://github.com/nymtech/nym). Now, four years later we are honoured to present our new product: [NymVPN](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d). NymVPN is a client/application which captures the entire network stack of a user's device, sending this traffic through either a 2-hop VPN or through the 5-hop Mixnet. CCC 2023 is the first public demo of both the GUI and CLI versions of NymVPN.
|
||||
|
||||
At first glance Nym platform may seem pretty complex. To make the connection between Nym and the community of developers, operators and users participating on the event as smooth as possible, we created a segment of documentation tailored specifically for the conference. The following pages provide an easy introduction to the NymVPN alpha client. We recommend interested developers to begin with [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) pages.
|
||||
|
||||
**See pages below for more content:**
|
||||
|
||||
* [NymVPN alpha guide](nym-vpn.md)
|
||||
* [FAQ @ 37c3](faq.md)
|
||||
|
||||
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/897010658?h=1f55870fe6&badge=0&autopause=0&player_id=0&app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="NYMVPN alpha demo 37C3"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
|
||||
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine.
|
||||
|
||||
[Read the docs](https://nymtech.net/docs/sdk/rust.html).
|
||||
[Read the docs](https://nymtech.net/docs/sdk/rust/rust.html).
|
||||
|
||||
@@ -25,14 +25,14 @@ These steps are for an operator who is setting up a [Gateway](gateway-setup.md)
|
||||
|
||||
1. New flags will need to be added to the `init` and `run` command. The `--host` option should be replaced with these flags:
|
||||
|
||||
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
|
||||
- `--public-ips`: A comma separated list of IP’s that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
|
||||
- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data.
|
||||
- `--public-ips`: A comma separated list of IP’s that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.**
|
||||
|
||||
```sh
|
||||
--listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)"
|
||||
```
|
||||
|
||||
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
|
||||
- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`.
|
||||
|
||||
2. Make sure to enable all necessary [ports](maintenance.md#configure-your-firewall) on the Gateway:
|
||||
|
||||
@@ -42,7 +42,7 @@ sudo ufw allow 1789,1790,8000,9000,9001,22/tcp, 9001/tcp
|
||||
|
||||
The Gateway will then be accessible on something like: *http://85.159.211.99:8080/api/v1/swagger/index.html*
|
||||
|
||||
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
|
||||
Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*?
|
||||
|
||||
Please modify `[host.public_ips]` section of your config file stored as `~/.nym/gateways/<ID>/config/config.toml`.
|
||||
|
||||
@@ -92,7 +92,7 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo
|
||||
apt update
|
||||
apt install caddy
|
||||
|
||||
systemctl enable caddy.service
|
||||
systemctl enable caddy.service
|
||||
|
||||
cd /etc/caddy
|
||||
|
||||
@@ -102,7 +102,7 @@ if [ -f Caddyfile ]; then
|
||||
rm -f Caddyfile
|
||||
fi
|
||||
|
||||
cat <<EOF >> Caddyfile
|
||||
cat <<EOF >> Caddyfile
|
||||
${host}:${port_value} {
|
||||
@websockets {
|
||||
header Connection *Upgrade*
|
||||
@@ -191,9 +191,9 @@ nohup ./<NODE> run --id <YOUR_ID> # where `<YOUR_ID>` is the id you set during t
|
||||
|
||||
#### tmux
|
||||
|
||||
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
|
||||
One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated.
|
||||
|
||||
Use the following command to get `tmux`.
|
||||
Use the following command to get `tmux`.
|
||||
|
||||
Platform|Install Command
|
||||
---|---
|
||||
@@ -204,15 +204,15 @@ RHEL or CentOS|`yum install tmux`
|
||||
macOS (using Homebrew|`brew install tmux`
|
||||
macOS (using MacPorts)|`port install tmux`
|
||||
openSUSE|`zypper install tmux`
|
||||
|
||||
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
|
||||
|
||||
In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control).
|
||||
|
||||
**Running tmux**
|
||||
|
||||
Now you have installed tmux on your VPS, let's run a Mix Node on tmux, which allows you to detach your terminal and let your `<NODE>` run on its own on the VPS.
|
||||
|
||||
* Pause your `<NODE>`
|
||||
* Start tmux with the command
|
||||
* Start tmux with the command
|
||||
```sh
|
||||
tmux
|
||||
```
|
||||
@@ -230,7 +230,7 @@ tmux attach-session
|
||||
|
||||
#### systemd
|
||||
|
||||
To automate with `systemd` use this init service file and follow the steps below.
|
||||
To automate with `systemd` use this init service file and follow the steps below.
|
||||
|
||||
##### For Mix Node
|
||||
|
||||
@@ -327,18 +327,18 @@ Change the `<PATH>` in `ExecStart` to point at your `<NODE>` binary (`nym-mixnod
|
||||
|
||||
Example: If you have built nym in the `$HOME` directory on your server, your username is `jetpanther`, and node `<ID>` is `puma`, then the `ExecStart` line (command) in the script located in `/etc/systemd/system/nym-mixnode.service` for Nym Mixnode might look like this:
|
||||
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
|
||||
`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`.
|
||||
|
||||
Basically, you want the full `/<PATH>/<TO>/nym-mixnode run --id <WHATEVER-YOUR-NODE-ID-IS>`. If you are unsure about your `/<PATH>/<TO>/<NODE>`, then `cd` to your directory where you run your `<NODE>` from and run `pwd` command which returns the full path for you.
|
||||
|
||||
Once done, save the script and follow these steps:
|
||||
|
||||
```sh
|
||||
systemctl daemon-reload
|
||||
systemctl daemon-reload
|
||||
# to pickup the new unit file
|
||||
```
|
||||
|
||||
Enable the newly created service:
|
||||
Enable the newly created service:
|
||||
|
||||
```sh
|
||||
# for Mix Node
|
||||
@@ -350,7 +350,7 @@ systemctl enable nym-gateway.service
|
||||
# for Network Requester
|
||||
systemctl enable nym-network-requester.service
|
||||
|
||||
# for Nymvisor
|
||||
# for Nymvisor
|
||||
systemctl enable nymvisor.service
|
||||
```
|
||||
|
||||
@@ -367,7 +367,7 @@ service nym-gateway start
|
||||
service nym-network-requester.service
|
||||
|
||||
# for Nymvisor
|
||||
service nymvisor.service start
|
||||
service nymvisor.service start
|
||||
```
|
||||
|
||||
This will cause your `<NODE>` to start at system boot time. If you restart your machine, your `<NODE>` will come back up automatically.
|
||||
@@ -427,7 +427,7 @@ systemctl start nymd # to actually start the service
|
||||
journalctl -f -u nymd # to monitor system logs showing the service start
|
||||
```
|
||||
|
||||
##### For Nym API
|
||||
##### For Nym API
|
||||
|
||||
Below is a `systemd` unit file to place at `/etc/systemd/system/nym-api.service` to automate your API instance:
|
||||
|
||||
@@ -535,7 +535,7 @@ In case you chose tmux option for Mix Node automation, see your `ulimit` list by
|
||||
ulimit -a
|
||||
|
||||
# watch for the output line -n
|
||||
-n: file descriptors 1024
|
||||
-n: file descriptors 1024
|
||||
```
|
||||
|
||||
You can change it either by running a command:
|
||||
@@ -582,14 +582,14 @@ scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/mixnodes/<YOUR_ID> <TA
|
||||
```
|
||||
* Re-run init (remember that init doesn't overwrite existing keys) to generate a config with the new listening address etc.
|
||||
* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks.
|
||||
* Re-run the node from the new location.
|
||||
* Re-run the node from the new location.
|
||||
|
||||
|
||||
## Virtual IPs and hosting via Google & AWS
|
||||
|
||||
For true internet decentralization we encourage operators to use diverse VPS providers instead of the largest companies offering such services. If for some reasons you have already running AWS or Google and want to setup a `<NODE>` there, please read the following.
|
||||
|
||||
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
|
||||
On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `<NODE>` in the wallet.
|
||||
|
||||
You can run `ifconfig` command. For example, on a Google machine, you may see the following output:
|
||||
|
||||
@@ -601,7 +601,7 @@ ens4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1460
|
||||
|
||||
The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`.
|
||||
|
||||
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
|
||||
To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `<NODE>` with the `nym-api` via Nym wallet.
|
||||
|
||||
On self-hosted machine it's a bit more tricky. In that case as an operator you must be sure that your ISP allows for public IPv4 and IPv6 and then it may be a bit of playing around to find the right configuration. One way may be to bind your binary with the `--host` flag to local address `127.0.0.1` and run `echo "$(curl -4 https://ifconfig.me)"` to get a public address which you use to bond your Mix Node to `nym-api` via Nym wallet.
|
||||
|
||||
@@ -650,7 +650,7 @@ Query Response:
|
||||
|
||||
### Validator: Installing and configuring nginx for HTTPS
|
||||
#### Setup
|
||||
[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
|
||||
[Nginx](https://www.nginx.com/resources/glossary/nginx) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security.
|
||||
|
||||
Install `nginx` and allow the 'Nginx Full' rule in your firewall:
|
||||
|
||||
@@ -678,15 +678,16 @@ Which should return:
|
||||
└─2380 nginx: worker process
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
#### Full Node Configuration
|
||||
|
||||
Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`:
|
||||
Proxying various full node services through port 80 can then be done by creating a file with the following at `/etc/nginx/sites-enabled/nyxd-webrequests.conf`:
|
||||
|
||||
```sh
|
||||
### To expose RPC server
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name "domain_name";
|
||||
server_name "<rpc.nyx.yourdomain.tld>";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:26657;
|
||||
@@ -695,20 +696,58 @@ server {
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
|
||||
### To expose Cosmos API server
|
||||
server {
|
||||
server_name "<api.nyx.yourdomain.tld>";
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:1317;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade websocket;
|
||||
proxy_set_header Connection Upgrade;
|
||||
}
|
||||
}
|
||||
|
||||
### To expose GRPC endpoint
|
||||
server {
|
||||
server_name "<grpc.nyx.yourdomain.tld>";
|
||||
location / {
|
||||
grpc_pass 127.0.0.1:9090;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### nym-api Configuration
|
||||
|
||||
```sh
|
||||
### To expose nym-api webserver
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name "<nym-api.nyx.yourdomain.tld>";
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Followed by:
|
||||
|
||||
```sh
|
||||
sudo apt install certbot nginx python3
|
||||
certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect
|
||||
certbot --nginx -m <you@yourdomain.com> --agree-tos
|
||||
```
|
||||
|
||||
```admonish caution title=""
|
||||
If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx`
|
||||
```
|
||||
|
||||
These commands will get you an https encrypted nginx proxy in front of the API.
|
||||
These commands will get you an https encrypted nginx proxy in front of the various endpoints.
|
||||
|
||||
### Configuring Prometheus metrics (optional)
|
||||
|
||||
@@ -795,6 +834,3 @@ All validator-specific port configuration can be found in `$HOME/.nymd/config/co
|
||||
| 1317 | REST API server endpoint |
|
||||
| 26656 | Listen for incoming peer connections |
|
||||
| 26660 | Listen for Prometheus connections |
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,33 +1,48 @@
|
||||
# Nym API Setup
|
||||
|
||||
[//]: # (> The Nym API binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
|
||||
[//]: # ()
|
||||
[//]: # (> The nym-api binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.)
|
||||
|
||||
> The `nym-api` binary should be coming out in the next release - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
|
||||
> The `nym-api` binary will be released in the immediate future - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality.
|
||||
>
|
||||
> You can build the API with `cargo build --release --bin nym-api`.
|
||||
|
||||
> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
## What is the Nym API?
|
||||
The Nym API is a binary that will be operated by the Nyx Blockchain Validator set. This binary can be run in several different modes, and has two main bits of functionality:
|
||||
* network monitoring (calculating the routing score of Mixnet nodes)
|
||||
The Nym API is a binary that will be operated by the Nyx validator set. This binary can be run in several different modes, and has two main bits of functionality:
|
||||
* network monitoring (calculating the routing score of Mixnet nodes)
|
||||
* generation and validation of [zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a), our implementation of the Coconut Selective Disclosure Credential Scheme.
|
||||
|
||||
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
|
||||
This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments.
|
||||
|
||||
The process of enabling these different aspects of the system will take time. Operators of the Nym API binary for the moment will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside their Validator.
|
||||
The process of enabling these different aspects of the system will take time. At the moment, Nym API operators will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside a full node.
|
||||
|
||||
### Rewards
|
||||
Operators of Nym API instances will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
|
||||
```admonish warning
|
||||
It is highly recommended to run `nym-api` alongside a full node since you will be exposing HTTP port(s) to the Internet. We also observed degradation in p2p and block signing operations when `nym-api` was run alongside a signing validator.
|
||||
```
|
||||
|
||||
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237600NYM per month) according to the % of credentials they have signed.
|
||||
### Rewards
|
||||
Operators of Nym API will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production.
|
||||
|
||||
### (Coming Soon) Machine Specs
|
||||
We are working on load testing currently in order to get good specs for a Validator + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
|
||||
Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237,600 NYM per month), proportional to the percentage of credentials they have signed.
|
||||
|
||||
### (Coming Soon) Machine Specs
|
||||
We are working on load testing currently in order to get good specs for a full node + Nym API setup. Bear in mind that credential signing is primarily CPU-bound.
|
||||
|
||||
### (Coming Soon) Credential Generation
|
||||
Validators that take part in the DKG ceremony (more details on this soon) will become part of the quorum generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
|
||||
Validators that take part in the DKG ceremony will become part of the 'quorum' generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221).
|
||||
|
||||
## Current version
|
||||
The DKG ceremony will be used to create a subset of existing validators - referred to as the quorum. As outlined above, they will be the ones taking part in the generation and verification of zk-Nym credentials. The size of the 'minimum viable quorum' is 10 - we are aiming for a larger number than this for the initial quorum in order to have some redundancy in the case of a Validator dropping or going offline.
|
||||
|
||||
We will be releasing more detailed step-by-step documentation for involved validators nearer to the ceremony itself, but at a high level it will involve:
|
||||
* the deployment and initialisation of [`group`](https://github.com/nymtech/nym/tree/develop/contracts/multisig/cw4-group) and [`multisig`](https://github.com/nymtech/nym/tree/develop/contracts/multisig) contracts by Nym. Validators that are members of the `group` contract are the only ones that will be able to take part in the ceremony.
|
||||
* the deployment and initialisation of an instance of the [DKG contract](https://github.com/nymtech/nym/tree/develop/contracts/coconut-dkg) by Nym.
|
||||
* Validators will update their `nym-api` configs with the address of the deployed contracts. They will also stop running their API instance in caching only mode, instead switching over run with the `--enabled-credentials-mode`.
|
||||
* From the perspective of operators, this is all they have to do. Under the hood, each `nym-api` instance will then take part in several rounds of key submission, verification, and derivation. This will continue until quorum is acheived. More information on this will be released closer to the time of the ceremony.
|
||||
|
||||
**We will be communicating individually with members of the existing Validator set who have expressed interest in joining the quorum concerning the timing and specifics of the ceremony**.
|
||||
|
||||
## Current version
|
||||
```
|
||||
<!-- cmdrun ../../../../target/release/nym-api --version | grep "Build Version" | cut -b 21-26 -->
|
||||
```
|
||||
@@ -54,31 +69,31 @@ You can also check the various arguments required for individual commands with:
|
||||
./nym-api <COMMAND> --help
|
||||
```
|
||||
|
||||
### Initialising your Nym API Instance
|
||||
Initialise your API instance with:
|
||||
### Initialising your Nym API Instance
|
||||
Initialise your API instance with:
|
||||
|
||||
```
|
||||
./nym-api init
|
||||
```
|
||||
|
||||
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
|
||||
You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`.
|
||||
|
||||
### Running your Nym API Instance
|
||||
The API binary currently defaults to running in caching mode. You can run your API with:
|
||||
### Running your Nym API Instance
|
||||
The API binary currently defaults to running in caching mode. You can run your API with:
|
||||
|
||||
```
|
||||
./nym-api run
|
||||
```
|
||||
|
||||
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
|
||||
By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`:
|
||||
|
||||
```
|
||||
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
|
||||
./nym-api run --nyxd-validator https://rpc.nymtech.net:443
|
||||
```
|
||||
|
||||
> You can also change the value of `local_validator` in the config file found by default in `$HOME/.nym/nym-api/<ID>/config/config.toml`.
|
||||
|
||||
This process is quite noisy, but informative:
|
||||
This process is quite noisy, but informative:
|
||||
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```
|
||||
@@ -160,5 +175,8 @@ Starting nym api...
|
||||
```
|
||||
~~~
|
||||
|
||||
## Automation
|
||||
## Automation
|
||||
You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md) for an example `service` file.
|
||||
|
||||
## Exposing web endpoint using HTTPS
|
||||
It is recommended to expose the webserver over HTTPS by using a webserver like Nginx. An example configuration for configuring Nginx is listed [on the maintenance page](maintenance.md#nym-api-configuration)
|
||||
|
||||
@@ -216,7 +216,7 @@ You can use the following command to download them for the correct network:
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json
|
||||
|
||||
# Sandbox testnet
|
||||
wget -O $HOME/.nyxd/config/genesis.json https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis'
|
||||
curl https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json
|
||||
```
|
||||
|
||||
### `config.toml` configuration
|
||||
@@ -254,7 +254,7 @@ And if you wish to add a human-readable moniker to your node:
|
||||
Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit.
|
||||
|
||||
### `app.toml` configuration
|
||||
In the file `$HOME/nyxd/config/app.toml`, set the following values:
|
||||
In the file `$HOME/.nyxd/config/app.toml`, set the following values:
|
||||
|
||||
```
|
||||
# Mainnet
|
||||
@@ -264,7 +264,6 @@ minimum-gas-prices = "0.025unym,0.025unyx"
|
||||
```
|
||||
# Sandbox Testnet
|
||||
minimum-gas-prices = "0.025unym,0.025unyx"
|
||||
enable = true` in the `[api]` section to get the API server running
|
||||
```
|
||||
|
||||
### Setting up your validator's admin user
|
||||
@@ -304,6 +303,31 @@ File at /path/to/genesis.json is a valid genesis file
|
||||
|
||||
> If this test did not pass, check that you have replaced the contents of `/<PATH-TO>/.nyxd/config/genesis.json` with that of the correct genesis file.
|
||||
|
||||
### Setting up nyxd as full node (non-signing)
|
||||
|
||||
```admonish danger title=""
|
||||
Skip this section if you're planning to run a validator node to join network consensus. To ensure security & maximum availability of validators, do not expose additional services to the Internet
|
||||
```
|
||||
|
||||
Unlike signing validators, full nodes do not propose / sign blocks. A full node is typically used for indexing blocks produced on the chain and for exposing web interfaces such as RPC, API and gRPC endpoints required for external applications/services to interact with the blockchain.
|
||||
|
||||
By default, API server is disabled and RPC/gRPC servers listen to the loopback address only. In a production setup, it is recommended to use a webserver such as Nginx or caddy to proxy requests to the endpoints as required.
|
||||
|
||||
To enable Cosmos REST API, you can enable it in `$HOME/.nyxd/config/app.toml` like :
|
||||
|
||||
```
|
||||
[api]
|
||||
|
||||
# Enable defines if the API server should be enabled. Toggle this to `true`
|
||||
enable = true
|
||||
|
||||
# Swagger defines if swagger documentation should automatically be registered.
|
||||
# You can also expose swagger documentation by toggling the below configuration to true
|
||||
swagger = true
|
||||
```
|
||||
|
||||
For more information on enabling access to various endpoints via Nginx, refer to the [example configuration here](./maintenance.md#setup)
|
||||
|
||||
### Open firewall ports
|
||||
|
||||
Before starting the validator, we will need to open the firewall ports:
|
||||
@@ -319,6 +343,9 @@ sudo ufw enable
|
||||
# 22 : Default SSH port
|
||||
sudo ufw allow 26656,26660,22
|
||||
|
||||
## !! FOR FULL NODES ONLY !! - exposing Nginx for serving web requests
|
||||
sudo ufw allow 80,443
|
||||
|
||||
# to check everything worked
|
||||
sudo ufw status
|
||||
```
|
||||
@@ -358,10 +385,8 @@ lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd
|
||||
You can then restart `nyxd` - it should start syncing from a block > 2000000.
|
||||
|
||||
### Joining Consensus
|
||||
```admonish caution title=""
|
||||
When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens.
|
||||
|
||||
Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time.
|
||||
```admonish info title=""
|
||||
You can skip this section if you are planning to run a full-node. This step will make your node a signing validator which joins network consensus
|
||||
```
|
||||
|
||||
Once your validator has synced and you have received tokens, you can join consensus and produce blocks.
|
||||
@@ -473,11 +498,15 @@ To upgrade your validator, follow the steps on the [maintenance page](./maintena
|
||||
|
||||
#### Common reasons for your validator being jailed
|
||||
|
||||
The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
|
||||
Your validator will be jailed if your node:
|
||||
- misses _`x`_ amount of blocks in _`y`_ interval, where _`x`_ and _`y`_ are parameters set by chain governance
|
||||
- performs double signing (two conflicting signatures on the same block using the same key)
|
||||
|
||||
Running the command `df -H` will return the size of the various partitions of your VPS.
|
||||
Double signing is a serious infraction. If a node double signs, all the delegators to the node (including self-delegation) will be slashed by 5%. Additionally, the node will be permanently jailed and removed from consensus (called _tombstoning_)
|
||||
|
||||
If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process.
|
||||
One of the most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs.
|
||||
|
||||
Running the command `df -H` will return the size of the various partitions of your VPS. If the partition with blockchain data is almost full, try pruning the blockchain data or expanding the storage size.
|
||||
|
||||
### Day 2 operations with your validator
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ pub(crate) enum BlockManagerError {
|
||||
BlockManager(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
#[allow(clippy::struct_field_names)] // this should get resolved properly at some point, but not now...
|
||||
/// It helps to use atomic state management for new blocks.
|
||||
pub(crate) struct BlockChainState {
|
||||
pub(crate) last_blocks: LruCache<Hash, Block>,
|
||||
|
||||
@@ -102,6 +102,8 @@ pub(crate) struct Peer {
|
||||
/// assert_eq!(peer_id, public_key.peer_id());
|
||||
///
|
||||
/// ```
|
||||
#[allow(clippy::struct_field_names)]
|
||||
// this should get resolved properly at some point, but not now...
|
||||
pub peer_id: PeerId,
|
||||
/// The peer's public key. It matches PeerId.
|
||||
pub public_key: PublicKey,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution;
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use nym_explorer_api_requests::PrettyDetailedGatewayBond;
|
||||
use rocket::response::status::NotFound;
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
@@ -61,7 +61,7 @@ impl FailedIpAddresses {
|
||||
if Path::new(&file_path).exists() {
|
||||
if let Ok(file) = File::open(&file_path) {
|
||||
let lines = io::BufReader::new(file).lines();
|
||||
for ip in lines.flatten() {
|
||||
for ip in lines.map_while(Result::ok) {
|
||||
failed_ips.insert(ip);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::mix_node::delegations::{
|
||||
get_single_mixnode_delegations, get_single_mixnode_delegations_summed,
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::mix_nodes::models::{MixNodeActiveSetSummary, MixNodeSummary};
|
||||
use crate::state::ExplorerApiStateContext;
|
||||
use nym_explorer_api_requests::{MixnodeStatus, PrettyDetailedMixNodeBond};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use nym_mixnet_contract_common::{MixId, MixNode};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::service_providers::models::{
|
||||
DirectoryService, DirectorySpDetailed, HarbourMasterService, PagedResult,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use rocket::response::status::NotFound;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Route, State};
|
||||
|
||||
+2
-2
@@ -36,7 +36,7 @@ pretty_env_logger = "0.4"
|
||||
rand = "0.7"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sqlx = { version = "0.5", features = [
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
@@ -89,7 +89,7 @@ hyper = "0.14.27"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.5", features = [
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
|
||||
@@ -5,6 +5,7 @@ use super::authenticated::RequestHandlingError;
|
||||
use log::*;
|
||||
use nym_coconut_interface::Credential;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use nym_validator_client::{
|
||||
nyxd::{
|
||||
contract_traits::{
|
||||
@@ -12,26 +13,27 @@ use nym_validator_client::{
|
||||
MultisigSigningClient,
|
||||
},
|
||||
cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID},
|
||||
Coin, Fee,
|
||||
Coin,
|
||||
},
|
||||
CoconutApiClient, DirectSigningHttpRpcNyxdClient,
|
||||
};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
const ONE_HOUR_SEC: u64 = 3600;
|
||||
const MAX_FEEGRANT_UNYM: u128 = 10000;
|
||||
use std::ops::Deref;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub(crate) struct CoconutVerifier {
|
||||
nyxd_client: DirectSigningHttpRpcNyxdClient,
|
||||
address: AccountId,
|
||||
nyxd_client: RwLock<DirectSigningHttpRpcNyxdClient>,
|
||||
mix_denom_base: String,
|
||||
}
|
||||
|
||||
impl CoconutVerifier {
|
||||
pub fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self {
|
||||
pub async fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self {
|
||||
let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone();
|
||||
let address = nyxd_client.address();
|
||||
|
||||
CoconutVerifier {
|
||||
nyxd_client,
|
||||
address,
|
||||
nyxd_client: RwLock::new(nyxd_client),
|
||||
mix_denom_base,
|
||||
}
|
||||
}
|
||||
@@ -39,7 +41,13 @@ impl CoconutVerifier {
|
||||
pub async fn all_current_coconut_api_clients(
|
||||
&self,
|
||||
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
|
||||
let epoch_id = self.nyxd_client.get_current_epoch().await?.epoch_id;
|
||||
let epoch_id = self
|
||||
.nyxd_client
|
||||
.read()
|
||||
.await
|
||||
.get_current_epoch()
|
||||
.await?
|
||||
.epoch_id;
|
||||
self.all_coconut_api_clients(epoch_id).await
|
||||
}
|
||||
|
||||
@@ -47,7 +55,7 @@ impl CoconutVerifier {
|
||||
&self,
|
||||
epoch_id: u64,
|
||||
) -> Result<Vec<CoconutApiClient>, RequestHandlingError> {
|
||||
Ok(all_coconut_api_clients(&self.nyxd_client, epoch_id).await?)
|
||||
Ok(all_coconut_api_clients(self.nyxd_client.read().await.deref(), epoch_id).await?)
|
||||
}
|
||||
|
||||
pub async fn release_funds(
|
||||
@@ -55,19 +63,17 @@ impl CoconutVerifier {
|
||||
api_clients: Vec<CoconutApiClient>,
|
||||
credential: &Credential,
|
||||
) -> Result<(), RequestHandlingError> {
|
||||
// Use a custom multiplier for revoke, as the default one (1.3)
|
||||
// isn't enough
|
||||
let revoke_fee = Some(Fee::Auto(Some(1.5)));
|
||||
|
||||
let res = self
|
||||
.nyxd_client
|
||||
.write()
|
||||
.await
|
||||
.spend_credential(
|
||||
Coin::new(
|
||||
credential.voucher_value().into(),
|
||||
self.mix_denom_base.clone(),
|
||||
),
|
||||
credential.blinded_serial_number(),
|
||||
self.nyxd_client.address().to_string(),
|
||||
self.address.to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
@@ -81,7 +87,12 @@ impl CoconutVerifier {
|
||||
reason: String::from("proposal id could not be parsed to u64"),
|
||||
})?;
|
||||
|
||||
let proposal = self.nyxd_client.query_proposal(proposal_id).await?;
|
||||
let proposal = self
|
||||
.nyxd_client
|
||||
.read()
|
||||
.await
|
||||
.query_proposal(proposal_id)
|
||||
.await?;
|
||||
if !credential.has_blinded_serial_number(&proposal.description)? {
|
||||
return Err(RequestHandlingError::ProposalIdError {
|
||||
reason: String::from("proposal has different serial number"),
|
||||
@@ -91,28 +102,10 @@ impl CoconutVerifier {
|
||||
let req = nym_api_requests::coconut::VerifyCredentialBody::new(
|
||||
credential.clone(),
|
||||
proposal_id,
|
||||
self.nyxd_client.address(),
|
||||
self.address.clone(),
|
||||
);
|
||||
for client in api_clients {
|
||||
self.nyxd_client
|
||||
.grant_allowance(
|
||||
&client.cosmos_address,
|
||||
vec![Coin::new(MAX_FEEGRANT_UNYM, self.mix_denom_base.clone())],
|
||||
SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)),
|
||||
// It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable
|
||||
vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")],
|
||||
"Create allowance to vote the release of funds".to_string(),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
let ret = client.api_client.verify_bandwidth_credential(&req).await;
|
||||
self.nyxd_client
|
||||
.revoke_allowance(
|
||||
&client.cosmos_address,
|
||||
"Cleanup the previous allowance for releasing funds".to_string(),
|
||||
revoke_fee.clone(),
|
||||
)
|
||||
.await?;
|
||||
match ret {
|
||||
Ok(res) => {
|
||||
if !res.verification_result {
|
||||
@@ -125,7 +118,11 @@ impl CoconutVerifier {
|
||||
}
|
||||
}
|
||||
|
||||
self.nyxd_client.execute_proposal(proposal_id, None).await?;
|
||||
self.nyxd_client
|
||||
.write()
|
||||
.await
|
||||
.execute_proposal(proposal_id, None)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+15
-8
@@ -22,8 +22,6 @@ use crate::node::statistics::collector::GatewayStatisticsCollector;
|
||||
use crate::node::storage::Storage;
|
||||
use anyhow::bail;
|
||||
use dashmap::DashMap;
|
||||
#[cfg(feature = "wireguard")]
|
||||
use defguard_wireguard_rs::{WGApi, WireguardInterfaceApi};
|
||||
use futures::channel::{mpsc, oneshot};
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
@@ -202,14 +200,19 @@ impl<St> Gateway<St> {
|
||||
mixnet_handling::Listener::new(listening_address, shutdown).start(connection_handler);
|
||||
}
|
||||
|
||||
#[cfg(feature = "wireguard")]
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
async fn start_wireguard(
|
||||
&self,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<WGApi, Box<dyn Error + Send + Sync>> {
|
||||
) -> Result<defguard_wireguard_rs::WGApi, Box<dyn Error + Send + Sync>> {
|
||||
nym_wireguard::start_wireguard(shutdown, Arc::clone(&self.client_registry)).await
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "wireguard", not(target_os = "linux")))]
|
||||
async fn start_wireguard(&self, _shutdown: TaskClient) {
|
||||
nym_wireguard::start_wireguard().await
|
||||
}
|
||||
|
||||
fn start_client_websocket_listener(
|
||||
&self,
|
||||
forwarding_channel: MixForwardingSender,
|
||||
@@ -451,7 +454,7 @@ impl<St> Gateway<St> {
|
||||
|
||||
let coconut_verifier = {
|
||||
let nyxd_client = self.random_nyxd_client()?;
|
||||
CoconutVerifier::new(nyxd_client)
|
||||
CoconutVerifier::new(nyxd_client).await
|
||||
};
|
||||
|
||||
let mix_forwarding_channel =
|
||||
@@ -524,21 +527,25 @@ impl<St> Gateway<St> {
|
||||
|
||||
// Once this is a bit more mature, make this a commandline flag instead of a compile time
|
||||
// flag
|
||||
#[cfg(feature = "wireguard")]
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
let wg_api = self
|
||||
.start_wireguard(shutdown.subscribe().named("wireguard"))
|
||||
.await
|
||||
.ok();
|
||||
|
||||
#[cfg(all(feature = "wireguard", not(target_os = "linux")))]
|
||||
self.start_wireguard(shutdown.subscribe().named("wireguard"))
|
||||
.await;
|
||||
|
||||
info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!");
|
||||
|
||||
if let Err(err) = Self::wait_for_interrupt(shutdown).await {
|
||||
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
|
||||
bail!("{err}")
|
||||
}
|
||||
#[cfg(feature = "wireguard")]
|
||||
#[cfg(all(feature = "wireguard", target_os = "linux"))]
|
||||
if let Some(wg_api) = wg_api {
|
||||
wg_api.remove_interface()?;
|
||||
defguard_wireguard_rs::WireguardInterfaceApi::remove_interface(&wg_api)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -75,7 +75,11 @@ impl InboxManager {
|
||||
sqlx::query_as!(
|
||||
StoredMessage,
|
||||
r#"
|
||||
SELECT * FROM message_store
|
||||
SELECT
|
||||
id as "id!",
|
||||
client_address_bs58 as "client_address_bs58!",
|
||||
content as "content!"
|
||||
FROM message_store
|
||||
WHERE client_address_bs58 = ? AND id > ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?;
|
||||
@@ -90,7 +94,11 @@ impl InboxManager {
|
||||
sqlx::query_as!(
|
||||
StoredMessage,
|
||||
r#"
|
||||
SELECT * FROM message_store
|
||||
SELECT
|
||||
id as "id!",
|
||||
client_address_bs58 as "client_address_bs58!",
|
||||
content as "content!"
|
||||
FROM message_store
|
||||
WHERE client_address_bs58 = ?
|
||||
ORDER BY id ASC
|
||||
LIMIT ?;
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ ts-rs = { workspace = true, optional = true}
|
||||
anyhow = { workspace = true }
|
||||
getset = "0.1.1"
|
||||
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
@@ -115,7 +115,7 @@ generate-ts = ["ts-rs"]
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = [
|
||||
sqlx = { workspace = true, features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
"macros",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use nym_api_requests::models::CirculatingSupplyResponse;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::coconut::api_routes::helpers::build_credentials_response;
|
||||
use crate::coconut::error::{CoconutError, Result};
|
||||
use crate::coconut::helpers::{accepted_vote_err, blind_sign};
|
||||
@@ -18,7 +21,7 @@ use nym_coconut_bandwidth_contract_common::spend_credential::{
|
||||
};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
|
||||
use nym_validator_client::nyxd::{Coin, Fee};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State as RocketState;
|
||||
|
||||
@@ -91,6 +94,9 @@ pub async fn verify_bandwidth_credential(
|
||||
) -> Result<Json<VerifyCredentialResponse>> {
|
||||
let proposal_id = verify_credential_body.proposal_id;
|
||||
let proposal = state.client.get_proposal(proposal_id).await?;
|
||||
|
||||
// TODO: introduce a check to make sure we haven't already voted for this proposal to prevent DDOS
|
||||
|
||||
// Proposal description is the blinded serial number
|
||||
if !verify_credential_body
|
||||
.credential
|
||||
@@ -133,15 +139,7 @@ pub async fn verify_bandwidth_credential(
|
||||
// Vote yes or no on the proposal based on the verification result
|
||||
let ret = state
|
||||
.client
|
||||
.vote_proposal(
|
||||
proposal_id,
|
||||
vote_yes,
|
||||
Some(Fee::new_payer_granter_auto(
|
||||
None,
|
||||
None,
|
||||
Some(verify_credential_body.gateway_cosmos_addr.clone()),
|
||||
)),
|
||||
)
|
||||
.vote_proposal(proposal_id, vote_yes, None)
|
||||
.await;
|
||||
accepted_vote_err(ret)?;
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
use crate::coconut::error::Result;
|
||||
use crate::nyxd;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use crate::support::nyxd::ClientInner;
|
||||
use nym_coconut_dkg_common::types::{Epoch, EpochId};
|
||||
use nym_coconut_interface::VerificationKey;
|
||||
use nym_credentials::coconut::utils::obtain_aggregate_verification_key;
|
||||
use nym_validator_client::coconut::all_coconut_api_clients;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::DirectSigningHttpRpcNyxdClient;
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
@@ -40,9 +40,7 @@ impl CachedEpoch {
|
||||
self.valid_until > OffsetDateTime::now_utc()
|
||||
}
|
||||
|
||||
async fn update(&mut self, client: &DirectSigningHttpRpcNyxdClient) -> Result<()> {
|
||||
let epoch = client.get_current_epoch().await?;
|
||||
|
||||
async fn update(&mut self, epoch: Epoch) -> Result<()> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
let state_end =
|
||||
OffsetDateTime::from_unix_timestamp(epoch.finish_timestamp.seconds() as i64).unwrap();
|
||||
@@ -84,8 +82,13 @@ impl APICommunicationChannel for QueryCommunicationChannel {
|
||||
// update cache
|
||||
drop(guard);
|
||||
let mut guard = self.cached_epoch.write().await;
|
||||
let client = self.nyxd_client.0.read().await;
|
||||
guard.update(&client).await?;
|
||||
|
||||
let epoch = match self.nyxd_client.read().await.deref() {
|
||||
ClientInner::Query(client) => client.get_current_epoch().await?,
|
||||
ClientInner::Signing(client) => client.get_current_epoch().await?,
|
||||
};
|
||||
|
||||
guard.update(epoch).await?;
|
||||
|
||||
return Ok(guard.current_epoch_id);
|
||||
}
|
||||
@@ -96,8 +99,11 @@ impl APICommunicationChannel for QueryCommunicationChannel {
|
||||
}
|
||||
|
||||
let mut guard = self.epoch_keys.write().await;
|
||||
let client = self.nyxd_client.0.read().await;
|
||||
let coconut_api_clients = all_coconut_api_clients(client.deref(), epoch_id).await?;
|
||||
let coconut_api_clients = match self.nyxd_client.read().await.deref() {
|
||||
ClientInner::Query(client) => all_coconut_api_clients(client, epoch_id).await?,
|
||||
ClientInner::Signing(client) => all_coconut_api_clients(client, epoch_id).await?,
|
||||
};
|
||||
|
||||
let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?;
|
||||
|
||||
guard.insert(epoch_id, vk.clone());
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::coconut::dkg::{
|
||||
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
|
||||
use crate::nyxd;
|
||||
use crate::support::config;
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use nym_coconut_dkg_common::types::EpochState;
|
||||
use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
@@ -53,6 +53,10 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
coconut_keypair: CoconutKeyPair,
|
||||
rng: R,
|
||||
) -> Result<Self> {
|
||||
let Some(announce_address) = &config.announce_address else {
|
||||
bail!("can't start a DKG controller without specifying an announce address!")
|
||||
};
|
||||
|
||||
let dkg_keypair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new(
|
||||
&config.storage_paths.decryption_key_path,
|
||||
&config.storage_paths.public_key_with_proof_path,
|
||||
@@ -76,7 +80,7 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
|
||||
state: State::new(
|
||||
config.storage_paths.dkg_persistent_state_path.clone(),
|
||||
persistent_state,
|
||||
config.announce_address.clone(),
|
||||
announce_address.clone(),
|
||||
dkg_keypair,
|
||||
coconut_keypair,
|
||||
),
|
||||
|
||||
@@ -24,6 +24,9 @@ pub(crate) mod storage;
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests;
|
||||
|
||||
// equivalent of 10nym
|
||||
pub(crate) const MINIMUM_BALANCE: u128 = 10_000000;
|
||||
|
||||
pub fn stage<C, D>(
|
||||
client: C,
|
||||
mix_denom: String,
|
||||
|
||||
+6
-17
@@ -9,7 +9,7 @@ use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::cli::{self, Commands};
|
||||
use crate::support::cli;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::storage;
|
||||
use crate::support::storage::NymApiStorage;
|
||||
@@ -21,11 +21,11 @@ use clap::Parser;
|
||||
use coconut::dkg::controller::DkgController;
|
||||
use node_status_api::NodeStatusCache;
|
||||
use nym_bin_common::logging::setup_logging;
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_contract_cache::cache::NymContractCache;
|
||||
use nym_sphinx::receiver::SphinxMessageReceiver;
|
||||
use nym_task::TaskManager;
|
||||
use rand::rngs::OsRng;
|
||||
use std::error::Error;
|
||||
use support::{http, nyxd};
|
||||
|
||||
mod circulating_supply_api;
|
||||
@@ -46,7 +46,7 @@ struct ShutdownHandles {
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn main() -> Result<(), anyhow::Error> {
|
||||
println!("Starting nym api...");
|
||||
|
||||
cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] {
|
||||
@@ -59,24 +59,13 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
trace!("{:#?}", args);
|
||||
|
||||
setup_env(args.config_env_file.as_ref());
|
||||
|
||||
let command = args.command.unwrap_or(Commands::Run(Box::new(args.run)));
|
||||
|
||||
match command {
|
||||
Commands::BuildInfo(m) => {
|
||||
cli::build_info::execute(m);
|
||||
Ok(())
|
||||
}
|
||||
Commands::Run(m) => cli::run::execute(*m).await,
|
||||
}
|
||||
args.execute().await
|
||||
}
|
||||
|
||||
async fn start_nym_api_tasks(
|
||||
config: Config,
|
||||
) -> Result<ShutdownHandles, Box<dyn Error + Send + Sync>> {
|
||||
async fn start_nym_api_tasks(config: Config) -> anyhow::Result<ShutdownHandles> {
|
||||
let nyxd_client = nyxd::Client::new(&config);
|
||||
let connected_nyxd = config.get_nyxd_url();
|
||||
let nym_network_details = config.get_network_details();
|
||||
let nym_network_details = NymNetworkDetails::new_from_env();
|
||||
let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details);
|
||||
|
||||
let coconut_keypair = coconut::keypair::KeyPair::new();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::network::models::{ContractInformation, NetworkDetails};
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use super::helpers::_get_gateways_detailed;
|
||||
use super::NodeStatusCache;
|
||||
use crate::node_status_api::helpers::{
|
||||
|
||||
+38
-28
@@ -47,44 +47,52 @@ impl NymContractCacheRefresher {
|
||||
}
|
||||
|
||||
async fn get_nym_contracts_info(&self) -> Result<CachedContractsInfo> {
|
||||
use crate::query_guard;
|
||||
|
||||
let mut updated = HashMap::new();
|
||||
|
||||
let client_guard = self.nyxd_client.read().await;
|
||||
|
||||
let mixnet = client_guard.mixnet_contract_address();
|
||||
let vesting = client_guard.vesting_contract_address();
|
||||
let name_service = client_guard.name_service_contract_address();
|
||||
let service_provider = client_guard.service_provider_contract_address();
|
||||
let coconut_bandwidth = client_guard.coconut_bandwidth_contract_address();
|
||||
let coconut_dkg = client_guard.dkg_contract_address();
|
||||
let group = client_guard.group_contract_address();
|
||||
let multisig = client_guard.multisig_contract_address();
|
||||
let mixnet = query_guard!(client_guard, mixnet_contract_address());
|
||||
let vesting = query_guard!(client_guard, vesting_contract_address());
|
||||
let name_service = query_guard!(client_guard, name_service_contract_address());
|
||||
let service_provider = query_guard!(client_guard, service_provider_contract_address());
|
||||
let coconut_bandwidth = query_guard!(client_guard, coconut_bandwidth_contract_address());
|
||||
let coconut_dkg = query_guard!(client_guard, dkg_contract_address());
|
||||
let group = query_guard!(client_guard, group_contract_address());
|
||||
let multisig = query_guard!(client_guard, multisig_contract_address());
|
||||
|
||||
// get cw2 versions
|
||||
let mixnet_cw2_future = client_guard.get_mixnet_contract_cw2_version();
|
||||
let vesting_cw2_future = client_guard.get_vesting_contract_cw2_version();
|
||||
let service_provider_cw2_future = client_guard.get_name_service_contract_cw2_version();
|
||||
let name_service_cw2_future = client_guard.get_name_service_contract_cw2_version();
|
||||
let mixnet_cw2_future = query_guard!(client_guard, get_mixnet_contract_cw2_version());
|
||||
let vesting_cw2_future = query_guard!(client_guard, get_vesting_contract_cw2_version());
|
||||
let service_provider_cw2_future =
|
||||
query_guard!(client_guard, get_name_service_contract_cw2_version());
|
||||
let name_service_cw2_future =
|
||||
query_guard!(client_guard, get_name_service_contract_cw2_version());
|
||||
|
||||
// group and multisig contract save that information in their storage but don't expose it via queries
|
||||
// so a temporary workaround...
|
||||
let multisig_cw2 = if let Some(multisig_contract) = multisig {
|
||||
client_guard
|
||||
.query_contract_raw(multisig_contract, b"contract_info".to_vec())
|
||||
.await
|
||||
.map(|r| serde_json::from_slice(&r).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
query_guard!(
|
||||
client_guard,
|
||||
query_contract_raw(multisig_contract, b"contract_info".to_vec())
|
||||
.await
|
||||
.map(|r| serde_json::from_slice(&r).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let group_cw2 = if let Some(group_contract) = group {
|
||||
client_guard
|
||||
.query_contract_raw(group_contract, b"contract_info".to_vec())
|
||||
.await
|
||||
.map(|r| serde_json::from_slice(&r).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
query_guard!(
|
||||
client_guard,
|
||||
query_contract_raw(group_contract, b"contract_info".to_vec())
|
||||
.await
|
||||
.map(|r| serde_json::from_slice(&r).ok())
|
||||
.ok()
|
||||
.flatten()
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -98,10 +106,12 @@ impl NymContractCacheRefresher {
|
||||
.await;
|
||||
|
||||
// get detailed build info
|
||||
let mixnet_detailed_future = client_guard.get_mixnet_contract_version();
|
||||
let vesting_detailed_future = client_guard.get_vesting_contract_version();
|
||||
let service_provider_detailed_future = client_guard.get_sp_contract_version();
|
||||
let name_service_detailed_future = client_guard.get_name_service_contract_version();
|
||||
let mixnet_detailed_future = query_guard!(client_guard, get_mixnet_contract_version());
|
||||
let vesting_detailed_future = query_guard!(client_guard, get_vesting_contract_version());
|
||||
let service_provider_detailed_future =
|
||||
query_guard!(client_guard, get_sp_contract_version());
|
||||
let name_service_detailed_future =
|
||||
query_guard!(client_guard, get_name_service_contract_version());
|
||||
|
||||
let mut build_info = join_all(vec![
|
||||
mixnet_detailed_future,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::{
|
||||
node_status_api::{
|
||||
helpers::{_get_active_set_detailed, _get_mixnodes_detailed, _get_rewarded_set_detailed},
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
// due to the macro expansion of rather old rocket macros...
|
||||
#![allow(unused_imports)]
|
||||
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
|
||||
@@ -10,6 +10,7 @@ pub(crate) struct Args {
|
||||
output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) fn execute(args: Args) {
|
||||
println!("{}", args.output.format(&bin_info_owned!()))
|
||||
pub(crate) fn execute(args: Args) -> anyhow::Result<()> {
|
||||
println!("{}", args.output.format(&bin_info_owned!()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::config::default_config_filepath;
|
||||
use crate::support::config::helpers::initialise_new;
|
||||
use anyhow::bail;
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
pub(crate) struct Args {
|
||||
/// Id of the nym-api we want to initialise. if unspecified, a default value will be used.
|
||||
/// default: "default"
|
||||
#[clap(long, default_value = "default")]
|
||||
pub(crate) id: String,
|
||||
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
/// default: false
|
||||
#[clap(short = 'm', long)]
|
||||
pub(crate) enable_monitor: bool,
|
||||
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
/// default: false
|
||||
#[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")]
|
||||
pub(crate) enable_rewarding: bool,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: http://localhost:26657
|
||||
#[clap(long)]
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
|
||||
/// default: None
|
||||
#[clap(long)]
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Flag to indicate whether credential signer authority is enabled on this API
|
||||
/// default: false
|
||||
#[clap(
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
alias = "enable_coconut"
|
||||
)]
|
||||
pub(crate) enable_zk_nym: bool,
|
||||
|
||||
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
|
||||
/// to obtain their credentials
|
||||
/// default: None
|
||||
#[clap(long)]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
#[clap(long, requires = "enable_monitor")]
|
||||
pub(crate) monitor_credentials_mode: bool,
|
||||
// #[clap(short, long, default_value_t = OutputFormat::default())]
|
||||
// output: OutputFormat,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
eprintln!("initialising nym-api...");
|
||||
|
||||
// let output = args.output;
|
||||
|
||||
let config_path = default_config_filepath(&args.id);
|
||||
if config_path.exists() {
|
||||
// don't bother with attempting to override some of the data and preserving the rest of it
|
||||
// if the config exists.
|
||||
// this situation should never occur under normal circumstances, so it's up to the user to deal with it
|
||||
bail!("there already exists a configuration file at '{}'. If you intend to replace it, you need to manually remove it first. Make sure to make backup of any keys and datastores first.", config_path.display())
|
||||
}
|
||||
|
||||
let config = initialise_new(&args.id)?;
|
||||
// args take precedence over env
|
||||
config
|
||||
.override_with_env()
|
||||
.override_with_args(args)
|
||||
.try_save()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+19
-110
@@ -1,31 +1,22 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::config::Config;
|
||||
use crate::support::config::default_config_filepath;
|
||||
use crate::support::config::helpers::{initialise_new, try_load_current_config};
|
||||
use ::nym_config::defaults::var_names::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS};
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_config::defaults::var_names::NYXD;
|
||||
use nym_config::OptionalSet;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
pub(crate) mod build_info;
|
||||
pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print();
|
||||
}
|
||||
|
||||
// Helper for passing LONG_VERSION to clap
|
||||
fn pretty_build_info_static() -> &'static str {
|
||||
&PRETTY_BUILD_INFORMATION
|
||||
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
|
||||
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
|
||||
}
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(args_conflicts_with_subcommands = true)]
|
||||
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
|
||||
pub(crate) struct Cli {
|
||||
/// Path pointing to an env file that configures the Nym API.
|
||||
@@ -33,109 +24,27 @@ pub(crate) struct Cli {
|
||||
pub(crate) config_env_file: Option<std::path::PathBuf>,
|
||||
|
||||
#[clap(subcommand)]
|
||||
pub(crate) command: Option<Commands>,
|
||||
pub(crate) command: Commands,
|
||||
}
|
||||
|
||||
// this shouldn't really be here, but we don't want to break backwards compat
|
||||
#[clap(flatten)]
|
||||
pub(crate) run: run::Args,
|
||||
impl Cli {
|
||||
pub(crate) async fn execute(self) -> Result<(), anyhow::Error> {
|
||||
match self.command {
|
||||
Commands::Init(args) => init::execute(args).await,
|
||||
Commands::Run(args) => run::execute(args).await,
|
||||
Commands::BuildInfo(args) => build_info::execute(args),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
pub(crate) enum Commands {
|
||||
/// Initialise a Nym Api instance with persistent config.toml file.
|
||||
Init(init::Args),
|
||||
|
||||
/// Run the Nym Api with provided configuration optionally overriding set parameters
|
||||
Run(Box<run::Args>),
|
||||
Run(run::Args),
|
||||
|
||||
/// Show build information of this binary
|
||||
BuildInfo(build_info::Args),
|
||||
}
|
||||
|
||||
pub(crate) fn override_config(config: Config, args: run::Args) -> Config {
|
||||
config
|
||||
.with_optional_env(
|
||||
Config::with_custom_nyxd_validator,
|
||||
args.nyxd_validator,
|
||||
NYXD,
|
||||
)
|
||||
.with_optional_env(
|
||||
Config::with_custom_mixnet_contract,
|
||||
args.mixnet_contract,
|
||||
MIXNET_CONTRACT_ADDRESS,
|
||||
)
|
||||
.with_optional_env(
|
||||
Config::with_custom_vesting_contract,
|
||||
args.vesting_contract,
|
||||
VESTING_CONTRACT_ADDRESS,
|
||||
)
|
||||
.with_optional(Config::with_mnemonic, args.mnemonic)
|
||||
.with_optional(
|
||||
Config::with_minimum_interval_monitor_threshold,
|
||||
args.monitor_threshold,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_min_mixnode_reliability,
|
||||
args.min_mixnode_reliability,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_min_gateway_reliability,
|
||||
args.min_gateway_reliability,
|
||||
)
|
||||
.with_optional(Config::with_network_monitor_enabled, args.enable_monitor)
|
||||
.with_optional(Config::with_rewarding_enabled, args.enable_rewarding)
|
||||
.with_optional(Config::with_ephemera_enabled, args.enable_ephemera)
|
||||
.with_optional(
|
||||
Config::with_disabled_credentials_mode,
|
||||
args.enabled_credentials_mode.map(|b| !b),
|
||||
)
|
||||
.with_optional(Config::with_announce_address, args.announce_address)
|
||||
.with_optional(Config::with_coconut_signer_enabled, args.enable_coconut)
|
||||
.with_optional(Config::with_ephemera_ip, args.ephemera_args.ephemera_ip)
|
||||
.with_optional(
|
||||
Config::with_ephemera_protocol_port,
|
||||
args.ephemera_args.ephemera_protocol_port,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_ephemera_websocket_port,
|
||||
args.ephemera_args.ephemera_websocket_port,
|
||||
)
|
||||
.with_optional(
|
||||
Config::with_ephemera_http_api_port,
|
||||
args.ephemera_args.ephemera_http_api_port,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn build_config(args: run::Args) -> Result<Config> {
|
||||
let id = match &args.id {
|
||||
Some(id) => id.clone(),
|
||||
None => {
|
||||
error!("--id argument must be provided to run nym-api");
|
||||
bail!("--id argument must be provided to run nym-api")
|
||||
}
|
||||
};
|
||||
|
||||
// try to load config from the file, if it doesn't exist, use default values
|
||||
let config = match try_load_current_config(&id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
let config_path = default_config_filepath(&id);
|
||||
warn!(
|
||||
"Could not load the configuration file from {}: {err}. Either the file did not exist or was malformed. Using the default values instead",
|
||||
config_path.display()
|
||||
);
|
||||
|
||||
initialise_new(&id)?
|
||||
}
|
||||
};
|
||||
|
||||
let mut config = override_config(config, args);
|
||||
// since we have no proper `init`, we have to do id check here:
|
||||
let made_new_keys = config
|
||||
.base
|
||||
.storage_paths
|
||||
.generate_identity_if_missing(&config.base.id)?;
|
||||
|
||||
if made_new_keys {
|
||||
config.save_to_default_location()?
|
||||
}
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -2,114 +2,68 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::start_nym_api_tasks;
|
||||
use crate::support::cli::build_config;
|
||||
use nym_validator_client::nyxd;
|
||||
use std::error::Error;
|
||||
|
||||
// explicitly defined custom parser (as opposed to just using
|
||||
// #[arg(value_parser = clap::value_parser!(u8).range(0..100))]
|
||||
// for better error message
|
||||
fn threshold_in_range(s: &str) -> Result<u8, String> {
|
||||
let threshold: usize = s
|
||||
.parse()
|
||||
.map_err(|_| format!("`{s}` isn't a valid threshold number"))?;
|
||||
if threshold > 100 {
|
||||
Err(format!("{threshold} is not within the range 0-100"))
|
||||
} else {
|
||||
Ok(threshold as u8)
|
||||
}
|
||||
}
|
||||
use crate::support::config::helpers::try_load_current_config;
|
||||
use anyhow::bail;
|
||||
|
||||
#[derive(clap::Args, Debug)]
|
||||
pub(crate) struct Args {
|
||||
/// Id of the nym-api we want to run
|
||||
#[clap(long)]
|
||||
// ugh. we had to make it optional in case somebody wanted to run `build-info`
|
||||
pub(crate) id: Option<String>,
|
||||
/// Id of the nym-api we want to run.if unspecified, a default value will be used.
|
||||
/// default: "default"
|
||||
#[clap(long, default_value = "default")]
|
||||
pub(crate) id: String,
|
||||
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(short = 'm', long)]
|
||||
pub(crate) enable_monitor: Option<bool>,
|
||||
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")]
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Specifies whether ephemera is used to aggregate monitor data on this API
|
||||
#[clap(short = 'e', long, requires = "enable_monitor")]
|
||||
pub(crate) enable_ephemera: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance from which the monitor will grab nodes to test
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
/// Address of the mixnet contract managing the network
|
||||
#[clap(long)]
|
||||
pub(crate) mixnet_contract: Option<nyxd::AccountId>,
|
||||
|
||||
/// Address of the vesting contract holding locked tokens
|
||||
#[clap(long)]
|
||||
pub(crate) vesting_contract: Option<nyxd::AccountId>,
|
||||
|
||||
/// Mnemonic of the network monitor used for rewarding operators
|
||||
// even though we're currently converting the mnemonic to string (and then back to the concrete type)
|
||||
// at least we're getting immediate validation when passing the arguments
|
||||
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Specifies whether a config file based on provided arguments should be saved to a file
|
||||
#[clap(short = 'w', long)]
|
||||
pub(crate) save_config: bool,
|
||||
|
||||
/// Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval.
|
||||
#[clap(long, value_parser = threshold_in_range)]
|
||||
pub(crate) monitor_threshold: Option<u8>,
|
||||
|
||||
/// Mixnodes with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.
|
||||
#[clap(long, value_parser = threshold_in_range)]
|
||||
pub(crate) min_mixnode_reliability: Option<u8>,
|
||||
|
||||
/// Gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.
|
||||
#[clap(long, value_parser = threshold_in_range)]
|
||||
pub(crate) min_gateway_reliability: Option<u8>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
#[clap(long)]
|
||||
pub(crate) enabled_credentials_mode: Option<bool>,
|
||||
|
||||
/// Announced address where coconut clients will connect.
|
||||
#[clap(long, hide = true)]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Flag to indicate whether coconut signer authority is enabled on this API
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(
|
||||
long,
|
||||
requires = "mnemonic",
|
||||
requires = "announce_address",
|
||||
hide = true
|
||||
alias = "enable_coconut"
|
||||
)]
|
||||
pub(crate) enable_coconut: Option<bool>,
|
||||
pub(crate) enable_zk_nym: Option<bool>,
|
||||
|
||||
/// Ephemera configuration arguments.
|
||||
#[command(flatten)]
|
||||
pub(crate) ephemera_args: ephemera::cli::init::Cmd,
|
||||
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
|
||||
/// to obtain their credentials
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
/// default: None - config value will be used instead
|
||||
#[clap(long)]
|
||||
pub(crate) monitor_credentials_mode: Option<bool>,
|
||||
// /// Ephemera configuration arguments.
|
||||
// #[command(flatten)]
|
||||
// pub(crate) ephemera_args: ephemera::cli::init::Cmd,
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: Args) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let save_to_file = args.save_config;
|
||||
let config = build_config(args)?;
|
||||
pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
|
||||
// args take precedence over env
|
||||
let config = try_load_current_config(&args.id)?
|
||||
.override_with_env()
|
||||
.override_with_args(args);
|
||||
|
||||
// if we just wanted to write data to the config, exit, don't start any tasks
|
||||
if save_to_file {
|
||||
info!("Saving the configuration to a file");
|
||||
config.save_to_default_location()?;
|
||||
config
|
||||
.get_ephemera_args()
|
||||
.cmd
|
||||
.clone()
|
||||
.execute(Some(&config.get_id()));
|
||||
return Ok(());
|
||||
}
|
||||
config.validate()?;
|
||||
|
||||
let shutdown_handlers = start_nym_api_tasks(config).await?;
|
||||
|
||||
@@ -120,5 +74,10 @@ pub(crate) async fn execute(args: Args) -> Result<(), Box<dyn Error + Send + Syn
|
||||
log::info!("Stopping nym API");
|
||||
shutdown_handlers.rocket_handle.notify();
|
||||
|
||||
res
|
||||
if let Err(err) = res {
|
||||
// that's a nasty workaround, but anyhow errors are generally nicer, especially on exit
|
||||
bail!("{err}")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,42 +1,26 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::config::old_config_v1_1_21::ConfigV1_1_21;
|
||||
use crate::support::config::old_config_v1_1_27::ConfigV1_1_27;
|
||||
use crate::support::config::{default_config_directory, default_data_directory, Config};
|
||||
use anyhow::Result;
|
||||
use crate::support::config;
|
||||
use crate::support::config::{
|
||||
default_config_directory, default_data_directory, upgrade_helpers, Config,
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use rand_07::rngs::OsRng;
|
||||
use std::{fs, io};
|
||||
|
||||
fn try_upgrade_v1_1_21_config(id: &str) -> Result<()> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
fn init_identity_keys(config: &config::NymApiPaths) -> Result<()> {
|
||||
let keypaths = nym_pemstore::KeyPairPath::new(
|
||||
&config.private_identity_key_file,
|
||||
&config.public_identity_key_file,
|
||||
);
|
||||
|
||||
// explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+)
|
||||
let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(());
|
||||
};
|
||||
info!("It seems the nym-api is using <= v1.1.21 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
Ok(updated.save_to_default_location()?)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_27_config(id: &str) -> Result<()> {
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
|
||||
// explicitly load it as v1.1.27 (which is incompatible with the current, i.e. 1.1.28+)
|
||||
let Ok(old_config) = ConfigV1_1_27::load_from_file(id) else {
|
||||
// if we failed to load it, there might have been nothing to upgrade
|
||||
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
|
||||
return Ok(());
|
||||
};
|
||||
info!("It seems the nym-api is using <= v1.1.27 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
Ok(updated.save_to_default_location()?)
|
||||
let mut rng = OsRng;
|
||||
let keypair = identity::KeyPair::new(&mut rng);
|
||||
nym_pemstore::store_keypair(&keypair, &keypaths)
|
||||
.context("failed to store identity keys of the nym api")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_paths(id: &str) -> io::Result<()> {
|
||||
@@ -46,14 +30,21 @@ fn init_paths(id: &str) -> io::Result<()> {
|
||||
|
||||
pub(crate) fn initialise_new(id: &str) -> Result<Config> {
|
||||
let config = Config::new(id);
|
||||
|
||||
// create base storage paths
|
||||
init_paths(id)?;
|
||||
|
||||
// create identity keys
|
||||
init_identity_keys(&config.base.storage_paths)?;
|
||||
|
||||
// create DKG BTE keys
|
||||
crate::coconut::dkg::controller::init_keypair(&config.coconut_signer)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
pub(crate) fn try_load_current_config(id: &str) -> Result<Config> {
|
||||
try_upgrade_v1_1_21_config(id)?;
|
||||
try_upgrade_v1_1_27_config(id)?;
|
||||
|
||||
Ok(Config::read_from_default_path(id)?)
|
||||
upgrade_helpers::try_upgrade_config(id)?;
|
||||
Config::read_from_default_path(id).context(
|
||||
"failed to load config.toml from the default path - are you sure you run `init` before?",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
use crate::support::config::persistence::{
|
||||
CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, NymApiPaths,
|
||||
};
|
||||
use crate::support::config::r#override::OverrideConfig;
|
||||
use crate::support::config::template::CONFIG_TEMPLATE;
|
||||
use nym_config::defaults::{mainnet, NymNetworkDetails};
|
||||
use anyhow::bail;
|
||||
use nym_config::defaults::mainnet::read_parsed_var_if_not_default;
|
||||
use nym_config::defaults::var_names::{CONFIGURED, NYXD};
|
||||
use nym_config::serde_helpers::de_maybe_stringified;
|
||||
use nym_config::{
|
||||
must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate,
|
||||
DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, DEFAULT_NYM_APIS_DIR, NYM_DIR,
|
||||
};
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -19,14 +22,13 @@ use url::Url;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub(crate) mod helpers;
|
||||
pub(crate) mod old_config_v1_1_21;
|
||||
pub(crate) mod old_config_v1_1_27;
|
||||
|
||||
mod r#override;
|
||||
mod persistence;
|
||||
mod template;
|
||||
mod upgrade_helpers;
|
||||
|
||||
pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
|
||||
pub const DEFAULT_NYM_API_PORT: u16 = 8080;
|
||||
|
||||
pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10);
|
||||
|
||||
@@ -86,6 +88,10 @@ pub fn default_data_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
pub struct Config {
|
||||
// additional metadata holding on-disk location of this config file
|
||||
#[serde(skip)]
|
||||
pub(crate) save_path: Option<PathBuf>,
|
||||
|
||||
pub base: Base,
|
||||
|
||||
// TODO: perhaps introduce separate 'path finder' field for all the paths and directories like we have with other configs
|
||||
@@ -104,16 +110,6 @@ pub struct Config {
|
||||
pub ephemera: Ephemera,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for NymNetworkDetails {
|
||||
fn from(value: &'a Config) -> Self {
|
||||
// we get the current environmental details and then overwrite whatever is appropriate with
|
||||
// the values from the config
|
||||
NymNetworkDetails::new_from_env()
|
||||
.with_mixnet_contract(Some(value.get_mixnet_contract_address().as_ref()))
|
||||
.with_vesting_contract(Some(value.get_vesting_contract_address().as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
impl NymConfigTemplate for Config {
|
||||
fn template(&self) -> &'static str {
|
||||
CONFIG_TEMPLATE
|
||||
@@ -123,6 +119,7 @@ impl NymConfigTemplate for Config {
|
||||
impl Config {
|
||||
pub fn new<S: AsRef<str>>(id: S) -> Self {
|
||||
Config {
|
||||
save_path: None,
|
||||
base: Base::new_default(id.as_ref()),
|
||||
network_monitor: NetworkMonitor::new_default(id.as_ref()),
|
||||
node_status_api: NodeStatusAPI::new_default(id.as_ref()),
|
||||
@@ -134,12 +131,78 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> anyhow::Result<()> {
|
||||
let can_sign = self.base.mnemonic.is_some();
|
||||
|
||||
if !can_sign && self.rewarding.enabled {
|
||||
bail!("can't enable rewarding without providing a mnemonic")
|
||||
}
|
||||
|
||||
if !can_sign && self.coconut_signer.enabled {
|
||||
bail!("can't enable coconut signer without providing a mnemonic")
|
||||
}
|
||||
|
||||
if !can_sign && self.ephemera.enabled {
|
||||
bail!("can't enable ephemera without providing a mnemonic")
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn override_with_args<O: Into<OverrideConfig>>(mut self, args: O) -> Self {
|
||||
let args = args.into();
|
||||
|
||||
if let Some(enabled_monitor) = args.enable_monitor {
|
||||
self.network_monitor.enabled = enabled_monitor;
|
||||
}
|
||||
if let Some(enable_rewarding) = args.enable_rewarding {
|
||||
self.rewarding.enabled = enable_rewarding;
|
||||
}
|
||||
if let Some(nyxd_upstream) = args.nyxd_validator {
|
||||
self.base.local_validator = nyxd_upstream;
|
||||
}
|
||||
if let Some(mnemonic) = args.mnemonic {
|
||||
self.base.mnemonic = Some(mnemonic)
|
||||
}
|
||||
if let Some(enable_zk_nym) = args.enable_zk_nym {
|
||||
self.coconut_signer.enabled = enable_zk_nym
|
||||
}
|
||||
if let Some(announce_address) = args.announce_address {
|
||||
self.coconut_signer.announce_address = Some(announce_address)
|
||||
}
|
||||
if let Some(monitor_credentials_mode) = args.monitor_credentials_mode {
|
||||
self.network_monitor.debug.disabled_credentials_mode = !monitor_credentials_mode
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn override_with_env(mut self) -> Self {
|
||||
if std::env::var(CONFIGURED).is_ok() {
|
||||
// currently the only value that can be overridden is 'nyxd'
|
||||
if let Some(Ok(custom_nyxd)) = read_parsed_var_if_not_default(NYXD) {
|
||||
self.base.local_validator = custom_nyxd
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
// simple wrapper that reads config file and assigns path location
|
||||
fn read_from_path<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
let path = path.as_ref();
|
||||
let mut loaded: Config = read_config_from_toml_file(path)?;
|
||||
loaded.save_path = Some(path.to_path_buf());
|
||||
debug!("loaded config file from {}", path.display());
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||
read_config_from_toml_file(path)
|
||||
Self::read_from_path(path)
|
||||
}
|
||||
|
||||
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
|
||||
Self::read_from_toml_file(default_config_filepath(id))
|
||||
Self::read_from_path(default_config_filepath(id))
|
||||
}
|
||||
|
||||
pub fn default_location(&self) -> PathBuf {
|
||||
@@ -151,93 +214,13 @@ impl Config {
|
||||
save_formatted_config_to_file(self, config_save_location)
|
||||
}
|
||||
|
||||
pub fn get_network_details(&self) -> NymNetworkDetails {
|
||||
self.into()
|
||||
}
|
||||
|
||||
pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self {
|
||||
self.network_monitor.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self {
|
||||
self.network_monitor.debug.disabled_credentials_mode = disabled_credentials_mode;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_rewarding_enabled(mut self, enabled: bool) -> Self {
|
||||
self.rewarding.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_coconut_signer_enabled(mut self, enabled: bool) -> Self {
|
||||
self.coconut_signer.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ephemera_enabled(mut self, enabled: bool) -> Self {
|
||||
self.ephemera.enabled = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_nyxd_validator(mut self, validator: Url) -> Self {
|
||||
self.base.local_validator = validator;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_announce_address(mut self, announce_address: Url) -> Self {
|
||||
self.coconut_signer.announce_address = announce_address;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_mixnet_contract(mut self, mixnet_contract: nyxd::AccountId) -> Self {
|
||||
self.base.mixnet_contract_address = mixnet_contract;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_custom_vesting_contract(mut self, vesting_contract: nyxd::AccountId) -> Self {
|
||||
self.base.vesting_contract_address = vesting_contract;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_mnemonic(mut self, mnemonic: bip39::Mnemonic) -> Self {
|
||||
self.base.mnemonic = mnemonic;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_minimum_interval_monitor_threshold(mut self, threshold: u8) -> Self {
|
||||
self.rewarding.debug.minimum_interval_monitor_threshold = threshold;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_min_mixnode_reliability(mut self, min_mixnode_reliability: u8) -> Self {
|
||||
self.network_monitor.debug.min_mixnode_reliability = min_mixnode_reliability;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_min_gateway_reliability(mut self, min_gateway_reliability: u8) -> Self {
|
||||
self.network_monitor.debug.min_gateway_reliability = min_gateway_reliability;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ephemera_ip(mut self, ip: String) -> Self {
|
||||
self.ephemera.args.cmd.ephemera_ip = Some(ip);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ephemera_protocol_port(mut self, port: u16) -> Self {
|
||||
self.ephemera.args.cmd.ephemera_protocol_port = Some(port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ephemera_websocket_port(mut self, port: u16) -> Self {
|
||||
self.ephemera.args.cmd.ephemera_websocket_port = Some(port);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ephemera_http_api_port(mut self, port: u16) -> Self {
|
||||
self.ephemera.args.cmd.ephemera_http_api_port = Some(port);
|
||||
self
|
||||
pub fn try_save(&self) -> io::Result<()> {
|
||||
if let Some(save_location) = &self.save_path {
|
||||
save_formatted_config_to_file(self, save_location)
|
||||
} else {
|
||||
debug!("config file save location is unknown. falling back to the default");
|
||||
self.save_to_default_location()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> String {
|
||||
@@ -248,16 +231,8 @@ impl Config {
|
||||
self.base.local_validator.clone()
|
||||
}
|
||||
|
||||
pub fn get_mixnet_contract_address(&self) -> nyxd::AccountId {
|
||||
self.base.mixnet_contract_address.clone()
|
||||
}
|
||||
|
||||
pub fn get_vesting_contract_address(&self) -> nyxd::AccountId {
|
||||
self.base.vesting_contract_address.clone()
|
||||
}
|
||||
|
||||
pub fn get_mnemonic(&self) -> bip39::Mnemonic {
|
||||
self.base.mnemonic.clone()
|
||||
pub fn get_mnemonic(&self) -> Option<&bip39::Mnemonic> {
|
||||
self.base.mnemonic.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_ephemera_args(&self) -> &crate::ephemera::Args {
|
||||
@@ -278,23 +253,13 @@ pub struct Base {
|
||||
#[zeroize(skip)]
|
||||
pub local_validator: Url,
|
||||
|
||||
/// Address of the validator contract managing the network
|
||||
#[zeroize(skip)]
|
||||
pub mixnet_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Address of the vesting contract holding locked tokens
|
||||
#[zeroize(skip)]
|
||||
pub vesting_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Mnemonic used for rewarding and/or multisig operations
|
||||
// TODO: similarly to the note in gateway, this should get moved to a separate file
|
||||
mnemonic: bip39::Mnemonic,
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Storage paths to the common nym-api files
|
||||
#[zeroize(skip)]
|
||||
// ideally we wouldn't be using default here, but I really really don't want to be writing
|
||||
// another big config migration
|
||||
#[serde(default)]
|
||||
pub storage_paths: NymApiPaths,
|
||||
}
|
||||
|
||||
@@ -310,10 +275,7 @@ impl Base {
|
||||
storage_paths: NymApiPaths::new_default(&id),
|
||||
id,
|
||||
local_validator: default_validator,
|
||||
mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
// this this doesn't make any sense since you really have a mnemonic beforehand...
|
||||
mnemonic: bip39::Mnemonic::generate(24).unwrap(),
|
||||
mnemonic: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,7 +523,8 @@ pub struct CoconutSigner {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
|
||||
pub announce_address: Url,
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub announce_address: Option<Url>,
|
||||
|
||||
pub storage_paths: CoconutSignerPaths,
|
||||
|
||||
@@ -571,17 +534,9 @@ pub struct CoconutSigner {
|
||||
|
||||
impl CoconutSigner {
|
||||
pub fn new_default<P: AsRef<Path>>(id: P) -> Self {
|
||||
let default_validator: Url = DEFAULT_LOCAL_VALIDATOR
|
||||
.parse()
|
||||
.expect("default local validator is malformed!");
|
||||
let mut default_announce_address = default_validator;
|
||||
default_announce_address
|
||||
.set_port(Some(DEFAULT_NYM_API_PORT))
|
||||
.expect("default local validator is malformed!");
|
||||
|
||||
CoconutSigner {
|
||||
enabled: false,
|
||||
announce_address: default_announce_address,
|
||||
announce_address: None,
|
||||
storage_paths: CoconutSignerPaths::new_default(id),
|
||||
debug: Default::default(),
|
||||
}
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::config::persistence::{
|
||||
CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths,
|
||||
};
|
||||
use crate::support::config::{
|
||||
Base, CirculatingSupplyCacher, CirculatingSupplyCacherDebug, CoconutSigner, CoconutSignerDebug,
|
||||
Config, NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding,
|
||||
RewardingDebug, TopologyCacher, TopologyCacherDebug,
|
||||
};
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_NYM_API_PORT: u16 = 8080;
|
||||
const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
|
||||
const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
|
||||
|
||||
const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10);
|
||||
|
||||
const DEFAULT_GATEWAY_SENDING_RATE: usize = 200;
|
||||
const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50;
|
||||
const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
const DEFAULT_MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(15 * 60);
|
||||
const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60);
|
||||
// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause
|
||||
// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the
|
||||
// bandwidth bridging protocol, we can come back to a smaller timeout value
|
||||
const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60);
|
||||
// This timeout value should be big enough to accommodate an initial bandwidth acquirement
|
||||
const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(2 * 60);
|
||||
|
||||
const DEFAULT_TEST_ROUTES: usize = 3;
|
||||
const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1;
|
||||
const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000;
|
||||
const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3;
|
||||
|
||||
const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120);
|
||||
const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600);
|
||||
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
|
||||
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
|
||||
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_21 {
|
||||
#[serde(default)]
|
||||
base: BaseV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
network_monitor: NetworkMonitorV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
node_status_api: NodeStatusAPIV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
topology_cacher: TopologyCacherV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
circulating_supply_cacher: CirculatingSupplyCacherV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
rewarding: RewardingV1_1_21,
|
||||
|
||||
#[serde(default)]
|
||||
coconut_signer: CoconutSignerV1_1_21,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_21> for Config {
|
||||
fn from(value: ConfigV1_1_21) -> Self {
|
||||
// this value was never properly saved (probably a bug)
|
||||
// so explicitly set it to the default
|
||||
|
||||
let dkg_persistent_state_path =
|
||||
CoconutSignerV1_1_21::default_dkg_persistent_state_path(&value.base.id);
|
||||
|
||||
Config {
|
||||
base: Base {
|
||||
id: value.base.id,
|
||||
local_validator: value.base.local_validator,
|
||||
mixnet_contract_address: value.base.mixnet_contract_address,
|
||||
vesting_contract_address: value.base.vesting_contract_address,
|
||||
mnemonic: value.base.mnemonic,
|
||||
storage_paths: Default::default(),
|
||||
},
|
||||
network_monitor: NetworkMonitor {
|
||||
enabled: value.network_monitor.enabled,
|
||||
storage_paths: NetworkMonitorPaths {
|
||||
credentials_database_path: value.network_monitor.credentials_database_path,
|
||||
},
|
||||
debug: NetworkMonitorDebug {
|
||||
min_mixnode_reliability: value.network_monitor.min_mixnode_reliability,
|
||||
min_gateway_reliability: value.network_monitor.min_gateway_reliability,
|
||||
disabled_credentials_mode: value.network_monitor.disabled_credentials_mode,
|
||||
run_interval: value.network_monitor.run_interval,
|
||||
gateway_ping_interval: value.network_monitor.gateway_ping_interval,
|
||||
gateway_sending_rate: value.network_monitor.gateway_sending_rate,
|
||||
max_concurrent_gateway_clients: value
|
||||
.network_monitor
|
||||
.max_concurrent_gateway_clients,
|
||||
gateway_response_timeout: value.network_monitor.gateway_response_timeout,
|
||||
gateway_connection_timeout: value.network_monitor.gateway_connection_timeout,
|
||||
packet_delivery_timeout: value.network_monitor.packet_delivery_timeout,
|
||||
test_routes: value.network_monitor.test_routes,
|
||||
minimum_test_routes: value.network_monitor.minimum_test_routes,
|
||||
route_test_packets: value.network_monitor.route_test_packets,
|
||||
per_node_test_packets: value.network_monitor.per_node_test_packets,
|
||||
},
|
||||
},
|
||||
node_status_api: NodeStatusAPI {
|
||||
storage_paths: NodeStatusAPIPaths {
|
||||
database_path: value.node_status_api.database_path,
|
||||
},
|
||||
debug: NodeStatusAPIDebug {
|
||||
caching_interval: value.node_status_api.caching_interval,
|
||||
},
|
||||
},
|
||||
topology_cacher: TopologyCacher {
|
||||
debug: TopologyCacherDebug {
|
||||
caching_interval: value.topology_cacher.caching_interval,
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
circulating_supply_cacher: CirculatingSupplyCacher {
|
||||
enabled: value.circulating_supply_cacher.enabled,
|
||||
debug: CirculatingSupplyCacherDebug {
|
||||
caching_interval: value.circulating_supply_cacher.caching_interval,
|
||||
},
|
||||
},
|
||||
rewarding: Rewarding {
|
||||
enabled: value.rewarding.enabled,
|
||||
debug: RewardingDebug {
|
||||
minimum_interval_monitor_threshold: value
|
||||
.rewarding
|
||||
.minimum_interval_monitor_threshold,
|
||||
},
|
||||
},
|
||||
coconut_signer: CoconutSigner {
|
||||
enabled: value.coconut_signer.enabled,
|
||||
announce_address: value.base.announce_address,
|
||||
storage_paths: CoconutSignerPaths {
|
||||
dkg_persistent_state_path,
|
||||
verification_key_path: value.coconut_signer.verification_key_path,
|
||||
secret_key_path: value.coconut_signer.secret_key_path,
|
||||
decryption_key_path: value.coconut_signer.decryption_key_path,
|
||||
public_key_with_proof_path: value.coconut_signer.public_key_with_proof_path,
|
||||
},
|
||||
debug: CoconutSignerDebug {
|
||||
dkg_contract_polling_rate: value.coconut_signer.dkg_contract_polling_rate,
|
||||
},
|
||||
},
|
||||
ephemera: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_21 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("nym-api")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct BaseV1_1_21 {
|
||||
/// ID specifies the human readable ID of this particular nym-api.
|
||||
id: String,
|
||||
|
||||
local_validator: Url,
|
||||
|
||||
/// Address announced to the directory server for the clients to connect to.
|
||||
// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address
|
||||
// later on by using name resolvable with a DNS query, such as `nymtech.net`.
|
||||
announce_address: Url,
|
||||
|
||||
/// Address of the validator contract managing the network
|
||||
mixnet_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Address of the vesting contract holding locked tokens
|
||||
vesting_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Mnemonic used for rewarding and/or multisig operations
|
||||
mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
impl Default for BaseV1_1_21 {
|
||||
fn default() -> Self {
|
||||
let default_validator: Url = DEFAULT_LOCAL_VALIDATOR
|
||||
.parse()
|
||||
.expect("default local validator is malformed!");
|
||||
let mut default_announce_address = default_validator.clone();
|
||||
default_announce_address
|
||||
.set_port(Some(DEFAULT_NYM_API_PORT))
|
||||
.expect("default local validator is malformed!");
|
||||
|
||||
BaseV1_1_21 {
|
||||
id: String::default(),
|
||||
local_validator: default_validator,
|
||||
announce_address: default_announce_address,
|
||||
mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
mnemonic: bip39::Mnemonic::generate(24).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NetworkMonitorV1_1_21 {
|
||||
min_mixnode_reliability: u8, // defaults to 50
|
||||
min_gateway_reliability: u8, // defaults to 20
|
||||
enabled: bool,
|
||||
#[serde(default)]
|
||||
disabled_credentials_mode: bool,
|
||||
#[serde(with = "humantime_serde")]
|
||||
run_interval: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
gateway_ping_interval: Duration,
|
||||
gateway_sending_rate: usize,
|
||||
max_concurrent_gateway_clients: usize,
|
||||
#[serde(with = "humantime_serde")]
|
||||
gateway_response_timeout: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
gateway_connection_timeout: Duration,
|
||||
#[serde(with = "humantime_serde")]
|
||||
packet_delivery_timeout: Duration,
|
||||
credentials_database_path: PathBuf,
|
||||
test_routes: usize,
|
||||
minimum_test_routes: usize,
|
||||
route_test_packets: usize,
|
||||
per_node_test_packets: usize,
|
||||
}
|
||||
|
||||
impl Default for NetworkMonitorV1_1_21 {
|
||||
fn default() -> Self {
|
||||
NetworkMonitorV1_1_21 {
|
||||
min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY,
|
||||
min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY,
|
||||
enabled: false,
|
||||
disabled_credentials_mode: true,
|
||||
run_interval: DEFAULT_MONITOR_RUN_INTERVAL,
|
||||
gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL,
|
||||
gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE,
|
||||
max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT,
|
||||
packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT,
|
||||
credentials_database_path: Default::default(),
|
||||
test_routes: DEFAULT_TEST_ROUTES,
|
||||
minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES,
|
||||
route_test_packets: DEFAULT_ROUTE_TEST_PACKETS,
|
||||
per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NodeStatusAPIV1_1_21 {
|
||||
database_path: PathBuf,
|
||||
#[serde(with = "humantime_serde")]
|
||||
caching_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for NodeStatusAPIV1_1_21 {
|
||||
fn default() -> Self {
|
||||
NodeStatusAPIV1_1_21 {
|
||||
database_path: Default::default(),
|
||||
caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct TopologyCacherV1_1_21 {
|
||||
#[serde(with = "humantime_serde")]
|
||||
caching_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for TopologyCacherV1_1_21 {
|
||||
fn default() -> Self {
|
||||
TopologyCacherV1_1_21 {
|
||||
caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CirculatingSupplyCacherV1_1_21 {
|
||||
enabled: bool,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
caching_interval: Duration,
|
||||
}
|
||||
|
||||
impl Default for CirculatingSupplyCacherV1_1_21 {
|
||||
fn default() -> Self {
|
||||
CirculatingSupplyCacherV1_1_21 {
|
||||
enabled: true,
|
||||
caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct RewardingV1_1_21 {
|
||||
enabled: bool,
|
||||
minimum_interval_monitor_threshold: u8,
|
||||
}
|
||||
|
||||
impl Default for RewardingV1_1_21 {
|
||||
fn default() -> Self {
|
||||
RewardingV1_1_21 {
|
||||
enabled: false,
|
||||
minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CoconutSignerV1_1_21 {
|
||||
enabled: bool,
|
||||
dkg_persistent_state_path: PathBuf,
|
||||
verification_key_path: PathBuf,
|
||||
secret_key_path: PathBuf,
|
||||
decryption_key_path: PathBuf,
|
||||
public_key_with_proof_path: PathBuf,
|
||||
dkg_contract_polling_rate: Duration,
|
||||
}
|
||||
|
||||
impl CoconutSignerV1_1_21 {
|
||||
pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json";
|
||||
|
||||
fn default_dkg_persistent_state_path(id: &str) -> PathBuf {
|
||||
ConfigV1_1_21::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CoconutSignerV1_1_21 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: Default::default(),
|
||||
dkg_persistent_state_path: Default::default(),
|
||||
verification_key_path: Default::default(),
|
||||
secret_key_path: Default::default(),
|
||||
decryption_key_path: Default::default(),
|
||||
public_key_with_proof_path: Default::default(),
|
||||
dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::config::persistence::{
|
||||
CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths,
|
||||
};
|
||||
use crate::support::config::{
|
||||
Base, CirculatingSupplyCacher, CoconutSigner, CoconutSignerDebug, Config, Ephemera,
|
||||
NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding,
|
||||
TopologyCacher,
|
||||
};
|
||||
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
|
||||
use nym_validator_client::nyxd;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use url::Url;
|
||||
|
||||
const DEFAULT_NYM_API_PORT: u16 = 8080;
|
||||
const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr";
|
||||
const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
|
||||
|
||||
const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657";
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigV1_1_27 {
|
||||
#[serde(default)]
|
||||
base: BaseV1_1_27,
|
||||
|
||||
#[serde(default)]
|
||||
network_monitor: NetworkMonitorV1_1_27,
|
||||
|
||||
#[serde(default)]
|
||||
node_status_api: NodeStatusAPIV1_1_27,
|
||||
|
||||
#[serde(default)]
|
||||
topology_cacher: TopologyCacher,
|
||||
|
||||
#[serde(default)]
|
||||
circulating_supply_cacher: CirculatingSupplyCacher,
|
||||
|
||||
#[serde(default)]
|
||||
rewarding: Rewarding,
|
||||
|
||||
#[serde(default)]
|
||||
coconut_signer: CoconutSignerV1_1_27,
|
||||
}
|
||||
|
||||
impl From<ConfigV1_1_27> for Config {
|
||||
fn from(value: ConfigV1_1_27) -> Self {
|
||||
// this value was never properly saved (probably a bug)
|
||||
// so explicitly set it to the default
|
||||
|
||||
Config {
|
||||
base: Base {
|
||||
id: value.base.id.clone(),
|
||||
local_validator: value.base.local_validator,
|
||||
mixnet_contract_address: value.base.mixnet_contract_address,
|
||||
vesting_contract_address: value.base.vesting_contract_address,
|
||||
mnemonic: value.base.mnemonic,
|
||||
storage_paths: Default::default(),
|
||||
},
|
||||
network_monitor: NetworkMonitor {
|
||||
enabled: value.network_monitor.enabled,
|
||||
storage_paths: NetworkMonitorPaths {
|
||||
credentials_database_path: value
|
||||
.network_monitor
|
||||
.storage_paths
|
||||
.credentials_database_path,
|
||||
},
|
||||
debug: NetworkMonitorDebug {
|
||||
min_mixnode_reliability: value.network_monitor.debug.min_mixnode_reliability,
|
||||
min_gateway_reliability: value.network_monitor.debug.min_gateway_reliability,
|
||||
disabled_credentials_mode: value
|
||||
.network_monitor
|
||||
.debug
|
||||
.disabled_credentials_mode,
|
||||
run_interval: value.network_monitor.debug.run_interval,
|
||||
gateway_ping_interval: value.network_monitor.debug.gateway_ping_interval,
|
||||
gateway_sending_rate: value.network_monitor.debug.gateway_sending_rate,
|
||||
max_concurrent_gateway_clients: value
|
||||
.network_monitor
|
||||
.debug
|
||||
.max_concurrent_gateway_clients,
|
||||
gateway_response_timeout: value.network_monitor.debug.gateway_response_timeout,
|
||||
gateway_connection_timeout: value
|
||||
.network_monitor
|
||||
.debug
|
||||
.gateway_connection_timeout,
|
||||
packet_delivery_timeout: value.network_monitor.debug.packet_delivery_timeout,
|
||||
test_routes: value.network_monitor.debug.test_routes,
|
||||
minimum_test_routes: value.network_monitor.debug.minimum_test_routes,
|
||||
route_test_packets: value.network_monitor.debug.route_test_packets,
|
||||
per_node_test_packets: value.network_monitor.debug.per_node_test_packets,
|
||||
},
|
||||
},
|
||||
node_status_api: NodeStatusAPI {
|
||||
storage_paths: NodeStatusAPIPaths {
|
||||
database_path: value.node_status_api.storage_paths.database_path,
|
||||
},
|
||||
debug: NodeStatusAPIDebug {
|
||||
caching_interval: value.node_status_api.debug.caching_interval,
|
||||
},
|
||||
},
|
||||
topology_cacher: value.topology_cacher,
|
||||
circulating_supply_cacher: value.circulating_supply_cacher,
|
||||
rewarding: value.rewarding,
|
||||
coconut_signer: CoconutSigner {
|
||||
enabled: value.coconut_signer.enabled,
|
||||
announce_address: value.base.announce_address,
|
||||
storage_paths: CoconutSignerPaths {
|
||||
dkg_persistent_state_path: value
|
||||
.coconut_signer
|
||||
.storage_paths
|
||||
.dkg_persistent_state_path,
|
||||
verification_key_path: value.coconut_signer.storage_paths.verification_key_path,
|
||||
secret_key_path: value.coconut_signer.storage_paths.secret_key_path,
|
||||
decryption_key_path: value.coconut_signer.storage_paths.decryption_key_path,
|
||||
public_key_with_proof_path: value
|
||||
.coconut_signer
|
||||
.storage_paths
|
||||
.public_key_with_proof_path,
|
||||
},
|
||||
debug: CoconutSignerDebug {
|
||||
dkg_contract_polling_rate: value.coconut_signer.debug.dkg_contract_polling_rate,
|
||||
},
|
||||
},
|
||||
ephemera: Ephemera::new_default(&value.base.id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MigrationNymConfig for ConfigV1_1_27 {
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("nym-api")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct BaseV1_1_27 {
|
||||
/// ID specifies the human readable ID of this particular nym-api.
|
||||
id: String,
|
||||
|
||||
local_validator: Url,
|
||||
|
||||
/// Address announced to the directory server for the clients to connect to.
|
||||
// It is useful, say, in NAT scenarios or wanting to more easily update actual IP address
|
||||
// later on by using name resolvable with a DNS query, such as `nymtech.net`.
|
||||
announce_address: Url,
|
||||
|
||||
/// Address of the validator contract managing the network
|
||||
mixnet_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Address of the vesting contract holding locked tokens
|
||||
vesting_contract_address: nyxd::AccountId,
|
||||
|
||||
/// Mnemonic used for rewarding and/or multisig operations
|
||||
mnemonic: bip39::Mnemonic,
|
||||
}
|
||||
|
||||
impl Default for BaseV1_1_27 {
|
||||
fn default() -> Self {
|
||||
let default_validator: Url = DEFAULT_LOCAL_VALIDATOR
|
||||
.parse()
|
||||
.expect("default local validator is malformed!");
|
||||
let mut default_announce_address = default_validator.clone();
|
||||
default_announce_address
|
||||
.set_port(Some(DEFAULT_NYM_API_PORT))
|
||||
.expect("default local validator is malformed!");
|
||||
|
||||
BaseV1_1_27 {
|
||||
id: String::default(),
|
||||
local_validator: default_validator,
|
||||
announce_address: default_announce_address,
|
||||
mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(),
|
||||
mnemonic: bip39::Mnemonic::generate(24).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NetworkMonitorV1_1_27 {
|
||||
/// Specifies whether network monitoring service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
|
||||
pub storage_paths: NetworkMonitorPaths,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: NetworkMonitorDebug,
|
||||
}
|
||||
|
||||
impl Default for NetworkMonitorV1_1_27 {
|
||||
fn default() -> Self {
|
||||
NetworkMonitorV1_1_27 {
|
||||
enabled: false,
|
||||
storage_paths: NetworkMonitorPaths {
|
||||
credentials_database_path: Default::default(),
|
||||
},
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct NodeStatusAPIV1_1_27 {
|
||||
// pub enabled: bool,
|
||||
pub storage_paths: NodeStatusAPIPaths,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: NodeStatusAPIDebug,
|
||||
}
|
||||
|
||||
impl Default for NodeStatusAPIV1_1_27 {
|
||||
fn default() -> Self {
|
||||
NodeStatusAPIV1_1_27 {
|
||||
storage_paths: NodeStatusAPIPaths {
|
||||
database_path: Default::default(),
|
||||
},
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct CoconutSignerV1_1_27 {
|
||||
/// Specifies whether rewarding service is enabled in this process.
|
||||
pub enabled: bool,
|
||||
|
||||
pub announce_address: Url,
|
||||
|
||||
pub storage_paths: CoconutSignerPaths,
|
||||
|
||||
#[serde(default)]
|
||||
pub debug: CoconutSignerDebug,
|
||||
}
|
||||
|
||||
impl Default for CoconutSignerV1_1_27 {
|
||||
fn default() -> Self {
|
||||
let announce_address: Url = DEFAULT_LOCAL_VALIDATOR
|
||||
.parse()
|
||||
.expect("default local validator is malformed!");
|
||||
CoconutSignerV1_1_27 {
|
||||
enabled: Default::default(),
|
||||
announce_address,
|
||||
storage_paths: CoconutSignerPaths {
|
||||
dkg_persistent_state_path: Default::default(),
|
||||
verification_key_path: Default::default(),
|
||||
secret_key_path: Default::default(),
|
||||
decryption_key_path: Default::default(),
|
||||
public_key_with_proof_path: Default::default(),
|
||||
},
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::cli::{init, run};
|
||||
|
||||
// Configuration that can be overridden.
|
||||
pub(crate) struct OverrideConfig {
|
||||
/// Specifies whether network monitoring is enabled on this API
|
||||
pub(crate) enable_monitor: Option<bool>,
|
||||
|
||||
/// Specifies whether network rewarding is enabled on this API
|
||||
pub(crate) enable_rewarding: Option<bool>,
|
||||
|
||||
/// Endpoint to nyxd instance used for contract information.
|
||||
pub(crate) nyxd_validator: Option<url::Url>,
|
||||
|
||||
/// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions
|
||||
pub(crate) mnemonic: Option<bip39::Mnemonic>,
|
||||
|
||||
/// Flag to indicate whether coconut signer authority is enabled on this API
|
||||
pub(crate) enable_zk_nym: Option<bool>,
|
||||
|
||||
/// Announced address that is going to be put in the DKG contract where zk-nym clients will connect
|
||||
/// to obtain their credentials
|
||||
pub(crate) announce_address: Option<url::Url>,
|
||||
|
||||
/// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement
|
||||
pub(crate) monitor_credentials_mode: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<init::Args> for OverrideConfig {
|
||||
fn from(args: init::Args) -> Self {
|
||||
OverrideConfig {
|
||||
enable_monitor: Some(args.enable_monitor),
|
||||
enable_rewarding: Some(args.enable_rewarding),
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: Some(args.enable_zk_nym),
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: Some(args.monitor_credentials_mode),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<run::Args> for OverrideConfig {
|
||||
fn from(args: run::Args) -> Self {
|
||||
OverrideConfig {
|
||||
enable_monitor: args.enable_monitor,
|
||||
enable_rewarding: args.enable_rewarding,
|
||||
nyxd_validator: args.nyxd_validator,
|
||||
mnemonic: args.mnemonic,
|
||||
enable_zk_nym: args.enable_zk_nym,
|
||||
announce_address: args.announce_address,
|
||||
monitor_credentials_mode: args.monitor_credentials_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,8 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::support::config::default_data_directory;
|
||||
use anyhow::{anyhow, Context};
|
||||
use nym_config::serde_helpers::de_maybe_stringified;
|
||||
use anyhow::Context;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use rand_07::rngs::OsRng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -111,12 +109,10 @@ impl CoconutSignerPaths {
|
||||
#[serde(default)]
|
||||
pub struct NymApiPaths {
|
||||
/// Path to file containing private identity key of the nym-api.
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub private_identity_key_file: Option<PathBuf>,
|
||||
pub private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key of the nym-api.
|
||||
#[serde(deserialize_with = "de_maybe_stringified")]
|
||||
pub public_identity_key_file: Option<PathBuf>,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
}
|
||||
|
||||
impl NymApiPaths {
|
||||
@@ -124,48 +120,19 @@ impl NymApiPaths {
|
||||
let data_dir = default_data_directory(id);
|
||||
|
||||
NymApiPaths {
|
||||
private_identity_key_file: Some(data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME)),
|
||||
public_identity_key_file: Some(data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_identity_if_missing<P: AsRef<Path>>(&mut self, id: P) -> anyhow::Result<bool> {
|
||||
if self.private_identity_key_file.is_none() {
|
||||
log::warn!("identity key paths are not set - going to generate a fresh pair!");
|
||||
|
||||
let data_dir = default_data_directory(id);
|
||||
self.private_identity_key_file =
|
||||
Some(data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME));
|
||||
self.public_identity_key_file =
|
||||
Some(data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME));
|
||||
|
||||
let keypaths = nym_pemstore::KeyPairPath::new(
|
||||
self.private_identity_key_file.as_ref().unwrap(),
|
||||
self.public_identity_key_file.as_ref().unwrap(),
|
||||
);
|
||||
|
||||
let mut rng = OsRng;
|
||||
let keypair = identity::KeyPair::new(&mut rng);
|
||||
|
||||
nym_pemstore::store_keypair(&keypair, &keypaths)
|
||||
.context("failed to store identity keys of the nym api")?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME),
|
||||
public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_identity(&self) -> anyhow::Result<identity::KeyPair> {
|
||||
// if somebody has set their private key but removed public key, the panic is totally on them.
|
||||
let keypaths = nym_pemstore::KeyPairPath::new(
|
||||
self.private_identity_key_file
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("private key path is not specified"))?,
|
||||
self.public_identity_key_file
|
||||
.as_ref()
|
||||
.ok_or(anyhow!("public key path is not specified"))?,
|
||||
&self.private_identity_key_file,
|
||||
&self.public_identity_key_file,
|
||||
);
|
||||
|
||||
nym_pemstore::load_keypair(&keypaths).context("failed to load identity keys of the nym api")
|
||||
nym_pemstore::load_keypair(&keypaths).context(format!(
|
||||
"failed to load identity keys of the nym api. paths: {keypaths:?}"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,15 @@ id = '{{ base.id }}'
|
||||
# Validator server to which the API will be getting information about the network.
|
||||
local_validator = '{{ base.local_validator }}'
|
||||
|
||||
# Address of the validator contract managing the network.
|
||||
mixnet_contract_address = '{{ base.mixnet_contract_address }}'
|
||||
|
||||
# Address of the vesting contract holding locked tokens
|
||||
vesting_contract_address = '{{ base.vesting_contract_address }}'
|
||||
|
||||
# Mnemonic used for rewarding and validator interaction
|
||||
mnemonic = '{{ base.mnemonic }}'
|
||||
|
||||
[base.storage_paths]
|
||||
# Path to file containing private identity key of the nym-api.
|
||||
keys.private_identity_key_file = '{{ base.storage_paths.private_identity_key_file }}'
|
||||
private_identity_key_file = '{{ base.storage_paths.private_identity_key_file }}'
|
||||
|
||||
# Path to file containing public identity key of the nym-api.
|
||||
keys.public_identity_key_file = '{{ base.storage_paths.public_identity_key_file }}'
|
||||
public_identity_key_file = '{{ base.storage_paths.public_identity_key_file }}'
|
||||
|
||||
##### network monitor config options #####
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
pub(crate) fn try_upgrade_config(_id: &str) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::coconut::client::Client;
|
||||
use crate::coconut::{self, comm::QueryCommunicationChannel};
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::network::network_routes;
|
||||
@@ -12,8 +13,9 @@ use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::{nyxd, storage};
|
||||
use crate::{circulating_supply_api, nym_contract_cache, nym_nodes::nym_node_routes};
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Result};
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use rocket::http::Method;
|
||||
use rocket::{Ignite, Rocket};
|
||||
use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors};
|
||||
@@ -67,6 +69,14 @@ pub(crate) async fn setup_rocket(
|
||||
};
|
||||
|
||||
let rocket = if config.coconut_signer.enabled {
|
||||
// make sure we have some tokens to cover multisig fees
|
||||
let balance = _nyxd_client.balance(&mix_denom).await?;
|
||||
if balance.amount < coconut::MINIMUM_BALANCE {
|
||||
let address = _nyxd_client.address().await;
|
||||
let min = Coin::new(coconut::MINIMUM_BALANCE, mix_denom);
|
||||
bail!("the account ({address}) doesn't have enough funds to cover verification fees. it has {balance} while it needs at least {min}")
|
||||
}
|
||||
|
||||
let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone());
|
||||
rocket.attach(coconut::stage(
|
||||
_nyxd_client.clone(),
|
||||
|
||||
+173
-141
@@ -16,7 +16,7 @@ use nym_coconut_dkg_common::{
|
||||
types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId},
|
||||
verification_key::{ContractVKShare, VerificationKeyShare},
|
||||
};
|
||||
use nym_config::defaults::ChainDetails;
|
||||
use nym_config::defaults::{ChainDetails, NymNetworkDetails};
|
||||
use nym_contracts_common::dealings::ContractSafeBytes;
|
||||
use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg;
|
||||
use nym_ephemera_common::types::JsonPeerInfo;
|
||||
@@ -46,61 +46,122 @@ use nym_validator_client::nyxd::{
|
||||
hash::{Hash, SHA256_HASH_SIZE},
|
||||
AccountId, Coin, TendermintTime,
|
||||
};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
|
||||
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient};
|
||||
use nym_vesting_contract_common::AccountVestingCoins;
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{RwLock, RwLockReadGuard};
|
||||
|
||||
pub(crate) struct Client(pub(crate) Arc<RwLock<DirectSigningHttpRpcNyxdClient>>);
|
||||
#[macro_export]
|
||||
macro_rules! query_guard {
|
||||
($guard:expr, $($op:tt)*) => {{
|
||||
match &*$guard {
|
||||
$crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*,
|
||||
$crate::support::nyxd::ClientInner::Query(client) => client.$($op)*,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
impl Clone for Client {
|
||||
fn clone(&self) -> Self {
|
||||
Client(Arc::clone(&self.0))
|
||||
}
|
||||
macro_rules! nyxd_query {
|
||||
($self:expr, $($op:tt)*) => {{
|
||||
let guard = $self.inner.read().await;
|
||||
match &*guard {
|
||||
$crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*,
|
||||
$crate::support::nyxd::ClientInner::Query(client) => client.$($op)*,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! nyxd_signing_shared {
|
||||
($self:expr, $($op:tt)*) => {{
|
||||
let guard = $self.inner.read().await;
|
||||
match &*guard {
|
||||
$crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*,
|
||||
$crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! nyxd_signing {
|
||||
($self:expr, $($op:tt)*) => {{
|
||||
let guard = $self.inner.write().await;
|
||||
match &*guard {
|
||||
$crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*,
|
||||
$crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"),
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Client {
|
||||
inner: Arc<RwLock<ClientInner>>,
|
||||
}
|
||||
|
||||
pub enum ClientInner {
|
||||
Signing(DirectSigningHttpRpcNyxdClient),
|
||||
Query(QueryHttpRpcNyxdClient),
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub(crate) fn new(config: &Config) -> Self {
|
||||
let details = config.get_network_details();
|
||||
let details = NymNetworkDetails::new_from_env();
|
||||
let nyxd_url = config.get_nyxd_url();
|
||||
|
||||
let client_config = nyxd::Config::try_from_nym_network_details(&details)
|
||||
.expect("failed to construct valid validator client config with the provided network");
|
||||
|
||||
let mnemonic = config.get_mnemonic();
|
||||
let inner = if let Some(mnemonic) = config.get_mnemonic() {
|
||||
ClientInner::Signing(
|
||||
DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
|
||||
client_config,
|
||||
nyxd_url.as_str(),
|
||||
mnemonic.clone(),
|
||||
)
|
||||
.expect("Failed to connect to nyxd!"),
|
||||
)
|
||||
} else {
|
||||
ClientInner::Query(
|
||||
QueryHttpRpcNyxdClient::connect(client_config, nyxd_url.as_str())
|
||||
.expect("Failed to connect to nyxd!"),
|
||||
)
|
||||
};
|
||||
|
||||
let inner = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
|
||||
client_config,
|
||||
nyxd_url.as_str(),
|
||||
mnemonic,
|
||||
)
|
||||
.expect("Failed to connect to nyxd!");
|
||||
|
||||
Client(Arc::new(RwLock::new(inner)))
|
||||
Client {
|
||||
inner: Arc::new(RwLock::new(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn read(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> {
|
||||
self.0.read().await
|
||||
pub(crate) async fn read(&self) -> RwLockReadGuard<'_, ClientInner> {
|
||||
self.inner.read().await
|
||||
}
|
||||
|
||||
pub(crate) async fn client_address(&self) -> AccountId {
|
||||
self.0.read().await.address()
|
||||
nyxd_signing_shared!(self, address())
|
||||
}
|
||||
|
||||
pub(crate) async fn balance<S: Into<String>>(&self, denom: S) -> Result<Coin, NyxdError> {
|
||||
let address = self.client_address().await;
|
||||
let denom = denom.into();
|
||||
let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?);
|
||||
|
||||
match balance {
|
||||
None => Ok(Coin::new(0, denom)),
|
||||
Some(coin) => Ok(coin),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn chain_details(&self) -> ChainDetails {
|
||||
self.0.read().await.current_chain_details().clone()
|
||||
nyxd_query!(self, current_chain_details().clone())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_rewarding_validator_address(&self) -> Result<AccountId, NyxdError> {
|
||||
let cosmwasm_addr = self
|
||||
.0
|
||||
.read()
|
||||
.await
|
||||
.get_mixnet_contract_state()
|
||||
.await?
|
||||
.rewarding_validator_address
|
||||
.into_string();
|
||||
let cosmwasm_addr = nyxd_query!(
|
||||
self,
|
||||
get_mixnet_contract_state()
|
||||
.await?
|
||||
.rewarding_validator_address
|
||||
.into_string()
|
||||
);
|
||||
|
||||
// this should never fail otherwise it implies either
|
||||
// 1) our mixnet contract state is invalid
|
||||
@@ -116,7 +177,7 @@ impl Client {
|
||||
// a helper function for the future to obtain the current block timestamp
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn current_block_timestamp(&self) -> Result<TendermintTime, NyxdError> {
|
||||
let time = self.0.read().await.get_current_block_timestamp().await?;
|
||||
let time = nyxd_query!(self, get_current_block_timestamp().await?);
|
||||
|
||||
Ok(time)
|
||||
}
|
||||
@@ -132,7 +193,7 @@ impl Client {
|
||||
&self,
|
||||
height: u32,
|
||||
) -> Result<Option<[u8; SHA256_HASH_SIZE]>, NyxdError> {
|
||||
let hash = match self.0.read().await.get_block_hash(height).await? {
|
||||
let hash = match nyxd_query!(self, get_block_hash(height).await?) {
|
||||
Hash::Sha256(hash) => Some(hash),
|
||||
Hash::None => None,
|
||||
};
|
||||
@@ -141,44 +202,46 @@ impl Client {
|
||||
}
|
||||
|
||||
pub(crate) async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NyxdError> {
|
||||
self.0.read().await.get_all_mixnodes_detailed().await
|
||||
nyxd_query!(self, get_all_mixnodes_detailed().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NyxdError> {
|
||||
self.0.read().await.get_all_gateways().await
|
||||
nyxd_query!(self, get_all_gateways().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_interval(&self) -> Result<CurrentIntervalResponse, NyxdError> {
|
||||
self.0.read().await.get_current_interval_details().await
|
||||
nyxd_query!(self, get_current_interval_details().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_epoch_status(&self) -> Result<EpochStatus, NyxdError> {
|
||||
self.0.read().await.get_current_epoch_status().await
|
||||
nyxd_query!(self, get_current_epoch_status().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_rewarding_parameters(
|
||||
&self,
|
||||
) -> Result<RewardingParams, NyxdError> {
|
||||
self.0.read().await.get_rewarding_parameters().await
|
||||
nyxd_query!(self, get_rewarding_parameters().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_rewarded_set_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, NyxdError> {
|
||||
self.0.read().await.get_all_rewarded_set_mixnodes().await
|
||||
nyxd_query!(self, get_all_rewarded_set_mixnodes().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_current_vesting_account_storage_key(&self) -> Result<u32, NyxdError> {
|
||||
let guard = self.0.read().await;
|
||||
let guard = self.inner.read().await;
|
||||
|
||||
// the expect is fine as we always construct the client with the vesting contract explicitly set
|
||||
let vesting_contract = guard
|
||||
.vesting_contract_address()
|
||||
.expect("vesting contract address is not available");
|
||||
let vesting_contract = query_guard!(
|
||||
guard,
|
||||
vesting_contract_address().expect("vesting contract address is not available")
|
||||
);
|
||||
// TODO: I don't like the usage of the hardcoded value here
|
||||
let res = guard
|
||||
.query_contract_raw(vesting_contract, b"key".to_vec())
|
||||
.await?;
|
||||
let res = query_guard!(
|
||||
guard,
|
||||
query_contract_raw(vesting_contract, b"key".to_vec()).await?
|
||||
);
|
||||
if res.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
@@ -189,22 +252,22 @@ impl Client {
|
||||
pub(crate) async fn get_all_vesting_coins(
|
||||
&self,
|
||||
) -> Result<Vec<AccountVestingCoins>, NyxdError> {
|
||||
self.0.read().await.get_all_accounts_vesting_coins().await
|
||||
nyxd_query!(self, get_all_accounts_vesting_coins().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_all_family_members(
|
||||
&self,
|
||||
) -> Result<Vec<(IdentityKey, FamilyHead)>, NyxdError> {
|
||||
self.0.read().await.get_all_family_members().await
|
||||
nyxd_query!(self, get_all_family_members().await)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_pending_events_count(&self) -> Result<u32, NyxdError> {
|
||||
let pending = self.0.read().await.get_number_of_pending_events().await?;
|
||||
let pending = nyxd_query!(self, get_number_of_pending_events().await?);
|
||||
Ok(pending.epoch_events + pending.interval_events)
|
||||
}
|
||||
|
||||
pub(crate) async fn begin_epoch_transition(&self) -> Result<(), NyxdError> {
|
||||
self.0.write().await.begin_epoch_transition(None).await?;
|
||||
nyxd_signing!(self, begin_epoch_transition(None).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -231,26 +294,29 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
// "technically" we don't need a write access to the client,
|
||||
// but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages
|
||||
// as that would have messed up sequence numbers
|
||||
let guard = self.0.write().await;
|
||||
|
||||
// the expect is fine as we always construct the client with the mixnet contract explicitly set
|
||||
let mixnet_contract = guard
|
||||
.mixnet_contract_address()
|
||||
.expect("mixnet contract address is not available");
|
||||
let mixnet_contract = nyxd_query!(
|
||||
self,
|
||||
mixnet_contract_address()
|
||||
.expect("mixnet contract address is not available")
|
||||
.clone()
|
||||
);
|
||||
|
||||
let msgs = generate_reward_messages(nodes);
|
||||
|
||||
guard
|
||||
.execute_multiple(
|
||||
mixnet_contract,
|
||||
// "technically" we don't need a write access to the client,
|
||||
// but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages
|
||||
// as that would have messed up sequence numbers
|
||||
nyxd_signing!(
|
||||
self,
|
||||
execute_multiple(
|
||||
&mixnet_contract,
|
||||
msgs,
|
||||
Default::default(),
|
||||
format!("rewarding {} mixnodes", nodes.len()),
|
||||
)
|
||||
.await?;
|
||||
.await?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -259,20 +325,15 @@ impl Client {
|
||||
new_rewarded_set: Vec<LayerAssignment>,
|
||||
expected_active_set_size: u32,
|
||||
) -> Result<(), NyxdError> {
|
||||
self.0
|
||||
.write()
|
||||
.await
|
||||
.advance_current_epoch(new_rewarded_set, expected_active_set_size, None)
|
||||
.await?;
|
||||
nyxd_signing!(
|
||||
self,
|
||||
advance_current_epoch(new_rewarded_set, expected_active_set_size, None).await?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn reconcile_epoch_events(&self, limit: Option<u32>) -> Result<(), NyxdError> {
|
||||
self.0
|
||||
.write()
|
||||
.await
|
||||
.reconcile_epoch_events(limit, None)
|
||||
.await?;
|
||||
nyxd_signing!(self, reconcile_epoch_events(limit, None).await?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -284,7 +345,7 @@ impl crate::coconut::client::Client for Client {
|
||||
}
|
||||
|
||||
async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result<nyxd::TxResponse> {
|
||||
self.0.read().await.get_tx(tx_hash).await.map_err(|source| {
|
||||
nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| {
|
||||
CoconutError::TxRetrievalFailure {
|
||||
tx_hash: tx_hash.to_string(),
|
||||
source,
|
||||
@@ -296,78 +357,69 @@ impl crate::coconut::client::Client for Client {
|
||||
&self,
|
||||
proposal_id: u64,
|
||||
) -> crate::coconut::error::Result<ProposalResponse> {
|
||||
Ok(self.0.read().await.query_proposal(proposal_id).await?)
|
||||
Ok(nyxd_query!(self, query_proposal(proposal_id).await?))
|
||||
}
|
||||
|
||||
async fn list_proposals(&self) -> crate::coconut::error::Result<Vec<ProposalResponse>> {
|
||||
Ok(self.0.read().await.get_all_proposals().await?)
|
||||
Ok(nyxd_query!(self, get_all_proposals().await?))
|
||||
}
|
||||
|
||||
async fn get_spent_credential(
|
||||
&self,
|
||||
blinded_serial_number: String,
|
||||
) -> crate::coconut::error::Result<SpendCredentialResponse> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.await
|
||||
.get_spent_credential(blinded_serial_number)
|
||||
.await?)
|
||||
Ok(nyxd_query!(
|
||||
self,
|
||||
get_spent_credential(blinded_serial_number).await?
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_current_epoch(&self) -> crate::coconut::error::Result<Epoch> {
|
||||
Ok(self.0.read().await.get_current_epoch().await?)
|
||||
Ok(nyxd_query!(self, get_current_epoch().await?))
|
||||
}
|
||||
|
||||
async fn group_member(&self, addr: String) -> crate::coconut::error::Result<MemberResponse> {
|
||||
Ok(self.0.read().await.member(addr, None).await?)
|
||||
Ok(nyxd_query!(self, member(addr, None).await?))
|
||||
}
|
||||
|
||||
async fn get_current_epoch_threshold(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<Option<nym_dkg::Threshold>> {
|
||||
Ok(self.0.read().await.get_current_epoch_threshold().await?)
|
||||
Ok(nyxd_query!(self, get_current_epoch_threshold().await?))
|
||||
}
|
||||
|
||||
async fn get_initial_dealers(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<Option<InitialReplacementData>> {
|
||||
Ok(self.0.read().await.get_initial_dealers().await?)
|
||||
Ok(nyxd_query!(self, get_initial_dealers().await?))
|
||||
}
|
||||
|
||||
async fn get_self_registered_dealer_details(
|
||||
&self,
|
||||
) -> crate::coconut::error::Result<DealerDetailsResponse> {
|
||||
let self_address = &self.address().await;
|
||||
Ok(self.0.read().await.get_dealer_details(self_address).await?)
|
||||
Ok(nyxd_query!(self, get_dealer_details(self_address).await?))
|
||||
}
|
||||
|
||||
async fn get_current_dealers(&self) -> crate::coconut::error::Result<Vec<DealerDetails>> {
|
||||
Ok(self.0.read().await.get_all_current_dealers().await?)
|
||||
Ok(nyxd_query!(self, get_all_current_dealers().await?))
|
||||
}
|
||||
|
||||
async fn get_dealings(
|
||||
&self,
|
||||
idx: usize,
|
||||
) -> crate::coconut::error::Result<Vec<ContractDealing>> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.await
|
||||
.get_all_epoch_dealings(idx as u64)
|
||||
.await?)
|
||||
Ok(nyxd_query!(self, get_all_epoch_dealings(idx as u64).await?))
|
||||
}
|
||||
|
||||
async fn get_verification_key_shares(
|
||||
&self,
|
||||
epoch_id: EpochId,
|
||||
) -> crate::coconut::error::Result<Vec<ContractVKShare>> {
|
||||
Ok(self
|
||||
.0
|
||||
.read()
|
||||
.await
|
||||
.get_all_verification_key_shares(epoch_id)
|
||||
.await?)
|
||||
Ok(nyxd_query!(
|
||||
self,
|
||||
get_all_verification_key_shares(epoch_id).await?
|
||||
))
|
||||
}
|
||||
|
||||
async fn vote_proposal(
|
||||
@@ -376,25 +428,17 @@ impl crate::coconut::client::Client for Client {
|
||||
vote_yes: bool,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<(), CoconutError> {
|
||||
self.0
|
||||
.write()
|
||||
.await
|
||||
.vote_proposal(proposal_id, vote_yes, fee)
|
||||
.await?;
|
||||
nyxd_signing!(self, vote_proposal(proposal_id, vote_yes, fee).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn execute_proposal(&self, proposal_id: u64) -> crate::coconut::error::Result<()> {
|
||||
self.0
|
||||
.write()
|
||||
.await
|
||||
.execute_proposal(proposal_id, None)
|
||||
.await?;
|
||||
nyxd_signing!(self, execute_proposal(proposal_id, None).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> {
|
||||
self.0.write().await.advance_dkg_epoch_state(None).await?;
|
||||
nyxd_signing!(self, advance_dkg_epoch_state(None).await?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -404,12 +448,10 @@ impl crate::coconut::client::Client for Client {
|
||||
announce_address: String,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.register_dealer(bte_key, announce_address, resharing, None)
|
||||
.await?)
|
||||
Ok(nyxd_signing!(
|
||||
self,
|
||||
register_dealer(bte_key, announce_address, resharing, None).await?
|
||||
))
|
||||
}
|
||||
|
||||
async fn submit_dealing(
|
||||
@@ -417,12 +459,10 @@ impl crate::coconut::client::Client for Client {
|
||||
dealing_bytes: ContractSafeBytes,
|
||||
resharing: bool,
|
||||
) -> Result<ExecuteResult, CoconutError> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.submit_dealing_bytes(dealing_bytes, resharing, None)
|
||||
.await?)
|
||||
Ok(nyxd_signing!(
|
||||
self,
|
||||
submit_dealing_bytes(dealing_bytes, resharing, None).await?
|
||||
))
|
||||
}
|
||||
|
||||
async fn submit_verification_key_share(
|
||||
@@ -430,31 +470,27 @@ impl crate::coconut::client::Client for Client {
|
||||
share: VerificationKeyShare,
|
||||
resharing: bool,
|
||||
) -> crate::coconut::error::Result<ExecuteResult> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.submit_verification_key_share(share, resharing, None)
|
||||
.await?)
|
||||
Ok(nyxd_signing!(
|
||||
self,
|
||||
submit_verification_key_share(share, resharing, None).await?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::ephemera::client::Client for Client {
|
||||
async fn get_ephemera_peers(&self) -> crate::ephemera::error::Result<Vec<JsonPeerInfo>> {
|
||||
Ok(self.0.read().await.get_all_ephemera_peers().await?)
|
||||
Ok(nyxd_query!(self, get_all_ephemera_peers().await?))
|
||||
}
|
||||
|
||||
async fn register_ephemera_peer(
|
||||
&self,
|
||||
peer_info: JsonPeerInfo,
|
||||
) -> crate::ephemera::error::Result<ExecuteResult> {
|
||||
Ok(self
|
||||
.0
|
||||
.write()
|
||||
.await
|
||||
.register_as_peer(peer_info, None)
|
||||
.await?)
|
||||
Ok(nyxd_signing!(
|
||||
self,
|
||||
register_as_peer(peer_info, None).await?
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +500,7 @@ impl DkgQueryClient for Client {
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.0.read().await.query_dkg_contract(query).await
|
||||
nyxd_query!(self, query_dkg_contract(query).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,7 +513,7 @@ impl EphemeraQueryClient for Client {
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.0.read().await.query_ephemera_contract(query).await
|
||||
nyxd_query!(self, query_ephemera_contract(query).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,11 +526,7 @@ impl SpDirectoryQueryClient for Client {
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.0
|
||||
.read()
|
||||
.await
|
||||
.query_service_provider_contract(query)
|
||||
.await
|
||||
nyxd_query!(self, query_service_provider_contract(query).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,6 +539,6 @@ impl NameServiceQueryClient for Client {
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.0.read().await.query_name_service_contract(query).await
|
||||
nyxd_query!(self, query_name_service_contract(query).await)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ContractCache from "../../../src/endpoints/ContractCache";
|
||||
import ConfigHandler from "../../../src/config/configHandler";
|
||||
import ConfigHandler from "../../../../../common/api-test-utils/config/configHandler"
|
||||
|
||||
let contract: ContractCache;
|
||||
let config: ConfigHandler;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user