Merge branch 'develop' into feature/wallet-delegation-fees

This commit is contained in:
fmtabbara
2022-06-17 10:43:53 +01:00
90 changed files with 1297 additions and 759 deletions
+56
View File
@@ -0,0 +1,56 @@
name: Nym Connect (rust)
on:
push:
paths-ignore:
- 'explorer/**'
jobs:
build:
runs-on: [ self-hosted, custom-linux ]
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
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
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-connect/Cargo.toml --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-connect/Cargo.toml --workspace
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-connect/Cargo.toml --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path nym-connect/Cargo.toml --workspace --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path nym-connect/Cargo.toml --workspace --all-features -- -D warnings
+20 -7
View File
@@ -1,5 +1,6 @@
name: Publish Nym Wallet (MacOS)
on:
workflow_dispatch:
release:
types: [created]
@@ -19,7 +20,7 @@ jobs:
- uses: actions/checkout@v2
- name: Check the release tag starts with `nym-wallet-`
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false
if: startsWith(github.ref, 'refs/tags/nym-wallet-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
@@ -55,6 +56,11 @@ jobs:
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Create env file
run: |
touch .env
echo ADMIN_ADDRESS="${{ secrets.WALLET_ADMIN_ADDRESS }}" >> .env
- name: Install app dependencies and build it
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -66,17 +72,24 @@ jobs:
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
run: yarn && yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
files: |
nym-wallet/target/release/bundle/dmg/*.dmg
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
name: nym-wallet.app.tar.gz
path: nym-wallet/target/release/bundle/macos/nym-wallet.app.tar.gz
retention-days: 5
- name: Clean up keychain
if: ${{ always() }}
run: |
security delete-keychain $RUNNER_TEMP/app-signing.keychain-db
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
nym-wallet/target/release/bundle/dmg/*.dmg
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
@@ -39,12 +39,15 @@ jobs:
toolchain: stable
- name: Install app dependencies
run: yarn
- name: Create env file
run: |
touch .env
echo ADMIN_ADDRESS="${{ secrets.WALLET_ADMIN_ADDRESS }}" >> .env
- name: Build app
run: yarn build
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
@@ -54,6 +54,11 @@ jobs:
with:
toolchain: stable
- name: Create env file
run: |
touch .env
echo ADMIN_ADDRESS="${{ secrets.WALLET_ADMIN_ADDRESS }}" >> .env
- name: Install app dependencies
run: yarn
@@ -65,7 +70,6 @@ jobs:
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
ADMIN_ADDRESS: ${{ secrets.WALLET_ADMIN_ADDRESS }}
run: yarn build
- name: Upload to release based on tag name
+6
View File
@@ -6,6 +6,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
### Added
- socks5 client/websocket client: add `--force-register-gateway` flag, useful when rerunning init ([#1353])
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- explorer-api: learned how to sum the delegations by owner in a new endpoint.
@@ -34,6 +35,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- mixnode, gateway: attempting to determine reconnection backoff to persistently failing mixnode could result in a crash ([#1260])
- mixnode: the mixnode learned how to shutdown gracefully
- native & socks5 clients: fail early when clients try to re-init with a different gateway, which is not supported yet ([#1322])
- native & socks5 clients: rerun init will now reuse previous gateway configuration instead of failing ([#1353])
- validator: fixed local docker-compose setup to work on Apple M1 ([#1329])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
- wallet: undelegating now uses either the mixnet or vesting contract, or both, depending on how delegations were made
@@ -43,7 +45,9 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
- all: updated all `cosmwasm`-related dependencies to `1.0.0` and `cw-storage-plus` to `0.13.4` [[#1318]]
- all: updated `rocket` to `0.5.0-rc.2`.
- network-requester: allow to voluntarily store and send statistical data about the number of bytes the proxied server serves ([#1328])
- gateway: allow to voluntarily send statistical data about the number of active inboxes served by a gateway ([#1376])
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
@@ -64,6 +68,8 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1324]: https://github.com/nymtech/nym/pull/1324
[#1328]: https://github.com/nymtech/nym/pull/1328
[#1329]: https://github.com/nymtech/nym/pull/1329
[#1353]: https://github.com/nymtech/nym/pull/1353
[#1376]: https://github.com/nymtech/nym/pull/1376
## [nym-wallet-v1.0.5](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.5) (2022-06-14)
Generated
+130 -210
View File
@@ -14,6 +14,15 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aead"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
dependencies = [
"generic-array 0.14.5",
]
[[package]]
name = "aes"
version = "0.7.5"
@@ -38,6 +47,20 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "aes-gcm"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
dependencies = [
"aead",
"aes 0.7.5",
"cipher 0.3.0",
"ctr 0.8.0",
"ghash",
"subtle 2.4.1",
]
[[package]]
name = "ahash"
version = "0.7.6"
@@ -198,12 +221,6 @@ dependencies = [
"serde",
]
[[package]]
name = "base-x"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
[[package]]
name = "base16ct"
version = "0.1.1"
@@ -250,7 +267,7 @@ dependencies = [
"pbkdf2",
"rand_core 0.6.3",
"ripemd160",
"sha2",
"sha2 0.9.9",
"subtle 2.4.1",
"zeroize",
]
@@ -410,7 +427,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3"
dependencies = [
"sha2",
"sha2 0.9.9",
]
[[package]]
@@ -467,7 +484,7 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c24dab4283a142afa2fdca129b80ad2c6284e073930f964c3a1293c225ee39a"
dependencies = [
"rustc_version 0.4.0",
"rustc_version",
]
[[package]]
@@ -701,12 +718,6 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
[[package]]
name = "const_fn"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
@@ -728,12 +739,19 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cookie"
version = "0.15.1"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5f1c7727e460397e56abc4bddc1d49e07a1ad78fc98eb2e1c8f032a58a2f80d"
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [
"aes-gcm",
"base64",
"hkdf 0.12.3",
"hmac 0.12.1",
"percent-encoding",
"time 0.2.27",
"rand 0.8.5",
"sha2 0.10.2",
"subtle 2.4.1",
"time 0.3.9",
"version_check",
]
@@ -1246,7 +1264,7 @@ dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version 0.4.0",
"rustc_version",
"syn",
]
@@ -1332,12 +1350,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "discard"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dkg"
version = "0.1.0"
@@ -1354,7 +1366,7 @@ dependencies = [
"rand_core 0.6.3",
"serde",
"serde_derive",
"sha2",
"sha2 0.9.9",
"thiserror",
"zeroize",
]
@@ -1410,7 +1422,7 @@ dependencies = [
"rand 0.7.3",
"serde",
"serde_bytes",
"sha2",
"sha2 0.9.9",
"zeroize",
]
@@ -1424,7 +1436,7 @@ dependencies = [
"hex",
"rand_core 0.6.3",
"serde",
"sha2",
"sha2 0.9.9",
"thiserror",
"zeroize",
]
@@ -2024,6 +2036,16 @@ dependencies = [
"syn",
]
[[package]]
name = "ghash"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
dependencies = [
"opaque-debug 0.3.0",
"polyval",
]
[[package]]
name = "git2"
version = "0.14.2"
@@ -2596,7 +2618,7 @@ dependencies = [
"ecdsa",
"elliptic-curve",
"sec1",
"sha2",
"sha2 0.9.9",
"sha3",
]
@@ -3074,6 +3096,7 @@ dependencies = [
"rand 0.7.3",
"serde",
"sqlx",
"statistics-common",
"subtle-encoding",
"thiserror",
"tokio",
@@ -3130,6 +3153,7 @@ dependencies = [
name = "nym-network-requester"
version = "1.0.1"
dependencies = [
"async-trait",
"clap 2.34.0",
"dirs",
"futures",
@@ -3146,7 +3170,7 @@ dependencies = [
"serde",
"socks5-requests",
"sqlx",
"statistics",
"statistics-common",
"thiserror",
"tokio",
"tokio-tungstenite",
@@ -3163,7 +3187,7 @@ dependencies = [
"rocket",
"serde",
"sqlx",
"statistics",
"statistics-common",
"thiserror",
"tokio",
"tokio-tungstenite",
@@ -3321,7 +3345,7 @@ dependencies = [
"rand 0.8.5",
"serde",
"serde_derive",
"sha2",
"sha2 0.9.9",
"thiserror",
]
@@ -3852,6 +3876,18 @@ dependencies = [
"plotters-backend",
]
[[package]]
name = "polyval"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"opaque-debug 0.3.0",
"universal-hash",
]
[[package]]
name = "ppv-lite86"
version = "0.2.16"
@@ -3915,12 +3951,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro2"
version = "1.0.37"
@@ -4482,9 +4512,9 @@ dependencies = [
[[package]]
name = "rocket"
version = "0.5.0-rc.1"
version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2"
checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317"
dependencies = [
"async-stream",
"async-trait",
@@ -4500,7 +4530,7 @@ dependencies = [
"memchr",
"multer",
"num_cpus",
"parking_lot 0.11.2",
"parking_lot 0.12.0",
"pin-project-lite",
"rand 0.8.5",
"ref-cast",
@@ -4510,10 +4540,10 @@ dependencies = [
"serde_json",
"state",
"tempfile",
"time 0.2.27",
"time 0.3.9",
"tokio",
"tokio-stream",
"tokio-util 0.6.9",
"tokio-util 0.7.3",
"ubyte",
"version_check",
"yansi",
@@ -4521,9 +4551,9 @@ dependencies = [
[[package]]
name = "rocket_codegen"
version = "0.5.0-rc.1"
version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66f5fa462f7eb958bba8710c17c5d774bbbd59809fa76fb1957af7e545aea8bb"
checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47"
dependencies = [
"devise",
"glob",
@@ -4552,19 +4582,18 @@ dependencies = [
[[package]]
name = "rocket_http"
version = "0.5.0-rc.1"
version = "0.5.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c8b7d512d2fcac2316ebe590cde67573844b99e6cc9ee0f53375fa16e25ebd"
checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2"
dependencies = [
"cookie",
"either",
"futures",
"http",
"hyper",
"indexmap",
"log",
"memchr",
"mime",
"parking_lot 0.11.2",
"pear",
"percent-encoding",
"pin-project-lite",
@@ -4573,16 +4602,16 @@ dependencies = [
"smallvec 1.8.0",
"stable-pattern",
"state",
"time 0.2.27",
"time 0.3.9",
"tokio",
"uncased",
]
[[package]]
name = "rocket_okapi"
version = "0.8.0-rc.1"
version = "0.8.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0025aa04994af8cd8e1fcdd5a73579a395c941ae090ecb0a39b41cca7e237a20"
checksum = "489f4f5b120762f7974e65b919fc462d0660fd8b839026d8985b850fe5acccb0"
dependencies = [
"either",
"log",
@@ -4596,9 +4625,9 @@ dependencies = [
[[package]]
name = "rocket_okapi_codegen"
version = "0.8.0-rc.1"
version = "0.8.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc114779fc27afb78179233e966f469e47fd7a98dc15181cff2574cdddb65612"
checksum = "54f94d1ffe41472e08463d7a2674f1db04dc4df745285e8369b33d3cfd6b0308"
dependencies = [
"darling",
"proc-macro2",
@@ -4609,9 +4638,9 @@ dependencies = [
[[package]]
name = "rocket_sync_db_pools"
version = "0.1.0-rc.1"
version = "0.1.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38cfdfebd552d075c368e641c88a5cd6ce1c58c5c710548aeb777abb48830f4b"
checksum = "5fa48b6ab25013e9812f1b0c592741900b3a2a83c0936292e0565c0ac842f558"
dependencies = [
"r2d2",
"rocket",
@@ -4622,9 +4651,9 @@ dependencies = [
[[package]]
name = "rocket_sync_db_pools_codegen"
version = "0.1.0-rc.1"
version = "0.1.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267808c094db5366e1d8925aaf9f2ce05ff9b3bd92cb18c7040a1fe219c2e25"
checksum = "280ef2d232923e69cb93da156972eb5476a7cce5ba44843f6608f46a4abf7aab"
dependencies = [
"devise",
"quote",
@@ -4636,15 +4665,6 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@@ -4721,9 +4741,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3"
checksum = "1847b767a3d62d95cbf3d8a9f0e421cf57a0d8aa4f411d4b16525afb0284d4ed"
dependencies = [
"dyn-clone",
"indexmap",
@@ -4734,9 +4754,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.8"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b"
checksum = "af4d7e1b012cb3d9129567661a63755ea4b8a7386d339dc945ae187e403c6743"
dependencies = [
"proc-macro2",
"quote",
@@ -4820,22 +4840,13 @@ dependencies = [
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser 0.7.0",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser 0.10.2",
"semver-parser",
]
[[package]]
@@ -4844,12 +4855,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "semver-parser"
version = "0.10.2"
@@ -4915,9 +4920,9 @@ dependencies = [
[[package]]
name = "serde_derive_internals"
version = "0.25.0"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c"
dependencies = [
"proc-macro2",
"quote",
@@ -5028,21 +5033,6 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "sha1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
dependencies = [
"sha1_smol",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "sha2"
version = "0.9.9"
@@ -5056,6 +5046,17 @@ dependencies = [
"opaque-debug 0.3.0",
]
[[package]]
name = "sha2"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.10.3",
]
[[package]]
name = "sha3"
version = "0.9.1"
@@ -5206,7 +5207,7 @@ dependencies = [
"log",
"rand 0.7.3",
"rand_distr",
"sha2",
"sha2 0.9.9",
"subtle 2.4.1",
]
@@ -5289,7 +5290,7 @@ dependencies = [
"paste",
"percent-encoding",
"rustls",
"sha2",
"sha2 0.9.9",
"smallvec 1.8.0",
"sqlformat",
"sqlx-rt",
@@ -5313,7 +5314,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"sha2",
"sha2 0.9.9",
"sqlx-core",
"sqlx-rt",
"syn",
@@ -5340,20 +5341,11 @@ dependencies = [
"memchr",
]
[[package]]
name = "standback"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff"
dependencies = [
"version_check",
]
[[package]]
name = "state"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cf4f5369e6d3044b5e365c9690f451516ac8f0954084622b49ea3fde2f6de5"
checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b"
dependencies = [
"loom",
]
@@ -5365,64 +5357,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "statistics"
name = "statistics-common"
version = "1.0.1"
dependencies = [
"async-trait",
"log",
"network-defaults",
"reqwest",
"serde",
"serde_json",
"sqlx",
"thiserror",
"tokio",
]
[[package]]
name = "stdweb"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version 0.2.3",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
"wasm-bindgen",
]
[[package]]
name = "stdweb-derive"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
"proc-macro2",
"quote",
"serde",
"serde_derive",
"syn",
]
[[package]]
name = "stdweb-internal-macros"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
"proc-macro2",
"quote",
"serde",
"serde_derive",
"serde_json",
"sha1",
"syn",
]
[[package]]
name = "stdweb-internal-runtime"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "stringprep"
version = "0.1.2"
@@ -5562,7 +5510,7 @@ dependencies = [
"serde_bytes",
"serde_json",
"serde_repr",
"sha2",
"sha2 0.9.9",
"signature",
"subtle 2.4.1",
"subtle-encoding",
@@ -5700,21 +5648,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242"
dependencies = [
"const_fn",
"libc",
"standback",
"stdweb",
"time-macros 0.1.1",
"version_check",
"winapi",
]
[[package]]
name = "time"
version = "0.3.9"
@@ -5725,17 +5658,7 @@ dependencies = [
"libc",
"num_threads",
"serde",
"time-macros 0.2.4",
]
[[package]]
name = "time-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1"
dependencies = [
"proc-macro-hack",
"time-macros-impl",
"time-macros",
]
[[package]]
@@ -5744,19 +5667,6 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "time-macros-impl"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"standback",
"syn",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@@ -6242,6 +6152,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "universal-hash"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
dependencies = [
"generic-array 0.14.5",
"subtle 2.4.1",
]
[[package]]
name = "untrusted"
version = "0.7.1"
@@ -6309,7 +6229,7 @@ dependencies = [
"reqwest",
"serde",
"serde_json",
"sha2",
"sha2 0.9.9",
"thiserror",
"tokio",
"ts-rs",
@@ -6349,7 +6269,7 @@ dependencies = [
"enum-iterator",
"getset",
"git2",
"rustc_version 0.4.0",
"rustc_version",
"rustversion",
"thiserror",
]
+1 -1
View File
@@ -32,4 +32,4 @@ validator-client = { path = "../../common/client-libs/validator-client" }
tempfile = "3.1.0"
[features]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
coconut = ["gateway-client/coconut", "gateway-requests/coconut"]
+23 -12
View File
@@ -114,12 +114,8 @@ impl<T: NymConfig> Config<T> {
self.client.disabled_credentials_mode = disabled_credentials_mode;
}
pub fn with_gateway_endpoint<S: Into<String>>(&mut self, id: S, owner: S, listener: S) {
self.client.gateway_endpoint = GatewayEndpoint {
gateway_id: id.into(),
gateway_owner: owner.into(),
gateway_listener: listener.into(),
};
pub fn with_gateway_endpoint(&mut self, gateway_endpoint: GatewayEndpoint) {
self.client.gateway_endpoint = gateway_endpoint;
}
pub fn with_gateway_id<S: Into<String>>(&mut self, id: S) {
@@ -142,7 +138,7 @@ impl<T: NymConfig> Config<T> {
pub fn set_high_default_traffic_volume(&mut self) {
self.debug.average_packet_delay = Duration::from_millis(10);
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2000000); // basically don't really send cover messages
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000); // basically don't really send cover messages
self.debug.message_sending_average_delay = Duration::from_millis(4); // 250 "real" messages / s
}
@@ -206,6 +202,10 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_listener.clone()
}
pub fn get_gateway_endpoint(&self) -> &GatewayEndpoint {
&self.client.gateway_endpoint
}
pub fn get_database_path(&self) -> PathBuf {
self.client.database_path.clone()
}
@@ -272,17 +272,28 @@ impl<T: NymConfig> Default for Config<T> {
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
struct GatewayEndpoint {
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct GatewayEndpoint {
/// gateway_id specifies ID of the gateway to which the client should send messages.
/// If initially omitted, a random gateway will be chosen from the available topology.
gateway_id: String,
pub gateway_id: String,
/// Address of the gateway owner to which the client should send messages.
gateway_owner: String,
pub gateway_owner: String,
/// Address of the gateway listener to which all client requests should be sent.
gateway_listener: String,
pub gateway_listener: String,
}
impl From<topology::gateway::Node> for GatewayEndpoint {
fn from(node: topology::gateway::Node) -> GatewayEndpoint {
let gateway_listener = node.clients_address();
GatewayEndpoint {
gateway_id: node.identity_key.to_base58_string(),
gateway_owner: node.owner,
gateway_listener,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
+1 -1
View File
@@ -26,4 +26,4 @@ pemstore = { path = "../../common/pemstore" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
[features]
coconut = ["credentials/coconut"]
coconut = ["credentials/coconut"]
+1 -1
View File
@@ -55,4 +55,4 @@ eth = []
serde_json = "1.0" # for the "textsend" example
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
+79 -41
View File
@@ -42,6 +42,11 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("force-register-gateway")
.long("force-register-gateway")
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
.takes_value(false)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
@@ -181,25 +186,68 @@ fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
async fn set_gateway_config(config: &mut Config, chosen_gateway_id: Option<&str>) -> gateway::Node {
println!("Setting gateway config");
log::trace!("Chosen gateway: {:?}", chosen_gateway_id);
// Get the gateway details by querying the validator-api, and using the chosen one if it's
// among the available ones.
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
log::trace!("Used gateway: {}", gateway_details);
config
.get_base_mut()
.with_gateway_endpoint(gateway_details.clone().into());
gateway_details
}
async fn register_and_store_gateway_keys(gateway_details: gateway::Node, config: &Config) {
println!("Registering gateway");
let mut rng = OsRng;
let mut key_manager = KeyManager::new(&mut rng);
let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
}
pub async fn execute(matches: ArgMatches<'static>) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
let already_init = if Config::default_config_file_path(Some(id)).exists() {
if matches.is_present("gateway") {
panic!("At the moment, gateway information can't be overwritten. If you want to point to a different gateway, client {}'s directory will need to be manually removed", id);
}
println!("Client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let already_init = Config::default_config_file_path(Some(id)).exists();
if already_init {
println!(
"Client \"{}\" was already initialised before! \
Config information will be overwritten (but keys will be kept)!",
id
);
}
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let should_force_register = matches.is_present("force-register-gateway");
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let will_register_gateway = !already_init || should_force_register;
// Attempt to use a user-provided gateway, if possible
let is_gateway_provided = matches.value_of("gateway").is_some();
let mut config = Config::new(id);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
@@ -207,36 +255,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
config.get_base_mut().set_high_default_traffic_volume();
}
// if client was already initialised, don't generate new keys, not re-register with gateway
// (because this would create new shared key)
if !already_init {
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
let chosen_gateway_id = matches.value_of("gateway");
log::trace!("Chosen gateway: {:?}", chosen_gateway_id);
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
log::trace!("Used gateway: {}", gateway_details);
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
config.get_base_mut().with_gateway_endpoint(
gateway_details.identity_key.to_base58_string(),
gateway_details.owner.clone(),
gateway_details.clients_address(),
);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
if will_register_gateway {
// Create identity, encryption and ack keys.
let gateway_details = set_gateway_config(&mut config, matches.value_of("gateway")).await;
register_and_store_gateway_keys(gateway_details, &config).await;
} else if is_gateway_provided {
// Just set the config, don't register or create any keys
set_gateway_config(&mut config, matches.value_of("gateway")).await;
} else {
// Read the existing config to reuse the gateway configuration
println!("Not registering gateway, will reuse existing config and keys");
if let Ok(existing_config) = Config::load_from_file(Some(id)) {
config
.get_base_mut()
.with_gateway_endpoint(existing_config.get_base().get_gateway_endpoint().clone());
} else {
log::warn!(
"Existing configuration found, but enable to load gateway details. \
Proceeding anyway."
);
};
}
let config_save_location = config.get_config_file_save_location();
+1 -1
View File
@@ -10,4 +10,4 @@ edition = "2021"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
nymsphinx = { path = "../../../common/nymsphinx" }
nymsphinx = { path = "../../../common/nymsphinx" }
+1 -1
View File
@@ -47,4 +47,4 @@ coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gate
eth = []
[build-dependencies]
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] }
+89 -41
View File
@@ -46,6 +46,11 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Id of the gateway we are going to connect to.")
.takes_value(true)
)
.arg(Arg::with_name("force-register-gateway")
.long("force-register-gateway")
.help("Force register gateway. WARNING: this will overwrite any existing keys for the given id, potentially causing loss of access.")
.takes_value(false)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
@@ -121,6 +126,7 @@ pub async fn gateway_details(
.expect("The list of validator apis is empty");
let validator_client = validator_client::ApiClient::new(validator_api.clone());
log::trace!("Fetching list of gateways from: {}", validator_api);
let gateways = validator_client.get_cached_gateways().await.unwrap();
let valid_gateways = gateways
.into_iter()
@@ -183,26 +189,70 @@ pub fn show_address(config: &Config) {
println!("\nThe address of this client is: {}", client_recipient);
}
async fn set_gateway_config(config: &mut Config, chosen_gateway_id: Option<&str>) -> gateway::Node {
println!("Setting gateway config");
log::trace!("Chosen gateway: {:?}", chosen_gateway_id);
// Get the gateway details by querying the validator-api, and using the chosen one if it's
// among the available ones.
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
log::trace!("Used gateway: {}", gateway_details);
config
.get_base_mut()
.with_gateway_endpoint(gateway_details.clone().into());
gateway_details
}
async fn register_and_store_gateway_keys(gateway_details: gateway::Node, config: &Config) {
println!("Registering gateway");
let mut rng = OsRng;
let mut key_manager = KeyManager::new(&mut rng);
let shared_keys = register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
}
pub async fn execute(matches: ArgMatches<'static>) {
println!("Initialising client...");
let id = matches.value_of("id").unwrap(); // required for now
let provider_address = matches.value_of("provider").unwrap();
let already_init = if Config::default_config_file_path(Some(id)).exists() {
if matches.is_present("gateway") {
panic!("At the moment, gateway information can't be overwritten. If you want to point to a different gateway, client {}'s directory will need to be manually removed", id);
}
println!("Socks5 client \"{}\" was already initialised before! Config information will be overwritten (but keys will be kept)!", id);
true
} else {
false
};
let already_init = Config::default_config_file_path(Some(id)).exists();
if already_init {
println!(
"SOCKS5 client \"{}\" was already initialised before! \
Config information will be overwritten (but keys will be kept)!",
id
);
}
// Usually you only register with the gateway on the first init, however you can force
// re-registering if wanted.
let should_force_register = matches.is_present("force-register-gateway");
// If the client was already initialized, don't generate new keys and don't re-register with
// the gateway (because this would create a new shared key).
// Unless the user really wants to.
let will_register_gateway = !already_init || should_force_register;
// Attempt to use a user-provided gateway, if possible
let is_gateway_provided = matches.value_of("gateway").is_some();
let mut config = Config::new(id, provider_address);
let mut rng = OsRng;
// TODO: ideally that should be the last thing that's being done to config.
// However, we are later further overriding it with gateway id
config = override_config(config, &matches);
@@ -210,34 +260,26 @@ pub async fn execute(matches: ArgMatches<'static>) {
config.get_base_mut().set_high_default_traffic_volume();
}
// if client was already initialised, don't generate new keys, not re-register with gateway
// (because this would create new shared key)
if !already_init {
// create identity, encryption and ack keys.
let mut key_manager = KeyManager::new(&mut rng);
let chosen_gateway_id = matches.value_of("gateway");
let gateway_details = gateway_details(
config.get_base().get_validator_api_endpoints(),
chosen_gateway_id,
)
.await;
let shared_keys =
register_with_gateway(&gateway_details, key_manager.identity_keypair()).await;
config.get_base_mut().with_gateway_endpoint(
gateway_details.identity_key.to_base58_string(),
gateway_details.owner.clone(),
gateway_details.clients_address(),
);
key_manager.insert_gateway_shared_key(shared_keys);
let pathfinder = ClientKeyPathfinder::new_from_config(config.get_base());
key_manager
.store_keys(&pathfinder)
.expect("Failed to generated keys");
println!("Saved all generated keys");
if will_register_gateway {
// Create identity, encryption and ack keys.
let gateway_details = set_gateway_config(&mut config, matches.value_of("gateway")).await;
register_and_store_gateway_keys(gateway_details, &config).await;
} else if is_gateway_provided {
// Just set the config, don't register or create any keys
set_gateway_config(&mut config, matches.value_of("gateway")).await;
} else {
// Read the existing config to reuse the gateway configuration
println!("Not registering gateway, will reuse existing config and keys");
if let Ok(existing_config) = Config::load_from_file(Some(id)) {
config
.get_base_mut()
.with_gateway_endpoint(existing_config.get_base().get_gateway_endpoint().clone());
} else {
log::warn!(
"Existing configuration found, but enable to load gateway details. \
Proceeding anyway."
);
};
}
let config_save_location = config.get_config_file_save_location();
@@ -245,8 +287,14 @@ pub async fn execute(matches: ArgMatches<'static>) {
.save_to_file(None)
.expect("Failed to save the config file");
println!("Saved configuration file to {:?}", config_save_location);
println!("Using gateway: {}", config.get_base().get_gateway_id(),);
println!("Client configuration completed.\n\n\n");
println!("Using gateway: {}", config.get_base().get_gateway_id());
log::debug!("Gateway id: {}", config.get_base().get_gateway_id());
log::debug!("Gateway owner: {}", config.get_base().get_gateway_owner());
log::debug!(
"Gateway listener: {}",
config.get_base().get_gateway_listener()
);
println!("Client configuration completed.");
show_address(&config);
}
+5 -5
View File
@@ -34,16 +34,16 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
.help("Address of the socks5 provider to send messages to.")
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("gateway")
.long("gateway")
.help("Id of the gateway we want to connect to. If overridden, it is user's responsibility to ensure prior registration happened")
.takes_value(true)
)
.arg(Arg::with_name("validators")
.long("validators")
.help("Comma separated list of rest endpoints of the validators")
.takes_value(true),
)
.arg(Arg::with_name("port")
.short("p")
.long("port")
+1 -1
View File
@@ -74,4 +74,4 @@ features = ["js"]
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
wasm = ["web3/wasm", "web3/http", "web3/signing"]
default = ["web3/default"]
default = ["web3/default"]
@@ -11,4 +11,4 @@ cw3 = { version = "0.13.1" }
cw4 = { version = "0.13.1" }
cosmwasm-std = "1.0.0"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
+1 -1
View File
@@ -17,4 +17,4 @@ tokio = { version = "1.19.1", features = [ "rt-multi-thread", "net", "signal", "
[build-dependencies]
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] }
tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros"] }
+1 -1
View File
@@ -21,4 +21,4 @@ validator-client = { path = "../client-libs/validator-client" }
rand = "0.7.3"
[features]
coconut = ["cosmrs"]
coconut = ["cosmrs"]
+1 -1
View File
@@ -38,4 +38,4 @@ criterion = "0.3"
[[bench]]
name = "benchmarks"
harness = false
harness = false
+6
View File
@@ -64,6 +64,10 @@ impl Network {
self.details().rewarding_validator_address
}
pub fn statistics_service_url(&self) -> &str {
self.details().statistics_service_url
}
pub fn validators(&self) -> impl Iterator<Item = &ValidatorDetails> {
self.details().validators.iter()
}
@@ -101,6 +105,7 @@ pub struct NetworkDetails {
mixnet_contract_address: String,
vesting_contract_address: String,
bandwidth_claim_contract_address: String,
statistics_service_url: String,
validators: Vec<ValidatorDetails>,
}
@@ -112,6 +117,7 @@ impl From<&DefaultNetworkDetails<'_>> for NetworkDetails {
mixnet_contract_address: details.mixnet_contract_address.into(),
vesting_contract_address: details.vesting_contract_address.into(),
bandwidth_claim_contract_address: details.bandwidth_claim_contract_address.into(),
statistics_service_url: details.statistics_service_url.into(),
validators: details.validators.clone(),
}
}
+11
View File
@@ -54,6 +54,7 @@ pub struct DefaultNetworkDetails<'a> {
coconut_bandwidth_contract_address: &'a str,
multisig_contract_address: &'a str,
rewarding_validator_address: &'a str,
statistics_service_url: &'a str,
validators: Vec<ValidatorDetails>,
}
@@ -67,6 +68,7 @@ static MAINNET_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> =
coconut_bandwidth_contract_address: mainnet::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: mainnet::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: mainnet::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: mainnet::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: mainnet::validators(),
});
@@ -80,6 +82,7 @@ static SANDBOX_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> =
coconut_bandwidth_contract_address: sandbox::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: sandbox::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: sandbox::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: sandbox::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: sandbox::validators(),
});
@@ -92,6 +95,7 @@ static QA_DEFAULTS: Lazy<DefaultNetworkDetails<'static>> = Lazy::new(|| DefaultN
coconut_bandwidth_contract_address: qa::COCONUT_BANDWIDTH_CONTRACT_ADDRESS,
multisig_contract_address: qa::MULTISIG_CONTRACT_ADDRESS,
rewarding_validator_address: qa::REWARDING_VALIDATOR_ADDRESS,
statistics_service_url: qa::STATISTICS_SERVICE_DOMAIN_ADDRESS,
validators: qa::validators(),
});
@@ -132,6 +136,13 @@ impl ValidatorDetails {
}
}
pub fn default_statistics_service_url() -> Url {
DEFAULT_NETWORK
.statistics_service_url()
.parse()
.expect("the provided statistics service url is invalid!")
}
pub fn default_nymd_endpoints() -> Vec<Url> {
DEFAULT_NETWORK
.validators()
+1
View File
@@ -22,6 +22,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://rpc.nyx.nodes.guru/",
+1
View File
@@ -22,6 +22,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://qa-validator.nymtech.net",
+1
View File
@@ -20,6 +20,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("E8883BAeF3869e14E4823F46662e81D4F7d2A81F");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1jh0s6qu6tuw9ut438836mmn7f3f2wencrnmdj4";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
"https://sandbox-validator.nymtech.net",
+1 -1
View File
@@ -12,4 +12,4 @@ nymsphinx-types = { path = "../types" } # we need to be able to refer to some ty
serde = "1.0" # implementing serialization/deserialization for some types, like `Recipient`
[dev-dependencies]
rand = "0.7"
rand = "0.7"
+1 -1
View File
@@ -16,4 +16,4 @@ nymsphinx-chunking = { path = "../chunking" }
nymsphinx-params = { path = "../params" }
nymsphinx-forwarding = { path = "../forwarding" }
nymsphinx-types = { path = "../types" }
topology = { path = "../../topology" }
topology = { path = "../../topology" }
+1 -1
View File
@@ -8,4 +8,4 @@ edition = "2021"
[dependencies]
crypto = { path = "../../crypto", features = ["hashing", "symmetric"] }
nymsphinx-types = { path = "../types" }
nymsphinx-types = { path = "../types" }
+1 -1
View File
@@ -18,4 +18,4 @@ socks5-requests = { path = "../requests" }
ordered-buffer = { path = "../ordered-buffer" }
[dev-dependencies]
tokio-test = "0.4.2"
tokio-test = "0.4.2"
+9 -3
View File
@@ -2,14 +2,20 @@
# SPDX-License-Identifier: Apache-2.0
[package]
name = "statistics"
name = "statistics-common"
version = "1.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
reqwest = "0.11"
async-trait = { version = "0.1.51" }
log = "0.4"
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
thiserror = "1"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
tokio = { version = "1.19.1", features = [ "time" ] }
network-defaults = { path = "../network-defaults" }
+17 -1
View File
@@ -10,13 +10,29 @@ pub const DEFAULT_STATISTICS_SERVICE_PORT: u16 = 8090;
pub const STATISTICS_SERVICE_VERSION: &str = "/v1";
pub const STATISTICS_SERVICE_API_STATISTICS: &str = "statistic";
pub async fn build_and_send_statistics_request(
msg: StatsMessage,
url: String,
) -> Result<(), StatsError> {
reqwest::Client::new()
.post(format!(
"{}{}/{}",
url, STATISTICS_SERVICE_VERSION, STATISTICS_SERVICE_API_STATISTICS
))
.json(&msg)
.send()
.await?;
Ok(())
}
pub fn build_statistics_request_bytes(msg: StatsMessage) -> Result<Vec<u8>, StatsError> {
let json_msg = msg.to_json()?;
let req = reqwest::Request::new(
reqwest::Method::POST,
reqwest::Url::parse(&format!(
"http://{}:{}/{}/{}",
"http://{}:{}{}/{}",
DEFAULT_STATISTICS_SERVICE_ADDRESS,
DEFAULT_STATISTICS_SERVICE_PORT,
STATISTICS_SERVICE_VERSION,
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use log::error;
use sqlx::types::chrono::{DateTime, Utc};
use std::time::Duration;
use tokio::time;
use crate::error::StatsError;
use crate::StatsMessage;
const STATISTICS_TIMER_INTERVAL: Duration = Duration::from_secs(60);
#[async_trait]
pub trait StatisticsCollector {
async fn create_stats_message(
&self,
interval: Duration,
timestamp: DateTime<Utc>,
) -> StatsMessage;
async fn send_stats_message(&self, stats_message: StatsMessage) -> Result<(), StatsError>;
async fn reset_stats(&mut self);
}
pub struct StatisticsSender<T: StatisticsCollector> {
collector: T,
interval: Duration,
timestamp: DateTime<Utc>,
}
impl<T: StatisticsCollector> StatisticsSender<T> {
pub fn new(collector: T) -> Self {
StatisticsSender {
collector,
interval: STATISTICS_TIMER_INTERVAL,
timestamp: Utc::now(),
}
}
pub async fn run(&mut self) {
let mut interval = time::interval(self.interval);
loop {
interval.tick().await;
let stats_message = self
.collector
.create_stats_message(self.interval, self.timestamp)
.await;
if let Err(e) = self.collector.send_stats_message(stats_message).await {
error!("Statistics not sent: {}", e);
}
self.collector.reset_stats().await;
self.timestamp = Utc::now();
}
}
}
+3
View File
@@ -7,4 +7,7 @@ use thiserror::Error;
pub enum StatsError {
#[error("Serde JSON error: {0}")]
SerdeJsonError(#[from] serde_json::Error),
#[error("Reqwest error: {0}")]
ReqwestError(#[from] reqwest::Error),
}
+19 -1
View File
@@ -6,11 +6,12 @@ use serde::{Deserialize, Serialize};
use error::StatsError;
pub mod api;
pub mod collector;
pub mod error;
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsMessage {
pub stats_data: Vec<StatsServiceData>,
pub stats_data: Vec<StatsData>,
pub interval_seconds: u32,
pub timestamp: String,
}
@@ -25,6 +26,23 @@ impl StatsMessage {
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum StatsData {
Service(StatsServiceData),
Gateway(StatsGatewayData),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsGatewayData {
pub inbox_count: u32,
}
impl StatsGatewayData {
pub fn new(inbox_count: u32) -> Self {
StatsGatewayData { inbox_count }
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct StatsServiceData {
pub requested_service: String,
+1 -1
View File
@@ -7,4 +7,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
semver = "0.11"
semver = "0.11"
+1 -1
View File
@@ -29,4 +29,4 @@ features = [
"ProgressEvent",
"WebSocket",
"Window",
]
]
+2 -2
View File
@@ -14,9 +14,9 @@ log = "0.4.0"
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
pretty_env_logger = "0.4.0"
reqwest = "0.11.4"
rocket = {version = "0.5.0-rc.1", features=["json"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
schemars = { version = "0.8", features = ["preserve_order"] }
serde = "1.0.126"
serde_json = "1.0.66"
+1
View File
@@ -51,6 +51,7 @@ mixnode-common = { path = "../common/mixnode-common" }
network-defaults = { path = "../common/network-defaults" }
nymsphinx = { path = "../common/nymsphinx" }
pemstore = { path = "../common/pemstore" }
statistics-common = { path = "../common/statistics" }
validator-client = { path = "../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../common/version-checker" }
+16 -3
View File
@@ -45,7 +45,7 @@ pub struct Init {
/// Cosmos wallet mnemonic needed for double spending protection
#[clap(long)]
mnemonic: String,
mnemonic: Option<String>,
/// Set this gateway to work in a enabled credentials mode that would disallow clients to bypass bandwidth credential requirement
#[cfg(all(feature = "eth", not(feature = "coconut")))]
@@ -57,6 +57,14 @@ pub struct Init {
#[clap(long)]
eth_endpoint: String,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
enabled_statistics: Option<bool>,
/// URL where a statistics aggregator is running. The default value is a Nym aggregator server
#[clap(long)]
statistics_service_url: Option<String>,
/// Comma separated list of endpoints of the validator
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
@@ -73,7 +81,7 @@ impl From<Init> for OverrideConfig {
datastore: init_config.datastore,
announce_host: init_config.announce_host,
validator_apis: init_config.validator_apis,
mnemonic: Some(init_config.mnemonic),
mnemonic: init_config.mnemonic,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
enabled_credentials_mode: init_config.enabled_credentials_mode,
@@ -81,6 +89,9 @@ impl From<Init> for OverrideConfig {
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: Some(init_config.eth_endpoint),
enabled_statistics: init_config.enabled_statistics,
statistics_service_url: init_config.statistics_service_url,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
validators: init_config.validators,
}
@@ -163,7 +174,9 @@ mod tests {
announce_host: Some("foo-announce-host".to_string()),
datastore: Some("foo-datastore".to_string()),
validator_apis: None,
mnemonic: "a b c".to_string(),
mnemonic: Some("a b c".to_string()),
statistics_service_url: None,
enabled_statistics: None,
};
let config = Config::new(&args.id);
+14
View File
@@ -46,6 +46,8 @@ pub(crate) struct OverrideConfig {
clients_port: Option<u16>,
datastore: Option<String>,
announce_host: Option<String>,
enabled_statistics: Option<bool>,
statistics_service_url: Option<String>,
validator_apis: Option<String>,
mnemonic: Option<String>,
@@ -102,6 +104,18 @@ pub(crate) fn override_config(mut config: Config, args: OverrideConfig) -> Confi
config = config.announce_host_from_listening_host();
}
if let Some(enabled_statistics) = args.enabled_statistics {
config = config.with_enabled_statistics(enabled_statistics);
}
if let Some(raw_url) = args.statistics_service_url {
config = config.with_custom_statistics_service_url(
raw_url
.parse()
.expect("the provided statistics service url is invalid!"),
);
}
if let Some(raw_validators) = args.validator_apis {
config = config.with_custom_validator_apis(parse_validators(&raw_validators));
}
+11
View File
@@ -57,6 +57,14 @@ pub struct Run {
#[clap(long)]
eth_endpoint: Option<String>,
/// Enable/disable gateway anonymized statistics that get sent to a statistics aggregator server
#[clap(long)]
enabled_statistics: Option<bool>,
/// URL where a statistics aggregator is running. The default value is a Nym aggregator server
#[clap(long)]
statistics_service_url: Option<String>,
/// Comma separated list of endpoints of the validator
#[cfg(all(feature = "eth", not(feature = "coconut")))]
#[clap(long)]
@@ -81,6 +89,9 @@ impl From<Run> for OverrideConfig {
#[cfg(all(feature = "eth", not(feature = "coconut")))]
eth_endpoint: run_config.eth_endpoint,
enabled_statistics: run_config.enabled_statistics,
statistics_service_url: run_config.statistics_service_url,
#[cfg(all(feature = "eth", not(feature = "coconut")))]
validators: run_config.validators,
}
+27 -1
View File
@@ -127,6 +127,16 @@ impl Config {
self
}
pub fn with_enabled_statistics(mut self, enabled_statistics: bool) -> Self {
self.gateway.enabled_statistics = enabled_statistics;
self
}
pub fn with_custom_statistics_service_url(mut self, statistics_service_url: Url) -> Self {
self.gateway.statistics_service_url = statistics_service_url;
self
}
pub fn with_custom_validator_apis(mut self, validator_api_urls: Vec<Url>) -> Self {
self.gateway.validator_api_urls = validator_api_urls;
self
@@ -227,6 +237,14 @@ impl Config {
self.gateway.eth_endpoint.clone()
}
pub fn get_enabled_statistics(&self) -> bool {
self.gateway.enabled_statistics
}
pub fn get_statistics_service_url(&self) -> Url {
self.gateway.statistics_service_url.clone()
}
pub fn get_validator_api_endpoints(&self) -> Vec<Url> {
self.gateway.validator_api_urls.clone()
}
@@ -339,6 +357,12 @@ pub struct Gateway {
#[cfg(not(feature = "coconut"))]
eth_endpoint: String,
/// Wheather gateway collects and sends anonymized statistics
enabled_statistics: bool,
/// Domain address of the statistics service
statistics_service_url: Url,
/// Addresses to APIs running on validator from which the node gets the view of the network.
validator_api_urls: Vec<Url>,
@@ -399,10 +423,12 @@ impl Default for Gateway {
public_sphinx_key_file: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_endpoint: "".to_string(),
enabled_statistics: false,
statistics_service_url: default_statistics_service_url(),
validator_api_urls: default_api_endpoints(),
#[cfg(not(feature = "coconut"))]
validator_nymd_urls: default_nymd_endpoints(),
cosmos_mnemonic: "".to_string(),
cosmos_mnemonic: "exact antique hybrid width raise anchor puzzle degree fee quit long crack net vague hip despair write put useless civil mechanic broom music day".to_string(),
nym_root_directory: Config::default_root_directory(),
persistent_storage: Default::default(),
wallet_address: "nymXXXXXXXX".to_string(),
+6
View File
@@ -56,6 +56,12 @@ mix_port = {{ gateway.mix_port }}
# (default: 9000)
clients_port = {{ gateway.clients_port }}
# Wheather gateway collects and sends anonymized statistics
enabled_statistics = {{ gateway.enabled_statistics }}
# Domain address of the statistics service
statistics_service_url = '{{ gateway.statistics_service_url }}'
# Addresses to APIs running on validator from which the node gets the view of the network.
validator_api_urls = [
{{#each gateway.validator_api_urls }}
@@ -55,4 +55,9 @@ impl ActiveClientsStore {
pub(crate) fn insert(&self, client: DestinationAddressBytes, handle: MixMessageSender) {
self.0.insert(client, handle);
}
/// Get number of active clients in store
pub(crate) fn size(&self) -> usize {
self.0.len()
}
}
+15
View File
@@ -7,12 +7,14 @@ use crate::config::Config;
use crate::node::client_handling::active_clients::ActiveClientsStore;
use crate::node::client_handling::websocket;
use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler;
use crate::node::statistics::collector::GatewayStatisticsCollector;
use crate::node::storage::Storage;
use crypto::asymmetric::{encryption, identity};
use log::*;
use mixnet_client::forwarder::{MixForwardingSender, PacketForwarder};
use rand::seq::SliceRandom;
use rand::thread_rng;
use statistics_common::collector::StatisticsSender;
use std::net::SocketAddr;
use std::process;
use std::sync::Arc;
@@ -29,6 +31,7 @@ use self::storage::PersistentStorage;
pub(crate) mod client_handling;
pub(crate) mod mixnet_handling;
pub(crate) mod statistics;
pub(crate) mod storage;
/// Wire up and create Gateway instance
@@ -304,6 +307,18 @@ where
active_clients_store.clone(),
);
if self.config.get_enabled_statistics() {
let statistics_service_url = self.config.get_statistics_service_url();
let stats_collector = GatewayStatisticsCollector::new(
active_clients_store.clone(),
statistics_service_url,
);
let mut stats_sender = StatisticsSender::new(stats_collector);
tokio::spawn(async move {
stats_sender.run().await;
});
}
self.start_client_websocket_listener(
mix_forwarding_channel,
active_clients_store,
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use sqlx::types::chrono::{DateTime, Utc};
use std::time::Duration;
use url::Url;
use statistics_common::{
api::build_and_send_statistics_request, collector::StatisticsCollector, error::StatsError,
StatsData, StatsGatewayData, StatsMessage,
};
use crate::node::client_handling::active_clients::ActiveClientsStore;
pub(crate) struct GatewayStatisticsCollector {
active_clients_store: ActiveClientsStore,
statistics_service_url: Url,
}
impl GatewayStatisticsCollector {
pub fn new(active_clients_store: ActiveClientsStore, statistics_service_url: Url) -> Self {
GatewayStatisticsCollector {
active_clients_store,
statistics_service_url,
}
}
}
#[async_trait]
impl StatisticsCollector for GatewayStatisticsCollector {
async fn create_stats_message(
&self,
interval: Duration,
timestamp: DateTime<Utc>,
) -> StatsMessage {
let inbox_count = self.active_clients_store.size() as u32;
let stats_data = vec![StatsData::Gateway(StatsGatewayData { inbox_count })];
StatsMessage {
stats_data,
interval_seconds: interval.as_secs() as u32,
timestamp: timestamp.to_rfc3339(),
}
}
async fn send_stats_message(&self, stats_message: StatsMessage) -> Result<(), StatsError> {
build_and_send_statistics_request(stats_message, self.statistics_service_url.to_string())
.await
}
async fn reset_stats(&mut self) {}
}
+4
View File
@@ -0,0 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod collector;
+1 -1
View File
@@ -26,7 +26,7 @@ humantime-serde = "1.0"
log = "0.4.0"
pretty_env_logger = "0.4.0"
rand = "0.7.3"
rocket = { version="0.5.0-rc.1", features = ["json"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
serde = { version="1.0", features = ["derive"] }
tokio = { version="1.19.1", features = ["rt-multi-thread", "net", "signal"] }
tokio-util = { version="0.7.3", features = ["codec"] }
View File
+3 -5
View File
@@ -56,11 +56,9 @@ pub async fn init_socks5(provider_address: &str, chosen_gateway_id: Option<&str>
.await;
info!("Setting gateway endpoint");
config.get_base_mut().with_gateway_endpoint(
gateway_details.identity_key.to_base58_string(),
gateway_details.owner.clone(),
gateway_details.clients_address(),
);
config
.get_base_mut()
.with_gateway_endpoint(gateway_details.into());
info!("Insert gateway shared key");
key_manager.insert_gateway_shared_key(shared_keys);
+3 -3
View File
@@ -1,10 +1,10 @@
use crate::window_toggle;
use tauri::{
AppHandle, CustomMenuItem, Menu,
SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem, Wry,
AppHandle, CustomMenuItem, Menu, SystemTray, SystemTrayEvent, SystemTrayMenu,
SystemTrayMenuItem, Wry,
};
#[cfg(target_os = "macos")]
use tauri::{CustomMenuItem, MenuItem, Submenu, SystemTray, SystemTrayMenu, SystemTrayMenuItem};
use tauri::{MenuItem, Submenu};
pub trait AddDefaultSubmenus {
fn add_default_app_submenu_if_macos(self) -> Self;
+1 -1
View File
@@ -1,6 +1,6 @@
use futures::channel::mpsc;
use futures::SinkExt;
use log::info;
use futures::channel::mpsc;
use config::NymConfig;
#[cfg(not(feature = "coconut"))]
+19 -2
View File
@@ -308,10 +308,27 @@ fn _archive_wallet_file(path: &Path) -> Result<(), BackendError> {
}
pub(crate) fn archive_wallet_file() -> Result<(), BackendError> {
log::info!("Archiving wallet file");
let store_dir = get_storage_directory()?;
let filepath = store_dir.join(WALLET_INFO_FILENAME);
_archive_wallet_file(&filepath)
if filepath.exists() {
if let Some(filepath) = filepath.to_str() {
log::info!("Archiving wallet file: {}", filepath);
} else {
log::info!("Archiving wallet file");
}
_archive_wallet_file(&filepath)
} else {
if let Some(filepath) = filepath.to_str() {
log::info!(
"Skipping archiving wallet file, as it's not found: {}",
filepath
);
} else {
log::info!("Skipping archiving wallet file, as it's not found");
}
Err(BackendError::WalletFileNotFound)
}
}
/// Remove an account from inside the encrypted login.
@@ -3,7 +3,6 @@ import {
Box,
Chip,
CircularProgress,
Link,
Table,
TableBody,
TableCell,
@@ -19,6 +18,7 @@ import { visuallyHidden } from '@mui/utils';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
import { DelegationWithEverything } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { format, formatDistanceToNow, parseISO } from 'date-fns';
import { styled } from '@mui/material/styles';
import { tableCellClasses } from '@mui/material/TableCell';
@@ -163,11 +163,8 @@ export const DelegationList: React.FC<{
<Link
target="_blank"
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
color="inherit"
underline="none"
>
{item.node_identity.slice(0, 6)}...{item.node_identity.slice(-6)}
</Link>
text={`${item.node_identity.slice(0, 6)}...${item.node_identity.slice(-6)}`}
/>
</Tooltip>
</TableCell>
<TableCell align="center">{!item.avg_uptime_percent ? '-' : `${item.avg_uptime_percent}%`}</TableCell>
@@ -1,5 +1,6 @@
import React from 'react';
import { Box, Button, CircularProgress, Link, Modal, Stack, Typography } from '@mui/material';
import { Box, Button, CircularProgress, Modal, Stack, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { modalStyle } from '../Modals/styles';
export type ActionType = 'delegate' | 'undelegate' | 'redeem' | 'redeem-all' | 'compound';
@@ -101,9 +102,13 @@ export const DelegationModal: React.FC<
<Typography mb={1} fontSize="small" color={(theme) => theme.palette.text.secondary}>
Check the transaction {transactions.length > 1 ? 'hashes' : 'hash'}:
{transactions.map((transaction) => (
<Link key={transaction.hash} href={transaction.url} target="_blank" sx={{ ml: 1 }}>
{transaction.hash.slice(0, 6)}
</Link>
<Link
key={transaction.hash}
href={transaction.url}
target="_blank"
sx={{ ml: 1 }}
text={transaction.hash.slice(0, 6)}
/>
))}
</Typography>
)}
@@ -1,6 +1,7 @@
import React from 'react';
import { Box, Link, Typography } from '@mui/material';
import { Box, Typography } from '@mui/material';
import { DelegationWithEverything } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { DelegationList } from './DelegationList';
import { DelegationListItemActions } from './DelegationActions';
@@ -18,13 +19,7 @@ export const Delegations: React.FC<{
onItemActionClick={onDelegationItemActionClick}
/>
<Box sx={{ mt: 3 }}>
<Link
href={`${explorerUrl}/network-components/mixnodes/`}
target="_blank"
rel="noreferrer"
underline="hover"
color={(theme) => theme.palette.text.primary}
>
<Link href={`${explorerUrl}/network-components/mixnodes/`} target="_blank" rel="noreferrer">
Check the{' '}
<Typography color="primary.main" component="span">
list of mixnodes
@@ -2,7 +2,6 @@ import React, { FC } from 'react';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {
Box,
Link,
Table,
TableBody,
TableCell,
@@ -17,6 +16,7 @@ import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
import { DelegationEvent } from '@nymproject/types';
import { ArrowDropDown } from '@mui/icons-material';
import { visuallyHidden } from '@mui/utils';
import { Link } from '@nymproject/react/link/Link';
type Order = 'asc' | 'desc';
@@ -138,11 +138,8 @@ export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl:
<Link
target="_blank"
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
color="inherit"
underline="none"
>
{item.node_identity.slice(0, 6)}...{item.node_identity.slice(-6)}
</Link>
text={`${item.node_identity.slice(0, 6)}...${item.node_identity.slice(-6)}`}
/>
</Tooltip>
</TableCell>
<TableCell>{!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom}`}</TableCell>
+3
View File
@@ -90,6 +90,9 @@ export const Nav = () => {
<ListItemText
sx={{
color: location.pathname === route ? 'primary.main' : 'text.primary',
'& .MuiListItemText-primary': {
fontWeight: '600',
},
}}
primary={label}
/>
+16 -5
View File
@@ -143,14 +143,25 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => {
useEffect(() => {
let newValue = false;
if (network && appEnv?.ADMIN_ADDRESS && clientDetails?.client_address) {
const adminAddressMap = JSON.parse(appEnv.ADMIN_ADDRESS);
const adminAddresses = adminAddressMap[network] || [];
if (adminAddresses.length) {
newValue = adminAddresses.includes(clientDetails?.client_address);
try {
const adminAddressMap = JSON.parse(appEnv.ADMIN_ADDRESS);
const adminAddresses = adminAddressMap[network] || [];
if (adminAddresses.length) {
newValue = adminAddresses.includes(clientDetails?.client_address);
if (newValue) {
Console.log('Wallet is in admin mode: ', {
network,
adminAddress: adminAddressMap[network],
clientAddress: clientDetails?.client_address,
});
}
}
} catch (e) {
Console.error('Failed to check admin addresses', e);
}
}
setIsAdminAddress(newValue);
}, [appEnv, network]);
}, [appEnv, network, clientDetails?.client_address]);
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
if (value.length === 0) {
@@ -3,8 +3,8 @@ import { useNavigate } from 'react-router-dom';
import { Button, CircularProgress, FormControl, Stack } from '@mui/material';
import { useSnackbar } from 'notistack';
import { AuthContext } from 'src/context/auth';
import { createPassword } from 'src/requests';
import { PasswordInput } from 'src/components';
import { archiveWalletFile, createPassword, isPasswordCreated } from 'src/requests';
import { Subtitle, Title, PasswordStrength } from '../components';
export const ConnectPassword = () => {
@@ -20,6 +20,12 @@ export const ConnectPassword = () => {
const storePassword = async () => {
try {
setIsLoading(true);
const exists = await isPasswordCreated();
if (exists) {
await archiveWalletFile();
}
await createPassword({ mnemonic, password });
resetState();
enqueueSnackbar('Password successfully created', { variant: 'success' });
@@ -2,32 +2,15 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Stack, Typography } from '@mui/material';
import { useSnackbar } from 'notistack';
import { archiveWalletFile } from 'src/requests';
import { Console } from 'src/utils/console';
import { Subtitle } from '../components';
export const ForgotPassword = () => {
const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar();
return (
<>
<Subtitle subtitle="Create a new password or sign in with mnemonic" />
<Stack spacing={2} sx={{ width: 300 }}>
<Button
variant="contained"
size="large"
onClick={async () => {
try {
await archiveWalletFile();
} catch (e) {
Console.error(e);
enqueueSnackbar('Failed to archive your existing wallet file', { variant: 'error' });
}
navigate('/confirm-mnemonic');
}}
fullWidth
>
<Button variant="contained" size="large" onClick={() => navigate('/confirm-mnemonic')} fullWidth>
Create a new password
</Button>
<Typography sx={{ textAlign: 'center', fontWeight: 600 }}>or</Typography>
+7 -5
View File
@@ -1,6 +1,6 @@
import React, { useContext, useEffect } from 'react';
import { Alert, Button, Grid, Link, Typography } from '@mui/material';
import { OpenInNew } from '@mui/icons-material';
import { Alert, Grid, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { NymCard, ClientAddress } from '../../components';
import { AppContext, urls } from '../../context/main';
@@ -33,9 +33,11 @@ export const BalanceCard = () => {
</Grid>
{network && (
<Grid item>
<Link href={`${urls(network).blockExplorer}/account/${clientDetails?.client_address}`} target="_blank">
<Button endIcon={<OpenInNew />}>Last transactions</Button>
</Link>
<Link
href={`${urls(network).blockExplorer}/account/${clientDetails?.client_address}`}
target="_blank"
text="Last transactions"
/>
</Grid>
)}
</Grid>
+7 -4
View File
@@ -1,6 +1,7 @@
import React, { useContext, useState } from 'react';
import { Alert, AlertTitle, Box, Button, Link, Typography } from '@mui/material';
import { Alert, AlertTitle, Box, Button, Typography } from '@mui/material';
import { TransactionExecuteResult } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { DelegateForm } from './DelegateForm';
import { NymCard } from '../../components';
import { EnumRequestStatus, RequestStatus } from '../../components/RequestStatus';
@@ -76,9 +77,11 @@ export const Delegate = () => {
</NymCard>
<Typography sx={{ p: 3 }}>
Checkout the{' '}
<Link href={`${urls(network).networkExplorer}/network-components/mixnodes`} target="_blank">
list of mixnodes
</Link>{' '}
<Link
href={`${urls(network).networkExplorer}/network-components/mixnodes`}
target="_blank"
text="list of mixnodes"
/>{' '}
for uptime and performances to help make delegation decisions
</Typography>
</PageLayout>
+4 -8
View File
@@ -1,6 +1,7 @@
import React, { FC, useContext, useEffect, useState } from 'react';
import { Box, Button, Link, Paper, Stack, Typography } from '@mui/material';
import { Box, Button, Paper, Stack, Typography } from '@mui/material';
import { DelegationWithEverything, MajorCurrencyAmount } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { AppContext, urls } from 'src/context/main';
import { DelegationList } from 'src/components/Delegation/DelegationList';
import { PendingEvents } from 'src/components/Delegation/PendingEvents';
@@ -273,13 +274,8 @@ export const Delegation: FC = () => {
href={`${urls(network).networkExplorer}/network-components/mixnodes/`}
target="_blank"
rel="noreferrer"
underline="hover"
sx={{ color: 'primary.main', textDecorationColor: 'primary.main' }}
>
<Typography color="primary.main" variant="body2">
Network Explorer
</Typography>
</Link>
text="Network Explorer"
/>
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<RewardsSummary isLoading={isLoading} totalDelegation={totalDelegations} totalRewards={totalRewards} />
@@ -1,6 +1,7 @@
import React, { useContext } from 'react';
import { Box, CircularProgress, Link, Typography } from '@mui/material';
import { Box, CircularProgress, Typography } from '@mui/material';
import { TransactionDetails as TTransactionDetails } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { SendError } from './SendError';
import { AppContext, urls } from '../../context/main';
import { SuccessReponse } from '../../components';
@@ -38,9 +39,11 @@ export const SendConfirmation = ({
subtitle={
<>
Check the transaction hash{' '}
<Link href={`${urls(network).blockExplorer}/transactions/${data.tx_hash}`} target="_blank">
here
</Link>
<Link
href={`${urls(network).blockExplorer}/transactions/${data.tx_hash}`}
target="_blank"
text="here"
/>
</>
}
caption={
+7 -5
View File
@@ -1,6 +1,6 @@
import React, { useContext } from 'react';
import { OpenInNew } from '@mui/icons-material';
import { Button, Link, Stack, Typography } from '@mui/material';
import { Stack, Typography } from '@mui/material';
import { Link } from '@nymproject/react/link/Link';
import { urls, AppContext } from '../../context/main';
export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => {
@@ -8,9 +8,11 @@ export const NodeStats = ({ mixnodeId }: { mixnodeId?: string }) => {
return (
<Stack spacing={2} sx={{ p: 4 }}>
<Typography>All your node stats are available on the link below</Typography>
<Link href={`${urls(network).networkExplorer}/network-components/mixnode/${mixnodeId}`} target="_blank">
<Button endIcon={<OpenInNew />}>Network Explorer</Button>
</Link>
<Link
href={`${urls(network).networkExplorer}/network-components/mixnode/${mixnodeId}`}
target="_blank"
text="Network Explorer"
/>
</Stack>
);
};
+1
View File
@@ -38,6 +38,7 @@ declare module '@mui/material/styles' {
dark: string;
muted: string;
};
linkHover: string;
}
interface NymPaletteVariant {
+6
View File
@@ -30,6 +30,7 @@ const nymPalette: NymPalette = {
dark: '#121726',
muted: '#7D7D7D',
},
linkHover: '#AF4D36',
};
const darkMode: NymPaletteVariant = {
@@ -235,6 +236,11 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
},
},
},
MuiLink: {
defaultProps: {
underline: 'none',
},
},
},
palette,
};
+3 -1
View File
@@ -13,7 +13,9 @@
"clean": "lerna run clean",
"build": "run-s build:types build:packages",
"build:types": "lerna run --scope @nymproject/types build --stream",
"build:packages": "lerna run --scope @nymproject/mui-theme --scope @nymproject/react --stream build",
"build:packages": "run-s build:packages:theme build:packages:react",
"build:packages:theme": "lerna run --scope @nymproject/mui-theme build",
"build:packages:react": "lerna run --scope @nymproject/react build",
"build:react-example": "lerna run --scope @nymproject/react-webpack-with-theme-example build --stream",
"build:playground": "lerna run --scope @nymproject/react storybook:build --stream",
"build:ci": "yarn build && run-p build:react-example build:playground && yarn build:ci:collect-artifacts",
@@ -10,6 +10,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { version = "0.1.51" }
clap = "2.33.0"
dirs = "3.0"
futures = "0.3"
@@ -22,7 +23,7 @@ reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]}
thiserror = "1"
tokio = { version = "1.19.1", features = [ "net", "rt-multi-thread", "macros", "time" ] }
tokio = { version = "1.19.1", features = [ "net", "rt-multi-thread", "macros" ] }
tokio-tungstenite = "0.14"
@@ -32,5 +33,5 @@ nymsphinx = { path = "../../common/nymsphinx" }
ordered-buffer = {path = "../../common/socks5/ordered-buffer"}
proxy-helpers = { path = "../../common/socks5/proxy-helpers" }
socks5-requests = { path = "../../common/socks5/requests" }
statistics = { path = "../../common/statistics" }
statistics-common = { path = "../../common/statistics" }
websocket-requests = { path = "../../clients/native/websocket-requests" }
@@ -3,7 +3,7 @@
use crate::allowed_hosts::{HostsStore, OutboundRequestFilter};
use crate::connection::Connection;
use crate::statistics::{StatisticsCollector, StatisticsSender, Timer};
use crate::statistics::ServiceStatisticsCollector;
use crate::websocket;
use crate::websocket::TSWebsocketStream;
use futures::channel::mpsc;
@@ -14,6 +14,7 @@ use nymsphinx::addressing::clients::Recipient;
use nymsphinx::receiver::ReconstructedMessage;
use proxy_helpers::connection_controller::{Controller, ControllerCommand, ControllerSender};
use socks5_requests::{ConnectionId, Message as Socks5Message, Request, Response};
use statistics_common::collector::StatisticsSender;
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use tokio_tungstenite::tungstenite::protocol::Message;
@@ -63,7 +64,7 @@ impl ServiceProvider {
async fn mixnet_response_listener(
mut websocket_writer: SplitSink<TSWebsocketStream, Message>,
mut mix_reader: mpsc::UnboundedReceiver<(Socks5Message, Recipient)>,
stats_collector: Option<StatisticsCollector>,
stats_collector: Option<ServiceStatisticsCollector>,
) {
// TODO: wire SURBs in here once they're available
while let Some((msg, return_address)) = mix_reader.next().await {
@@ -228,7 +229,7 @@ impl ServiceProvider {
raw_request: &[u8],
controller_sender: &mut ControllerSender,
mix_input_sender: &mpsc::UnboundedSender<(Socks5Message, Recipient)>,
stats_collector: Option<StatisticsCollector>,
stats_collector: Option<ServiceStatisticsCollector>,
) {
let deserialized_msg = match Socks5Message::try_from_bytes(raw_request) {
Ok(msg) => msg,
@@ -290,12 +291,6 @@ impl ServiceProvider {
let (mix_input_sender, mix_input_receiver) =
mpsc::unbounded::<(Socks5Message, Recipient)>();
let (mut timer_sender, timer_receiver) = Timer::new();
let interval = timer_sender.interval();
tokio::spawn(async move {
timer_sender.run().await;
});
// controller for managing all active connections
let (mut active_connections_controller, mut controller_sender) = Controller::new();
tokio::spawn(async move {
@@ -303,15 +298,14 @@ impl ServiceProvider {
});
let stats_collector = if self.enable_statistics {
let mut stats_sender =
StatisticsSender::new(interval, timer_receiver, self.stats_provider_addr)
let stats_collector =
ServiceStatisticsCollector::new(self.stats_provider_addr, mix_input_sender.clone())
.await
.expect("Statistics controller could not be bootstrapped");
let stats_collector = StatisticsCollector::from(&stats_sender);
.expect("Service statistics collector could not be bootstrapped");
let mut stats_sender = StatisticsSender::new(stats_collector.clone());
let mix_input_sender_clone = mix_input_sender.clone();
tokio::spawn(async move {
stats_sender.run(&mix_input_sender_clone).await;
stats_sender.run().await;
});
Some(stats_collector)
} else {
@@ -36,7 +36,7 @@ fn parse_args<'a>() -> ArgMatches<'a> {
)
.arg(
Arg::with_name(ENABLE_STATISTICS)
.help("enable service statistics that get sent to a statistics aggregator server")
.help("enable service anonymized statistics that get sent to a statistics aggregator server")
.long(ENABLE_STATISTICS),
)
.arg(
@@ -0,0 +1,215 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use futures::channel::mpsc;
use log::*;
use rand::RngCore;
use serde::Deserialize;
use sqlx::types::chrono::{DateTime, Utc};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use network_defaults::DEFAULT_NETWORK;
use nymsphinx::addressing::clients::Recipient;
use ordered_buffer::OrderedMessageSender;
use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Request};
use statistics_common::api::{
build_statistics_request_bytes, DEFAULT_STATISTICS_SERVICE_ADDRESS,
DEFAULT_STATISTICS_SERVICE_PORT,
};
use statistics_common::{
collector::StatisticsCollector, error::StatsError as CommonStatsError, StatsMessage,
StatsServiceData,
};
use super::error::StatsError;
const REMOTE_SOURCE_OF_STATS_PROVIDER_CONFIG: &str =
"https://nymtech.net/.wellknown/network-requester/stats-provider.json";
#[derive(Clone, Debug)]
pub struct StatsData {
client_processed_bytes: HashMap<String, u32>,
}
impl StatsData {
pub fn new() -> Self {
StatsData {
client_processed_bytes: HashMap::new(),
}
}
pub fn processed(&mut self, remote_addr: &str, bytes: u32) {
if let Some(curr_bytes) = self.client_processed_bytes.get_mut(remote_addr) {
*curr_bytes += bytes;
} else {
self.client_processed_bytes
.insert(remote_addr.to_string(), bytes);
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct StatsProviderConfigEntry {
stats_client_address: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OptionalStatsProviderConfig {
mainnet: Option<StatsProviderConfigEntry>,
sandbox: Option<StatsProviderConfigEntry>,
qa: Option<StatsProviderConfigEntry>,
}
impl OptionalStatsProviderConfig {
pub fn stats_client_address(&self) -> Option<String> {
let entry_config = match DEFAULT_NETWORK {
network_defaults::all::Network::MAINNET => self.mainnet.clone(),
network_defaults::all::Network::SANDBOX => self.sandbox.clone(),
network_defaults::all::Network::QA => self.qa.clone(),
};
entry_config.map(|e| e.stats_client_address)
}
}
#[derive(Clone)]
pub struct ServiceStatisticsCollector {
pub(crate) request_stats_data: Arc<RwLock<StatsData>>,
pub(crate) response_stats_data: Arc<RwLock<StatsData>>,
pub(crate) connected_services: Arc<RwLock<HashMap<ConnectionId, RemoteAddress>>>,
stats_provider_addr: Recipient,
mix_input_sender: mpsc::UnboundedSender<(Socks5Message, Recipient)>,
}
impl ServiceStatisticsCollector {
pub async fn new(
stats_provider_addr: Option<Recipient>,
mix_input_sender: mpsc::UnboundedSender<(Socks5Message, Recipient)>,
) -> Result<Self, StatsError> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()?;
let stats_provider_config: OptionalStatsProviderConfig = client
.get(REMOTE_SOURCE_OF_STATS_PROVIDER_CONFIG.to_string())
.send()
.await?
.json()
.await?;
let stats_provider_addr = stats_provider_addr.unwrap_or(
Recipient::try_from_base58_string(
stats_provider_config
.stats_client_address()
.ok_or(StatsError::InvalidClientAddress)?,
)
.map_err(|_| StatsError::InvalidClientAddress)?,
);
Ok(ServiceStatisticsCollector {
request_stats_data: Arc::new(RwLock::new(StatsData::new())),
response_stats_data: Arc::new(RwLock::new(StatsData::new())),
connected_services: Arc::new(RwLock::new(HashMap::new())),
stats_provider_addr,
mix_input_sender,
})
}
}
#[async_trait]
impl StatisticsCollector for ServiceStatisticsCollector {
async fn create_stats_message(
&self,
interval: Duration,
timestamp: DateTime<Utc>,
) -> StatsMessage {
let stats_data = {
let request_data_bytes = self.request_stats_data.read().await;
let response_data_bytes = self.response_stats_data.read().await;
let services: HashSet<String> = request_data_bytes
.client_processed_bytes
.keys()
.chain(response_data_bytes.client_processed_bytes.keys())
.cloned()
.collect();
services
.into_iter()
.map(|requested_service| {
let request_bytes = request_data_bytes
.client_processed_bytes
.get(&requested_service)
.copied()
.unwrap_or(0);
let response_bytes = response_data_bytes
.client_processed_bytes
.get(&requested_service)
.copied()
.unwrap_or(0);
statistics_common::StatsData::Service(StatsServiceData::new(
requested_service,
request_bytes,
response_bytes,
))
})
.collect()
};
StatsMessage {
stats_data,
interval_seconds: interval.as_secs() as u32,
timestamp: timestamp.to_rfc3339(),
}
}
async fn send_stats_message(
&self,
stats_message: StatsMessage,
) -> Result<(), CommonStatsError> {
let msg = build_statistics_request_bytes(stats_message)?;
trace!("Connecting to statistics service");
let mut rng = rand::rngs::OsRng;
let conn_id = rng.next_u64();
let connect_req = Request::new_connect(
conn_id,
format!(
"{}:{}",
DEFAULT_STATISTICS_SERVICE_ADDRESS, DEFAULT_STATISTICS_SERVICE_PORT
),
self.stats_provider_addr,
);
self.mix_input_sender
.unbounded_send((
Socks5Message::Request(connect_req),
self.stats_provider_addr,
))
.unwrap();
trace!("Sending data to statistics service");
let mut message_sender = OrderedMessageSender::new();
let ordered_msg = message_sender.wrap_message(msg).into_bytes();
let send_req = Request::new_send(conn_id, ordered_msg, true);
self.mix_input_sender
.unbounded_send((Socks5Message::Request(send_req), self.stats_provider_addr))
.unwrap();
Ok(())
}
async fn reset_stats(&mut self) {
self.request_stats_data
.write()
.await
.client_processed_bytes
.iter_mut()
.for_each(|(_, b)| *b = 0);
self.response_stats_data
.write()
.await
.client_processed_bytes
.iter_mut()
.for_each(|(_, b)| *b = 0);
}
}
@@ -1,229 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use futures::StreamExt;
use log::*;
use rand::RngCore;
use serde::Deserialize;
use sqlx::types::chrono::{DateTime, Utc};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use network_defaults::DEFAULT_NETWORK;
use nymsphinx::addressing::clients::Recipient;
use ordered_buffer::OrderedMessageSender;
use socks5_requests::{ConnectionId, Message as Socks5Message, RemoteAddress, Request};
use statistics::api::{
build_statistics_request_bytes, DEFAULT_STATISTICS_SERVICE_ADDRESS,
DEFAULT_STATISTICS_SERVICE_PORT,
};
use statistics::{StatsMessage, StatsServiceData};
use super::error::StatsError;
const REMOTE_SOURCE_OF_STATS_PROVIDER_CONFIG: &str =
"https://nymtech.net/.wellknown/network-requester/stats-provider.json";
#[derive(Clone, Debug)]
pub struct StatsData {
client_processed_bytes: HashMap<String, u32>,
}
impl StatsData {
pub fn new() -> Self {
StatsData {
client_processed_bytes: HashMap::new(),
}
}
pub fn processed(&mut self, remote_addr: &str, bytes: u32) {
if let Some(curr_bytes) = self.client_processed_bytes.get_mut(remote_addr) {
*curr_bytes += bytes;
} else {
self.client_processed_bytes
.insert(remote_addr.to_string(), bytes);
}
}
}
#[derive(Clone, Debug, Deserialize)]
pub struct StatsProviderConfigEntry {
stats_client_address: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct OptionalStatsProviderConfig {
mainnet: Option<StatsProviderConfigEntry>,
sandbox: Option<StatsProviderConfigEntry>,
qa: Option<StatsProviderConfigEntry>,
}
impl OptionalStatsProviderConfig {
pub fn stats_client_address(&self) -> Option<String> {
let entry_config = match DEFAULT_NETWORK {
network_defaults::all::Network::MAINNET => self.mainnet.clone(),
network_defaults::all::Network::SANDBOX => self.sandbox.clone(),
network_defaults::all::Network::QA => self.qa.clone(),
};
entry_config.map(|e| e.stats_client_address)
}
}
#[derive(Clone)]
pub struct StatisticsCollector {
pub(crate) request_stats_data: Arc<RwLock<StatsData>>,
pub(crate) response_stats_data: Arc<RwLock<StatsData>>,
pub(crate) connected_services: Arc<RwLock<HashMap<ConnectionId, RemoteAddress>>>,
}
impl StatisticsCollector {
pub fn from(stats: &StatisticsSender) -> Self {
Self {
request_stats_data: Arc::clone(&stats.request_data),
response_stats_data: Arc::clone(&stats.response_data),
connected_services: Arc::new(RwLock::new(HashMap::new())),
}
}
}
pub struct StatisticsSender {
request_data: Arc<RwLock<StatsData>>,
response_data: Arc<RwLock<StatsData>>,
interval_seconds: u32,
timestamp: DateTime<Utc>,
timer_receiver: mpsc::Receiver<()>,
stats_provider_addr: Recipient,
}
impl StatisticsSender {
pub async fn new(
interval_seconds: Duration,
timer_receiver: mpsc::Receiver<()>,
stats_provider_addr: Option<Recipient>,
) -> Result<Self, StatsError> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()?;
let stats_provider_config: OptionalStatsProviderConfig = client
.get(REMOTE_SOURCE_OF_STATS_PROVIDER_CONFIG.to_string())
.send()
.await?
.json()
.await?;
let stats_provider_addr = stats_provider_addr.unwrap_or(
Recipient::try_from_base58_string(
stats_provider_config
.stats_client_address()
.ok_or(StatsError::InvalidClientAddress)?,
)
.map_err(|_| StatsError::InvalidClientAddress)?,
);
Ok(StatisticsSender {
request_data: Arc::new(RwLock::new(StatsData::new())),
response_data: Arc::new(RwLock::new(StatsData::new())),
timestamp: Utc::now(),
interval_seconds: interval_seconds.as_secs() as u32,
timer_receiver,
stats_provider_addr,
})
}
pub async fn run(
&mut self,
mix_input_sender: &mpsc::UnboundedSender<(Socks5Message, Recipient)>,
) {
loop {
if self.timer_receiver.next().await == None {
error!("Timer thread has died. No more statistics will be sent");
} else {
let stats_data = {
let request_data_bytes = self.request_data.read().await;
let response_data_bytes = self.response_data.read().await;
let services: HashSet<String> = request_data_bytes
.client_processed_bytes
.keys()
.chain(response_data_bytes.client_processed_bytes.keys())
.cloned()
.collect();
services
.into_iter()
.map(|requested_service| {
let request_bytes = request_data_bytes
.client_processed_bytes
.get(&requested_service)
.copied()
.unwrap_or(0);
let response_bytes = response_data_bytes
.client_processed_bytes
.get(&requested_service)
.copied()
.unwrap_or(0);
StatsServiceData::new(requested_service, request_bytes, response_bytes)
})
.collect()
};
let stats_message = StatsMessage {
stats_data,
interval_seconds: self.interval_seconds,
timestamp: self.timestamp.to_rfc3339(),
};
match build_statistics_request_bytes(stats_message) {
Ok(data) => {
trace!("Connecting to statistics service");
let mut rng = rand::rngs::OsRng;
let conn_id = rng.next_u64();
let connect_req = Request::new_connect(
conn_id,
format!(
"{}:{}",
DEFAULT_STATISTICS_SERVICE_ADDRESS, DEFAULT_STATISTICS_SERVICE_PORT
),
self.stats_provider_addr,
);
mix_input_sender
.unbounded_send((
Socks5Message::Request(connect_req),
self.stats_provider_addr,
))
.unwrap();
trace!("Sending data to statistics service");
let mut message_sender = OrderedMessageSender::new();
let ordered_msg = message_sender.wrap_message(data).into_bytes();
let send_req = Request::new_send(conn_id, ordered_msg, true);
mix_input_sender
.unbounded_send((
Socks5Message::Request(send_req),
self.stats_provider_addr,
))
.unwrap();
}
Err(e) => error!("Statistics not sent: {}", e),
}
self.reset_stats().await;
}
}
}
async fn reset_stats(&mut self) {
self.request_data
.write()
.await
.client_processed_bytes
.iter_mut()
.for_each(|(_, b)| *b = 0);
self.response_data
.write()
.await
.client_processed_bytes
.iter_mut()
.for_each(|(_, b)| *b = 0);
self.timestamp = Utc::now();
}
}
@@ -10,4 +10,7 @@ pub enum StatsError {
#[error("Invalid stats provider client address")]
InvalidClientAddress,
#[error("Common statistics error {0}")]
CommonError(#[from] statistics_common::error::StatsError),
}
@@ -1,9 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod comm;
mod collector;
mod error;
mod timer;
pub use comm::{StatisticsCollector, StatisticsSender, StatsData};
pub use timer::Timer;
pub use collector::ServiceStatisticsCollector;
@@ -1,40 +0,0 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use futures::channel::mpsc;
use std::time::Duration;
use tokio::time;
const STATISTICS_INTERVAL: Duration = Duration::from_secs(60);
pub type TimerReceiver = mpsc::Receiver<()>;
pub struct Timer {
interval: Duration,
stats_sender: mpsc::Sender<()>,
}
impl Timer {
pub fn new() -> (Self, TimerReceiver) {
let (stats_sender, stats_receiver) = mpsc::channel::<()>(1);
(
Timer {
interval: STATISTICS_INTERVAL,
stats_sender,
},
stats_receiver,
)
}
pub fn interval(&self) -> Duration {
self.interval
}
pub async fn run(&mut self) {
let mut interval = time::interval(self.interval);
loop {
interval.tick().await;
let _ = self.stats_sender.try_send(());
}
}
}
@@ -9,14 +9,14 @@ edition = "2021"
dirs = "3.0"
log = "0.4"
pretty_env_logger = "0.4"
rocket = { version = "0.5.0-rc.1", features = ["json"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "chrono"]}
thiserror = "1"
tokio = { version = "1.4", features = [ "net", "rt-multi-thread", "macros", "time" ] }
tokio-tungstenite = "0.14"
statistics = { path = "../../common/statistics" }
statistics-common = { path = "../../common/statistics" }
[build-dependencies]
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
@@ -11,4 +11,11 @@ CREATE TABLE service_statistics
response_processed_bytes INTEGER NOT NULL,
interval_seconds INTEGER NOT NULL,
timestamp DATETIME NOT NULL
);
CREATE TABLE gateway_statistics
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
inbox_count INTEGER NOT NULL,
timestamp DATETIME NOT NULL
);
@@ -8,7 +8,7 @@ use crate::storage::NetworkStatisticsStorage;
use error::Result;
use routes::{post_all_statistics, post_statistic};
use statistics::api::STATISTICS_SERVICE_VERSION;
use statistics_common::api::STATISTICS_SERVICE_VERSION;
mod error;
mod routes;
@@ -5,19 +5,25 @@ use rocket::serde::json::Json;
use rocket::State;
use serde::{Deserialize, Serialize};
use statistics::StatsMessage;
use statistics_common::StatsMessage;
use crate::api::error::Result;
use crate::storage::NetworkStatisticsStorage;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ServiceStatisticsRequest {
pub struct StatisticsRequest {
// date, RFC 3339 format
since: String,
// date, RFC 3339 format
until: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub enum GenericStatistic {
Service(ServiceStatistic),
Gateway(GatewayStatistic),
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct ServiceStatistic {
pub requested_service: String,
@@ -27,28 +33,51 @@ pub struct ServiceStatistic {
pub timestamp: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct GatewayStatistic {
pub inbox_count: u32,
pub timestamp: String,
}
#[rocket::post("/all-statistics", data = "<all_statistics_request>")]
pub(crate) async fn post_all_statistics(
all_statistics_request: Json<ServiceStatisticsRequest>,
all_statistics_request: Json<StatisticsRequest>,
storage: &State<NetworkStatisticsStorage>,
) -> Result<Json<Vec<ServiceStatistic>>> {
let service_statistics = storage
) -> Result<Json<Vec<GenericStatistic>>> {
let all_statistics = storage
.get_service_statistics_in_interval(
&all_statistics_request.since,
&all_statistics_request.until,
)
.await?
.into_iter()
.map(|data| ServiceStatistic {
requested_service: data.requested_service,
request_processed_bytes: data.request_processed_bytes as u32,
response_processed_bytes: data.response_processed_bytes as u32,
interval_seconds: data.interval_seconds as u32,
timestamp: data.timestamp.to_string(),
.map(|data| {
GenericStatistic::Service(ServiceStatistic {
requested_service: data.requested_service,
request_processed_bytes: data.request_processed_bytes as u32,
response_processed_bytes: data.response_processed_bytes as u32,
interval_seconds: data.interval_seconds as u32,
timestamp: data.timestamp.to_string(),
})
})
.chain(
storage
.get_gateway_statistics_in_interval(
&all_statistics_request.since,
&all_statistics_request.until,
)
.await?
.into_iter()
.map(|data| {
GenericStatistic::Gateway(GatewayStatistic {
inbox_count: data.inbox_count as u32,
timestamp: data.timestamp.to_string(),
})
}),
)
.collect();
Ok(Json(service_statistics))
Ok(Json(all_statistics))
}
#[rocket::post("/statistic", data = "<statistic>")]
@@ -56,6 +85,6 @@ pub(crate) async fn post_statistic(
statistic: Json<StatsMessage>,
storage: &State<NetworkStatisticsStorage>,
) -> Result<Json<()>> {
storage.insert_service_statistics(statistic.0).await?;
storage.insert_statistics(statistic.0).await?;
Ok(Json(()))
}
@@ -3,7 +3,7 @@
use sqlx::types::chrono::{DateTime, Utc};
use crate::storage::models::ServiceStatistics;
use crate::storage::models::{GatewayStatistics, ServiceStatistics};
#[derive(Clone)]
pub(crate) struct StorageManager {
@@ -12,7 +12,7 @@ pub(crate) struct StorageManager {
// all SQL goes here
impl StorageManager {
/// Adds an entry for some statistical data.
/// Adds an entry for some service statistical data.
///
/// # Arguments
///
@@ -20,6 +20,7 @@ impl StorageManager {
/// * `request_processed_bytes`: Number of bytes for socks5 requests.
/// * `response_processed_bytes`: Number of bytes for socks5 responses.
/// * `interval_seconds`: Duration in seconds in which the data was gathered.
/// * `timestamp`: The moment in time when the data started being collected.
pub(super) async fn insert_service_statistics(
&self,
requested_service: String,
@@ -42,7 +43,30 @@ impl StorageManager {
Ok(())
}
/// Returns data submitted within the provided time interval.
/// Adds an entry for some gateway statistical data.
///
/// # Arguments
///
/// * `inbox_count`: Number of clients of a gateway.
/// * `interval_seconds`: Duration in seconds in which the data was gathered.
/// * `timestamp`: The moment in time when the data started being collected.
pub(super) async fn insert_gateway_statistics(
&self,
inbox_count: u32,
timestamp: DateTime<Utc>,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO gateway_statistics(inbox_count, timestamp) VALUES (?, ?)",
inbox_count,
timestamp,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Returns service statistical data submitted within the provided time interval.
///
/// # Arguments
///
@@ -62,4 +86,25 @@ impl StorageManager {
.fetch_all(&self.connection_pool)
.await
}
/// Returns gateway statistical data submitted within the provided time interval.
///
/// # Arguments
///
/// * `since`: indicates the lower bound timestamp for the data
/// * `until`: indicates the upper bound timestamp for the data
pub(super) async fn get_gateway_statistics_in_interval(
&self,
since: DateTime<Utc>,
until: DateTime<Utc>,
) -> Result<Vec<GatewayStatistics>, sqlx::Error> {
sqlx::query_as!(
GatewayStatistics,
"SELECT * FROM gateway_statistics WHERE timestamp BETWEEN ? AND ?",
since,
until
)
.fetch_all(&self.connection_pool)
.await
}
}
@@ -6,11 +6,11 @@ use sqlx::types::chrono::{DateTime, Utc};
use sqlx::ConnectOptions;
use std::path::PathBuf;
use statistics::StatsMessage;
use statistics_common::StatsMessage;
use crate::storage::error::NetworkStatisticsStorageError;
use crate::storage::manager::StorageManager;
use crate::storage::models::ServiceStatistics;
use crate::storage::models::{GatewayStatistics, ServiceStatistics};
pub(crate) mod error;
mod manager;
@@ -49,29 +49,38 @@ impl NetworkStatisticsStorage {
/// # Arguments
///
/// * `msg`: Message containing the statistical data.
pub(super) async fn insert_service_statistics(
pub(super) async fn insert_statistics(
&self,
msg: StatsMessage,
) -> Result<(), NetworkStatisticsStorageError> {
let timestamp: DateTime<Utc> = DateTime::parse_from_rfc3339(&msg.timestamp)
.map_err(|_| NetworkStatisticsStorageError::TimestampParse)?
.into();
for service_data in msg.stats_data {
self.manager
.insert_service_statistics(
service_data.requested_service.clone(),
service_data.request_bytes,
service_data.response_bytes,
msg.interval_seconds,
timestamp,
)
.await?;
for stats_data in msg.stats_data {
match stats_data {
statistics_common::StatsData::Service(service_data) => {
self.manager
.insert_service_statistics(
service_data.requested_service.clone(),
service_data.request_bytes,
service_data.response_bytes,
msg.interval_seconds,
timestamp,
)
.await?;
}
statistics_common::StatsData::Gateway(gateway_data) => {
self.manager
.insert_gateway_statistics(gateway_data.inbox_count, timestamp)
.await?
}
}
}
Ok(())
}
/// Returns data submitted within the provided time interval.
/// Returns service data submitted within the provided time interval.
///
/// # Arguments
///
@@ -93,4 +102,27 @@ impl NetworkStatisticsStorage {
.get_service_statistics_in_interval(since, until)
.await?)
}
/// Returns gateway data submitted within the provided time interval.
///
/// # Arguments
///
/// * `since`: indicates the lower bound timestamp for the data, RFC 3339 format
/// * `until`: indicates the upper bound timestamp for the data, RFC 3339 format
pub(super) async fn get_gateway_statistics_in_interval(
&self,
since: &str,
until: &str,
) -> Result<Vec<GatewayStatistics>, NetworkStatisticsStorageError> {
let since = DateTime::parse_from_rfc3339(since)
.map_err(|_| NetworkStatisticsStorageError::TimestampParse)?
.into();
let until = DateTime::parse_from_rfc3339(until)
.map_err(|_| NetworkStatisticsStorageError::TimestampParse)?
.into();
Ok(self
.manager
.get_gateway_statistics_in_interval(since, until)
.await?)
}
}
@@ -13,3 +13,10 @@ pub(crate) struct ServiceStatistics {
pub(crate) interval_seconds: i64,
pub(crate) timestamp: NaiveDateTime,
}
pub(crate) struct GatewayStatistics {
#[allow(dead_code)]
pub(crate) id: i64,
pub(crate) inbox_count: i64,
pub(crate) timestamp: NaiveDateTime,
}
@@ -18,6 +18,8 @@ export interface NymPalette {
muted: {
onDarkBg: string;
};
linkHover: string;
}
/**
@@ -79,6 +81,8 @@ export const nymPalette: NymPalette = {
muted: {
onDarkBg: '#666B77',
},
linkHover: '#AF4D36',
};
// -------------------------------------------------------------------------------------------------------------------
+5
View File
@@ -126,6 +126,11 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
},
},
},
MuiLink: {
defaultProps: {
underline: 'none',
},
},
},
palette,
};
+6
View File
@@ -50,3 +50,9 @@ This project uses [shared asset files](../../assets/README.md) such as favicons
## Publishing
This package is not published to NPM ... yet.
## TODO
- ugprade to React 18
- upgrade Storybook
- upgrade MUI
@@ -0,0 +1,26 @@
import * as React from 'react';
import { ComponentMeta } from '@storybook/react';
import { Typography } from '@mui/material';
import { Link as LinkIcon } from '@mui/icons-material';
import { Link } from './Link';
export default {
title: 'Basics/Link',
component: Link,
} as ComponentMeta<typeof Link>;
export const Default = () => <Link text="link" href="https://nymtech.net/" target="_blank" />;
export const NoIcon = () => <Link text="link" href="https://nymtech.net/" target="_blank" noIcon />;
export const WithCustomChildren = () => (
<Link href="https://nymtech.net/" target="_blank">
<LinkIcon />
</Link>
);
export const InTextExample = () => (
<Typography>
You can find the Nym website <Link href="https://nymtech.net/" target="_blank" text="here" />.
</Typography>
);
@@ -0,0 +1,42 @@
import * as React from 'react';
import { Box, Typography, Link as MUILink, LinkProps as MUILinkProps } from '@mui/material';
import { OpenInNew } from '@mui/icons-material';
export interface LinkProps {
text?: string;
icon?: React.ReactNode;
noIcon?: boolean;
}
export const Link = (props: MUILinkProps & LinkProps) => {
const { text, icon, underline, noIcon, children } = props;
let typoProps = {};
if (!noIcon) {
typoProps = { mr: 0.5 };
}
return (
<MUILink
{...props}
sx={{
display: 'inline-block',
':hover': {
color: (theme) => theme.palette.nym.linkHover,
},
}}
underline={underline || 'none'}
>
{children || (
<Box
sx={{
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
}}
>
<Typography sx={{ ...typoProps, fontWeight: 400 }}>{text}</Typography>
{!noIcon && (icon || <OpenInNew fontSize="inherit" />)}
</Box>
)}
</MUILink>
);
};
+3 -3
View File
@@ -27,7 +27,7 @@ pretty_env_logger = "0.4"
rand = "0.8"
rand-07 = { package = "rand", version = "0.7" } # required for compatibility
reqwest = { version = "0.11", features = ["json"] }
rocket = { version = "0.5.0-rc.1", features = ["json"] }
rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
serde = "1.0"
serde_json = "1.0"
@@ -42,11 +42,11 @@ ts-rs = "6.1.2"
anyhow = "1"
getset = "0.1.1"
rocket_sync_db_pools = { version = "0.1.0-rc.1", default-features = false }
rocket_sync_db_pools = { version = "0.1.0-rc.2", default-features = false }
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]}
okapi = { version = "0.7.0-rc.1", features = ["impl_json_schema"] }
rocket_okapi = { version = "0.8.0-rc.1", features = ["swagger"] }
rocket_okapi = { version = "0.8.0-rc.2", features = ["swagger"] }
schemars = { version = "0.8", features = ["preserve_order"] }
## internal