Compare commits

...

56 Commits

Author SHA1 Message Date
Tommy Verrall 8d8d436bf0 Update sandbox.env 2024-01-16 12:24:28 +01:00
Jędrzej Stuczyński f4e42d74c4 Merge pull request #4326 from nymtech/bugfix/nymvisor-windows-ci
Bugfix/nymvisor windows ci
2024-01-16 11:36:47 +01:00
Pierre Dommerc 9de1e6e844 chore: remove nym-vpn projects (#4327) 2024-01-16 11:10:26 +01:00
Tommy Verrall 713df39106 Merge pull request #4324 from nymtech/feature/bity-sell
Update text on buy page to Buy/Sell
2024-01-15 16:55:18 +01:00
Jędrzej Stuczyński 5ec4674f9b even more target locking 2024-01-15 15:45:19 +00:00
Jędrzej Stuczyński 58c5092e80 added a dummy main for non unix targets 2024-01-15 15:18:52 +00:00
Jędrzej Stuczyński 677ad54a7f used global unix cfg 2024-01-15 14:54:49 +00:00
fmtabbara 5e17c3199f update text on buy page to buy/sell 2024-01-15 14:32:39 +00:00
Tommy Verrall 3c3a34ec0f Merge pull request #4322 from nymtech/update-localnet-script
Check that localnet is running fromt the scripts dir
2024-01-15 13:43:27 +01:00
Tommy Verrall e9b442e634 Merge pull request #4303 from nymtech/dependabot/npm_and_yarn/follow-redirects-1.15.4
Bump follow-redirects from 1.15.3 to 1.15.4
2024-01-15 13:42:55 +01:00
Tommy Verrall ca02e2bce1 Merge pull request #4310 from nymtech/dependabot/npm_and_yarn/wasm/node-tester/internal-dev/follow-redirects-1.15.4
Bump follow-redirects from 1.15.2 to 1.15.4 in /wasm/node-tester/internal-dev
2024-01-15 13:42:36 +01:00
Tommy Verrall 985ab43fe9 Merge pull request #4306 from nymtech/dependabot/npm_and_yarn/clients/native/examples/js-examples/websocket/follow-redirects-1.15.4
Bump follow-redirects from 1.14.9 to 1.15.4 in /clients/native/examples/js-examples/websocket
2024-01-15 13:42:12 +01:00
dependabot[bot] f4dad37b14 Bump follow-redirects from 1.15.2 to 1.15.4 in /wasm/client/internal-dev (#4311)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

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

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

* docs: nymvpn guide, testing, troubleshooting and faq

* add faq

* remove todo points

* resolve review comments

* change landing page order

* incorporate huxis user feedback

* add binaries link

* change menu naming -> upper case

* final version for cryptotalk demo

* change naming convention client -> cli
2024-01-11 16:34:25 +00:00
Jon Häggblad d3d5cc3424 Don't build wg deps where it's not supported (#4305)
* Don't build wg deps where it's not supported

* Fix compilation

* Another fix

* More fixes

* another fix
2024-01-11 13:53:57 +01:00
Pierre Dommerc a834bb17f8 chore(vpn-desktop): bump version 0.0.2 (#4314)
* bump version

* add nym-vpn html icon
2024-01-11 12:18:33 +01:00
Zane Schepke cee6d8c308 Merge pull request #4298 from nymtech/fix/vpndesktop_ui_divergences
fix(vpn-desktop-ui): fix design divergences
2024-01-11 06:03:54 -05:00
Zane Schepke 142eaf533b fix macos artifact 2024-01-11 06:01:37 -05:00
pierre 9fc822298f fix overflow padding
fix settings screens
remove spin animation on connect button
use cursor progress
add select-none on some elements
2024-01-11 06:56:01 +01:00
dependabot[bot] cfb9f3d356 Bump follow-redirects in /wasm/mix-fetch/internal-dev
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 23:47:41 +00:00
pierre 75221cfd3e wip 2024-01-10 18:25:35 +01:00
pierre 50f71a21e0 fix various design divergences 2024-01-10 18:16:55 +01:00
pierre cfa9ecfcc4 fix various design divergences 2024-01-10 17:52:10 +01:00
dependabot[bot] d0fa1792e2 Bump follow-redirects in /clients/native/examples/js-examples/websocket
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.9 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.9...v1.15.4)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-09 09:32:28 +00:00
pierre 28f118c73b provide lato font 2024-01-08 17:22:25 +01:00
durch 3126053cbe Add three measurements grace period 2023-12-14 14:50:53 +01:00
durch 54266fd5df Only use good nodes for test route construction 2023-12-13 16:39:21 +01:00
569 changed files with 4383 additions and 23514 deletions
-40
View File
@@ -1,40 +0,0 @@
name: ci-nym-vpn-ui-js
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src/**'
- 'nym-vpn/ui/package.json'
- 'nym-vpn/ui/index.html'
jobs:
check:
runs-on: custom-linux
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Node
uses: actions/setup-node@v3
with:
node-version: 18
- name: Install Yarn
run: npm install -g yarn
- name: Install dependencies
working-directory: nym-vpn/ui
run: yarn
- name: Type-check
working-directory: nym-vpn/ui
run: yarn typecheck
- name: Check lint
working-directory: nym-vpn/ui
run: yarn lint
- name: Check formatting
working-directory: nym-vpn/ui
run: yarn fmt:check
# - name: Run tests
# working-directory: nym-vpn/ui
# run: yarn test
- name: Check build
working-directory: nym-vpn/ui
run: yarn build
-63
View File
@@ -1,63 +0,0 @@
name: ci-nym-vpn-ui-rust
on:
workflow_dispatch:
pull_request:
paths:
- 'nym-vpn/ui/src-tauri/**'
jobs:
build:
runs-on: custom-linux
env:
CARGO_TERM_COLOR: always
CARGOTOML_PATH: ./nym-vpn/ui/src-tauri/Cargo.toml
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools libayatana-appindicator3-dev
continue-on-error: true
- name: Checkout
uses: actions/checkout@v4
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Prepare build
run: mkdir nym-vpn/ui/dist
- name: Build
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path ${{ env.CARGOTOML_PATH }} --features custom-protocol
# - name: Run all tests
# uses: actions-rs/cargo@v1
# with:
# command: test
# args: --manifest-path ${{ env.CARGOTOML_PATH }}
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all -- --check
- name: Annotate with clippy checks
uses: actions-rs/clippy-check@v1
continue-on-error: true
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path ${{ env.CARGOTOML_PATH }} --all-features --all-targets -- -D warnings
Generated
+149 -37
View File
@@ -1649,6 +1649,16 @@ dependencies = [
"tendermint-proto",
]
[[package]]
name = "cosmos-sdk-proto"
version = "0.20.0"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#41ed4631e146268b0300033c8bbb993d79a49f58"
dependencies = [
"prost 0.12.1",
"prost-types 0.12.1",
"tendermint-proto",
]
[[package]]
name = "cosmrs"
version = "0.15.0"
@@ -1656,7 +1666,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea"
dependencies = [
"bip32",
"cosmos-sdk-proto",
"cosmos-sdk-proto 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ecdsa 0.16.8",
"eyre",
"k256",
"rand_core 0.6.4",
"serde",
"serde_json",
"signature 2.1.0",
"subtle-encoding",
"tendermint",
"thiserror",
]
[[package]]
name = "cosmrs"
version = "0.15.0"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#41ed4631e146268b0300033c8bbb993d79a49f58"
dependencies = [
"bip32",
"cosmos-sdk-proto 0.20.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"ecdsa 0.16.8",
"eyre",
"k256",
@@ -2212,6 +2241,16 @@ dependencies = [
"darling_macro 0.14.4",
]
[[package]]
name = "darling"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e"
dependencies = [
"darling_core 0.20.3",
"darling_macro 0.20.3",
]
[[package]]
name = "darling_core"
version = "0.13.4"
@@ -2240,6 +2279,20 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_core"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.38",
]
[[package]]
name = "darling_macro"
version = "0.13.4"
@@ -2262,6 +2315,17 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "darling_macro"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
dependencies = [
"darling_core 0.20.3",
"quote",
"syn 2.0.38",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@@ -2771,26 +2835,6 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "enum-iterator"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -5870,6 +5914,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
dependencies = [
"libc",
]
[[package]]
name = "nym-api"
version = "1.1.34"
@@ -5956,7 +6009,7 @@ name = "nym-api-requests"
version = "0.1.0"
dependencies = [
"bs58 0.4.0",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"getset",
"nym-coconut-interface",
@@ -6014,7 +6067,7 @@ name = "nym-bity-integration"
version = "0.1.0"
dependencies = [
"anyhow",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"eyre",
"k256",
"nym-cli-commands",
@@ -6059,7 +6112,7 @@ dependencies = [
"cfg-if",
"clap 4.4.7",
"comfy-table",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"cw-utils",
"handlebars",
@@ -6328,7 +6381,7 @@ name = "nym-credentials"
version = "0.1.0"
dependencies = [
"bls12_381",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"log",
"nym-api-requests",
"nym-coconut-interface",
@@ -6687,7 +6740,6 @@ dependencies = [
"nym-types",
"nym-validator-client",
"opentelemetry",
"pretty_env_logger",
"rand 0.7.3",
"serde",
"serde_json",
@@ -7416,7 +7468,7 @@ name = "nym-types"
version = "1.0.0"
dependencies = [
"base64 0.21.4",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"eyre",
"hmac 0.12.1",
@@ -7450,7 +7502,7 @@ dependencies = [
"bip32",
"bip39",
"colored",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"cosmwasm-std",
"cw-controllers",
"cw-utils",
@@ -7492,6 +7544,40 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-validator-rewarder"
version = "0.1.0"
dependencies = [
"anyhow",
"bip39",
"clap 4.4.7",
"cosmwasm-std",
"futures",
"humantime 2.1.0",
"humantime-serde",
"nym-bin-common",
"nym-coconut",
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
"nym-config",
"nym-credentials",
"nym-crypto",
"nym-network-defaults",
"nym-task",
"nym-validator-client",
"nyxd-scraper",
"serde",
"serde_with",
"sha2 0.10.8",
"sqlx",
"thiserror",
"time",
"tokio",
"tracing",
"url",
"zeroize",
]
[[package]]
name = "nym-vesting-contract-common"
version = "0.7.0"
@@ -7510,7 +7596,7 @@ dependencies = [
name = "nym-wallet-types"
version = "1.0.0"
dependencies = [
"cosmrs",
"cosmrs 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cosmwasm-std",
"hex-literal",
"nym-config",
@@ -7594,7 +7680,7 @@ version = "0.1.0"
dependencies = [
"async-trait",
"const_format",
"cosmrs",
"cosmrs 0.15.0 (git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features)",
"eyre",
"futures",
"nym-bin-common",
@@ -9818,6 +9904,35 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cd236ccc1b7a29e7e2739f27c0b2dd199804abc4290e32f59f3b68d6405c23"
dependencies = [
"base64 0.21.4",
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.0.2",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788"
dependencies = [
"darling 0.20.3",
"proc-macro2",
"quote",
"syn 2.0.38",
]
[[package]]
name = "serde_yaml"
version = "0.9.25"
@@ -10702,6 +10817,8 @@ dependencies = [
"deranged",
"itoa",
"js-sys",
"libc",
"num_threads",
"powerfmt",
"serde",
"time-core",
@@ -11652,18 +11769,13 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "7.4.3"
version = "8.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37"
checksum = "1290fd64cc4e7d3c9b07d7f333ce0ce0007253e32870e632624835cc80b83939"
dependencies = [
"anyhow",
"cfg-if",
"enum-iterator",
"getset",
"git2",
"rustc_version 0.4.0",
"rustversion",
"thiserror",
"time",
]
+6 -1
View File
@@ -102,6 +102,7 @@ members = [
"nym-node",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"tools/internal/ssl-inject",
"tools/internal/sdk-version-bump",
"tools/nym-cli",
@@ -124,6 +125,7 @@ default-members = [
"nym-api",
"tools/nymvisor",
"explorer-api",
"nym-validator-rewarder",
]
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-vpn/ui/src-tauri", "cpu-cycles"]
@@ -201,7 +203,10 @@ cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = "0.5.1"
cosmrs = "=0.15.0"
# temporarily using a fork again (yay.) because we need staking and slashing support
cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch ="nym-temp/all-validator-features" }
#cosmrs = { git = "https://github.com/jstuczyn/cosmos-rust", branch = "nym-temp/all-validator-features" } # unfortuntely we need a fork by yours truly to get the staking support
tendermint = "0.34" # same version as used by cosmrs
tendermint-rpc = "0.34" # same version as used by cosmrs
prost = "0.12"
@@ -1667,9 +1667,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true,
"funding": [
{
@@ -5800,9 +5800,9 @@
}
},
"follow-redirects": {
"version": "1.14.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"dev": true
},
"forwarded": {
+2 -1
View File
@@ -35,9 +35,10 @@ opentelemetry = { version = "0.19.0", optional = true, features = ["rt-tokio"] }
[build-dependencies]
vergen = { version = "=7.4.3", default-features = false, features = [
vergen = { version = "=8.2.6", default-features = false, features = [
"build",
"git",
"gitcl",
"rustc",
"cargo",
] }
+8 -7
View File
@@ -1,13 +1,14 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use vergen::{vergen, Config};
use vergen::EmitBuilder;
fn main() {
let mut config = Config::default();
if std::env::var("DOCS_RS").is_ok() {
// If we don't have access to git information, such as in a docs.rs build, don't error
*config.git_mut().skip_if_error_mut() = true;
}
vergen(config).expect("failed to extract build metadata");
EmitBuilder::builder()
.all_build()
.all_git()
.all_rustc()
.all_cargo()
.emit()
.expect("failed to extract build metadata");
}
+10 -10
View File
@@ -40,9 +40,9 @@ pub struct BinaryBuildInformation {
/// Provides the rustc channel that was used for the build, for example `nightly`.
pub rustc_channel: &'static str,
// VERGEN_CARGO_PROFILE
/// Provides the cargo profile that was used for the build, for example `debug`.
pub cargo_profile: &'static str,
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: &'static str,
}
impl BinaryBuildInformation {
@@ -57,7 +57,7 @@ impl BinaryBuildInformation {
commit_branch: env!("VERGEN_GIT_BRANCH"),
rustc_version: env!("VERGEN_RUSTC_SEMVER"),
rustc_channel: env!("VERGEN_RUSTC_CHANNEL"),
cargo_profile: env!("VERGEN_CARGO_PROFILE"),
cargo_debug: env!("VERGEN_CARGO_DEBUG"),
}
}
@@ -71,7 +71,7 @@ impl BinaryBuildInformation {
commit_branch: self.commit_branch.to_owned(),
rustc_version: self.rustc_version.to_owned(),
rustc_channel: self.rustc_channel.to_owned(),
cargo_profile: self.cargo_profile.to_owned(),
cargo_debug: self.cargo_debug.to_owned(),
}
}
@@ -115,9 +115,9 @@ pub struct BinaryBuildInformationOwned {
/// Provides the rustc channel that was used for the build, for example `nightly`.
pub rustc_channel: String,
// VERGEN_CARGO_PROFILE
/// Provides the cargo profile that was used for the build, for example `debug`.
pub cargo_profile: String,
// VERGEN_CARGO_DEBUG
/// Provides the cargo debug mode that was used for the build.
pub cargo_debug: String,
}
impl Display for BinaryBuildInformationOwned {
@@ -151,8 +151,8 @@ impl Display for BinaryBuildInformationOwned {
self.rustc_version,
"rustc Channel:",
self.rustc_channel,
"cargo Profile:",
self.cargo_profile,
"cargo Debug:",
self.cargo_debug,
)
}
}
@@ -42,6 +42,14 @@ pub struct Config {
nyxd_config: nyxd::Config,
}
impl TryFrom<NymNetworkDetails> for Config {
type Error = ValidatorClientError;
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
Config::try_from_nym_network_details(&value)
}
}
impl Config {
pub fn try_from_nym_network_details(
details: &NymNetworkDetails,
@@ -5,21 +5,24 @@ use crate::nym_api::error::NymAPIError;
use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG};
use async_trait::async_trait;
use http_api_client::{ApiClient, NO_PARAMS};
use nym_api_requests::coconut::models::{
EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse,
pub use nym_api_requests::{
coconut::{
models::{
EpochCredentialsResponse, IssuedCredential, IssuedCredentialBody,
IssuedCredentialResponse, IssuedCredentialsResponse,
},
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody,
VerifyCredentialBody, VerifyCredentialResponse,
},
models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
},
};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody,
VerifyCredentialResponse,
};
use nym_api_requests::models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
};
use nym_coconut_dkg_common::types::EpochId;
pub use nym_coconut_dkg_common::types::EpochId;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_name_service_common::response::NamesListResponse;
@@ -8,6 +8,8 @@ use cosmwasm_std::{Fraction, Uint128};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::Div;
use std::str::FromStr;
use thiserror::Error;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq, Eq)]
pub struct MismatchedDenoms;
@@ -126,6 +128,37 @@ impl From<CosmWasmCoin> for Coin {
}
}
// unfortunately cosmwasm didn't re-export this correct so we just redefine its
#[derive(Error, Debug, PartialEq, Eq)]
pub enum CoinFromStrError {
#[error("Missing denominator")]
MissingDenom,
#[error("Missing amount or non-digit characters in amount")]
MissingAmount,
#[error("Invalid amount: {0}")]
InvalidAmount(#[from] std::num::ParseIntError),
}
impl FromStr for Coin {
type Err = CoinFromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let pos = s
.find(|c: char| !c.is_ascii_digit())
.ok_or(CoinFromStrError::MissingDenom)?;
let (amount, denom) = s.split_at(pos);
if amount.is_empty() {
return Err(CoinFromStrError::MissingAmount);
}
Ok(Coin {
amount: amount.parse::<u128>()?,
denom: denom.to_string(),
})
}
}
pub trait CoinConverter {
type Target;
@@ -7,12 +7,12 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_coconut_dkg_common::dealer::{
ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse,
use nym_coconut_dkg_common::{
dealer::{ContractDealing, DealerDetailsResponse, PagedDealerResponse, PagedDealingsResponse},
msg::QueryMsg as DkgQueryMsg,
types::{DealerDetails, Epoch, EpochId, InitialReplacementData},
verification_key::{ContractVKShare, PagedVKSharesResponse},
};
use nym_coconut_dkg_common::msg::QueryMsg as DkgQueryMsg;
use nym_coconut_dkg_common::types::{DealerDetails, Epoch, EpochId, InitialReplacementData};
use nym_coconut_dkg_common::verification_key::{ContractVKShare, PagedVKSharesResponse};
use serde::Deserialize;
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -52,10 +52,6 @@ use wasmtimer::tokio::sleep;
pub const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
pub const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
#[cfg(feature = "http-client")]
#[async_trait]
impl CosmWasmClient for cosmrs::rpc::HttpClient {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait CosmWasmClient: TendermintRpcClient {
@@ -522,3 +518,7 @@ pub trait CosmWasmClient: TendermintRpcClient {
res.try_into()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T> CosmWasmClient for T where T: TendermintRpcClient {}
@@ -425,7 +425,7 @@ where
amount: amount.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NyxdError::SerializationError("MsgExecuteContract".to_owned()))
.map_err(|_| NyxdError::SerializationError("MsgSend".to_owned()))
})
.collect::<Result<_, _>>()?;
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
use crate::nyxd::cosmwasm_client::client_traits::SigningCosmWasmClient;
use crate::nyxd::error::NyxdError;
use crate::nyxd::{Config, GasPrice, Hash, Height};
use crate::rpc::TendermintRpcClient;
@@ -26,6 +26,7 @@ use cosmrs::rpc::{HttpClient, HttpClientUrl};
pub mod client_traits;
mod helpers;
pub mod logs;
pub mod module_traits;
pub mod types;
#[derive(Debug)]
@@ -329,14 +330,6 @@ where
}
}
#[async_trait]
impl<C, S> CosmWasmClient for MaybeSigningClient<C, S>
where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
}
#[async_trait]
impl<C, S> SigningCosmWasmClient for MaybeSigningClient<C, S>
where
@@ -0,0 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod slashing;
pub mod staking;
pub use staking::query::StakingQueryClient;
// pub use slashing::query
@@ -0,0 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod query;
@@ -0,0 +1,2 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod query;
pub use cosmrs::staking::{
QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse, Validator,
};
@@ -0,0 +1,78 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::{QueryHistoricalInfoResponse, QueryValidatorResponse, QueryValidatorsResponse};
use crate::nyxd::error::NyxdError;
use crate::nyxd::{CosmWasmClient, PageRequest};
use async_trait::async_trait;
use cosmrs::proto::cosmos::staking::v1beta1::{
QueryHistoricalInfoRequest as ProtoQueryHistoricalInfoRequest,
QueryHistoricalInfoResponse as ProtoQueryHistoricalInfoResponse,
QueryValidatorRequest as ProtoQueryValidatorRequest,
QueryValidatorResponse as ProtoQueryValidatorResponse,
QueryValidatorsRequest as ProtoQueryValidatorsRequest,
QueryValidatorsResponse as ProtoQueryValidatorsResponse,
};
use cosmrs::staking::{QueryHistoricalInfoRequest, QueryValidatorRequest, QueryValidatorsRequest};
use cosmrs::AccountId;
// TODO: change trait restriction from `CosmWasmClient` to `TendermintRpcClient`
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait StakingQueryClient: CosmWasmClient {
async fn historical_info(&self, height: i64) -> Result<QueryHistoricalInfoResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/HistoricalInfo".to_owned());
let req = QueryHistoricalInfoRequest { height };
let res = self
.make_abci_query::<ProtoQueryHistoricalInfoRequest, ProtoQueryHistoricalInfoResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
async fn validator(
&self,
validator_addr: AccountId,
) -> Result<QueryValidatorResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/Validator".to_owned());
let req = QueryValidatorRequest { validator_addr };
let res = self
.make_abci_query::<ProtoQueryValidatorRequest, ProtoQueryValidatorResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
async fn validators(
&self,
status: String,
pagination: Option<PageRequest>,
) -> Result<QueryValidatorsResponse, NyxdError> {
let path = Some("/cosmos.staking.v1beta1.Query/Validators".to_owned());
let req = QueryValidatorsRequest { status, pagination };
let res = self
.make_abci_query::<ProtoQueryValidatorsRequest, ProtoQueryValidatorsResponse>(
path,
req.into(),
)
.await?;
Ok(res.try_into()?)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<T> StakingQueryClient for T where T: CosmWasmClient {}
@@ -29,24 +29,30 @@ use tendermint_rpc::endpoint::*;
use tendermint_rpc::{Error as TendermintRpcError, Order};
use url::Url;
pub use crate::nyxd::cosmwasm_client::client_traits::{CosmWasmClient, SigningCosmWasmClient};
pub use crate::nyxd::fee::Fee;
pub use crate::nyxd::{
cosmwasm_client::{
client_traits::{CosmWasmClient, SigningCosmWasmClient},
module_traits::{self, StakingQueryClient},
},
fee::Fee,
};
pub use crate::rpc::TendermintRpcClient;
pub use coin::Coin;
pub use cosmrs::bank::MsgSend;
pub use cosmrs::tendermint::abci::{
response::DeliverTx, types::ExecTxResult, Event, EventAttribute,
pub use cosmrs::{
bank::MsgSend,
bip32,
crypto::PublicKey,
query::{PageRequest, PageResponse},
tendermint::{
abci::{response::DeliverTx, types::ExecTxResult, Event, EventAttribute},
block::Height,
hash::{self, Algorithm, Hash},
validator::Info as TendermintValidatorInfo,
Time as TendermintTime,
},
tx::{self, Msg},
AccountId, Any, Coin as CosmosCoin, Denom, Gas,
};
pub use cosmrs::tendermint::block::Height;
pub use cosmrs::tendermint::hash::{self, Algorithm, Hash};
pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::Msg;
pub use cosmrs::tx::{self};
pub use cosmrs::Any;
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::Gas;
pub use cosmrs::{bip32, AccountId, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use cw2;
pub use cw3;
@@ -56,9 +62,8 @@ pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
pub use tendermint_rpc::{
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
query::Query,
Paging,
Paging, Request, Response, SimpleRequest,
};
pub use tendermint_rpc::{Request, Response, SimpleRequest};
#[cfg(feature = "http-client")]
use crate::http_client;
@@ -98,6 +103,14 @@ impl Config {
}
}
impl TryFrom<NymNetworkDetails> for Config {
type Error = NyxdError;
fn try_from(value: NymNetworkDetails) -> Result<Self, Self::Error> {
Config::try_from_nym_network_details(&value)
}
}
#[derive(Debug)]
pub struct NyxdClient<C, S = NoSigner> {
client: MaybeSigningClient<C, S>,
@@ -729,7 +742,7 @@ where
where
H: Into<Height> + Send,
{
self.client.validators(height, paging).await
TendermintRpcClient::validators(&self.client, height, paging).await
}
async fn latest_consensus_params(
@@ -804,14 +817,6 @@ where
}
}
#[async_trait]
impl<C, S> CosmWasmClient for NyxdClient<C, S>
where
C: TendermintRpcClient + Send + Sync,
S: Send + Sync,
{
}
impl<C, S> OfflineSigner for NyxdClient<C, S>
where
S: OfflineSigner,
+13 -1
View File
@@ -111,6 +111,7 @@ impl NymNetworkDetails {
.with_additional_validator_endpoint(ValidatorDetails::new(
var(var_names::NYXD).expect("nyxd validator not set"),
Some(var(var_names::NYM_API).expect("nym api not set")),
get_optional_env(var_names::NYXD_WEBSOCKET),
))
.with_mixnet_contract(Some(
var(var_names::MIXNET_CONTRACT_ADDRESS).expect("mixnet contract not set"),
@@ -340,6 +341,9 @@ impl DenomDetailsOwned {
pub struct ValidatorDetails {
// it is assumed those values are always valid since they're being provided in our defaults file
pub nyxd_url: String,
//
pub websocket_url: Option<String>,
// Right now api_url is optional as we are not running the api reliably on all validators
// however, later on it should be a mandatory field
pub api_url: Option<String>,
@@ -347,9 +351,10 @@ pub struct ValidatorDetails {
}
impl ValidatorDetails {
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>) -> Self {
pub fn new<S: Into<String>>(nyxd_url: S, api_url: Option<S>, websocket_url: Option<S>) -> Self {
ValidatorDetails {
nyxd_url: nyxd_url.into(),
websocket_url: websocket_url.map(Into::into),
api_url: api_url.map(Into::into),
}
}
@@ -357,6 +362,7 @@ impl ValidatorDetails {
pub fn new_nyxd_only<S: Into<String>>(nyxd_url: S) -> Self {
ValidatorDetails {
nyxd_url: nyxd_url.into(),
websocket_url: None,
api_url: None,
}
}
@@ -372,6 +378,12 @@ impl ValidatorDetails {
.as_ref()
.map(|url| url.parse().expect("the provided api url is invalid!"))
}
pub fn websocket_url(&self) -> Option<Url> {
self.websocket_url
.as_ref()
.map(|url| url.parse().expect("the provided websocket url is invalid!"))
}
}
fn fix_deprecated_environmental_variables() {
+8 -1
View File
@@ -26,6 +26,7 @@ pub const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
pub const NYXD_URL: &str = "https://rpc.nymtech.net";
pub const NYM_API: &str = "https://validator.nymtech.net/api/";
pub const NYXD_WS: &str = "wss://rpc.nymtech.net/websocket";
pub const EXPLORER_API: &str = "https://explorer.nymtech.net/api/";
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
@@ -33,7 +34,11 @@ pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(NYXD_URL, Some(NYM_API))]
vec![ValidatorDetails::new(
NYXD_URL,
Some(NYM_API),
Some(NYXD_WS),
)]
}
const DEFAULT_SUFFIX: &str = "_MAINNET_DEFAULT";
@@ -111,6 +116,7 @@ pub fn export_to_env() {
);
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
@@ -159,6 +165,7 @@ pub fn export_to_env_if_not_set() {
);
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_conditionally_to_default(var_names::EXPLORER_API, EXPLORER_API);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
}
+1
View File
@@ -26,6 +26,7 @@ pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
pub const NAME_SERVICE_CONTRACT_ADDRESS: &str = "NAME_SERVICE_CONTRACT_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXPLORER_API: &str = "EXPLORER_API";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
+1 -1
View File
@@ -255,7 +255,7 @@ fn bench_coconut(c: &mut Criterion) {
b.iter(|| {
verify_partial_blind_signature(
&params,
&blind_sign_request,
blind_sign_request.get_private_attributes_pedersen_commitments(),
&public_attributes,
random_blind_signature,
partial_verification_key,
+2
View File
@@ -43,3 +43,5 @@ mod utils;
pub type Attribute = bls12_381::Scalar;
pub type PrivateAttribute = Attribute;
pub type PublicAttribute = Attribute;
pub use bls12_381::G1Projective;
+7 -8
View File
@@ -362,12 +362,12 @@ pub fn blind_sign(
/// The function returns `true` if the partial blind signature is valid, and `false` otherwise.
pub fn verify_partial_blind_signature(
params: &Parameters,
blind_sign_request: &BlindSignRequest,
private_attribute_commitments: &[G1Projective],
public_attributes: &[&Attribute],
blind_sig: &BlindedSignature,
partial_verification_key: &VerificationKey,
) -> bool {
let num_private_attributes = blind_sign_request.private_attributes_commitments.len();
let num_private_attributes = private_attribute_commitments.len();
if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() {
return false;
}
@@ -388,8 +388,7 @@ pub fn verify_partial_blind_signature(
];
// for each private attribute, add (cm_i, beta_i) to the miller terms
for (private_attr_commit, beta_g2) in blind_sign_request
.private_attributes_commitments
for (private_attr_commit, beta_g2) in private_attribute_commitments
.iter()
.zip(&partial_verification_key.beta_g2)
{
@@ -422,7 +421,7 @@ pub fn verify_partial_blind_signature(
// is equivalent to checking e(a, b) • e(c, d)^{-1} == id
// and thus to e(a, b) • e(c^{-1}, d) == id
//
// compute e(c^1, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
// compute e(c^{-1}, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j})
multi_miller_loop(&terms_refs)
.final_exponentiation()
.is_identity()
@@ -519,7 +518,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator_keypair.verification_key()
@@ -539,7 +538,7 @@ mod tests {
assert!(verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&[],
&blind_sig,
validator_keypair.verification_key()
@@ -568,7 +567,7 @@ mod tests {
// this assertion should fail, as we try to verify with a wrong validator key
assert!(!verify_partial_blind_signature(
&params,
&request,
&request.private_attributes_commitments,
&public_attributes,
&blind_sig,
validator2_keypair.verification_key()
+10 -1
View File
@@ -11,8 +11,10 @@ use crate::storage::{persist_block, ScraperStorage};
use futures::StreamExt;
use std::collections::{BTreeMap, HashSet, VecDeque};
use std::ops::{Add, Range};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
use tokio::sync::Notify;
use tokio::time::{interval_at, Instant};
use tokio_stream::wrappers::UnboundedReceiverStream;
use tokio_util::sync::CancellationToken;
@@ -39,6 +41,7 @@ impl PendingSync {
pub struct BlockProcessor {
cancel: CancellationToken,
synced: Arc<Notify>,
last_processed_height: u32,
last_processed_at: Instant,
pending_sync: PendingSync,
@@ -60,6 +63,7 @@ pub struct BlockProcessor {
impl BlockProcessor {
pub async fn new(
cancel: CancellationToken,
synced: Arc<Notify>,
incoming: UnboundedReceiver<BlockToProcess>,
block_requester: Sender<BlockRequest>,
storage: ScraperStorage,
@@ -69,6 +73,7 @@ impl BlockProcessor {
Ok(BlockProcessor {
cancel,
synced,
last_processed_height: last_processed.try_into().unwrap_or_default(),
last_processed_at: Instant::now(),
pending_sync: Default::default(),
@@ -243,6 +248,10 @@ impl BlockProcessor {
}
self.try_request_pending().await;
if self.pending_sync.is_empty() {
self.synced.notify_one();
}
}
async fn try_request_pending(&mut self) -> bool {
@@ -294,7 +303,7 @@ impl BlockProcessor {
error!("failed to perform startup sync: {err}");
self.cancel.cancel();
return;
}
};
loop {
tokio::select! {
+1
View File
@@ -16,3 +16,4 @@ pub mod storage;
pub use modules::{BlockModule, MsgModule, TxModule};
pub use scraper::{Config, NyxdScraper};
pub use storage::models;
+14 -1
View File
@@ -9,8 +9,10 @@ use crate::rpc_client::RpcClient;
use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber};
use crate::storage::ScraperStorage;
use std::path::PathBuf;
use std::sync::Arc;
use tendermint_rpc::WebSocketClientDriver;
use tokio::sync::mpsc::{channel, unbounded_channel};
use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use tracing::info;
@@ -54,6 +56,7 @@ impl NyxdScraperBuilder {
);
let mut block_processor = BlockProcessor::new(
scraper.cancel_token.clone(),
scraper.startup_sync.clone(),
processing_rx,
req_tx,
scraper.storage.clone(),
@@ -112,7 +115,8 @@ pub struct NyxdScraper {
task_tracker: TaskTracker,
cancel_token: CancellationToken,
storage: ScraperStorage,
startup_sync: Arc<Notify>,
pub storage: ScraperStorage,
}
impl NyxdScraper {
@@ -127,6 +131,7 @@ impl NyxdScraper {
config,
task_tracker: TaskTracker::new(),
cancel_token: CancellationToken::new(),
startup_sync: Arc::new(Default::default()),
storage,
})
}
@@ -164,6 +169,7 @@ impl NyxdScraper {
);
let block_processor = BlockProcessor::new(
self.cancel_token.clone(),
self.startup_sync.clone(),
processing_rx,
req_tx,
self.storage.clone(),
@@ -189,8 +195,15 @@ impl NyxdScraper {
Ok(())
}
pub async fn wait_for_startup_sync(&self) {
info!("awaiting startup chain sync");
self.startup_sync.notified().await
}
pub async fn stop(self) {
info!("stopping the chain scrapper");
assert!(self.task_tracker.is_closed());
self.cancel_token.cancel();
self.task_tracker.wait().await
}
+27 -3
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::storage::models::Validator;
use crate::storage::models::{CommitSignature, Validator};
use sqlx::types::time::OffsetDateTime;
use sqlx::{Executor, Sqlite};
use tracing::{instrument, trace};
@@ -84,8 +84,8 @@ impl StorageManager {
SELECT COUNT(*) as count FROM pre_commit
WHERE
validator_address == ?
AND height > ?
AND height < ?
AND height >= ?
AND height <= ?
"#,
consensus_address,
start_height,
@@ -98,6 +98,24 @@ impl StorageManager {
Ok(count)
}
pub(crate) async fn get_precommit(
&self,
consensus_address: &str,
height: i64,
) -> Result<Option<CommitSignature>, sqlx::Error> {
sqlx::query_as(
r#"
SELECT * FROM pre_commit
WHERE validator_address = ?
AND height = ?
"#,
)
.bind(consensus_address)
.bind(height)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn get_block_validators(
&self,
height: i64,
@@ -118,6 +136,12 @@ impl StorageManager {
.await
}
pub(crate) async fn get_validators(&self) -> Result<Vec<Validator>, sqlx::Error> {
sqlx::query_as("SELECT * FROM validator")
.fetch_all(&self.connection_pool)
.await
}
pub(crate) async fn get_last_processed_height(&self) -> Result<i64, sqlx::Error> {
let maybe_record = sqlx::query!(
r#"
+32 -4
View File
@@ -7,7 +7,7 @@ use crate::storage::manager::{
insert_block, insert_message, insert_precommit, insert_transaction, insert_validator,
update_last_processed, StorageManager,
};
use crate::storage::models::Validator;
use crate::storage::models::{CommitSignature, Validator};
use sqlx::types::time::OffsetDateTime;
use sqlx::{ConnectOptions, Sqlite, Transaction};
use std::fmt::Debug;
@@ -19,7 +19,7 @@ use tracing::{debug, error, info, instrument, trace, warn};
mod helpers;
mod manager;
mod models;
pub mod models;
pub type StorageTransaction = Transaction<'static, Sqlite>;
@@ -31,8 +31,6 @@ pub struct ScraperStorage {
impl ScraperStorage {
#[instrument]
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> {
// TODO: we can inject here more stuff based on our nym-api global config
// struct. Maybe different pool size or timeout intervals?
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
@@ -91,6 +89,21 @@ impl ScraperStorage {
Ok(self.manager.get_last_block_height_before(time).await?)
}
pub async fn get_blocks_between(
&self,
start_time: OffsetDateTime,
end_time: OffsetDateTime,
) -> Result<i64, ScraperError> {
let Some(block_start) = self.get_first_block_height_after(start_time).await? else {
return Ok(0);
};
let Some(block_end) = self.get_last_block_height_before(end_time).await? else {
return Ok(0);
};
Ok(block_end - block_start)
}
pub async fn get_signed_between(
&self,
consensus_address: &str,
@@ -120,10 +133,25 @@ impl ScraperStorage {
.await
}
pub async fn get_precommit(
&self,
consensus_address: &str,
height: i64,
) -> Result<Option<CommitSignature>, ScraperError> {
Ok(self
.manager
.get_precommit(consensus_address, height)
.await?)
}
pub async fn get_block_signers(&self, height: i64) -> Result<Vec<Validator>, ScraperError> {
Ok(self.manager.get_block_validators(height).await?)
}
pub async fn get_all_known_validators(&self) -> Result<Vec<Validator>, ScraperError> {
Ok(self.manager.get_validators().await?)
}
pub async fn get_last_processed_height(&self) -> Result<i64, ScraperError> {
Ok(self.manager.get_last_processed_height().await?)
}
+1 -1
View File
@@ -4,7 +4,7 @@
use sqlx::types::time::OffsetDateTime;
use sqlx::FromRow;
#[derive(Debug, Clone, FromRow)]
#[derive(Debug, Clone, Eq, PartialEq, Hash, FromRow)]
pub struct Validator {
pub consensus_address: String,
pub consensus_pubkey: String,
+3 -3
View File
@@ -116,8 +116,8 @@ impl TaskManager {
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn catch_interrupt(mut self) -> Result<(), SentError> {
let res = crate::wait_for_signal_and_error(&mut self).await;
pub async fn catch_interrupt(&mut self) -> Result<(), SentError> {
let res = crate::wait_for_signal_and_error(self).await;
log::info!("Sending shutdown");
self.signal_shutdown().ok();
@@ -629,7 +629,7 @@ impl TaskHandle {
#[cfg(not(target_arch = "wasm32"))]
pub async fn wait_for_shutdown(self) -> Result<(), SentError> {
match self {
TaskHandle::Internal(task_manager) => task_manager.catch_interrupt().await,
TaskHandle::Internal(mut task_manager) => task_manager.catch_interrupt().await,
TaskHandle::External(mut task_client) => {
task_client.recv().await;
Ok(())
+3 -1
View File
@@ -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
View File
@@ -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");
}
+10 -4
View File
@@ -24,7 +24,7 @@
- [NymConnect X Electrum](tutorials/electrum.md)
- [NymConnect X Firo wallet](tutorials/firo.md)
# Code Examples
# Code Examples
- [Custom Service Providers](examples/custom-services.md)
- [Apps Using Network Requesters](examples/using-nrs.md)
@@ -49,9 +49,9 @@
- [Preparing Your Service](tutorials/cosmos-service/service.md)
- [Preparing Your Service pt2](tutorials/cosmos-service/service-src.md)
- [Querying the Chain](tutorials/cosmos-service/querying.md)
- [Typescript](tutorials/typescript.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md)
- [Tutorial Overview](tutorials/simple-service-provider/overview.md)
- [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md)
- [Building Your User Client](tutorials/simple-service-provider/user-client.md)
@@ -68,6 +68,12 @@
# Events
- [CryptoTalk](events/cryptotalk/nym-vpn.md)
- [NymVPN Linux Guide](events/cryptotalk/nym-vpn-linux.md)
- [NymVPN MacOS Guide](events/cryptotalk/nym-vpn-mac.md)
- [NymVPN Testing](events/cryptotalk/nym-vpn-testing.md)
- [NymVPN Troubleshooting](events/cryptotalk/nym-vpn-troubleshooting.md)
- [NymVPN FAQ](events/cryptotalk/nym-vpn-faq.md)
- [Web3Privacy Now](./events/web3-privacy.md)
- [HCPP23-serinko](./events/hcpp23-serinko.md)
- [HCPP23-max](./events/hcpp23-max.md)
@@ -83,7 +89,7 @@
- [Community Applications and Guides](community-resources/community-applications-and-guides.md)
- [Change Service Grantee Information](info-request.md)
---
---
# Misc.
- [Code of Conduct](coc.md)
- [Licensing](licensing.md)
@@ -1,457 +0,0 @@
# NymVPN alpha
Nym has announced [NymVPN](https://nymvpn.com/en) and presented the [NymVPN Litepaper](https://nymvpn.com/nymVPN-litepaper.pdf). At CCC 2023 we have the unique opportunity to do the first alpha public testing. This page provides a how to guide, explaining steps to install and run NymVPN CLI and GUI on the Nym Sandbox testnet.
NymVPN is a client that uses [Nym Mixnet](https://nymtech.net) to anonymise users entire internet traffic.
The default is to run in 5-hop mode:
```
┌─►mix──┐ mix mix
│ │
Entry │ │ Exit
client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet
│ │
│ │
mix └─►mix──┘ mix
```
Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateway's, and is not passed between Mix Nodes.
The client can optionally do the first connection to the entry gateway using Wireguard. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device.
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn.md#testing) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research at 37c3*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko).
```
## Goals
This alpha testing will help:
* Stabilise NymVPN client
* Understand NymVPN client behavior in various setups (OS, connectivity, etc.)
* Stabilize the VPN infrastructure and improve its reliability / speed / features (e.g. IPv6 support)
* Load test the network in Sandbox environment and identify / anticipate potential weaknesses
```admonish info
Our alpha testing round is done live with some participants at CCC 2023. This guide will not work for everyone, as the NymVPN binaries aren't publicly accessible yet. Note that this setup of Nym testnet Sandbox environment is limited for CCC 2023 event and some of the configurations will not work in the future.
```
> **If you commit to test NymVPN alpha, please start with the [user research form](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) where all the steps will be provided**.
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
We have CLI and UI binaries available for Linux (Debian based) and macOS.
![](images/image1.png)
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/ccc) to download the binary for your system.
* Open terminal in the same directory and make executable by running:
```sh
# for CLI
chmod +x ./nym-vpn-cli
# for GUI
chmod +x ./nym-vpn_0.0.0_amd64.AppImage
# make sure your path to package is correct and the package name as well
```
* If you prefer to use the `.deb` version for installation (Linux only), open terminal in the same directory and run:
```
sudo dpkg -i ./<PACKAGE_NAME>.deb
# or
sudo apt-get install -f ./<PACKAGE_NAME>.deb
```
* For CLI: Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below.
### GUI configuration
* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
```sh
# for Linux
mkdir $HOME/.config/nym-vpn/
# for macOS
mkdir $HOME/Library/Application Support/nym-vpn/
```
* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the directory `nym-vpn` you just created
* Create the main config file called `config.toml` in the same directory with this content:
```toml
# for Linux
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env" # change <USER> to your username
entry_node_location = "DE" # two letters country code
# for macOS
env_config_file = "$HOME/Library/Application Support/nym-vpn/"
entry_node_location = "DE" # two letters country code
```
## Running
***For NymVPN to work, all other VPNs must be switched off!***
**Note:** At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
* Get your private key for wireguard setup [here](https://nymvpn.com/en/37c3)
* See a JSON list of all Gateways [here](https://nymvpn.com/en/ccc/api/gateways)
* As NymVPN need a permission for network settings, it is run with `sudo` root command
### CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
```admonish info
The entire command syntax with all the needed arguments' values and your private key for testing purposes can be found on our event webpage [nymvpn.com/en/37c3](https://nymvpn.com/en/37c3).
```
Running a help command:
```sh
./nym-vpn-cli --help
```
~~~admonish example collapsible=true title="Console output"
```
Usage: nym-vpn-cli [OPTIONS]
Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
--mixnet-client-path <MIXNET_CLIENT_PATH>
Path to the data directory of a previously initialised mixnet client, where the keys reside
--entry-gateway-id <ENTRY_GATEWAY_ID>
Mixnet public ID of the entry gateway
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
Auto-select entry gateway by country ISO
--exit-router-address <EXIT_ROUTER_ADDRESS>
Mixnet recipient address
--exit-gateway-id <EXIT_GATEWAY_ID>
--exit-router-country <EXIT_ROUTER_COUNTRY>
Mixnet recipient address
--enable-wireguard
Enable the wireguard traffic between the client and the entry gateway
--private-key <PRIVATE_KEY>
Associated private key
--wg-ip <WG_IP>
The IP address of the wireguard interface
--ip <IP>
The IP address of the TUN device
--mtu <MTU>
The MTU of the TUN device
--disable-routing
Disable routing all traffic through the VPN TUN device
--enable-two-hop
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
--enable-poisson-rate
Enable Poisson process rate limiting of outbound traffic
-h, --help
Print help
-V, --version
Print version
```
~~~
To run the CLI a few things need to be specified:
```sh
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
```
### Options Flags
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users:
- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env`
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/ccc/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/ccc/api/gateways)
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/37c3)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/37c3)
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
**More advanced options**
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
### GUI
If you installed the `.deb` package you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission.
Make sure you went through the GUI configuration in the [preparation section](./nym-vpn.md#gui-configuration). Then open terminal in the same directory where you [installed](./nym-vpn.md#preparation) the binary run:
```sh
sudo -E ./nym-vpn_0.0.0_amd64.AppImage
# In case you use another binary, just add the correct name as:
sudo -E ./<FULL_BINARY_NAME>
```
In case of errors, see [troubleshooting section](./nym-vpn.md#macos-alert-on-nymvpn-ui-startup).
## Testing
One of the main aims of the demo is testing; your results will help us to make NymVPN robust and stabilise both the client and the network through provided measurements. Before you go into testing, make sure you went through [preparation steps](./nym-vpn.md#preparation) and the [CLI binary](https://github.com/nymtech/nym/releases/tag/ccc) for your system is ready to run.
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary and `sandbox.env` to that directory
2. Copy the [block below](./nym-vpn.md#testssh) and save it to the same folder as `tests.sh`
3. Open terminal in the same directory
4. Turn off any existing VPN's (including the NymVPN instances) and make the script executable by running `chmod +x ./tests.sh`
5. Run the tests: `sudo ./tests.sh`
6. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
7. The script will print a JSON view of existing Gateways and prompt you to:
- ***(Make sure to use two different Gateways for entry and exit!)***
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
- `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/37c3)
8. Note that the testing script doesn't print many logs, in case of doubts you can check logs in the log file `temp_log.txt` located in the same directory.
9. The script shall run the tests and generate a folder called `tests_<LONG_STRING>` and files `perf_test_results.log` or `two_hop_perf_test_results.log` as well as some temp files. This is how the directory structure will look like:
```sh
nym-vpn-tests
├── tests.sh
├── nym-vpn-cli
├── sandbox.env
├── perf_test_results.log
├── tests_<LONG_STRING>
│   ├── api_response_times.txt
│   ├── download_time.txt
│   └── ping_results.txt
├── timeout
└── two_hop_perf_test_results.log
```
10. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below
11. When the tests are finished, remove the `nym-vpn-cli` binary from the folder and compress it as `nym-vpn-tests.zip`
12. Upload this compressed file to the [questionnaire](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) upload field when prompted.
#### tests.sh
```sh
#!/bin/bash
ENDPOINT="https://sandbox-nym-api1.nymtech.net/api/v1/gateways/described"
json_array=()
echo "🚀 🏎 - please be patient, i'm fetching you your entry points - 🚀 🏎 "
data=$(curl -s "$ENDPOINT" | jq -c '.[] | {host: .bond.gateway.host, hostname: .self_described.host_information.hostname, identity_key: .bond.gateway.identity_key, exitGateway: .self_described.ip_packet_router.address}')
while IFS= read -r entry; do
host=$(echo "$entry" | jq -r '.host')
hostname=$(echo "$entry" | jq -r '.hostname')
identity_key=$(echo "$entry" | jq -r '.identity_key')
exit_gateway_address=$(echo "$entry" | jq -r '.exitGateway // empty')
valid_ip=$(echo "$host")
if [ -n "$exit_gateway_address" ]; then
exit_gateway="{\"address\": \"${exit_gateway_address}\"}"
else
exit_gateway="{}"
fi
if [[ $valid_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
country_info=$(curl -s "http://ipinfo.io/${valid_ip}/country" | tr -d '\n')
country_info_escaped=$(echo "$country_info" | tr -d '\n' | jq -aRs . | tr -d '"')
else
country_info_escaped=""
fi
json_object="{\"hostname\": \"${hostname}\", \"identityKey\": \"${identity_key}\", \"exitGateway\": ${exit_gateway}, \"location\": \"${country_info_escaped}\"}"
json_array+=("$json_object")
done < <(echo "$data")
if [ $? -ne 0 ]; then
echo "error fetching data from endpoint"
exit 1
fi
download_file() {
local file_url=$1
local output_file=$2
local time_file=$3
echo "starting download speed test..."
local start_time=$(date +%s)
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
wget -O $output_file $file_url
elif [[ "$OSTYPE" == "darwin"* ]]; then
curl -o $output_file $file_url
fi
local end_time=$(date +%s)
local elapsed_time=$((end_time - start_time))
echo "download speed test completed in $elapsed_time seconds." >"$time_file"
}
if ! command -v jq &>/dev/null; then
echo "jq is not installed. Please install jq to proceed."
exit 1
fi
temp_log_file="temp_log.txt"
perform_tests() {
local gateway_id=$1
local exit_address=$2
local test_directory="tests_${gateway_id}_${exit_address}"
local file_url="http://ipv4.download.thinkbroadband.com/2MB.zip"
mkdir -p "$test_directory"
local ping_results_file="${test_directory}/ping_results.txt"
local download_time_file="${test_directory}/download_time.txt"
local api_response_file="${test_directory}/api_response_times.txt"
# ping test
echo "starting ping test..."
for site in google.com youtube.com facebook.com baidu.com wikipedia.org amazon.com twitter.com instagram.com yahoo.com ebay.com netflix.com; do
ping -c 4 $site >>"$ping_results_file"
done
echo "ping test completed. Results saved in $ping_results_file"
# download speed test
download_file $file_url /dev/null "$download_time_file"
# api test
local api_endpoint="https://validator.nymtech.net/api/v1/mixnodes"
local iterations=10
>"$api_response_file"
for i in $(seq 1 $iterations); do
local start_time=$(date +%s)
local response=$(curl -s -o /dev/null -w '%{http_code}' $api_endpoint)
local end_time=$(date +%s)
local elapsed_seconds=$((end_time - start_time))
local hours=$((elapsed_seconds / 3600))
local minutes=$(((elapsed_seconds % 3600) / 60))
local seconds=$((elapsed_seconds % 60))
local human_readable_time=$(printf "%02dh:%02dm:%02ds" $hours $minutes $seconds)
echo "iteration $i: response Time = ${human_readable_time}, status code = $response" >>"$api_response_file"
done
echo "api response test completed. Results saved in $api_response_file."
}
printf "%s\n" "${json_array[@]}" | jq -s .
read -p "enter a gateway ID: " identity_key
read -p "enter an exit address: " exit_address
while true; do
read -p "enable WireGuard? (yes/no): " enable_wireguard
enable_wireguard=$(echo "$enable_wireguard" | tr '[:upper:]' '[:lower:]')
case "$enable_wireguard" in
"yes")
read -p "enter your WireGuard private key: " priv_key
read -p "enter your WireGuard IP: " wg_ip
wireguard_options="--enable-wireguard --private-key $priv_key --wg-ip $wg_ip"
break
;;
"no")
wireguard_options=""
break
;;
*)
echo "invalid response. please enter 'yes' or 'no'."
;;
esac
done
sudo ./nym-vpn-cli -c sandbox.env --entry-gateway-id ${identity_key} --exit-router-address ${exit_address} --enable-two-hop $wireguard_options >"$temp_log_file" 2>&1 &
timeout=15
start_time=$(date +%s)
while true; do
current_time=$(date +%s)
if grep -q "received plain" "$temp_log_file"; then
echo "successful configuration with identity_key: $identity_key and exit address: $exit_address" >>perf_test_results.log
perform_tests "$identity_key" "$exit_address"
break
fi
if ((current_time - start_time > timeout)); then
echo "failed to connect with identity_key: $identity_key using the exit address: $exit_address" >>perf_test_results.log
break
fi
sleep 1
done
echo "terminating nym-vpn-cli..."
pkill -f './nym-vpn-cli'
sleep 5
rm -f "$temp_log_file"
```
## Troubleshooting
Below are listed some points which may need to be addressed when testing NymVPN alpha. If you crashed into any errors which are not listed, please contact us at the event.
#### Thread `main` panicked
If you see a message like:
```sh
thread 'main' panicked at /Users/runner/.cargo/git/checkouts/mullvadvpn-app-a575cf705b5dfd76/ccfbaa2/talpid-routing/src/unix.rs:301:30:
```
Restart your wifi connection and start again.
#### macOS alert on NymVPN UI startup
If you are running NymVPN on mac OS for the first time, you may see this alert message:
![](images/image3.png)
1. Head to System Settings -> Privacy & Security and click `Allow anyway`
![](images/image5.png)
2. Confirm with your password or TouchID
3. Possibly you may have to confirm again upon running the application
#### Missing `jq` error
In case of missing `jq` on Linux (Debian) install it with:
```sh
# Linux (Debian)
sudo apt-get install jq
# macOS
brew install jq
```
On some Linux distributions however the [script](./nym-vpn.md#testssh) returns `jq` error even if your system claims that `jq is already the newest version`.
In that case, comment the `jq` check in the script as follows:
```sh
#if ! command -v jq &>/dev/null; then
# echo "jq is not installed. Please install jq to proceed."
# exit 1
#fi
```
#### Error current_time: not found
When running `sudo sh ./test.sh` you may see an error like: `93: current_time: not found`. This has something to do with the `current_time` setup of your system and on itself shall not have a negative impact on the test. It has nothing to do with the client at all as it only relates to the code in our testing script.
#### Not connecting to the endpoint
In case the automatic download of all the Gateways fail (and it shouldn't), you do an easy manual work around:
1. Open the list of Gateways created by API [here](https://nymvpn.com/en/ccc/api/gateways)
2. On top click on `JSON` option (shall be default view) and `Save`
3. Save it as `data.json` to the `nym-vpn-tests` folder
4. Replace line 3 in the [script `tests.sh`](./nym-vpn.md#testssh) with:
```sh
NEW_ENDPOINT="http://localhost:8000/data.json"
```
5. In a new terminal window run:
```sh
python3 -m http.server 8000
```
6. Continue with the steps listed in [testing section](./nym-vpn.md#testing)
@@ -1,14 +0,0 @@
# Chaos Communication Congress 2023
At [CCC 2019](https://constructiveproof.com/posts/2020-01-27-nym-testnet-up-and-running/) we announced the first demo of the [Nym Mixnet](https://github.com/nymtech/nym). Now, four years later we are honoured to present our new product: [NymVPN](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d). NymVPN is a client/application which captures the entire network stack of a user's device, sending this traffic through either a 2-hop VPN or through the 5-hop Mixnet. CCC 2023 is the first public demo of both the GUI and CLI versions of NymVPN.
At first glance Nym platform may seem pretty complex. To make the connection between Nym and the community of developers, operators and users participating on the event as smooth as possible, we created a segment of documentation tailored specifically for the conference. The following pages provide an easy introduction to the NymVPN alpha client. We recommend interested developers to begin with [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) pages.
**See pages below for more content:**
* [NymVPN alpha guide](nym-vpn.md)
* [FAQ @ 37c3](faq.md)
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/897010658?h=1f55870fe6&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="NYMVPN alpha demo 37C3"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

@@ -1,16 +1,16 @@
# Frequently Asked Questions @ 37C3
# Frequently Asked Questions
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).
This is a FAQ page tailored for this event. 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.
If this your first time hearing about NymVPN, make sure you visit [NymVPN webpage](https://nymvpn.com/en), the official NymVPN [support & FAQ page](https://nymvpn.com/en/support) and the proceed to the introduction and 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.
Below are some extra FAQs which came out during the initial alpha testing round.
### 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.
The default is 5-hops (including Entry and Exit Gateways), which means that the traffic goes from the local client to Entry Gateway -> through 3 layers of Mix Nodes -> to Exit Gateway -> internet. this option uses all the Nym Mixnet features for maximum privacy.
```
┌─►mix──┐ mix mix
@@ -22,7 +22,7 @@ client ───► Gateway ──┘ mix │ mix ┌─►mix ───►
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.
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. The anonymising features done by your local client like breaking data into same-size packets with inserting additional "dummy" ones to break the time and volume patterns is done in both options.
```
Entry Exit
@@ -50,15 +50,15 @@ We have a list of questions related to Nym Nodes and the incentives behind runni
## 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.
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.
Part of the the transition under code name [Project Smoosh](./nym-vpn-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
* 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
@@ -67,4 +67,3 @@ If you are a dev who is interested to integrate Nym, have a look on our SDK tuto
* [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)
@@ -0,0 +1,198 @@
# NymVPN alpha: Guide for GNU/Linux
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn-testing.md) 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*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2).
```
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
You can use our automated script of follow the steps below to download, verify, install and run NymVPN client.
### Automated Script CLI Installation
Open a terminal and follow these steps:
* Download the script
```sh
curl -o nym-vpn-cli-executor.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/99cea8f4d80f2d002802ed1cbedba288bfca4488/execute-nym-vpn-cli-binary.sh
```
* Make the script executable
```sh
chmod u+x nym-vpn-cli-executor.sh
```
* Run the script
```sh
./nym-vpn-cli-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
61a08de46881411e140f66cdb4940041150adb573c10e3f510a58a86a32f08fb nym-vpn-cli-ubuntu-22.04.zip
```
The script will automatically start the client. Follow the instructions:
* 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 `" "`)
- `do you want five hop or two hop?`: type `five` or `two`
### Manual CLI and GUI Installation
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) to download the binary for Debian based Linux
* Open terminal in the same directory and check the the `sha256sum` by running:
```sh
# for CLI
sha256sum ./nym-vpn-cli_0.1.0_ubuntu-22.04_amd64.zip
# for GUI
sha256sum ./nym-vpn-ui_0.0.2_ubuntu-22.04_amd64.zip
```
* Compare the result with the sha256 hash shared on the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2)
* Extract files with `unzip` command or manually as you are used to
* If you prefer to run `.AppImage` make executable by running:
```sh
# for CLI
chmod +x ./nym-vpn-cli
# for GUI
chmod +x ./appimage/nym-vpn_0.0.2_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:
```sh
cd deb
sudo dpkg -i ./nym-vpn_0.0.2_amd64.deb
# or
sudo apt-get install -f ./nym-vpn_0.0.2_amd64.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/
```
* 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
# change <USER> to your username
env_config_file = "/home/<USER>/.config/nym-vpn/sandbox.env"
entry_node_location = "DE" # two letters country code
```
## Run NymVPN
* **For NymVPN to work, all other VPNs must be switched off!**
* At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
### Run CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
* Find the entire command with all the needed arguments' values and your wireguard private key for testing purposes at [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha)
* Run it with `sudo` as root, the command will look like this with specified arguments:
```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>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities see [command explanation](#cli-commands-and-options) below
### Run GUI
In case you used `.deb` package and installed the client, 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](#gui-configuration). Then open terminal in the same directory where you [installed](#preparation) the binary and run:
```sh
# .AppImage must be run from the same directory
sudo -E ./nym-vpn_0.0.2_amd64.AppImage
# .deb installation shall be executable from anywhere as
sudo -E nym-vpn
```
In case of errors, see [troubleshooting section](./nym-vpn-troubleshooting.md).
### CLI Commands and Options
The basic syntax of `nym-vpn-cli` is:
```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>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities run with `--help` flag:
```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
```
~~~
**Fundamental commands and arguments**
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/alpha/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/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/alpha)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
- `--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)
**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.
@@ -0,0 +1,191 @@
# NymVPN alpha: Guide for Mac OS
```admonish warning
NymVPN is an experimental software and it's for [testing](./nym-vpn-testing.md) 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*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2).
```
## Preparation
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
### CLI Installation
You can use our automated script of follow the steps below to download, verify, install and run NymVPN client.
#### Automated Script CLI Installation
Open a terminal and follow these steps:
* Download the script
```sh
curl -o nym-vpn-client-executor.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/99cea8f4d80f2d002802ed1cbedba288bfca4488/execute-nym-vpn-cli-binary.sh
```
* Make the script executable
```sh
chmod u+x nym-vpn-client-executor.sh
```
* Run the script
```sh
./nym-vpn-client-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
96623ccc69bc4cc0e4e3e18528b6dae6be69f645d0a592d926a3158ce2d0c269 nym-vpn-cli_0.1.0_macos_x86_64.zip
```
The script will automatically start the client. Follow the instructions:
* 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 `" "`)
- `do you want five hop or two hop?`: type `five` or `two`
#### Manual CLI Installation
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) to download the binary for Debian based Linux
* Open terminal in the same directory and check the the `sha256sum` by running:
```sh
# x86_64
sha256sum ./nym-vpn-cli_0.1.0_macos_x86_64.zip
# aarch64
sha256sum ./nym-vpn-cli_0.1.0_macos_aarch64.zip
```
* Compare the result with the sha256 hash shared on the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2)
* Extract files with `unzip` command or manually as you are used to
```sh
# for CLI
chmod +x ./nym-vpn-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 Installation
We created a [script](https://gist.github.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19) which does download, extraction, installation and configuration for MacOS users automatically following the steps below:
* To download the script, open a terminal in a directory where you want to download the script and run:
```sh
curl -o nym-vpn-client-executor.sh - L https://gist.githubusercontent.com/tommyv1987/7d210d4daa8f7abc61f9a696d0321f19/raw/4397365b4cf74594c7f99c1ef5d690b2f5b41192/nym-vpn-client-macos-executor.sh
```
* Make executable
```sh
chmod u+x nym-vpn-client-macos-executor.sh
```
* Run
```sh
./nym-vpn-client-macos-executor.sh
```
* When prompted to verify `sha256sum` paste one from the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.2) including the binary name (all as one input with a space in between), for example:
```sh
06c7c82f032f230187da1002a9a9a88242d3bbf6c5c09dc961a71df151d768d0 nym-vpn-ui_0.0.2_macos_x86_64.zip
```
* The script will run the application and it will prompt you for a country code to exit, chose one of the offered options
In case of errors check out the [troubleshooting](./nym-vpn-troubleshooting.html#installing-gui-on-macos-not-working) section.
## Run NymVPN
* **For NymVPN to work, all other VPNs must be switched off!**
* At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds.
### Run CLI
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
* Find the entire command with all the needed arguments' values and your wireguard private key for testing purposes at [nymvpn.com/en/alpha](https://nymvpn.com/en/alpha)
* Run it with `sudo` as root, the command will look like this with specified arguments:
```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>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities see [command explanation](#cli-commands-and-options) below
### Run GUI
Make sure you went through the GUI configuration in the [preparation section](#gui-installation).
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.
* Run GUI from terminal:
```sh
sudo $nym_vpn_dir/nym-vpn
```
In case of errors, see [troubleshooting section](./nym-vpn-troubleshooting.md#macos-alert-on-nymvpn-ui-startup).
### CLI Commands and Options
The basic syntax of `nym-vpn-cli` is:
```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>
```
* To chose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
* To see all possibilities run with `--help` flag:
```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
```
~~~
**Fundamental commands and arguments**
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/alpha/api/gateways)
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/alpha/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/alpha)
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
- `--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)
**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.
@@ -0,0 +1,195 @@
# Testing NymVPN alpha
> Before you get into testing NymVPN, make sure to go through the preparation steps for [Linux](nym-vpn-linux.md) or [MacOS](nym-vpn-mac.md).
One of the main aims of NymVPN alpha release is testing; your results will help us to make NymVPN robust and stabilise both the client and the network through provided measurements.
## Steps to test NymVPN
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary and [`sandbox.env`](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) to that directory
2. Copy the [block below](#testssh) and save it as `tests.sh` to the same folder
3. Open terminal in the same directory
4. Turn off any existing VPN's (including the NymVPN instances), reconnect your wifi and make the script executable by running `chmod +x ./tests.sh`
5. Run the tests: `sudo ./tests.sh`
6. In case of errors, see the [troubleshooting section](./nym-vpn-troubleshooting.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/alpha)
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-troubleshooting.md#missing-jq-error) below
11. When the tests are finished, remove the `nym-vpn-cli` binary from the folder and compress the entire folder as `nym-vpn-tests.zip`
12. Upload this compressed file to the [questionnaire](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko-2) upload field when prompted
#### tests.sh
This is the testing script which needs to be copied and saved as `tests.sh` to your `nym-vpn-tests` folder and then run from there as described [above](#steps-to-test-nymvpn).
```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"
```
@@ -0,0 +1,100 @@
# 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.
#### Problems with the newest version `nym-vpn-alpha-0.0.2 demo`
Try the previous version `nym-vpn-alpha-0.0.1 demo` which was tested multiple times by downloading the client from this [release page(https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.1) and doing all the steps with the name of your downloaded binary.
#### Installing GUI on MacOS not working
In case there was a problem running the script, try a manual configuration:
* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.1) to download the binary for Debian based Linux
* Open terminal in the same directory and check the the `sha256sum` by running:
```sh
# for MacOS GUI
sha256sum ./nym-vpn-ui-macos-latest.zip
```
* Compare the result with the sha256 hash shared on the [release page](https://github.com/nymtech/nym/releases/tag/nym-vpn-alpha-0.0.1)
* Extract files with `unzip` command or manually as you are used toi
* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command:
```sh
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 config directory `nym-vpn` you just created
* Create the main config file called `config.toml` in the same directory with this content:
```toml
env_config_file = "$HOME/Library/Application Support/nym-vpn/"
entry_node_location = "DE" # two letters country code
```
**Note:** Some users had a problem to access their home config folder on macOS, in that case save the configuration files in the same directory where you downloaded your `nym-vpn` binary as following:
* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `.env` (yes just like that) in the same directory like `nym-vpn` binaries.
* Create the main config file called `config.toml` in the very same directory with this content:
```toml
env_config_file = "$HOME/Library/Application Support/nym-vpn/"
entry_node_location = "DE" # two letters country code
```
#### Thread `main` panicked
If you see a message like:
```sh
thread 'main' panicked at /Users/runner/.cargo/git/checkouts/mullvadvpn-app-a575cf705b5dfd76/ccfbaa2/talpid-routing/src/unix.rs:301:30:
```
Restart your wifi connection and start again.
#### macOS alert on NymVPN UI startup
If you are running NymVPN on mac OS for the first time, you may see this alert message:
![](images/image3.png)
1. Head to System Settings -> Privacy & Security and click `Allow anyway`
![](images/image5.png)
2. Confirm with your password or TouchID
3. Possibly you may have to confirm again upon running the application
#### Missing `jq` error
In case of missing `jq` on Linux (Debian) install it with:
```sh
# Linux (Debian)
sudo apt-get install jq
# macOS
brew install jq
```
On some Linux distributions however the [script](./nym-vpn.md#testssh) returns `jq` error even if your system claims that `jq is already the newest version`.
In that case, comment the `jq` check in the script as follows:
```sh
#if ! command -v jq &>/dev/null; then
# echo "jq is not installed. Please install jq to proceed."
# exit 1
#fi
```
#### Error current_time: not found
When running `sudo sh ./test.sh` you may see an error like: `93: current_time: not found`. This has something to do with the `current_time` setup of your system and on itself shall not have a negative impact on the test. It has nothing to do with the client at all as it only relates to the code in our testing script.
#### Not connecting to the endpoint
In case the automatic download of all the Gateways fail (and it shouldn't), you do an easy manual work around:
1. Open the list of Gateways created by API [here](https://nymvpn.com/en/ccc/api/gateways)
2. On top click on `JSON` option (shall be default view) and `Save`
3. Save it as `data.json` to the `nym-vpn-tests` folder
4. Replace line 3 in the [script `tests.sh`](./nym-vpn.md#testssh) with:
```sh
NEW_ENDPOINT="http://localhost:8000/data.json"
```
5. In a new terminal window run:
```sh
python3 -m http.server 8000
```
6. Continue with the steps listed in [testing section](./nym-vpn.md#testing)
@@ -0,0 +1,51 @@
# NymVPN alpha
<div style="padding:56.25% 0 0 0;position:relative;"><iframe src="https://player.vimeo.com/video/897010658?h=1f55870fe6&amp;badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;top:0;left:0;width:100%;height:100%;" title="NYMVPN alpha demo 37C3"></iframe></div><script src="https://player.vimeo.com/api/player.js"></script>
We are honored to present NymVPN, a client that uses [Nym Mixnet](https://nymtech.net) to anonymise all of a user's internet traffic through either a 5-hop mixnet (for a full network privacy) or the faster 2-hop decentralised VPN (with some extra features). At this event we have the unique opportunity to be part of the initial alpha testing. The following pages provide a how-to guide, explaining steps to install and run NymVPN CLI and GUI on our testing environment Nym Sandbox.
## NymVPN
The following overview provides 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.
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 Gateways, and is not passed between Mix Nodes.
The client can optionally do the first hop (local client to 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.
## NymVPN Guide & FAQ pages
To download, install and test NymVPN alpha, visit pages listed below:
* [GNU/Linux](nym-vpn-linux.md)
* [Mac OS](nym-vpn-mac.md)
* [Testing scripts](nym-vpn-testing.md)
* [Troubleshooting](nym-vpn-troubleshooting.md)
* [NymVPN FAQ](nym-vpn-faq.md)
## Goals of testing
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 the event. 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 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-2) where all the steps will be provided**.
```
+1
View File
@@ -22,6 +22,7 @@ REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYXD="https://rpc.nymtech.net"
NYM_API="https://validator.nymtech.net/api/"
NYXD_WS="wss://rpc.nymtech.net/websocket"
EXPLORER_API="https://explorer.nymtech.net/api/"
DKG_TIME_CONFIGURATION="259200,300,300,60,60,1209600"
+8 -6
View File
@@ -13,15 +13,17 @@ DENOMS_EXPONENT=6
REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa
MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n16a32stm6kknhq5cc8rx77elr66pygf2hfszw7wvpq746x3uffylqkjar4l
GROUP_CONTRACT_ADDRESS=n1pd7kfgvr5tpcv0xnlv46c4jsq9jg2r799xxrcwqdm4l2jhq2pjwqrmz5ju
MULTISIG_CONTRACT_ADDRESS=n14ph4e660eyqz0j36zlkaey4zgzexm5twkmjlqaequxr2cjm9eprqsmad6k
COCONUT_DKG_CONTRACT_ADDRESS=n1ahg0erc2fs6xx3j5m8sfx3ryuzdjh6kf6qm9plsf865fltekyrfsesac6a
EPHEMERA_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n13902g92xfefeyzuyed49snlm5fxv5ms6mdq5kvrut27hasdw5a9q9vyw6c
GROUP_CONTRACT_ADDRESS=n18nczmqw6adwxg2wnlef3hf0etf8anccafp2pjpul5rrtmv96umyq5mv7t5
MULTISIG_CONTRACT_ADDRESS=n1q3zzxl78rlmxv3vn0uf4vkyz285lk8q2xzne299yt9x6mpfgk90qukuzmv
COCONUT_DKG_CONTRACT_ADDRESS=n1jsz20ggp5a6v76j060erkzvxmeus8htlpl77yxp878f0gf95cyaq6p2pee
NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs36asfp
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4
EPHEMERA_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0"
EXPLORER_API=https://sandbox-explorer.nymtech.net/api
NYXD="https://rpc.sandbox.nymtech.net"
NYXD="https://sandbox-validator1.nymtech.net"
NYM_API="https://sandbox-nym-api1.nymtech.net/api"
+1 -1
View File
@@ -60,7 +60,7 @@ impl RunExternalNodeCmd {
.with_members_provider(members_provider)?
.build();
let shutdown = TaskManager::new(10);
let mut shutdown = TaskManager::new(10);
let shutdown_listener = shutdown.subscribe();
tokio::spawn(ephemera.run(shutdown_listener));
+1 -1
View File
@@ -79,7 +79,7 @@ impl ExplorerApi {
self.wait_for_interrupt(shutdown).await
}
async fn wait_for_interrupt(&self, shutdown: TaskManager) {
async fn wait_for_interrupt(&self, mut shutdown: TaskManager) {
let _res = shutdown.catch_interrupt().await;
log::info!("Stopping explorer API");
}
+17 -8
View File
@@ -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,
@@ -388,7 +391,9 @@ impl<St> Gateway<St> {
))
}
async fn wait_for_interrupt(shutdown: TaskManager) -> Result<(), Box<dyn Error + Send + Sync>> {
async fn wait_for_interrupt(
mut shutdown: TaskManager,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let res = shutdown.catch_interrupt().await;
log::info!("Stopping nym gateway");
res
@@ -524,21 +529,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(())
}
+3 -4
View File
@@ -18,7 +18,7 @@ rust-version = "1.58.1"
[dependencies]
axum = { workspace = true }
anyhow = "1.0.40"
anyhow = { workspace = true }
bs58 = "0.4.0"
clap = { workspace = true, features = ["cargo", "derive"] }
colored = "2.0"
@@ -28,7 +28,6 @@ futures = { workspace = true }
humantime-serde = "1.0"
lazy_static = "1.4.0"
log = { workspace = true }
pretty_env_logger = "0.4.0"
rand = "0.7.3"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
@@ -41,7 +40,7 @@ cfg-if = "1.0.0"
thiserror = { workspace = true }
## tracing
tracing = { version = "0.1.37", optional = true }
tracing = { workspace = true, optional = true }
opentelemetry = { version = "0.19.0", optional = true }
@@ -65,7 +64,7 @@ nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
cpu-cycles = { path = "../cpu-cycles", optional = true }
[dev-dependencies]
tokio = { version = "1.21.2", features = [
tokio = { workspace = true, features = [
"rt-multi-thread",
"net",
"signal",
+1 -1
View File
@@ -215,7 +215,7 @@ impl MixNode {
})
}
async fn wait_for_interrupt(&self, shutdown: TaskManager) {
async fn wait_for_interrupt(&self, mut shutdown: TaskManager) {
let _res = shutdown.catch_interrupt().await;
log::info!("Stopping nym mixnode");
}
@@ -187,18 +187,18 @@ pub struct EpochCredentialsResponse {
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialsResponse {
// note: BTreeMap returns ordered results so it's fine to use it with pagination
pub credentials: BTreeMap<i64, IssuedCredentialInner>,
pub credentials: BTreeMap<i64, IssuedCredentialBody>,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialResponse {
pub credential: Option<IssuedCredentialInner>,
pub credential: Option<IssuedCredentialBody>,
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IssuedCredentialInner {
pub struct IssuedCredentialBody {
pub credential: IssuedCredential,
pub signature: identity::Signature,
+2 -2
View File
@@ -3,7 +3,7 @@
use crate::coconut::error::Result;
use crate::coconut::storage::models::IssuedCredential;
use nym_api_requests::coconut::models::IssuedCredentialInner;
use nym_api_requests::coconut::models::IssuedCredentialBody;
use nym_api_requests::coconut::models::IssuedCredentialsResponse;
use std::collections::BTreeMap;
@@ -14,7 +14,7 @@ pub(crate) fn build_credentials_response(
for raw_credential in raw {
let id = raw_credential.id;
let api_issued = IssuedCredentialInner::try_from(raw_credential)?;
let api_issued = IssuedCredentialBody::try_from(raw_credential)?;
let old = credentials.insert(id, api_issued);
if old.is_some() {
// why do we panic here rather than return an error? because it's a critical failure because
+1 -8
View File
@@ -4,7 +4,7 @@
use crate::coconut::error::CoconutError;
use nym_api_requests::coconut::models::{
EpochCredentialsResponse, IssuedCredential as ApiIssuedCredential,
IssuedCredentialInner as ApiIssuedCredentialInner,
IssuedCredentialBody as ApiIssuedCredentialInner,
};
use nym_api_requests::coconut::BlindedSignatureResponse;
use nym_coconut::{Base58, BlindedSignature};
@@ -97,13 +97,6 @@ impl TryFrom<IssuedCredential> for BlindedSignature {
}
}
impl IssuedCredential {
// safety: this should only ever be called on sanitized data from the database,
// thus the unwraps are fine (if somebody manually entered their db file and modified it, it's on them)
// pub fn private_attribute_commitments(&self) -> Vec<Scalar>
// pub fn public_attributes(&self)
}
pub fn join_attributes<I, M>(attrs: I) -> String
where
I: IntoIterator<Item = M>,
+2 -2
View File
@@ -9,7 +9,7 @@ use async_trait::async_trait;
use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg};
use cw3::ProposalResponse;
use cw4::MemberResponse;
use nym_api_requests::coconut::models::{IssuedCredentialInner, IssuedCredentialResponse};
use nym_api_requests::coconut::models::{IssuedCredentialBody, IssuedCredentialResponse};
use nym_api_requests::coconut::{
BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse,
};
@@ -716,7 +716,7 @@ impl TestFixture {
serde_json::from_str(&response.into_string().await.unwrap()).unwrap()
}
async fn issued_unchecked(&self, id: i64) -> IssuedCredentialInner {
async fn issued_unchecked(&self, id: i64) -> IssuedCredentialBody {
self.issued_credential(id)
.await
.unwrap()
@@ -146,7 +146,7 @@ impl PacketPreparer {
let initialisation_backoff = Duration::from_secs(30);
loop {
let gateways = self.validator_cache.gateways_all().await;
let mixnodes = self.validator_cache.mixnodes_basic().await;
let mixnodes = self.validator_cache.mixnodes_all_basic().await;
if gateways.len() < minimum_full_routes {
self.topology_wait_backoff(initialisation_backoff).await;
@@ -179,12 +179,21 @@ impl PacketPreparer {
async fn all_mixnodes_and_gateways(&self) -> (Vec<MixNodeBond>, Vec<GatewayBond>) {
info!("Obtaining network topology...");
let mixnodes = self.validator_cache.mixnodes_basic().await;
let mixnodes = self.validator_cache.mixnodes_all_basic().await;
let gateways = self.validator_cache.gateways_all().await;
(mixnodes, gateways)
}
async fn filtered_mixnodes_and_gateways(&self) -> (Vec<MixNodeBond>, Vec<GatewayBond>) {
info!("Obtaining network topology...");
let mixnodes = self.validator_cache.mixnodes_filtered_basic().await;
let gateways = self.validator_cache.gateways_filtered().await;
(mixnodes, gateways)
}
pub(crate) fn try_parse_mix_bond(&self, mix: &MixNodeBond) -> Result<mix::Node, String> {
let identity = mix.mix_node.identity_key.clone();
mix.try_into().map_err(|_| identity)
@@ -208,7 +217,7 @@ impl PacketPreparer {
n: usize,
blacklist: &mut HashSet<String>,
) -> Option<Vec<TestRoute>> {
let (mixnodes, gateways) = self.all_mixnodes_and_gateways().await;
let (mixnodes, gateways) = self.filtered_mixnodes_and_gateways().await;
// separate mixes into layers for easier selection
let mut layered_mixes = HashMap::new();
for mix in mixnodes {
+14 -13
View File
@@ -182,19 +182,20 @@ impl NymContractCache {
}
}
pub async fn mixnodes_basic(&self) -> Vec<MixNodeBond> {
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
Ok(cache) => cache
.mixnodes
.clone()
.into_iter()
.map(|bond| bond.bond_information)
.collect(),
Err(err) => {
error!("{err}");
Vec::new()
}
}
pub async fn mixnodes_filtered_basic(&self) -> Vec<MixNodeBond> {
self.mixnodes_filtered()
.await
.into_iter()
.map(|bond| bond.bond_information)
.collect()
}
pub async fn mixnodes_all_basic(&self) -> Vec<MixNodeBond> {
self.mixnodes_all()
.await
.into_iter()
.map(|bond| bond.bond_information)
.collect()
}
pub async fn gateways_filtered(&self) -> Vec<GatewayBond> {
+1 -1
View File
@@ -65,7 +65,7 @@ pub(crate) async fn execute(args: Args) -> anyhow::Result<()> {
config.validate()?;
let shutdown_handlers = start_nym_api_tasks(config).await?;
let mut shutdown_handlers = start_nym_api_tasks(config).await?;
let res = shutdown_handlers
.task_manager_handle
+1 -1
View File
@@ -119,7 +119,7 @@ impl StorageManager {
r#"
SELECT
d.identity as "identity: String",
AVG(reliability) as "value: f32"
CASE WHEN count(*) > 3 THEN AVG(reliability) ELSE 100 END as "value: f32"
FROM
gateway_details d
JOIN
+4 -78
View File
@@ -697,7 +697,6 @@ version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"jobserver",
"libc",
]
@@ -1014,8 +1013,7 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#67718aa27a96efb9881e927a8f998c94b885093c"
dependencies = [
"prost",
"prost-types",
@@ -1025,8 +1023,7 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea"
source = "git+https://github.com/jstuczyn/cosmos-rust?branch=nym-temp/all-validator-features#67718aa27a96efb9881e927a8f998c94b885093c"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -1866,26 +1863,6 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enum-iterator"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401"
dependencies = [
"enum-iterator-derive",
]
[[package]]
name = "enum-iterator-derive"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.28",
]
[[package]]
name = "enumflags2"
version = "0.7.7"
@@ -2469,19 +2446,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "git2"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c"
dependencies = [
"bitflags 1.3.2",
"libc",
"libgit2-sys",
"log",
"url",
]
[[package]]
name = "glib"
version = "0.15.12"
@@ -3205,15 +3169,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.64"
@@ -3313,18 +3268,6 @@ version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "libgit2-sys"
version = "0.13.5+1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba"
dependencies = [
"cc",
"libc",
"libz-sys",
"pkg-config",
]
[[package]]
name = "libloading"
version = "0.7.4"
@@ -3352,18 +3295,6 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "libz-sys"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "line-wrap"
version = "0.1.1"
@@ -7651,18 +7582,13 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vergen"
version = "7.4.3"
version = "8.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37"
checksum = "1290fd64cc4e7d3c9b07d7f333ce0ce0007253e32870e632624835cc80b83939"
dependencies = [
"anyhow",
"cfg-if",
"enum-iterator",
"getset",
"git2",
"rustc_version",
"rustversion",
"thiserror",
"time",
]
+48
View File
@@ -0,0 +1,48 @@
[package]
name = "nym-validator-rewarder"
version = "0.1.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
edition.workspace = true
license = "GPL-3"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
bip39 = { workspace = true, features = ["zeroize"] }
cosmwasm-std.workspace = true
clap = { workspace = true, features = ["cargo"] }
futures.workspace = true
serde = { workspace = true, features = ["derive"] }
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["rt-multi-thread", "time", "macros"] }
tracing.workspace = true
time.workspace = true
url.workspace = true
zeroize.workspace = true
serde_with = "3.4.0"
sha2 = "0.10.8"
humantime = "2.1.0"
humantime-serde = "1.0"
# internal
nym-bin-common = { path = "../common/bin-common", features = ["output_format"] }
nym-config = { path = "../common/config" }
nym-coconut = { path = "../common/nymcoconut" }
nym-crypto = { path = "../common/crypto", features = ["asymmetric"] }
nym-credentials = { path = "../common/credentials" }
nym-network-defaults = { path = "../common/network-defaults" }
nym-task = { path = "../common/task" }
nym-validator-client = { path = "../common/client-libs/validator-client" }
nym-coconut-dkg-common = { path = "../common/cosmwasm-smart-contracts/coconut-dkg"}
nym-coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract"}
nyxd-scraper = { path = "../common/nyxd-scraper" }
[build-dependencies]
sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
#[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!("./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,71 @@
/*
* Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: GPL-3.0-only
*/
CREATE TABLE rewarding_epoch
(
id INTEGER NOT NULL PRIMARY KEY,
start_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
end_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
budget TEXT NOT NULL,
spent TEXT NOT NULL,
rewarding_tx TEXT,
rewarding_error TEXT
);
CREATE TABLE epoch_block_signing
(
rewarding_epoch_id INTEGER NOT NULL PRIMARY KEY REFERENCES rewarding_epoch (id),
total_voting_power_at_epoch_start INTEGER NOT NULL,
num_blocks INTEGER NOT NULL,
budget TEXT NOT NULL
);
CREATE TABLE block_signing_reward
(
rewarding_epoch_id INTEGER NOT NULL REFERENCES rewarding_epoch (id),
validator_consensus_address TEXT NOT NULL,
operator_account TEXT NOT NULL,
amount TEXT NOT NULL,
voting_power BIGINT NOT NULL,
voting_power_share TEXT NOT NULL,
signed_blocks INTEGER NOT NULL,
signed_blocks_percent TEXT NOT NULL,
UNIQUE (rewarding_epoch_id, operator_account)
);
CREATE TABLE epoch_credential_issuance
(
rewarding_epoch_id INTEGER NOT NULL PRIMARY KEY REFERENCES rewarding_epoch (id),
starting_dkg_epoch INTEGER NOT NULL,
ending_dkg_epoch INTEGER NOT NULL,
total_issued_partial_credentials INTEGER NOT NULL,
budget TEXT NOT NULL
);
CREATE TABLE malformed_credential
(
rewarding_epoch_id INTEGER NOT NULL REFERENCES rewarding_epoch (id)
);
CREATE TABLE credential_issuance_reward
(
rewarding_epoch_id INTEGER NOT NULL REFERENCES rewarding_epoch (id),
operator_account TEXT NOT NULL,
amount TEXT NOT NULL,
api_endpoint TEXT NOT NULL,
issued_partial_credentials INTEGER NOT NULL,
issued_credentials_share TEXT NOT NULL,
validated_issued_credentials INTEGER NOT NULL,
UNIQUE (rewarding_epoch_id, operator_account)
);
-- CREATE TABLE credential_verification_reward
-- (
--
-- );
@@ -0,0 +1,17 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use nym_bin_common::bin_info_owned;
use nym_bin_common::output_format::OutputFormat;
#[derive(clap::Args, Debug)]
pub(crate) struct Args {
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
pub(crate) fn execute(args: Args) -> Result<(), NymRewarderError> {
println!("{}", args.output.format(&bin_info_owned!()));
Ok(())
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::ConfigOverridableArgs;
use crate::config::{default_config_directory, default_data_directory, Config};
use crate::error::NymRewarderError;
use nym_network_defaults::NymNetworkDetails;
use std::path::PathBuf;
use std::{fs, io};
#[derive(Debug, clap::Args)]
pub struct Args {
#[command(flatten)]
config_override: ConfigOverridableArgs,
/// Specifies custom location for the configuration file of nym validators rewarder.
#[clap(long)]
custom_config_path: Option<PathBuf>,
/// Mnemonic used for rewarding operations
#[clap(long)]
mnemonic: bip39::Mnemonic,
/// Overwrite existing configuration file.
#[clap(long, short)]
force: bool,
}
fn init_paths() -> io::Result<()> {
fs::create_dir_all(default_data_directory())?;
fs::create_dir_all(default_config_directory())
}
pub(crate) fn execute(args: Args) -> Result<(), NymRewarderError> {
let path = args
.custom_config_path
.clone()
.unwrap_or(Config::default_location());
if path.exists() && !args.force {
return Err(NymRewarderError::ExistingConfig { path });
}
init_paths().map_err(|source| NymRewarderError::PathInitialisationFailure { source })?;
let network = NymNetworkDetails::new_from_env();
let nyxd = network.endpoints[0].nyxd_url();
let Some(websocket) = network.endpoints[0].websocket_url() else {
return Err(NymRewarderError::UnavailableWebsocketUrl);
};
let config = Config::new(args.mnemonic, websocket, nyxd).with_override(args.config_override);
config.ensure_is_valid()?;
config
.save_to_path(&path)
.map_err(|source| NymRewarderError::ConfigSaveFailure { path, source })?;
Ok(())
}
+136
View File
@@ -0,0 +1,136 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NymRewarderError;
use clap::{Parser, Subcommand};
use humantime_serde::re::humantime;
use nym_bin_common::bin_info;
use nym_validator_client::nyxd::Coin;
use std::path::PathBuf;
use std::sync::OnceLock;
use tracing::{debug, error};
use url::Url;
pub mod build_info;
pub mod init;
pub mod run;
pub mod upgrade_helpers;
// Helper for passing LONG_VERSION to clap
fn pretty_build_info_static() -> &'static str {
static PRETTY_BUILD_INFORMATION: OnceLock<String> = OnceLock::new();
PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print())
}
#[derive(Parser, Debug)]
#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)]
pub(crate) struct Cli {
/// Path pointing to an env file that configures the validator rewarder and overrides any preconfigured values.
#[clap(short, long)]
pub(crate) config_env_file: Option<std::path::PathBuf>,
/// Flag used for disabling the printed banner in tty.
#[clap(long)]
pub(crate) no_banner: bool,
#[clap(subcommand)]
command: Commands,
}
impl Cli {
pub(crate) async fn execute(self) -> Result<(), NymRewarderError> {
match self.command {
Commands::Init(args) => init::execute(args),
Commands::Run(args) => run::execute(args).await,
Commands::BuildInfo(args) => build_info::execute(args),
}
}
}
#[derive(Debug, clap::Args)]
pub struct ConfigOverridableArgs {
#[clap(long)]
pub disable_block_signing_rewarding: bool,
#[clap(long)]
pub disable_credential_issuance_rewarding: bool,
#[clap(long)]
pub credential_monitor_run_interval: Option<humantime::Duration>,
#[clap(long)]
pub credential_monitor_min_validation: Option<usize>,
#[clap(long)]
pub credential_monitor_sampling_rate: Option<f64>,
#[clap(long)]
pub scraper_endpoint: Option<Url>,
#[clap(long)]
pub nyxd_endpoint: Option<Url>,
#[clap(long)]
pub epoch_budget: Option<Coin>,
#[clap(long)]
pub epoch_duration: Option<humantime::Duration>,
#[clap(long)]
pub block_signing_reward_ratio: Option<f64>,
#[clap(long)]
pub credential_issuance_reward_ratio: Option<f64>,
#[clap(long)]
pub credential_verification_reward_ratio: Option<f64>,
}
#[derive(Subcommand, Debug)]
pub(crate) enum Commands {
/// Initialise a validator rewarder with persistent config.toml file.
Init(init::Args),
/// Run the validator rewarder with the preconfigured settings.
Run(run::Args),
/// Show build information of this binary
BuildInfo(build_info::Args),
}
fn try_load_current_config(custom_path: &Option<PathBuf>) -> Result<Config, NymRewarderError> {
let config_path = custom_path.clone().unwrap_or(Config::default_location());
debug!(
"attempting to load configuration file from {}",
config_path.display()
);
if let Ok(cfg) = Config::read_from_toml_file(&config_path) {
cfg.ensure_is_valid()?;
return Ok(cfg);
}
upgrade_helpers::try_upgrade_config(&config_path)?;
let config = Config::read_from_toml_file(&config_path).map_err(|err| {
error!(
"Failed to load config. Are you sure you have run `init` before? (Error was: {err})",
);
err
})?;
config.ensure_is_valid()?;
Ok(config)
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
Cli::command().debug_assert();
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::{try_load_current_config, ConfigOverridableArgs};
use crate::error::NymRewarderError;
use crate::rewarder::Rewarder;
use std::path::PathBuf;
#[derive(Debug, clap::Args)]
pub struct Args {
#[command(flatten)]
config_override: ConfigOverridableArgs,
/// Specifies custom location for the configuration file of nym validators rewarder.
#[clap(long)]
custom_config_path: Option<PathBuf>,
}
pub(crate) async fn execute(args: Args) -> Result<(), NymRewarderError> {
let config =
try_load_current_config(&args.custom_config_path)?.with_override(args.config_override);
Rewarder::new(config).await?.run().await
}
@@ -0,0 +1,10 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use std::path::Path;
pub(crate) fn try_upgrade_config<P: AsRef<Path>>(config_path: P) -> Result<(), NymRewarderError> {
let _ = config_path;
Ok(())
}
+278
View File
@@ -0,0 +1,278 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::persistence::paths::ValidatorRewarderPaths;
use crate::config::r#override::ConfigOverride;
use crate::config::template::CONFIG_TEMPLATE;
use crate::error::NymRewarderError;
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, NYM_DIR,
};
use nym_validator_client::nyxd::Coin;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::io;
use std::path::{Path, PathBuf};
use std::time::Duration;
use tracing::debug;
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};
pub mod r#override;
pub mod persistence;
mod template;
const DEFAULT_REWARDER_DIR: &str = "validators-rewarder";
#[allow(clippy::inconsistent_digit_grouping)]
const DEFAULT_MIX_REWARDING_BUDGET: u128 = 1000_000000;
const DEFAULT_MIX_REWARDING_DENOM: &str = "unym";
const DEFAULT_EPOCH_DURATION: Duration = Duration::from_secs(60 * 60);
const DEFAULT_MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(10 * 60);
const DEFAULT_MONITOR_MIN_VALIDATE: usize = 10;
const DEFAULT_MONITOR_SAMPLING_RATE: f64 = 0.10;
/// Get default path to rewarder's config directory.
/// It should get resolved to `$HOME/.nym/validators-rewarder/config`
pub fn default_config_directory() -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_REWARDER_DIR)
.join(DEFAULT_CONFIG_DIR)
}
/// Get default path to rewarder's config file.
/// It should get resolved to `$HOME/.nym/validators-rewarder/config/config.toml`
pub fn default_config_filepath() -> PathBuf {
default_config_directory().join(DEFAULT_CONFIG_FILENAME)
}
/// Get default path to rewarder's data directory where files, such as keys, are stored.
/// It should get resolved to `$HOME/.nym/validators-rewarder/data`
pub fn default_data_directory() -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_REWARDER_DIR)
.join(DEFAULT_DATA_DIR)
}
#[derive(Debug, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct Config {
// additional metadata holding on-disk location of this config file
#[serde(skip)]
#[zeroize(skip)]
pub(crate) save_path: Option<PathBuf>,
#[zeroize(skip)]
#[serde(default)]
pub rewarding: Rewarding,
#[zeroize(skip)]
#[serde(default)]
pub block_signing: BlockSigning,
#[zeroize(skip)]
#[serde(default)]
pub issuance_monitor: IssuanceMonitor,
#[zeroize(skip)]
pub nyxd_scraper: NyxdScraper,
#[serde(flatten)]
pub base: Base,
#[zeroize(skip)]
pub storage_paths: ValidatorRewarderPaths,
}
impl NymConfigTemplate for Config {
fn template(&self) -> &'static str {
CONFIG_TEMPLATE
}
}
impl Config {
pub fn new(mnemonic: bip39::Mnemonic, websocket_url: Url, nyxd_url: Url) -> Self {
Config {
save_path: None,
rewarding: Rewarding::default(),
block_signing: Default::default(),
issuance_monitor: IssuanceMonitor::default(),
nyxd_scraper: NyxdScraper { websocket_url },
base: Base {
upstream_nyxd: nyxd_url,
mnemonic,
},
storage_paths: Default::default(),
}
}
pub fn scraper_config(&self) -> nyxd_scraper::Config {
nyxd_scraper::Config {
websocket_url: self.nyxd_scraper.websocket_url.clone(),
rpc_url: self.base.upstream_nyxd.clone(),
database_path: self.storage_paths.nyxd_scraper.clone(),
}
}
pub fn ensure_is_valid(&self) -> Result<(), NymRewarderError> {
self.rewarding.ratios.ensure_is_valid()?;
Ok(())
}
pub fn r#override<O: ConfigOverride>(&mut self, r#override: O) {
r#override.override_config(self)
}
pub fn with_override<O: ConfigOverride>(mut self, r#override: O) -> Self {
self.r#override(r#override);
self
}
// simple wrapper that reads config file and assigns path location
fn read_from_path<P: AsRef<Path>>(path: P) -> Result<Self, NymRewarderError> {
let path = path.as_ref();
let mut loaded: Config = read_config_from_toml_file(path).map_err(|source| {
NymRewarderError::ConfigLoadFailure {
path: path.to_path_buf(),
source,
}
})?;
loaded.ensure_is_valid()?;
loaded.save_path = Some(path.to_path_buf());
debug!("loaded config file from {}", path.display());
Ok(loaded)
}
pub fn read_from_toml_file<P: AsRef<Path>>(path: P) -> Result<Self, NymRewarderError> {
Self::read_from_path(path)
}
pub fn default_location() -> PathBuf {
default_config_filepath()
}
pub fn save_to_default_location(&self) -> io::Result<()> {
let config_save_location: PathBuf = Self::default_location();
save_formatted_config_to_file(self, config_save_location)
}
pub fn save_to_path<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
save_formatted_config_to_file(self, path)
}
}
#[derive(Debug, Deserialize, Serialize, Zeroize, ZeroizeOnDrop)]
pub struct Base {
/// Url to the upstream instance of nyxd to use for any queries and rewarding.
#[zeroize(skip)]
pub upstream_nyxd: Url,
/// Mnemonic to the nyx account distributing the rewards
pub(crate) mnemonic: bip39::Mnemonic,
}
#[serde_as]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Rewarding {
/// Specifies total budget for the epoch
#[serde_as(as = "DisplayFromStr")]
pub epoch_budget: Coin,
#[serde(with = "humantime_serde")]
pub epoch_duration: Duration,
pub ratios: RewardingRatios,
}
impl Default for Rewarding {
fn default() -> Self {
Rewarding {
epoch_budget: Coin::new(DEFAULT_MIX_REWARDING_BUDGET, DEFAULT_MIX_REWARDING_DENOM),
epoch_duration: DEFAULT_EPOCH_DURATION,
ratios: RewardingRatios::default(),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize)]
pub struct RewardingRatios {
/// The percent of the epoch reward being awarded for block signing.
pub block_signing: f64,
/// The percent of the epoch reward being awarded for credential issuance.
pub credential_issuance: f64,
/// The percent of the epoch reward being awarded for credential verification.
pub credential_verification: f64,
// /// The percent of the epoch reward given to Nym.
// pub nym: f64,
}
impl Default for RewardingRatios {
fn default() -> Self {
RewardingRatios {
block_signing: 0.67,
credential_issuance: 0.33,
credential_verification: 0.0,
// nym: 0.0,
}
}
}
impl RewardingRatios {
pub fn ensure_is_valid(&self) -> Result<(), NymRewarderError> {
if self.block_signing + self.credential_verification + self.credential_issuance != 1.0 {
todo!()
}
Ok(())
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct NyxdScraper {
/// Url to the websocket endpoint of a validator, for example `wss://rpc.nymtech.net/websocket`
pub websocket_url: Url,
// TODO: debug with everything that's currently hardcoded in the scraper
}
#[derive(Debug, Clone, Deserialize, Serialize, Copy)]
pub struct BlockSigning {
/// Specifies whether credential issuance for block signing is enabled.
pub enabled: bool,
}
impl Default for BlockSigning {
fn default() -> Self {
BlockSigning { enabled: true }
}
}
#[derive(Debug, Clone, Deserialize, Serialize, Copy)]
pub struct IssuanceMonitor {
/// Specifies whether credential issuance monitoring (and associated rewards) are enabled.
pub enabled: bool,
#[serde(with = "humantime_serde")]
pub run_interval: Duration,
/// Defines the minimum number of credentials the monitor will validate
/// regardless of the sampling rate
pub min_validate_per_issuer: usize,
/// The sampling rate of the issued credentials
pub sampling_rate: f64,
}
impl Default for IssuanceMonitor {
fn default() -> Self {
IssuanceMonitor {
enabled: true,
run_interval: DEFAULT_MONITOR_RUN_INTERVAL,
min_validate_per_issuer: DEFAULT_MONITOR_MIN_VALIDATE,
sampling_rate: DEFAULT_MONITOR_SAMPLING_RATE,
}
}
}
@@ -0,0 +1,63 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::cli::ConfigOverridableArgs;
use crate::config::Config;
pub trait ConfigOverride {
fn override_config(self, config: &mut Config);
}
impl ConfigOverride for ConfigOverridableArgs {
fn override_config(self, config: &mut Config) {
if self.disable_block_signing_rewarding {
config.block_signing.enabled = false
}
if self.disable_credential_issuance_rewarding {
config.issuance_monitor.enabled = false
}
if let Some(credential_monitor_run_interval) = self.credential_monitor_run_interval {
config.issuance_monitor.run_interval = credential_monitor_run_interval.into()
}
if let Some(credential_monitor_min_validation) = self.credential_monitor_min_validation {
config.issuance_monitor.min_validate_per_issuer = credential_monitor_min_validation
}
if let Some(credential_monitor_sampling_rate) = self.credential_monitor_sampling_rate {
config.issuance_monitor.sampling_rate = credential_monitor_sampling_rate
}
if let Some(scraper_endpoint) = self.scraper_endpoint {
config.nyxd_scraper.websocket_url = scraper_endpoint
}
if let Some(nyxd_endpoint) = self.nyxd_endpoint {
config.base.upstream_nyxd = nyxd_endpoint
}
if let Some(epoch_budget) = self.epoch_budget {
config.rewarding.epoch_budget = epoch_budget
}
if let Some(epoch_duration_secs) = self.epoch_duration {
config.rewarding.epoch_duration = epoch_duration_secs.into()
}
if let Some(block_signing_reward_ratio) = self.block_signing_reward_ratio {
config.rewarding.ratios.block_signing = block_signing_reward_ratio;
}
if let Some(credential_issuance_reward_ratio) = self.credential_issuance_reward_ratio {
config.rewarding.ratios.credential_issuance = credential_issuance_reward_ratio;
}
if let Some(credential_verification_reward_ratio) =
self.credential_verification_reward_ratio
{
config.rewarding.ratios.credential_verification = credential_verification_reward_ratio;
}
}
}
@@ -0,0 +1,4 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
pub mod paths;
@@ -0,0 +1,25 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::default_data_directory;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
pub const DEFAULT_SCRAPER_DB_FILENAME: &str = "nyxd_blocks.sqlite";
pub const DEFAULT_REWARD_HISTORY_DB_FILENAME: &str = "rewards.sqlite";
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ValidatorRewarderPaths {
pub nyxd_scraper: PathBuf,
pub reward_history: PathBuf,
}
impl Default for ValidatorRewarderPaths {
fn default() -> Self {
ValidatorRewarderPaths {
nyxd_scraper: default_data_directory().join(DEFAULT_SCRAPER_DB_FILENAME),
reward_history: default_data_directory().join(DEFAULT_REWARD_HISTORY_DB_FILENAME),
}
}
}
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
// While using normal toml marshalling would have been way simpler with less overhead,
// I think it's useful to have comments attached to the saved config file to explain behaviour of
// particular fields.
// Note: any changes to the template must be reflected in the appropriate structs.
pub(crate) const CONFIG_TEMPLATE: &str = r#"
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
# Url to the upstream instance of nyxd to use for any queries and rewarding.
upstream_nyxd = '{{ upstream_nyxd }}'
# Mnemonic to the nyx account distributing the rewards
mnemonic = '{{ mnemonic }}'
[storage_paths]
nyxd_scraper = '{{ storage_paths.nyxd_scraper }}'
reward_history = '{{ storage_paths.reward_history }}'
[rewarding]
# Specifies total budget for the epoch
epoch_budget = '{{ rewarding.epoch_budget }}'
epoch_duration = '{{ rewarding.epoch_duration }}'
[rewarding.ratios]
# The percent of the epoch reward being awarded for block signing.
block_signing = {{ rewarding.ratios.block_signing }}
# The percent of the epoch reward being awarded for credential issuance.
credential_issuance = {{ rewarding.ratios.credential_issuance }}
# The percent of the epoch reward being awarded for credential verification.
credential_verification = {{ rewarding.ratios.credential_verification }}
[block_signing]
# Specifies whether credential issuance for block signing is enabled.
enabled = {{ block_signing.enabled }}
[issuance_monitor]
# Specifies whether credential issuance monitoring (and associated rewards) are enabled.
enabled = {{ issuance_monitor.enabled }}
run_interval = '{{ issuance_monitor.run_interval }}'
# Defines the minimum number of credentials the monitor will validate
# regardless of the sampling rate
min_validate_per_issuer = {{ issuance_monitor.min_validate_per_issuer }}
# The sampling rate of the issued credentials
sampling_rate = {{ issuance_monitor.sampling_rate }}
[nyxd_scraper]
# Url to the websocket endpoint of a validator, for example `wss://rpc.nymtech.net/websocket`
websocket_url = '{{ nyxd_scraper.websocket_url }}'
"#;
+166
View File
@@ -0,0 +1,166 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_coconut::CoconutError;
use nym_validator_client::nym_api::error::NymAPIError;
use nym_validator_client::nyxd::error::NyxdError;
use nym_validator_client::nyxd::tx::ErrorReport;
use nym_validator_client::nyxd::{AccountId, Coin, Hash};
use std::io;
use std::path::PathBuf;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NymRewarderError {
#[error("experienced internal database error: {0}")]
InternalDatabaseError(#[from] sqlx::Error),
#[error("failed to perform startup SQL migration: {0}")]
StartupMigrationFailure(#[from] sqlx::migrate::MigrateError),
#[error(
"failed to load config file using path '{}'. detailed message: {source}", path.display()
)]
ConfigLoadFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error(
"failed to save config file using path '{}'. detailed message: {source}", path.display()
)]
ConfigSaveFailure {
path: PathBuf,
#[source]
source: io::Error,
},
#[error("failed to initialise paths")]
PathInitialisationFailure {
#[source]
source: io::Error,
},
#[error("there already exists a config file at: {}. if you want to overwrite its content, use --force flag", path.display())]
ExistingConfig { path: PathBuf },
// TODO: I think this one should get split into more, explicit, variants
#[error(transparent)]
NyxdFailure(#[from] NyxdError),
#[error("the provided rewarding ratios don't add up to 1. ratios: {ratios:?}")]
InvalidRewardingRatios { ratios: Vec<f32> },
#[error("chain scraping failure: {source}")]
ScraperFailure {
#[from]
source: nyxd_scraper::error::ScraperError,
},
// this should never happen but unwrapping everywhere was more cumbersome than just propagating the error
#[error("failed to determine epoch boundaries: {0}")]
TimeComponentFailure(#[from] time::error::ComponentRange),
#[error(
"could not convert operator address: {operator_address} to a nym account address: {source}"
)]
MalformedBech32Address {
operator_address: String,
#[source]
source: ErrorReport,
},
#[error(
"could not convert validator public key: {public_key} into a consensus address: {source}"
)]
MalformedConsensusPublicKey {
public_key: String,
#[source]
source: ErrorReport,
},
#[error("somehow the total voting power was negative: {val}")]
NegativeTotalVotingPower { val: i64 },
#[error("somehow the signed blocks was negative: {val}")]
NegativeSignedBlocks { val: i64 },
#[error("could not find details for validator {consensus_address}")]
MissingValidatorDetails { consensus_address: String },
#[error("api url ({raw}) provided by {runner_account} is invalid: {source}")]
MalformedApiUrl {
raw: String,
runner_account: AccountId,
#[source]
source: url::ParseError,
},
#[error("failed to resolve nym-api query: {0}")]
ApiQueryFailure(#[from] NymAPIError),
#[error("operator {runner_account} didn't return all requested credentials! requested {requested} but got only {received}")]
IncompleteRequest {
runner_account: AccountId,
requested: usize,
received: usize,
},
#[error("the following private attribute commitment is malformed: {raw}: {source}")]
MalformedCredentialCommitment {
raw: String,
#[source]
source: CoconutError,
},
#[error("the partial verification key for runner {runner} is malformed: {source}")]
MalformedPartialVerificationKey {
runner: String,
#[source]
source: CoconutError,
},
#[error("could not verify the blinded credential")]
BlindVerificationFailure,
#[error("the same deposit transaction ({tx_hash}) has been used for multiple issued credentials! {first} and {other}")]
DuplicateDepositHash {
tx_hash: Hash,
first: i64,
other: i64,
},
#[error("could not find the deposit value in the event of transaction {tx_hash}")]
DepositValueNotFound { tx_hash: Hash },
#[error("could not find the deposit info in the event of transaction {tx_hash}")]
DepositInfoNotFound { tx_hash: Hash },
#[error("the provided deposit value of transaction {tx_hash} is inconsistent. got '{request:?}' while the value on chain is '{on_chain}'")]
InconsistentDepositValue {
tx_hash: Hash,
request: Option<String>,
on_chain: String,
},
#[error("the provided deposit info of transaction {tx_hash} is inconsistent. got '{request:?}' while the value on chain is '{on_chain}'")]
InconsistentDepositInfo {
tx_hash: Hash,
request: Option<String>,
on_chain: String,
},
#[error("the current rewarder balance is insufficient to start the process. The epoch budget is: {} while we currently have {}. (the minimum is set to {})", .0.epoch_budget, .0.balance, .0.minimum)]
InsufficientRewarderBalance(Box<InsufficientBalance>),
#[error("the scraper websocket endpoint hasn't been provided")]
UnavailableWebsocketUrl,
}
#[derive(Debug)]
pub struct InsufficientBalance {
pub epoch_budget: Coin,
pub balance: Coin,
pub minimum: Coin,
}
+35
View File
@@ -0,0 +1,35 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
use crate::cli::Cli;
use clap::{crate_name, crate_version, Parser};
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
use nym_network_defaults::setup_env;
pub mod cli;
pub mod config;
pub mod error;
mod rewarder;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// std::env::set_var(
// "RUST_LOG",
// "debug,tendermint_rpc=warn,h2=warn,hyper=warn,rustls=warn,reqwest=warn,tungstenite=warn,async_tungstenite=warn",
// );
let args = Cli::parse();
setup_env(args.config_env_file.as_ref());
setup_tracing_logger();
if !args.no_banner {
maybe_print_banner(crate_name!(), crate_version!());
}
args.execute().await?;
Ok(())
}
@@ -0,0 +1,139 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use crate::rewarder::block_signing::types::{EpochSigningResults, RawValidatorResult};
use crate::rewarder::epoch::Epoch;
use crate::rewarder::nyxd_client::NyxdClient;
use nym_validator_client::nyxd::module_traits::staking;
use nym_validator_client::nyxd::PageRequest;
use nyxd_scraper::NyxdScraper;
use std::cmp::min;
use std::collections::HashMap;
use std::ops::Range;
use tracing::info;
pub(crate) mod types;
pub struct EpochSigning {
pub(crate) nyxd_client: NyxdClient,
pub(crate) nyxd_scraper: NyxdScraper,
}
impl EpochSigning {
async fn get_voting_power(
&self,
address: &str,
height_range: Range<i64>,
) -> Result<Option<i64>, NymRewarderError> {
for height in height_range {
if let Some(precommit) = self
.nyxd_scraper
.storage
.get_precommit(address, height)
.await?
{
return Ok(Some(precommit.voting_power));
}
}
Ok(None)
}
// TODO: eventually this will be replaced by scraping the data from the staking module in the scraper itself
async fn get_validator_details(
&self,
height: i64,
) -> Result<Vec<staking::Validator>, NymRewarderError> {
// first attempt to get it via the historical info.
// if that fails, attempt to use current block information to at least get **something**
if let Some(validators) = self.nyxd_client.historical_info(height).await?.hist {
Ok(validators.valset)
} else {
let mut page_request = None;
let mut response = Vec::new();
loop {
let mut res = self.nyxd_client.validators(page_request).await?;
response.append(&mut res.validators);
let Some(pagination) = res.pagination else {
break;
};
page_request = Some(PageRequest {
key: pagination.next_key,
offset: 0,
limit: 0,
count_total: false,
reverse: false,
});
}
Ok(response)
}
}
pub(crate) async fn get_signed_blocks_results(
&self,
current_epoch: Epoch,
) -> Result<EpochSigningResults, NymRewarderError> {
info!(
"looking up block signers for epoch {} ({} - {})",
current_epoch.id,
current_epoch.start_rfc3339(),
current_epoch.end_rfc3339()
);
let validators = self.nyxd_scraper.storage.get_all_known_validators().await?;
let epoch_start = current_epoch.start_time;
let epoch_end = current_epoch.end_time;
let first_block = self
.nyxd_scraper
.storage
.get_first_block_height_after(epoch_start)
.await?
.unwrap_or_default();
let last_block = self
.nyxd_scraper
.storage
.get_last_block_height_before(epoch_end)
.await?
.unwrap_or_default();
// each validator MUST be online at some point during the first 20 blocks, otherwise they're not getting anything.
let vp_range_end = min(first_block + 20, last_block);
let vp_range = first_block..vp_range_end;
let mut total_vp = 0;
let mut signed_in_epoch = HashMap::new();
// for each validator, with a valid voting power, get number of signed blocks in the rewarding epoch
for validator in validators {
let Some(vp) = self
.get_voting_power(&validator.consensus_address, vp_range.clone())
.await?
else {
continue;
};
total_vp += vp;
let signed = self
.nyxd_scraper
.storage
.get_signed_between_times(&validator.consensus_address, epoch_start, epoch_end)
.await?;
signed_in_epoch.insert(validator, RawValidatorResult::new(signed, vp));
}
let total = self
.nyxd_scraper
.storage
.get_blocks_between(epoch_start, epoch_end)
.await?;
let details = self.get_validator_details(last_block).await?;
EpochSigningResults::construct(total, total_vp, signed_in_epoch, details)
}
}
@@ -0,0 +1,141 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use crate::rewarder::helpers::{consensus_pubkey_to_address, operator_account_to_owner_account};
use cosmwasm_std::{Decimal, Uint128};
use nym_validator_client::nyxd::module_traits::staking;
use nym_validator_client::nyxd::{AccountId, Coin};
use nyxd_scraper::models;
use std::collections::HashMap;
use tracing::info;
#[derive(Debug)]
pub struct ValidatorSigning {
pub validator: models::Validator,
pub staking_details: staking::Validator,
pub operator_account: AccountId,
pub voting_power_at_epoch_start: i64,
pub voting_power_ratio: Decimal,
pub signed_blocks: i32,
pub ratio_signed: Decimal,
}
impl ValidatorSigning {
pub fn moniker(&self) -> String {
self.staking_details
.description
.as_ref()
.map(|d| d.moniker.clone())
.unwrap_or("UNKNOWN MONIKER".to_string())
}
pub fn reward_amount(&self, signing_budget: &Coin) -> Coin {
let amount =
Uint128::new(signing_budget.amount) * self.ratio_signed * self.voting_power_ratio;
Coin::new(amount.u128(), &signing_budget.denom)
}
}
#[derive(Debug)]
pub struct EpochSigningResults {
pub blocks: i64,
pub total_voting_power_at_epoch_start: i64,
pub validators: Vec<ValidatorSigning>,
}
pub struct RawValidatorResult {
pub signed_blocks: i32,
pub voting_power: i64,
}
impl RawValidatorResult {
pub fn new(signed_blocks: i32, voting_power: i64) -> Self {
Self {
signed_blocks,
voting_power,
}
}
}
impl EpochSigningResults {
pub fn construct(
blocks: i64,
total_vp: i64,
validator_results: HashMap<models::Validator, RawValidatorResult>,
validator_details: Vec<staking::Validator>,
) -> Result<Self, NymRewarderError> {
let Ok(total_vp_u64): Result<u64, _> = total_vp.try_into() else {
return Err(NymRewarderError::NegativeTotalVotingPower { val: total_vp });
};
let Ok(blocks_u64): Result<u64, _> = blocks.try_into() else {
return Err(NymRewarderError::NegativeSignedBlocks { val: blocks });
};
let mut validator_details: HashMap<_, _> = validator_details
.into_iter()
.filter(|v| v.consensus_pubkey.is_some())
.map(|v| {
// safety: we know the key is definitely set as we just filtered the iterator based on that condition
#[allow(clippy::unwrap_used)]
consensus_pubkey_to_address(v.consensus_pubkey.unwrap())
.map(|addr| (addr.to_string(), v))
})
.collect::<Result<_, _>>()?;
let mut validators = Vec::new();
for (validator, raw_results) in validator_results {
let vp: u64 = raw_results.voting_power.try_into().unwrap_or_default();
let signed: u64 = raw_results.signed_blocks.try_into().unwrap_or_default();
let voting_power_ratio = Decimal::from_ratio(vp, total_vp_u64);
debug_assert!(signed <= blocks_u64);
let ratio_signed = Decimal::from_ratio(signed, blocks_u64);
let staking_details = validator_details
.remove(&validator.consensus_address)
.ok_or_else(|| NymRewarderError::MissingValidatorDetails {
consensus_address: validator.consensus_address.clone(),
})?;
let operator_account =
operator_account_to_owner_account(&staking_details.operator_address)?;
validators.push(ValidatorSigning {
validator,
staking_details,
operator_account,
voting_power_at_epoch_start: raw_results.voting_power,
voting_power_ratio,
signed_blocks: raw_results.signed_blocks,
ratio_signed,
})
}
Ok(EpochSigningResults {
blocks,
total_voting_power_at_epoch_start: total_vp,
validators,
})
}
pub fn rewarding_amounts(&self, budget: &Coin) -> Vec<(AccountId, Vec<Coin>)> {
self.validators
.iter()
.inspect(|v| {
info!(
"validator {} will receive {} at address {} for block signing work",
v.moniker(),
v.reward_amount(budget),
v.operator_account
);
})
.map(|v| (v.operator_account.clone(), vec![v.reward_amount(budget)]))
.collect()
}
}
@@ -0,0 +1,58 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config;
use crate::error::NymRewarderError;
use crate::rewarder::credential_issuance::monitor::CredentialIssuanceMonitor;
use crate::rewarder::credential_issuance::types::{CredentialIssuanceResults, MonitoringResults};
use crate::rewarder::epoch::Epoch;
use crate::rewarder::nyxd_client::NyxdClient;
use nym_task::TaskClient;
use tracing::info;
mod monitor;
pub mod types;
pub struct CredentialIssuance {
monitoring_results: MonitoringResults,
}
impl CredentialIssuance {
pub(crate) async fn new(
epoch: Epoch,
nyxd_client: &NyxdClient,
) -> Result<Self, NymRewarderError> {
Ok(CredentialIssuance {
monitoring_results: MonitoringResults::new_initial(epoch, nyxd_client).await?,
})
}
pub(crate) fn start_monitor(
&self,
monitor_config: config::IssuanceMonitor,
nyxd_client: NyxdClient,
task_client: TaskClient,
) {
let monitoring_results = self.monitoring_results.clone();
let mut monitor =
CredentialIssuanceMonitor::new(monitor_config, nyxd_client, monitoring_results);
tokio::spawn(async move { monitor.run(task_client).await });
}
pub(crate) async fn get_issued_credentials_results(
&self,
current_epoch: Epoch,
) -> Result<CredentialIssuanceResults, NymRewarderError> {
info!(
"looking up credential issuers for epoch {} ({} - {})",
current_epoch.id,
current_epoch.start_rfc3339(),
current_epoch.end_rfc3339()
);
let raw_results = self.monitoring_results.finish_epoch().await;
Ok(raw_results.into())
}
}
@@ -0,0 +1,340 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::config;
use crate::error::NymRewarderError;
use crate::rewarder::credential_issuance::types::{
CredentialIssuer, MonitoringResults, RawOperatorResult,
};
use crate::rewarder::helpers::api_client;
use crate::rewarder::nyxd_client::NyxdClient;
use bip39::rand::prelude::SliceRandom;
use bip39::rand::thread_rng;
use nym_coconut::{
hash_to_scalar, verify_partial_blind_signature, Base58, G1Projective, Parameters,
VerificationKey,
};
use nym_coconut_dkg_common::types::EpochId;
use nym_credentials::coconut::bandwidth::BandwidthVoucher;
use nym_task::TaskClient;
use nym_validator_client::nym_api::{IssuedCredential, IssuedCredentialBody, NymApiClientExt};
use nym_validator_client::nyxd::Hash;
use std::cmp::max;
use std::collections::HashMap;
use std::sync::OnceLock;
use tokio::time::interval;
use tracing::{debug, error, info, instrument, trace, warn};
pub(crate) fn bandwidth_voucher_params() -> &'static Parameters {
static BANDWIDTH_CREDENTIAL_PARAMS: OnceLock<Parameters> = OnceLock::new();
BANDWIDTH_CREDENTIAL_PARAMS.get_or_init(BandwidthVoucher::default_parameters)
}
pub struct CredentialIssuanceMonitor {
nyxd_client: NyxdClient,
monitoring_results: MonitoringResults,
config: config::IssuanceMonitor,
// map of validator address -> transaction hash -> issued credential
// (ideally we'd have hashed the AccountId directly, but it doesn't implement `Hash`)
seen_deposits: HashMap<String, HashMap<Hash, i64>>,
}
impl CredentialIssuanceMonitor {
pub fn new(
config: config::IssuanceMonitor,
nyxd_client: NyxdClient,
monitoring_results: MonitoringResults,
) -> CredentialIssuanceMonitor {
CredentialIssuanceMonitor {
config,
nyxd_client,
monitoring_results,
seen_deposits: Default::default(),
}
}
fn validate_credential_signature(
&mut self,
_issued_credential: &IssuedCredentialBody,
) -> Result<(), NymRewarderError> {
warn!("unimplemented: public key sharing mechanism");
// let plaintext = issued_credential.credential.signable_plaintext();
// if !operator_public_key.verify(&plaintext) {
// ...
// }
Ok(())
}
fn check_deposit_reuse(
&mut self,
runner: &str,
credential_id: i64,
deposit_tx: Hash,
) -> Result<bool, NymRewarderError> {
// check if we've seen this tx hash before
// TODO: we should persist them in the database in case we crash
if let Some(known_runner) = self.seen_deposits.get_mut(runner) {
if let Some(&used) = known_runner.get(&deposit_tx) {
return if used != credential_id {
Err(NymRewarderError::DuplicateDepositHash {
tx_hash: deposit_tx,
first: used,
other: credential_id,
})
} else {
debug!("we have already verified this credential before");
Ok(true)
};
} else {
known_runner.insert(deposit_tx, credential_id);
}
}
Ok(false)
}
async fn validate_deposit(
&mut self,
issued_credential: &IssuedCredentialBody,
) -> Result<(), NymRewarderError> {
// check if this deposit even exists
let deposit_tx = issued_credential.credential.tx_hash;
let (deposit_value, deposit_info) = self
.nyxd_client
.get_deposit_transaction_attributes(deposit_tx)
.await?;
trace!("deposit exists");
// check if the deposit values match
let credential_value = issued_credential.credential.public_attributes.first();
let credential_info = issued_credential.credential.public_attributes.get(1);
if credential_value != Some(&deposit_value) {
return Err(NymRewarderError::InconsistentDepositValue {
tx_hash: deposit_tx,
request: credential_value.cloned(),
on_chain: deposit_value,
});
}
trace!("credential values matches the deposit");
if credential_info != Some(&deposit_info) {
return Err(NymRewarderError::InconsistentDepositInfo {
tx_hash: deposit_tx,
request: credential_info.cloned(),
on_chain: deposit_info,
});
}
trace!("credential info matches the deposit");
Ok(())
}
fn verify_credential(
&mut self,
vk: &VerificationKey,
credential: IssuedCredential,
) -> Result<(), NymRewarderError> {
let public_attributes = credential
.public_attributes
.iter()
.map(hash_to_scalar)
.collect::<Vec<_>>();
#[allow(clippy::map_identity)]
let attributes_refs = public_attributes.iter().collect::<Vec<_>>();
let mut public_attribute_commitments =
Vec::with_capacity(credential.bs58_encoded_private_attributes_commitments.len());
for raw_cm in credential.bs58_encoded_private_attributes_commitments {
match G1Projective::try_from_bs58(&raw_cm) {
Ok(cm) => public_attribute_commitments.push(cm),
Err(source) => {
return Err(NymRewarderError::MalformedCredentialCommitment {
raw: raw_cm,
source,
})
}
}
}
// actually do verify the credential now
if !verify_partial_blind_signature(
bandwidth_voucher_params(),
&public_attribute_commitments,
&attributes_refs,
&credential.blinded_partial_credential,
vk,
) {
return Err(NymRewarderError::BlindVerificationFailure);
}
trace!("credential correctly verifies");
Ok(())
}
// TODO: currently we can't obtain public key of the runner in order to verify the signature
#[instrument(skip_all, fields(credential_id = credential_id, deposit = %issued_credential.credential.tx_hash))]
async fn validate_issued_credential(
&mut self,
runner: String,
credential_id: i64,
issued_credential: IssuedCredentialBody,
vk: &VerificationKey,
) -> Result<(), NymRewarderError> {
self.validate_credential_signature(&issued_credential)?;
let deposit_tx = issued_credential.credential.tx_hash;
// make sure the issuer is not using the same deposit for multiple credentials
let already_checked = self.check_deposit_reuse(&runner, credential_id, deposit_tx)?;
if already_checked {
return Ok(());
}
// check the correctness of the deposit itself
self.validate_deposit(&issued_credential).await?;
// check if the partial credential correctly verifies
self.verify_credential(vk, issued_credential.credential)?;
Ok(())
}
fn sample_credential_ids(&self, first_id: i64, total_issued: i64) -> Vec<i64> {
let credential_range: Vec<_> = (first_id..first_id + total_issued).collect();
let issued = credential_range.len();
let sampled = if issued <= self.config.min_validate_per_issuer {
credential_range
} else {
let mut rng = thread_rng();
let sample_size = max(
self.config.min_validate_per_issuer,
(issued as f64 * self.config.sampling_rate) as usize,
);
credential_range
.choose_multiple(&mut rng, sample_size)
.copied()
.collect::<Vec<_>>()
};
sampled
}
#[instrument(skip(self, issuer, epoch_id), fields(dkg_epoch = epoch_id, issuer = %issuer.operator_account, url = issuer.api_runner), err(Display))]
async fn check_issuer(
&mut self,
epoch_id: EpochId,
issuer: CredentialIssuer,
) -> Result<RawOperatorResult, NymRewarderError> {
info!("checking the issuer's credentials...");
debug!("checking the issuer's credentials...");
let api_client = api_client(&issuer)?;
let epoch_credentials = api_client.epoch_credentials(epoch_id).await?;
let Some(first_id) = epoch_credentials.first_epoch_credential_id else {
// no point in doing anything more - if they haven't issued anything, there's nothing to verify
debug!("no credentials issued this epoch",);
return Ok(RawOperatorResult::new_empty(
issuer.operator_account,
issuer.api_runner,
));
};
trace!("issued credentials: {epoch_credentials:?}");
let sampled = self.sample_credential_ids(first_id, epoch_credentials.total_issued as i64);
let request_size = sampled.len();
trace!("sampled credentials to validate: {sampled:?}");
let credentials = api_client.issued_credentials(sampled.clone()).await?;
if credentials.credentials.len() != request_size {
// TODO: we need some signatures here to actually show the validator is cheating
return Err(NymRewarderError::IncompleteRequest {
runner_account: issuer.operator_account,
requested: request_size,
received: credentials.credentials.len(),
});
}
for (id, credential) in credentials.credentials {
trace!("checking credential {id}...");
// TODO: insert the failure information, alongside the signature, to the evidence db
if let Err(err) = self
.validate_issued_credential(
issuer.operator_account.to_string(),
id,
credential,
&issuer.verification_key,
)
.await
{
error!(
"failed to verify credential {id} from {}!!: {err}",
issuer.operator_account
);
return Err(err);
}
}
Ok(RawOperatorResult {
operator_account: issuer.operator_account,
api_runner: issuer.api_runner,
issued_credentials: epoch_credentials.total_issued,
validated_credentials: sampled,
})
}
async fn check_issuers(&mut self) -> Result<(), NymRewarderError> {
info!("checking credential issuers");
let epoch = self.nyxd_client.dkg_epoch().await?;
let issuers = self
.nyxd_client
.get_credential_issuers(epoch.epoch_id)
.await?;
let mut results = Vec::with_capacity(issuers.len());
for issuer in issuers {
let address = issuer.operator_account.clone();
// we could parallelize it, but we're running the test so infrequently (relatively speaking)
// that doing it sequentially is fine
match self.check_issuer(epoch.epoch_id, issuer).await {
Ok(res) => results.push(res),
Err(err) => {
// TODO: insert info to the db
error!("failed to check credential issuance of {address}: {err}")
}
}
}
self.monitoring_results
.append_run_results(epoch.epoch_id as u32, results)
.await;
Ok(())
}
pub async fn run(&mut self, mut task_client: TaskClient) {
info!("starting");
let mut run_interval = interval(self.config.run_interval);
while !task_client.is_shutdown() {
tokio::select! {
biased;
_ = task_client.recv() => {
info!("received shutdown");
break
}
_ = run_interval.tick() => {
if let Err(err) = self.check_issuers().await {
error!("failed to perform credential issuance check: {err}")
}
}
}
}
}
}
@@ -0,0 +1,349 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::NymRewarderError;
use crate::rewarder::epoch::Epoch;
use crate::rewarder::helpers::api_client;
use crate::rewarder::nyxd_client::NyxdClient;
use cosmwasm_std::{Addr, Decimal, Uint128};
use nym_coconut::{Base58, VerificationKey};
use nym_coconut_dkg_common::verification_key::ContractVKShare;
use nym_validator_client::nym_api::NymApiClientExt;
use nym_validator_client::nyxd::{AccountId, Coin};
use std::collections::{HashMap, HashSet};
use std::mem;
use std::sync::Arc;
use tokio::sync::Mutex;
use tracing::{info, warn};
#[derive(Clone)]
pub struct MonitoringResults {
inner: Arc<Mutex<MonitoringResultsInner>>,
}
impl MonitoringResults {
pub(crate) async fn new_initial(
initial_epoch: Epoch,
nyxd_client: &NyxdClient,
) -> Result<Self, NymRewarderError> {
let epoch = nyxd_client.dkg_epoch().await?;
let issuers = nyxd_client.get_credential_issuers(epoch.epoch_id).await?;
let mut initial_results = HashMap::new();
for issuer in issuers {
let issuer_account = issuer.operator_account.to_string();
let mut raw_issuer = RawOperatorIssuing {
api_runner: issuer.api_runner.clone(),
runner_account: issuer.operator_account.clone(),
per_epoch: Default::default(),
};
let Ok(api_client) = api_client(&issuer) else {
warn!("failed to create api client for {issuer_account}");
initial_results.insert(issuer_account, raw_issuer);
continue;
};
let Ok(epoch_credentials) = api_client.epoch_credentials(epoch.epoch_id).await else {
warn!("failed to get initial epoch credentials from {issuer_account}");
initial_results.insert(issuer_account, raw_issuer);
continue;
};
raw_issuer.per_epoch.insert(
epoch.epoch_id as u32,
IssuedEpochCredentials {
issued_since_monitor_started: 0,
validated_ids: Default::default(),
last_total_issued: epoch_credentials.total_issued,
},
);
initial_results.insert(issuer_account, raw_issuer);
}
Ok(MonitoringResults {
inner: Arc::new(Mutex::new(MonitoringResultsInner::new(
initial_epoch,
epoch.epoch_id as u32,
initial_results,
))),
})
}
pub(crate) async fn append_run_results(&self, dkg_epoch: u32, results: Vec<RawOperatorResult>) {
let mut guard = self.inner.lock().await;
for result in results {
let Some(entry) = guard.operators.get_mut(result.operator_account.as_ref()) else {
// if this is the first time we're seeing this data, make sure to set the current results as the starting point
guard.operators.insert(
result.operator_account.to_string(),
RawOperatorIssuing::new_empty(dkg_epoch, result),
);
continue;
};
let Some(epoch_data) = entry.per_epoch.get_mut(&dkg_epoch) else {
// similar situation to the above, if we don't have the proper initial data, set it to what we got now
entry
.per_epoch
.insert(dkg_epoch, IssuedEpochCredentials::new_initial(&result));
continue;
};
let issued = result.issued_credentials - epoch_data.last_total_issued;
epoch_data.last_total_issued = result.issued_credentials;
for validated in result.validated_credentials {
epoch_data.validated_ids.insert(validated);
}
epoch_data.issued_since_monitor_started += issued;
}
}
pub(crate) async fn finish_epoch(&self) -> MonitoringResultsInner {
let mut guard = self.inner.lock().await;
let next_epoch = guard.epoch.next();
// safety: whenever the monitor results are constructed, we always have at least a single value there
#[allow(clippy::unwrap_used)]
let latest_dkg_epoch = guard.dkg_epochs.pop().unwrap();
// only keep results from the latest dkg epoch (but after resetting the counters)
let mut to_keep = HashMap::new();
for (runner, result) in &guard.operators {
let mut kept_epoch = HashMap::new();
if let Some(data) = result.per_epoch.get(&latest_dkg_epoch) {
kept_epoch.insert(
latest_dkg_epoch,
IssuedEpochCredentials {
issued_since_monitor_started: 0,
validated_ids: Default::default(),
last_total_issued: data.last_total_issued,
},
);
}
to_keep.insert(
runner.clone(),
RawOperatorIssuing {
api_runner: result.api_runner.clone(),
runner_account: result.runner_account.clone(),
per_epoch: kept_epoch,
},
);
}
let next_results = MonitoringResultsInner {
epoch: next_epoch,
dkg_epochs: vec![latest_dkg_epoch],
operators: to_keep,
};
mem::replace(&mut guard, next_results)
}
}
pub(crate) struct MonitoringResultsInner {
pub(crate) epoch: Epoch,
pub(crate) dkg_epochs: Vec<u32>,
// map from operator's account to their results
pub(crate) operators: HashMap<String, RawOperatorIssuing>,
}
impl From<MonitoringResultsInner> for CredentialIssuanceResults {
fn from(value: MonitoringResultsInner) -> Self {
let total_issued = value
.operators
.values()
.map(|o| {
let operator_issued: u32 = o
.per_epoch
.values()
.map(|e| e.issued_since_monitor_started)
.sum();
operator_issued
})
.sum();
CredentialIssuanceResults {
total_issued_partial_credentials: total_issued,
dkg_epochs: value.dkg_epochs,
api_runners: value
.operators
.into_values()
.map(|runner| {
let issued_ratio = if total_issued == 0 {
Decimal::zero()
} else {
Decimal::from_ratio(runner.issued_credentials(), total_issued)
};
OperatorIssuing {
issued_ratio,
issued_credentials: runner.issued_credentials(),
validated_credentials: runner.validated_credentials(),
api_runner: runner.api_runner,
runner_account: runner.runner_account,
}
})
.collect(),
}
}
}
impl MonitoringResultsInner {
fn new(
epoch: Epoch,
initial_dkg_epoch: u32,
initial_operators: HashMap<String, RawOperatorIssuing>,
) -> MonitoringResultsInner {
MonitoringResultsInner {
epoch,
dkg_epochs: vec![initial_dkg_epoch],
operators: initial_operators,
}
}
}
pub(crate) struct RawOperatorResult {
pub(crate) operator_account: AccountId,
pub(crate) api_runner: String,
// how many credentials the operator claims to have issued in **TOTAL** in this **DKG** epoch
pub(crate) issued_credentials: u32,
pub(crate) validated_credentials: Vec<i64>,
}
impl RawOperatorResult {
pub(crate) fn new_empty(operator_account: AccountId, api_runner: String) -> RawOperatorResult {
RawOperatorResult {
operator_account,
api_runner,
issued_credentials: 0,
validated_credentials: Default::default(),
}
}
}
pub struct RawOperatorIssuing {
pub api_runner: String,
pub runner_account: AccountId,
pub per_epoch: HashMap<u32, IssuedEpochCredentials>,
}
impl RawOperatorIssuing {
pub fn new_empty(epoch: u32, raw_result: RawOperatorResult) -> RawOperatorIssuing {
let mut per_epoch = HashMap::new();
per_epoch.insert(epoch, IssuedEpochCredentials::new_initial(&raw_result));
RawOperatorIssuing {
api_runner: raw_result.api_runner,
runner_account: raw_result.operator_account,
per_epoch,
}
}
pub fn issued_credentials(&self) -> u32 {
self.per_epoch
.values()
.map(|e| e.issued_since_monitor_started)
.sum()
}
pub fn validated_credentials(&self) -> u32 {
let ids: usize = self.per_epoch.values().map(|e| e.validated_ids.len()).sum();
ids as u32
}
}
pub struct IssuedEpochCredentials {
pub issued_since_monitor_started: u32,
pub validated_ids: HashSet<i64>,
pub last_total_issued: u32,
}
impl IssuedEpochCredentials {
pub fn new_initial(raw: &RawOperatorResult) -> Self {
IssuedEpochCredentials {
issued_since_monitor_started: 0,
validated_ids: raw.validated_credentials.iter().copied().collect(),
last_total_issued: raw.issued_credentials,
}
}
}
pub struct OperatorIssuing {
pub api_runner: String,
pub runner_account: AccountId,
pub issued_ratio: Decimal,
pub issued_credentials: u32,
pub validated_credentials: u32,
}
impl OperatorIssuing {
pub fn reward_amount(&self, signing_budget: &Coin) -> Coin {
let amount = Uint128::new(signing_budget.amount) * self.issued_ratio;
Coin::new(amount.u128(), &signing_budget.denom)
}
}
pub struct CredentialIssuanceResults {
pub total_issued_partial_credentials: u32,
pub dkg_epochs: Vec<u32>,
pub api_runners: Vec<OperatorIssuing>,
}
impl CredentialIssuanceResults {
pub fn rewarding_amounts(&self, budget: &Coin) -> Vec<(AccountId, Vec<Coin>)> {
self.api_runners
.iter()
.inspect(|a| {
info!(
"operator {} will receive {} at address {} for credential issuance work",
a.api_runner,
a.reward_amount(budget),
a.runner_account,
);
})
.map(|v| (v.runner_account.clone(), vec![v.reward_amount(budget)]))
.collect()
}
}
#[derive(Debug)]
pub struct CredentialIssuer {
// pub public_key: identity::PublicKey,
pub operator_account: AccountId,
pub api_runner: String,
pub verification_key: VerificationKey,
}
impl TryFrom<ContractVKShare> for CredentialIssuer {
type Error = NymRewarderError;
fn try_from(value: ContractVKShare) -> Result<Self, Self::Error> {
Ok(CredentialIssuer {
operator_account: addr_to_account_id(value.owner.clone()),
api_runner: value.announce_address,
verification_key: VerificationKey::try_from_bs58(value.share).map_err(|source| {
NymRewarderError::MalformedPartialVerificationKey {
runner: value.owner.to_string(),
source,
}
})?,
})
}
}
// safety: we're converting between different wrappers for bech32 addresses
// and we trust (reasonably so), the values coming out of registered dealers in the DKG contract
fn addr_to_account_id(addr: Addr) -> AccountId {
#[allow(clippy::unwrap_used)]
addr.as_str().parse().unwrap()
}
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use sqlx::FromRow;
use std::ops::Add;
use std::time::Duration;
use time::format_description::well_known::Rfc3339;
use time::OffsetDateTime;
const HOUR: Duration = Duration::from_secs(60 * 60);
#[derive(Debug, Clone, Copy, FromRow)]
pub struct Epoch {
pub id: i64,
pub start_time: OffsetDateTime,
pub end_time: OffsetDateTime,
}
impl Epoch {
pub fn first(epoch_duration: Duration) -> Result<Self, NymRewarderError> {
let start = OffsetDateTime::now_utc()
.add(HOUR)
.replace_nanosecond(0)?
.replace_microsecond(0)?
.replace_second(0)?;
Ok(Epoch {
id: 0,
start_time: start,
end_time: start + epoch_duration,
})
}
pub fn until_end(&self) -> Duration {
let now = OffsetDateTime::now_utc();
(self.end_time - now).try_into().unwrap_or_default()
}
pub fn next(&self) -> Self {
let duration = self.end_time - self.start_time;
Epoch {
id: self.id + 1,
start_time: self.end_time,
end_time: self.end_time + duration,
}
}
pub fn start_rfc3339(&self) -> String {
// safety: unwrap here is fine as we're using a predefined formatter
#[allow(clippy::unwrap_used)]
self.start_time.format(&Rfc3339).unwrap()
}
pub fn end_rfc3339(&self) -> String {
// safety: unwrap here is fine as we're using a predefined formatter
#[allow(clippy::unwrap_used)]
self.end_time.format(&Rfc3339).unwrap()
}
}
@@ -0,0 +1,50 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use crate::rewarder::credential_issuance::types::CredentialIssuer;
use nym_validator_client::nym_api;
use nym_validator_client::nyxd::{AccountId, PublicKey};
use nyxd_scraper::constants::{BECH32_CONSENSUS_ADDRESS_PREFIX, BECH32_PREFIX};
use sha2::{Digest, Sha256};
pub(crate) fn consensus_pubkey_to_address(
pubkey: PublicKey,
) -> Result<AccountId, NymRewarderError> {
let digest = Sha256::digest(pubkey.to_bytes()).to_vec();
// TODO: make those configurable, etc
AccountId::new(BECH32_CONSENSUS_ADDRESS_PREFIX, &digest[..20]).map_err(|source| {
NymRewarderError::MalformedConsensusPublicKey {
public_key: pubkey.to_string(),
source,
}
})
}
// it's just a matter of swapping bech32 prefixes and recalculating the checksum
pub(crate) fn operator_account_to_owner_account(
operator_address: &AccountId,
) -> Result<AccountId, NymRewarderError> {
AccountId::new(BECH32_PREFIX, &operator_address.to_bytes()).map_err(|source| {
NymRewarderError::MalformedBech32Address {
operator_address: operator_address.to_string(),
source,
}
})
}
pub(crate) fn api_client(issuer: &CredentialIssuer) -> Result<nym_api::Client, NymRewarderError> {
let url = match issuer.api_runner.parse() {
Ok(url) => url,
Err(source) => {
return Err(NymRewarderError::MalformedApiUrl {
raw: issuer.api_runner.clone(),
runner_account: issuer.operator_account.clone(),
source,
})
}
};
Ok(nym_api::Client::new(url, None))
}
+287
View File
@@ -0,0 +1,287 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::{InsufficientBalance, NymRewarderError};
use crate::rewarder::block_signing::types::EpochSigningResults;
use crate::rewarder::block_signing::EpochSigning;
use crate::rewarder::credential_issuance::types::CredentialIssuanceResults;
use crate::rewarder::credential_issuance::CredentialIssuance;
use crate::rewarder::epoch::Epoch;
use crate::rewarder::nyxd_client::NyxdClient;
use crate::rewarder::storage::RewarderStorage;
use nym_task::TaskManager;
use nym_validator_client::nyxd::{AccountId, Coin, Hash};
use nyxd_scraper::NyxdScraper;
use std::ops::Add;
use tokio::pin;
use tokio::time::{interval_at, Instant};
use tracing::{error, info, instrument};
mod block_signing;
mod credential_issuance;
mod epoch;
mod helpers;
mod nyxd_client;
mod storage;
mod tasks;
pub struct EpochRewards {
pub epoch: Epoch,
pub signing: Option<EpochSigningResults>,
pub credentials: Option<CredentialIssuanceResults>,
pub total_budget: Coin,
pub signing_budget: Coin,
pub credentials_budget: Coin,
}
impl EpochRewards {
pub fn amounts(&self) -> Vec<(AccountId, Vec<Coin>)> {
let mut amounts = Vec::new();
if let Some(signing) = &self.signing {
for signing_amount in signing.rewarding_amounts(&self.signing_budget) {
if signing_amount.1[0].amount != 0 {
amounts.push(signing_amount)
}
}
}
if let Some(credentials) = &self.credentials {
for credential_amount in credentials.rewarding_amounts(&self.credentials_budget) {
if credential_amount.1[0].amount != 0 {
amounts.push(credential_amount)
}
}
}
amounts
}
}
pub fn total_spent(amounts: &[(AccountId, Vec<Coin>)], denom: &str) -> Coin {
let amount = amounts.iter().map(|(_, amount)| amount[0].amount).sum();
Coin::new(amount, denom)
}
pub struct Rewarder {
config: Config,
current_epoch: Epoch,
storage: RewarderStorage,
nyxd_client: NyxdClient,
epoch_signing: Option<EpochSigning>,
credential_issuance: Option<CredentialIssuance>,
}
impl Rewarder {
pub async fn new(config: Config) -> Result<Self, NymRewarderError> {
let nyxd_client = NyxdClient::new(&config)?;
let storage = RewarderStorage::init(&config.storage_paths.reward_history).await?;
let current_epoch = if let Some(last_epoch) = storage.load_last_rewarding_epoch().await? {
last_epoch.next()
} else {
Epoch::first(config.rewarding.epoch_duration)?
};
let epoch_signing = if config.block_signing.enabled {
let nyxd_scraper = NyxdScraper::new(config.scraper_config()).await?;
Some(EpochSigning {
nyxd_scraper,
nyxd_client: nyxd_client.clone(),
})
} else {
None
};
let credential_issuance = if config.issuance_monitor.enabled {
Some(CredentialIssuance::new(current_epoch, &nyxd_client).await?)
} else {
None
};
if config.issuance_monitor.enabled || config.block_signing.enabled {
let balance = nyxd_client
.balance(&config.rewarding.epoch_budget.denom)
.await?;
let minimum = Coin::new(
config.rewarding.epoch_budget.amount * 100,
&config.rewarding.epoch_budget.denom,
);
if balance.amount < minimum.amount {
return Err(NymRewarderError::InsufficientRewarderBalance(Box::new(
InsufficientBalance {
epoch_budget: config.rewarding.epoch_budget.clone(),
balance,
minimum,
},
)));
}
}
Ok(Rewarder {
current_epoch,
credential_issuance,
epoch_signing,
nyxd_client,
storage,
config,
})
}
#[instrument(skip(self))]
async fn calculate_block_signing_rewards(
&mut self,
) -> Result<Option<EpochSigningResults>, NymRewarderError> {
info!("calculating reward shares");
if let Some(epoch_signing) = &mut self.epoch_signing {
Some(
epoch_signing
.get_signed_blocks_results(self.current_epoch)
.await,
)
} else {
None
}
.transpose()
}
#[instrument(skip(self))]
async fn calculate_credential_rewards(
&mut self,
) -> Result<Option<CredentialIssuanceResults>, NymRewarderError> {
info!("calculating reward shares");
if let Some(credential_issuance) = &mut self.credential_issuance {
Some(
credential_issuance
.get_issued_credentials_results(self.current_epoch)
.await,
)
} else {
None
}
.transpose()
}
async fn determine_epoch_rewards(&mut self) -> Result<EpochRewards, NymRewarderError> {
let epoch_budget = self.config.rewarding.epoch_budget.clone();
let denom = &epoch_budget.denom;
let signing_budget = Coin::new(
(self.config.rewarding.ratios.block_signing * epoch_budget.amount as f64) as u128,
denom,
);
let credentials_budget = Coin::new(
(self.config.rewarding.ratios.credential_issuance * epoch_budget.amount as f64) as u128,
denom,
);
let signing_rewards = self.calculate_block_signing_rewards().await?;
let credential_rewards = self.calculate_credential_rewards().await?;
Ok(EpochRewards {
epoch: self.current_epoch,
signing: signing_rewards,
credentials: credential_rewards,
total_budget: epoch_budget.clone(),
signing_budget,
credentials_budget,
})
}
#[instrument(skip(self))]
async fn send_rewards(
&self,
amounts: Vec<(AccountId, Vec<Coin>)>,
) -> Result<Hash, NymRewarderError> {
info!("sending rewards");
self.nyxd_client
.send_rewards(self.current_epoch, amounts)
.await
}
async fn handle_epoch_end(&mut self) {
info!("handling the epoch end");
let rewards = match self.determine_epoch_rewards().await {
Ok(rewards) => rewards,
Err(err) => {
error!("failed to determine epoch rewards: {err}");
return;
}
};
let rewarding_amounts = rewards.amounts();
let total_spent = total_spent(
&rewarding_amounts,
&self.config.rewarding.epoch_budget.denom,
);
let rewarding_result = self.send_rewards(rewarding_amounts).await;
if let Err(err) = self
.storage
.save_rewarding_information(rewards, total_spent, rewarding_result)
.await
{
error!("failed to persist rewarding information: {err}")
}
self.current_epoch = self.current_epoch.next();
}
pub async fn run(mut self) -> Result<(), NymRewarderError> {
info!("Starting nym validators rewarder");
// setup shutdowns
let mut task_manager = TaskManager::new(5);
if let Some(ref credential_issuance) = self.credential_issuance {
credential_issuance.start_monitor(
self.config.issuance_monitor,
self.nyxd_client.clone(),
task_manager.subscribe(),
);
}
if let Some(epoch_signing) = &self.epoch_signing {
epoch_signing.nyxd_scraper.start().await?;
epoch_signing.nyxd_scraper.wait_for_startup_sync().await;
}
let until_end = self.current_epoch.until_end();
info!(
"the first epoch will finish in {} secs",
until_end.as_secs()
);
let mut epoch_ticker = interval_at(
Instant::now().add(until_end),
self.config.rewarding.epoch_duration,
);
let shutdown_future = task_manager.catch_interrupt();
pin!(shutdown_future);
loop {
tokio::select! {
biased;
interrupt_res = &mut shutdown_future => {
info!("received interrupt");
if let Err(err) = interrupt_res {
error!("runtime interrupt failure: {err}")
}
break;
}
_ = epoch_ticker.tick() => self.handle_epoch_end().await
}
}
if let Some(epoch_signing) = self.epoch_signing {
epoch_signing.nyxd_scraper.stop().await;
}
Ok(())
}
}
@@ -0,0 +1,123 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Config;
use crate::error::NymRewarderError;
use crate::rewarder::credential_issuance::types::CredentialIssuer;
use nym_coconut_bandwidth_contract_common::events::{
COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO, DEPOSIT_VALUE,
};
use nym_coconut_dkg_common::types::Epoch;
use nym_network_defaults::NymNetworkDetails;
use nym_validator_client::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
use nym_validator_client::nyxd::helpers::find_tx_attribute;
use nym_validator_client::nyxd::module_traits::staking::{
QueryHistoricalInfoResponse, QueryValidatorsResponse,
};
use nym_validator_client::nyxd::{
AccountId, Coin, CosmWasmClient, Hash, PageRequest, StakingQueryClient,
};
use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient};
use std::ops::Deref;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Clone)]
pub struct NyxdClient {
inner: Arc<RwLock<DirectSigningHttpRpcNyxdClient>>,
}
impl NyxdClient {
pub(crate) fn new(config: &Config) -> Result<Self, NymRewarderError> {
let client_config =
nyxd::Config::try_from_nym_network_details(&NymNetworkDetails::new_from_env())?;
let nyxd_url = config.base.upstream_nyxd.as_str();
let mnemonic = config.base.mnemonic.clone();
let inner = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic(
client_config,
nyxd_url,
mnemonic,
)?;
Ok(NyxdClient {
inner: Arc::new(RwLock::new(inner)),
})
}
pub(crate) async fn balance(&self, denom: &str) -> Result<Coin, NymRewarderError> {
let guard = self.inner.read().await;
let address = guard.address();
Ok(guard
.get_balance(&address, denom.to_string())
.await?
.unwrap_or(Coin::new(0, denom)))
}
pub(crate) async fn send_rewards(
&self,
epoch: crate::rewarder::Epoch,
amounts: Vec<(AccountId, Vec<Coin>)>,
) -> Result<Hash, NymRewarderError> {
self.inner
.write()
.await
.send_multiple(amounts, format!("sending rewards for {epoch:?}"), None)
.await
.map(|res| res.hash)
.map_err(Into::into)
}
pub(crate) async fn historical_info(
&self,
height: i64,
) -> Result<QueryHistoricalInfoResponse, NymRewarderError> {
Ok(self.inner.read().await.historical_info(height).await?)
}
pub(crate) async fn validators(
&self,
pagination: Option<PageRequest>,
) -> Result<QueryValidatorsResponse, NymRewarderError> {
let guard = self.inner.read().await;
Ok(StakingQueryClient::validators(guard.deref(), "".to_string(), pagination).await?)
}
pub(crate) async fn dkg_epoch(&self) -> Result<Epoch, NymRewarderError> {
Ok(self.inner.read().await.get_current_epoch().await?)
}
pub(crate) async fn get_credential_issuers(
&self,
dkg_epoch: u64,
) -> Result<Vec<CredentialIssuer>, NymRewarderError> {
self.inner
.read()
.await
.get_all_verification_key_shares(dkg_epoch)
.await?
.into_iter()
.map(TryInto::try_into)
.collect()
}
pub(crate) async fn get_deposit_transaction_attributes(
&self,
tx_hash: Hash,
) -> Result<(String, String), NymRewarderError> {
let tx = self.inner.read().await.get_tx(tx_hash).await?;
// todo: we need to make it more concrete that the first attribute is the deposit value
// and the second one is the deposit info
let deposit_value =
find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_VALUE)
.ok_or(NymRewarderError::DepositValueNotFound { tx_hash })?;
let deposit_info =
find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO)
.ok_or(NymRewarderError::DepositInfoNotFound { tx_hash })?;
Ok((deposit_value, deposit_info))
}
}
@@ -0,0 +1,178 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::rewarder::epoch::Epoch;
#[derive(Clone)]
pub(crate) struct StorageManager {
pub(crate) connection_pool: sqlx::SqlitePool,
}
impl StorageManager {
pub(crate) async fn load_last_rewarding_epoch(&self) -> Result<Option<Epoch>, sqlx::Error> {
sqlx::query_as(
r#"
SELECT id, start_time, end_time
FROM rewarding_epoch
ORDER BY id DESC
LIMIT 1
"#,
)
.fetch_optional(&self.connection_pool)
.await
}
pub(crate) async fn insert_rewarding_epoch(
&self,
epoch: Epoch,
rewarding_budget: String,
total_spent: String,
rewarding_tx: Option<String>,
rewarding_error: Option<String>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO rewarding_epoch (id, start_time, end_time, budget, spent, rewarding_tx, rewarding_error)
VALUES (?, ?, ? ,?, ?, ?, ?)
"#,
epoch.id,
epoch.start_time,
epoch.end_time,
rewarding_budget,
total_spent: String,
rewarding_tx,
rewarding_error
).execute(&self.connection_pool).await?;
Ok(())
}
pub(crate) async fn insert_rewarding_epoch_block_signing(
&self,
epoch: i64,
total_voting_power_at_epoch_start: i64,
num_blocks: i64,
budget: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO epoch_block_signing (rewarding_epoch_id, total_voting_power_at_epoch_start, num_blocks, budget)
VALUES (?, ?, ?, ?)
"#,
epoch,
total_voting_power_at_epoch_start,
num_blocks,
budget,
).execute(&self.connection_pool).await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn insert_rewarding_epoch_block_signing_reward(
&self,
epoch: i64,
consensus_address: String,
operator_account: String,
amount: String,
voting_power: i64,
voting_power_share: String,
signed_blocks: i32,
signed_blocks_percent: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO block_signing_reward (
rewarding_epoch_id,
validator_consensus_address,
operator_account,
amount,
voting_power,
voting_power_share,
signed_blocks,
signed_blocks_percent
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
"#,
epoch,
consensus_address,
operator_account,
amount,
voting_power,
voting_power_share,
signed_blocks,
signed_blocks_percent,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
pub(crate) async fn insert_rewarding_epoch_credential_issuance(
&self,
epoch: i64,
starting_dkg_epoch: u32,
ending_dkg_epoch: u32,
total_issued_partial_credentials: u32,
budget: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO epoch_credential_issuance (
rewarding_epoch_id,
starting_dkg_epoch,
ending_dkg_epoch,
total_issued_partial_credentials,
budget
)
VALUES (?, ?, ?, ?, ?)
"#,
epoch,
starting_dkg_epoch,
ending_dkg_epoch,
total_issued_partial_credentials,
budget,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) async fn insert_rewarding_epoch_credential_issuance_reward(
&self,
epoch: i64,
operator_account: String,
amount: String,
api_endpoint: String,
issued_partial_credentials: u32,
issued_credentials_share: String,
validated_issued_credentials: u32,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO credential_issuance_reward (
rewarding_epoch_id,
operator_account,
amount,
api_endpoint,
issued_partial_credentials,
issued_credentials_share,
validated_issued_credentials
) VALUES (?, ?, ?, ?, ?, ?, ?)
"#,
epoch,
operator_account,
amount,
api_endpoint,
issued_partial_credentials,
issued_credentials_share,
validated_issued_credentials,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
@@ -0,0 +1,167 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::error::NymRewarderError;
use crate::rewarder::epoch::Epoch;
use crate::rewarder::storage::manager::StorageManager;
use crate::rewarder::EpochRewards;
use nym_validator_client::nyxd::{Coin, Hash};
use sqlx::ConnectOptions;
use std::fmt::Debug;
use std::path::Path;
use tracing::{error, info, instrument};
mod manager;
pub struct RewarderStorage {
pub(crate) manager: StorageManager,
}
impl RewarderStorage {
#[instrument]
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, NymRewarderError> {
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!("./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 };
let storage = RewarderStorage { manager };
Ok(storage)
}
pub(crate) async fn load_last_rewarding_epoch(
&self,
) -> Result<Option<Epoch>, NymRewarderError> {
Ok(self.manager.load_last_rewarding_epoch().await?)
}
pub(crate) async fn save_rewarding_information(
&self,
reward: EpochRewards,
total_spent: Coin,
rewarding_tx: Result<Hash, NymRewarderError>,
) -> Result<(), NymRewarderError> {
info!("persisting reward details");
let (reward_tx, reward_err) = match rewarding_tx {
Ok(hash) => (Some(hash.to_string()), None),
Err(err) => (None, Some(err.to_string())),
};
let epoch_id = reward.epoch.id;
self.manager
.insert_rewarding_epoch(
reward.epoch,
reward.total_budget.to_string(),
total_spent.to_string(),
reward_tx,
reward_err,
)
.await?;
self.manager
.insert_rewarding_epoch_block_signing(
epoch_id,
reward
.signing
.as_ref()
.map(|s| s.total_voting_power_at_epoch_start)
.unwrap_or_default(),
reward
.signing
.as_ref()
.map(|s| s.blocks)
.unwrap_or_default(),
reward.signing_budget.to_string(),
)
.await?;
if let Some(signing) = reward.signing {
for validator in signing.validators {
let reward_amount = validator.reward_amount(&reward.signing_budget).to_string();
self.manager
.insert_rewarding_epoch_block_signing_reward(
epoch_id,
validator.validator.consensus_address,
validator.operator_account.to_string(),
reward_amount,
validator.voting_power_at_epoch_start,
validator.voting_power_ratio.to_string(),
validator.signed_blocks,
validator.ratio_signed.to_string(),
)
.await?;
}
}
// safety: we must have at least a single value here
#[allow(clippy::unwrap_used)]
let dkg_epoch_start = reward
.credentials
.as_ref()
.and_then(|c| c.dkg_epochs.first().copied())
.unwrap_or_default();
#[allow(clippy::unwrap_used)]
let dkg_epoch_end = reward
.credentials
.as_ref()
.and_then(|c| c.dkg_epochs.last().copied())
.unwrap_or_default();
self.manager
.insert_rewarding_epoch_credential_issuance(
epoch_id,
dkg_epoch_start,
dkg_epoch_end,
reward
.credentials
.as_ref()
.map(|c| c.total_issued_partial_credentials)
.unwrap_or_default(),
reward.credentials_budget.to_string(),
)
.await?;
if let Some(credentials) = reward.credentials {
for api_runner in credentials.api_runners {
let reward_amount = api_runner
.reward_amount(&reward.credentials_budget)
.to_string();
self.manager
.insert_rewarding_epoch_credential_issuance_reward(
epoch_id,
api_runner.runner_account.to_string(),
reward_amount,
api_runner.api_runner,
api_runner.issued_credentials,
api_runner.issued_ratio.to_string(),
api_runner.validated_credentials,
)
.await?;
}
}
Ok(())
}
}
@@ -0,0 +1,2 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
-71
View File
@@ -1,71 +0,0 @@
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
release/
build/
# Gradle files
.gradle/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ
*.iml
.idea/
# .idea/workspace.xml
# .idea/tasks.xml
# .idea/gradle.xml
# .idea/assetWizardSettings.xml
# .idea/dictionaries
.idea/libraries
# Android Studio 3 in .gitignore file.
.idea/caches
.idea/modules.xml
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
.idea/navEditor.xml
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
*.jks
*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# MacOS
.DS_Store
# App Specific cases
app/release/output.json
.idea/codeStyles/
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 NymConnect
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-42
View File
@@ -1,42 +0,0 @@
## NymConnect for Android
### Prerequisites
_TODO_
### Getting started
[Install](https://developer.android.com/studio/install) Android Studio and open
the project.\
Setup an android emulator using AVD.\
[Run](https://developer.android.com/studio/run/emulator) the project.
**⚠ NOTE**: be sure
to [set](https://developer.android.com/studio/run#changing-variant)
the build variant to `x86_64Debug` when running on emulator
### Features
* Add tunnels via .conf file
* Auto connect to VPN based on Wi-Fi SSID
* Split tunneling by application with search
* Always-on VPN for Android support
* Quick tile support for vpn toggling
* Dynamic shortcuts support for automation integration
* Configurable Trusted Network list
* Optional auto connect on mobile data
* Automatic service restart after reboot
* Service will stay running in background after app has been closed
### Building
_TODO_
### Credits
This project is based on the "WG Tunnel" project made by Zane Schepke
https://github.com/zaneschepke/wgtunnel
### License
MIT
-2
View File
@@ -1,2 +0,0 @@
/build
/release
-159
View File
@@ -1,159 +0,0 @@
val rExtra = rootProject.extra
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
id("com.google.dagger.hilt.android")
id("org.jetbrains.kotlin.plugin.serialization")
id("io.objectbox")
}
android {
namespace = "net.nymtech.nymconnect"
compileSdk = 34
val versionMajor = 1
val versionMinor = 0
val versionPatch = 0
val versionBuild = 0
defaultConfig {
applicationId = "net.nymtech.nymconnect"
minSdk = 28
targetSdk = 34
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "${versionMajor}.${versionMinor}.${versionPatch}"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isDebuggable = false
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.4.8"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
/* flavorDimensions += "abi"
productFlavors {
create("universal") {
dimension = "abi"
ndk {
abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64", "x86")
}
}
create("arch64") {
dimension = "abi"
ndk {
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
create("arm64") {
dimension = "abi"
ndk {
abiFilters += "arm64-v8a"
}
}
create("arm") {
dimension = "abi"
ndk {
abiFilters += "armeabi-v7a"
}
}
create("x86_64") {
dimension = "abi"
ndk {
abiFilters += "x86_64"
}
}
create("x86") {
dimension = "abi"
ndk {
abiFilters += "x86"
}
}
} */
}
dependencies {
implementation("androidx.core:core-ktx:1.10.1")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3:1.1.1")
implementation("androidx.appcompat:appcompat:1.6.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
//wireguard tunnel
implementation("com.wireguard.android:tunnel:1.0.20230706")
//logging
implementation("com.jakewharton.timber:timber:5.0.1")
// compose navigation
implementation("androidx.navigation:navigation-compose:2.7.1")
implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
// hilt
implementation("com.google.dagger:hilt-android:${rExtra.get("hiltVersion")}")
kapt("com.google.dagger:hilt-android-compiler:${rExtra.get("hiltVersion")}")
//accompanist
implementation("com.google.accompanist:accompanist-systemuicontroller:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-permissions:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-flowlayout:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-navigation-animation:${rExtra.get("accompanistVersion")}")
implementation("com.google.accompanist:accompanist-drawablepainter:${rExtra.get("accompanistVersion")}")
//db
implementation("io.objectbox:objectbox-kotlin:${rExtra.get("objectBoxVersion")}")
//lifecycle
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
//icons
implementation("androidx.compose.material:material-icons-extended:1.5.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
}
kapt {
correctErrorTypes = true
}
@@ -1,99 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "1:2692736974585027589",
"lastPropertyId": "15:5057486545428188436",
"name": "TunnelConfig",
"properties": [
{
"id": "1:1985347930017457084",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "12:2409068226744965585",
"name": "name",
"indexId": "1:4811206443952699137",
"type": 9,
"flags": 34848
},
{
"id": "13:8987443291286312275",
"name": "wgQuick",
"type": 9
}
],
"relations": []
},
{
"id": "2:8887605597748372702",
"lastPropertyId": "9:4468844863383145378",
"name": "Settings",
"properties": [
{
"id": "1:7485739868216068651",
"name": "id",
"type": 6,
"flags": 1
},
{
"id": "2:5814013113141456749",
"name": "isAutoTunnelEnabled",
"type": 1
},
{
"id": "4:5645665441196906014",
"name": "trustedNetworkSSIDs",
"type": 30
},
{
"id": "5:4989886999117763881",
"name": "isTunnelOnMobileDataEnabled",
"type": 1
},
{
"id": "6:3370284381040192129",
"name": "defaultTunnel",
"type": 9
},
{
"id": "9:4468844863383145378",
"name": "isAlwaysOnVpnEnabled",
"type": 1
}
],
"relations": []
}
],
"lastEntityId": "2:8887605597748372702",
"lastIndexId": "1:4811206443952699137",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 5,
"modelVersionParserMinimum": 5,
"retiredEntityUids": [],
"retiredIndexUids": [],
"retiredPropertyUids": [
1763475292291320186,
6483820955437198310,
8323071516033820771,
5904440563612311217,
1408037976996390989,
7737847485212546994,
8215616901775229364,
8021610768066328637,
6174306582797008721,
2175939938544485767,
7555225587864607050,
969146862000617878,
5057486545428188436,
2814640993034665120,
4981008812459251156
],
"retiredRelationUids": [],
"version": 1
}

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