diff --git a/.github/actions/nym-hash-releases/create-hashes.mjs b/.github/actions/nym-hash-releases/create-hashes.mjs index 38f7e650e5..28e1e3e282 100644 --- a/.github/actions/nym-hash-releases/create-hashes.mjs +++ b/.github/actions/nym-hash-releases/create-hashes.mjs @@ -3,8 +3,27 @@ import fetch from "node-fetch"; import { Octokit } from "@octokit/rest"; import fs from "fs"; import path from "path"; +import { execSync } from "child_process"; + +function getBinInfo(path) { + // let's be super naive about it. add a+x bits on the file and try to run the command + try { + let mode = fs.statSync(path).mode + fs.chmodSync(path, mode | 0o111) + + const raw = execSync(`${path} build-info --output=json`, { stdio: 'pipe', encoding: "utf8" }); + const parsed = JSON.parse(raw) + return parsed + } catch (_) { + return undefined + } +} async function run(assets, algorithm, filename, cache) { + if (!cache) { + console.warn("cache is set to 'false', but we we no longer support it") + } + try { fs.mkdirSync('.tmp'); } catch(e) { @@ -19,26 +38,25 @@ async function run(assets, algorithm, filename, cache) { let buffer = null; let sig = null; - if(cache) { - // cache in `${WORKING_DIR}/.tmp/` - const cacheFilename = path.resolve(`.tmp/${asset.name}`); - if(!fs.existsSync(cacheFilename)) { - console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`); - buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer())); - fs.writeFileSync(cacheFilename, buffer); - } else { - console.log(`Loading from ${cacheFilename}`); - buffer = Buffer.from(fs.readFileSync(cacheFilename)); - // console.log('Reading signature from content'); - // if(asset.name.endsWith('.sig')) { - // sig = fs.readFileSync(cacheFilename).toString(); - // } - } - } else { - // fetch always + // cache in `${WORKING_DIR}/.tmp/` + const cacheFilename = path.resolve(`.tmp/${asset.name}`); + if(!fs.existsSync(cacheFilename)) { + console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`); buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer())); + fs.writeFileSync(cacheFilename, buffer); + } else { + console.log(`Loading from ${cacheFilename}`); + buffer = Buffer.from(fs.readFileSync(cacheFilename)); + + // console.log('Reading signature from content'); + // if(asset.name.endsWith('.sig')) { + // sig = fs.readFileSync(cacheFilename).toString(); + // } } + + const binInfo = getBinInfo(cacheFilename) + if(!hashes[asset.name]) { hashes[asset.name] = {}; } @@ -99,6 +117,9 @@ async function run(assets, algorithm, filename, cache) { if(kind) { hashes[asset.name].kind = kind; } + if(binInfo) { + hashes[asset.name].details = binInfo; + } // process Tauri signature files if(asset.name.endsWith('.sig')) { @@ -225,6 +246,8 @@ export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrI assets: hashes, }; + console.log(output) + if(upload) { console.log(`🚚 Uploading ${filename} to release name="${release.name}" id=${release.id} (${release.upload_url})...`); diff --git a/.github/workflows/cd-docs.yml b/.github/workflows/cd-docs.yml index 2f1f924861..9b6e6facc5 100644 --- a/.github/workflows/cd-docs.yml +++ b/.github/workflows/cd-docs.yml @@ -2,15 +2,14 @@ name: cd-docs on: workflow_dispatch: - push: - paths: - - 'documentation/docs/**' jobs: build: - runs-on: custom-linux + runs-on: ubuntu-20.04-16-core steps: - uses: actions/checkout@v3 + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler - name: Install rsync run: sudo apt-get install rsync - uses: rlespinasse/github-slug-action@v3.x @@ -26,14 +25,11 @@ jobs: with: command: build args: --workspace --release - - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.33" mdbook) - - name: Install mdbook plugins - run: | - cargo install --vers "=0.2.2" mdbook-variables && cargo install \ - --vers "^1.8.0" mdbook-admonish && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ - && cargo install --vers "^0.7.7" mdbook-linkcheck + - name: Install mdbook and plugins + run: cd documentation && ./install_mdbook_deps.sh + - name: Remove existing Nym config directory (`~/.nym/`) + run: cd documentation && ./remove_existing_config.sh + continue-on-error: false - name: Build all projects in documentation/ & move to ~/dist/docs/ run: cd documentation && ./build_all_to_dist.sh continue-on-error: false @@ -52,6 +48,7 @@ jobs: - name: Install Vercel CLI run: npm install --global vercel@latest + continue-on-error: false - name: Pull Vercel Environment Information (preview) if: github.ref != 'refs/heads/master' @@ -61,15 +58,18 @@ jobs: if: github.ref == 'refs/heads/master' run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} working-directory: dist/docs + continue-on-error: false - name: Build Project Artifacts (preview) if: github.ref != 'refs/heads/master' run: vercel build --token=${{ secrets.VERCEL_TOKEN }} working-directory: dist/docs + continue-on-error: false - name: Build Project Artifacts (production) if: github.ref == 'refs/heads/master' run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} working-directory: dist/docs + continue-on-error: false - name: Deploy Project Artifacts to Vercel (preview) if: github.ref != 'refs/heads/master' @@ -79,6 +79,7 @@ jobs: if: github.ref == 'refs/heads/master' run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} working-directory: dist/docs + continue-on-error: false - name: Matrix - Node Install run: npm install diff --git a/.github/workflows/ci-cargo-deny.yml b/.github/workflows/ci-cargo-deny.yml new file mode 100644 index 0000000000..a9a84fc271 --- /dev/null +++ b/.github/workflows/ci-cargo-deny.yml @@ -0,0 +1,21 @@ +name: ci-cargo-deny +on: [workflow_dispatch] +jobs: + cargo-deny: + runs-on: ubuntu-22.04 + strategy: + matrix: + checks: + # - advisories + - licenses + - bans sources + + continue-on-error: ${{ matrix.checks == 'licenses' }} + + steps: + - uses: actions/checkout@v3 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + log-level: warn + command: check ${{ matrix.checks }} + argument: --all-features diff --git a/.github/workflows/ci-docs.yml b/.github/workflows/ci-docs.yml index 3bcf0a2679..7e67a9f432 100644 --- a/.github/workflows/ci-docs.yml +++ b/.github/workflows/ci-docs.yml @@ -9,9 +9,11 @@ on: jobs: build: - runs-on: custom-linux + runs-on: ubuntu-20.04-16-core steps: - uses: actions/checkout@v3 + - name: Install Dependencies (Linux) + run: sudo apt-get update && sudo apt-get install -y build-essential curl wget libssl-dev libudev-dev squashfs-tools protobuf-compiler - name: Install rsync run: sudo apt-get install rsync - uses: rlespinasse/github-slug-action@v3.x @@ -27,22 +29,15 @@ jobs: with: command: build args: --workspace --release - - name: Install mdbook - run: (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.4.35" mdbook) - - name: Install mdbook plugins - run: | - cargo install --vers "=0.2.2" mdbook-variables && cargo install \ - --vers "^1.8.0" mdbook-admonish --force && cargo install --vers \ - "^0.1.2" mdbook-last-changed && cargo install --vers "^0.1.2" mdbook-theme \ - && cargo install --vers "^0.7.7" mdbook-linkcheck \ - # && cd documentation \ - # && mdbook-admonish install dev-portal \ - # && mdbook-admonish install docs \ - # && mdbook-admonish install operators - - - name: Build all projects in documentation/ & move to ~/dist/docs/ - run: cd documentation && ./build_all_to_dist.sh + - name: Install mdbook and plugins + run: cd documentation && ./install_mdbook_deps.sh + - name: Remove existing Nym config directory (`~/.nym/`) + run: cd documentation && ./remove_existing_config.sh continue-on-error: false + - name: Build all projects in documentation/ & move to ~/dist/docs/ + run: cd documentation && ./build_all_to_dist.sh + continue-on-error: false + - name: Deploy branch to CI www continue-on-error: true uses: easingthemes/ssh-deploy@main @@ -54,6 +49,7 @@ jobs: REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }} TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/docs-${{ env.GITHUB_REF_SLUG }} EXCLUDE: "/node_modules/" + - name: Matrix - Node Install run: npm install working-directory: .github/workflows/support-files diff --git a/.github/workflows/ci-nym-vpn-ui-rust.yml b/.github/workflows/ci-nym-vpn-ui-rust.yml index c60f11597d..89048d612d 100644 --- a/.github/workflows/ci-nym-vpn-ui-rust.yml +++ b/.github/workflows/ci-nym-vpn-ui-rust.yml @@ -35,7 +35,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: build - args: --manifest-path ${{ env.CARGOTOML_PATH }} --lib --features custom-protocol + args: --manifest-path ${{ env.CARGOTOML_PATH }} --features custom-protocol # - name: Run all tests # uses: actions-rs/cargo@v1 diff --git a/.github/workflows/deploy-github-pages.yml b/.github/workflows/deploy-github-pages.yml new file mode 100644 index 0000000000..7c7e689ee5 --- /dev/null +++ b/.github/workflows/deploy-github-pages.yml @@ -0,0 +1,43 @@ +# Simple workflow for deploying static content to GitHub Pages +name: Deploy static content to Pages + +on: + # Runs on pushes targeting the default branch + push: + branches: ["feature/ppa-repo"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Pages + uses: actions/configure-pages@v3 + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + # Upload entire repository + path: './ppa' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.gitignore b/.gitignore index aef1cee421..4dfbe52586 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ target .env .env.dev +envs/devnet.env /.vscode/settings.json validator/.vscode sample-configs/validator-config.toml @@ -45,4 +46,6 @@ envs/qwerty.env cpu-cycles/libcpucycles/build foxyfox.env -.next \ No newline at end of file +.next +ppa-private-key.b64 +ppa-private-key.asc \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 554b9bcb3b..f5a720221c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,17 +4,41 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https:// ## [Unreleased] +## [2023.5-rolo] (2023-11-28) + +- Gateway won't open websocket listener until embedded Network Requester becomes available ([#4166]) +- Feature/gateway described nr ([#4147]) +- Bugfix/prerelease versionbump ([#4145]) +- returning 'nil' for non-existing origin as opposed to an empty string ([#4135]) +- using performance^20 when calculating active set selection weight ([#4126]) +- Change default http API timeout from 3s to 10s ([#4117]) + +[#4166]: https://github.com/nymtech/nym/issues/4166 +[#4147]: https://github.com/nymtech/nym/pull/4147 +[#4145]: https://github.com/nymtech/nym/pull/4145 +[#4135]: https://github.com/nymtech/nym/pull/4135 +[#4126]: https://github.com/nymtech/nym/pull/4126 +[#4117]: https://github.com/nymtech/nym/pull/4117 + +## [2023.nyxd-upgrade] (2023-11-22) + +- Chore/nyxd 043 upgrade ([#3968]) + +[#3968]: https://github.com/nymtech/nym/pull/3968 + ## [2023.4-galaxy] (2023-11-07) - DRY up client cli ([#4077]) - [mixnode] replace rocket with axum ([#4071]) - incorporate the nym node HTTP api into the mixnode ([#4070]) - replaced '--disable-sign-ext' with '--signext-lowering' when running wasm-opt ([#3896]) +- Added PPA repo hosting support and nym-mixnode package with tooling for publishing ([#4165]) [#4077]: https://github.com/nymtech/nym/pull/4077 [#4071]: https://github.com/nymtech/nym/pull/4071 [#4070]: https://github.com/nymtech/nym/issues/4070 [#3896]: https://github.com/nymtech/nym/pull/3896 +[#4165]: https://github.com/nymtech/nym/pull/4165 ## [2023.3-kinder] (2023-10-31) diff --git a/Cargo.lock b/Cargo.lock index c167170d73..153ba1b793 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,17 +628,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-recursion" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.38", -] - [[package]] name = "async-stream" version = "0.3.5" @@ -998,27 +987,13 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "bls12_381" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" +version = "0.8.0" +source = "git+https://github.com/jstuczyn/bls12_381?branch=feature/gt-serialization-0.8.0#c4543fde7d02efea6ecfcf22e14476ddb516b483" dependencies = [ "digest 0.9.0", - "ff 0.10.1", - "group 0.10.0", - "pairing 0.20.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "bls12_381" -version = "0.6.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" -dependencies = [ - "digest 0.9.0", - "ff 0.11.1", - "group 0.11.0", - "pairing 0.21.0", + "ff 0.13.0", + "group 0.13.0", + "pairing", "rand_core 0.6.4", "subtle 2.4.1", "zeroize", @@ -1030,29 +1005,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" -[[package]] -name = "boringtun" -version = "0.6.0" -source = "git+https://github.com/cloudflare/boringtun?rev=e1d6360d6ab4529fc942a078e4c54df107abe2ba#e1d6360d6ab4529fc942a078e4c54df107abe2ba" -dependencies = [ - "aead 0.5.2", - "base64 0.13.1", - "blake2 0.10.6", - "chacha20poly1305 0.10.1", - "hex", - "hmac 0.12.1", - "ip_network", - "ip_network_table", - "libc", - "nix 0.25.1", - "parking_lot 0.12.1", - "rand_core 0.6.4", - "ring 0.16.20", - "tracing", - "untrusted 0.9.0", - "x25519-dalek 2.0.0", -] - [[package]] name = "brotli" version = "3.4.0" @@ -1554,8 +1506,8 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2895653b4d9f1538a83970077cb01dfc77a4810524e51a110944688e916b18e" dependencies = [ - "prost", - "prost-types", + "prost 0.11.9", + "prost-types 0.11.9", "tonic", "tracing-core", ] @@ -1572,7 +1524,7 @@ dependencies = [ "futures", "hdrhistogram", "humantime 2.1.0", - "prost-types", + "prost-types 0.11.9", "serde", "serde_json", "thread_local", @@ -1661,30 +1613,30 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ - "prost", - "prost-types", + "prost 0.12.1", + "prost-types 0.12.1", "tendermint-proto", ] [[package]] name = "cosmrs" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" +checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" dependencies = [ "bip32", "cosmos-sdk-proto", "ecdsa 0.16.8", "eyre", - "getrandom 0.2.10", "k256", "rand_core 0.6.4", "serde", "serde_json", + "signature 2.1.0", "subtle-encoding", "tendermint", "tendermint-rpc", @@ -2009,15 +1961,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.1", -] - [[package]] name = "ctor" version = "0.1.26" @@ -2346,6 +2289,25 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "defguard_wireguard_rs" +version = "0.3.0" +source = "git+https://github.com/neacsu/wireguard-rs.git?rev=c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed#c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed" +dependencies = [ + "base64 0.21.4", + "libc", + "log", + "netlink-packet-core 0.7.0", + "netlink-packet-generic", + "netlink-packet-route 0.17.1", + "netlink-packet-utils", + "netlink-packet-wireguard", + "netlink-sys", + "nix 0.27.1", + "serde", + "thiserror", +] + [[package]] name = "der" version = "0.6.1" @@ -2938,7 +2900,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "explorer-api" -version = "1.1.31" +version = "1.1.32" dependencies = [ "chrono", "clap 4.4.7", @@ -2994,7 +2956,7 @@ dependencies = [ [[package]] name = "extension-storage" -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" dependencies = [ "bip39", "console_error_panic_hook", @@ -3054,26 +3016,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "ff" version = "0.12.1" @@ -3090,6 +3032,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle 2.4.1", ] @@ -3543,30 +3486,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "byteorder", - "ff 0.10.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "group" version = "0.12.1" @@ -3696,30 +3615,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "headers" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" -dependencies = [ - "base64 0.21.4", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.3.3" @@ -3958,40 +3853,17 @@ dependencies = [ ] [[package]] -name = "hyper-proxy" -version = "0.9.1" +name = "hyper-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ - "bytes", - "futures", - "headers", + "futures-util", "http", "hyper", - "hyper-rustls", - "rustls-native-certs", + "rustls 0.21.7", "tokio", - "tokio-rustls 0.22.0", - "tower-service", - "webpki 0.21.4", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", - "rustls 0.19.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", + "tokio-rustls 0.24.1", ] [[package]] @@ -4248,22 +4120,6 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" -[[package]] -name = "ip_network_table" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" -dependencies = [ - "ip_network", - "ip_network_table-deps-treebitmap", -] - -[[package]] -name = "ip_network_table-deps-treebitmap" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" - [[package]] name = "ipconfig" version = "0.3.2" @@ -4282,6 +4138,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +[[package]] +name = "ipnetwork" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e" +dependencies = [ + "serde", +] + [[package]] name = "ipnetwork" version = "0.18.0" @@ -4364,6 +4229,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -4649,7 +4523,7 @@ dependencies = [ "once_cell", "parking_lot 0.12.1", "pin-project", - "prost", + "prost 0.11.9", "prost-build", "rand 0.8.5", "rw-stream-sink 0.3.0 (git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6)", @@ -4735,7 +4609,7 @@ dependencies = [ "libp2p-swarm 0.42.0", "log", "prometheus-client", - "prost", + "prost 0.11.9", "prost-build", "prost-codec", "rand 0.8.5", @@ -4793,7 +4667,7 @@ dependencies = [ "libp2p-swarm 0.42.0", "log", "lru 0.9.0", - "prost", + "prost 0.11.9", "prost-build", "prost-codec", "smallvec", @@ -4943,7 +4817,7 @@ dependencies = [ "libp2p-core 0.39.0", "log", "once_cell", - "prost", + "prost 0.11.9", "prost-build", "rand 0.8.5", "sha2 0.10.8", @@ -5195,7 +5069,7 @@ dependencies = [ "libp2p-noise 0.42.0", "log", "multihash", - "prost", + "prost 0.11.9", "prost-build", "prost-codec", "rand 0.8.5", @@ -5559,7 +5433,7 @@ dependencies = [ [[package]] name = "mix-fetch-wasm" -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" dependencies = [ "async-trait", "futures", @@ -5741,6 +5615,29 @@ dependencies = [ "netlink-packet-utils", ] +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-generic" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7eb8ad331c84c6b8cb7f685b448133e5ad82e1ffd5acafac374af4a5a308b" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-core 0.7.0", + "netlink-packet-utils", +] + [[package]] name = "netlink-packet-route" version = "0.12.0" @@ -5751,7 +5648,21 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "libc", - "netlink-packet-core", + "netlink-packet-core 0.4.2", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core 0.7.0", "netlink-packet-utils", ] @@ -5767,6 +5678,20 @@ dependencies = [ "thiserror", ] +[[package]] +name = "netlink-packet-wireguard" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b25b050ff1f6a1e23c6777b72db22790fe5b6b5ccfd3858672587a79876c8f" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "log", + "netlink-packet-generic", + "netlink-packet-utils", +] + [[package]] name = "netlink-proto" version = "0.10.0" @@ -5776,7 +5701,7 @@ dependencies = [ "bytes", "futures", "log", - "netlink-packet-core", + "netlink-packet-core 0.4.2", "netlink-sys", "thiserror", "tokio", @@ -5807,18 +5732,6 @@ dependencies = [ "memoffset 0.6.5", ] -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg 1.1.0", - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.27.1" @@ -5828,6 +5741,7 @@ dependencies = [ "bitflags 2.4.1", "cfg-if", "libc", + "memoffset 0.9.0", ] [[package]] @@ -5952,7 +5866,7 @@ dependencies = [ [[package]] name = "nym-api" -version = "1.1.32" +version = "1.1.34" dependencies = [ "actix-web", "anyhow", @@ -5975,6 +5889,7 @@ dependencies = [ "futures-util", "getset", "humantime-serde", + "itertools 0.12.0", "lazy_static", "log", "nym-api-requests", @@ -6039,10 +5954,12 @@ dependencies = [ "cosmwasm-std", "getset", "nym-coconut-interface", + "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", "schemars", "serde", + "tendermint", "ts-rs", ] @@ -6060,6 +5977,7 @@ dependencies = [ "rand 0.7.3", "thiserror", "url", + "zeroize", ] [[package]] @@ -6102,7 +6020,7 @@ dependencies = [ [[package]] name = "nym-cli" -version = "1.1.31" +version = "1.1.33" dependencies = [ "anyhow", "base64 0.13.1", @@ -6175,7 +6093,7 @@ dependencies = [ [[package]] name = "nym-client" -version = "1.1.31" +version = "1.1.32" dependencies = [ "clap 4.4.7", "dirs 4.0.0", @@ -6261,7 +6179,7 @@ dependencies = [ [[package]] name = "nym-client-wasm" -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" dependencies = [ "anyhow", "futures", @@ -6295,14 +6213,14 @@ dependencies = [ name = "nym-coconut" version = "0.5.0" dependencies = [ - "bls12_381 0.6.0", + "bls12_381", "bs58 0.4.0", "criterion", "digest 0.9.0", "doc-comment", - "ff 0.11.1", + "ff 0.13.0", "getrandom 0.2.10", - "group 0.11.0", + "group 0.13.0", "itertools 0.10.5", "nym-dkg", "nym-pemstore", @@ -6312,6 +6230,7 @@ dependencies = [ "serde_derive", "sha2 0.9.9", "thiserror", + "zeroize", ] [[package]] @@ -6402,7 +6321,7 @@ dependencies = [ name = "nym-credentials" version = "0.1.0" dependencies = [ - "bls12_381 0.5.0", + "bls12_381", "cosmrs", "log", "nym-api-requests", @@ -6411,6 +6330,7 @@ dependencies = [ "nym-validator-client", "rand 0.7.3", "thiserror", + "zeroize", ] [[package]] @@ -6444,11 +6364,11 @@ name = "nym-dkg" version = "0.1.0" dependencies = [ "bitvec", - "bls12_381 0.6.0", + "bls12_381", "bs58 0.4.0", "criterion", - "ff 0.11.1", - "group 0.11.0", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "nym-contracts-common", "nym-pemstore", @@ -6519,7 +6439,7 @@ dependencies = [ [[package]] name = "nym-gateway" -version = "1.1.31" +version = "1.1.32" dependencies = [ "anyhow", "async-trait", @@ -6529,11 +6449,13 @@ dependencies = [ "clap 4.4.7", "colored", "dashmap", + "defguard_wireguard_rs", "dirs 4.0.0", "dotenvy", "futures", "humantime-serde", "hyper", + "ipnetwork 0.16.0", "lazy_static", "log", "nym-api-requests", @@ -6647,27 +6569,49 @@ dependencies = [ "thiserror", ] +[[package]] +name = "nym-ip-packet-requests" +version = "0.1.0" +dependencies = [ + "bincode", + "bytes", + "nym-sphinx", + "rand 0.8.5", + "serde", + "thiserror", +] + [[package]] name = "nym-ip-packet-router" version = "0.1.0" dependencies = [ + "bincode", + "bytes", "etherparse", "futures", "log", "nym-bin-common", "nym-client-core", "nym-config", + "nym-exit-policy", + "nym-ip-packet-requests", + "nym-network-requester", "nym-sdk", "nym-service-providers-common", "nym-sphinx", "nym-task", + "nym-tun", "nym-wireguard", "nym-wireguard-types", + "rand 0.8.5", + "reqwest", "serde", "serde_json", "tap", "thiserror", "tokio", + "tokio-tun", + "url", ] [[package]] @@ -6705,7 +6649,7 @@ dependencies = [ [[package]] name = "nym-mixnode" -version = "1.1.33" +version = "1.1.34" dependencies = [ "anyhow", "axum", @@ -6825,7 +6769,7 @@ dependencies = [ [[package]] name = "nym-network-requester" -version = "1.1.31" +version = "1.1.32" dependencies = [ "anyhow", "async-file-watcher", @@ -6873,7 +6817,7 @@ dependencies = [ [[package]] name = "nym-network-statistics" -version = "1.1.31" +version = "1.1.32" dependencies = [ "dirs 4.0.0", "log", @@ -6900,11 +6844,13 @@ dependencies = [ "fastrand 2.0.1", "hmac 0.12.1", "hyper", + "ipnetwork 0.16.0", "mime", "nym-config", "nym-crypto", "nym-node-requests", "nym-task", + "nym-wireguard", "nym-wireguard-types", "rand 0.7.3", "serde", @@ -6960,7 +6906,7 @@ dependencies = [ [[package]] name = "nym-node-tester-wasm" -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" dependencies = [ "futures", "js-sys", @@ -7116,7 +7062,7 @@ dependencies = [ [[package]] name = "nym-socks5-client" -version = "1.1.31" +version = "1.1.32" dependencies = [ "clap 4.4.7", "lazy_static", @@ -7447,6 +7393,18 @@ dependencies = [ "wasm-utils", ] +[[package]] +name = "nym-tun" +version = "0.1.0" +dependencies = [ + "etherparse", + "log", + "nym-wireguard-types", + "thiserror", + "tokio", + "tokio-tun", +] + [[package]] name = "nym-types" version = "1.0.0" @@ -7483,6 +7441,7 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.13.1", + "bip32", "bip39", "colored", "cosmrs", @@ -7513,7 +7472,7 @@ dependencies = [ "nym-service-provider-directory-common", "nym-vesting-contract-common", "openssl", - "prost", + "prost 0.12.1", "reqwest", "serde", "serde_json", @@ -7564,24 +7523,15 @@ dependencies = [ name = "nym-wireguard" version = "0.1.0" dependencies = [ - "async-recursion", "base64 0.21.4", - "boringtun", - "bytes", - "dashmap", - "etherparse", - "futures", + "defguard_wireguard_rs", "ip_network", - "ip_network_table", "log", + "nym-network-defaults", "nym-task", "nym-wireguard-types", - "rand 0.8.5", - "serde", - "tap", - "thiserror", "tokio", - "tokio-tun", + "x25519-dalek 2.0.0", ] [[package]] @@ -7589,9 +7539,9 @@ name = "nym-wireguard-types" version = "0.1.0" dependencies = [ "base64 0.21.4", - "boringtun", "dashmap", "hmac 0.12.1", + "log", "nym-crypto", "rand 0.7.3", "serde", @@ -7602,6 +7552,36 @@ dependencies = [ "x25519-dalek 2.0.0", ] +[[package]] +name = "nymvisor" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-file-watcher", + "bytes", + "clap 4.4.7", + "dotenvy", + "flate2", + "futures", + "hex", + "humantime 2.1.0", + "humantime-serde", + "nix 0.27.1", + "nym-bin-common", + "nym-config", + "nym-task", + "reqwest", + "serde", + "serde_json", + "sha2 0.10.8", + "tar", + "thiserror", + "time", + "tokio", + "tracing", + "url", +] + [[package]] name = "object" version = "0.32.1" @@ -7860,20 +7840,11 @@ dependencies = [ [[package]] name = "pairing" -version = "0.20.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" dependencies = [ - "group 0.10.0", -] - -[[package]] -name = "pairing" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" -dependencies = [ - "group 0.11.0", + "group 0.13.0", ] [[package]] @@ -8381,7 +8352,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" +dependencies = [ + "bytes", + "prost-derive 0.12.1", ] [[package]] @@ -8398,8 +8379,8 @@ dependencies = [ "multimap", "petgraph", "prettyplease", - "prost", - "prost-types", + "prost 0.11.9", + "prost-types 0.11.9", "regex", "syn 1.0.109", "tempfile", @@ -8413,7 +8394,7 @@ source = "git+https://github.com/ChainSafe/rust-libp2p.git?rev=e3440d25681df380c dependencies = [ "asynchronous-codec", "bytes", - "prost", + "prost 0.11.9", "thiserror", "unsigned-varint", ] @@ -8431,13 +8412,35 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "prost-types" version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "prost", + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" +dependencies = [ + "prost 0.12.1", ] [[package]] @@ -8949,6 +8952,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -8958,17 +8962,23 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite 0.2.13", + "rustls 0.21.7", + "rustls-native-certs", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tokio-socks", + "tokio-util", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "winreg", ] @@ -9187,7 +9197,7 @@ checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ "futures", "log", - "netlink-packet-route", + "netlink-packet-route 0.12.0", "netlink-proto", "nix 0.24.3", "thiserror", @@ -9363,12 +9373,12 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.5.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls 0.19.1", + "rustls-pemfile", "schannel", "security-framework", ] @@ -9427,9 +9437,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "safer-ffi" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c1d19b288ca9898cd421c7b105fb7269918a7f8e9253a991e228981ca421ad" +checksum = "395ace5aff9629c7268ca8255aceb945525b2cb644015f3caec5131a6a537c11" dependencies = [ "inventory", "libc", @@ -9444,9 +9454,9 @@ dependencies = [ [[package]] name = "safer_ffi-proc_macros" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d7a04caa3ca2224f5ea4ddd850e2629c3b36b2b83621f87a8303bf41020110" +checksum = "9255504d5467bae9e07d58b8de446ba6739b29bf72e1fa35b2387e30d29dcbfe" dependencies = [ "macro_rules_attribute", "prettyplease", @@ -9729,9 +9739,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -10554,6 +10564,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.8.0" @@ -10569,9 +10590,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" dependencies = [ "bytes", "digest 0.10.7", @@ -10582,8 +10603,8 @@ dependencies = [ "k256", "num-traits", "once_cell", - "prost", - "prost-types", + "prost 0.12.1", + "prost-types 0.12.1", "ripemd", "serde", "serde_bytes", @@ -10600,9 +10621,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" +checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" dependencies = [ "flex-error", "serde", @@ -10614,16 +10635,16 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" dependencies = [ "bytes", "flex-error", "num-derive", "num-traits", - "prost", - "prost-types", + "prost 0.12.1", + "prost-types 0.12.1", "serde", "serde_bytes", "subtle-encoding", @@ -10632,21 +10653,18 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" +checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" dependencies = [ "async-trait", "bytes", "flex-error", "futures", "getrandom 0.2.10", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", "peg", "pin-project", + "reqwest", "semver 1.0.20", "serde", "serde_bytes", @@ -10870,6 +10888,16 @@ dependencies = [ "webpki 0.22.4", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.1" @@ -10909,9 +10937,9 @@ dependencies = [ [[package]] name = "tokio-tun" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a67d1405a577ba1f4cd61f46608f1db2cadbb6a9549c3fc2eed7f1195393c9" +checksum = "bf2efaf33e86779a3a68b1f1d6e9e13a346c1c75ee3cab7a4293235c463b2668" dependencies = [ "libc", "nix 0.27.1", @@ -10935,16 +10963,16 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "futures-util", - "hashbrown 0.12.3", + "hashbrown 0.14.1", "pin-project-lite 0.2.13", "slab", "tokio", @@ -11061,7 +11089,7 @@ dependencies = [ "hyper-timeout", "percent-encoding", "pin-project", - "prost", + "prost 0.11.9", "tokio", "tokio-stream", "tower", @@ -11911,6 +11939,19 @@ dependencies = [ "wasm-utils", ] +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasm-timer" version = "0.2.5" @@ -12535,6 +12576,15 @@ dependencies = [ "time", ] +[[package]] +name = "xattr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +dependencies = [ + "libc", +] + [[package]] name = "yamux" version = "0.10.2" diff --git a/Cargo.toml b/Cargo.toml index 01195764d5..bbd1f04c64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ members = [ "common/exit-policy", "common/http-api-client", "common/inclusion-probability", + "common/ip-packet-requests", "common/ledger", "common/mixnode-common", "common/network-defaults", @@ -66,6 +67,7 @@ members = [ "common/nymsphinx/params", "common/nymsphinx/routing", "common/nymsphinx/types", + "common/nyxd-scraper", "common/pemstore", "common/socks5-client-core", "common/socks5/proxy-helpers", @@ -74,6 +76,7 @@ members = [ "common/store-cipher", "common/task", "common/topology", + "common/tun", "common/types", "common/wasm/client-core", "common/wasm/storage", @@ -103,6 +106,7 @@ members = [ "tools/internal/sdk-version-bump", "tools/nym-cli", "tools/nym-nr-query", + "tools/nymvisor", "tools/ts-rs-cli", "wasm/client", # "wasm/full-nym-wasm", @@ -118,6 +122,7 @@ default-members = [ "service-providers/network-statistics", "mixnode", "nym-api", + "tools/nymvisor", "explorer-api", ] @@ -137,24 +142,8 @@ async-trait = "0.1.68" axum = "0.6.20" base64 = "0.21.4" bip39 = { version = "2.0.0", features = ["zeroize"] } -boringtun = { git = "https://github.com/cloudflare/boringtun", rev = "e1d6360d6ab4529fc942a078e4c54df107abe2ba" } clap = "4.4.7" cfg-if = "1.0.0" -cosmwasm-derive = "=1.3.0" -cosmwasm-schema = "=1.3.0" -cosmwasm-std = "=1.3.0" -# use 0.5.0 as that's the version used by cosmwasm-std 1.3.0 -# (and ideally we don't want to pull the same dependency twice) -serde-json-wasm = "=0.5.0" -cosmwasm-storage = "=1.3.0" -cosmrs = "=0.14.0" -# same version as used by cosmrs -cw-utils = "=1.0.1" -cw-storage-plus = "=1.1.0" -cw2 = { version = "=1.1.0" } -cw3 = { version = "=1.1.0" } -cw4 = { version = "=1.1.0" } -cw-controllers = { version = "=1.1.0" } dashmap = "5.5.3" dotenvy = "0.15.6" futures = "0.3.28" @@ -171,10 +160,12 @@ reqwest = "0.11.22" schemars = "0.8.1" serde = "1.0.152" serde_json = "1.0.91" +sqlx = "0.6.3" tap = "1.0.1" -tendermint-rpc = "0.32" # same version as used by cosmrs +time = "0.3.30" thiserror = "1.0.48" -tokio = "1.24.1" +tokio = "1.33.0" +tokio-util = "0.7.10" tokio-tungstenite = "0.20.1" tracing = "0.1.37" tungstenite = { version = "0.20.1", default-features = false } @@ -184,6 +175,37 @@ utoipa-swagger-ui = "3.1.5" url = "2.4" zeroize = "1.6.0" +# coconut/DKG related +# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork +# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm +bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="feature/gt-serialization-0.8.0" } +group = "0.13.0" +ff = "0.13.0" + + +# cosmwasm-related +cosmwasm-derive = "=1.3.0" +cosmwasm-schema = "=1.3.0" +cosmwasm-std = "=1.3.0" +# use 0.5.0 as that's the version used by cosmwasm-std 1.3.0 +# (and ideally we don't want to pull the same dependency twice) +serde-json-wasm = "=0.5.0" +cosmwasm-storage = "=1.3.0" +# same version as used by cosmwasm +cw-utils = "=1.0.1" +cw-storage-plus = "=1.1.0" +cw2 = { version = "=1.1.0" } +cw3 = { version = "=1.1.0" } +cw4 = { version = "=1.1.0" } +cw-controllers = { version = "=1.1.0" } + +# cosmrs-related +bip32 = "0.5.1" +cosmrs = "=0.15.0" +tendermint = "0.34" # same version as used by cosmrs +tendermint-rpc = "0.34" # same version as used by cosmrs +prost = "0.12" + # wasm-related dependencies gloo-utils = "0.1.7" js-sys = "0.3.63" diff --git a/LICENSES/CC-BY-NC-SA-4.0.txt b/LICENSES/CC-BY-NC-SA-4.0.txt new file mode 100644 index 0000000000..8e418ce385 --- /dev/null +++ b/LICENSES/CC-BY-NC-SA-4.0.txt @@ -0,0 +1,439 @@ +Attribution-NonCommercial-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International +Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial-ShareAlike 4.0 International Public License +("Public License"). To the extent this Public License may be +interpreted as a contract, You are granted the Licensed Rights in +consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the +Licensor receives from making the Licensed Material available under +these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-NC-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution, NonCommercial, and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + l. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + m. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + n. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-NC-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + + diff --git a/LICENSES/gpl-3.0 b/LICENSES/gpl-3.0 new file mode 100644 index 0000000000..d4f30f00ab --- /dev/null +++ b/LICENSES/gpl-3.0 @@ -0,0 +1,675 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means tocopy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + diff --git a/Makefile b/Makefile index 499a0d7d71..d50cdbdb9f 100644 --- a/Makefile +++ b/Makefile @@ -168,3 +168,7 @@ generate-typescript: run-api-tests: cd nym-api/tests/functional_test && yarn test:qa +# Build debian package, and update PPA +# Requires base64 encode GPG key to be set up in environment PPA_SIGNING_KEY +deb: + scripts/ppa.sh diff --git a/README.md b/README.md index e46c02478d..c75bcc7c3e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,6 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr * nym-explorer - a (projected) block explorer and (existing) mixnet viewer. * nym-wallet - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework. -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge)](https://opensource.org/licenses/Apache-2.0) [![Build Status](https://img.shields.io/github/actions/workflow/status/nymtech/nym/build.yml?branch=develop&style=for-the-badge&logo=github-actions)](https://github.com/nymtech/nym/actions?query=branch%3Adevelop) @@ -83,4 +82,11 @@ where `s'` is stake `s` scaled over total token circulating supply. ### Licensing and copyright information -This program is available as open source under the terms of the Apache 2.0 license. However, some elements are being licensed under CC0-1.0 and MIT. For accurate information, please check individual files. +This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files. + +As a general approach, licensing is as follows this pattern: +- applications and binaries are GPLv3 +- libraries and components are Apache 2.0 or MIT +- documentation is Apache 2.0 or CC0-1.0 + +Again, for accurate information, please check individual files. diff --git a/about.hbs b/about.hbs new file mode 100644 index 0000000000..699b3b04ed --- /dev/null +++ b/about.hbs @@ -0,0 +1,70 @@ + + + + + + + +
+
+

Third Party Licenses

+

This page lists the licenses of the projects used in cargo-about.

+
+ +

Overview of licenses:

+
    + {{#each overview}} +
  • {{name}} ({{count}})
  • + {{/each}} +
+ +

All license text:

+ +
+ + + diff --git a/about.toml b/about.toml new file mode 100644 index 0000000000..e138da6efe --- /dev/null +++ b/about.toml @@ -0,0 +1,19 @@ +private = { ignore = true } + +accepted = [ + "0BSD", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "CC0-1.0", + "ISC", + "MIT", + "MPL-2.0", + "Unicode-DFS-2016", + "OpenSSL", +] + +workarounds = [ + "ring", + "rustls", +] diff --git a/clients/native/Cargo.toml b/clients/native/Cargo.toml index eaec06aa21..0c5d4b5fd1 100644 --- a/clients/native/Cargo.toml +++ b/clients/native/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "nym-client" -version = "1.1.31" +version = "1.1.32" authors = ["Dave Hrycyszyn ", "Jędrzej Stuczyński "] description = "Implementation of the Nym Client" edition = "2021" rust-version = "1.65" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clients/native/src/main.rs b/clients/native/src/main.rs index cf3203b1e3..abeb07a647 100644 --- a/clients/native/src/main.rs +++ b/clients/native/src/main.rs @@ -22,5 +22,10 @@ async fn main() -> Result<(), Box> { } setup_logging(); - commands::execute(args).await + if let Err(err) = commands::execute(args).await { + log::error!("{err}"); + println!("An error occurred: {err}"); + std::process::exit(1); + } + Ok(()) } diff --git a/clients/native/websocket-requests/Cargo.toml b/clients/native/websocket-requests/Cargo.toml index 99ec632fef..a3ef6a1f28 100644 --- a/clients/native/websocket-requests/Cargo.toml +++ b/clients/native/websocket-requests/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-client-websocket-requests" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/clients/socks5/Cargo.toml b/clients/socks5/Cargo.toml index 3dc2b411ce..72a94c4c2d 100644 --- a/clients/socks5/Cargo.toml +++ b/clients/socks5/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "nym-socks5-client" -version = "1.1.31" +version = "1.1.32" authors = ["Dave Hrycyszyn "] description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address" edition = "2021" rust-version = "1.56" +license.workspace = true [dependencies] clap = { workspace = true, features = ["cargo", "derive"] } diff --git a/clients/socks5/src/main.rs b/clients/socks5/src/main.rs index 8e6d899652..8ad4ddf954 100644 --- a/clients/socks5/src/main.rs +++ b/clients/socks5/src/main.rs @@ -21,5 +21,10 @@ async fn main() -> Result<(), Box> { } setup_logging(); - commands::execute(args).await + if let Err(err) = commands::execute(args).await { + log::error!("{err}"); + println!("An error occurred: {err}"); + std::process::exit(1); + } + Ok(()) } diff --git a/common/async-file-watcher/Cargo.toml b/common/async-file-watcher/Cargo.toml index 0a44f239cf..eb7ddd6ff3 100644 --- a/common/async-file-watcher/Cargo.toml +++ b/common/async-file-watcher/Cargo.toml @@ -2,6 +2,7 @@ name = "async-file-watcher" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/async-file-watcher/src/lib.rs b/common/async-file-watcher/src/lib.rs index e2ff23e830..62bb895bcd 100644 --- a/common/async-file-watcher/src/lib.rs +++ b/common/async-file-watcher/src/lib.rs @@ -10,6 +10,8 @@ use std::path::{Path, PathBuf}; use std::time::Duration; use tokio::time::Instant; +pub use notify::{Error as NotifyError, Result as NotifyResult}; + pub type FileWatcherEventSender = mpsc::UnboundedSender; pub type FileWatcherEventReceiver = mpsc::UnboundedReceiver; @@ -22,7 +24,7 @@ pub struct AsyncFileWatcher { last_received: HashMap, tick_duration: Duration, - inner_rx: mpsc::UnboundedReceiver>, + inner_rx: mpsc::UnboundedReceiver>, event_sender: FileWatcherEventSender, } @@ -30,7 +32,7 @@ impl AsyncFileWatcher { pub fn new_file_changes_watcher>( path: P, event_sender: FileWatcherEventSender, - ) -> notify::Result { + ) -> NotifyResult { Self::new( path, event_sender, @@ -48,7 +50,7 @@ impl AsyncFileWatcher { event_sender: FileWatcherEventSender, filters: Option>, tick_duration: Option, - ) -> notify::Result { + ) -> NotifyResult { let watcher_config = Config::default(); let (inner_tx, inner_rx) = mpsc::unbounded(); let watcher = RecommendedWatcher::new( @@ -112,17 +114,17 @@ impl AsyncFileWatcher { false } - fn start_watching(&mut self) -> notify::Result<()> { + fn start_watching(&mut self) -> NotifyResult<()> { self.is_watching = true; self.watcher.watch(&self.path, RecursiveMode::NonRecursive) } - fn stop_watching(&mut self) -> notify::Result<()> { + fn stop_watching(&mut self) -> NotifyResult<()> { self.is_watching = false; self.watcher.unwatch(&self.path) } - pub async fn watch(&mut self) -> notify::Result<()> { + pub async fn watch(&mut self) -> NotifyResult<()> { self.start_watching()?; while let Some(event) = self.inner_rx.next().await { diff --git a/common/bandwidth-controller/Cargo.toml b/common/bandwidth-controller/Cargo.toml index 7b8e4a51cb..75050e6e75 100644 --- a/common/bandwidth-controller/Cargo.toml +++ b/common/bandwidth-controller/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-bandwidth-controller" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,6 +11,7 @@ bip39 = { workspace = true } rand = "0.7.3" thiserror = { workspace = true } url = { workspace = true } +zeroize = { workspace = true } nym-coconut-interface = { path = "../coconut-interface" } nym-credential-storage = { path = "../credential-storage" } diff --git a/common/bandwidth-controller/src/acquire/mod.rs b/common/bandwidth-controller/src/acquire/mod.rs index c03ae8e90a..116634cf41 100644 --- a/common/bandwidth-controller/src/acquire/mod.rs +++ b/common/bandwidth-controller/src/acquire/mod.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::BandwidthControllerError; -use nym_coconut_interface::{Base58, Parameters}; +use nym_coconut_interface::Base58; use nym_credential_storage::storage::Storage; -use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; use nym_credentials::coconut::utils::obtain_aggregate_signature; use nym_crypto::asymmetric::{encryption, identity}; use nym_network_defaults::VOUCHER_INFO; @@ -12,10 +12,8 @@ use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nyxd::contract_traits::CoconutBandwidthSigningClient; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use nym_validator_client::nyxd::Coin; -use nym_validator_client::nyxd::Hash; use rand::rngs::OsRng; -use state::{KeyPair, State}; -use std::str::FromStr; +use state::State; pub mod state; @@ -24,30 +22,29 @@ where C: CoconutBandwidthSigningClient + Sync, { let mut rng = OsRng; - let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng)); - let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng)); - let params = Parameters::new(TOTAL_ATTRIBUTES).unwrap(); + let signing_key = identity::PrivateKey::new(&mut rng); + let encryption_key = encryption::PrivateKey::new(&mut rng); + let params = BandwidthVoucher::default_parameters(); let voucher_value = amount.amount.to_string(); let tx_hash = client .deposit( amount, String::from(VOUCHER_INFO), - signing_keypair.public_key.clone(), - encryption_keypair.public_key.clone(), + signing_key.public_key().to_base58_string(), + encryption_key.public_key().to_base58_string(), None, ) .await? - .transaction_hash - .to_string(); + .transaction_hash; let voucher = BandwidthVoucher::new( ¶ms, voucher_value, VOUCHER_INFO.to_string(), - Hash::from_str(&tx_hash).map_err(|_| BandwidthControllerError::InvalidTxHash)?, - identity::PrivateKey::from_base58_string(&signing_keypair.private_key)?, - encryption::PrivateKey::from_base58_string(&encryption_keypair.private_key)?, + tx_hash, + signing_key, + encryption_key, ); let state = State { voucher, params }; diff --git a/common/bandwidth-controller/src/acquire/state.rs b/common/bandwidth-controller/src/acquire/state.rs index de77992962..7c6cc31c05 100644 --- a/common/bandwidth-controller/src/acquire/state.rs +++ b/common/bandwidth-controller/src/acquire/state.rs @@ -2,32 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use nym_coconut_interface::Parameters; -use nym_credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES}; - -use nym_crypto::asymmetric::{encryption, identity}; - -pub(crate) struct KeyPair { - pub public_key: String, - pub private_key: String, -} - -impl From for KeyPair { - fn from(kp: identity::KeyPair) -> Self { - Self { - public_key: kp.public_key().to_base58_string(), - private_key: kp.private_key().to_base58_string(), - } - } -} - -impl From for KeyPair { - fn from(kp: encryption::KeyPair) -> Self { - Self { - public_key: kp.public_key().to_base58_string(), - private_key: kp.private_key().to_base58_string(), - } - } -} +use nym_credentials::coconut::bandwidth::BandwidthVoucher; pub struct State { pub voucher: BandwidthVoucher, @@ -38,7 +13,7 @@ impl State { pub fn new(voucher: BandwidthVoucher) -> Self { State { voucher, - params: Parameters::new(TOTAL_ATTRIBUTES).unwrap(), + params: BandwidthVoucher::default_parameters(), } } } diff --git a/common/bandwidth-controller/src/lib.rs b/common/bandwidth-controller/src/lib.rs index d9a714b263..b7d61e2935 100644 --- a/common/bandwidth-controller/src/lib.rs +++ b/common/bandwidth-controller/src/lib.rs @@ -7,6 +7,7 @@ use nym_credential_storage::storage::Storage; use nym_validator_client::coconut::all_coconut_api_clients; use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use std::str::FromStr; +use zeroize::Zeroizing; use { nym_coconut_interface::Base58, nym_credentials::coconut::{ @@ -46,10 +47,12 @@ impl BandwidthController { let voucher_value = u64::from_str(&bandwidth_credential.voucher_value) .map_err(|_| StorageError::InconsistentData)?; let voucher_info = bandwidth_credential.voucher_info.clone(); - let serial_number = - nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?; - let binding_number = - nym_coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?; + let serial_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( + bandwidth_credential.serial_number, + )?); + let binding_number = Zeroizing::new(nym_coconut_interface::Attribute::try_from_bs58( + bandwidth_credential.binding_number, + )?); let signature = nym_coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?; let epoch_id = u64::from_str(&bandwidth_credential.epoch_id) @@ -64,8 +67,8 @@ impl BandwidthController { prepare_for_spending( voucher_value, voucher_info, - serial_number, - binding_number, + &serial_number, + &binding_number, epoch_id, &signature, &verification_key, diff --git a/common/bin-common/Cargo.toml b/common/bin-common/Cargo.toml index 9ed9958a07..44f12bc19d 100644 --- a/common/bin-common/Cargo.toml +++ b/common/bin-common/Cargo.toml @@ -47,8 +47,9 @@ default = [] openapi = ["utoipa"] output_format = ["serde_json"] bin_info_schema = ["schemars"] +basic_tracing = ["tracing-subscriber"] tracing = [ - "tracing-subscriber", + "basic_tracing", "tracing-tree", "opentelemetry-jaeger", "tracing-opentelemetry", diff --git a/common/bin-common/src/build_information/mod.rs b/common/bin-common/src/build_information/mod.rs index 06dc1da51c..900b04507d 100644 --- a/common/bin-common/src/build_information/mod.rs +++ b/common/bin-common/src/build_information/mod.rs @@ -80,7 +80,7 @@ impl BinaryBuildInformation { } } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] #[cfg_attr(feature = "bin_info_schema", derive(schemars::JsonSchema))] pub struct BinaryBuildInformationOwned { diff --git a/common/bin-common/src/logging/mod.rs b/common/bin-common/src/logging/mod.rs index 135736ac2b..a2f6f36d9e 100644 --- a/common/bin-common/src/logging/mod.rs +++ b/common/bin-common/src/logging/mod.rs @@ -43,6 +43,30 @@ pub fn setup_logging() { .init(); } +#[cfg(feature = "basic_tracing")] +pub fn setup_tracing_logger() { + let log_builder = tracing_subscriber::fmt() + // Use a more compact, abbreviated log format + .compact() + // Display source code file paths + .with_file(true) + // Display source code line numbers + .with_line_number(true) + // Don't display the event's target (module path) + .with_target(false); + + if ::std::env::var("RUST_LOG").is_ok() { + log_builder + .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env()) + .init() + } else { + // default to 'Info + log_builder + .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .init() + } +} + // TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason #[cfg(feature = "tracing")] #[macro_export] diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index ccd2113a28..556978f1f8 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -4,6 +4,7 @@ version = "1.1.15" authors = ["Dave Hrycyszyn "] edition = "2021" rust-version = "1.66" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -58,7 +59,7 @@ features = ["time"] version = "0.20.1" [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] -version = "0.6.2" +workspace = true features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] optional = true @@ -89,7 +90,7 @@ tempfile = "3.1.0" [build-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } [features] default = [] diff --git a/common/client-core/src/client/base_client/storage/gateway_details.rs b/common/client-core/src/client/base_client/storage/gateway_details.rs index fe04aee5c3..d9eaddfa87 100644 --- a/common/client-core/src/client/base_client/storage/gateway_details.rs +++ b/common/client-core/src/client/base_client/storage/gateway_details.rs @@ -77,7 +77,7 @@ pub struct PersistedGatewayConfig { key_hash: Vec, /// Actual gateway details being persisted. - pub(crate) details: GatewayEndpointConfig, + pub details: GatewayEndpointConfig, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/common/client-core/src/client/helpers/wasm.rs b/common/client-core/src/client/helpers/wasm.rs index 5046463ee5..502ae53479 100644 --- a/common/client-core/src/client/helpers/wasm.rs +++ b/common/client-core/src/client/helpers/wasm.rs @@ -1,6 +1,8 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(unused_imports)] + use std::time::Duration; pub use wasmtimer::{std::Instant, tokio::*}; diff --git a/common/client-core/src/client/inbound_messages.rs b/common/client-core/src/client/inbound_messages.rs index 1530073d3f..baf163913f 100644 --- a/common/client-core/src/client/inbound_messages.rs +++ b/common/client-core/src/client/inbound_messages.rs @@ -28,6 +28,7 @@ pub enum InputMessage { recipient: Recipient, data: Vec, lane: TransmissionLane, + mix_hops: Option, }, /// Creates a message used for a duplex anonymous communication where the recipient @@ -43,6 +44,7 @@ pub enum InputMessage { data: Vec, reply_surbs: u32, lane: TransmissionLane, + mix_hops: Option, }, /// Attempt to use our internally received and stored `ReplySurb` to send the message back @@ -92,6 +94,29 @@ impl InputMessage { recipient, data, lane, + mix_hops: None, + }; + if let Some(packet_type) = packet_type { + InputMessage::new_wrapper(message, packet_type) + } else { + message + } + } + + // IMHO `new_regular` should take `mix_hops: Option` as an argument instead of creating + // this function, but that would potentially break backwards compatibility with the current API + pub fn new_regular_with_custom_hops( + recipient: Recipient, + data: Vec, + lane: TransmissionLane, + packet_type: Option, + mix_hops: Option, + ) -> Self { + let message = InputMessage::Regular { + recipient, + data, + lane, + mix_hops, }; if let Some(packet_type) = packet_type { InputMessage::new_wrapper(message, packet_type) @@ -112,6 +137,31 @@ impl InputMessage { data, reply_surbs, lane, + mix_hops: None, + }; + if let Some(packet_type) = packet_type { + InputMessage::new_wrapper(message, packet_type) + } else { + message + } + } + + // IMHO `new_anonymous` should take `mix_hops: Option` as an argument instead of creating + // this function, but that would potentially break backwards compatibility with the current API + pub fn new_anonymous_with_custom_hops( + recipient: Recipient, + data: Vec, + reply_surbs: u32, + lane: TransmissionLane, + packet_type: Option, + mix_hops: Option, + ) -> Self { + let message = InputMessage::Anonymous { + recipient, + data, + reply_surbs, + lane, + mix_hops, }; if let Some(packet_type) = packet_type { InputMessage::new_wrapper(message, packet_type) diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs index 95fd31ec01..cb50d4ba09 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/action_controller.rs @@ -127,7 +127,9 @@ impl ActionController { .insert(frag_id, (Arc::new(pending_ack), None)) .is_some() { - panic!("Tried to insert duplicate pending ack") + // This used to be a panic, however since we've seen this actually happen in the + // wild, let's not take the whole client (and possibly gateway) down because of it. + error!("Tried to insert duplicate pending ack! This should not be possible!") } } } @@ -263,7 +265,7 @@ impl ActionController { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started ActionController with graceful shutdown support"); - while !shutdown.is_shutdown() { + loop { tokio::select! { action = self.incoming_actions.next() => match action { Some(action) => self.process_action(action), @@ -283,6 +285,7 @@ impl ActionController { }, _ = shutdown.recv_with_delay() => { log::trace!("ActionController: Received shutdown"); + break; } } } diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs index 19ba2d1cae..00a7abe4e7 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/input_message_listener.rs @@ -73,10 +73,11 @@ where content: Vec, lane: TransmissionLane, packet_type: PacketType, + mix_hops: Option, ) { if let Err(err) = self .message_handler - .try_send_plain_message(recipient, content, lane, packet_type) + .try_send_plain_message(recipient, content, lane, packet_type, mix_hops) .await { warn!("failed to send a plain message - {err}") @@ -90,10 +91,18 @@ where reply_surbs: u32, lane: TransmissionLane, packet_type: PacketType, + mix_hops: Option, ) { if let Err(err) = self .message_handler - .try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type) + .try_send_message_with_reply_surbs( + recipient, + content, + reply_surbs, + lane, + packet_type, + mix_hops, + ) .await { warn!("failed to send a repliable message - {err}") @@ -106,8 +115,9 @@ where recipient, data, lane, + mix_hops, } => { - self.handle_plain_message(recipient, data, lane, PacketType::Mix) + self.handle_plain_message(recipient, data, lane, PacketType::Mix, mix_hops) .await } InputMessage::Anonymous { @@ -115,9 +125,17 @@ where data, reply_surbs, lane, + mix_hops, } => { - self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix) - .await + self.handle_repliable_message( + recipient, + data, + reply_surbs, + lane, + PacketType::Mix, + mix_hops, + ) + .await } InputMessage::Reply { recipient_tag, @@ -135,8 +153,9 @@ where recipient, data, lane, + mix_hops, } => { - self.handle_plain_message(recipient, data, lane, packet_type) + self.handle_plain_message(recipient, data, lane, packet_type, mix_hops) .await } InputMessage::Anonymous { @@ -144,9 +163,17 @@ where data, reply_surbs, lane, + mix_hops, } => { - self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type) - .await + self.handle_repliable_message( + recipient, + data, + reply_surbs, + lane, + packet_type, + mix_hops, + ) + .await } InputMessage::Reply { recipient_tag, diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs index acf0f54ca9..d7a9eac0ab 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/mod.rs @@ -69,6 +69,7 @@ pub(crate) struct PendingAcknowledgement { message_chunk: Fragment, delay: SphinxDelay, destination: PacketDestination, + mix_hops: Option, } impl PendingAcknowledgement { @@ -77,11 +78,13 @@ impl PendingAcknowledgement { message_chunk: Fragment, delay: SphinxDelay, recipient: Recipient, + mix_hops: Option, ) -> Self { PendingAcknowledgement { message_chunk, delay, destination: PacketDestination::KnownRecipient(recipient.into()), + mix_hops, } } @@ -98,6 +101,9 @@ impl PendingAcknowledgement { recipient_tag, extra_surb_request, }, + // Messages sent using SURBs are using the number of mix hops set by the recipient when + // they provided the SURBs, so it doesn't make sense to include it here. + mix_hops: None, } } diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs index c8acfca441..1d305ff5ff 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/retransmission_request_listener.rs @@ -49,12 +49,18 @@ where packet_recipient: Recipient, chunk_data: Fragment, packet_type: PacketType, + mix_hops: Option, ) -> Result { debug!("retransmitting normal packet..."); // TODO: Figure out retransmission packet type signaling self.message_handler - .try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type) + .try_prepare_single_chunk_for_sending( + packet_recipient, + chunk_data, + packet_type, + mix_hops, + ) .await } @@ -89,6 +95,7 @@ where **recipient, timed_out_ack.message_chunk.clone(), packet_type, + timed_out_ack.mix_hops, ) .await } diff --git a/common/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs b/common/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs index 4d2445f162..32e39e4f07 100644 --- a/common/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs +++ b/common/client-core/src/client/real_messages_control/acknowledgement_control/sent_notification_listener.rs @@ -40,7 +40,7 @@ impl SentNotificationListener { pub(super) async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) { debug!("Started SentNotificationListener with graceful shutdown support"); - while !shutdown.is_shutdown() { + loop { tokio::select! { frag_id = self.sent_notifier.next() => match frag_id { Some(frag_id) => { @@ -53,6 +53,7 @@ impl SentNotificationListener { }, _ = shutdown.recv_with_delay() => { log::trace!("SentNotificationListener: Received shutdown"); + break; } } } diff --git a/common/client-core/src/client/real_messages_control/message_handler.rs b/common/client-core/src/client/real_messages_control/message_handler.rs index 07482aa77f..42a1993844 100644 --- a/common/client-core/src/client/real_messages_control/message_handler.rs +++ b/common/client-core/src/client/real_messages_control/message_handler.rs @@ -418,9 +418,10 @@ where message: Vec, lane: TransmissionLane, packet_type: PacketType, + mix_hops: Option, ) -> Result<(), PreparationError> { let message = NymMessage::new_plain(message); - self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) + self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops) .await } @@ -430,6 +431,7 @@ where recipient: Recipient, lane: TransmissionLane, packet_type: PacketType, + mix_hops: Option, ) -> Result<(), PreparationError> { debug!("Sending non-reply message with packet type {packet_type}"); // TODO: I really dislike existence of this assertion, it implies code has to be re-organised @@ -461,6 +463,7 @@ where &self.config.ack_key, &recipient, packet_type, + mix_hops, )?; let real_message = RealMessage::new( @@ -468,7 +471,8 @@ where Some(fragment.fragment_identifier()), ); let delay = prepared_fragment.total_delay; - let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient); + let pending_ack = + PendingAcknowledgement::new_known(fragment, delay, recipient, mix_hops); real_messages.push(real_message); pending_acks.push(pending_ack); @@ -485,6 +489,7 @@ where recipient: Recipient, amount: u32, packet_type: PacketType, + mix_hops: Option, ) -> Result<(), PreparationError> { debug!("Sending additional reply SURBs with packet type {packet_type}"); let sender_tag = self.get_or_create_sender_tag(&recipient); @@ -501,6 +506,7 @@ where recipient, TransmissionLane::AdditionalReplySurbs, packet_type, + mix_hops, ) .await?; @@ -517,6 +523,7 @@ where num_reply_surbs: u32, lane: TransmissionLane, packet_type: PacketType, + mix_hops: Option, ) -> Result<(), SurbWrappedPreparationError> { debug!("Sending message with reply SURBs with packet type {packet_type}"); let sender_tag = self.get_or_create_sender_tag(&recipient); @@ -527,7 +534,7 @@ where let message = NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs)); - self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type) + self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops) .await?; log::trace!("storing {} reply keys", reply_keys.len()); @@ -541,6 +548,7 @@ where recipient: Recipient, chunk: Fragment, packet_type: PacketType, + mix_hops: Option, ) -> Result { debug!("Sending single chunk with packet type {packet_type}"); let topology_permit = self.topology_access.get_read_permit().await; @@ -554,6 +562,7 @@ where &self.config.ack_key, &recipient, packet_type, + mix_hops, ) .unwrap(); diff --git a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs index a160a51894..7874d758f7 100644 --- a/common/client-core/src/client/real_messages_control/real_traffic_stream.rs +++ b/common/client-core/src/client/real_messages_control/real_traffic_stream.rs @@ -500,11 +500,12 @@ where { let mut status_timer = tokio::time::interval(Duration::from_secs(5)); - while !shutdown.is_shutdown() { + loop { tokio::select! { biased; _ = shutdown.recv_with_delay() => { log::trace!("OutQueueControl: Received shutdown"); + break; } _ = status_timer.tick() => { self.log_status(&mut shutdown); diff --git a/common/client-core/src/client/replies/reply_controller/mod.rs b/common/client-core/src/client/replies/reply_controller/mod.rs index 4ba9ce3ac9..da58f377c6 100644 --- a/common/client-core/src/client/replies/reply_controller/mod.rs +++ b/common/client-core/src/client/replies/reply_controller/mod.rs @@ -516,6 +516,7 @@ where recipient, to_send, nym_sphinx::params::PacketType::Mix, + self.config.reply_surbs.surb_mix_hops, ) .await { diff --git a/common/client-core/src/client/replies/reply_storage/mod.rs b/common/client-core/src/client/replies/reply_storage/mod.rs index 5b8c7b9dee..16a0a36cd4 100644 --- a/common/client-core/src/client/replies/reply_storage/mod.rs +++ b/common/client-core/src/client/replies/reply_storage/mod.rs @@ -39,7 +39,7 @@ where mem_state: CombinedReplyStorage, mut shutdown: nym_task::TaskClient, ) { - use log::{debug, error, info, warn}; + use log::{debug, error, info}; debug!("Started PersistentReplyStorage"); if let Err(err) = self.backend.start_storage_session().await { @@ -50,7 +50,7 @@ where shutdown.recv().await; info!("PersistentReplyStorage is flushing all reply-related data to underlying storage"); - warn!("you MUST NOT forcefully shutdown now or you risk data corruption!"); + info!("you MUST NOT forcefully shutdown now or you risk data corruption!"); if let Err(err) = self.backend.flush_surb_storage(&mem_state).await { error!("failed to flush our reply-related data to the persistent storage: {err}") } else { diff --git a/common/client-core/src/config/mod.rs b/common/client-core/src/config/mod.rs index ce6d8d4f91..3f4bbb4efe 100644 --- a/common/client-core/src/config/mod.rs +++ b/common/client-core/src/config/mod.rs @@ -607,6 +607,10 @@ pub struct ReplySurbs { /// This is going to be superseded by key rotation once implemented. #[serde(with = "humantime_serde")] pub maximum_reply_key_age: Duration, + + /// Specifies the number of mixnet hops the packet should go through. If not specified, then + /// the default value is used. + pub surb_mix_hops: Option, } impl Default for ReplySurbs { @@ -622,6 +626,7 @@ impl Default for ReplySurbs { maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE, maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE, + surb_mix_hops: None, } } } diff --git a/common/client-core/src/config/old_config_v1_1_30.rs b/common/client-core/src/config/old_config_v1_1_30.rs index 20501f5880..87dae0eb3f 100644 --- a/common/client-core/src/config/old_config_v1_1_30.rs +++ b/common/client-core/src/config/old_config_v1_1_30.rs @@ -155,6 +155,7 @@ impl From for Config { .maximum_reply_surb_drop_waiting_period, maximum_reply_surb_age: value.debug.reply_surbs.maximum_reply_surb_age, maximum_reply_key_age: value.debug.reply_surbs.maximum_reply_key_age, + surb_mix_hops: None, }, }, } diff --git a/common/client-core/src/error.rs b/common/client-core/src/error.rs index 8a193d8a75..e4496e461a 100644 --- a/common/client-core/src/error.rs +++ b/common/client-core/src/error.rs @@ -15,34 +15,34 @@ pub enum ClientCoreError { #[error("I/O error: {0}")] IoError(#[from] std::io::Error), - #[error("Gateway client error ({gateway_id}): {source}")] + #[error("gateway client error ({gateway_id}): {source}")] GatewayClientError { gateway_id: String, source: GatewayClientError, }, - #[error("Custom gateway client error: {source}")] + #[error("custom gateway client error: {source}")] ErasedGatewayClientError { #[from] source: ErasedGatewayError, }, - #[error("Ed25519 error: {0}")] + #[error("ed25519 error: {0}")] Ed25519RecoveryError(#[from] Ed25519RecoveryError), - #[error("Validator client error: {0}")] + #[error("validator client error: {0}")] ValidatorClientError(#[from] ValidatorClientError), - #[error("No gateway with id: {0}")] + #[error("no gateway with id: {0}")] NoGatewayWithId(String), - #[error("No gateways on network")] + #[error("no gateways on network")] NoGatewaysOnNetwork, - #[error("List of nym apis is empty")] + #[error("list of nym apis is empty")] ListOfNymApisIsEmpty, - #[error("The current network topology seem to be insufficient to route any packets through")] + #[error("the current network topology seem to be insufficient to route any packets through")] InsufficientNetworkTopology(#[from] NymTopologyError), #[error("experienced a failure with our reply surb persistent storage: {source}")] @@ -60,7 +60,7 @@ pub enum ClientCoreError { source: Box, }, - #[error("The gateway id is invalid - {0}")] + #[error("the gateway id is invalid - {0}")] UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError), #[error("The gateway is malformed: {source}")] @@ -79,23 +79,23 @@ pub enum ClientCoreError { #[error("failed to establish gateway connection (wasm)")] GatewayJsConnectionFailure, - #[error("Gateway connection was abruptly closed")] + #[error("gateway connection was abruptly closed")] GatewayConnectionAbruptlyClosed, - #[error("Timed out while trying to establish gateway connection")] + #[error("timed out while trying to establish gateway connection")] GatewayConnectionTimeout, - #[error("No ping measurements for the gateway ({identity}) performed")] + #[error("no ping measurements for the gateway ({identity}) performed")] NoGatewayMeasurements { identity: String }, #[error("failed to register receiver for reconstructed mixnet messages")] FailedToRegisterReceiver, - #[error("Unexpected exit")] + #[error("unexpected exit")] UnexpectedExit, #[error( - "This operation would have resulted in clients keys being overwritten without permission" + "this operation would have resulted in clients keys being overwritten without permission" )] ForbiddenKeyOverwrite, diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 4b50f0864b..38a0c3e841 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -68,13 +68,23 @@ pub async fn current_gateways( log::trace!("Fetching list of gateways from: {nym_api}"); let gateways = client.get_cached_described_gateways().await?; + log::debug!("Found {} gateways", gateways.len()); + log::trace!("Gateways: {:#?}", gateways); + let valid_gateways = gateways .into_iter() .filter_map(|gateway| gateway.try_into().ok()) .collect::>(); + log::debug!("Ater checking validity: {}", valid_gateways.len()); + log::trace!("Valid gateways: {:#?}", valid_gateways); // we were always filtering by version so I'm not removing that 'feature' let filtered_gateways = valid_gateways.filter_by_version(env!("CARGO_PKG_VERSION")); + log::debug!("After filtering for version: {}", filtered_gateways.len()); + log::trace!("Filtered gateways: {:#?}", filtered_gateways); + + log::info!("nym-api reports {} valid gateways", filtered_gateways.len()); + Ok(filtered_gateways) } @@ -249,6 +259,7 @@ pub(super) fn get_specified_gateway( gateways: &[gateway::Node], must_use_tls: bool, ) -> Result { + log::debug!("Requesting specified gateway: {}", gateway_identity); let user_gateway = identity::PublicKey::from_base58_string(gateway_identity) .map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?; diff --git a/common/client-core/src/init/mod.rs b/common/client-core/src/init/mod.rs index 395fdd45c2..13129bf5f5 100644 --- a/common/client-core/src/init/mod.rs +++ b/common/client-core/src/init/mod.rs @@ -94,6 +94,8 @@ where D::StorageError: Send + Sync + 'static, T: DeserializeOwned + Serialize + Send + Sync, { + log::trace!("Setting up new gateway"); + // if we're setting up new gateway, failing to load existing information is fine. // as a matter of fact, it's only potentially a problem if we DO succeed if _load_gateway_details(details_store).await.is_ok() && !overwrite_data { @@ -210,6 +212,7 @@ where D::StorageError: Send + Sync + 'static, T: DeserializeOwned + Serialize + Send + Sync, { + log::debug!("Setting up gateway"); match setup { GatewaySetup::MustLoad => use_loaded_gateway_details(key_store, details_store).await, GatewaySetup::New { diff --git a/common/client-libs/gateway-client/Cargo.toml b/common/client-libs/gateway-client/Cargo.toml index 92b1134512..75f56fd7c0 100644 --- a/common/client-libs/gateway-client/Cargo.toml +++ b/common/client-libs/gateway-client/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-gateway-client" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index a95fee0bf7..f65e61c29f 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -792,6 +792,7 @@ pub struct InitOnly; impl GatewayClient { // for initialisation we do not need credential storage. Though it's still a bit weird we have to set the generic... pub fn new_init(config: GatewayConfig, local_identity: Arc) -> Self { + log::trace!("Initialising gateway client"); use futures::channel::mpsc; // note: this packet_router is completely invalid in normal circumstances, but "works" diff --git a/common/client-libs/mixnet-client/Cargo.toml b/common/client-libs/mixnet-client/Cargo.toml index 9eede069da..68e048d1f0 100644 --- a/common/client-libs/mixnet-client/Cargo.toml +++ b/common/client-libs/mixnet-client/Cargo.toml @@ -3,14 +3,15 @@ name = "nym-mixnet-client" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] futures = { workspace = true } log = { workspace = true } -tokio = { version = "1.24.1", features = ["time", "net", "rt"] } -tokio-util = { version = "0.7.4", features = ["codec"] } +tokio = { workspace = true, features = ["time", "net", "rt"] } +tokio-util = { workspace = true, features = ["codec"] } # internal nym-sphinx = { path = "../../nymsphinx" } diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index e16345f446..5e778b4cb2 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" rust-version = "1.56" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -45,13 +46,17 @@ cosmrs = { workspace = true, features = ["bip32", "cosmwasm"] } # import it just for the `Client` trait tendermint-rpc = { workspace = true } +# this is an extremely nasty import. we're explicitly bringing in bip32 so that via the magic (or curse, pick your poison) +# of cargo's feature unification we'd get `bip32/std` meaning we'd get `std::error::Error` for the re-exported (via cosmrs) bip32::Error type +bip32 = { workspace = true, default-features = false, features = ["std"] } + eyre = { version = "0.6" } cw-utils = { workspace = true } cw2 = { workspace = true } cw3 = { workspace = true } cw4 = { workspace = true } cw-controllers = { workspace = true } -prost = { version = "0.11", default-features = false } +prost = { workspace = true, default-features = false } flate2 = { version = "1.0.20" } sha2 = { version = "0.9.5" } itertools = { version = "0.10" } diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 21ac049840..d4902bd1a1 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -5,8 +5,12 @@ use crate::nym_api::error::NymAPIError; use crate::nym_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; use async_trait::async_trait; use http_api_client::{ApiClient, NO_PARAMS}; +use nym_api_requests::coconut::models::{ + EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse, +}; use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, + BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, VerifyCredentialBody, + VerifyCredentialResponse, }; use nym_api_requests::models::{ ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse, @@ -15,6 +19,7 @@ use nym_api_requests::models::{ MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse, }; +use nym_coconut_dkg_common::types::EpochId; use nym_mixnet_contract_common::mixnode::MixNodeDetails; use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId}; use nym_name_service_common::response::NamesListResponse; @@ -399,6 +404,60 @@ pub trait NymApiClientExt: ApiClient { .await } + async fn epoch_credentials( + &self, + dkg_epoch: EpochId, + ) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_EPOCH_CREDENTIALS, + &dkg_epoch.to_string(), + ], + NO_PARAMS, + ) + .await + } + + async fn issued_credential( + &self, + credential_id: i64, + ) -> Result { + self.get_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_ISSUED_CREDENTIAL, + &credential_id.to_string(), + ], + NO_PARAMS, + ) + .await + } + + async fn issued_credentials( + &self, + credential_ids: Vec, + ) -> Result { + self.post_json( + &[ + routes::API_VERSION, + routes::COCONUT_ROUTES, + routes::BANDWIDTH, + routes::COCONUT_ISSUED_CREDENTIALS, + ], + NO_PARAMS, + &CredentialsRequestBody { + credential_ids, + pagination: None, + }, + ) + .await + } + async fn get_service_providers(&self) -> Result { log::trace!("Getting service providers"); self.get_json(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS) diff --git a/common/client-libs/validator-client/src/nym_api/routes.rs b/common/client-libs/validator-client/src/nym_api/routes.rs index df063a114d..d670033049 100644 --- a/common/client-libs/validator-client/src/nym_api/routes.rs +++ b/common/client-libs/validator-client/src/nym_api/routes.rs @@ -17,6 +17,9 @@ pub const BANDWIDTH: &str = "bandwidth"; pub const COCONUT_BLIND_SIGN: &str = "blind-sign"; pub const COCONUT_VERIFY_BANDWIDTH_CREDENTIAL: &str = "verify-bandwidth-credential"; +pub const COCONUT_EPOCH_CREDENTIALS: &str = "epoch-credentials"; +pub const COCONUT_ISSUED_CREDENTIAL: &str = "issued-credential"; +pub const COCONUT_ISSUED_CREDENTIALS: &str = "issued-credentials"; pub const STATUS_ROUTES: &str = "status"; pub const MIXNODE: &str = "mixnode"; diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/coconut_bandwidth_signing_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/coconut_bandwidth_signing_client.rs index 030d78b29a..2402dc078f 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/coconut_bandwidth_signing_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/coconut_bandwidth_signing_client.rs @@ -32,7 +32,7 @@ pub trait CoconutBandwidthSigningClient { fee: Option, ) -> Result { let req = CoconutBandwidthExecuteMsg::DepositFunds { - data: DepositData::new(info.to_string(), verification_key, encryption_key), + data: DepositData::new(info, verification_key, encryption_key), }; self.execute_coconut_bandwidth_contract( fee, diff --git a/common/client-libs/validator-client/src/nyxd/contract_traits/multisig_query_client.rs b/common/client-libs/validator-client/src/nyxd/contract_traits/multisig_query_client.rs index 48fcadc050..57c6ce2bf7 100644 --- a/common/client-libs/validator-client/src/nyxd/contract_traits/multisig_query_client.rs +++ b/common/client-libs/validator-client/src/nyxd/contract_traits/multisig_query_client.rs @@ -6,8 +6,8 @@ use crate::nyxd::error::NyxdError; use crate::nyxd::CosmWasmClient; use async_trait::async_trait; use cw3::{ - ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse, VoterListResponse, - VoterResponse, + ProposalListResponse, ProposalResponse, VoteListResponse, VoteResponse, VoterDetail, + VoterListResponse, VoterResponse, }; use cw_utils::ThresholdResponse; use nym_multisig_contract_common::msg::QueryMsg as MultisigQueryMsg; @@ -114,6 +114,26 @@ pub trait PagedMultisigQueryClient: MultisigQueryClient { Ok(proposals) } + + async fn get_all_voters(&self) -> Result, NyxdError> { + let mut voters = Vec::new(); + let mut start_after = None; + + loop { + let mut paged_response = self.list_voters(start_after.take(), None).await?; + + let last_voter = paged_response.voters.last().map(|prop| prop.addr.clone()); + voters.append(&mut paged_response.voters); + + if let Some(start_after_res) = last_voter { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(voters) + } } #[async_trait] diff --git a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs index b639b223e3..1718ab2f02 100644 --- a/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs +++ b/common/client-libs/validator-client/src/nyxd/cosmwasm_client/helpers.rs @@ -20,12 +20,12 @@ impl CheckResponse for broadcast::tx_commit::Response { }); } - if self.deliver_tx.code.is_err() { + if self.tx_result.code.is_err() { return Err(NyxdError::BroadcastTxErrorDeliverTx { hash: self.hash, height: Some(self.height), - code: self.deliver_tx.code.value(), - raw_log: self.deliver_tx.log, + code: self.tx_result.code.value(), + raw_log: self.tx_result.log, }); } diff --git a/common/client-libs/validator-client/src/nyxd/fee/mod.rs b/common/client-libs/validator-client/src/nyxd/fee/mod.rs index ee30d1ffe5..f062dfdb49 100644 --- a/common/client-libs/validator-client/src/nyxd/fee/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/fee/mod.rs @@ -11,7 +11,7 @@ pub mod gas_price; pub type GasAdjustment = f32; -pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.3; +pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: GasAdjustment = 1.5; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AutoFeeGrant { diff --git a/common/client-libs/validator-client/src/nyxd/helpers.rs b/common/client-libs/validator-client/src/nyxd/helpers.rs new file mode 100644 index 0000000000..85999eac6f --- /dev/null +++ b/common/client-libs/validator-client/src/nyxd/helpers.rs @@ -0,0 +1,13 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::nyxd::TxResponse; + +pub fn find_tx_attribute(tx: &TxResponse, event_type: &str, attribute_key: &str) -> Option { + let event = tx.tx_result.events.iter().find(|e| e.kind == event_type)?; + let attribute = event + .attributes + .iter() + .find(|attr| attr.key == attribute_key)?; + Some(attribute.value.clone()) +} diff --git a/common/client-libs/validator-client/src/nyxd/mod.rs b/common/client-libs/validator-client/src/nyxd/mod.rs index e7d6650052..46f9b0622a 100644 --- a/common/client-libs/validator-client/src/nyxd/mod.rs +++ b/common/client-libs/validator-client/src/nyxd/mod.rs @@ -34,17 +34,24 @@ pub use crate::nyxd::fee::Fee; pub use crate::rpc::TendermintRpcClient; pub use coin::Coin; pub use cosmrs::bank::MsgSend; -pub use cosmrs::tendermint::abci::{response::DeliverTx, Event, EventAttribute}; +pub use cosmrs::tendermint::abci::{ + response::DeliverTx, types::ExecTxResult, Event, EventAttribute, +}; pub use cosmrs::tendermint::block::Height; pub use cosmrs::tendermint::hash::{self, Algorithm, Hash}; pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo; pub use cosmrs::tendermint::Time as TendermintTime; pub use cosmrs::tx::Msg; pub use cosmrs::tx::{self}; +pub use cosmrs::Any; pub use cosmrs::Coin as CosmosCoin; pub use cosmrs::Gas; pub use cosmrs::{bip32, AccountId, Denom}; pub use cosmwasm_std::Coin as CosmWasmCoin; +pub use cw2; +pub use cw3; +pub use cw4; +pub use cw_controllers; pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment}; pub use tendermint_rpc::{ endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse}, @@ -65,6 +72,7 @@ pub mod contract_traits; pub mod cosmwasm_client; pub mod error; pub mod fee; +pub mod helpers; #[derive(Debug, Clone)] pub struct Config { @@ -374,7 +382,11 @@ where }) } - pub async fn simulate(&self, messages: I) -> Result + pub async fn simulate( + &self, + messages: I, + memo: impl Into + Send + 'static, + ) -> Result where I: IntoIterator + Send, M: Msg, @@ -389,7 +401,7 @@ where .map_err(|_| { NyxdError::SerializationError("custom simulate messages".to_owned()) })?, - "simulating execution of transactions", + memo, ) .await } diff --git a/common/client-libs/validator-client/src/rpc/mod.rs b/common/client-libs/validator-client/src/rpc/mod.rs index fd3f7991ba..78f5476b48 100644 --- a/common/client-libs/validator-client/src/rpc/mod.rs +++ b/common/client-libs/validator-client/src/rpc/mod.rs @@ -28,7 +28,7 @@ where U: TryInto, { HttpRpcClient::builder(url.try_into()?) - .compat_mode(CompatMode::V0_34) + .compat_mode(CompatMode::V0_37) .build() } diff --git a/common/client-libs/validator-client/src/rpc/reqwest.rs b/common/client-libs/validator-client/src/rpc/reqwest.rs index ffad7dd815..973fb63376 100644 --- a/common/client-libs/validator-client/src/rpc/reqwest.rs +++ b/common/client-libs/validator-client/src/rpc/reqwest.rs @@ -36,7 +36,8 @@ pub struct ReqwestRpcClient { impl ReqwestRpcClient { pub fn new(url: Url) -> Self { ReqwestRpcClient { - compat: CompatMode::V0_34, + // after updating to nyxd 0.42 and thus updating to cometbft, the compat mode changed + compat: CompatMode::V0_37, inner: reqwest::Client::new(), url, } diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index e749824e51..dc81f4847f 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-coconut-interface" version = "0.1.0" edition = "2021" description = "Crutch library until there is proper SerDe support for coconut structs" +license.workspace = true [dependencies] bs58 = "0.4.0" diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 2322bc3978..65aab8fb7f 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -21,10 +21,14 @@ pub use nym_coconut::{ pub struct Credential { #[getset(get = "pub")] n_params: u32, + #[getset(get = "pub")] theta: Theta, + voucher_value: u64, + voucher_info: String, + #[getset(get = "pub")] epoch_id: u64, } @@ -64,14 +68,12 @@ impl Credential { pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - let public_attributes = [ - self.voucher_value.to_string().as_bytes(), - self.voucher_info.as_bytes(), - ] - .iter() - .map(hash_to_scalar) - .collect::>(); - nym_coconut::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) + + let hashed_value = hash_to_scalar(self.voucher_value.to_string()); + let hashed_info = hash_to_scalar(&self.voucher_info); + let public_attributes = &[&hashed_value, &hashed_info]; + + nym_coconut::verify_credential(¶ms, verification_key, &self.theta, public_attributes) } pub fn as_bytes(&self) -> Vec { @@ -180,8 +182,8 @@ mod tests { ¶ms, &verification_key, &signature, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); let credential = Credential::new(4, theta, voucher_value, voucher_info, 42); diff --git a/common/commands/Cargo.toml b/common/commands/Cargo.toml index e538a7ed31..6d4133acc4 100644 --- a/common/commands/Cargo.toml +++ b/common/commands/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-cli-commands" version = "1.0.0" authors.workspace = true edition = "2021" +license.workspace = true [dependencies] anyhow = { workspace = true } @@ -21,7 +22,7 @@ rand = {version = "0.6", features = ["std"] } serde = { version = "1.0", features = ["derive"] } serde_json = { workspace = true } thiserror = { workspace = true } -time = { version = "0.3.6", features = ["parsing", "formatting"] } +time = { workspace = true, features = ["parsing", "formatting"] } toml = "0.5.6" url = { workspace = true } tap = "1" diff --git a/common/commands/src/coconut/issue_credentials.rs b/common/commands/src/coconut/issue_credentials.rs index cd8a9b5f36..9533af614e 100644 --- a/common/commands/src/coconut/issue_credentials.rs +++ b/common/commands/src/coconut/issue_credentials.rs @@ -26,6 +26,10 @@ pub struct Args { } pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> { + if args.amount == 0 { + bail!("did not specify credential amount") + } + let loaded = CommonConfigsWrapper::try_load(args.client_config)?; if let Ok(id) = loaded.try_get_id() { diff --git a/common/commands/src/validator/mixnet/operators/mixnode/settings/mod.rs b/common/commands/src/validator/mixnet/operators/mixnode/settings/mod.rs index 9d0c018cf9..4ab2a0a4ff 100644 --- a/common/commands/src/validator/mixnet/operators/mixnode/settings/mod.rs +++ b/common/commands/src/validator/mixnet/operators/mixnode/settings/mod.rs @@ -4,6 +4,7 @@ use clap::{Args, Subcommand}; pub mod update_config; +pub mod update_cost_params; pub mod vesting_update_config; #[derive(Debug, Args)] @@ -20,7 +21,5 @@ pub enum MixnetOperatorsMixnodeSettingsCommands { /// Update mixnode configuration for a mixnode bonded with locked tokens VestingUpdateConfig(vesting_update_config::Args), /// Update mixnode cost parameters - UpdateCostParameters, - /// Update mixnode cost parameters for a mixnode bonded with locked tokens - VestingUpdateCostParameters, + UpdateCostParameters(update_cost_params::Args), } diff --git a/common/commands/src/validator/mixnet/operators/mixnode/settings/update_cost_params.rs b/common/commands/src/validator/mixnet/operators/mixnode/settings/update_cost_params.rs new file mode 100644 index 0000000000..0616a81d33 --- /dev/null +++ b/common/commands/src/validator/mixnet/operators/mixnode/settings/update_cost_params.rs @@ -0,0 +1,48 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::context::SigningClient; +use clap::Parser; +use cosmwasm_std::Uint128; +use log::info; +use nym_mixnet_contract_common::{MixNodeCostParams, Percent}; +use nym_validator_client::nyxd::contract_traits::MixnetSigningClient; +use nym_validator_client::nyxd::CosmWasmCoin; + +#[derive(Debug, Parser)] +pub struct Args { + #[clap( + long, + help = "input your profit margin as follows; (so it would be 10, rather than 0.1)" + )] + pub profit_margin_percent: Option, + + #[clap( + long, + help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')" + )] + pub interval_operating_cost: Option, +} + +pub async fn update_cost_params(args: Args, client: SigningClient) { + let denom = client.current_chain_details().mix_denom.base.as_str(); + + let cost_params = MixNodeCostParams { + profit_margin_percent: Percent::from_percentage_value( + args.profit_margin_percent.unwrap_or(10) as u64, + ) + .unwrap(), + interval_operating_cost: CosmWasmCoin { + denom: denom.into(), + amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)), + }, + }; + + info!("Starting mixnode params updating!"); + let res = client + .update_mixnode_cost_params(cost_params, None) + .await + .expect("failed to update cost params"); + + info!("Cost params result: {:?}", res) +} diff --git a/common/config/Cargo.toml b/common/config/Cargo.toml index d7ca649e60..fde42a3c2b 100644 --- a/common/config/Cargo.toml +++ b/common/config/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-config" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,4 +18,4 @@ url = { workspace = true } nym-network-defaults = { path = "../network-defaults" } [features] -default = ["dirs"] \ No newline at end of file +default = ["dirs"] diff --git a/common/config/src/lib.rs b/common/config/src/lib.rs index 3a9104eeb6..932d7515a6 100644 --- a/common/config/src/lib.rs +++ b/common/config/src/lib.rs @@ -4,7 +4,7 @@ use handlebars::{Handlebars, TemplateRenderError}; use serde::de::DeserializeOwned; use serde::Serialize; -use std::fs::File; +use std::fs::{create_dir_all, File}; use std::io::Write; use std::path::{Path, PathBuf}; use std::{fs, io}; @@ -25,12 +25,20 @@ pub const DEFAULT_CONFIG_FILENAME: &str = "config.toml"; #[cfg(feature = "dirs")] pub fn must_get_home() -> PathBuf { - dirs::home_dir().expect("Failed to evaluate $HOME value") + if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") { + home_dir.into() + } else { + dirs::home_dir().expect("Failed to evaluate $HOME value") + } } #[cfg(feature = "dirs")] pub fn may_get_home() -> Option { - dirs::home_dir() + if let Some(home_dir) = std::env::var_os("NYM_HOME_DIR") { + Some(home_dir.into()) + } else { + dirs::home_dir() + } } pub trait NymConfigTemplate: Serialize { @@ -64,16 +72,22 @@ where C: NymConfigTemplate, P: AsRef, { - log::debug!("trying to save config file to {}", path.as_ref().display()); - let file = File::create(path.as_ref())?; + let path = path.as_ref(); + log::info!("saving config file to {}", path.display()); - // TODO: check for whether any of our configs stores anything sensitive + if let Some(parent) = path.parent() { + create_dir_all(parent)?; + } + + let file = File::create(path)?; + + // TODO: check for whether any of our configs store anything sensitive // and change that to 0o644 instead #[cfg(target_family = "unix")] { use std::os::unix::fs::PermissionsExt; - let mut perms = fs::metadata(path.as_ref())?.permissions(); + let mut perms = fs::metadata(path)?.permissions(); perms.set_mode(0o600); fs::set_permissions(path, perms)?; } diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml index 90cf90ba17..c60406bc57 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-coconut-bandwidth-contract-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,4 +13,4 @@ cw2 = { workspace = true, optional = true } nym-multisig-contract-common = { path = "../multisig-contract" } [features] -schema = ["cw2"] \ No newline at end of file +schema = ["cw2"] diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs index 5204f6a97e..450ecd2543 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/events.rs @@ -4,6 +4,9 @@ // event types pub const DEPOSITED_FUNDS_EVENT_TYPE: &str = "deposited-funds"; +// a 'wasm-' prefix is added to all cosmwasm events +pub const COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE: &str = "wasm-deposited-funds"; + // attributes that are used in multiple places pub const DEPOSIT_VALUE: &str = "deposit-value"; pub const DEPOSIT_INFO: &str = "deposit-info"; diff --git a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs index 835d6175de..3ab4e72150 100644 --- a/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs +++ b/common/cosmwasm-smart-contracts/coconut-bandwidth-contract/src/spend_credential.rs @@ -142,7 +142,7 @@ pub fn funds_from_cosmos_msgs(msgs: Vec) -> Option { contract_addr: _, msg, funds: _, - })) = msgs.get(0) + })) = msgs.first() { if let Ok(ExecuteMsg::ReleaseFunds { funds }) = from_binary::(msg) { return Some(funds); diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml index 271d5b5bb9..b5872e102f 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml +++ b/common/cosmwasm-smart-contracts/coconut-dkg/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-coconut-dkg-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,4 +15,4 @@ contracts-common = { path = "../contracts-common", package = "nym-contracts-comm nym-multisig-contract-common = { path = "../multisig-contract" } [features] -schema = [] \ No newline at end of file +schema = [] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index b01f1101ab..29e7c52d67 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -174,17 +174,19 @@ impl Display for EpochState { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { EpochState::PublicKeySubmission { resharing } => { - write!(f, "PublicKeySubmission with resharing {resharing}") + write!(f, "PublicKeySubmission (resharing: {resharing})") + } + EpochState::DealingExchange { resharing } => { + write!(f, "DealingExchange (resharing: {resharing})") } - EpochState::DealingExchange { resharing } => write!(f, "DealingExchange {resharing}"), EpochState::VerificationKeySubmission { resharing } => { - write!(f, "VerificationKeySubmission with resharing {resharing}") + write!(f, "VerificationKeySubmission (resharing: {resharing})") } EpochState::VerificationKeyValidation { resharing } => { - write!(f, "VerificationKeyValidation with resharing {resharing}") + write!(f, "VerificationKeyValidation (resharing: {resharing})") } EpochState::VerificationKeyFinalization { resharing } => { - write!(f, "VerificationKeyFinalization with resharing {resharing}") + write!(f, "VerificationKeyFinalization (resharing: {resharing})") } EpochState::InProgress => write!(f, "InProgress"), } diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs index 7a3d02f2e8..188215b3b0 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/verification_key.rs @@ -62,7 +62,7 @@ pub fn owner_from_cosmos_msgs(msgs: &[CosmosMsg]) -> Option { contract_addr: _, msg, funds: _, - })) = msgs.get(0) + })) = msgs.first() { if let Ok(ExecuteMsg::VerifyVerificationKeyShare { owner, .. }) = from_binary::(msg) diff --git a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs index 4dca6b173b..218bd6ca50 100644 --- a/common/cosmwasm-smart-contracts/contracts-common/src/types.rs +++ b/common/cosmwasm-smart-contracts/contracts-common/src/types.rs @@ -3,6 +3,7 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::Decimal; +use cosmwasm_std::OverflowError; use cosmwasm_std::Uint128; use serde::de::Error; use serde::{Deserialize, Deserializer}; @@ -71,6 +72,10 @@ impl Percent { // we know the cast from u128 to u8 is a safe one since the internal value must be within 0 - 1 range truncate_decimal(hundred * self.0).u128() as u8 } + + pub fn checked_pow(&self, exp: u32) -> Result { + self.0.checked_pow(exp).map(Percent) + } } impl Display for Percent { diff --git a/common/cosmwasm-smart-contracts/ephemera/Cargo.toml b/common/cosmwasm-smart-contracts/ephemera/Cargo.toml index fda88a18f5..f944fa18f8 100644 --- a/common/cosmwasm-smart-contracts/ephemera/Cargo.toml +++ b/common/cosmwasm-smart-contracts/ephemera/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-ephemera-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,4 +14,4 @@ cw-utils = { workspace = true } contracts-common = { path = "../contracts-common", package = "nym-contracts-common" } [features] -schema = [] \ No newline at end of file +schema = [] diff --git a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml index 2856c44925..c27c9d1356 100644 --- a/common/cosmwasm-smart-contracts/group-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/group-contract/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-group-contract-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml index 205b3d7d01..3477658c40 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/mixnet-contract/Cargo.toml @@ -25,12 +25,12 @@ humantime-serde = "1.1.1" # TO CHECK WHETHER STILL NEEDED: log = { workspace = true } -time = { version = "0.3.6", features = ["parsing", "formatting"] } +time = { workspace = true, features = ["parsing", "formatting"] } ts-rs = { workspace = true, optional = true } [dev-dependencies] rand_chacha = "0.3" -time = { version = "0.3.5", features = ["serde", "macros"] } +time = { workspace = true, features = ["serde", "macros"] } [features] default = [] diff --git a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml index 6ffc7deb14..704f19a6cf 100644 --- a/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml +++ b/common/cosmwasm-smart-contracts/multisig-contract/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-multisig-contract-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/cosmwasm-smart-contracts/name-service/Cargo.toml b/common/cosmwasm-smart-contracts/name-service/Cargo.toml index 4af7d851f1..5e3a3081bd 100644 --- a/common/cosmwasm-smart-contracts/name-service/Cargo.toml +++ b/common/cosmwasm-smart-contracts/name-service/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-name-service-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,4 +17,4 @@ serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } [features] -schema = ["cw2"] \ No newline at end of file +schema = ["cw2"] diff --git a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml index aed3912abd..341c985e34 100644 --- a/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml +++ b/common/cosmwasm-smart-contracts/service-provider-directory/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-service-provider-directory-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -15,4 +16,4 @@ nym-contracts-common = { path = "../contracts-common", version = "0.5.0" } thiserror = { workspace = true } [features] -schema = ["cw2"] \ No newline at end of file +schema = ["cw2"] diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs index 0d1e5b754e..5768cde2f5 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/account.rs @@ -49,7 +49,7 @@ impl Account { pub fn period_duration(&self) -> Result { self.periods - .get(0) + .first() .ok_or(VestingContractError::UnpopulatedVestingPeriods { owner: self.owner_address.clone(), }) diff --git a/common/credential-storage/Cargo.toml b/common/credential-storage/Cargo.toml index a5c6f9e71d..9beb030e22 100644 --- a/common/credential-storage/Cargo.toml +++ b/common/credential-storage/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-credential-storage" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,14 +14,14 @@ thiserror = { workspace = true } tokio = { version = "1.24.1", features = ["sync"]} [target."cfg(not(target_arch = \"wasm32\"))".dependencies.sqlx] -version = "0.5" +workspace = true features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] -version = "1.24.1" +workspace = true features = [ "rt-multi-thread", "net", "signal", "fs" ] [build-dependencies] -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } diff --git a/common/credential-utils/Cargo.toml b/common/credential-utils/Cargo.toml index 1949107036..1b5ebfd5bd 100644 --- a/common/credential-utils/Cargo.toml +++ b/common/credential-utils/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-credential-utils" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/credentials/Cargo.toml b/common/credentials/Cargo.toml index aff9cf01ab..8e96ce7b30 100644 --- a/common/credentials/Cargo.toml +++ b/common/credentials/Cargo.toml @@ -2,18 +2,20 @@ name = "nym-credentials" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] } +bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } cosmrs = { workspace = true } thiserror = { workspace = true } log = { workspace = true } +zeroize = { workspace = true } # I guess temporarily until we get serde support in coconut up and running nym-coconut-interface = { path = "../coconut-interface" } -nym-crypto = { path = "../crypto", features = ["rand", "asymmetric", "symmetric", "hashing"] } +nym-crypto = { path = "../crypto", features = ["rand", "asymmetric"] } nym-api-requests = { path = "../../nym-api/nym-api-requests" } nym-validator-client = { path = "../client-libs/validator-client", default-features = false } diff --git a/common/credentials/src/coconut/bandwidth.rs b/common/credentials/src/coconut/bandwidth.rs index 7e458456df..9646860a74 100644 --- a/common/credentials/src/coconut/bandwidth.rs +++ b/common/credentials/src/coconut/bandwidth.rs @@ -13,38 +13,60 @@ use nym_coconut_interface::{ PrivateAttribute, PublicAttribute, Signature, VerificationKey, }; use nym_crypto::asymmetric::{encryption, identity}; +use zeroize::{Zeroize, ZeroizeOnDrop}; use super::utils::prepare_credential_for_spending; use crate::error::Error; -pub const PUBLIC_ATTRIBUTES: u32 = 2; -pub const PRIVATE_ATTRIBUTES: u32 = 2; -pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES; - +#[derive(Zeroize, ZeroizeOnDrop)] pub struct BandwidthVoucher { - // a random secret value generated by the client used for double-spending detection + // private attributes + /// a random secret value generated by the client used for double-spending detection serial_number: PrivateAttribute, - // a random secret value generated by the client used to bind multiple credentials together + + /// a random secret value generated by the client used to bind multiple credentials together binding_number: PrivateAttribute, - // the value (e.g., bandwidth) encoded in this voucher - voucher_value: PublicAttribute, - // the plain text value (e.g., bandwidth) encoded in this voucher + + // public atttributes: + /// the plain text value (e.g., bandwidth) encoded in this voucher + // TODO: in another PR change the value from `"1000"` to `"1000unym"` voucher_value_plain: String, - // a field with public information, e.g., type of voucher, interval etc. - voucher_info: PublicAttribute, - // the plain text information + + /// the plain text information voucher_info_plain: String, - // the hash of the deposit transaction + + /// the precomputed value (e.g., bandwidth) encoded in this voucher + _voucher_value_prehashed: PublicAttribute, + + /// the precomputed field with public information, e.g., type of voucher, interval etc. + _voucher_info_prehashed: PublicAttribute, + + /// the hash of the deposit transaction + #[zeroize(skip)] tx_hash: Hash, - // base58 encoded private key ensuring the depositer requested these attributes + + /// base58 encoded private key ensuring the depositer requested these attributes signing_key: identity::PrivateKey, - // base58 encoded private key ensuring only this client receives the signature share - encryption_key: encryption::PrivateKey, + + /// base58 encoded private key ensuring only this client receives the signature share + unused_ed25519: encryption::PrivateKey, + pedersen_commitments_openings: Vec, + + #[zeroize(skip)] blind_sign_request: BlindSignRequest, } impl BandwidthVoucher { + pub const PUBLIC_ATTRIBUTES: u32 = 2; + pub const PRIVATE_ATTRIBUTES: u32 = 2; + pub const ENCODED_ATTRIBUTES: u32 = 4; + + pub fn default_parameters() -> Parameters { + // safety: the unwrap is fine here as Self::ENCODED_ATTRIBUTES is non-zero + Parameters::new(Self::ENCODED_ATTRIBUTES).unwrap() + } + pub fn new( params: &Parameters, voucher_value: String, @@ -57,24 +79,26 @@ impl BandwidthVoucher { let binding_number = params.random_scalar(); let voucher_value_plain = voucher_value.clone(); let voucher_info_plain = voucher_info.clone(); - let voucher_value = hash_to_scalar(voucher_value.as_bytes()); - let voucher_info = hash_to_scalar(voucher_info.as_bytes()); + + let _voucher_value_prehashed = hash_to_scalar(voucher_value); + let _voucher_info_prehashed = hash_to_scalar(voucher_info); + let (pedersen_commitments_openings, blind_sign_request) = prepare_blind_sign( params, - &[serial_number, binding_number], - &[voucher_value, voucher_info], + &[&serial_number, &binding_number], + &[&_voucher_value_prehashed, &_voucher_info_prehashed], ) .unwrap(); BandwidthVoucher { serial_number, binding_number, - voucher_value, + _voucher_value_prehashed, voucher_value_plain, - voucher_info, + _voucher_info_prehashed, voucher_info_plain, tx_hash, signing_key, - encryption_key, + unused_ed25519: encryption_key, pedersen_commitments_openings, blind_sign_request, } @@ -87,7 +111,7 @@ impl BandwidthVoucher { let voucher_info_plain_b = self.voucher_info_plain.as_bytes(); let tx_hash_b = self.tx_hash.as_bytes(); let signing_key_b = self.signing_key.to_bytes(); - let encryption_key_b = self.encryption_key.to_bytes(); + let encryption_key_b = self.unused_ed25519.to_bytes(); let blind_sign_request_b = self.blind_sign_request.to_bytes(); let mut ret = Vec::new(); @@ -171,13 +195,13 @@ impl BandwidthVoucher { bytes[var_length_pointer..var_length_pointer + voucher_value_plain_no].to_vec(), ) .or_else(utf_err)?; - let voucher_value = hash_to_scalar(&voucher_value_plain); + let _voucher_value_prehashed = hash_to_scalar(&voucher_value_plain); var_length_pointer += voucher_value_plain_no; let voucher_info_plain = String::from_utf8( bytes[var_length_pointer..var_length_pointer + voucher_info_plain_no].to_vec(), ) .or_else(utf_err)?; - let voucher_info = hash_to_scalar(&voucher_info_plain); + let _voucher_info_prehashed = hash_to_scalar(&voucher_info_plain); var_length_pointer += voucher_info_plain_no; let blind_sign_request = BlindSignRequest::from_bytes( &bytes[var_length_pointer..var_length_pointer + blind_sign_request_no], @@ -196,36 +220,43 @@ impl BandwidthVoucher { Ok(Self { serial_number, binding_number, - voucher_value, + _voucher_value_prehashed, voucher_value_plain, - voucher_info, + _voucher_info_prehashed, voucher_info_plain, tx_hash, signing_key, - encryption_key, + unused_ed25519: encryption_key, pedersen_commitments_openings, blind_sign_request, }) } /// Check if the plain values correspond to the PublicAttributes - pub fn verify_against_plain(values: &[PublicAttribute], plain_values: &[String]) -> bool { + pub fn verify_against_plain(values: &[&PublicAttribute], plain_values: &[String]) -> bool { values.len() == 2 && plain_values.len() == 2 - && values[0] == hash_to_scalar(&plain_values[0]) - && values[1] == hash_to_scalar(&plain_values[1]) + && values[0] == &hash_to_scalar(&plain_values[0]) + && values[1] == &hash_to_scalar(&plain_values[1]) } - pub fn tx_hash(&self) -> &Hash { - &self.tx_hash + pub fn tx_hash(&self) -> Hash { + self.tx_hash } - pub fn get_public_attributes(&self) -> Vec { - vec![self.voucher_value, self.voucher_info] + pub fn get_public_attributes(&self) -> Vec<&PublicAttribute> { + vec![ + &self._voucher_value_prehashed, + &self._voucher_info_prehashed, + ] + } + + pub fn identity_key(&self) -> &identity::PrivateKey { + &self.signing_key } pub fn encryption_key(&self) -> &encryption::PrivateKey { - &self.encryption_key + &self.unused_ed25519 } pub fn pedersen_commitments_openings(&self) -> &Vec { @@ -247,27 +278,32 @@ impl BandwidthVoucher { ] } - pub fn get_private_attributes(&self) -> Vec { - vec![self.serial_number, self.binding_number] + pub fn get_private_attributes(&self) -> Vec<&PrivateAttribute> { + vec![&self.serial_number, &self.binding_number] } - pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature { + pub fn signable_plaintext(request: &BlindSignRequest, tx_hash: Hash) -> Vec { let mut message = request.to_bytes(); - message.extend_from_slice(self.tx_hash.to_string().as_bytes()); - self.signing_key.sign(&message) + message.extend_from_slice(tx_hash.as_bytes()); + message + } + + pub fn sign(&self) -> identity::Signature { + let message = Self::signable_plaintext(&self.blind_sign_request, self.tx_hash); + self.signing_key.sign(message) } } pub fn prepare_for_spending( voucher_value: u64, voucher_info: String, - serial_number: PrivateAttribute, - binding_number: PrivateAttribute, + serial_number: &PrivateAttribute, + binding_number: &PrivateAttribute, epoch_id: u64, signature: &Signature, verification_key: &VerificationKey, ) -> Result { - let params = Parameters::new(TOTAL_ATTRIBUTES)?; + let params = Parameters::new(BandwidthVoucher::ENCODED_ATTRIBUTES)?; prepare_credential_for_spending( ¶ms, @@ -316,24 +352,30 @@ mod test { let deserialized_voucher = BandwidthVoucher::try_from_bytes(&bytes).unwrap(); assert_eq!(voucher.serial_number, deserialized_voucher.serial_number); assert_eq!(voucher.binding_number, deserialized_voucher.binding_number); - assert_eq!(voucher.voucher_value, deserialized_voucher.voucher_value); assert_eq!( voucher.voucher_value_plain, deserialized_voucher.voucher_value_plain ); - assert_eq!(voucher.voucher_info, deserialized_voucher.voucher_info); assert_eq!( voucher.voucher_info_plain, deserialized_voucher.voucher_info_plain ); + assert_eq!( + voucher._voucher_value_prehashed, + deserialized_voucher._voucher_value_prehashed + ); + assert_eq!( + voucher._voucher_info_prehashed, + deserialized_voucher._voucher_info_prehashed + ); assert_eq!(voucher.tx_hash, deserialized_voucher.tx_hash); assert_eq!( voucher.signing_key.to_string(), deserialized_voucher.signing_key.to_string() ); assert_eq!( - voucher.encryption_key.to_string(), - deserialized_voucher.encryption_key.to_string() + voucher.unused_ed25519.to_string(), + deserialized_voucher.unused_ed25519.to_string() ); assert_eq!( voucher.pedersen_commitments_openings, @@ -371,11 +413,11 @@ mod test { ] )); assert!(!BandwidthVoucher::verify_against_plain( - &[voucher.get_public_attributes()[0], Attribute::one()], + &[voucher.get_public_attributes()[0], &Attribute::one()], &voucher.get_public_attributes_plain() )); assert!(!BandwidthVoucher::verify_against_plain( - &[Attribute::one(), voucher.get_public_attributes()[1]], + &[&Attribute::one(), voucher.get_public_attributes()[1]], &voucher.get_public_attributes_plain() )); assert!(BandwidthVoucher::verify_against_plain( diff --git a/common/credentials/src/coconut/mod.rs b/common/credentials/src/coconut/mod.rs index c1d900689a..bf480ad58a 100644 --- a/common/credentials/src/coconut/mod.rs +++ b/common/credentials/src/coconut/mod.rs @@ -2,5 +2,4 @@ // SPDX-License-Identifier: Apache-2.0 pub mod bandwidth; -pub mod params; pub mod utils; diff --git a/common/credentials/src/coconut/params.rs b/common/credentials/src/coconut/params.rs deleted file mode 100644 index 906ef7cae0..0000000000 --- a/common/credentials/src/coconut/params.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use nym_crypto::aes::Aes128; -use nym_crypto::blake3; -use nym_crypto::ctr; - -type Aes128Ctr = ctr::Ctr64LE; - -/// Hashing algorithm used during hkdf for ephemeral shared key generation per blinded signature -/// response encryption. -pub type NymApiCredentialHkdfAlgorithm = blake3::Hasher; - -/// Encryption algorithm used for end-to-end encryption of blinded signature response -pub type NymApiCredentialEncryptionAlgorithm = Aes128Ctr; diff --git a/common/credentials/src/coconut/utils.rs b/common/credentials/src/coconut/utils.rs index b7fda42f73..367f62e2c3 100644 --- a/common/credentials/src/coconut/utils.rs +++ b/common/credentials/src/coconut/utils.rs @@ -1,18 +1,14 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; -use crate::coconut::params::{NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm}; +use crate::coconut::bandwidth::BandwidthVoucher; use crate::error::Error; use log::{debug, warn}; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut_interface::{ aggregate_signature_shares, aggregate_verification_keys, prove_bandwidth_credential, Attribute, - BlindedSignature, Credential, Parameters, Signature, SignatureShare, VerificationKey, + Credential, Parameters, Signature, SignatureShare, VerificationKey, }; -use nym_crypto::asymmetric::encryption::PublicKey; -use nym_crypto::shared_key::recompute_shared_key; -use nym_crypto::symmetric::stream_cipher; use nym_validator_client::client::CoconutApiClient; pub async fn obtain_aggregate_verification_key( @@ -36,47 +32,34 @@ pub async fn obtain_aggregate_verification_key( async fn obtain_partial_credential( params: &Parameters, - attributes: &BandwidthVoucher, + voucher: &BandwidthVoucher, client: &nym_validator_client::client::NymApiClient, validator_vk: &VerificationKey, ) -> Result { - let public_attributes = attributes.get_public_attributes(); - let public_attributes_plain = attributes.get_public_attributes_plain(); - let private_attributes = attributes.get_private_attributes(); - let blind_sign_request = attributes.blind_sign_request(); + let public_attributes_plain = voucher.get_public_attributes_plain(); + let blind_sign_request = voucher.blind_sign_request(); + let request_signature = voucher.sign(); let blind_sign_request_body = BlindSignRequestBody::new( - blind_sign_request, - attributes.tx_hash().to_string(), - attributes.sign(blind_sign_request).to_base58_string(), - &public_attributes, + blind_sign_request.clone(), + voucher.tx_hash(), + request_signature, public_attributes_plain, - (public_attributes.len() + private_attributes.len()) as u32, ); let response = client.blind_sign(&blind_sign_request_body).await?; - let encrypted_signature = response.encrypted_signature; - let remote_key = PublicKey::from_bytes(&response.remote_key)?; - let encryption_key = recompute_shared_key::< - NymApiCredentialEncryptionAlgorithm, - NymApiCredentialHkdfAlgorithm, - >(&remote_key, attributes.encryption_key()); - let zero_iv = stream_cipher::zero_iv::(); - let blinded_signature_bytes = stream_cipher::decrypt::( - &encryption_key, - &zero_iv, - &encrypted_signature, - ); + let blinded_signature = response.blinded_signature; - let blinded_signature = BlindedSignature::from_bytes(&blinded_signature_bytes)?; + let public_attributes = voucher.get_public_attributes(); + let private_attributes = voucher.get_private_attributes(); - let unblinded_signature = blinded_signature.unblind( + let unblinded_signature = blinded_signature.unblind_and_verify( params, validator_vk, &private_attributes, &public_attributes, &blind_sign_request.get_commitment_hash(), - attributes.pedersen_commitments_openings(), + voucher.pedersen_commitments_openings(), )?; Ok(unblinded_signature) @@ -84,16 +67,13 @@ async fn obtain_partial_credential( pub async fn obtain_aggregate_signature( params: &Parameters, - attributes: &BandwidthVoucher, + voucher: &BandwidthVoucher, coconut_api_clients: &[CoconutApiClient], threshold: u64, ) -> Result { if coconut_api_clients.is_empty() { return Err(Error::NoValidatorsAvailable); } - let public_attributes = attributes.get_public_attributes(); - let private_attributes = attributes.get_private_attributes(); - let mut shares = Vec::with_capacity(coconut_api_clients.len()); let validators_partial_vks: Vec<_> = coconut_api_clients .iter() @@ -114,7 +94,7 @@ pub async fn obtain_aggregate_signature( match obtain_partial_credential( params, - attributes, + voucher, &coconut_api_client.api_client, &coconut_api_client.verification_key, ) @@ -136,6 +116,9 @@ pub async fn obtain_aggregate_signature( return Err(Error::NotEnoughShares); } + let public_attributes = voucher.get_public_attributes(); + let private_attributes = voucher.get_private_attributes(); + let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); attributes.extend_from_slice(&private_attributes); attributes.extend_from_slice(&public_attributes); @@ -150,8 +133,8 @@ pub fn prepare_credential_for_spending( params: &Parameters, voucher_value: u64, voucher_info: String, - serial_number: Attribute, - binding_number: Attribute, + serial_number: &Attribute, + binding_number: &Attribute, epoch_id: u64, signature: &Signature, verification_key: &VerificationKey, @@ -165,7 +148,7 @@ pub fn prepare_credential_for_spending( )?; Ok(Credential::new( - PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES, + BandwidthVoucher::ENCODED_ATTRIBUTES, theta, voucher_value, voucher_info, diff --git a/common/crypto/src/asymmetric/encryption/mod.rs b/common/crypto/src/asymmetric/encryption/mod.rs index 6c45d41d60..bc97f67df4 100644 --- a/common/crypto/src/asymmetric/encryption/mod.rs +++ b/common/crypto/src/asymmetric/encryption/mod.rs @@ -201,6 +201,17 @@ impl<'a> From<&'a PrivateKey> for PublicKey { } impl PrivateKey { + #[cfg(feature = "rand")] + pub fn new(rng: &mut R) -> Self { + let x25519_secret = x25519_dalek::StaticSecret::new(rng); + + PrivateKey(x25519_secret) + } + + pub fn public_key(&self) -> PublicKey { + self.into() + } + pub fn to_bytes(&self) -> [u8; PRIVATE_KEY_SIZE] { self.0.to_bytes() } diff --git a/common/crypto/src/asymmetric/identity/mod.rs b/common/crypto/src/asymmetric/identity/mod.rs index a56abdee32..ffbcac88ac 100644 --- a/common/crypto/src/asymmetric/identity/mod.rs +++ b/common/crypto/src/asymmetric/identity/mod.rs @@ -5,13 +5,14 @@ pub use ed25519_dalek::ed25519::signature::Signature as SignatureTrait; pub use ed25519_dalek::SignatureError; pub use ed25519_dalek::{Verifier, PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, SIGNATURE_LENGTH}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; -#[cfg(feature = "sphinx")] -use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; use std::fmt::{self, Display, Formatter}; use std::str::FromStr; use thiserror::Error; use zeroize::{Zeroize, ZeroizeOnDrop}; +#[cfg(feature = "sphinx")] +use nym_sphinx_types::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; + #[cfg(feature = "rand")] use rand::{CryptoRng, RngCore}; #[cfg(feature = "serde")] @@ -224,6 +225,17 @@ impl<'a> From<&'a PrivateKey> for PublicKey { } impl PrivateKey { + #[cfg(feature = "rand")] + pub fn new(rng: &mut R) -> Self { + let ed25519_secret = ed25519_dalek::SecretKey::generate(rng); + + PrivateKey(ed25519_secret) + } + + pub fn public_key(&self) -> PublicKey { + self.into() + } + pub fn to_bytes(&self) -> [u8; SECRET_KEY_LENGTH] { self.0.to_bytes() } @@ -295,7 +307,7 @@ impl PemStorableKey for PrivateKey { } } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub struct Signature(ed25519_dalek::Signature); impl Signature { @@ -319,6 +331,14 @@ impl Signature { } } +impl FromStr for Signature { + type Err = Ed25519RecoveryError; + + fn from_str(s: &str) -> Result { + Signature::from_base58_string(s) + } +} + #[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result diff --git a/common/dkg/Cargo.toml b/common/dkg/Cargo.toml index 26808724bf..03b0d35c9f 100644 --- a/common/dkg/Cargo.toml +++ b/common/dkg/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-dkg" version = "0.1.0" edition = "2021" resolver = "2" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,7 +12,7 @@ bitvec = "1.0.0" # unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork # as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm -bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } +bls12_381 = { workspace = true, default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] } nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common", optional = true } bs58 = "0.4" @@ -29,11 +30,11 @@ zeroize = { workspace = true, features = ["zeroize_derive"] } nym-pemstore = { path = "../pemstore" } [dependencies.group] -version = "0.11" +workspace = true default-features = false [dependencies.ff] -version = "0.11" +workspace = true default-features = false [dev-dependencies] diff --git a/common/dkg/benches/benchmarks.rs b/common/dkg/benches/benchmarks.rs index 483c6d20e9..2951a57a7c 100644 --- a/common/dkg/benches/benchmarks.rs +++ b/common/dkg/benches/benchmarks.rs @@ -366,11 +366,7 @@ pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); let ordered_public_keys = receivers.values().copied().collect::>(); let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &mut rng); @@ -400,11 +396,7 @@ pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); let ordered_public_keys = receivers.values().copied().collect::>(); let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &mut rng); @@ -436,11 +428,7 @@ pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &mut rng); @@ -478,11 +466,7 @@ pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, ¶ms, &mut rng); @@ -545,11 +529,7 @@ pub fn share_encryption_100(c: &mut Criterion) { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); c.bench_function("100 shares encryption", |b| { b.iter(|| black_box(encrypt_shares(&remote_share_key_pairs, ¶ms, &mut rng))) diff --git a/common/dkg/src/dealing.rs b/common/dkg/src/dealing.rs index ff31c5736e..1e0363635f 100644 --- a/common/dkg/src/dealing.rs +++ b/common/dkg/src/dealing.rs @@ -115,11 +115,7 @@ impl Dealing { .map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into()) .collect::>(); - let remote_share_key_pairs = shares - .iter() - .zip(receivers.values()) - .map(|(share, key)| (share, key)) - .collect::>(); + let remote_share_key_pairs = shares.iter().zip(receivers.values()).collect::>(); let ordered_public_keys = receivers.values().copied().collect::>(); let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, params, &mut rng); diff --git a/common/execute/Cargo.toml b/common/execute/Cargo.toml index 634737397f..586ce0d352 100644 --- a/common/execute/Cargo.toml +++ b/common/execute/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-execute" version = "0.1.0" edition = "2021" +license.workspace = true [lib] proc-macro = true diff --git a/common/exit-policy/src/lib.rs b/common/exit-policy/src/lib.rs index f1b41274bc..4c54e575b9 100644 --- a/common/exit-policy/src/lib.rs +++ b/common/exit-policy/src/lib.rs @@ -81,6 +81,15 @@ ExitPolicy accept6 *6:119 ExitPolicy accept *4:120 ExitPolicy reject6 [FC00::]/7:* +# Portless +ExitPolicy accept *:0 +ExitPolicy accept *4:0 +ExitPolicy accept *6:0 + +ExitPolicy reject *:0 +ExitPolicy reject *4:0 +ExitPolicy reject *6:0 + #ExitPolicy accept *:8080 #and another comment here ExitPolicy reject FE80:0000:0000:0000:0202:B3FF:FE1E:8329:* @@ -184,6 +193,60 @@ ExitPolicy reject *:* }, ); + // ExitPolicy accept *:0 + expected.push( + Accept, + AddressPortPattern { + ip_pattern: IpPattern::Star, + ports: PortRange::new_zero(), + }, + ); + + // ExitPolicy accept *4:0 + expected.push( + Accept, + AddressPortPattern { + ip_pattern: IpPattern::V4Star, + ports: PortRange::new_zero(), + }, + ); + + // ExitPolicy accept *6:0 + expected.push( + Accept, + AddressPortPattern { + ip_pattern: IpPattern::V6Star, + ports: PortRange::new_zero(), + }, + ); + + // ExitPolicy reject *:0 + expected.push( + Reject, + AddressPortPattern { + ip_pattern: IpPattern::Star, + ports: PortRange::new_zero(), + }, + ); + + // ExitPolicy reject *4:0 + expected.push( + Reject, + AddressPortPattern { + ip_pattern: IpPattern::V4Star, + ports: PortRange::new_zero(), + }, + ); + + // ExitPolicy reject *6:0 + expected.push( + Reject, + AddressPortPattern { + ip_pattern: IpPattern::V6Star, + ports: PortRange::new_zero(), + }, + ); + // ExitPolicy FE80:0000:0000:0000:0202:B3FF:FE1E:8329:* expected.push( Reject, diff --git a/common/exit-policy/src/policy/address_policy.rs b/common/exit-policy/src/policy/address_policy.rs index 09162a6e9b..621fd291e9 100644 --- a/common/exit-policy/src/policy/address_policy.rs +++ b/common/exit-policy/src/policy/address_policy.rs @@ -264,7 +264,13 @@ mod stringified_ip_pattern { impl AddressPortPattern { /// Return true iff this pattern matches a given address and port. pub fn matches(&self, addr: &IpAddr, port: u16) -> bool { - self.ip_pattern.matches(addr) && self.ports.contains(port) + // For backward compatibility, we treat port 0 as a wildcard until all gateways have + // upgraded, at which point we can add *:0 to the policy list. + if port == 0 { + self.ip_pattern.matches(addr) + } else { + self.ip_pattern.matches(addr) && self.ports.contains(port) + } } /// As matches, but accept a SocketAddr. @@ -395,19 +401,9 @@ fn parse_addr(s: &str) -> Result { }) } -/// Helper: try to parse a port making sure it's non-zero fn parse_port(s: &str) -> Result { - let port = s - .parse::() - .map_err(|_| PolicyError::InvalidPort { raw: s.to_string() })?; - - if port == 0 { - Err(PolicyError::InvalidPort { - raw: port.to_string(), - }) - } else { - Ok(port) - } + s.parse::() + .map_err(|_| PolicyError::InvalidPort { raw: s.to_string() }) } impl FromStr for IpPattern { @@ -494,6 +490,10 @@ impl PortRange { PortRange::new_unchecked(1, 65535) } + pub fn new_zero() -> Self { + PortRange { start: 0, end: 0 } + } + /// Create a new PortRange. /// /// The Portrange contains all ports between `start` and `end` inclusive. @@ -574,6 +574,7 @@ mod test { check("marzipan:80"); check("1.2.3.4:90-80"); + check("1.2.3.4:0-80"); check("1.2.3.4/100:8888"); check("[1.2.3.4]/16:80"); check("[::1]/130:8888"); @@ -612,6 +613,22 @@ mod test { check("0.0.0.0/0:*", &["127.0.0.1:80"], &["[f00b::]:80"]); check("[::]/0:*", &["[f00b::]:80"], &["127.0.0.1:80"]); + + check( + "*:0", + &["1.2.3.4:0", "[::1]:0", "9.0.0.0:0"], + &["1.2.3.4:443", "[::1]:500", "9.0.0.0:80", "[::1]:80"], + ); + check( + "*4:0", + &["1.2.3.4:0", "9.0.0.0:0"], + &["1.2.3.4:443", "9.0.0.0:80", "[::1]:0", "[::1]:80"], + ); + check( + "*6:0", + &["[::1]:0"], + &["[::1]:80", "1.2.3.4:0", "1.2.3.4:443"], + ); } #[test] @@ -620,6 +637,7 @@ mod test { policy.push(AddressPolicyAction::Accept, "*:443".parse()?); policy.push(AddressPolicyAction::Accept, "[::1]:80".parse()?); policy.push(AddressPolicyAction::Reject, "*:80".parse()?); + policy.push(AddressPolicyAction::Accept, "*:0".parse()?); let policy = policy; // drop mut assert!(policy @@ -640,6 +658,9 @@ mod test { assert!(policy .allows_sockaddr(&"127.0.0.1:66".parse().unwrap()) .is_none()); + assert!(policy + .allows_sockaddr(&"127.0.0.1:0".parse().unwrap()) + .unwrap()); Ok(()) } @@ -672,7 +693,6 @@ mod test { assert_eq!("*".parse::().unwrap(), PortRange::new_all()); assert!("hello".parse::().is_err()); - assert!("0".parse::().is_err()); assert!("65536".parse::().is_err()); assert!("65537".parse::().is_err()); assert!("1-2-3".parse::().is_err()); @@ -680,6 +700,9 @@ mod test { assert!("1-".parse::().is_err()); assert!("-2".parse::().is_err()); assert!("-".parse::().is_err()); + + assert_eq!("0".parse::().unwrap(), PortRange::new_zero(),); + assert!("0-1".parse::().is_err()); } #[test] diff --git a/common/ip-packet-requests/Cargo.toml b/common/ip-packet-requests/Cargo.toml new file mode 100644 index 0000000000..2a92715f82 --- /dev/null +++ b/common/ip-packet-requests/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "nym-ip-packet-requests" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1.3.3" +bytes = "1.5.0" +nym-sphinx = { path = "../nymsphinx" } +rand = "0.8.5" +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } diff --git a/common/ip-packet-requests/src/lib.rs b/common/ip-packet-requests/src/lib.rs new file mode 100644 index 0000000000..bbdcd12dc9 --- /dev/null +++ b/common/ip-packet-requests/src/lib.rs @@ -0,0 +1,383 @@ +use std::net::IpAddr; + +use nym_sphinx::addressing::clients::Recipient; +use serde::{Deserialize, Serialize}; + +pub const CURRENT_VERSION: u8 = 1; + +fn generate_random() -> u64 { + use rand::RngCore; + let mut rng = rand::rngs::OsRng; + rng.next_u64() +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketRequest { + pub version: u8, + pub data: IpPacketRequestData, +} + +impl IpPacketRequest { + pub fn new_static_connect_request( + ip: IpAddr, + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::StaticConnect(StaticConnectRequest { + request_id, + ip, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + }), + }, + request_id, + ) + } + + pub fn new_dynamic_connect_request( + reply_to: Recipient, + reply_to_hops: Option, + reply_to_avg_mix_delays: Option, + ) -> (Self, u64) { + let request_id = generate_random(); + ( + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::DynamicConnect(DynamicConnectRequest { + request_id, + reply_to, + reply_to_hops, + reply_to_avg_mix_delays, + }), + }, + request_id, + ) + } + + pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketRequestData::Data(DataRequest { ip_packet }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(request.request_id), + IpPacketRequestData::DynamicConnect(request) => Some(request.request_id), + IpPacketRequestData::Data(_) => None, + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketRequestData::StaticConnect(request) => Some(&request.reply_to), + IpPacketRequestData::DynamicConnect(request) => Some(&request.reply_to), + IpPacketRequestData::Data(_) => None, + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub enum IpPacketRequestData { + StaticConnect(StaticConnectRequest), + DynamicConnect(DynamicConnectRequest), + Data(DataRequest), +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct StaticConnectRequest { + pub request_id: u64, + pub ip: IpAddr, + // The nym-address the response should be sent back to + pub reply_to: Recipient, + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DynamicConnectRequest { + pub request_id: u64, + // The nym-address the response should be sent back to + pub reply_to: Recipient, + // The number of mix node hops that responses should take, in addition to the entry and exit + // node. Zero means only client -> entry -> exit -> client. + pub reply_to_hops: Option, + // The average delay at each mix node, in milliseconds. Currently this is not supported by the + // ip packet router. + pub reply_to_avg_mix_delays: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct DataRequest { + pub ip_packet: bytes::Bytes, +} + +// --- + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IpPacketResponse { + pub version: u8, + pub data: IpPacketResponseData, +} + +impl IpPacketResponse { + pub fn new_static_connect_success(request_id: u64, reply_to: Recipient) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Success, + }), + } + } + + pub fn new_static_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: StaticConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::StaticConnect(StaticConnectResponse { + request_id, + reply_to, + reply: StaticConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_dynamic_connect_success(request_id: u64, reply_to: Recipient, ip: IpAddr) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Success(DynamicConnectSuccess { ip }), + }), + } + } + + pub fn new_dynamic_connect_failure( + request_id: u64, + reply_to: Recipient, + reason: DynamicConnectFailureReason, + ) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::DynamicConnect(DynamicConnectResponse { + request_id, + reply_to, + reply: DynamicConnectResponseReply::Failure(reason), + }), + } + } + + pub fn new_ip_packet(ip_packet: bytes::Bytes) -> Self { + Self { + version: CURRENT_VERSION, + data: IpPacketResponseData::Data(DataResponse { ip_packet }), + } + } + + pub fn id(&self) -> Option { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(response.request_id), + IpPacketResponseData::DynamicConnect(response) => Some(response.request_id), + IpPacketResponseData::Data(_) => None, + } + } + + pub fn recipient(&self) -> Option<&Recipient> { + match &self.data { + IpPacketResponseData::StaticConnect(response) => Some(&response.reply_to), + IpPacketResponseData::DynamicConnect(response) => Some(&response.reply_to), + IpPacketResponseData::Data(_) => None, + } + } + + pub fn to_bytes(&self) -> Result, bincode::Error> { + use bincode::Options; + make_bincode_serializer().serialize(self) + } + + pub fn from_reconstructed_message( + message: &nym_sphinx::receiver::ReconstructedMessage, + ) -> Result { + use bincode::Options; + make_bincode_serializer().deserialize(&message.message) + } +} + +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum IpPacketResponseData { + StaticConnect(StaticConnectResponse), + DynamicConnect(DynamicConnectResponse), + Data(DataResponse), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct StaticConnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: StaticConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum StaticConnectResponseReply { + Success, + Failure(StaticConnectFailureReason), +} + +impl StaticConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + StaticConnectResponseReply::Success => true, + StaticConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum StaticConnectFailureReason { + #[error("requested ip address is already in use")] + RequestedIpAlreadyInUse, + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectResponse { + pub request_id: u64, + pub reply_to: Recipient, + pub reply: DynamicConnectResponseReply, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DynamicConnectResponseReply { + Success(DynamicConnectSuccess), + Failure(DynamicConnectFailureReason), +} + +impl DynamicConnectResponseReply { + pub fn is_success(&self) -> bool { + match self { + DynamicConnectResponseReply::Success(_) => true, + DynamicConnectResponseReply::Failure(_) => false, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DynamicConnectSuccess { + pub ip: IpAddr, +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +pub enum DynamicConnectFailureReason { + #[error("requested nym-address is already in use")] + RequestedNymAddressAlreadyInUse, + #[error("no available ip address")] + NoAvailableIp, + #[error("{0}")] + Other(String), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DataResponse { + pub ip_packet: bytes::Bytes, +} + +fn make_bincode_serializer() -> impl bincode::Options { + use bincode::Options; + bincode::DefaultOptions::new() + .with_big_endian() + .with_varint_encoding() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn check_size_of_request() { + let connect = IpPacketRequest { + version: 4, + data: IpPacketRequestData::StaticConnect( + StaticConnectRequest { + request_id: 123, + ip: IpAddr::from([10, 0, 0, 1]), + reply_to: Recipient::try_from_base58_string("D1rrpsysCGCYXy9saP8y3kmNpGtJZUXN9SvFoUcqAsM9.9Ssso1ea5NfkbMASdiseDSjTN1fSWda5SgEVjdSN4CvV@GJqd3ZxpXWSNxTfx7B1pPtswpetH4LnJdFeLeuY5KUuN").unwrap(), + reply_to_hops: None, + reply_to_avg_mix_delays: None, + }, + ) + }; + assert_eq!(connect.to_bytes().unwrap().len(), 107); + } + + #[test] + fn check_size_of_data() { + let data = IpPacketRequest { + version: 4, + data: IpPacketRequestData::Data(DataRequest { + ip_packet: bytes::Bytes::from(vec![1u8; 32]), + }), + }; + assert_eq!(data.to_bytes().unwrap().len(), 35); + } + + #[test] + fn serialize_and_deserialize_data_request() { + let data = IpPacketRequest { + version: 4, + data: IpPacketRequestData::Data(DataRequest { + ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }), + }; + + let serialized = data.to_bytes().unwrap(); + let deserialized = IpPacketRequest::from_reconstructed_message( + &nym_sphinx::receiver::ReconstructedMessage { + message: serialized, + sender_tag: None, + }, + ) + .unwrap(); + + assert_eq!(deserialized.version, 4); + assert_eq!( + deserialized.data, + IpPacketRequestData::Data(DataRequest { + ip_packet: bytes::Bytes::from(vec![1, 2, 4, 2, 5]), + }) + ); + } +} diff --git a/common/ledger/Cargo.toml b/common/ledger/Cargo.toml index dbbce3e920..b3a6bb6f95 100644 --- a/common/ledger/Cargo.toml +++ b/common/ledger/Cargo.toml @@ -2,6 +2,7 @@ name = "ledger" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -10,4 +11,4 @@ bip32 = "0.5.1" k256 = { workspace = true } ledger-transport = "0.10.0" ledger-transport-hid = "0.10.0" -thiserror = { workspace = true } \ No newline at end of file +thiserror = { workspace = true } diff --git a/common/mixnode-common/Cargo.toml b/common/mixnode-common/Cargo.toml index b2fcf6e2a3..9cf2c51cf0 100644 --- a/common/mixnode-common/Cargo.toml +++ b/common/mixnode-common/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-mixnode-common" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,7 +21,7 @@ tokio = { version = "1.24.1", features = [ "net", "io-util", ] } -tokio-util = { version = "0.7.4", features = ["codec"] } +tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true } thiserror = { workspace = true } diff --git a/common/mixnode-common/src/verloc/listener.rs b/common/mixnode-common/src/verloc/listener.rs index b91b7f50bc..fada769a76 100644 --- a/common/mixnode-common/src/verloc/listener.rs +++ b/common/mixnode-common/src/verloc/listener.rs @@ -108,13 +108,13 @@ impl ConnectionHandler { let reply_packet = match maybe_echo_packet { Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet), Some(Err(err)) => { - error!( + debug!( "The socket connection got corrupted with error: {err}. Closing the socket", ); return; } None => { - error!("The socket connection got terminated by the remote!"); + debug!("The socket connection got terminated by the remote!"); return; } }; @@ -125,7 +125,7 @@ impl ConnectionHandler { .write_all(reply_packet.to_bytes().as_ref()) .await { - error!( + debug!( "Failed to write reply packet back to the sender - {}. Closing the socket on our end", err ); diff --git a/common/network-defaults/src/lib.rs b/common/network-defaults/src/lib.rs index b45b5483e9..bcf02d1f02 100644 --- a/common/network-defaults/src/lib.rs +++ b/common/network-defaults/src/lib.rs @@ -476,3 +476,11 @@ pub const DEFAULT_NYM_NODE_HTTP_PORT: u16 = 8080; pub const TOTAL_SUPPLY: u128 = 1_000_000_000_000_000; pub const DEFAULT_PROFIT_MARGIN: u8 = 10; + +// WIREGUARD +pub const WG_PORT: u16 = 51822; + +// The interface used to route traffic +pub const WG_TUN_BASE_NAME: &str = "nymwg"; +pub const WG_TUN_DEVICE_ADDRESS: &str = "10.1.0.1"; +pub const WG_TUN_DEVICE_NETMASK: &str = "255.255.255.0"; diff --git a/common/network-defaults/src/mainnet.rs b/common/network-defaults/src/mainnet.rs index 236c9d9f1f..13e8f21024 100644 --- a/common/network-defaults/src/mainnet.rs +++ b/common/network-defaults/src/mainnet.rs @@ -3,6 +3,7 @@ use crate::var_names; use crate::{DenomDetails, ValidatorDetails}; +use std::str::FromStr; pub const NETWORK_NAME: &str = "mainnet"; @@ -60,6 +61,12 @@ pub fn read_var_if_not_default(var: &str) -> Option { } } +pub fn read_parsed_var_if_not_default(var: &str) -> Option> { + read_var_if_not_default(var) + .as_deref() + .map(FromStr::from_str) +} + pub fn export_to_env() { set_var_to_default(var_names::CONFIGURED, "true"); set_var_to_default(var_names::NETWORK_NAME, NETWORK_NAME); diff --git a/common/node-tester-utils/Cargo.toml b/common/node-tester-utils/Cargo.toml index d3e7c1cdf1..647453edfd 100644 --- a/common/node-tester-utils/Cargo.toml +++ b/common/node-tester-utils/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-node-tester-utils" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/node-tester-utils/src/tester.rs b/common/node-tester-utils/src/tester.rs index 6577ef61f5..dd88e1469d 100644 --- a/common/node-tester-utils/src/tester.rs +++ b/common/node-tester-utils/src/tester.rs @@ -258,6 +258,7 @@ where &address, &address, PacketType::Mix, + None, )?) } diff --git a/common/nonexhaustive-delayqueue/Cargo.toml b/common/nonexhaustive-delayqueue/Cargo.toml index f79284dd50..7551b56586 100644 --- a/common/nonexhaustive-delayqueue/Cargo.toml +++ b/common/nonexhaustive-delayqueue/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-nonexhaustive-delayqueue" version = "0.1.0" authors = ["Jędrzej Stuczyński "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/nymcoconut/Cargo.toml b/common/nymcoconut/Cargo.toml index 7aed2bb595..9984550071 100644 --- a/common/nymcoconut/Cargo.toml +++ b/common/nymcoconut/Cargo.toml @@ -3,11 +3,12 @@ name = "nym-coconut" version = "0.5.0" authors = ["Jedrzej Stuczynski ", "Ania Piotrowska "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["pairings", "alloc", "experimental"] } +bls12_381 = { workspace = true, default-features = false, features = ["pairings", "alloc", "experimental"] } itertools = "0.10" digest = "0.9" rand = "0.8" @@ -16,16 +17,17 @@ serde = { workspace = true } serde_derive = "1.0" bs58 = "0.4.0" sha2 = "0.9" +zeroize = { workspace = true, optional = true } nym-dkg = { path = "../dkg" } nym-pemstore = { path = "../pemstore" } [dependencies.ff] -version = "0.11" +workspace = true default-features = false [dependencies.group] -version = "0.11" +workspace = true default-features = false [dev-dependencies] @@ -38,7 +40,9 @@ name = "benchmarks" harness = false [features] +key-zeroize = ["zeroize", "bls12_381/zeroize"] default = [] + [target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies] getrandom = { version="0.2", features=["js"] } diff --git a/common/nymcoconut/benches/benchmarks.rs b/common/nymcoconut/benches/benchmarks.rs index 74149c15c9..6bf582a244 100644 --- a/common/nymcoconut/benches/benchmarks.rs +++ b/common/nymcoconut/benches/benchmarks.rs @@ -6,9 +6,10 @@ use criterion::{criterion_group, criterion_main, Criterion}; use ff::Field; use group::{Curve, Group}; use nym_coconut::{ - aggregate_signature_shares, aggregate_verification_keys, blind_sign, elgamal_keygen, - prepare_blind_sign, prove_bandwidth_credential, setup, ttp_keygen, verify_credential, - Attribute, BlindedSignature, Parameters, Signature, SignatureShare, VerificationKey, + aggregate_signature_shares, aggregate_verification_keys, blind_sign, prepare_blind_sign, + prove_bandwidth_credential, random_scalars_refs, setup, ttp_keygen, verify_credential, + verify_partial_blind_signature, Attribute, BlindedSignature, Parameters, Signature, + SignatureShare, VerificationKey, }; use rand::seq::SliceRandom; use std::ops::Neg; @@ -65,8 +66,8 @@ fn unblind_and_aggregate( params: &Parameters, blinded_signatures: &[BlindedSignature], partial_verification_keys: &[VerificationKey], - private_attributes: &[Attribute], - public_attributes: &[Attribute], + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], commitment_hash: &G1Projective, pedersen_commitments_openings: &[Scalar], verification_key: &VerificationKey, @@ -77,7 +78,7 @@ fn unblind_and_aggregate( .zip(partial_verification_keys.iter()) .map(|(signature, partial_verification_key)| { signature - .unblind( + .unblind_and_verify( params, partial_verification_key, private_attributes, @@ -170,12 +171,10 @@ fn bench_coconut(c: &mut Criterion) { let params = setup(case.num_public_attrs + case.num_private_attrs).unwrap(); - let public_attributes = params.n_random_scalars(case.num_public_attrs as usize); + random_scalars_refs!(public_attributes, params, case.num_public_attrs as usize); let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; - - let _elgamal_keypair = elgamal_keygen(¶ms); + let private_attributes = vec![&serial_number, &binding_number]; // The prepare blind sign is performed by the user let (pedersen_commitments_openings, blind_sign_request) = @@ -214,7 +213,7 @@ fn bench_coconut(c: &mut Criterion) { b.iter(|| { blind_sign( ¶ms, - &keypair.secret_key(), + keypair.secret_key(), &blind_sign_request, &public_attributes, ) @@ -229,7 +228,7 @@ fn bench_coconut(c: &mut Criterion) { for keypair in coconut_keypairs.iter() { let blinded_signature = blind_sign( ¶ms, - &keypair.secret_key(), + keypair.secret_key(), &blind_sign_request, &public_attributes, ) @@ -239,9 +238,32 @@ fn bench_coconut(c: &mut Criterion) { let verification_keys: Vec = coconut_keypairs .iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect(); + // verify a random partial blind signature + let rand_idx = 1; + let random_blind_signature = blinded_signatures.get(rand_idx).unwrap(); + let partial_verification_key = verification_keys.get(rand_idx).unwrap(); + + group.bench_function( + &format!( + "verify_partial_blind_signature_{}_private_attributes_{}_public_attributes", + case.num_private_attrs, case.num_public_attrs + ), + |b| { + b.iter(|| { + verify_partial_blind_signature( + ¶ms, + &blind_sign_request, + &public_attributes, + random_blind_signature, + partial_verification_key, + ) + }) + }, + ); + // Lets bench worse case, ie aggregating all let indices: Vec = (1..=case.num_authorities).collect(); // aggregate verification keys @@ -288,8 +310,8 @@ fn bench_coconut(c: &mut Criterion) { ¶ms, &aggr_verification_key, &aggregated_signature, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); @@ -307,8 +329,8 @@ fn bench_coconut(c: &mut Criterion) { ¶ms, &aggr_verification_key, &aggregated_signature, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap() }) diff --git a/common/nymcoconut/src/elgamal.rs b/common/nymcoconut/src/elgamal.rs index bf3895861e..3a1d473b2e 100644 --- a/common/nymcoconut/src/elgamal.rs +++ b/common/nymcoconut/src/elgamal.rs @@ -34,7 +34,10 @@ impl TryFrom<&[u8]> for Ciphertext { ))); } + // safety: we just checked for the length so the unwraps are fine + #[allow(clippy::unwrap_used)] let c1_bytes: &[u8; 48] = &bytes[..48].try_into().unwrap(); + #[allow(clippy::unwrap_used)] let c2_bytes: &[u8; 48] = &bytes[48..].try_into().unwrap(); let c1 = try_deserialize_g1_projective( @@ -112,7 +115,16 @@ impl Bytable for PrivateKey { } fn try_from_byte_slice(slice: &[u8]) -> Result { - PrivateKey::from_bytes(slice.try_into().unwrap()) + let received = slice.len(); + let Ok(arr) = slice.try_into() else { + return Err(CoconutError::UnexpectedArrayLength { + typ: "elgamal::PrivateKey".to_string(), + received, + expected: 32, + }); + }; + + PrivateKey::from_bytes(arr) } } @@ -145,21 +157,36 @@ impl PublicKey { } pub fn to_bytes(&self) -> [u8; 48] { - self.to_byte_vec().try_into().unwrap() + self.0.to_affine().to_compressed() } pub fn from_bytes(bytes: &[u8; 48]) -> Result { - Ok(PublicKey::try_from(bytes.to_vec().as_slice()).unwrap()) + try_deserialize_g1_projective( + bytes, + CoconutError::Deserialization( + "Failed to deserialize compressed ElGamal public key".to_string(), + ), + ) + .map(PublicKey) } } impl Bytable for PublicKey { fn to_byte_vec(&self) -> Vec { - self.0.to_affine().to_compressed().into() + self.to_bytes().into() } fn try_from_byte_slice(slice: &[u8]) -> Result { - Ok(PublicKey::from_bytes(slice.try_into().unwrap()).unwrap()) + let received = slice.len(); + let Ok(arr) = slice.try_into() else { + return Err(CoconutError::UnexpectedArrayLength { + typ: "elgamal::PublicKey".to_string(), + received, + expected: 48, + }); + }; + + PublicKey::from_bytes(arr) } } @@ -167,13 +194,7 @@ impl TryFrom<&[u8]> for PublicKey { type Error = CoconutError; fn try_from(slice: &[u8]) -> Result { - try_deserialize_g1_projective( - slice.try_into().unwrap(), - CoconutError::Deserialization( - "Failed to deserialize compressed ElGamal public key".to_string(), - ), - ) - .map(PublicKey) + PublicKey::try_from_byte_slice(slice) } } @@ -225,7 +246,7 @@ pub fn elgamal_keygen(params: &Parameters) -> ElGamalKeyPair { pub fn compute_attribute_encryption( params: &Parameters, - private_attributes: &[Attribute], + private_attributes: &[&Attribute], pub_key: &PublicKey, commitment_hash: G1Projective, ) -> (Vec, Vec) { diff --git a/common/nymcoconut/src/error.rs b/common/nymcoconut/src/error.rs index 7faf11c86e..d502c76c95 100644 --- a/common/nymcoconut/src/error.rs +++ b/common/nymcoconut/src/error.rs @@ -50,4 +50,20 @@ pub enum CoconutError { modulus: usize, object: String, }, + + #[error("received an array of unexpected size for deserialization of {typ}. got {received} but expected {expected}")] + UnexpectedArrayLength { + typ: String, + received: usize, + expected: usize, + }, + + #[error("failed to decode the base58 representation: {0}")] + Base58DecodingFailure(#[from] bs58::decode::Error), + + #[error("failed to deserialize scalar from the received bytes - it might not have been canonically encoded")] + ScalarDeserializationFailure, + + #[error("failed to deserialize G1Projective point from the received bytes - it might not have been canonically encoded")] + G1ProjectiveDeserializationFailure, } diff --git a/common/nymcoconut/src/lib.rs b/common/nymcoconut/src/lib.rs index a98b546fc1..4f0db30a15 100644 --- a/common/nymcoconut/src/lib.rs +++ b/common/nymcoconut/src/lib.rs @@ -1,11 +1,9 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use std::convert::TryInto; +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] -use bls12_381::Scalar; - -pub use crate::traits::Bytable; pub use elgamal::elgamal_keygen; pub use elgamal::ElGamalKeyPair; pub use elgamal::PublicKey; @@ -14,6 +12,7 @@ pub use scheme::aggregation::aggregate_signature_shares; pub use scheme::aggregation::aggregate_verification_keys; pub use scheme::issuance::blind_sign; pub use scheme::issuance::prepare_blind_sign; +pub use scheme::issuance::verify_partial_blind_signature; pub use scheme::issuance::BlindSignRequest; pub use scheme::keygen::ttp_keygen; pub use scheme::keygen::KeyPair; @@ -29,6 +28,7 @@ pub use scheme::BlindedSignature; pub use scheme::Signature; pub use scheme::SignatureShare; pub use traits::Base58; +pub use traits::Bytable; pub use utils::hash_to_scalar; pub mod elgamal; @@ -40,18 +40,6 @@ pub mod tests; mod traits; mod utils; -pub type Attribute = Scalar; +pub type Attribute = bls12_381::Scalar; pub type PrivateAttribute = Attribute; pub type PublicAttribute = Attribute; - -impl Bytable for Attribute { - fn to_byte_vec(&self) -> Vec { - self.to_bytes().to_vec() - } - - fn try_from_byte_slice(slice: &[u8]) -> Result { - Ok(Attribute::from_bytes(slice.try_into().unwrap()).unwrap()) - } -} - -impl Base58 for Attribute {} diff --git a/common/nymcoconut/src/proofs/mod.rs b/common/nymcoconut/src/proofs/mod.rs index 42f923466d..a7a5a97f79 100644 --- a/common/nymcoconut/src/proofs/mod.rs +++ b/common/nymcoconut/src/proofs/mod.rs @@ -91,8 +91,8 @@ impl ProofCmCs { commitment_opening: &Scalar, commitments: &[G1Projective], pedersen_commitments_openings: &[Scalar], - private_attributes: &[Attribute], - public_attributes: &[Attribute], + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], ) -> Self { // note: this is only called from `prepare_blind_sign` that already checks // whether private attributes are non-empty and whether we don't have too many @@ -162,11 +162,8 @@ impl ProofCmCs { &challenge, &pedersen_commitments_openings.iter().collect::>(), ); - let response_attributes = produce_responses( - &witness_attributes, - &challenge, - &private_attributes.iter().collect::>(), - ); + let response_attributes = + produce_responses(&witness_attributes, &challenge, private_attributes); ProofCmCs { challenge, @@ -181,7 +178,7 @@ impl ProofCmCs { params: &Parameters, commitment: &G1Projective, commitments: &[G1Projective], - public_attributes: &[Attribute], + public_attributes: &[&Attribute], ) -> bool { if self.response_attributes.len() != commitments.len() { return false; @@ -203,7 +200,7 @@ impl ProofCmCs { - public_attributes .iter() .zip(params.gen_hs().iter().skip(self.response_attributes.len())) - .map(|(pub_attr, hs)| hs * pub_attr) + .map(|(&pub_attr, hs)| hs * pub_attr) .sum::()) * self.challenge + g1 * self.response_opening @@ -280,8 +277,12 @@ impl ProofCmCs { } let mut idx = 0; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let challenge_bytes = bytes[idx..idx + 32].try_into().unwrap(); idx += 32; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let response_opening_bytes = bytes[idx..idx + 32].try_into().unwrap(); idx += 32; @@ -297,6 +298,8 @@ impl ProofCmCs { ), )?; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let ro_len = u64::from_le_bytes(bytes[idx..idx + 8].try_into().unwrap()); idx += 8; if bytes[idx..].len() < ro_len as usize * 32 + 8 { @@ -313,6 +316,8 @@ impl ProofCmCs { CoconutError::Deserialization("Failed to deserialize openings response".to_string()), )?; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let rm_len = u64::from_le_bytes(bytes[ro_end..ro_end + 8].try_into().unwrap()); let response_attributes = try_deserialize_scalar_vec( rm_len, @@ -462,7 +467,7 @@ impl ProofKappaZeta { pub(crate) fn from_bytes(bytes: &[u8]) -> Result { // at the very minimum there must be a single attribute being proven - if bytes.len() < 32 * 4 || (bytes.len()) % 32 != 0 { + if bytes.len() != 128 { return Err(CoconutError::DeserializationInvalidLength { actual: bytes.len(), modulus_target: bytes.len(), @@ -472,24 +477,32 @@ impl ProofKappaZeta { }); } + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let challenge_bytes = bytes[..32].try_into().unwrap(); let challenge = try_deserialize_scalar( &challenge_bytes, CoconutError::Deserialization("Failed to deserialize challenge".to_string()), )?; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let serial_number_bytes = &bytes[32..64].try_into().unwrap(); let response_serial_number = try_deserialize_scalar( serial_number_bytes, CoconutError::Deserialization("failed to deserialize the serial number".to_string()), )?; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let binding_number_bytes = &bytes[64..96].try_into().unwrap(); let response_binding_number = try_deserialize_scalar( binding_number_bytes, CoconutError::Deserialization("failed to deserialize the binding number".to_string()), )?; + // safety: bound checked + constant offset + #[allow(clippy::unwrap_used)] let blinder_bytes = bytes[96..].try_into().unwrap(); let response_blinder = try_deserialize_scalar( &blinder_bytes, @@ -512,14 +525,13 @@ impl ProofKappaZeta { #[cfg(test)] mod tests { - use group::Group; - use rand::thread_rng; - + use super::*; use crate::scheme::keygen::keygen; use crate::scheme::setup::setup; use crate::scheme::verification::{compute_kappa, compute_zeta}; - - use super::*; + use crate::tests::helpers::random_scalars_refs; + use group::Group; + use rand::thread_rng; #[test] fn proof_cm_cs_bytes_roundtrip() { @@ -530,7 +542,7 @@ mod tests { let r = params.random_scalar(); let cms: [G1Projective; 1] = [G1Projective::random(&mut rng)]; let rs = params.n_random_scalars(1); - let private_attributes = params.n_random_scalars(1); + random_scalars_refs!(private_attributes, params, 1); // 0 public 1 private let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); @@ -546,7 +558,7 @@ mod tests { G1Projective::random(&mut rng), ]; let rs = params.n_random_scalars(2); - let private_attributes = params.n_random_scalars(2); + random_scalars_refs!(private_attributes, params, 2); // 0 public 2 privates let pi_s = ProofCmCs::construct(¶ms, &cm, &r, &cms, &rs, &private_attributes, &[]); @@ -562,20 +574,20 @@ mod tests { let keypair = keygen(¶ms); // we don't care about 'correctness' of the proof. only whether we can correctly recover it from bytes - let serial_number = params.random_scalar(); - let binding_number = params.random_scalar(); + let serial_number = ¶ms.random_scalar(); + let binding_number = ¶ms.random_scalar(); let private_attributes = vec![serial_number, binding_number]; let r = params.random_scalar(); - let kappa = compute_kappa(¶ms, &keypair.verification_key(), &private_attributes, r); + let kappa = compute_kappa(¶ms, keypair.verification_key(), &private_attributes, r); let zeta = compute_zeta(¶ms, serial_number); // 0 public 2 private let pi_v = ProofKappaZeta::construct( ¶ms, - &keypair.verification_key(), - &serial_number, - &binding_number, + keypair.verification_key(), + serial_number, + binding_number, &r, &kappa, &zeta, @@ -592,9 +604,9 @@ mod tests { let pi_v = ProofKappaZeta::construct( ¶ms, - &keypair.verification_key(), - &serial_number, - &binding_number, + keypair.verification_key(), + serial_number, + binding_number, &r, &kappa, &zeta, diff --git a/common/nymcoconut/src/scheme/aggregation.rs b/common/nymcoconut/src/scheme/aggregation.rs index 499dca4e3f..3ff5f93bc2 100644 --- a/common/nymcoconut/src/scheme/aggregation.rs +++ b/common/nymcoconut/src/scheme/aggregation.rs @@ -50,7 +50,7 @@ where impl Aggregatable for PartialSignature { fn aggregate(sigs: &[PartialSignature], indices: Option<&[u64]>) -> Result { let h = sigs - .get(0) + .first() .ok_or_else(|| CoconutError::Aggregation("Empty set of signatures".to_string()))? .sig1(); @@ -83,7 +83,7 @@ pub fn aggregate_verification_keys( pub fn aggregate_signatures( params: &Parameters, verification_key: &VerificationKey, - attributes: &[Attribute], + attributes: &[&Attribute], signatures: &[PartialSignature], indices: Option<&[SignerIndex]>, ) -> Result { @@ -100,7 +100,7 @@ pub fn aggregate_signatures( let tmp = attributes .iter() .zip(verification_key.beta_g2.iter()) - .map(|(attr, beta_i)| beta_i * attr) + .map(|(&attr, beta_i)| beta_i * attr) .sum::(); if !check_bilinear_pairing( @@ -119,7 +119,7 @@ pub fn aggregate_signatures( pub fn aggregate_signature_shares( params: &Parameters, verification_key: &VerificationKey, - attributes: &[Attribute], + attributes: &[&Attribute], shares: &[SignatureShare], ) -> Result { let (signatures, indices): (Vec<_>, Vec<_>) = shares @@ -138,13 +138,13 @@ pub fn aggregate_signature_shares( #[cfg(test)] mod tests { - use bls12_381::G1Projective; - use group::Group; - use crate::scheme::issuance::sign; use crate::scheme::keygen::ttp_keygen; use crate::scheme::setup::Parameters; use crate::scheme::verification::verify; + use crate::tests::helpers::random_scalars_refs; + use bls12_381::G1Projective; + use group::Group; use super::*; @@ -155,7 +155,7 @@ mod tests { let vks = keypairs .into_iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect::>(); let aggr_vk1 = aggregate_verification_keys(&vks[..3], Some(&[1, 2, 3])).unwrap(); @@ -212,13 +212,18 @@ mod tests { #[test] fn signature_aggregation_works_for_any_subset_of_signatures() { let mut params = Parameters::new(2).unwrap(); - let attributes = params.n_random_scalars(2); + random_scalars_refs!(attributes, params, 2); let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); let (sks, vks): (Vec<_>, Vec<_>) = keypairs .into_iter() - .map(|keypair| (keypair.secret_key(), keypair.verification_key())) + .map(|keypair| { + ( + keypair.secret_key().clone(), + keypair.verification_key().clone(), + ) + }) .unzip(); let sigs = sks @@ -312,12 +317,17 @@ mod tests { fn signature_aggregation_doesnt_work_for_empty_set_of_signatures() { let signatures: Vec = vec![]; let params = Parameters::new(2).unwrap(); - let attributes = params.n_random_scalars(2); + random_scalars_refs!(attributes, params, 2); let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); let (_, vks): (Vec<_>, Vec<_>) = keypairs .into_iter() - .map(|keypair| (keypair.secret_key(), keypair.verification_key())) + .map(|keypair| { + ( + keypair.secret_key().clone(), + keypair.verification_key().clone(), + ) + }) .unzip(); let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); @@ -330,11 +340,16 @@ mod tests { fn signature_aggregation_doesnt_work_if_indices_have_invalid_length() { let signatures = vec![random_signature()]; let params = Parameters::new(2).unwrap(); - let attributes = params.n_random_scalars(2); + random_scalars_refs!(attributes, params, 2); let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); let (_, vks): (Vec<_>, Vec<_>) = keypairs .into_iter() - .map(|keypair| (keypair.secret_key(), keypair.verification_key())) + .map(|keypair| { + ( + keypair.secret_key().clone(), + keypair.verification_key().clone(), + ) + }) .unzip(); let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); @@ -356,11 +371,16 @@ mod tests { fn signature_aggregation_doesnt_work_for_non_unique_indices() { let signatures = vec![random_signature(), random_signature()]; let params = Parameters::new(2).unwrap(); - let attributes = params.n_random_scalars(2); + random_scalars_refs!(attributes, params, 2); let keypairs = ttp_keygen(¶ms, 3, 5).unwrap(); let (_, vks): (Vec<_>, Vec<_>) = keypairs .into_iter() - .map(|keypair| (keypair.secret_key(), keypair.verification_key())) + .map(|keypair| { + ( + keypair.secret_key().clone(), + keypair.verification_key().clone(), + ) + }) .unzip(); let aggr_vk_all = aggregate_verification_keys(&vks, None).unwrap(); diff --git a/common/nymcoconut/src/scheme/double_use.rs b/common/nymcoconut/src/scheme/double_use.rs index f952938a72..ce3cf58d67 100644 --- a/common/nymcoconut/src/scheme/double_use.rs +++ b/common/nymcoconut/src/scheme/double_use.rs @@ -25,6 +25,8 @@ impl TryFrom<&[u8]> for BlindedSerialNumber { )); } + // safety: we've just made a check for 96 bytes + #[allow(clippy::unwrap_used)] let inner = try_deserialize_g2_projective( &bytes.try_into().unwrap(), CoconutError::Deserialization( diff --git a/common/nymcoconut/src/scheme/issuance.rs b/common/nymcoconut/src/scheme/issuance.rs index 710fc41df9..11d0dcb35b 100644 --- a/common/nymcoconut/src/scheme/issuance.rs +++ b/common/nymcoconut/src/scheme/issuance.rs @@ -3,12 +3,14 @@ use std::convert::TryFrom; use std::convert::TryInto; +use std::ops::Neg; -use bls12_381::{G1Affine, G1Projective, Scalar}; -use group::{Curve, GroupEncoding}; +use bls12_381::{multi_miller_loop, G1Affine, G1Projective, G2Prepared, Scalar}; +use group::{Curve, Group, GroupEncoding}; use crate::error::{CoconutError, Result}; use crate::proofs::ProofCmCs; +use crate::scheme::keygen::VerificationKey; use crate::scheme::setup::Parameters; use crate::scheme::BlindedSignature; use crate::scheme::SecretKey; @@ -52,6 +54,8 @@ impl TryFrom<&[u8]> for BlindSignRequest { let commitment_bytes_len = 48; let commitment_hash_bytes_len = 48; + // safety: we made bound check and we're using constant offest + #[allow(clippy::unwrap_used)] let cm_bytes = bytes[..j + commitment_bytes_len].try_into().unwrap(); let commitment = try_deserialize_g1_projective( &cm_bytes, @@ -61,6 +65,8 @@ impl TryFrom<&[u8]> for BlindSignRequest { )?; j += commitment_bytes_len; + // safety: we made bound check and we're using constant offest + #[allow(clippy::unwrap_used)] let cm_hash_bytes = bytes[j..j + commitment_hash_bytes_len].try_into().unwrap(); let commitment_hash = try_deserialize_g1_projective( &cm_hash_bytes, @@ -70,6 +76,8 @@ impl TryFrom<&[u8]> for BlindSignRequest { )?; j += commitment_hash_bytes_len; + // safety: we made bound check and we're using constant offest + #[allow(clippy::unwrap_used)] let c_len = u64::from_le_bytes(bytes[j..j + 8].try_into().unwrap()); j += 8; if bytes[j..].len() < c_len as usize * 48 { @@ -84,6 +92,14 @@ impl TryFrom<&[u8]> for BlindSignRequest { let start = j + i * 48; let end = start + 48; + if bytes.len() < end { + return Err(CoconutError::Deserialization( + "Failed to deserialize compressed commitment".to_string(), + )); + } + + // safety: we made bound check and we're using constant offest + #[allow(clippy::unwrap_used)] let private_attributes_commitment_bytes = bytes[start..end].try_into().unwrap(); let private_attributes_commitment = try_deserialize_g1_projective( &private_attributes_commitment_bytes, @@ -135,7 +151,7 @@ impl Bytable for BlindSignRequest { impl Base58 for BlindSignRequest {} impl BlindSignRequest { - fn verify_proof(&self, params: &Parameters, public_attributes: &[Attribute]) -> bool { + fn verify_proof(&self, params: &Parameters, public_attributes: &[&Attribute]) -> bool { self.pi_s.verify( params, &self.commitment, @@ -148,8 +164,8 @@ impl BlindSignRequest { self.commitment_hash } - pub fn get_private_attributes_pedersen_commitments(&self) -> Vec { - self.private_attributes_commitments.clone() + pub fn get_private_attributes_pedersen_commitments(&self) -> &[G1Projective] { + &self.private_attributes_commitments } pub fn to_bytes(&self) -> Vec { @@ -159,12 +175,16 @@ impl BlindSignRequest { pub fn from_bytes(bytes: &[u8]) -> Result { BlindSignRequest::try_from(bytes) } + + pub fn num_private_attributes(&self) -> usize { + self.private_attributes_commitments.len() + } } pub fn compute_attributes_commitment( params: &Parameters, - private_attributes: &[Attribute], - public_attributes: &[Attribute], + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], hs: &[G1Affine], ) -> (Scalar, G1Projective) { let commitment_opening = params.random_scalar(); @@ -185,7 +205,7 @@ pub fn compute_attributes_commitment( pub fn compute_pedersen_commitments_for_private_attributes( params: &Parameters, - private_attributes: &[Attribute], + private_attributes: &[&Attribute], h: &G1Projective, ) -> (Vec, Vec) { // Generate openings for Pedersen commitment for each private attribute @@ -195,13 +215,13 @@ pub fn compute_pedersen_commitments_for_private_attributes( let pedersen_commitments = commitments_openings .iter() .zip(private_attributes.iter()) - .map(|(o_j, m_j)| params.gen1() * o_j + h * m_j) + .map(|(o_j, &m_j)| params.gen1() * o_j + h * m_j) .collect::>(); (commitments_openings, pedersen_commitments) } -pub fn compute_hash(commitment: G1Projective, public_attributes: &[Attribute]) -> G1Projective { +pub fn compute_hash(commitment: G1Projective, public_attributes: &[&Attribute]) -> G1Projective { let mut buff = Vec::new(); buff.extend_from_slice(commitment.to_bytes().as_ref()); for attr in public_attributes { @@ -213,8 +233,8 @@ pub fn compute_hash(commitment: G1Projective, public_attributes: &[Attribute]) - /// Builds cryptographic material required for blind sign. pub fn prepare_blind_sign( params: &Parameters, - private_attributes: &[Attribute], - public_attributes: &[Attribute], + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], ) -> Result<(Vec, BlindSignRequest)> { if private_attributes.is_empty() { return Err(CoconutError::Issuance( @@ -269,7 +289,7 @@ pub fn blind_sign( params: &Parameters, signing_secret_key: &SecretKey, blind_sign_request: &BlindSignRequest, - public_attributes: &[Attribute], + public_attributes: &[&Attribute], ) -> Result { let num_private = blind_sign_request.private_attributes_commitments.len(); let hs = params.gen_hs(); @@ -302,7 +322,7 @@ pub fn blind_sign( let signed_public = h * public_attributes .iter() .zip(signing_secret_key.ys.iter().skip(num_private)) - .map(|(attr, yi)| attr * yi) + .map(|(&attr, yi)| attr * yi) .sum::(); // h ^ x + c[0] ^ y[0] + ... c[m] ^ y[m] + h ^ (pub_m[0] * y[m + 1] + ... + pub_m[n] * y[m + n]) @@ -318,11 +338,102 @@ pub fn blind_sign( Ok(BlindedSignature(h, sig)) } +/// Verifies a partial blind signature using the provided parameters and validator's verification key. +/// +/// # Arguments +/// +/// * `params` - A reference to the cryptographic parameters. +/// * `blind_sign_request` - A reference to the blind signature request signed by the client. +/// * `public_attributes` - A reference to the public attributes included in the client's request. +/// * `blind_sig` - A reference to the issued partial blinded signature to be verified. +/// * `partial_verification_key` - A reference to the validator's partial verification key. +/// +/// # Returns +/// +/// A boolean indicating whether the partial blind signature is valid (`true`) or not (`false`). +/// +/// # Remarks +/// +/// This function verifies the correctness and validity of a partial blind signature using +/// the provided cryptographic parameters, blind signature request, blinded signature, +/// and partial verification key. +/// It calculates pairings based on the provided values and checks whether the partial blind signature +/// is consistent with the verification key and commitments in the blind signature request. +/// The function returns `true` if the partial blind signature is valid, and `false` otherwise. +pub fn verify_partial_blind_signature( + params: &Parameters, + blind_sign_request: &BlindSignRequest, + public_attributes: &[&Attribute], + blind_sig: &BlindedSignature, + partial_verification_key: &VerificationKey, +) -> bool { + let num_private_attributes = blind_sign_request.private_attributes_commitments.len(); + if num_private_attributes + public_attributes.len() > partial_verification_key.beta_g2.len() { + return false; + } + + // TODO: we're losing some memory here due to extra allocation, + // but worst-case scenario (given SANE amount of attributes), it's just few kb at most + let c_neg = blind_sig.1.to_affine().neg(); + let g2_prep = params.prepared_miller_g2(); + + let mut terms = vec![ + // (c^{-1}, g2) + (c_neg, g2_prep.clone()), + // (s, alpha) + ( + blind_sig.0.to_affine(), + G2Prepared::from(partial_verification_key.alpha.to_affine()), + ), + ]; + + // for each private attribute, add (cm_i, beta_i) to the miller terms + for (private_attr_commit, beta_g2) in blind_sign_request + .private_attributes_commitments + .iter() + .zip(&partial_verification_key.beta_g2) + { + // (cm_i, beta_i) + terms.push(( + private_attr_commit.to_affine(), + G2Prepared::from(beta_g2.to_affine()), + )) + } + + // for each public attribute, add (s^pub_j, beta_{priv + j}) to the miller terms + for (&pub_attr, beta_g2) in public_attributes.iter().zip( + partial_verification_key + .beta_g2 + .iter() + .skip(num_private_attributes), + ) { + // (s^pub_j, beta_j) + terms.push(( + (blind_sig.0 * pub_attr).to_affine(), + G2Prepared::from(beta_g2.to_affine()), + )) + } + + // get the references to all the terms to get the arguments the miller loop expects + #[allow(clippy::map_identity)] + let terms_refs = terms.iter().map(|(g1, g2)| (g1, g2)).collect::>(); + + // since checking whether e(a, b) == e(c, d) + // is equivalent to checking e(a, b) • e(c, d)^{-1} == id + // and thus to e(a, b) • e(c^{-1}, d) == id + // + // compute e(c^1, g2) • e(s, alpha) • e(cm_0, beta_0) • e(cm_i, beta_i) • (s^pub_0, beta_{i+1}) (s^pub_j, beta_{i + j}) + multi_miller_loop(&terms_refs) + .final_exponentiation() + .is_identity() + .into() +} + #[cfg(test)] pub fn sign( params: &mut Parameters, secret_key: &SecretKey, - public_attributes: &[Attribute], + public_attributes: &[&Attribute], ) -> Result { if public_attributes.len() > secret_key.ys.len() { return Err(CoconutError::IssuanceMaxAttributes { @@ -336,7 +447,7 @@ pub fn sign( // (the python implementation hashes string representation of all attributes onto the curve, // but I think the same can be achieved by just summing the attributes thus avoiding the unnecessary // transformation. If I'm wrong, please correct me.) - let attributes_sum = public_attributes.iter().sum::(); + let attributes_sum = public_attributes.iter().copied().sum::(); let h = hash_g1((params.gen1() * attributes_sum).to_bytes()); // x + m0 * y0 + m1 * y1 + ... mn * yn @@ -344,7 +455,7 @@ pub fn sign( + public_attributes .iter() .zip(secret_key.ys.iter()) - .map(|(m_i, y_i)| m_i * y_i) + .map(|(&m_i, y_i)| m_i * y_i) .sum::(); let sig2 = h * exponent; @@ -354,13 +465,15 @@ pub fn sign( #[cfg(test)] mod tests { use super::*; + use crate::scheme::keygen::keygen; + use crate::tests::helpers::random_scalars_refs; #[test] fn blind_sign_request_bytes_roundtrip() { // 0 public and 1 private attribute let params = Parameters::new(1).unwrap(); - let private_attributes = params.n_random_scalars(1); - let public_attributes = params.n_random_scalars(0); + random_scalars_refs!(private_attributes, params, 1); + random_scalars_refs!(public_attributes, params, 0); let (_commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); @@ -373,8 +486,8 @@ mod tests { // 2 public and 2 private attributes let params = Parameters::new(4).unwrap(); - let private_attributes = params.n_random_scalars(2); - let public_attributes = params.n_random_scalars(2); + random_scalars_refs!(private_attributes, params, 2); + random_scalars_refs!(public_attributes, params, 2); let (_commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); @@ -385,4 +498,80 @@ mod tests { lambda ); } + + #[test] + fn successful_verify_partial_blind_signature() { + let params = Parameters::new(4).unwrap(); + random_scalars_refs!(private_attributes, params, 2); + random_scalars_refs!(public_attributes, params, 2); + + let (_commitments_openings, request) = + prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); + + let validator_keypair = keygen(¶ms); + let blind_sig = blind_sign( + ¶ms, + validator_keypair.secret_key(), + &request, + &public_attributes, + ) + .unwrap(); + + assert!(verify_partial_blind_signature( + ¶ms, + &request, + &public_attributes, + &blind_sig, + validator_keypair.verification_key() + )); + } + + #[test] + fn successful_verify_partial_blind_signature_no_public_attributes() { + let params = Parameters::new(4).unwrap(); + random_scalars_refs!(private_attributes, params, 2); + + let (_commitments_openings, request) = + prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); + + let validator_keypair = keygen(¶ms); + let blind_sig = blind_sign(¶ms, validator_keypair.secret_key(), &request, &[]).unwrap(); + + assert!(verify_partial_blind_signature( + ¶ms, + &request, + &[], + &blind_sig, + validator_keypair.verification_key() + )); + } + + #[test] + fn fail_verify_partial_blind_signature_with_wrong_key() { + let params = Parameters::new(4).unwrap(); + random_scalars_refs!(private_attributes, params, 2); + random_scalars_refs!(public_attributes, params, 2); + + let (_commitments_openings, request) = + prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); + + let validator_keypair = keygen(¶ms); + let validator2_keypair = keygen(¶ms); + let blind_sig = blind_sign( + ¶ms, + validator_keypair.secret_key(), + &request, + &public_attributes, + ) + .unwrap(); + + // this assertion should fail, as we try to verify with a wrong validator key + assert!(!verify_partial_blind_signature( + ¶ms, + &request, + &public_attributes, + &blind_sig, + validator2_keypair.verification_key() + ),); + } } diff --git a/common/nymcoconut/src/scheme/keygen.rs b/common/nymcoconut/src/scheme/keygen.rs index 352d704193..d033d8f0be 100644 --- a/common/nymcoconut/src/scheme/keygen.rs +++ b/common/nymcoconut/src/scheme/keygen.rs @@ -23,8 +23,12 @@ use crate::utils::{ }; use crate::Base58; -#[derive(Debug, Clone)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Debug)] +#[cfg_attr(test, derive(PartialEq, Eq, Clone))] +#[cfg_attr( + feature = "key-zeroize", + derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop) +)] pub struct SecretKey { pub(crate) x: Scalar, pub(crate) ys: Vec, @@ -62,7 +66,9 @@ impl TryFrom<&[u8]> for SecretKey { } // this conversion will not fail as we are taking the same length of data + #[allow(clippy::unwrap_used)] let x_bytes: [u8; 32] = bytes[..32].try_into().unwrap(); + #[allow(clippy::unwrap_used)] let ys_len = u64::from_le_bytes(bytes[32..40].try_into().unwrap()); let actual_ys_len = (bytes.len() - 40) / 32; @@ -97,6 +103,10 @@ impl SecretKey { (self.x, self.ys.clone()) } + pub fn size(&self) -> usize { + self.ys.len() + } + /// Derive verification key using this secret key. pub fn verification_key(&self, params: &Parameters) -> VerificationKey { let g1 = params.gen1(); @@ -141,6 +151,10 @@ impl Base58 for SecretKey {} // TODO: perhaps change points to affine representation // to make verification slightly more efficient? #[derive(Debug, PartialEq, Eq, Clone)] +#[cfg_attr( + feature = "key-zeroize", + derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop) +)] pub struct VerificationKey { // TODO add gen2 as per the paper or imply it from the fact library is using bls381? pub(crate) alpha: G2Projective, @@ -180,7 +194,9 @@ impl TryFrom<&[u8]> for VerificationKey { } // this conversion will not fail as we are taking the same length of data + #[allow(clippy::unwrap_used)] let alpha_bytes: [u8; 96] = bytes[..96].try_into().unwrap(); + #[allow(clippy::unwrap_used)] let betas_len = u64::from_le_bytes(bytes[96..104].try_into().unwrap()); let actual_betas_len = (bytes.len() - 104) / (96 + 48); @@ -204,6 +220,8 @@ impl TryFrom<&[u8]> for VerificationKey { for i in 0..betas_len { let start = (104 + i * 48) as usize; let end = start + 48; + // we're using a constant 48 byte offset (which is the size of G1 compressed) so unwrap is fine + #[allow(clippy::unwrap_used)] let beta_i_bytes = bytes[start..end].try_into().unwrap(); let beta_i = try_deserialize_g1_projective( &beta_i_bytes, @@ -220,6 +238,8 @@ impl TryFrom<&[u8]> for VerificationKey { for i in 0..betas_len { let start = (beta_g1_end + i * 96) as usize; let end = start + 96; + // we're using a constant 96 byte offset (which is the size of G2 compressed) so unwrap is fine + #[allow(clippy::unwrap_used)] let beta_i_bytes = bytes[start..end].try_into().unwrap(); let beta_i = try_deserialize_g2_projective( &beta_i_bytes, @@ -392,7 +412,11 @@ impl Bytable for VerificationKey { impl Base58 for VerificationKey {} #[derive(Debug, Serialize, Deserialize)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[cfg_attr(test, derive(PartialEq, Eq, Clone))] +#[cfg_attr( + feature = "key-zeroize", + derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop) +)] pub struct KeyPair { secret_key: SecretKey, verification_key: VerificationKey, @@ -429,12 +453,12 @@ impl KeyPair { } } - pub fn secret_key(&self) -> SecretKey { - self.secret_key.clone() + pub fn secret_key(&self) -> &SecretKey { + &self.secret_key } - pub fn verification_key(&self) -> VerificationKey { - self.verification_key.clone() + pub fn verification_key(&self) -> &VerificationKey { + &self.verification_key } pub fn to_bytes(&self) -> Vec { @@ -488,6 +512,8 @@ impl TryFrom<&[u8]> for KeyPair { }); } + // safety: we made bound check and we're using constant offest + #[allow(clippy::unwrap_used)] let secret_key_len = u64::from_le_bytes(bytes[header_len..header_len + 8].try_into().unwrap()) as usize; let secret_key_start = header_len + 8; @@ -503,6 +529,8 @@ impl TryFrom<&[u8]> for KeyPair { }); } + // safety: we made bound check + #[allow(clippy::unwrap_used)] let verification_key_len = u64::from_le_bytes( bytes[secret_key_start + secret_key_len..secret_key_start + secret_key_len + 8] .try_into() @@ -515,6 +543,7 @@ impl TryFrom<&[u8]> for KeyPair { )?; let consumed_bytes = verification_key_start + verification_key_len; let index = if consumed_bytes < bytes.len() && [consumed_bytes..].len() == 8 { + #[allow(clippy::unwrap_used)] Some(u64::from_le_bytes( bytes[consumed_bytes..consumed_bytes + 8] .try_into() diff --git a/common/nymcoconut/src/scheme/mod.rs b/common/nymcoconut/src/scheme/mod.rs index 9e642716ca..68dd0d2b9a 100644 --- a/common/nymcoconut/src/scheme/mod.rs +++ b/common/nymcoconut/src/scheme/mod.rs @@ -44,7 +44,10 @@ impl TryFrom<&[u8]> for Signature { ))); } + // safety: we just checked for the length so the unwraps are fine + #[allow(clippy::expect_used)] let sig1_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); + #[allow(clippy::expect_used)] let sig2_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); let sig1 = try_deserialize_g1_projective( @@ -88,6 +91,45 @@ impl Signature { pub fn from_bytes(bytes: &[u8]) -> Result { Signature::try_from(bytes) } + + pub fn verify( + &self, + params: &Parameters, + partial_verification_key: &VerificationKey, + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], + commitment_hash: &G1Projective, + ) -> Result<()> { + // Verify the commitment hash + if !(commitment_hash == &self.0) { + return Err(CoconutError::Verification( + "Verification of commitment hash from signature failed".to_string(), + )); + } + + let alpha = partial_verification_key.alpha; + + let signed_attributes = private_attributes + .iter() + .chain(public_attributes.iter()) + .zip(partial_verification_key.beta_g2.iter()) + .map(|(&attr, beta_i)| beta_i * attr) + .sum::(); + + // Verify the signature share + if !check_bilinear_pairing( + &self.0.to_affine(), + &G2Prepared::from((alpha + signed_attributes).to_affine()), + &self.1.to_affine(), + params.prepared_miller_g2(), + ) { + return Err(CoconutError::Unblind( + "Verification of signature share failed".to_string(), + )); + } + + Ok(()) + } } impl Bytable for Signature { @@ -102,8 +144,7 @@ impl Bytable for Signature { impl Base58 for Signature {} -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq, Eq))] +#[derive(Debug, PartialEq, Eq)] pub struct BlindedSignature(G1Projective, G1Projective); impl Bytable for BlindedSignature { @@ -129,7 +170,10 @@ impl TryFrom<&[u8]> for BlindedSignature { ))); } + // safety: we just checked for the length so the unwraps are fine + #[allow(clippy::expect_used)] let h_bytes: &[u8; 48] = &bytes[..48].try_into().expect("Slice size != 48"); + #[allow(clippy::expect_used)] let sig_bytes: &[u8; 48] = &bytes[48..].try_into().expect("Slice size != 48"); let h = try_deserialize_g1_projective( @@ -148,24 +192,12 @@ impl TryFrom<&[u8]> for BlindedSignature { impl BlindedSignature { pub fn unblind( &self, - params: &Parameters, partial_verification_key: &VerificationKey, - private_attributes: &[Attribute], - public_attributes: &[Attribute], - commitment_hash: &G1Projective, pedersen_commitments_openings: &[Scalar], ) -> Result { // parse the signature let h = &self.0; let c = &self.1; - - // Verify the commitment hash - if !(commitment_hash == h) { - return Err(CoconutError::Unblind( - "Verification of commitment hash from signature failed".to_string(), - )); - } - let blinding_removers = partial_verification_key .beta_g1 .iter() @@ -175,30 +207,29 @@ impl BlindedSignature { let unblinded_c = c - blinding_removers; - let alpha = partial_verification_key.alpha; - - let signed_attributes = private_attributes - .iter() - .chain(public_attributes.iter()) - .zip(partial_verification_key.beta_g2.iter()) - .map(|(attr, beta_i)| beta_i * attr) - .sum::(); - - // Verify the signature share - if !check_bilinear_pairing( - &h.to_affine(), - &G2Prepared::from((alpha + signed_attributes).to_affine()), - &unblinded_c.to_affine(), - params.prepared_miller_g2(), - ) { - return Err(CoconutError::Unblind( - "Verification of signature share failed".to_string(), - )); - } - Ok(Signature(*h, unblinded_c)) } + pub fn unblind_and_verify( + &self, + params: &Parameters, + partial_verification_key: &VerificationKey, + private_attributes: &[&Attribute], + public_attributes: &[&Attribute], + commitment_hash: &G1Projective, + pedersen_commitments_openings: &[Scalar], + ) -> Result { + let unblinded = self.unblind(partial_verification_key, pedersen_commitments_openings)?; + unblinded.verify( + params, + partial_verification_key, + private_attributes, + public_attributes, + commitment_hash, + )?; + Ok(unblinded) + } + pub fn to_bytes(&self) -> [u8; 96] { let mut bytes = [0u8; 96]; bytes[..48].copy_from_slice(&self.0.to_affine().to_compressed()); @@ -237,25 +268,25 @@ impl SignatureShare { #[cfg(test)] mod tests { + use super::*; use crate::hash_to_scalar; use crate::scheme::aggregation::{aggregate_signatures, aggregate_verification_keys}; use crate::scheme::issuance::{blind_sign, compute_hash, prepare_blind_sign, sign}; use crate::scheme::keygen::{keygen, ttp_keygen}; use crate::scheme::verification::{prove_bandwidth_credential, verify, verify_credential}; - - use super::*; + use crate::tests::helpers::random_scalars_refs; #[test] fn unblind_returns_error_if_integrity_check_on_commitment_hash_fails() { let params = Parameters::new(2).unwrap(); - let private_attributes = params.n_random_scalars(2_usize); + random_scalars_refs!(private_attributes, params, 2); let (_commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); let keypair1 = keygen(¶ms); - let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[]).unwrap(); + let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); let wrong_commitment_opening = params.random_scalar(); let wrong_commitment = params.gen1() * wrong_commitment_opening; @@ -263,9 +294,9 @@ mod tests { let wrong_commitments_openings = params.n_random_scalars(private_attributes.len()); assert!(sig1 - .unblind( + .unblind_and_verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &private_attributes, &[], &fake_commitment_hash, @@ -277,20 +308,23 @@ mod tests { #[test] fn unblind_returns_error_if_signature_verification_fails() { let params = Parameters::new(2).unwrap(); - let private_attributes = vec![hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")]; - let private_attributes2 = vec![hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")]; + let p = [hash_to_scalar("Attribute1"), hash_to_scalar("Attribute2")]; + let private_attributes = vec![&p[0], &p[1]]; + + let p2 = [hash_to_scalar("Attribute3"), hash_to_scalar("Attribute4")]; + let private_attributes2 = vec![&p2[0], &p2[1]]; let (commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); let keypair1 = keygen(¶ms); - let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[]).unwrap(); + let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]).unwrap(); assert!(sig1 - .unblind( + .unblind_and_verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &private_attributes2, &[], &lambda.get_commitment_hash(), @@ -304,7 +338,7 @@ mod tests { let params = Parameters::new(2).unwrap(); let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; + let private_attributes = vec![&serial_number, &binding_number]; let keypair1 = keygen(¶ms); let keypair2 = keygen(¶ms); @@ -312,11 +346,11 @@ mod tests { let (commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &[]).unwrap(); - let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &[]) + let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &[]) .unwrap() - .unblind( + .unblind_and_verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &private_attributes, &[], &lambda.get_commitment_hash(), @@ -324,11 +358,11 @@ mod tests { ) .unwrap(); - let sig2 = blind_sign(¶ms, &keypair2.secret_key(), &lambda, &[]) + let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &[]) .unwrap() - .unblind( + .unblind_and_verify( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &private_attributes, &[], &lambda.get_commitment_hash(), @@ -338,39 +372,39 @@ mod tests { let theta1 = prove_bandwidth_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &sig1, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); let theta2 = prove_bandwidth_credential( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &sig2, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); assert!(verify_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &theta1, &[], )); assert!(verify_credential( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &theta2, &[], )); assert!(!verify_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &theta2, &[], )); @@ -379,30 +413,30 @@ mod tests { #[test] fn verification_on_two_public_attributes() { let mut params = Parameters::new(2).unwrap(); - let attributes = params.n_random_scalars(2); + random_scalars_refs!(attributes, params, 2); let keypair1 = keygen(¶ms); let keypair2 = keygen(¶ms); - let sig1 = sign(&mut params, &keypair1.secret_key(), &attributes).unwrap(); - let sig2 = sign(&mut params, &keypair2.secret_key(), &attributes).unwrap(); + let sig1 = sign(&mut params, keypair1.secret_key(), &attributes).unwrap(); + let sig2 = sign(&mut params, keypair2.secret_key(), &attributes).unwrap(); assert!(verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &attributes, &sig1, )); assert!(!verify( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &attributes, &sig1, )); assert!(!verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &attributes, &sig2, )); @@ -411,10 +445,11 @@ mod tests { #[test] fn verification_on_two_public_and_two_private_attributes() { let params = Parameters::new(4).unwrap(); - let public_attributes = params.n_random_scalars(2); + random_scalars_refs!(public_attributes, params, 2); + let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; + let private_attributes = vec![&serial_number, &binding_number]; let keypair1 = keygen(¶ms); let keypair2 = keygen(¶ms); @@ -422,11 +457,11 @@ mod tests { let (commitments_openings, lambda) = prepare_blind_sign(¶ms, &private_attributes, &public_attributes).unwrap(); - let sig1 = blind_sign(¶ms, &keypair1.secret_key(), &lambda, &public_attributes) + let sig1 = blind_sign(¶ms, keypair1.secret_key(), &lambda, &public_attributes) .unwrap() - .unblind( + .unblind_and_verify( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &private_attributes, &public_attributes, &lambda.get_commitment_hash(), @@ -434,11 +469,11 @@ mod tests { ) .unwrap(); - let sig2 = blind_sign(¶ms, &keypair2.secret_key(), &lambda, &public_attributes) + let sig2 = blind_sign(¶ms, keypair2.secret_key(), &lambda, &public_attributes) .unwrap() - .unblind( + .unblind_and_verify( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &private_attributes, &public_attributes, &lambda.get_commitment_hash(), @@ -448,39 +483,39 @@ mod tests { let theta1 = prove_bandwidth_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &sig1, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); let theta2 = prove_bandwidth_credential( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &sig2, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); assert!(verify_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &theta1, &public_attributes, )); assert!(verify_credential( ¶ms, - &keypair2.verification_key(), + keypair2.verification_key(), &theta2, &public_attributes, )); assert!(!verify_credential( ¶ms, - &keypair1.verification_key(), + keypair1.verification_key(), &theta2, &public_attributes, )); @@ -489,10 +524,11 @@ mod tests { #[test] fn verification_on_two_public_and_two_private_attributes_from_two_signers() { let params = Parameters::new(4).unwrap(); - let public_attributes = params.n_random_scalars(2); + random_scalars_refs!(public_attributes, params, 2); + let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; + let private_attributes = vec![&serial_number, &binding_number]; let keypairs = ttp_keygen(¶ms, 2, 3).unwrap(); @@ -502,11 +538,11 @@ mod tests { let sigs = keypairs .iter() .map(|keypair| { - blind_sign(¶ms, &keypair.secret_key(), &lambda, &public_attributes) + blind_sign(¶ms, keypair.secret_key(), &lambda, &public_attributes) .unwrap() - .unblind( + .unblind_and_verify( ¶ms, - &keypair.verification_key(), + keypair.verification_key(), &private_attributes, &public_attributes, &lambda.get_commitment_hash(), @@ -518,7 +554,7 @@ mod tests { let vks = keypairs .into_iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect::>(); let mut attributes = Vec::with_capacity(private_attributes.len() + public_attributes.len()); @@ -530,9 +566,14 @@ mod tests { aggregate_signatures(¶ms, &aggr_vk, &attributes, &sigs[..2], Some(&[1, 2])) .unwrap(); - let theta = - prove_bandwidth_credential(¶ms, &aggr_vk, &aggr_sig, serial_number, binding_number) - .unwrap(); + let theta = prove_bandwidth_credential( + ¶ms, + &aggr_vk, + &aggr_sig, + &serial_number, + &binding_number, + ) + .unwrap(); assert!(verify_credential( ¶ms, @@ -547,9 +588,14 @@ mod tests { aggregate_signatures(¶ms, &aggr_vk, &attributes, &sigs[1..], Some(&[2, 3])) .unwrap(); - let theta = - prove_bandwidth_credential(¶ms, &aggr_vk, &aggr_sig, serial_number, binding_number) - .unwrap(); + let theta = prove_bandwidth_credential( + ¶ms, + &aggr_vk, + &aggr_sig, + &serial_number, + &binding_number, + ) + .unwrap(); assert!(verify_credential( ¶ms, diff --git a/common/nymcoconut/src/scheme/verification.rs b/common/nymcoconut/src/scheme/verification.rs index 96ef28e9ca..0addda985a 100644 --- a/common/nymcoconut/src/scheme/verification.rs +++ b/common/nymcoconut/src/scheme/verification.rs @@ -43,6 +43,8 @@ impl TryFrom<&[u8]> for Theta { )); } + // safety: we just checked for the length so the unwraps are fine + #[allow(clippy::unwrap_used)] let blinded_message_bytes = bytes[..96].try_into().unwrap(); let blinded_message = try_deserialize_g2_projective( &blinded_message_bytes, @@ -51,6 +53,8 @@ impl TryFrom<&[u8]> for Theta { ), )?; + // safety: we just checked for the length so the unwraps are fine + #[allow(clippy::unwrap_used)] let blinded_serial_number_bytes = bytes[96..192].try_into().unwrap(); let blinded_serial_number = try_deserialize_g2_projective( &blinded_serial_number_bytes, @@ -130,7 +134,7 @@ impl Base58 for Theta {} pub fn compute_kappa( params: &Parameters, verification_key: &VerificationKey, - private_attributes: &[Attribute], + private_attributes: &[&Attribute], blinding_factor: Scalar, ) -> G2Projective { params.gen2() * blinding_factor @@ -138,11 +142,11 @@ pub fn compute_kappa( + private_attributes .iter() .zip(verification_key.beta_g2.iter()) - .map(|(priv_attr, beta_i)| beta_i * priv_attr) + .map(|(&priv_attr, beta_i)| beta_i * priv_attr) .sum::() } -pub fn compute_zeta(params: &Parameters, serial_number: Attribute) -> G2Projective { +pub fn compute_zeta(params: &Parameters, serial_number: &Attribute) -> G2Projective { params.gen2() * serial_number } @@ -150,8 +154,8 @@ pub fn prove_bandwidth_credential( params: &Parameters, verification_key: &VerificationKey, signature: &Signature, - serial_number: Attribute, - binding_number: Attribute, + serial_number: &Attribute, + binding_number: &Attribute, ) -> Result { if verification_key.beta_g2.len() < 2 { return Err( @@ -171,7 +175,7 @@ pub fn prove_bandwidth_credential( // Thus, we need kappa which allows us to verify sigma'. In particular, // kappa is computed on m as input, but thanks to the use or random value r, // it does not reveal any information about m. - let private_attributes = vec![serial_number, binding_number]; + let private_attributes = [serial_number, binding_number]; let blinded_message = compute_kappa( params, verification_key, @@ -185,8 +189,8 @@ pub fn prove_bandwidth_credential( let pi_v = ProofKappaZeta::construct( params, verification_key, - &serial_number, - &binding_number, + serial_number, + binding_number, &sign_blinding_factor, &blinded_message, &blinded_serial_number, @@ -221,7 +225,10 @@ pub fn check_vk_pairing( if values_len == 0 || values_len - 1 != vk.beta_g1.len() || values_len - 1 != vk.beta_g2.len() { return false; } - if vk.alpha != *dkg_values.last().unwrap() { + + // safety: we made an explicit check for if the length of the slice is 0, thus unwrap here is fine + #[allow(clippy::unwrap_used)] + if &vk.alpha != *dkg_values.last().as_ref().unwrap() { return false; } if dkg_values @@ -249,7 +256,7 @@ pub fn verify_credential( params: &Parameters, verification_key: &VerificationKey, theta: &Theta, - public_attributes: &[Attribute], + public_attributes: &[&Attribute], ) -> bool { if public_attributes.len() + theta.pi_v.private_attributes_len() > verification_key.beta_g2.len() @@ -272,7 +279,7 @@ pub fn verify_credential( .iter() .skip(theta.pi_v.private_attributes_len()), ) - .map(|(pub_attr, beta_i)| beta_i * pub_attr) + .map(|(&pub_attr, beta_i)| beta_i * pub_attr) .sum::(); theta.blinded_message + signed_public_attributes @@ -291,14 +298,14 @@ pub fn verify_credential( pub fn verify( params: &Parameters, verification_key: &VerificationKey, - public_attributes: &[Attribute], + public_attributes: &[&Attribute], sig: &Signature, ) -> bool { let kappa = (verification_key.alpha + public_attributes .iter() .zip(verification_key.beta_g2.iter()) - .map(|(m_i, b_i)| b_i * m_i) + .map(|(&m_i, b_i)| b_i * m_i) .sum::()) .to_affine(); @@ -320,10 +327,11 @@ mod tests { #[test] fn vk_pairing() { let params = setup(2).unwrap(); - let vk = keygen(¶ms).verification_key(); + let keypair = keygen(¶ms); + let vk = keypair.verification_key(); let mut dkg_values = vk.beta_g2.clone(); dkg_values.push(vk.alpha); - assert!(check_vk_pairing(¶ms, &dkg_values, &vk)); + assert!(check_vk_pairing(¶ms, &dkg_values, vk)); } #[test] @@ -340,10 +348,10 @@ mod tests { let theta = prove_bandwidth_credential( ¶ms, - &keypair.verification_key(), + keypair.verification_key(), &signature, - serial_number, - binding_number, + &serial_number, + &binding_number, ) .unwrap(); diff --git a/common/nymcoconut/src/tests/e2e.rs b/common/nymcoconut/src/tests/e2e.rs index db950d0c3f..b9cc397845 100644 --- a/common/nymcoconut/src/tests/e2e.rs +++ b/common/nymcoconut/src/tests/e2e.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::random_scalars_refs; use crate::tests::helpers::tests::generate_dkg_keys; use crate::{ aggregate_verification_keys, setup, tests::helpers::*, ttp_keygen, verify_credential, @@ -12,14 +13,14 @@ fn keygen() -> Result<(), CoconutError> { let params = setup(5)?; let node_indices = vec![15u64, 248, 33521]; - let public_attributes = params.n_random_scalars(2); + random_scalars_refs!(public_attributes, params, 2); // generate_keys let coconut_keypairs = ttp_keygen(¶ms, 2, 3)?; let verification_keys: Vec = coconut_keypairs .iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect(); // aggregate verification keys @@ -50,14 +51,14 @@ fn dkg() -> Result<(), CoconutError> { let params = setup(5)?; let node_indices = vec![15u64, 248, 33521]; - let public_attributes = params.n_random_scalars(2); + random_scalars_refs!(public_attributes, params, 2); // generate_keys let coconut_keypairs = generate_dkg_keys(5, &node_indices); let verification_keys: Vec = coconut_keypairs .iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect(); // aggregate verification keys diff --git a/common/nymcoconut/src/tests/helpers.rs b/common/nymcoconut/src/tests/helpers.rs index a432cecd60..e0f58ba33b 100644 --- a/common/nymcoconut/src/tests/helpers.rs +++ b/common/nymcoconut/src/tests/helpers.rs @@ -5,15 +5,17 @@ use crate::*; use itertools::izip; use std::fmt::Debug; +// unwraps are fine in the test code +#[allow(clippy::unwrap_used)] pub fn theta_from_keys_and_attributes( params: &Parameters, coconut_keypairs: &Vec, indices: &[scheme::SignerIndex], - public_attributes: &Vec, + public_attributes: &[&PublicAttribute], ) -> Result { let serial_number = params.random_scalar(); let binding_number = params.random_scalar(); - let private_attributes = vec![serial_number, binding_number]; + let private_attributes = vec![&serial_number, &binding_number]; // generate commitment let (commitments_openings, blind_sign_request) = @@ -21,7 +23,7 @@ pub fn theta_from_keys_and_attributes( let verification_keys: Vec = coconut_keypairs .iter() - .map(|keypair| keypair.verification_key()) + .map(|keypair| keypair.verification_key().clone()) .collect(); // aggregate verification keys @@ -33,7 +35,7 @@ pub fn theta_from_keys_and_attributes( for keypair in coconut_keypairs { let blinded_signature = blind_sign( params, - &keypair.secret_key(), + keypair.secret_key(), &blind_sign_request, public_attributes, )?; @@ -49,7 +51,7 @@ pub fn theta_from_keys_and_attributes( .map(|(idx, s, vk)| { ( *idx, - s.unblind( + s.unblind_and_verify( params, vk, &private_attributes, @@ -81,13 +83,15 @@ pub fn theta_from_keys_and_attributes( params, &verification_key, &signature, - serial_number, - binding_number, + &serial_number, + &binding_number, )?; Ok(theta) } +// unwraps are fine in the test code +#[allow(clippy::unwrap_used)] pub fn transpose_matrix(matrix: Vec>) -> Vec> { if matrix.is_empty() { return vec![]; @@ -104,6 +108,17 @@ pub fn transpose_matrix(matrix: Vec>) -> Vec> { .collect::>() } +#[macro_export] +macro_rules! random_scalars_refs { + ( $x: ident, $params: expr, $n: expr ) => { + let _vec = $params.n_random_scalars($n); + #[allow(clippy::map_identity)] + let $x = _vec.iter().collect::>(); + }; +} + +pub use random_scalars_refs; + #[cfg(test)] pub mod tests { use super::*; diff --git a/common/nymcoconut/src/traits.rs b/common/nymcoconut/src/traits.rs index a800388f88..d521b133ac 100644 --- a/common/nymcoconut/src/traits.rs +++ b/common/nymcoconut/src/traits.rs @@ -1,4 +1,13 @@ +// Copyright 2021-2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + use crate::CoconutError; +use bls12_381::{G1Affine, G1Projective, Scalar}; +use group::GroupEncoding; +use std::convert::TryInto; pub trait Bytable where @@ -14,9 +23,67 @@ where Self: Bytable, { fn try_from_bs58>(x: S) -> Result { - Self::try_from_byte_slice(&bs58::decode(x.as_ref()).into_vec().unwrap()) + let bs58_decoded = &bs58::decode(x.as_ref()).into_vec()?; + Self::try_from_byte_slice(bs58_decoded) } fn to_bs58(&self) -> String { bs58::encode(self.to_byte_vec()).into_string() } } + +impl Bytable for Scalar { + fn to_byte_vec(&self) -> Vec { + self.to_bytes().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + let received = slice.len(); + let Ok(arr) = slice.try_into() else { + return Err(CoconutError::UnexpectedArrayLength { + typ: "Scalar".to_string(), + received, + expected: 32, + }); + }; + + let maybe_scalar = Scalar::from_bytes(arr); + if maybe_scalar.is_none().into() { + Err(CoconutError::ScalarDeserializationFailure) + } else { + // safety: this unwrap is fine as we've just checked the element is not none + #[allow(clippy::unwrap_used)] + Ok(maybe_scalar.unwrap()) + } + } +} + +impl Base58 for Scalar {} + +impl Bytable for G1Projective { + fn to_byte_vec(&self) -> Vec { + self.to_bytes().as_ref().to_vec() + } + + fn try_from_byte_slice(slice: &[u8]) -> Result { + let received = slice.len(); + let arr: Result<[u8; 48], _> = slice.try_into(); + let Ok(bytes) = arr else { + return Err(CoconutError::UnexpectedArrayLength { + typ: "G1Projective".to_string(), + received, + expected: 48, + }); + }; + + let maybe_g1 = G1Affine::from_compressed(&bytes); + if maybe_g1.is_none().into() { + Err(CoconutError::G1ProjectiveDeserializationFailure) + } else { + // safety: this unwrap is fine as we've just checked the element is not none + #[allow(clippy::unwrap_used)] + Ok(maybe_g1.unwrap().into()) + } + } +} + +impl Base58 for G1Projective {} diff --git a/common/nymcoconut/src/utils.rs b/common/nymcoconut/src/utils.rs index 9fec3b334c..c2fcd4da3f 100644 --- a/common/nymcoconut/src/utils.rs +++ b/common/nymcoconut/src/utils.rs @@ -34,6 +34,7 @@ impl Polynomial { // just return the last term of the polynomial } else if x.is_zero().into() { // we checked that coefficients are not empty so unwrap here is fine + #[allow(clippy::unwrap_used)] *self.coefficients.first().unwrap() } else { self.coefficients @@ -148,6 +149,8 @@ pub(crate) fn try_deserialize_scalar_vec( let mut out = Vec::with_capacity(expected_len as usize); for i in 0..expected_len as usize { + // we just checked we have exactly the amount of bytes we need and thus the unwrap is fine + #[allow(clippy::unwrap_used)] let s_bytes = bytes[i * 32..(i + 1) * 32].try_into().unwrap(); let s = match Into::>::into(Scalar::from_bytes(&s_bytes)) { None => return Err(err), diff --git a/common/nymsphinx/framing/Cargo.toml b/common/nymsphinx/framing/Cargo.toml index bf2c030e2e..b62de91f53 100644 --- a/common/nymsphinx/framing/Cargo.toml +++ b/common/nymsphinx/framing/Cargo.toml @@ -9,7 +9,7 @@ repository = { workspace = true } [dependencies] bytes = "1.0" -tokio-util = { version = "0.7.4", features = ["codec"] } +tokio-util = { workspace = true, features = ["codec"] } thiserror = { workspace = true } nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index e6892f773a..f6e19dec7d 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -181,6 +181,7 @@ pub trait FragmentPreparer { /// - compute vk_b = g^x || v_b /// - compute sphinx_plaintext = SURB_ACK || g^x || v_b /// - compute sphinx_packet = Sphinx(recipient, sphinx_plaintext) + #[allow(clippy::too_many_arguments)] fn prepare_chunk_for_sending( &mut self, fragment: Fragment, @@ -189,6 +190,7 @@ pub trait FragmentPreparer { packet_sender: &Recipient, packet_recipient: &Recipient, packet_type: PacketType, + mix_hops: Option, ) -> Result { // each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient // could perform diffie-hellman with its own keys followed by a kdf to re-derive @@ -226,7 +228,8 @@ pub trait FragmentPreparer { }; // generate pseudorandom route for the packet - let hops = self.num_mix_hops(); + let hops = mix_hops.unwrap_or(self.num_mix_hops()); + log::trace!("Preparing chunk for sending with {} mix hops", hops); let route = topology.random_route_to_gateway(self.rng(), hops, packet_recipient.gateway())?; let destination = packet_recipient.as_sphinx_destination(); @@ -389,6 +392,7 @@ where ack_key: &AckKey, packet_recipient: &Recipient, packet_type: PacketType, + mix_hops: Option, ) -> Result { let sender = self.sender_address; @@ -400,6 +404,7 @@ where &sender, packet_recipient, packet_type, + mix_hops, ) } diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index cbf3db277c..742de7c85e 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -169,6 +169,10 @@ pub struct SphinxMessageReceiver { impl SphinxMessageReceiver { /// Allows setting non-default number of expected mix hops in the network. + // IMPORTANT NOTE: this is among others used to deserialize SURBs. Meaning that this is a + // global setting and currently always set to the default value. The implication is that it is + // not currently possible to have different number of hops for different SURB messages. So, + // don't try to use <3 mix hops for SURBs until this is refactored. #[must_use] pub fn with_mix_hops(mut self, hops: u8) -> Self { self.num_mix_hops = hops; diff --git a/common/nyxd-scraper/Cargo.toml b/common/nyxd-scraper/Cargo.toml new file mode 100644 index 0000000000..772ab8e7b3 --- /dev/null +++ b/common/nyxd-scraper/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "nyxd-scraper" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +async-trait.workspace = true +const_format = "0.2.32" +cosmrs.workspace = true +eyre = "0.6.9" +futures.workspace = true +sha2 = "0.10.8" +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "time"] } +tendermint.workspace = true +tendermint-rpc = { workspace = true, features = ["websocket-client", "http-client"] } +thiserror.workspace = true +tokio = { workspace = true, features = ["full"] } +tokio-stream = "0.1.14" +tokio-util = { version = "0.7.10", features = ["rt"]} +tracing.workspace = true +url.workspace = true + + +# TEMP +nym-bin-common = { path = "../bin-common", features = ["basic_tracing"]} + + +[build-dependencies] +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/common/nyxd-scraper/build.rs b/common/nyxd-scraper/build.rs new file mode 100644 index 0000000000..cfe3b9c079 --- /dev/null +++ b/common/nyxd-scraper/build.rs @@ -0,0 +1,28 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#[tokio::main] +async fn main() { + use sqlx::{Connection, SqliteConnection}; + use std::env; + + let out_dir = env::var("OUT_DIR").unwrap(); + let database_path = format!("{out_dir}/scraper-example.sqlite"); + + let mut conn = SqliteConnection::connect(&format!("sqlite://{database_path}?mode=rwc")) + .await + .expect("Failed to create SQLx database connection"); + + sqlx::migrate!("./sql_migrations") + .run(&mut conn) + .await + .expect("Failed to perform SQLx migrations"); + + #[cfg(target_family = "unix")] + println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path); + + #[cfg(target_family = "windows")] + // for some strange reason we need to add a leading `/` to the windows path even though it's + // not a valid windows path... but hey, it works... + println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path); +} diff --git a/common/nyxd-scraper/sql_migrations/01_metadata.sql b/common/nyxd-scraper/sql_migrations/01_metadata.sql new file mode 100644 index 0000000000..bce13940e8 --- /dev/null +++ b/common/nyxd-scraper/sql_migrations/01_metadata.sql @@ -0,0 +1,10 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: Apache-2.0 + */ + +CREATE TABLE METADATA +( + id INTEGER PRIMARY KEY CHECK (id = 0), + last_processed_height INTEGER NOT NULL +); \ No newline at end of file diff --git a/common/nyxd-scraper/sql_migrations/02_cosmos.sql b/common/nyxd-scraper/sql_migrations/02_cosmos.sql new file mode 100644 index 0000000000..373949a923 --- /dev/null +++ b/common/nyxd-scraper/sql_migrations/02_cosmos.sql @@ -0,0 +1,76 @@ +CREATE TABLE validator +( + consensus_address TEXT NOT NULL PRIMARY KEY, /* Validator consensus address */ + consensus_pubkey TEXT NOT NULL UNIQUE /* Validator consensus public key */ +); + +CREATE TABLE pre_commit +( + validator_address TEXT NOT NULL REFERENCES validator (consensus_address), + height BIGINT NOT NULL, + timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, + voting_power BIGINT NOT NULL, + proposer_priority BIGINT NOT NULL, + UNIQUE (validator_address, timestamp) +); +CREATE INDEX pre_commit_validator_address_index ON pre_commit (validator_address); +CREATE INDEX pre_commit_height_index ON pre_commit (height); + +CREATE TABLE block +( + height BIGINT UNIQUE PRIMARY KEY, + hash TEXT NOT NULL UNIQUE, + num_txs INTEGER DEFAULT 0, + total_gas BIGINT DEFAULT 0, + proposer_address TEXT REFERENCES validator (consensus_address), + timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL +); +CREATE INDEX block_height_index ON block (height); +CREATE INDEX block_hash_index ON block (hash); +CREATE INDEX block_proposer_address_index ON block (proposer_address); + +-- no JSONB in sqlite (yet) : ) +CREATE TABLE "transaction" +( + hash TEXT UNIQUE NOT NULL, + height BIGINT NOT NULL REFERENCES block (height), + "index" INTEGER NOT NULL, + success BOOLEAN NOT NULL, + + /* Body */ + num_messages INTEGER NOT NULL, +-- messages JSONB NOT NULL,-- DEFAULT '[]'::JSONB, + memo TEXT, +-- signatures TEXT[] NOT NULL, + + /* AuthInfo */ +-- signer_infos JSONB NOT NULL,-- DEFAULT '[]'::JSONB, +-- fee JSONB NOT NULL,-- DEFAULT '{}'::JSONB, + + /* Tx response */ + gas_wanted BIGINT DEFAULT 0, + gas_used BIGINT DEFAULT 0, + raw_log TEXT +-- logs JSONB +); +CREATE INDEX transaction_hash_index ON "transaction" (hash); +CREATE INDEX transaction_height_index ON "transaction" (height); + +CREATE TABLE message +( + transaction_hash TEXT NOT NULL REFERENCES "transaction" (hash), + "index" BIGINT NOT NULL, + type TEXT NOT NULL, +-- value JSONB NOT NULL, +-- involved_accounts_addresses TEXT[] NOT NULL, + + height BIGINT NOT NULL, + CONSTRAINT unique_message_per_tx UNIQUE (transaction_hash, "index") +); +CREATE INDEX message_transaction_hash_index ON message (transaction_hash); +CREATE INDEX message_type_index ON message (type); + +CREATE TABLE pruning +( + last_pruned_height BIGINT NOT NULL +); \ No newline at end of file diff --git a/common/nyxd-scraper/src/block_processor/helpers.rs b/common/nyxd-scraper/src/block_processor/helpers.rs new file mode 100644 index 0000000000..ffc8c592c5 --- /dev/null +++ b/common/nyxd-scraper/src/block_processor/helpers.rs @@ -0,0 +1,65 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::MAX_RANGE_SIZE; +use std::cmp::min; +use std::collections::VecDeque; +use std::ops::Range; + +pub(crate) fn split_request_range(request_range: Range) -> VecDeque> { + let mut requests = VecDeque::new(); + + let mut start = request_range.start; + let mut end = min(request_range.end, start + MAX_RANGE_SIZE as u32); + + loop { + requests.push_back(start..end); + start = min(start + MAX_RANGE_SIZE as u32, request_range.end); + end = min(end + MAX_RANGE_SIZE as u32, request_range.end); + + if start == end { + break; + } + } + + requests +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn splitting_request_range() { + let range = 0..100; + let mut expected = VecDeque::new(); + expected.push_back(0..30); + expected.push_back(30..60); + expected.push_back(60..90); + expected.push_back(90..100); + assert_eq!(expected, split_request_range(range)); + + let range = 0..30; + let mut expected = VecDeque::new(); + expected.push_back(0..30); + assert_eq!(expected, split_request_range(range)); + + let range = 0..60; + let mut expected = VecDeque::new(); + expected.push_back(0..30); + expected.push_back(30..60); + assert_eq!(expected, split_request_range(range)); + + let range = 0..5; + let mut expected = VecDeque::new(); + expected.push_back(0..5); + assert_eq!(expected, split_request_range(range)); + + let range = 123..200; + let mut expected = VecDeque::new(); + expected.push_back(123..153); + expected.push_back(153..183); + expected.push_back(183..200); + assert_eq!(expected, split_request_range(range)); + } +} diff --git a/common/nyxd-scraper/src/block_processor/mod.rs b/common/nyxd-scraper/src/block_processor/mod.rs new file mode 100644 index 0000000000..bb3e886b0e --- /dev/null +++ b/common/nyxd-scraper/src/block_processor/mod.rs @@ -0,0 +1,323 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::helpers::split_request_range; +use crate::block_processor::types::BlockToProcess; +use crate::block_requester::BlockRequest; +use crate::error::ScraperError; +use crate::modules::{BlockModule, MsgModule, TxModule}; +use crate::rpc_client::RpcClient; +use crate::storage::{persist_block, ScraperStorage}; +use futures::StreamExt; +use std::collections::{BTreeMap, HashSet, VecDeque}; +use std::ops::{Add, Range}; +use std::time::Duration; +use tokio::sync::mpsc::{Sender, UnboundedReceiver}; +use tokio::time::{interval_at, Instant}; +use tokio_stream::wrappers::UnboundedReceiverStream; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error, info, warn}; + +mod helpers; +pub(crate) mod types; + +const MISSING_BLOCKS_CHECK_INTERVAL: Duration = Duration::from_secs(30); +const MAX_MISSING_BLOCKS_DELAY: Duration = Duration::from_secs(15); +const MAX_RANGE_SIZE: usize = 30; + +#[derive(Debug, Default)] +struct PendingSync { + request_in_flight: HashSet, + queued_requests: VecDeque>, +} + +impl PendingSync { + fn is_empty(&self) -> bool { + self.request_in_flight.is_empty() && self.queued_requests.is_empty() + } +} + +pub struct BlockProcessor { + cancel: CancellationToken, + last_processed_height: u32, + last_processed_at: Instant, + pending_sync: PendingSync, + queued_blocks: BTreeMap, + + rpc_client: RpcClient, + incoming: UnboundedReceiverStream, + block_requester: Sender, + storage: ScraperStorage, + + // future work: rather than sending each msg to every msg module, + // let them subscribe based on `type_url` inside the message itself + // (like "/cosmwasm.wasm.v1.MsgExecuteContract") + block_modules: Vec>, + tx_modules: Vec>, + msg_modules: Vec>, +} + +impl BlockProcessor { + pub async fn new( + cancel: CancellationToken, + incoming: UnboundedReceiver, + block_requester: Sender, + storage: ScraperStorage, + rpc_client: RpcClient, + ) -> Result { + let last_processed = storage.get_last_processed_height().await?; + + Ok(BlockProcessor { + cancel, + last_processed_height: last_processed.try_into().unwrap_or_default(), + last_processed_at: Instant::now(), + pending_sync: Default::default(), + queued_blocks: Default::default(), + rpc_client, + incoming: incoming.into(), + block_requester, + storage, + block_modules: vec![], + tx_modules: vec![], + msg_modules: vec![], + }) + } + + async fn process_block(&mut self, block: BlockToProcess) -> Result<(), ScraperError> { + info!("processing block at height {}", block.height); + + let full_info = self.rpc_client.try_get_full_details(block).await?; + + debug!( + "this block has {} transaction(s)", + full_info.transactions.len() + ); + for tx in &full_info.transactions { + debug!("{} has {} message(s)", tx.hash, tx.tx.body.messages.len()); + for (index, msg) in tx.tx.body.messages.iter().enumerate() { + debug!("{index}: {:?}", msg.type_url) + } + } + + // process the entire block as a transaction so that if anything fails, + // we won't end up with a corrupted storage. + let mut tx = self.storage.begin_processing_tx().await?; + + persist_block(&full_info, &mut tx).await?; + + // let the modules do whatever they want + // the ones wanting the full block: + for block_module in &mut self.block_modules { + block_module.handle_block(&full_info, &mut tx).await?; + } + + // the ones wanting transactions: + for block_tx in full_info.transactions { + for tx_module in &mut self.tx_modules { + tx_module.handle_tx(&block_tx, &mut tx).await?; + } + // the ones concerned with individual messages + for (index, msg) in block_tx.tx.body.messages.iter().enumerate() { + for msg_module in &mut self.msg_modules { + msg_module + .handle_msg(index, msg, &block_tx, &mut tx) + .await? + } + } + } + + tx.commit() + .await + .map_err(|source| ScraperError::StorageTxCommitFailure { source })?; + + self.last_processed_height = full_info.block.header.height.value() as u32; + self.last_processed_at = Instant::now(); + + Ok(()) + } + + pub fn set_block_modules(&mut self, modules: Vec>) { + self.block_modules = modules; + } + + pub fn set_tx_modules(&mut self, modules: Vec>) { + self.tx_modules = modules; + } + + pub fn set_msg_modules(&mut self, modules: Vec>) { + self.msg_modules = modules; + } + + async fn maybe_request_missing_blocks(&mut self) -> Result<(), ScraperError> { + // we're still processing, so we're good + if self.last_processed_at.elapsed() < MAX_MISSING_BLOCKS_DELAY { + debug!("no need to request missing blocks"); + return Ok(()); + } + + if self.try_request_pending().await { + return Ok(()); + } + + // TODO: properly fill in the gaps later with BlockRequest::Specific, + let request_range = if let Some((next_available, _)) = self.queued_blocks.first_key_value() + { + self.last_processed_height + 1..*next_available + } else { + let current_height = self.rpc_client.current_block_height().await? as u32; + self.last_processed_height + 1..current_height + 1 + }; + + self.request_missing_blocks(request_range).await?; + + Ok(()) + } + + async fn request_missing_blocks( + &mut self, + request_range: Range, + ) -> Result<(), ScraperError> { + let request_range = if request_range.len() > MAX_RANGE_SIZE { + let mut split = split_request_range(request_range); + + // SAFETY: we know that after the split of a non-empty range we have AT LEAST one value + #[allow(clippy::unwrap_used)] + let first = split.pop_front().unwrap(); + self.pending_sync.queued_requests = split; + self.pending_sync.request_in_flight = first.clone().collect(); + + first + } else { + request_range + }; + + self.send_blocks_request(request_range).await + } + + // technically we're not mutating self here, + // but we need it to help the compiler figure out the future is `Send` + async fn send_blocks_request(&mut self, request_range: Range) -> Result<(), ScraperError> { + debug!("requesting missing blocks: {request_range:?}"); + + self.block_requester + .send(BlockRequest::Range(request_range)) + .await?; + Ok(()) + } + + async fn next_incoming(&mut self, block: BlockToProcess) { + let height = block.height; + + self.pending_sync.request_in_flight.remove(&height); + + if self.last_processed_height == 0 { + // this is the first time we've started up the process + debug!("setting up initial processing height"); + self.last_processed_height = height - 1 + } + + if height <= self.last_processed_height { + warn!("we have already processed block for height {height}"); + return; + } + + if self.last_processed_height + 1 != height { + if self.queued_blocks.insert(height, block).is_some() { + warn!("we have already queued up block for height {height}"); + } + return; + } + + if let Err(err) = self.process_block(block).await { + error!("failed to process block at height {height}: {err}"); + return; + } + + // process as much as we can from the queue + let mut next = height + 1; + while let Some(next_block) = self.queued_blocks.remove(&next) { + if let Err(err) = self.process_block(next_block).await { + error!("failed to process queued-up block at height {next}: {err}") + } + next += 1; + } + + self.try_request_pending().await; + } + + async fn try_request_pending(&mut self) -> bool { + if self.pending_sync.request_in_flight.is_empty() { + if let Some(next_sync) = self.pending_sync.queued_requests.pop_front() { + debug!( + "current request range has been resolved. requesting another bunch of blocks" + ); + if let Err(err) = self.send_blocks_request(next_sync.clone()).await { + error!("failed to request resync blocks: {err}"); + self.pending_sync.queued_requests.push_front(next_sync); + } else { + self.pending_sync.request_in_flight = next_sync.collect() + } + + return true; + } + } + + false + } + + // technically we're not mutating self here, + // but we need it to help the compiler figure out the future is `Send` + async fn startup_resync(&mut self) -> Result<(), ScraperError> { + assert!(self.pending_sync.is_empty()); + + let latest_block = self.rpc_client.current_block_height().await? as u32; + if latest_block > self.last_processed_height && self.last_processed_height != 0 { + let request_range = self.last_processed_height + 1..latest_block + 1; + info!("we need to request {request_range:?} to resync"); + self.request_missing_blocks(request_range).await?; + } + + Ok(()) + } + + pub(crate) async fn run(&mut self) { + info!("starting processing loop"); + + // sure, we could be more efficient and reset it on every processed block, + // but the overhead is so minimal that it doesn't matter + let mut missing_check_interval = interval_at( + Instant::now().add(MISSING_BLOCKS_CHECK_INTERVAL), + MISSING_BLOCKS_CHECK_INTERVAL, + ); + + if let Err(err) = self.startup_resync().await { + error!("failed to perform startup sync: {err}"); + self.cancel.cancel(); + return; + } + + loop { + tokio::select! { + _ = self.cancel.cancelled() => { + info!("received cancellation token"); + break + } + _ = missing_check_interval.tick() => { + if let Err(err) = self.maybe_request_missing_blocks().await { + error!("failed to request missing blocks: {err}") + } + } + block = self.incoming.next() => { + match block { + Some(block) => self.next_incoming(block).await, + None => { + warn!("stopped receiving new blocks"); + self.cancel.cancel(); + break + } + } + } + } + } + } +} diff --git a/common/nyxd-scraper/src/block_processor/types.rs b/common/nyxd-scraper/src/block_processor/types.rs new file mode 100644 index 0000000000..93ffb3ba76 --- /dev/null +++ b/common/nyxd-scraper/src/block_processor/types.rs @@ -0,0 +1,121 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::ScraperError; +use crate::helpers; +use tendermint::{abci, block, tx, Block, Hash}; +use tendermint_rpc::endpoint::{block as block_endpoint, block_results, validators}; +use tendermint_rpc::event::{Event, EventData}; + +// just get all everything out of tx::Response, but parse raw `tx` bytes +#[derive(Clone, Debug)] +pub struct ParsedTransactionResponse { + /// The hash of the transaction. + /// + /// Deserialized from a hex-encoded string (there is a discrepancy between + /// the format used for the request and the format used for the response in + /// the Tendermint RPC). + pub hash: Hash, + + pub height: block::Height, + + pub index: u32, + + pub tx_result: abci::types::ExecTxResult, + + pub tx: cosmrs::tx::Tx, + + pub proof: Option, +} + +#[derive(Debug)] +pub struct FullBlockInformation { + /// Basic block information, including its signers. + pub block: Block, + + /// All of the emitted events alongside any tx results. + pub results: block_results::Response, + + /// Validator set for this particular block + pub validators: validators::Response, + + /// Transaction results from this particular block + pub transactions: Vec, +} + +impl FullBlockInformation { + pub fn ensure_proposer(&self) -> Result<(), ScraperError> { + let block_proposer = self.block.header.proposer_address; + if !self + .validators + .validators + .iter() + .any(|v| v.address == block_proposer) + { + let proposer = helpers::validator_consensus_address(block_proposer)?; + return Err(ScraperError::BlockProposerNotInValidatorSet { + height: self.block.header.height.value() as u32, + proposer: proposer.to_string(), + }); + } + Ok(()) + } +} + +pub(crate) struct BlockToProcess { + pub(crate) height: u32, + pub(crate) block: Block, +} + +impl From for BlockToProcess { + fn from(block: Block) -> Self { + BlockToProcess { + height: block.header.height.value() as u32, + block, + } + } +} + +impl TryFrom for BlockToProcess { + type Error = ScraperError; + + fn try_from(event: Event) -> Result { + let query = event.query.clone(); + + // TODO: we're losing `result_begin_block` and `result_end_block` here but maybe that's fine? + let maybe_block = match event.data { + // we don't care about `NewBlock` until CometBFT 0.38, i.e. until we upgrade to wasmd 0.50 + EventData::NewBlock { .. } => { + return Err(ScraperError::InvalidSubscriptionEvent { + query, + kind: "NewBlock".to_string(), + }) + } + EventData::LegacyNewBlock { block, .. } => block, + EventData::Tx { .. } => { + return Err(ScraperError::InvalidSubscriptionEvent { + query, + kind: "Tx".to_string(), + }) + } + EventData::GenericJsonEvent(_) => { + return Err(ScraperError::InvalidSubscriptionEvent { + query, + kind: "GenericJsonEvent".to_string(), + }) + } + }; + + let Some(block) = maybe_block else { + return Err(ScraperError::EmptyBlockData { query }); + }; + + Ok((*block).into()) + } +} + +impl From for BlockToProcess { + fn from(value: block_endpoint::Response) -> Self { + value.block.into() + } +} diff --git a/common/nyxd-scraper/src/block_requester/mod.rs b/common/nyxd-scraper/src/block_requester/mod.rs new file mode 100644 index 0000000000..b2a1a4d43a --- /dev/null +++ b/common/nyxd-scraper/src/block_requester/mod.rs @@ -0,0 +1,91 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::BlockToProcess; +use crate::error::ScraperError; +use crate::rpc_client::RpcClient; +use futures::StreamExt; +use std::ops::Range; +use tokio::sync::mpsc::{Receiver, UnboundedSender}; +use tokio_stream::wrappers::ReceiverStream; +use tokio_util::sync::CancellationToken; +use tracing::{error, info, instrument, warn}; + +#[derive(Debug)] +pub enum BlockRequest { + Range(Range), + + // UNIMPLEMENTED: + #[allow(dead_code)] + Specific(Vec), +} + +pub(crate) struct BlockRequester { + cancel: CancellationToken, + rpc_client: RpcClient, + requests: ReceiverStream, + blocks: UnboundedSender, +} + +impl BlockRequester { + pub(crate) fn new( + cancel: CancellationToken, + rpc_client: RpcClient, + requests: Receiver, + blocks: UnboundedSender, + ) -> Self { + BlockRequester { + cancel, + rpc_client, + requests: requests.into(), + blocks, + } + } + + async fn request_and_send(&self, height: u32) -> Result<(), ScraperError> { + let block = self.rpc_client.get_basic_block_details(height).await?; + self.blocks.send(block.into())?; + Ok(()) + } + + async fn request_blocks>(&self, heights: I) { + futures::stream::iter(heights) + .for_each_concurrent(4, |height| async move { + if let Err(err) = self.request_and_send(height).await { + error!("failed to request block data: {err}") + } + }) + .await + } + + #[instrument(skip(self))] + async fn handle_blocks_request(&self, request: BlockRequest) { + info!("received request for missed blocks"); + + match request { + BlockRequest::Range(range) => self.request_blocks(range).await, + BlockRequest::Specific(heights) => self.request_blocks(heights).await, + } + } + + pub(crate) async fn run(&mut self) { + loop { + tokio::select! { + _ = self.cancel.cancelled() => { + info!("received cancellation token"); + break + } + maybe_request = self.requests.next() => { + match maybe_request { + Some(request) => self.handle_blocks_request(request).await, + None => { + warn!("stopped receiving new requests"); + self.cancel.cancel(); + break + } + } + } + } + } + } +} diff --git a/common/nyxd-scraper/src/constants.rs b/common/nyxd-scraper/src/constants.rs new file mode 100644 index 0000000000..9401cd360e --- /dev/null +++ b/common/nyxd-scraper/src/constants.rs @@ -0,0 +1,47 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use const_format::concatcp; + +// TODO: make those configurable via 'NymNetworkDetails' + +// BECH32_PREFIX defines the main SDK Bech32 prefix of an account's address +pub const BECH32_PREFIX: &str = "n"; + +// ACCOUNT_PREFIX is the prefix for account keys +pub const ACCOUNT_PREFIX: &str = "acc"; +// VALIDATOR_PREFIX is the prefix for validator keys +pub const VALIDATOR_PREFIX: &str = "val"; +// CONSENSUS_PREFIX is the prefix for consensus keys +pub const CONSENSUS_PREFIX: &str = "cons"; +// PUBKEY_PREFIX is the prefix for public keys +pub const PUBKEY_PREFIX: &str = "pub"; +// OPERATOR_PREFIX is the prefix for operator keys +pub const OPERATOR_PREFIX: &str = "oper"; +// ADDRESS_PREFIX is the prefix for addresses +pub const ADDRESS_PREFIX: &str = "addr"; + +// BECH32_ACCOUNT_ADDRESS_PREFIX defines the Bech32 prefix of an account's address +pub const BECH32_ACCOUNT_ADDRESS_PREFIX: &str = BECH32_PREFIX; +// BECH32_ACCOUNT_PUBKEY_PREFIX defines the Bech32 prefix of an account's public key +pub const BECH32_ACCOUNT_PUBKEY_PREFIX: &str = concatcp!(BECH32_PREFIX, PUBKEY_PREFIX); +// BECH32_VALIDATOR_ADDRESS_PREFIX defines the Bech32 prefix of a validator's operator address +pub const BECH32_VALIDATOR_ADDRESS_PREFIX: &str = + concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, OPERATOR_PREFIX); +// BECH32_VALIDATOR_PUBKEY_PREFIX defines the Bech32 prefix of a validator's operator public key +pub const BECH32_VALIDATOR_PUBKEY_PREFIX: &str = concatcp!( + BECH32_PREFIX, + VALIDATOR_PREFIX, + OPERATOR_PREFIX, + PUBKEY_PREFIX +); +// BECH32_CONSENSUS_ADDRESS_PREFIX defines the Bech32 prefix of a consensus node address +pub const BECH32_CONSENSUS_ADDRESS_PREFIX: &str = + concatcp!(BECH32_PREFIX, VALIDATOR_PREFIX, CONSENSUS_PREFIX); +// BECH32_CONESNSUS_PUBKEY_PREFIX defines the Bech32 prefix of a consensus node public key +pub const BECH32_CONESNSUS_PUBKEY_PREFIX: &str = concatcp!( + BECH32_PREFIX, + VALIDATOR_PREFIX, + CONSENSUS_PREFIX, + PUBKEY_PREFIX +); diff --git a/common/nyxd-scraper/src/error.rs b/common/nyxd-scraper/src/error.rs new file mode 100644 index 0000000000..dec4fb26e9 --- /dev/null +++ b/common/nyxd-scraper/src/error.rs @@ -0,0 +1,131 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use tendermint::Hash; +use thiserror::Error; +use tokio::sync::mpsc::error::SendError; + +#[derive(Debug, Error)] +pub enum ScraperError { + #[error("experienced internal database error: {0}")] + InternalDatabaseError(#[from] sqlx::Error), + + #[error("failed to perform startup SQL migration: {0}")] + StartupMigrationFailure(#[from] sqlx::migrate::MigrateError), + + #[error("can't add any modules to the scraper as it's already running")] + ScraperAlreadyRunning, + + #[error("failed to establish websocket connection to {url}: {source}")] + WebSocketConnectionFailure { + url: String, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("failed to establish rpc connection to {url}: {source}")] + HttpConnectionFailure { + url: String, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("failed to create chain subscription: {source}")] + ChainSubscriptionFailure { + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not obtain basic block information at height: {height}: {source}")] + BlockQueryFailure { + height: u32, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not obtain block results information at height: {height}: {source}")] + BlockResultsQueryFailure { + height: u32, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not obtain validators information at height: {height}: {source}")] + ValidatorsQueryFailure { + height: u32, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not obtain tx results for tx: {hash}: {source}")] + TxResultsQueryFailure { + hash: Hash, + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not obtain current abci info: {source}")] + AbciInfoQueryFailure { + #[source] + source: tendermint_rpc::Error, + }, + + #[error("could not parse tx {hash}: {source}")] + TxParseFailure { + hash: Hash, + #[source] + source: cosmrs::ErrorReport, + }, + + #[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")] + InvalidSubscriptionEvent { query: String, kind: String }, + + #[error("received block data was empty (query: '{query}')")] + EmptyBlockData { query: String }, + + #[error("reached maximum number of allowed errors for subscription events")] + MaximumSubscriptionFailures, + + #[error("failed to begin storage tx: {source}")] + StorageTxBeginFailure { + #[source] + source: sqlx::Error, + }, + + #[error("failed to commit storage tx: {source}")] + StorageTxCommitFailure { + #[source] + source: sqlx::Error, + }, + + #[error("failed to send on a closed channel")] + ClosedChannelError, + + #[error("failed to parse validator's address: {source}")] + MalformedValidatorAddress { + #[source] + source: eyre::Report, + }, + + #[error("failed to parse validator's address: {source}")] + MalformedValidatorPubkey { + #[source] + source: eyre::Report, + }, + + #[error( + "could not find the block proposer ('{proposer}') for height {height} in the validator set" + )] + BlockProposerNotInValidatorSet { height: u32, proposer: String }, + + #[error( + "could not find validator information for {address}; the validator has signed a commit" + )] + MissingValidatorInfoCommitted { address: String }, +} + +impl From> for ScraperError { + fn from(_: SendError) -> Self { + ScraperError::ClosedChannelError + } +} diff --git a/common/nyxd-scraper/src/helpers.rs b/common/nyxd-scraper/src/helpers.rs new file mode 100644 index 0000000000..d11b25a2f3 --- /dev/null +++ b/common/nyxd-scraper/src/helpers.rs @@ -0,0 +1,46 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::ParsedTransactionResponse; +use crate::constants::{BECH32_CONESNSUS_PUBKEY_PREFIX, BECH32_CONSENSUS_ADDRESS_PREFIX}; +use crate::error::ScraperError; +use cosmrs::AccountId; +use sha2::{Digest, Sha256}; +use tendermint::{account, PublicKey}; +use tendermint::{validator, Hash}; +use tendermint_rpc::endpoint::validators; + +pub(crate) fn tx_hash>(raw_tx: M) -> Hash { + Hash::Sha256(Sha256::digest(raw_tx).into()) +} + +pub(crate) fn validator_pubkey_to_bech32(pubkey: PublicKey) -> Result { + // TODO: this one seem to attach additional prefix to they pubkeys, is that what we want instead maybe? + // Ok(pubkey.to_bech32(BECH32_CONESNSUS_PUBKEY_PREFIX)) + AccountId::new(BECH32_CONESNSUS_PUBKEY_PREFIX, &pubkey.to_bytes()) + .map_err(|source| ScraperError::MalformedValidatorPubkey { source }) +} + +pub(crate) fn validator_consensus_address(id: account::Id) -> Result { + AccountId::new(BECH32_CONSENSUS_ADDRESS_PREFIX, id.as_ref()) + .map_err(|source| ScraperError::MalformedValidatorAddress { source }) +} + +pub(crate) fn tx_gas_sum(txs: &[ParsedTransactionResponse]) -> i64 { + txs.iter().map(|tx| tx.tx_result.gas_used).sum() +} + +pub(crate) fn validator_info( + id: account::Id, + validators: &validators::Response, +) -> Result<&validator::Info, ScraperError> { + match validators.validators.iter().find(|v| v.address == id) { + Some(info) => Ok(info), + None => { + let addr = validator_consensus_address(id)?; + Err(ScraperError::MissingValidatorInfoCommitted { + address: addr.to_string(), + }) + } + } +} diff --git a/common/nyxd-scraper/src/lib.rs b/common/nyxd-scraper/src/lib.rs new file mode 100644 index 0000000000..79372e0e30 --- /dev/null +++ b/common/nyxd-scraper/src/lib.rs @@ -0,0 +1,18 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] + +pub(crate) mod block_processor; +pub(crate) mod block_requester; +pub mod constants; +pub mod error; +pub(crate) mod helpers; +pub mod modules; +pub(crate) mod rpc_client; +pub(crate) mod scraper; +pub mod storage; + +pub use modules::{BlockModule, MsgModule, TxModule}; +pub use scraper::{Config, NyxdScraper}; diff --git a/common/nyxd-scraper/src/modules/block_module.rs b/common/nyxd-scraper/src/modules/block_module.rs new file mode 100644 index 0000000000..9ca1ba9b20 --- /dev/null +++ b/common/nyxd-scraper/src/modules/block_module.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::FullBlockInformation; +use crate::error::ScraperError; +use crate::storage::StorageTransaction; +use async_trait::async_trait; + +#[async_trait] +pub trait BlockModule { + async fn handle_block( + &mut self, + block: &FullBlockInformation, + storage_tx: &mut StorageTransaction, + ) -> Result<(), ScraperError>; +} diff --git a/common/nyxd-scraper/src/modules/mod.rs b/common/nyxd-scraper/src/modules/mod.rs new file mode 100644 index 0000000000..1f37d149e0 --- /dev/null +++ b/common/nyxd-scraper/src/modules/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +mod block_module; +mod msg_module; +mod tx_module; + +pub use block_module::BlockModule; +pub use msg_module::MsgModule; +pub use tx_module::TxModule; diff --git a/common/nyxd-scraper/src/modules/msg_module.rs b/common/nyxd-scraper/src/modules/msg_module.rs new file mode 100644 index 0000000000..df64761f7d --- /dev/null +++ b/common/nyxd-scraper/src/modules/msg_module.rs @@ -0,0 +1,19 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::ParsedTransactionResponse; +use crate::error::ScraperError; +use crate::storage::StorageTransaction; +use async_trait::async_trait; +use cosmrs::Any; + +#[async_trait] +pub trait MsgModule { + async fn handle_msg( + &mut self, + index: usize, + msg: &Any, + tx: &ParsedTransactionResponse, + storage_tx: &mut StorageTransaction, + ) -> Result<(), ScraperError>; +} diff --git a/common/nyxd-scraper/src/modules/tx_module.rs b/common/nyxd-scraper/src/modules/tx_module.rs new file mode 100644 index 0000000000..07d012ab5a --- /dev/null +++ b/common/nyxd-scraper/src/modules/tx_module.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::ParsedTransactionResponse; +use crate::error::ScraperError; +use crate::storage::StorageTransaction; +use async_trait::async_trait; + +#[async_trait] +pub trait TxModule { + async fn handle_tx( + &mut self, + tx: &ParsedTransactionResponse, + storage_tx: &mut StorageTransaction, + ) -> Result<(), ScraperError>; +} diff --git a/common/nyxd-scraper/src/rpc_client.rs b/common/nyxd-scraper/src/rpc_client.rs new file mode 100644 index 0000000000..d2c141f711 --- /dev/null +++ b/common/nyxd-scraper/src/rpc_client.rs @@ -0,0 +1,175 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::{ + BlockToProcess, FullBlockInformation, ParsedTransactionResponse, +}; +use crate::error::ScraperError; +use crate::helpers::tx_hash; +use futures::future::join3; +use futures::StreamExt; +use std::collections::BTreeMap; +use std::sync::Arc; +use tendermint::Hash; +use tendermint_rpc::endpoint::{block, block_results, tx, validators}; +use tendermint_rpc::{Client, HttpClient, Paging}; +use tokio::sync::Mutex; +use tracing::{debug, instrument}; +use url::Url; + +#[derive(Clone)] +pub struct RpcClient { + // right now I don't care about anything nym specific, so a simple http client is sufficient, + // once this is inadequate, we can switch to a NyxdClient + inner: Arc, +} + +impl RpcClient { + pub fn new(url: &Url) -> Result { + let http_client = HttpClient::new(url.as_str()).map_err(|source| { + ScraperError::HttpConnectionFailure { + url: url.to_string(), + source, + } + })?; + + Ok(RpcClient { + inner: Arc::new(http_client), + }) + } + + #[instrument(skip(self, block), fields(height = block.height))] + pub async fn try_get_full_details( + &self, + block: BlockToProcess, + ) -> Result { + debug!("getting complete block details"); + let height = block.height; + + // make all the http requests concurrently + let (results, validators, raw_transactions) = join3( + self.get_block_results(height), + self.get_validators_details(height), + self.get_transaction_results(&block.block.data), + ) + .await; + + let raw_transactions = raw_transactions?; + let mut transactions = Vec::with_capacity(raw_transactions.len()); + for tx in raw_transactions { + transactions.push(ParsedTransactionResponse { + hash: tx.hash, + height: tx.height, + index: tx.index, + tx_result: tx.tx_result, + tx: cosmrs::Tx::from_bytes(&tx.tx).map_err(|source| { + ScraperError::TxParseFailure { + hash: tx.hash, + source, + } + })?, + proof: tx.proof, + }) + } + + Ok(FullBlockInformation { + block: block.block, + results: results?, + validators: validators?, + transactions, + }) + } + + #[instrument(skip(self), err(Display))] + pub async fn get_basic_block_details( + &self, + height: u32, + ) -> Result { + debug!("getting basic block details"); + + self.inner + .block(height) + .await + .map_err(|source| ScraperError::BlockQueryFailure { height, source }) + } + + #[instrument(skip(self), err(Display))] + pub async fn get_block_results( + &self, + height: u32, + ) -> Result { + debug!("getting block results"); + + self.inner + .block_results(height) + .await + .map_err(|source| ScraperError::BlockResultsQueryFailure { height, source }) + } + + pub(crate) async fn current_block_height(&self) -> Result { + debug!("getting current block height"); + + let info = self + .inner + .abci_info() + .await + .map_err(|source| ScraperError::AbciInfoQueryFailure { source })?; + Ok(info.last_block_height.value()) + } + + async fn get_transaction_results( + &self, + raw: &[Vec], + ) -> Result, ScraperError> { + let ordered_results = Arc::new(Mutex::new(BTreeMap::new())); + + // "Data is just a wrapper for a list of transactions, where transactions are arbitrary byte arrays" + // source: https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#data + // + // I hate that zip as much as you, dear reader, but for some reason the compiler didn't let me remove the `move` + futures::stream::iter( + raw.iter() + .map(tx_hash) + .enumerate() + .zip(std::iter::repeat(ordered_results.clone())), + ) + .for_each_concurrent(4, |((id, tx_hash), ordered_results)| async move { + let res = self.get_transaction_result(tx_hash).await; + ordered_results.lock().await.insert(id, res); + }) + .await; + + // safety the futures have completed so we MUST have the only arc reference + #[allow(clippy::unwrap_used)] + let inner = Arc::into_inner(ordered_results).unwrap().into_inner(); + + // BTreeMap is ordered by its keys so we're guaranteed to get txs in correct order + inner.into_values().collect() + } + + #[instrument(skip(self, tx_hash), fields(tx_hash = %tx_hash), err(Display))] + async fn get_transaction_result(&self, tx_hash: Hash) -> Result { + debug!("getting tx results"); + + self.inner + .tx(tx_hash, false) + .await + .map_err(|source| ScraperError::TxResultsQueryFailure { + hash: tx_hash, + source, + }) + } + + #[instrument(skip(self))] + pub async fn get_validators_details( + &self, + height: u32, + ) -> Result { + debug!("getting validators set"); + + self.inner + .validators(height, Paging::All) + .await + .map_err(|source| ScraperError::ValidatorsQueryFailure { height, source }) + } +} diff --git a/common/nyxd-scraper/src/scraper/mod.rs b/common/nyxd-scraper/src/scraper/mod.rs new file mode 100644 index 0000000000..656cd6c921 --- /dev/null +++ b/common/nyxd-scraper/src/scraper/mod.rs @@ -0,0 +1,197 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::BlockProcessor; +use crate::block_requester::BlockRequester; +use crate::error::ScraperError; +use crate::modules::{BlockModule, MsgModule, TxModule}; +use crate::rpc_client::RpcClient; +use crate::scraper::subscriber::{run_websocket_driver, ChainSubscriber}; +use crate::storage::ScraperStorage; +use std::path::PathBuf; +use tendermint_rpc::WebSocketClientDriver; +use tokio::sync::mpsc::{channel, unbounded_channel}; +use tokio_util::sync::CancellationToken; +use tokio_util::task::TaskTracker; +use tracing::info; +use url::Url; + +mod subscriber; + +pub struct Config { + /// Url to the websocket endpoint of a validator, for example `wss://rpc.nymtech.net/websocket` + pub websocket_url: Url, + + /// Url to the rpc endpoint of a validator, for example `https://rpc.nymtech.net/` + pub rpc_url: Url, + + pub database_path: PathBuf, +} + +pub struct NyxdScraperBuilder { + config: Config, + + block_modules: Vec>, + tx_modules: Vec>, + msg_modules: Vec>, +} + +impl NyxdScraperBuilder { + pub async fn build_and_start(self) -> Result { + let scraper = NyxdScraper::new(self.config).await?; + + let (processing_tx, processing_rx) = unbounded_channel(); + let (req_tx, req_rx) = channel(5); + + let rpc_client = RpcClient::new(&scraper.config.rpc_url)?; + + // create the tasks + let block_requester = BlockRequester::new( + scraper.cancel_token.clone(), + rpc_client.clone(), + req_rx, + processing_tx.clone(), + ); + let mut block_processor = BlockProcessor::new( + scraper.cancel_token.clone(), + processing_rx, + req_tx, + scraper.storage.clone(), + rpc_client, + ) + .await?; + block_processor.set_block_modules(self.block_modules); + block_processor.set_tx_modules(self.tx_modules); + block_processor.set_msg_modules(self.msg_modules); + + let mut chain_subscriber = ChainSubscriber::new( + &scraper.config.websocket_url, + scraper.cancel_token.clone(), + processing_tx, + ) + .await?; + let ws_driver = chain_subscriber.ws_driver(); + + scraper.start_tasks( + block_requester, + block_processor, + chain_subscriber, + ws_driver, + ); + + Ok(scraper) + } + + pub fn new(config: Config) -> Self { + NyxdScraperBuilder { + config, + block_modules: vec![], + tx_modules: vec![], + msg_modules: vec![], + } + } + + pub fn with_block_module(mut self, module: M) -> Self { + self.block_modules.push(Box::new(module)); + self + } + + pub fn with_tx_module(mut self, module: M) -> Self { + self.tx_modules.push(Box::new(module)); + self + } + + pub fn with_msg_module(mut self, module: M) -> Self { + self.msg_modules.push(Box::new(module)); + self + } +} + +pub struct NyxdScraper { + config: Config, + + task_tracker: TaskTracker, + cancel_token: CancellationToken, + storage: ScraperStorage, +} + +impl NyxdScraper { + pub fn builder(config: Config) -> NyxdScraperBuilder { + NyxdScraperBuilder::new(config) + } + + pub async fn new(config: Config) -> Result { + let storage = ScraperStorage::init(&config.database_path).await?; + + Ok(NyxdScraper { + config, + task_tracker: TaskTracker::new(), + cancel_token: CancellationToken::new(), + storage, + }) + } + + fn start_tasks( + &self, + mut block_requester: BlockRequester, + mut block_processor: BlockProcessor, + mut chain_subscriber: ChainSubscriber, + ws_driver: WebSocketClientDriver, + ) { + self.task_tracker + .spawn(async move { block_requester.run().await }); + self.task_tracker + .spawn(async move { block_processor.run().await }); + self.task_tracker + .spawn(async move { chain_subscriber.run().await }); + self.task_tracker + .spawn(run_websocket_driver(ws_driver, self.cancel_token.clone())); + self.task_tracker.close(); + } + + pub async fn start(&self) -> Result<(), ScraperError> { + let (processing_tx, processing_rx) = unbounded_channel(); + let (req_tx, req_rx) = channel(5); + + let rpc_client = RpcClient::new(&self.config.rpc_url)?; + + // create the tasks + let block_requester = BlockRequester::new( + self.cancel_token.clone(), + rpc_client.clone(), + req_rx, + processing_tx.clone(), + ); + let block_processor = BlockProcessor::new( + self.cancel_token.clone(), + processing_rx, + req_tx, + self.storage.clone(), + rpc_client, + ) + .await?; + let mut chain_subscriber = ChainSubscriber::new( + &self.config.websocket_url, + self.cancel_token.clone(), + processing_tx, + ) + .await?; + let ws_driver = chain_subscriber.ws_driver(); + + // spawn them + self.start_tasks( + block_requester, + block_processor, + chain_subscriber, + ws_driver, + ); + + Ok(()) + } + + pub async fn stop(self) { + info!("stopping the chain scrapper"); + self.cancel_token.cancel(); + self.task_tracker.wait().await + } +} diff --git a/common/nyxd-scraper/src/scraper/subscriber.rs b/common/nyxd-scraper/src/scraper/subscriber.rs new file mode 100644 index 0000000000..6e9fd3e85d --- /dev/null +++ b/common/nyxd-scraper/src/scraper/subscriber.rs @@ -0,0 +1,129 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::BlockToProcess; +use crate::error::ScraperError; +use tendermint_rpc::event::Event; +use tendermint_rpc::query::EventType; +use tendermint_rpc::{SubscriptionClient, WebSocketClient, WebSocketClientDriver}; +use tokio::sync::mpsc::UnboundedSender; +use tokio_stream::StreamExt; +use tokio_util::sync::CancellationToken; +use tracing::{error, info, warn}; +use url::Url; + +const MAX_FAILURES: usize = 10; + +pub struct ChainSubscriber { + cancel: CancellationToken, + block_sender: UnboundedSender, + + websocket_client: WebSocketClient, + websocket_driver: Option, +} + +impl ChainSubscriber { + pub async fn new( + websocket_endpoint: &Url, + cancel: CancellationToken, + block_sender: UnboundedSender, + ) -> Result { + // sure, we could have just used websocket client entirely, but let's keep the logic for + // getting current blocks and historical blocks completely separate with the dual connection + let (client, driver) = WebSocketClient::new(websocket_endpoint.as_str()) + .await + .map_err(|source| ScraperError::WebSocketConnectionFailure { + url: websocket_endpoint.to_string(), + source, + })?; + + Ok(ChainSubscriber { + cancel, + block_sender, + websocket_client: client, + websocket_driver: Some(driver), + }) + } + + fn handle_new_event(&mut self, event: Event) -> Result<(), ScraperError> { + if let Err(err) = self.block_sender.send(event.try_into()?) { + // this error has nothing to do with the websocket or chain + error!("failed to send block for processing: {err} - are we shutting down?") + } + Ok(()) + } + + pub(crate) async fn run(&mut self) -> Result<(), ScraperError> { + let _drop_guard = self.cancel.clone().drop_guard(); + + info!("creating chain subscription"); + let mut subs = self + .websocket_client + .subscribe(EventType::NewBlock.into()) + .await + .map_err(|source| ScraperError::ChainSubscriptionFailure { source })?; + + let mut failures = 0; + + info!("starting processing loop"); + loop { + tokio::select! { + _ = self.cancel.cancelled() => { + info!("received cancellation token"); + break + } + maybe_event = subs.next() => { + let Some(maybe_event) = maybe_event else { + warn!("stopped receiving new events"); + break; + }; + match maybe_event { + Ok(event) => { + if let Err(err) = self.handle_new_event(event) { + error!("failed to process received block: {err}"); + failures += 1 + } else { + failures = 0; + } + } + Err(err) => { + error!("failed to receive a valid subscription event: {err}"); + failures += 1 + } + } + if failures >= MAX_FAILURES { + // note: the drop_guard will get dropped and thus cause a shutdown + return Err(ScraperError::MaximumSubscriptionFailures); + } + } + } + } + Ok(()) + } + + pub(crate) fn ws_driver(&mut self) -> WebSocketClientDriver { + #[allow(clippy::expect_used)] + self.websocket_driver + .take() + .expect("websocket driver has already been started!") + } +} + +pub async fn run_websocket_driver(driver: WebSocketClientDriver, cancel: CancellationToken) { + info!("starting websocket driver"); + tokio::select! { + _ = cancel.cancelled() => { + info!("received cancellation token") + } + res = driver.run() => { + match res { + Ok(_) => info!("our websocket driver has finished execution"), + Err(err) => { + // TODO: in the future just attempt to reconnect + error!("our websocket driver has errored out: {err}") + } + } + cancel.cancel() + } + } +} diff --git a/tools/nymvisor/src/upgrades/http_upstream.rs b/common/nyxd-scraper/src/storage/helpers.rs similarity index 100% rename from tools/nymvisor/src/upgrades/http_upstream.rs rename to common/nyxd-scraper/src/storage/helpers.rs diff --git a/common/nyxd-scraper/src/storage/manager.rs b/common/nyxd-scraper/src/storage/manager.rs new file mode 100644 index 0000000000..67ea50d610 --- /dev/null +++ b/common/nyxd-scraper/src/storage/manager.rs @@ -0,0 +1,328 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::storage::models::Validator; +use sqlx::types::time::OffsetDateTime; +use sqlx::{Executor, Sqlite}; +use tracing::{instrument, trace}; + +#[derive(Clone)] +pub(crate) struct StorageManager { + pub(crate) connection_pool: sqlx::SqlitePool, +} + +impl StorageManager { + pub(crate) async fn set_initial_metadata(&self) -> Result<(), sqlx::Error> { + if sqlx::query("SELECT * from metadata") + .fetch_optional(&self.connection_pool) + .await? + .is_none() + { + sqlx::query("INSERT INTO metadata (id, last_processed_height) VALUES (0, 0)") + .execute(&self.connection_pool) + .await?; + } + Ok(()) + } + + pub(crate) async fn get_first_block_height_after( + &self, + time: OffsetDateTime, + ) -> Result, sqlx::Error> { + let maybe_record = sqlx::query!( + r#" + SELECT height + FROM block + WHERE timestamp > ? + ORDER BY timestamp + LIMIT 1 + "#, + time + ) + .fetch_optional(&self.connection_pool) + .await?; + + if let Some(row) = maybe_record { + Ok(row.height) + } else { + Ok(None) + } + } + + pub(crate) async fn get_last_block_height_before( + &self, + time: OffsetDateTime, + ) -> Result, sqlx::Error> { + let maybe_record = sqlx::query!( + r#" + SELECT height + FROM block + WHERE timestamp < ? + ORDER BY timestamp DESC + LIMIT 1 + "#, + time + ) + .fetch_optional(&self.connection_pool) + .await?; + + if let Some(row) = maybe_record { + Ok(row.height) + } else { + Ok(None) + } + } + + pub(crate) async fn get_signed_between( + &self, + consensus_address: &str, + start_height: i64, + end_height: i64, + ) -> Result { + let count = sqlx::query!( + r#" + SELECT COUNT(*) as count FROM pre_commit + WHERE + validator_address == ? + AND height > ? + AND height < ? + "#, + consensus_address, + start_height, + end_height + ) + .fetch_one(&self.connection_pool) + .await? + .count; + + Ok(count) + } + + pub(crate) async fn get_block_validators( + &self, + height: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + Validator, + r#" + SELECT * FROM validator + WHERE EXISTS ( + SELECT 1 FROM pre_commit + WHERE height == ? + AND pre_commit.validator_address = validator.consensus_address + ) + "#, + height + ) + .fetch_all(&self.connection_pool) + .await + } + + pub(crate) async fn get_last_processed_height(&self) -> Result { + let maybe_record = sqlx::query!( + r#" + SELECT last_processed_height FROM metadata + "# + ) + .fetch_optional(&self.connection_pool) + .await?; + + if let Some(row) = maybe_record { + Ok(row.last_processed_height) + } else { + Ok(-1) + } + } +} + +// make those generic over executor so that they could be performed over connection pool and a tx + +#[instrument(skip(executor))] +pub(crate) async fn insert_validator<'a, E>( + consensus_address: String, + consensus_pubkey: String, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("insert validator"); + + sqlx::query!( + r#" + INSERT INTO validator (consensus_address, consensus_pubkey) + VALUES (?, ?) + ON CONFLICT DO NOTHING + "#, + consensus_address, + consensus_pubkey + ) + .execute(executor) + .await?; + + Ok(()) +} + +#[instrument(skip(executor))] +pub(crate) async fn insert_block<'a, E>( + height: i64, + hash: String, + num_txs: u32, + total_gas: i64, + proposer_address: String, + timestamp: OffsetDateTime, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("insert block"); + + sqlx::query!( + r#" + INSERT INTO block (height, hash, num_txs, total_gas, proposer_address, timestamp) + VALUES (?, ?, ?, ?, ?, ?) + ON CONFLICT DO NOTHING + "#, + height, + hash, + num_txs, + total_gas, + proposer_address, + timestamp + ) + .execute(executor) + .await?; + + Ok(()) +} + +#[instrument(skip(executor))] +pub(crate) async fn insert_precommit<'a, E>( + validator_address: String, + height: i64, + timestamp: OffsetDateTime, + voting_power: i64, + proposer_priority: i64, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("insert precommit"); + + sqlx::query!( + r#" + INSERT INTO pre_commit (validator_address, height, timestamp, voting_power, proposer_priority) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT (validator_address, timestamp) DO NOTHING + "#, + validator_address, + height, + timestamp, + voting_power, + proposer_priority + ) + .execute(executor) + .await?; + + Ok(()) +} + +#[instrument(skip(executor))] +#[allow(clippy::too_many_arguments)] +pub(crate) async fn insert_transaction<'a, E>( + hash: String, + height: i64, + index: i64, + success: bool, + messages: i64, + memo: String, + gas_wanted: i64, + gas_used: i64, + raw_log: String, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("insert transaction"); + + sqlx::query!( + r#" + INSERT INTO "transaction" (hash, height, "index", success, num_messages, memo, gas_wanted, gas_used, raw_log) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT (hash) DO UPDATE + SET height = excluded.height, + "index" = excluded."index", + success = excluded.success, + num_messages = excluded.num_messages, + memo = excluded.memo, + gas_wanted = excluded.gas_wanted, + gas_used = excluded.gas_used, + raw_log = excluded.raw_log + "#, + hash, + height, + index, + success, + messages, + memo, + gas_wanted, + gas_used, + raw_log, + ) + .execute(executor) + .await?; + + Ok(()) +} + +#[instrument(skip(executor))] +pub(crate) async fn insert_message<'a, E>( + transaction_hash: String, + index: i64, + typ: String, + height: i64, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("insert message"); + + sqlx::query!( + r#" + INSERT INTO message (transaction_hash, "index", type, height) + VALUES (?, ?, ?, ?) + ON CONFLICT (transaction_hash, "index") DO UPDATE + SET height = excluded.height, + type = excluded.type + "#, + transaction_hash, + index, + typ, + height + ) + .execute(executor) + .await?; + + Ok(()) +} + +#[instrument(skip(executor))] +pub(crate) async fn update_last_processed<'a, E>( + height: i64, + executor: E, +) -> Result<(), sqlx::Error> +where + E: Executor<'a, Database = Sqlite>, +{ + trace!("update_last_processed"); + + sqlx::query!("UPDATE metadata SET last_processed_height = ?", height) + .execute(executor) + .await?; + + Ok(()) +} diff --git a/common/nyxd-scraper/src/storage/mod.rs b/common/nyxd-scraper/src/storage/mod.rs new file mode 100644 index 0000000000..1889752a97 --- /dev/null +++ b/common/nyxd-scraper/src/storage/mod.rs @@ -0,0 +1,299 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::block_processor::types::{FullBlockInformation, ParsedTransactionResponse}; +use crate::error::ScraperError; +use crate::storage::manager::{ + insert_block, insert_message, insert_precommit, insert_transaction, insert_validator, + update_last_processed, StorageManager, +}; +use crate::storage::models::Validator; +use sqlx::types::time::OffsetDateTime; +use sqlx::{ConnectOptions, Sqlite, Transaction}; +use std::fmt::Debug; +use std::path::Path; +use tendermint::block::{Commit, CommitSig}; +use tendermint::Block; +use tendermint_rpc::endpoint::validators; +use tracing::{debug, error, info, instrument, trace, warn}; + +mod helpers; +mod manager; +mod models; + +pub type StorageTransaction = Transaction<'static, Sqlite>; + +#[derive(Clone)] +pub struct ScraperStorage { + pub(crate) manager: StorageManager, +} + +impl ScraperStorage { + #[instrument] + pub async fn init + Debug>(database_path: P) -> Result { + // TODO: we can inject here more stuff based on our nym-api global config + // struct. Maybe different pool size or timeout intervals? + let mut opts = sqlx::sqlite::SqliteConnectOptions::new() + .filename(database_path) + .create_if_missing(true); + + // TODO: do we want auto_vacuum ? + + opts.disable_statement_logging(); + + let connection_pool = match sqlx::SqlitePool::connect_with(opts).await { + Ok(db) => db, + Err(err) => { + error!("Failed to connect to SQLx database: {err}"); + return Err(err.into()); + } + }; + + if let Err(err) = sqlx::migrate!("./sql_migrations") + .run(&connection_pool) + .await + { + error!("Failed to initialize SQLx database: {err}"); + return Err(err.into()); + } + + info!("Database migration finished!"); + + let manager = StorageManager { connection_pool }; + manager.set_initial_metadata().await?; + + let storage = ScraperStorage { manager }; + + Ok(storage) + } + + #[instrument(skip_all)] + pub async fn begin_processing_tx(&self) -> Result { + debug!("starting storage tx"); + self.manager + .connection_pool + .begin() + .await + .map_err(|source| ScraperError::StorageTxBeginFailure { source }) + } + + pub async fn get_first_block_height_after( + &self, + time: OffsetDateTime, + ) -> Result, ScraperError> { + Ok(self.manager.get_first_block_height_after(time).await?) + } + + pub async fn get_last_block_height_before( + &self, + time: OffsetDateTime, + ) -> Result, ScraperError> { + Ok(self.manager.get_last_block_height_before(time).await?) + } + + pub async fn get_signed_between( + &self, + consensus_address: &str, + start_height: i64, + end_height: i64, + ) -> Result { + Ok(self + .manager + .get_signed_between(consensus_address, start_height, end_height) + .await?) + } + + pub async fn get_signed_between_times( + &self, + consensus_address: &str, + start_time: OffsetDateTime, + end_time: OffsetDateTime, + ) -> Result { + let Some(block_start) = self.get_first_block_height_after(start_time).await? else { + return Ok(0); + }; + let Some(block_end) = self.get_last_block_height_before(end_time).await? else { + return Ok(0); + }; + + self.get_signed_between(consensus_address, block_start, block_end) + .await + } + + pub async fn get_block_signers(&self, height: i64) -> Result, ScraperError> { + Ok(self.manager.get_block_validators(height).await?) + } + + pub async fn get_last_processed_height(&self) -> Result { + Ok(self.manager.get_last_processed_height().await?) + } +} + +pub async fn persist_block( + block: &FullBlockInformation, + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + let total_gas = crate::helpers::tx_gas_sum(&block.transactions); + + // SANITY CHECK: make sure the block proposer is present in the validator set + block.ensure_proposer()?; + + // persist validators + persist_validators(&block.validators, tx).await?; + + // persist block data + persist_block_data(&block.block, total_gas, tx).await?; + + // persist commits + if let Some(commit) = &block.block.last_commit { + persist_commits(commit, &block.validators, tx).await?; + } else { + warn!("no commits for block {}", block.block.header.height) + } + + // persist txs + persist_txs(&block.transactions, tx).await?; + + // persist messages (inside the transactions) + persist_messages(&block.transactions, tx).await?; + + update_last_processed(block.block.header.height.into(), tx).await?; + + Ok(()) +} + +async fn persist_validators( + validators: &validators::Response, + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + debug!("persisting {} validators", validators.total); + for validator in &validators.validators { + let consensus_address = crate::helpers::validator_consensus_address(validator.address)?; + let consensus_pubkey = crate::helpers::validator_pubkey_to_bech32(validator.pub_key)?; + + insert_validator( + consensus_address.to_string(), + consensus_pubkey.to_string(), + &mut *tx, + ) + .await?; + } + + Ok(()) +} + +async fn persist_block_data( + block: &Block, + total_gas: i64, + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + let proposer_address = + crate::helpers::validator_consensus_address(block.header.proposer_address)?.to_string(); + + insert_block( + block.header.height.into(), + block.header.hash().to_string(), + block.data.len() as u32, + total_gas, + proposer_address, + block.header.time.into(), + tx, + ) + .await?; + Ok(()) +} + +async fn persist_commits( + commits: &Commit, + validators: &validators::Response, + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + debug!("persisting up to {} commits", commits.signatures.len()); + let height: i64 = commits.height.into(); + + for commit_sig in &commits.signatures { + let (validator_id, timestamp, signature) = match commit_sig { + CommitSig::BlockIdFlagAbsent => { + trace!("absent signature"); + continue; + } + CommitSig::BlockIdFlagCommit { + validator_address, + timestamp, + signature, + } => (validator_address, timestamp, signature), + CommitSig::BlockIdFlagNil { + validator_address, + timestamp, + signature, + } => (validator_address, timestamp, signature), + }; + + let validator = crate::helpers::validator_info(*validator_id, validators)?; + let validator_address = crate::helpers::validator_consensus_address(*validator_id)?; + + if signature.is_none() { + warn!("empty signature for {validator_address} at height {height}"); + continue; + } + + insert_precommit( + validator_address.to_string(), + height, + (*timestamp).into(), + validator.power.into(), + validator.proposer_priority.value(), + &mut *tx, + ) + .await?; + } + + Ok(()) +} + +async fn persist_txs( + txs: &[ParsedTransactionResponse], + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + debug!("persisting {} txs", txs.len()); + + for chain_tx in txs { + insert_transaction( + chain_tx.hash.to_string(), + chain_tx.height.into(), + chain_tx.index as i64, + chain_tx.tx_result.code.is_ok(), + chain_tx.tx.body.messages.len() as i64, + chain_tx.tx.body.memo.clone(), + chain_tx.tx_result.gas_wanted, + chain_tx.tx_result.gas_used, + chain_tx.tx_result.log.clone(), + &mut *tx, + ) + .await?; + } + + Ok(()) +} + +async fn persist_messages( + txs: &[ParsedTransactionResponse], + tx: &mut StorageTransaction, +) -> Result<(), ScraperError> { + debug!("persisting messages"); + + for chain_tx in txs { + for (index, msg) in chain_tx.tx.body.messages.iter().enumerate() { + insert_message( + chain_tx.hash.to_string(), + index as i64, + msg.type_url.clone(), + chain_tx.height.into(), + &mut *tx, + ) + .await? + } + } + + Ok(()) +} diff --git a/common/nyxd-scraper/src/storage/models.rs b/common/nyxd-scraper/src/storage/models.rs new file mode 100644 index 0000000000..38006bfdaf --- /dev/null +++ b/common/nyxd-scraper/src/storage/models.rs @@ -0,0 +1,30 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use sqlx::types::time::OffsetDateTime; +use sqlx::FromRow; + +#[derive(Debug, Clone, FromRow)] +pub struct Validator { + pub consensus_address: String, + pub consensus_pubkey: String, +} + +#[derive(Debug, Clone, FromRow)] +pub struct Block { + pub height: i64, + pub hash: String, + pub num_txs: u32, + pub total_gas: i64, + pub proposer_address: String, + pub timestamp: OffsetDateTime, +} + +#[derive(Debug, Clone, FromRow)] +pub struct CommitSignature { + pub height: i64, + pub validator_address: String, + pub voting_power: i64, + pub proposer_priority: i64, + pub timestamp: OffsetDateTime, +} diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index a02684958f..80aefcf934 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-socks5-client-core" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/socks5/ordered-buffer/Cargo.toml b/common/socks5/ordered-buffer/Cargo.toml index b92662f633..f8d684cd1f 100644 --- a/common/socks5/ordered-buffer/Cargo.toml +++ b/common/socks5/ordered-buffer/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-ordered-buffer" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/socks5/proxy-helpers/Cargo.toml b/common/socks5/proxy-helpers/Cargo.toml index 6ebaaa03c9..374a219061 100644 --- a/common/socks5/proxy-helpers/Cargo.toml +++ b/common/socks5/proxy-helpers/Cargo.toml @@ -3,13 +3,14 @@ name = "nym-socks5-proxy-helpers" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bytes = "1.0" -tokio = { version = "1.24.1", features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] } -tokio-util = { version = "0.7.4", features = [ "io" ] } # reason for getting this guy is to to able to port to tokio 1.X more quickly by being able to use +tokio = { workspace = true, features = [ "net", "io-util", "sync", "macros", "time", "rt-multi-thread" ] } +tokio-util = { workspace = true, features = [ "io" ] } # reason for getting this guy is to to able to port to tokio 1.X more quickly by being able to use # their `read_buf` [from the util crate] replacement rather than having to rethink/reimplement `AvailableReader` with the new AsyncRead trait definition. # In the long run, the dependency should probably get removed in favour of pure-tokio implementation, but for time being it's fine. futures = { workspace = true } diff --git a/common/socks5/requests/Cargo.toml b/common/socks5/requests/Cargo.toml index a5b60ee08f..db2784c5b4 100644 --- a/common/socks5/requests/Cargo.toml +++ b/common/socks5/requests/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-socks5-requests" version = "0.1.0" authors = ["Dave Hrycyszyn "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/statistics/Cargo.toml b/common/statistics/Cargo.toml index 8a6974096d..f431de5572 100644 --- a/common/statistics/Cargo.toml +++ b/common/statistics/Cargo.toml @@ -5,6 +5,7 @@ name = "nym-statistics-common" version = "1.0.1" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,6 +15,6 @@ log = { workspace = true } reqwest = { workspace = true, features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1" -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "chrono"]} +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "chrono"]} thiserror = { workspace = true } tokio = { version = "1.24.1", features = [ "time" ] } diff --git a/common/store-cipher/Cargo.toml b/common/store-cipher/Cargo.toml index b50a3dc3c3..cbc53aac60 100644 --- a/common/store-cipher/Cargo.toml +++ b/common/store-cipher/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-store-cipher" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,4 +21,4 @@ getrandom = { version = "0.2", features = ["js"] } [features] default = [] -json = ["serde_json"] \ No newline at end of file +json = ["serde_json"] diff --git a/common/task/src/manager.rs b/common/task/src/manager.rs index 03e92f05cf..35b4df3bf8 100644 --- a/common/task/src/manager.rs +++ b/common/task/src/manager.rs @@ -493,10 +493,16 @@ impl TaskClient { impl Drop for TaskClient { fn drop(&mut self) { if !self.mode.should_signal_on_drop() { - self.log(Level::Debug, "the task client is getting dropped"); + self.log( + Level::Trace, + "the task client is getting dropped but inststructed to not signal: this is expected during client shutdown", + ); return; } else { - self.log(Level::Info, "the task client is getting dropped"); + self.log( + Level::Debug, + "the task client is getting dropped: this is expected during client shutdown", + ); } if !self.is_shutdown_poll() { diff --git a/common/task/src/signal.rs b/common/task/src/signal.rs index e3fb3786a2..483c7c630b 100644 --- a/common/task/src/signal.rs +++ b/common/task/src/signal.rs @@ -1,7 +1,6 @@ use crate::{manager::SentError, TaskManager}; #[cfg(unix)] -#[deprecated] pub async fn wait_for_signal() { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel"); diff --git a/common/tun/Cargo.toml b/common/tun/Cargo.toml new file mode 100644 index 0000000000..2cde7db280 --- /dev/null +++ b/common/tun/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "nym-tun" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util", "time", "sync", "macros"] } +etherparse = "0.13.0" +log.workspace = true +nym-wireguard-types = { path = "../wireguard-types", optional = true } + +[target.'cfg(target_os = "linux")'.dependencies] +tokio-tun = "0.11.2" diff --git a/common/tun/src/lib.rs b/common/tun/src/lib.rs new file mode 100644 index 0000000000..2b8336d071 --- /dev/null +++ b/common/tun/src/lib.rs @@ -0,0 +1,7 @@ +#[cfg(target_os = "linux")] +mod linux; + +pub mod tun_task_channel; + +#[cfg(target_os = "linux")] +pub use linux::tun_device; diff --git a/common/wireguard/src/platform/linux/mod.rs b/common/tun/src/linux/mod.rs similarity index 100% rename from common/wireguard/src/platform/linux/mod.rs rename to common/tun/src/linux/mod.rs diff --git a/common/wireguard/src/platform/linux/tun_device.rs b/common/tun/src/linux/tun_device.rs similarity index 52% rename from common/wireguard/src/platform/linux/tun_device.rs rename to common/tun/src/linux/tun_device.rs index 022e07462b..20f514f4a9 100644 --- a/common/wireguard/src/platform/linux/tun_device.rs +++ b/common/tun/src/linux/tun_device.rs @@ -1,22 +1,49 @@ use std::{ collections::HashMap, net::{IpAddr, Ipv4Addr}, - sync::Arc, + time::Duration, }; use etherparse::{InternetSlice, SlicedPacket}; -use tap::TapFallible; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -use crate::{ - event::Event, - tun_task_channel::{ - tun_task_channel, tun_task_response_channel, TunTaskPayload, TunTaskResponseRx, - TunTaskResponseTx, TunTaskRx, TunTaskTx, - }, - udp_listener::PeersByIp, +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + time::timeout, }; +use crate::tun_task_channel::{ + tun_task_channel, tun_task_response_channel, TunTaskPayload, TunTaskResponseRx, + TunTaskResponseSendError, TunTaskResponseTx, TunTaskRx, TunTaskTx, +}; + +const TUN_WRITE_TIMEOUT_MS: u64 = 1000; + +#[derive(thiserror::Error, Debug)] +pub enum TunDeviceError { + #[error("timeout writing to tun device, dropping packet")] + TunWriteTimeout, + + #[error("error writing to tun device: {source}")] + TunWriteError { source: std::io::Error }, + + #[error("failed to forward responding packet with tag: {source}")] + ForwardNatResponseFailed { + #[from] + source: TunTaskResponseSendError, + }, + + #[error("unable to parse headers in packet")] + UnableToParseHeaders { + #[from] + source: etherparse::ReadError, + }, + + #[error("unable to parse src and dst address from packet: ip header missing")] + UnableToParseAddressIpHeaderMissing, + + #[error("unable to lock peer mutex")] + FailedToLockPeer, +} + fn setup_tokio_tun_device(name: &str, address: Ipv4Addr, netmask: Ipv4Addr) -> tokio_tun::Tun { log::info!("Creating TUN device with: address={address}, netmask={netmask}"); // Read MTU size from env variable NYM_MTU_SIZE, else default to 1420. @@ -50,12 +77,12 @@ pub struct TunDevice { } pub enum RoutingMode { - // The routing table, as how wireguard does it - AllowedIps(AllowedIpsInner), - // This is an alternative to the routing table, where we just match outgoing source IP with // incoming destination IP. Nat(NatInner), + + // Just forward without checking anything + Passthrough, } impl RoutingMode { @@ -65,15 +92,11 @@ impl RoutingMode { }) } - pub fn new_allowed_ips(peers_by_ip: Arc>) -> Self { - RoutingMode::AllowedIps(AllowedIpsInner { peers_by_ip }) + pub fn new_passthrough() -> Self { + RoutingMode::Passthrough } } -pub struct AllowedIpsInner { - peers_by_ip: Arc>, -} - pub struct NatInner { nat_table: HashMap, } @@ -89,15 +112,7 @@ impl TunDevice { routing_mode: RoutingMode, config: TunDeviceConfig, ) -> (Self, TunTaskTx, TunTaskResponseRx) { - let TunDeviceConfig { - base_name, - ip, - netmask, - } = config; - let name = format!("{base_name}%d"); - - let tun = setup_tokio_tun_device(&name, ip, netmask); - log::info!("Created TUN device: {}", tun.name()); + let tun = Self::new_device_only(config); // Channels to communicate with the other tasks let (tun_task_tx, tun_task_rx) = tun_task_channel(); @@ -113,18 +128,24 @@ impl TunDevice { (tun_device, tun_task_tx, tun_task_response_rx) } + pub fn new_device_only(config: TunDeviceConfig) -> tokio_tun::Tun { + let TunDeviceConfig { + base_name, + ip, + netmask, + } = config; + let name = format!("{base_name}%d"); + + let tun = setup_tokio_tun_device(&name, ip, netmask); + log::info!("Created TUN device: {}", tun.name()); + tun + } + // Send outbound packets out on the wild internet - async fn handle_tun_write(&mut self, data: TunTaskPayload) { + async fn handle_tun_write(&mut self, data: TunTaskPayload) -> Result<(), TunDeviceError> { let (tag, packet) = data; - let Some(dst_addr) = boringtun::noise::Tunn::dst_address(&packet) else { - log::error!("Unable to parse dst_address in packet that was supposed to be written to tun device"); - return; - }; - let Some(src_addr) = parse_src_address(&packet) else { - log::error!("Unable to parse src_address in packet that was supposed to be written to tun device"); - return; - }; - log::info!( + let ParsedAddresses { src_addr, dst_addr } = parse_src_dst_address(&packet)?; + log::debug!( "iface: write Packet({src_addr} -> {dst_addr}, {} bytes)", packet.len() ); @@ -134,62 +155,49 @@ impl TunDevice { nat_table.nat_table.insert(src_addr, tag); } - self.tun - .write_all(&packet) - .await - .tap_err(|err| { - log::error!("iface: write error: {err}"); - }) - .ok(); + timeout( + Duration::from_millis(TUN_WRITE_TIMEOUT_MS), + self.tun.write_all(&packet), + ) + .await + .map_err(|_| TunDeviceError::TunWriteTimeout)? + .map_err(|err| TunDeviceError::TunWriteError { source: err }) } // Receive reponse packets from the wild internet - async fn handle_tun_read(&self, packet: &[u8]) { - let Some(dst_addr) = boringtun::noise::Tunn::dst_address(packet) else { - log::error!("Unable to parse dst_address in packet that was read from tun device"); - return; - }; - let Some(src_addr) = parse_src_address(packet) else { - log::error!("Unable to parse src_address in packet that was read from tun device"); - return; - }; - log::info!( - "iface: read Packet({src_addr} -> {dst_addr}, {} bytes)", + async fn handle_tun_read(&self, packet: &[u8]) -> Result<(), TunDeviceError> { + let ParsedAddresses { src_addr, dst_addr } = parse_src_dst_address(packet)?; + log::debug!( + "iface: read Packet({dst_addr} <- {src_addr}, {} bytes)", packet.len(), ); // Route packet to the correct peer. match self.routing_mode { - // This is how wireguard does it, by consulting the AllowedIPs table. - RoutingMode::AllowedIps(ref peers_by_ip) => { - let peers = peers_by_ip.peers_by_ip.as_ref().lock().await; - if let Some(peer_tx) = peers.longest_match(dst_addr).map(|(_, tx)| tx) { - log::info!("Forward packet to wg tunnel"); - peer_tx - .send(Event::Ip(packet.to_vec().into())) - .await - .tap_err(|err| log::error!("{err}")) - .ok(); - return; - } - } - // But we can also do it by consulting the NAT table. RoutingMode::Nat(ref nat_table) => { if let Some(tag) = nat_table.nat_table.get(&dst_addr) { - log::info!("Forward packet with tag: {tag}"); - self.tun_task_response_tx - .send((*tag, packet.to_vec())) - .await - .tap_err(|err| log::error!("{err}")) - .ok(); - return; + log::debug!("Forward packet with NAT tag: {tag}"); + return self + .tun_task_response_tx + .try_send((*tag, packet.to_vec())) + .map_err(|err| err.into()); } } + + RoutingMode::Passthrough => { + // TODO: skip the parsing at the top of the function + log::debug!("Forward packet without checking anything"); + return self + .tun_task_response_tx + .try_send((0, packet.to_vec())) + .map_err(|err| err.into()); + } } log::info!("No peer found, packet dropped"); + Ok(()) } pub async fn run(mut self) { @@ -201,20 +209,24 @@ impl TunDevice { len = self.tun.read(&mut buf) => match len { Ok(len) => { let packet = &buf[..len]; - self.handle_tun_read(packet).await; + if let Err(err) = self.handle_tun_read(packet).await { + log::error!("iface: handle_tun_read failed: {err}") + } }, Err(err) => { log::info!("iface: read error: {err}"); - break; + // break; } }, // Writing to the TUN device Some(data) = self.tun_task_rx.recv() => { - self.handle_tun_write(data).await; + if let Err(err) = self.handle_tun_write(data).await { + log::error!("iface: handle_tun_write failed: {err}"); + } } } } - log::info!("TUN device shutting down"); + // log::info!("TUN device shutting down"); } pub fn start(self) { @@ -222,12 +234,22 @@ impl TunDevice { } } -fn parse_src_address(packet: &[u8]) -> Option { - let headers = SlicedPacket::from_ip(packet) - .tap_err(|err| log::error!("Unable to parse IP packet: {err:?}")) - .ok()?; - Some(match headers.ip? { - InternetSlice::Ipv4(ip, _) => ip.source_addr().into(), - InternetSlice::Ipv6(ip, _) => ip.source_addr().into(), - }) +struct ParsedAddresses { + src_addr: IpAddr, + dst_addr: IpAddr, +} + +fn parse_src_dst_address(packet: &[u8]) -> Result { + let headers = SlicedPacket::from_ip(packet)?; + match headers.ip { + Some(InternetSlice::Ipv4(ip, _)) => Ok(ParsedAddresses { + src_addr: ip.source_addr().into(), + dst_addr: ip.destination_addr().into(), + }), + Some(InternetSlice::Ipv6(ip, _)) => Ok(ParsedAddresses { + src_addr: ip.source_addr().into(), + dst_addr: ip.destination_addr().into(), + }), + None => Err(TunDeviceError::UnableToParseAddressIpHeaderMissing), + } } diff --git a/common/tun/src/tun_task_channel.rs b/common/tun/src/tun_task_channel.rs new file mode 100644 index 0000000000..0e0be60cec --- /dev/null +++ b/common/tun/src/tun_task_channel.rs @@ -0,0 +1,84 @@ +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] + +use std::time::Duration; + +use tokio::sync::mpsc::{ + self, + error::{SendError, SendTimeoutError, TrySendError}, +}; + +pub(crate) type TunTaskPayload = (u64, Vec); + +#[derive(Clone)] +pub struct TunTaskTx(mpsc::Sender); +pub(crate) struct TunTaskRx(mpsc::Receiver); + +impl TunTaskTx { + pub async fn send(&self, data: TunTaskPayload) -> Result<(), SendError> { + self.0.send(data).await + } + + pub fn try_send(&self, data: TunTaskPayload) -> Result<(), TrySendError> { + self.0.try_send(data) + } +} + +impl TunTaskRx { + pub(crate) async fn recv(&mut self) -> Option { + self.0.recv().await + } +} + +pub(crate) fn tun_task_channel() -> (TunTaskTx, TunTaskRx) { + let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(128); + (TunTaskTx(tun_task_tx), TunTaskRx(tun_task_rx)) +} + +const TUN_TASK_RESPONSE_SEND_TIMEOUT_MS: u64 = 1_000; + +// Send responses back from the tun device back to the PacketRelayer +pub(crate) struct TunTaskResponseTx(mpsc::Sender); +pub struct TunTaskResponseRx(mpsc::Receiver); + +#[derive(thiserror::Error, Debug)] +pub enum TunTaskResponseSendError { + #[error("failed to send tun response: {0}")] + SendTimeoutError(#[from] SendTimeoutError), + + #[error("failed to send tun response: {0}")] + SendError(#[from] SendError), + + #[error("failed to send tun response: {0}")] + TrySendError(#[from] TrySendError), +} + +impl TunTaskResponseTx { + #[allow(unused)] + pub(crate) async fn send(&self, data: TunTaskPayload) -> Result<(), TunTaskResponseSendError> { + Ok(self + .0 + .send_timeout( + data, + Duration::from_millis(TUN_TASK_RESPONSE_SEND_TIMEOUT_MS), + ) + .await?) + } + + pub(crate) fn try_send(&self, data: TunTaskPayload) -> Result<(), TunTaskResponseSendError> { + Ok(self.0.try_send(data)?) + } +} + +impl TunTaskResponseRx { + pub async fn recv(&mut self) -> Option { + self.0.recv().await + } +} + +pub(crate) fn tun_task_response_channel() -> (TunTaskResponseTx, TunTaskResponseRx) { + let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(128); + ( + TunTaskResponseTx(tun_task_tx), + TunTaskResponseRx(tun_task_rx), + ) +} diff --git a/common/types/Cargo.toml b/common/types/Cargo.toml index 4ac66b8a9b..d08e435486 100644 --- a/common/types/Cargo.toml +++ b/common/types/Cargo.toml @@ -5,6 +5,7 @@ description = "Nym common types" authors.workspace = true edition = "2021" rust-version = "1.58" +license.workspace = true [dependencies] base64 = "0.21.4" diff --git a/common/types/src/gateway.rs b/common/types/src/gateway.rs index 750871e881..90d012a5ff 100644 --- a/common/types/src/gateway.rs +++ b/common/types/src/gateway.rs @@ -77,7 +77,7 @@ impl GatewayBond { } } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct GatewayNodeDetailsResponse { pub identity_key: String, pub sphinx_key: String, @@ -88,6 +88,7 @@ pub struct GatewayNodeDetailsResponse { pub data_store: String, pub network_requester: Option, + pub ip_packet_router: Option, } impl fmt::Display for GatewayNodeDetailsResponse { @@ -98,7 +99,7 @@ impl fmt::Display for GatewayNodeDetailsResponse { writeln!(f, "bind address: {}", self.bind_address)?; writeln!( f, - "mix Port: {}, clients port: {}", + "mix port: {}, clients port: {}", self.mix_port, self.clients_port )?; @@ -107,11 +108,15 @@ impl fmt::Display for GatewayNodeDetailsResponse { if let Some(nr) = &self.network_requester { nr.fmt(f)?; } + + if let Some(ipr) = &self.ip_packet_router { + ipr.fmt(f)?; + } Ok(()) } } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct GatewayNetworkRequesterDetails { pub enabled: bool, @@ -149,7 +154,7 @@ impl fmt::Display for GatewayNetworkRequesterDetails { } } -#[derive(Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct GatewayIpPacketRouterDetails { pub enabled: bool, @@ -164,7 +169,7 @@ pub struct GatewayIpPacketRouterDetails { impl fmt::Display for GatewayIpPacketRouterDetails { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "IP packet router:")?; + writeln!(f, "ip packet router:")?; writeln!(f, "\tenabled: {}", self.enabled)?; writeln!(f, "\tconfig path: {}", self.config_path)?; diff --git a/common/wasm/client-core/src/config/mod.rs b/common/wasm/client-core/src/config/mod.rs index 6aca58b3ba..ad4fd0d83c 100644 --- a/common/wasm/client-core/src/config/mod.rs +++ b/common/wasm/client-core/src/config/mod.rs @@ -434,6 +434,10 @@ pub struct ReplySurbsWasm { /// Defines maximum amount of time given reply key is going to be valid for. /// This is going to be superseded by key rotation once implemented. pub maximum_reply_key_age_ms: u32, + + /// Defines how many mix nodes the reply surb should go through. + /// If not set, the default value is going to be used. + pub surb_mix_hops: Option, } impl Default for ReplySurbsWasm { @@ -463,6 +467,7 @@ impl From for ConfigReplySurbs { maximum_reply_key_age: Duration::from_millis( reply_surbs.maximum_reply_key_age_ms as u64, ), + surb_mix_hops: reply_surbs.surb_mix_hops, } } } @@ -484,6 +489,7 @@ impl From for ReplySurbsWasm { .as_millis() as u32, maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32, maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32, + surb_mix_hops: reply_surbs.surb_mix_hops, } } } diff --git a/common/wasm/client-core/src/config/override.rs b/common/wasm/client-core/src/config/override.rs index 147584f3d4..e8d66169cf 100644 --- a/common/wasm/client-core/src/config/override.rs +++ b/common/wasm/client-core/src/config/override.rs @@ -303,6 +303,9 @@ pub struct ReplySurbsWasmOverride { /// This is going to be superseded by key rotation once implemented. #[tsify(optional)] pub maximum_reply_key_age_ms: Option, + + #[tsify(optional)] + pub surb_mix_hops: Option, } impl From for ReplySurbsWasm { @@ -337,6 +340,7 @@ impl From for ReplySurbsWasm { maximum_reply_key_age_ms: value .maximum_reply_key_age_ms .unwrap_or(def.maximum_reply_key_age_ms), + surb_mix_hops: value.surb_mix_hops, } } } diff --git a/common/wasm/storage/Cargo.toml b/common/wasm/storage/Cargo.toml index 35681dbf9e..227d7357bb 100644 --- a/common/wasm/storage/Cargo.toml +++ b/common/wasm/storage/Cargo.toml @@ -3,6 +3,7 @@ name = "wasm-storage" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,4 +18,4 @@ indexed_db_futures = { version = " 0.3.0"} thiserror = { workspace = true } nym-store-cipher = { path = "../../store-cipher", features = ["json"] } -wasm-utils = { path = "../utils", default-features = false } \ No newline at end of file +wasm-utils = { path = "../utils", default-features = false } diff --git a/common/wasm/utils/Cargo.toml b/common/wasm/utils/Cargo.toml index 5d95fe8082..ee3536b260 100644 --- a/common/wasm/utils/Cargo.toml +++ b/common/wasm/utils/Cargo.toml @@ -3,6 +3,7 @@ name = "wasm-utils" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/wireguard-types/Cargo.toml b/common/wireguard-types/Cargo.toml index 41c5a19b16..07d440ad6b 100644 --- a/common/wireguard-types/Cargo.toml +++ b/common/wireguard-types/Cargo.toml @@ -13,6 +13,7 @@ license.workspace = true [dependencies] base64 = { workspace = true } dashmap = { workspace = true } +log = { workspace = true } serde = { workspace = true, features = ["derive"] } thiserror = { workspace = true } @@ -28,13 +29,7 @@ sha2 = { version = "0.10.8", optional = true } utoipa = { workspace = true, optional = true } serde_json = { workspace = true, optional = true } -# target-specific dependencies -[target."cfg(not(target_arch = \"wasm32\"))".dependencies.boringtun] -workspace = true - -[target."cfg(target_arch = \"wasm32\")".dependencies.x25519-dalek] -version = "2.0.0" - +x25519-dalek = { version = "2.0.0", features = ["static_secrets"] } [dev-dependencies] rand = "0.7.3" @@ -45,4 +40,4 @@ nym-crypto = { path = "../crypto", features = ["rand"]} default = ["verify"] openapi = ["utoipa", "serde_json"] # this is moved to a separate feature as we really need clients to import it (especially, *cough*, wasm) -verify = ["hmac", "sha2"] \ No newline at end of file +verify = ["hmac", "sha2"] diff --git a/common/wireguard-types/src/lib.rs b/common/wireguard-types/src/lib.rs index 6467dfa6e9..acb3366f86 100644 --- a/common/wireguard-types/src/lib.rs +++ b/common/wireguard-types/src/lib.rs @@ -13,5 +13,3 @@ pub use registration::{ #[cfg(feature = "verify")] pub use registration::HmacSha256; - -pub const WG_PORT: u16 = 51822; diff --git a/common/wireguard-types/src/public_key.rs b/common/wireguard-types/src/public_key.rs index 7843f35c4c..b6bad8ec80 100644 --- a/common/wireguard-types/src/public_key.rs +++ b/common/wireguard-types/src/public_key.rs @@ -10,20 +10,14 @@ use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::str::FromStr; -// underneath the same library is being used, i.e. x25519-dalek 2.0, -// which is being reexported by boringtun but wasm hates internals of boringtun -#[cfg(target_arch = "wasm32")] -use x25519_dalek::PublicKey as BoringtunPublicKey; - -#[cfg(not(target_arch = "wasm32"))] -use boringtun::x25519::PublicKey as BoringtunPublicKey; +use x25519_dalek::PublicKey; #[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct PeerPublicKey(BoringtunPublicKey); +pub struct PeerPublicKey(PublicKey); impl PeerPublicKey { #[allow(dead_code)] - pub fn new(key: BoringtunPublicKey) -> Self { + pub fn new(key: PublicKey) -> Self { PeerPublicKey(key) } @@ -45,7 +39,7 @@ impl Hash for PeerPublicKey { } impl Deref for PeerPublicKey { - type Target = BoringtunPublicKey; + type Target = PublicKey; fn deref(&self) -> &Self::Target { &self.0 @@ -71,7 +65,7 @@ impl FromStr for PeerPublicKey { })?; }; - Ok(PeerPublicKey(BoringtunPublicKey::from(key_arr))) + Ok(PeerPublicKey(PublicKey::from(key_arr))) } } diff --git a/common/wireguard-types/src/registration.rs b/common/wireguard-types/src/registration.rs index 1da614aaa0..e698609ab7 100644 --- a/common/wireguard-types/src/registration.rs +++ b/common/wireguard-types/src/registration.rs @@ -6,6 +6,7 @@ use crate::PeerPublicKey; use base64::{engine::general_purpose, Engine}; use dashmap::DashMap; use serde::{Deserialize, Serialize}; +use std::net::IpAddr; use std::{fmt, ops::Deref, str::FromStr}; #[cfg(feature = "verify")] @@ -17,11 +18,13 @@ use sha2::Sha256; pub type GatewayClientRegistry = DashMap; pub type PendingRegistrations = DashMap; +pub type PrivateIPs = DashMap; #[cfg(feature = "verify")] pub type HmacSha256 = Hmac; pub type Nonce = u64; +pub type Free = bool; #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(tag = "type", rename_all = "camelCase")] @@ -72,6 +75,9 @@ pub struct GatewayClient { #[cfg_attr(feature = "openapi", schema(value_type = String, format = Byte))] pub pub_key: PeerPublicKey, + /// Assigned private IP + pub private_ip: IpAddr, + /// Sha256 hmac on the data (alongside the prior nonce) #[cfg_attr(feature = "openapi", schema(value_type = String, format = Byte))] pub mac: ClientMac, @@ -79,14 +85,18 @@ pub struct GatewayClient { impl GatewayClient { #[cfg(feature = "verify")] - pub fn new(local_secret: &PrivateKey, remote_public: PublicKey, nonce: u64) -> Self { + pub fn new( + local_secret: &PrivateKey, + remote_public: PublicKey, + private_ip: IpAddr, + nonce: u64, + ) -> Self { // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek #[allow(clippy::expect_used)] - let static_secret = boringtun::x25519::StaticSecret::try_from(local_secret.to_bytes()) - .expect("conversion between x25519 private keys is infallible"); - let local_public: boringtun::x25519::PublicKey = (&static_secret).into(); + let static_secret = x25519_dalek::StaticSecret::from(local_secret.to_bytes()); + let local_public: x25519_dalek::PublicKey = (&static_secret).into(); - let remote_public = boringtun::x25519::PublicKey::from(remote_public.to_bytes()); + let remote_public = x25519_dalek::PublicKey::from(remote_public.to_bytes()); let dh = static_secret.diffie_hellman(&remote_public); @@ -96,10 +106,12 @@ impl GatewayClient { .expect("x25519 shared secret is always 32 bytes long"); mac.update(local_public.as_bytes()); + mac.update(private_ip.to_string().as_bytes()); mac.update(&nonce.to_le_bytes()); GatewayClient { pub_key: PeerPublicKey::new(local_public), + private_ip, mac: ClientMac(mac.finalize().into_bytes().to_vec()), } } @@ -110,8 +122,7 @@ impl GatewayClient { pub fn verify(&self, gateway_key: &PrivateKey, nonce: u64) -> Result<(), Error> { // convert from 1.0 x25519-dalek private key into 2.0 x25519-dalek #[allow(clippy::expect_used)] - let static_secret = boringtun::x25519::StaticSecret::try_from(gateway_key.to_bytes()) - .expect("conversion between x25519 private keys is infallible"); + let static_secret = x25519_dalek::StaticSecret::from(gateway_key.to_bytes()); let dh = static_secret.diffie_hellman(&self.pub_key); @@ -121,6 +132,7 @@ impl GatewayClient { .expect("x25519 shared secret is always 32 bytes long"); mac.update(self.pub_key.as_bytes()); + mac.update(self.private_ip.to_string().as_bytes()); mac.update(&nonce.to_le_bytes()); mac.verify_slice(&self.mac) @@ -209,6 +221,7 @@ mod tests { let client = GatewayClient::new( client_key_pair.private_key(), *gateway_key_pair.public_key(), + "10.0.0.42".parse().unwrap(), nonce, ); assert!(client.verify(gateway_key_pair.private_key(), nonce).is_ok()) diff --git a/common/wireguard/Cargo.toml b/common/wireguard/Cargo.toml index bddacbca1f..d1744d975b 100644 --- a/common/wireguard/Cargo.toml +++ b/common/wireguard/Cargo.toml @@ -11,27 +11,15 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-recursion = "1.0.4" base64 = "0.21.3" # The latest version on crates.io at the time of writing this (6.0.0) has a # version mismatch with x25519-dalek/curve25519-dalek that is resolved in the # latest commit. So pick that for now. -#boringtun = "0.6.0" -boringtun = { workspace = true } -bytes = "1.5.0" -dashmap = "5.5.3" -etherparse = "0.13.0" -futures = "0.3.28" +x25519-dalek = "2.0.0" +defguard_wireguard_rs = { git = "https://github.com/neacsu/wireguard-rs.git", rev = "c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed" } ip_network = "0.4.1" -ip_network_table = "0.2.0" log.workspace = true +nym-network-defaults = { path = "../network-defaults" } nym-task = { path = "../task" } nym-wireguard-types = { path = "../wireguard-types" } -rand.workspace = true -serde = { workspace = true, features = ["derive"] } -tap.workspace = true -thiserror.workspace = true tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } - -[target.'cfg(target_os = "linux")'.dependencies] -tokio-tun = "0.9.0" diff --git a/common/wireguard/src/active_peers.rs b/common/wireguard/src/active_peers.rs deleted file mode 100644 index a22453c861..0000000000 --- a/common/wireguard/src/active_peers.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::net::SocketAddr; - -use boringtun::x25519; -use dashmap::{ - mapref::one::{Ref, RefMut}, - DashMap, -}; -use tokio::sync::mpsc::{self}; - -use crate::event::Event; - -// Channels that are used to communicate with the various tunnels -#[derive(Clone)] -pub struct PeerEventSender(mpsc::Sender); -pub(crate) struct PeerEventReceiver(mpsc::Receiver); - -impl PeerEventSender { - pub(crate) async fn send(&self, event: Event) -> Result<(), mpsc::error::SendError> { - self.0.send(event).await - } -} - -impl PeerEventReceiver { - pub(crate) async fn recv(&mut self) -> Option { - self.0.recv().await - } -} - -pub(crate) fn peer_event_channel() -> (PeerEventSender, PeerEventReceiver) { - let (tx, rx) = mpsc::channel(16); - (PeerEventSender(tx), PeerEventReceiver(rx)) -} - -pub(crate) type PeersByKey = DashMap; -pub(crate) type PeersByAddr = DashMap; - -#[derive(Default)] -pub(crate) struct ActivePeers { - active_peers: PeersByKey, - active_peers_by_addr: PeersByAddr, -} - -impl ActivePeers { - pub(crate) fn remove(&self, public_key: &x25519::PublicKey) { - log::info!("Removing peer: {public_key:?}"); - self.active_peers.remove(public_key); - log::warn!("TODO: remove from peers_by_ip?"); - log::warn!("TODO: remove from peers_by_addr"); - } - - pub(crate) fn insert( - &self, - public_key: x25519::PublicKey, - addr: SocketAddr, - peer_tx: PeerEventSender, - ) { - self.active_peers.insert(public_key, peer_tx.clone()); - self.active_peers_by_addr.insert(addr, peer_tx); - } - - pub(crate) fn get_by_key_mut( - &self, - public_key: &x25519::PublicKey, - ) -> Option> { - self.active_peers.get_mut(public_key) - } - - pub(crate) fn get_by_addr( - &self, - addr: &SocketAddr, - ) -> Option> { - self.active_peers_by_addr.get(addr) - } -} diff --git a/common/wireguard/src/error.rs b/common/wireguard/src/error.rs deleted file mode 100644 index ec528854bd..0000000000 --- a/common/wireguard/src/error.rs +++ /dev/null @@ -1,9 +0,0 @@ -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum WgError { - #[error("unable to get tunnel")] - UnableToGetTunnel, - #[error("handshake failed")] - HandshakeFailed, -} diff --git a/common/wireguard/src/event.rs b/common/wireguard/src/event.rs deleted file mode 100644 index 226d20bafa..0000000000 --- a/common/wireguard/src/event.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::fmt::{Display, Formatter}; - -use bytes::Bytes; - -#[allow(unused)] -#[derive(Debug)] -pub enum Event { - /// IP packet received from the WireGuard tunnel that should be passed through to the - /// corresponding virtual device/internet. - Wg(Bytes), - /// IP packet received from the WireGuard tunnel that was verified as part of the handshake. - WgVerified(Bytes), - /// IP packet to be sent through the WireGuard tunnel as crafted by the virtual device. - Ip(Bytes), -} - -impl Display for Event { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Event::Wg(data) => { - let size = data.len(); - write!(f, "Wg{{ size={size} }}") - } - Event::WgVerified(data) => { - let size = data.len(); - write!(f, "WgVerified{{ size={size} }}") - } - Event::Ip(data) => { - let size = data.len(); - write!(f, "Ip{{ size={size} }}") - } - } - } -} diff --git a/common/wireguard/src/lib.rs b/common/wireguard/src/lib.rs index c449929377..d9e72f382b 100644 --- a/common/wireguard/src/lib.rs +++ b/common/wireguard/src/lib.rs @@ -3,89 +3,55 @@ // #![warn(clippy::expect_used)] // #![warn(clippy::unwrap_used)] -mod active_peers; -mod error; -mod event; -mod network_table; -mod packet_relayer; -mod platform; -mod registered_peers; -mod setup; -pub mod tun_task_channel; -mod udp_listener; -mod wg_tunnel; +pub mod setup; use nym_wireguard_types::registration::GatewayClientRegistry; use std::sync::Arc; // Currently the module related to setting up the virtual network device is platform specific. #[cfg(target_os = "linux")] -pub use platform::linux::tun_device; +use crate::setup::{peer_allowed_ips, peer_static_public_key, PRIVATE_KEY}; +use defguard_wireguard_rs::WGApi; +#[cfg(target_os = "linux")] +use defguard_wireguard_rs::{ + host::Peer, key::Key, net::IpAddrMask, InterfaceConfiguration, WireguardInterfaceApi, +}; +#[cfg(target_os = "linux")] +use nym_network_defaults::{WG_PORT, WG_TUN_DEVICE_ADDRESS}; -/// Start wireguard UDP listener and TUN device -/// -/// # Errors -/// -/// This function will return an error if either the UDP listener of the TUN device fails to start. +/// Start wireguard device #[cfg(target_os = "linux")] pub async fn start_wireguard( - task_client: nym_task::TaskClient, - gateway_client_registry: Arc, -) -> Result<(), Box> { - // TODO: make this configurable - - // We can optionally index peers by their IP like standard wireguard. If we don't then we do - // plain NAT where we match incoming destination IP with outgoing source IP. - let peers_by_ip = Arc::new(tokio::sync::Mutex::new(network_table::NetworkTable::new())); - - // Alternative 1: - let routing_mode = tun_device::RoutingMode::new_allowed_ips(peers_by_ip.clone()); - // Alternative 2: - //let routing_mode = tun_device::RoutingMode::new_nat(); - - // Start the tun device that is used to relay traffic outbound - let config = tun_device::TunDeviceConfig { - base_name: setup::TUN_BASE_NAME.to_string(), - ip: setup::TUN_DEVICE_ADDRESS.parse().unwrap(), - netmask: setup::TUN_DEVICE_NETMASK.parse().unwrap(), + mut task_client: nym_task::TaskClient, + _gateway_client_registry: Arc, +) -> Result> { + let ifname = String::from("wg0"); + let wgapi = WGApi::new(ifname.clone(), false)?; + wgapi.create_interface()?; + let interface_config = InterfaceConfiguration { + name: ifname.clone(), + prvkey: PRIVATE_KEY.to_string(), + address: WG_TUN_DEVICE_ADDRESS.to_string(), + port: WG_PORT as u32, + peers: vec![], }; - let (tun, tun_task_tx, tun_task_response_rx) = tun_device::TunDevice::new(routing_mode, config); - tun.start(); + wgapi.configure_interface(&interface_config)?; + let peer = peer_static_public_key(); + let mut peer = Peer::new(Key::new(peer.to_bytes())); + let peer_ip = peer_allowed_ips(); + let peer_ip_mask = IpAddrMask::new(peer_ip.network_address(), peer_ip.netmask()); + peer.set_allowed_ips(vec![peer_ip_mask]); + wgapi.configure_peer(&peer)?; + wgapi.configure_peer_routing(&[peer.clone()])?; - // We also index peers by a tag - let peers_by_tag = Arc::new(tokio::sync::Mutex::new(wg_tunnel::PeersByTag::new())); + tokio::spawn(async move { task_client.recv().await }); - // If we want to have the tun device on a separate host, it's the tun_task and - // tun_task_response channels that needs to be sent over the network to the host where the tun - // device is running. - - // The packet relayer's responsibility is to route packets between the correct tunnel and the - // tun device. The tun device may or may not be on a separate host, which is why we can't do - // this routing in the tun device itself. - let (packet_relayer, packet_tx) = packet_relayer::PacketRelayer::new( - tun_task_tx.clone(), - tun_task_response_rx, - peers_by_tag.clone(), - ); - packet_relayer.start(); - - // Start the UDP listener that clients connect to - let udp_listener = udp_listener::WgUdpListener::new( - packet_tx, - peers_by_ip, - peers_by_tag, - Arc::clone(&gateway_client_registry), - ) - .await?; - udp_listener.start(task_client); - - Ok(()) + Ok(wgapi) } - #[cfg(not(target_os = "linux"))] pub async fn start_wireguard( _task_client: nym_task::TaskClient, _gateway_client_registry: Arc, -) -> Result<(), Box> { +) -> Result> { todo!("WireGuard is currently only supported on Linux") } diff --git a/common/wireguard/src/network_table.rs b/common/wireguard/src/network_table.rs deleted file mode 100644 index 3e609007af..0000000000 --- a/common/wireguard/src/network_table.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::net::IpAddr; - -use ip_network::IpNetwork; -use ip_network_table::IpNetworkTable; - -#[derive(Default)] -pub struct NetworkTable { - ips: IpNetworkTable, -} - -impl NetworkTable { - pub(crate) fn new() -> Self { - Self { - ips: IpNetworkTable::new(), - } - } - - pub fn insert>(&mut self, network: N, data: T) -> Option { - self.ips.insert(network, data) - } - - pub fn longest_match>(&self, ip: I) -> Option<(IpNetwork, &T)> { - self.ips.longest_match(ip) - } -} diff --git a/common/wireguard/src/packet_relayer.rs b/common/wireguard/src/packet_relayer.rs deleted file mode 100644 index c37af0648e..0000000000 --- a/common/wireguard/src/packet_relayer.rs +++ /dev/null @@ -1,76 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use tap::TapFallible; -use tokio::sync::mpsc::{self}; - -use crate::{ - active_peers::PeerEventSender, - event::Event, - tun_task_channel::{TunTaskResponseRx, TunTaskTx}, -}; - -#[derive(Clone)] -pub struct PacketRelaySender(pub(crate) mpsc::Sender<(u64, Vec)>); -pub(crate) struct PacketRelayReceiver(pub(crate) mpsc::Receiver<(u64, Vec)>); - -pub(crate) fn packet_relay_channel() -> (PacketRelaySender, PacketRelayReceiver) { - let (tx, rx) = mpsc::channel(16); - (PacketRelaySender(tx), PacketRelayReceiver(rx)) -} - -// The tunnels send packets to the packet relayer, which then relays it to the tun device. And -// conversely, it's where the tun device send responses to, which are relayed back to the correct -// tunnel. -pub(crate) struct PacketRelayer { - // Receive packets from the various tunnels - packet_rx: PacketRelayReceiver, - - // After receive from tunnels, send to the tun device - tun_task_tx: TunTaskTx, - - // Receive responses from the tun device - tun_task_response_rx: TunTaskResponseRx, - - // After receiving from the tun device, relay back to the correct tunnel - peers_by_tag: Arc>>, -} - -impl PacketRelayer { - pub(crate) fn new( - tun_task_tx: TunTaskTx, - tun_task_response_rx: TunTaskResponseRx, - peers_by_tag: Arc>>, - ) -> (Self, PacketRelaySender) { - let (packet_tx, packet_rx) = packet_relay_channel(); - ( - Self { - packet_rx, - tun_task_tx, - tun_task_response_rx, - peers_by_tag, - }, - packet_tx, - ) - } - - pub(crate) async fn run(mut self) { - loop { - tokio::select! { - Some((tag, packet)) = self.packet_rx.0.recv() => { - log::info!("Sent packet to tun device with tag: {tag}"); - self.tun_task_tx.send((tag, packet)).await.tap_err(|e| log::error!("{e}")).ok(); - }, - Some((tag, packet)) = self.tun_task_response_rx.recv() => { - log::info!("Received response from tun device with tag: {tag}"); - if let Some(tx) = self.peers_by_tag.lock().await.get(&tag) { - tx.send(Event::Ip(packet.into())).await.tap_err(|e| log::error!("{e}")).ok(); - } - } - } - } - } - - pub(crate) fn start(self) { - tokio::spawn(async move { self.run().await }); - } -} diff --git a/common/wireguard/src/platform/mod.rs b/common/wireguard/src/platform/mod.rs deleted file mode 100644 index 2305239628..0000000000 --- a/common/wireguard/src/platform/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[cfg(target_os = "linux")] -pub(crate) mod linux; diff --git a/common/wireguard/src/registered_peers.rs b/common/wireguard/src/registered_peers.rs deleted file mode 100644 index a0a5b7f6dc..0000000000 --- a/common/wireguard/src/registered_peers.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; - -use ip_network::IpNetwork; -use nym_wireguard_types::PeerPublicKey; - -pub(crate) type PeerIdx = u32; - -#[derive(Debug)] -pub(crate) struct RegisteredPeer { - pub(crate) public_key: PeerPublicKey, - pub(crate) index: PeerIdx, - pub(crate) allowed_ips: IpNetwork, - // endpoint: SocketAddr, -} - -#[derive(Debug, Default)] -pub(crate) struct RegisteredPeers { - peers: HashMap>>, - peers_by_idx: HashMap>>, -} - -impl RegisteredPeers { - pub(crate) fn contains_key(&self, public_key: &PeerPublicKey) -> bool { - self.peers.contains_key(public_key) - } - - pub(crate) fn next_idx(&self) -> PeerIdx { - self.peers_by_idx.keys().max().unwrap_or(&0) + 1 - } - - pub(crate) async fn insert(&mut self, peer: Arc>) { - let peer_idx = { peer.lock().await.index }; - let public_key = { peer.lock().await.public_key }; - self.peers.insert(public_key, Arc::clone(&peer)); - self.peers_by_idx.insert(peer_idx, peer); - } - - #[allow(unused)] - pub(crate) async fn remove(&mut self, public_key: &PeerPublicKey) { - if let Some(peer) = self.peers.remove(public_key) { - let peer_idx = peer.lock().await.index; - if self.peers_by_idx.remove(&peer_idx).is_none() { - log::error!("Removed registered peer but no registered index was found"); - } - } - } - - pub(crate) fn get_by_key( - &self, - public_key: &PeerPublicKey, - ) -> Option<&Arc>> { - self.peers.get(public_key) - } - - pub(crate) fn get_by_idx( - &self, - peer_idx: PeerIdx, - ) -> Option<&Arc>> { - self.peers_by_idx.get(&peer_idx) - } -} diff --git a/common/wireguard/src/setup.rs b/common/wireguard/src/setup.rs index 04c11a3b55..5cda585a52 100644 --- a/common/wireguard/src/setup.rs +++ b/common/wireguard/src/setup.rs @@ -1,20 +1,14 @@ use std::net::IpAddr; use base64::{engine::general_purpose, Engine as _}; -use boringtun::x25519; use log::info; // The wireguard UDP listener pub const WG_ADDRESS: &str = "0.0.0.0"; -// The interface used to route traffic -pub const TUN_BASE_NAME: &str = "nymwg"; -pub const TUN_DEVICE_ADDRESS: &str = "10.1.0.1"; -pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0"; - // The private key of the listener // Corresponding public key: "WM8s8bYegwMa0TJ+xIwhk+dImk2IpDUKslDBCZPizlE=" -const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg="; +pub(crate) const PRIVATE_KEY: &str = "AEqXrLFT4qjYq3wmX0456iv94uM6nDj5ugp6Jedcflg="; // The AllowedIPs for the connected peer, which is one a single IP and the same as the IP that the // peer has configured on their side. @@ -28,11 +22,11 @@ fn decode_base64_key(base64_key: &str) -> [u8; 32] { .unwrap() } -pub fn server_static_private_key() -> x25519::StaticSecret { +pub fn server_static_private_key() -> x25519_dalek::StaticSecret { // TODO: this is a temporary solution for development let static_private_bytes: [u8; 32] = decode_base64_key(PRIVATE_KEY); - let static_private = x25519::StaticSecret::try_from(static_private_bytes).unwrap(); - let static_public = x25519::PublicKey::from(&static_private); + let static_private = x25519_dalek::StaticSecret::from(static_private_bytes); + let static_public = x25519_dalek::PublicKey::from(&static_private); info!( "wg public key: {}", general_purpose::STANDARD.encode(static_public) @@ -40,14 +34,14 @@ pub fn server_static_private_key() -> x25519::StaticSecret { static_private } -pub fn peer_static_public_key() -> x25519::PublicKey { +pub fn peer_static_public_key() -> x25519_dalek::PublicKey { // A single static public key is used during development // Read from NYM_PEER_PUBLIC_KEY env variable let peer = std::env::var("NYM_PEER_PUBLIC_KEY").expect("NYM_PEER_PUBLIC_KEY must be set"); let peer_static_public_bytes: [u8; 32] = decode_base64_key(&peer); - let peer_static_public = x25519::PublicKey::try_from(peer_static_public_bytes).unwrap(); + let peer_static_public = x25519_dalek::PublicKey::from(peer_static_public_bytes); info!( "Adding wg peer public key: {}", general_purpose::STANDARD.encode(peer_static_public) @@ -57,6 +51,6 @@ pub fn peer_static_public_key() -> x25519::PublicKey { pub fn peer_allowed_ips() -> ip_network::IpNetwork { let key: IpAddr = ALLOWED_IPS.parse().unwrap(); - let cidr = 0u8; + let cidr = 32u8; ip_network::IpNetwork::new_truncate(key, cidr).unwrap() } diff --git a/common/wireguard/src/tun_task_channel.rs b/common/wireguard/src/tun_task_channel.rs deleted file mode 100644 index 8928aa6049..0000000000 --- a/common/wireguard/src/tun_task_channel.rs +++ /dev/null @@ -1,54 +0,0 @@ -use tokio::sync::mpsc; - -pub(crate) type TunTaskPayload = (u64, Vec); - -#[derive(Clone)] -pub struct TunTaskTx(mpsc::Sender); -pub(crate) struct TunTaskRx(mpsc::Receiver); - -impl TunTaskTx { - pub async fn send( - &self, - data: TunTaskPayload, - ) -> Result<(), tokio::sync::mpsc::error::SendError> { - self.0.send(data).await - } -} - -impl TunTaskRx { - pub(crate) async fn recv(&mut self) -> Option { - self.0.recv().await - } -} - -pub(crate) fn tun_task_channel() -> (TunTaskTx, TunTaskRx) { - let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(16); - (TunTaskTx(tun_task_tx), TunTaskRx(tun_task_rx)) -} - -// Send responses back from the tun device back to the PacketRelayer -pub(crate) struct TunTaskResponseTx(mpsc::Sender); -pub struct TunTaskResponseRx(mpsc::Receiver); - -impl TunTaskResponseTx { - pub(crate) async fn send( - &self, - data: TunTaskPayload, - ) -> Result<(), tokio::sync::mpsc::error::SendError> { - self.0.send(data).await - } -} - -impl TunTaskResponseRx { - pub async fn recv(&mut self) -> Option { - self.0.recv().await - } -} - -pub(crate) fn tun_task_response_channel() -> (TunTaskResponseTx, TunTaskResponseRx) { - let (tun_task_tx, tun_task_rx) = tokio::sync::mpsc::channel(16); - ( - TunTaskResponseTx(tun_task_tx), - TunTaskResponseRx(tun_task_rx), - ) -} diff --git a/common/wireguard/src/udp_listener.rs b/common/wireguard/src/udp_listener.rs deleted file mode 100644 index 49aade2330..0000000000 --- a/common/wireguard/src/udp_listener.rs +++ /dev/null @@ -1,279 +0,0 @@ -use std::{net::SocketAddr, sync::Arc, time::Duration}; - -use boringtun::{ - noise::{self, handshake::parse_handshake_anon, rate_limiter::RateLimiter, TunnResult}, - x25519, -}; -use futures::StreamExt; -use log::error; -use nym_task::TaskClient; -use nym_wireguard_types::{registration::GatewayClientRegistry, PeerPublicKey, WG_PORT}; -use tap::TapFallible; -use tokio::{net::UdpSocket, sync::Mutex}; - -use crate::{ - active_peers::{ActivePeers, PeerEventSender}, - error::WgError, - event::Event, - network_table::NetworkTable, - packet_relayer::PacketRelaySender, - registered_peers::{RegisteredPeer, RegisteredPeers}, - setup::{self, WG_ADDRESS}, - wg_tunnel::PeersByTag, -}; - -const MAX_PACKET: usize = 65535; - -// Registered peers -pub(crate) type PeersByIp = NetworkTable; - -async fn add_test_peer(registered_peers: &mut RegisteredPeers) { - let peer_static_public = PeerPublicKey::new(setup::peer_static_public_key()); - let peer_index = 0; - let peer_allowed_ips = setup::peer_allowed_ips(); - let test_peer = Arc::new(tokio::sync::Mutex::new(RegisteredPeer { - public_key: peer_static_public, - index: peer_index, - allowed_ips: peer_allowed_ips, - })); - registered_peers.insert(test_peer).await; -} - -pub struct WgUdpListener { - // Our private key - static_private: x25519::StaticSecret, - - // Our public key - static_public: x25519::PublicKey, - - // The list of registered peers that we allow - registered_peers: RegisteredPeers, - - // The routing table, as defined by wireguard - peers_by_ip: Arc>, - - // ... or alternatively we can map peers by their tag - peers_by_tag: Arc>, - - // The UDP socket to the peer - udp: Arc, - - // Send data to the TUN device for sending - packet_tx: PacketRelaySender, - - // Wireguard rate limiter - rate_limiter: RateLimiter, - - gateway_client_registry: Arc, -} - -impl WgUdpListener { - pub async fn new( - packet_tx: PacketRelaySender, - peers_by_ip: Arc>, - peers_by_tag: Arc>, - gateway_client_registry: Arc, - ) -> Result> { - let wg_address = SocketAddr::new(WG_ADDRESS.parse().unwrap(), WG_PORT); - log::info!("Starting wireguard UDP listener on {wg_address}"); - let udp = Arc::new(UdpSocket::bind(wg_address).await?); - - // Setup our own keys - let static_private = setup::server_static_private_key(); - let static_public = x25519::PublicKey::from(&static_private); - let handshake_max_rate = 100u64; - let rate_limiter = RateLimiter::new(&static_public, handshake_max_rate); - - // Create a test peer for dev - let mut registered_peers = RegisteredPeers::default(); - add_test_peer(&mut registered_peers).await; - - Ok(Self { - static_private, - static_public, - registered_peers, - peers_by_ip, - peers_by_tag, - udp, - packet_tx, - rate_limiter, - gateway_client_registry, - }) - } - - pub async fn run(mut self, mut task_client: TaskClient) { - // The set of active tunnels - let active_peers = ActivePeers::default(); - // Each tunnel is run in its own task, and the task handle is stored here so we can remove - // it from `active_peers` when the tunnel is closed - let mut active_peers_task_handles = futures::stream::FuturesUnordered::new(); - - let mut buf = [0u8; MAX_PACKET]; - let mut dst_buf = [0u8; MAX_PACKET]; - - while !task_client.is_shutdown() { - tokio::select! { - () = task_client.recv() => { - log::trace!("WireGuard UDP listener: received shutdown"); - break; - } - // Reset the rate limiter every 1 sec - () = tokio::time::sleep(Duration::from_secs(1)) => { - self.rate_limiter.reset_count(); - }, - // Handle tunnel closing - Some(public_key) = active_peers_task_handles.next() => { - match public_key { - Ok(public_key) => { - active_peers.remove(&public_key); - } - Err(err) => { - error!("WireGuard UDP listener: error receiving shutdown from peer: {err}"); - } - } - }, - // Handle incoming packets - Ok((len, addr)) = self.udp.recv_from(&mut buf) => { - log::trace!("udp: received {} bytes from {}", len, addr); - - // If this addr has already been encountered, send directly to tunnel - // TODO: optimization opportunity to instead create a connected UDP socket - // inside the wg tunnel, where you can recv the data directly. - if let Some(peer_tx) = active_peers.get_by_addr(&addr) { - log::info!("udp: received {len} bytes from {addr} from known peer"); - peer_tx - .send(Event::Wg(buf[..len].to_vec().into())) - .await - .tap_err(|e| log::error!("{e}")) - .ok(); - continue; - } - - // Verify the incoming packet - let verified_packet = match self.rate_limiter.verify_packet(Some(addr.ip()), &buf[..len], &mut dst_buf) { - Ok(packet) => packet, - Err(TunnResult::WriteToNetwork(cookie)) => { - log::info!("Send back cookie to: {addr}"); - self.udp.send_to(cookie, addr).await.tap_err(|e| log::error!("{e}")).ok(); - continue; - } - Err(err) => { - log::warn!("{err:?}"); - continue; - } - }; - - // Check if this is a registered peer, if not, just skip - let registered_peer = match parse_peer( - verified_packet, - &mut self.registered_peers, - &self.static_private, - &self.static_public, - Arc::clone(&self.gateway_client_registry), - ).await { - Ok(Some(peer)) => peer.lock().await, - Ok(None) => { - log::warn!("Peer not registered: {addr}"); - continue; - } - Err(err) => { - log::error!("{err}"); - continue; - }, - }; - - // Look up if the peer is already connected - if let Some(peer_tx) = active_peers.get_by_key_mut(®istered_peer.public_key) { - // We found the peer as connected, even though the addr was not known - log::info!("udp: received {len} bytes from {addr} which is a known peer with unknown addr"); - peer_tx.send(Event::WgVerified(buf[..len].to_vec().into())) - .await - .tap_err(|err| log::error!("{err}")) - .ok(); - } else { - // If it isn't, start a new tunnel - log::info!("udp: received {len} bytes from {addr} from unknown peer, starting tunnel"); - // NOTE: we are NOT passing in the existing rate_limiter. Re-visit this - // choice later. - log::warn!("Creating new rate limiter, consider re-using?"); - let (join_handle, peer_tx, tag) = crate::wg_tunnel::start_wg_tunnel( - addr, - self.udp.clone(), - self.static_private.clone(), - *registered_peer.public_key, - registered_peer.index, - registered_peer.allowed_ips, - // self.tun_task_tx.clone(), - self.packet_tx.clone(), - ); - - self.peers_by_ip.lock().await.insert(registered_peer.allowed_ips, peer_tx.clone()); - self.peers_by_tag.lock().await.insert(tag, peer_tx.clone()); - - peer_tx.send(Event::Wg(buf[..len].to_vec().into())) - .await - .tap_err(|e| log::error!("{e}")) - .ok(); - - log::info!("Adding peer: {:?}: {addr}", registered_peer.public_key); - active_peers.insert(*registered_peer.public_key, addr, peer_tx); - active_peers_task_handles.push(join_handle); - } - }, - } - } - log::info!("WireGuard listener: shutting down"); - } - - pub fn start(self, task_client: TaskClient) { - tokio::spawn(async move { self.run(task_client).await }); - } -} - -async fn parse_peer<'a>( - verified_packet: noise::Packet<'a>, - registered_peers: &'a mut RegisteredPeers, - static_private: &x25519::StaticSecret, - static_public: &x25519::PublicKey, - gateway_client_registry: Arc, -) -> Result>>, WgError> { - let registered_peer = match verified_packet { - noise::Packet::HandshakeInit(ref packet) => { - let Ok(handshake) = parse_handshake_anon(static_private, static_public, packet) else { - return Err(WgError::HandshakeFailed); - }; - let peer_public_key = - PeerPublicKey::new(x25519::PublicKey::from(handshake.peer_static_public)); - - let already_registered = registered_peers.contains_key(&peer_public_key); - - if already_registered { - registered_peers.get_by_key(&peer_public_key) - } else if gateway_client_registry.contains_key(&peer_public_key) { - let peer_idx = registered_peers.next_idx(); - let peer = Arc::new(Mutex::new(RegisteredPeer { - public_key: peer_public_key, - index: peer_idx, - allowed_ips: setup::peer_allowed_ips(), - })); - registered_peers.insert(peer).await; - registered_peers.get_by_key(&peer_public_key) - } else { - None - } - } - noise::Packet::HandshakeResponse(packet) => { - let peer_idx = packet.receiver_idx >> 8; - registered_peers.get_by_idx(peer_idx) - } - noise::Packet::PacketCookieReply(packet) => { - let peer_idx = packet.receiver_idx >> 8; - registered_peers.get_by_idx(peer_idx) - } - noise::Packet::PacketData(packet) => { - let peer_idx = packet.receiver_idx >> 8; - registered_peers.get_by_idx(peer_idx) - } - }; - Ok(registered_peer) -} diff --git a/common/wireguard/src/wg_tunnel.rs b/common/wireguard/src/wg_tunnel.rs deleted file mode 100644 index f35f51e1bd..0000000000 --- a/common/wireguard/src/wg_tunnel.rs +++ /dev/null @@ -1,353 +0,0 @@ -use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; - -use async_recursion::async_recursion; -use boringtun::{ - noise::{errors::WireGuardError, rate_limiter::RateLimiter, Tunn, TunnResult}, - x25519, -}; -use bytes::Bytes; -use log::{debug, error, info, warn}; -use rand::RngCore; -use tap::TapFallible; -use tokio::{net::UdpSocket, sync::broadcast, time::timeout}; - -use crate::{ - active_peers::{peer_event_channel, PeerEventReceiver, PeerEventSender}, - error::WgError, - event::Event, - network_table::NetworkTable, - packet_relayer::PacketRelaySender, - registered_peers::PeerIdx, -}; - -const HANDSHAKE_MAX_RATE: u64 = 10; - -const MAX_PACKET: usize = 65535; - -// We index the tunnels by tag -pub(crate) type PeersByTag = HashMap; - -pub struct WireGuardTunnel { - // Incoming data from the UDP socket received in the main event loop - peer_rx: PeerEventReceiver, - - // UDP socket used for sending data - udp: Arc, - - // Peer endpoint - endpoint: Arc>, - - // AllowedIPs for this peer - allowed_ips: NetworkTable<()>, - - // `boringtun` tunnel, used for crypto & WG protocol - wg_tunnel: Arc>, - - // Signal close - close_tx: broadcast::Sender<()>, - close_rx: broadcast::Receiver<()>, - - // Send data to the task that handles sending data through the tun device - packet_tx: PacketRelaySender, - - tag: u64, -} - -impl Drop for WireGuardTunnel { - fn drop(&mut self) { - info!("WireGuard tunnel: dropping"); - self.close(); - } -} - -impl WireGuardTunnel { - pub(crate) fn new( - udp: Arc, - endpoint: SocketAddr, - static_private: x25519::StaticSecret, - peer_static_public: x25519::PublicKey, - index: PeerIdx, - peer_allowed_ips: ip_network::IpNetwork, - // rate_limiter: Option, - packet_tx: PacketRelaySender, - ) -> (Self, PeerEventSender, u64) { - let local_addr = udp.local_addr().unwrap(); - let peer_addr = udp.peer_addr(); - log::info!("New wg tunnel: endpoint: {endpoint}, local_addr: {local_addr}, peer_addr: {peer_addr:?}"); - - let preshared_key = None; - let persistent_keepalive = None; - - let static_public = x25519::PublicKey::from(&static_private); - let rate_limiter = Some(Arc::new(RateLimiter::new( - &static_public, - HANDSHAKE_MAX_RATE, - ))); - - let wg_tunnel = Arc::new(tokio::sync::Mutex::new( - Tunn::new( - static_private, - peer_static_public, - preshared_key, - persistent_keepalive, - index, - rate_limiter, - ) - .expect("failed to create Tunn instance"), - )); - - // Channels with incoming data that is received by the main event loop - let (peer_tx, peer_rx) = peer_event_channel(); - - // Signal close tunnel - let (close_tx, close_rx) = broadcast::channel(1); - - let mut allowed_ips = NetworkTable::new(); - allowed_ips.insert(peer_allowed_ips, ()); - - let tag = Self::new_tag(); - - let tunnel = WireGuardTunnel { - peer_rx, - udp, - endpoint: Arc::new(tokio::sync::RwLock::new(endpoint)), - allowed_ips, - wg_tunnel, - close_tx, - close_rx, - packet_tx, - tag, - }; - - (tunnel, peer_tx, tag) - } - - fn new_tag() -> u64 { - // TODO: check for collisions - rand::thread_rng().next_u64() - } - - fn close(&self) { - let _ = self.close_tx.send(()); - } - - pub async fn spin_off(&mut self) { - loop { - tokio::select! { - _ = self.close_rx.recv() => { - info!("WireGuard tunnel: received msg to close"); - break; - }, - packet = self.peer_rx.recv() => match packet { - Some(packet) => { - info!("event loop: {packet}"); - match packet { - Event::Wg(data) => { - let _ = self.consume_wg(&data) - .await - .tap_err(|err| error!("WireGuard tunnel: consume_wg error: {err}")); - }, - Event::WgVerified(data) => { - let _ = self.consume_verified_wg(&data) - .await - .tap_err(|err| error!("WireGuard tunnel: consume_verified_wg error: {err}")); - } - Event::Ip(data) => self.consume_eth(&data).await, - } - }, - None => { - info!("WireGuard tunnel: incoming UDP stream closed, closing tunnel"); - break; - }, - }, - () = tokio::time::sleep(Duration::from_millis(250)) => { - let _ = self.update_wg_timers() - .await - .map_err(|err| error!("WireGuard tunnel: update_wg_timers error: {err}")); - }, - } - } - info!("WireGuard tunnel ({}): closed", self.endpoint.read().await); - } - - async fn wg_tunnel_lock(&self) -> Result, WgError> { - timeout(Duration::from_millis(100), self.wg_tunnel.lock()) - .await - .map_err(|_| WgError::UnableToGetTunnel) - } - - #[allow(unused)] - async fn set_endpoint(&self, addr: SocketAddr) { - if *self.endpoint.read().await != addr { - log::info!("wg tunnel update endpoint: {addr}"); - *self.endpoint.write().await = addr; - } - } - - async fn consume_wg(&mut self, data: &[u8]) -> Result<(), WgError> { - let mut send_buf = [0u8; MAX_PACKET]; - let mut tunnel = self.wg_tunnel_lock().await?; - match tunnel.decapsulate(None, data, &mut send_buf) { - TunnResult::WriteToNetwork(packet) => { - let endpoint = self.endpoint.read().await; - log::info!("udp: send {} bytes to {}", packet.len(), *endpoint); - if let Err(err) = self.udp.send_to(packet, *endpoint).await { - error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}"); - }; - // Flush pending queue - loop { - let mut send_buf = [0u8; MAX_PACKET]; - match tunnel.decapsulate(None, &[], &mut send_buf) { - TunnResult::WriteToNetwork(packet) => { - log::info!("udp: send {} bytes to {}", packet.len(), *endpoint); - if let Err(err) = self.udp.send_to(packet, *endpoint).await { - error!("Failed to send decapsulation-instructed packet to WireGuard endpoint: {err:?}"); - break; - }; - } - _ => { - break; - } - } - } - } - TunnResult::WriteToTunnelV4(packet, addr) => { - if self.allowed_ips.longest_match(addr).is_some() { - self.packet_tx - .0 - .send((self.tag, packet.to_vec())) - .await - .unwrap(); - } else { - warn!("Packet from {addr} not in allowed_ips"); - } - } - TunnResult::WriteToTunnelV6(packet, addr) => { - if self.allowed_ips.longest_match(addr).is_some() { - self.packet_tx - .0 - .send((self.tag, packet.to_vec())) - .await - .unwrap(); - } else { - warn!("Packet (v6) from {addr} not in allowed_ips"); - } - } - TunnResult::Done => { - debug!("WireGuard: decapsulate done"); - } - TunnResult::Err(err) => { - error!("WireGuard: decapsulate error: {err:?}"); - } - } - Ok(()) - } - - async fn consume_verified_wg(&mut self, data: &[u8]) -> Result<(), WgError> { - // Potentially we could take some shortcuts here in the name of performance, but currently - // I don't see that the needed functions in boringtun is exposed in the public API. - // TODO: make sure we don't put double pressure on the rate limiter! - self.consume_wg(data).await - } - - async fn consume_eth(&self, data: &Bytes) { - info!("consume_eth: raw packet size: {}", data.len()); - let encapsulated_packet = self.encapsulate_packet(data).await; - info!( - "consume_eth: after encapsulate: {}", - encapsulated_packet.len() - ); - - let endpoint = self.endpoint.read().await; - info!("consume_eth: send to {}: {}", *endpoint, data.len()); - self.udp - .send_to(&encapsulated_packet, *endpoint) - .await - .unwrap(); - } - - async fn encapsulate_packet(&self, payload: &[u8]) -> Vec { - // TODO: use fixed dst and src buffers that we can reuse - let len = 148.max(payload.len() + 32); - let mut dst = vec![0; len]; - - let mut wg_tunnel = self.wg_tunnel_lock().await.unwrap(); - - match wg_tunnel.encapsulate(payload, &mut dst) { - TunnResult::WriteToNetwork(packet) => packet.to_vec(), - unexpected => { - error!("{:?}", unexpected); - vec![] - } - } - } - - async fn update_wg_timers(&mut self) -> Result<(), WgError> { - let mut send_buf = [0u8; MAX_PACKET]; - let mut tun = self.wg_tunnel_lock().await?; - let tun_result = tun.update_timers(&mut send_buf); - self.handle_routine_tun_result(tun_result).await; - Ok(()) - } - - #[async_recursion] - async fn handle_routine_tun_result<'a: 'async_recursion>(&self, result: TunnResult<'a>) { - match result { - TunnResult::WriteToNetwork(packet) => { - let endpoint = self.endpoint.read().await; - log::info!("routine: write to network: {}: {}", endpoint, packet.len()); - if let Err(err) = self.udp.send_to(packet, *endpoint).await { - error!("routine: failed to send packet: {err:?}"); - }; - } - TunnResult::Err(WireGuardError::ConnectionExpired) => { - warn!("Wireguard handshake has expired!"); - // WIP(JON): consider just closing the tunnel here - let mut buf = vec![0u8; MAX_PACKET]; - let Ok(mut peer) = self.wg_tunnel_lock().await else { - warn!("Failed to lock WireGuard peer, closing tunnel"); - self.close(); - return; - }; - peer.format_handshake_initiation(&mut buf[..], false); - self.handle_routine_tun_result(result).await; - } - TunnResult::Err(err) => { - error!("Failed to prepare routine packet for WireGuard endpoint: {err:?}"); - } - TunnResult::Done => {} - other => { - warn!("Unexpected WireGuard routine task state: {other:?}"); - } - }; - } -} - -pub(crate) fn start_wg_tunnel( - endpoint: SocketAddr, - udp: Arc, - static_private: x25519::StaticSecret, - peer_static_public: x25519::PublicKey, - peer_index: PeerIdx, - peer_allowed_ips: ip_network::IpNetwork, - packet_tx: PacketRelaySender, -) -> ( - tokio::task::JoinHandle, - PeerEventSender, - u64, -) { - let (mut tunnel, peer_tx, tag) = WireGuardTunnel::new( - udp, - endpoint, - static_private, - peer_static_public, - peer_index, - peer_allowed_ips, - packet_tx, - ); - let join_handle = tokio::spawn(async move { - tunnel.spin_off().await; - peer_static_public - }); - (join_handle, peer_tx, tag) -} diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 5855dd5322..760f982b0e 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -551,6 +551,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "deranged" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -1558,6 +1567,12 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1870,9 +1885,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -1888,9 +1903,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -2085,11 +2100,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.20" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ + "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -2097,15 +2114,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] diff --git a/deny.toml b/deny.toml index 4bed0dce56..63bf414358 100644 --- a/deny.toml +++ b/deny.toml @@ -104,8 +104,16 @@ unlicensed = "deny" # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ - #"MIT", - #"Apache-2.0", + "MIT", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "0BSD", + "MPL-2.0", + "CC0-1.0", + "Unicode-DFS-2016", + "OpenSSL", ] # List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses @@ -143,22 +151,22 @@ exceptions = [ # Some crates don't have (easily) machine readable licensing information, # adding a clarification entry for it allows you to manually specify the # licensing information -#[[licenses.clarify]] +[[licenses.clarify]] # The name of the crate the clarification applies to -#name = "ring" +name = "ring" # The optional version constraint for the crate -#version = "*" +version = "*" # The SPDX expression for the license requirements of the crate -#expression = "MIT AND ISC AND OpenSSL" +expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored # and the crate will be checked normally, which may produce warnings or errors # depending on the rest of your configuration -#license-files = [ +license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents - #{ path = "LICENSE", hash = 0xbd0eed23 } -#] + { path = "LICENSE", hash = 0xbd0eed23 } +] [licenses.private] # If true, ignores workspace crates that aren't published, or are only diff --git a/documentation/README.md b/documentation/README.md index 3defd29526..66293cf92f 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -6,7 +6,34 @@ Each directory contains a readme with more information about running and contrib * `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers) * `operators` contains node setup and maintenance guides hosted at [https://nymtech.net/operators](https://nymtech.net/operators) +> If you are looking for the Typescript SDK documentation located at [sdk.nymtech.net](https://sdk.nymtech.net) this can be found in `../sdk/typescript/docs/` + +## Contribution +* If you wish to add to the documentation please create a PR against this repo. +* If you are **adding a plugin dependency** make sure to also **add that to the list of plugins in `install_mdbook_deps.sh` line 12**. + ## Scripts * `bump_versions.sh` allows you to update the ~~`platform_release_version` and~~ `wallet_release_version` variable~~s~~ in the `book.toml` of each mdbook project at once. You can also optionally update the `minimum_rust_version` as well. Helpful for lazy-updating when cutting a new version of the docs. -* `build_all_to_dist.sh` is used by the `ci-dev.yml` and `cd-dev.yml` scripts for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers. -* `post_process.sh` is a script called by the github CI and CD workflows to post process CSS/image/href links for serving several mdbooks from a subdirectory. + +* The following scripts are used by the `ci-dev.yml` and `cd-dev.yml` scripts (located in `../.github/workflows/`): + * `build_all_to_dist.sh` is used for building all mdbook projects and moving the rendered html to `../dist/` to be rsynced with various servers. + * `install_mdbook_deps.sh` checks for an existing install of mdbook (and plugins), uninstalls them, and then installs them on a clean slate. This is to avoid weird dependency clashes if relying on an existing mdbook version. + * `post_process.sh` is used to post process CSS/image/href links for serving several mdbooks from a subdirectory. + * `removed_existing_config.sh` is used to check for existing nym client/node config files on the CI/CD server and remove it if it exists. This is to mitigate issues with `mdbook-cmdrun` where e.g. a node is already initialised, and the command fails. + +## CI/CD +Deployment of the docs is partially automated and partially manual. +* `ci-docs.yml` will run on pushes to all branches **apart from `master`** +* `cd-docs.yml` must be run manually. This pushes to a staging branch which then must be manually promoted to production. + +## Licensing and copyright information +This is a monorepo and components that make up Nym as a system are licensed individually, so for accurate information, please check individual files. + +As a general approach, licensing is as follows this pattern: + +*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+ +* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) + +* Used libraries and different components are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) or [MIT](https://mit-license.org/) + diff --git a/documentation/dev-portal/book.toml b/documentation/dev-portal/book.toml index d0dfbe0674..75313d9473 100644 --- a/documentation/dev-portal/book.toml +++ b/documentation/dev-portal/book.toml @@ -55,6 +55,11 @@ wallet_release_version = "1.2.8" command = "mdbook-last-changed" renderer = ["html"] +# used for grabbing output of binary commands for automation +# https://github.com/FauconFan/mdbook-cmdrun +[preprocessor.cmdrun] + + ######### # BUILD # ######### diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index cd930be17a..42c25632a2 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -21,6 +21,8 @@ - [NymConnect X Monero](tutorials/monero.md) - [NymConnect X Matrix](tutorials/matrix.md) - [NymConnect X Telegram](tutorials/telegram.md) +- [NymConnect X Electrum](tutorials/electrum.md) +- [NymConnect X Firo wallet](tutorials/firo.md) # Code Examples @@ -49,7 +51,7 @@ - [Querying the Chain](tutorials/cosmos-service/querying.md) - [Typescript](tutorials/typescript.md) - - [[DEPRECATED] Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) + - [Simple Service Provider](tutorials/simple-service-provider/simple-service-provider.md) - [Tutorial Overview](tutorials/simple-service-provider/overview.md) - [Preparing Your User Client Environment](tutorials/simple-service-provider/preparating-env.md) - [Building Your User Client](tutorials/simple-service-provider/user-client.md) @@ -57,11 +59,12 @@ - [Building Your Service Provider](tutorials/simple-service-provider/service-provider.md) - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) -# Shipyard Builders Hackathon 2023 -- [General Info & Resources](shipyard/general.md) -- [Hackathon Challenges](shipyard/challenges-overview.md) -- [A Note on Infrastructure](shipyard/infra.md) -- [Submission Guidelines](shipyard/guidelines.md) +[//]: # (TODO make generic ) +[//]: # (# Shipyard Builders Hackathon 2023 ) +[//]: # (- [General Info & Resources](shipyard/general.md)) +[//]: # (- [Hackathon Challenges](shipyard/challenges-overview.md)) +[//]: # (- [A Note on Infrastructure](shipyard/infra.md)) +[//]: # (- [Submission Guidelines](shipyard/guidelines.md)) # Events diff --git a/documentation/dev-portal/src/events/37c3/faq.md b/documentation/dev-portal/src/events/37c3/faq.md new file mode 100644 index 0000000000..3fc48ea00b --- /dev/null +++ b/documentation/dev-portal/src/events/37c3/faq.md @@ -0,0 +1,70 @@ +# Frequently Asked Questions @ 37C3 + +This is a FAQ page tailored for CCC 2023 event (37c3). If you interested to read more about Nym platform, you can have a look at [Nym general FAQ](https://nymtech.net/developers/faq/general-faq.html) and read through Nym's technical [documentation](https://nymtech.net/docs), [Developer Portal](https://nymtech.net/developers) and [Operators Guide](https://nymtech.net/operators). + +## NymVPN + +If this your first time hearing about NymVPN, make sure you read [NymVPN webpage](https://nymvpn.com/en), the official [support & FAQ page](https://nymvpn.com/en/support) and our guide on how to [install, run and test](./nym-vpn.md) the client. + +Below are some extra FAQ's which came out during the event. + +### What's the difference between 2-hops and 5-hops + +The default is 5-hops (including Entry and Exit Gateways), which means that the traffic goes from the local client to Entry Gateway -> though 3 layes of Mix Nodes -> to Exit Gateway -> internet. this option uses all the Nym Mixnet features for maximum privacy. + +``` + ┌─►mix──┐ mix mix + │ │ + Entry │ │ Exit +client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet + │ │ + │ │ + mix └─►mix──┘ mix +``` + +The 2-hop option is going from the local client -> Entry Gateway -> directly to Exit Gateway -> internet. This option is good for operations demanding faster connection. Keep in mind that this setup by-passes the 3 layers of Mix Nodes. + +``` + Entry Exit +client ───► Gateway ────► Gateway ───► internet +``` + +We highly recommend to read more about [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html). + +### Why do I see different sizes of packets in my terminal log? + +One of features of Nym Mixnet's clients is to break data into the same size packets called Sphinx, which is currently ~2kb. When running NymVPN, the data log shows payload sizes, which are the raw sizes of the IP packets, not Sphinx. The payload sizes will be capped by the configured MTU, which is set around 1500 bytes. + +### What is 'poisson filter' about? + +By default `--enable-poisson` is disabled and packets are sent from the local client to the Entry Gateway as quickly as possible. With the poisson process enabled the Nym client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead. + +Enabling the poisson filter is one of the key mechanisms to de-correlate input and output traffic to the Mixnet. The performance impact however is dramatic: +1 packer per 20ms is 50 packets / sec so ballpark 100kb/s. +For mobile clients that means constantly sending data eating up data allowance. + + +## Nym Mixnet Architecture and Rewards + +We have a list of questions related to Nym Nodes and the incentives behind running them under [FAQ pages](https://nymtech.net/operators/faq/mixnodes-faq.html) in our [Operators Guide](https://nymtech.net/operators). For better knowledge about Nym architecture we recommend to read [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) in our [technical documentation](https://nymtech.net/docs). + +## Project Smoosh + +Project Smoosh is a code name for a process in which different components of Nym Mixnet architecture get *smooshed* into one binary. Check out [Smoosh FAQ](https://nymtech.net/operators/faq/smoosh-faq.html) in Operators Guide to read more. + +## Exit Gateway + +Part of the the transition under code name [Project Smoosh](./faq.md#project-smoosh) is a creation of [Nym Exit Gateway](https://nymtech.net/operators/legal/exit-gateway.html) functionality. The operators running Gateways would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays. The main change will be to expand the original short [allowed.list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to a more permissive setup. An [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) will constrain the hosts that the users of the Nym VPN and Mixnet can connect to. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym VPN and Mixnet clients. + +* Read more how the exit policy gets implemented [here](https://nymtech.net/operators/faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented) +* Check out [Nym Operators Legal Forum](https://nymtech.net/operators/legal/exit-gateway.html) +* Do reach out to us during 37c3 with any experiences you may have running Tor Exit relays or legal findings and suggestions for Nym Exit Gateway operators + +## Nym Integrations and SDKs + +If you are a dev who is interested to integrate Nym, have a look on our SDK tutorials: + +* [Rust SDKs](https://nymtech.net/developers/tutorials/cosmos-service/intro.html) +* [TypeScript SDKs](https://sdk.nymtech.net/) +* [Integration FAQ](https://nymtech.net/developers/faq/integrations-faq.html) + diff --git a/documentation/dev-portal/src/events/37c3/images/image1.png b/documentation/dev-portal/src/events/37c3/images/image1.png new file mode 100644 index 0000000000..d5f9bcb2f5 Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/image1.png differ diff --git a/documentation/dev-portal/src/events/37c3/images/image2.png b/documentation/dev-portal/src/events/37c3/images/image2.png new file mode 100644 index 0000000000..14b501383c Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/image2.png differ diff --git a/documentation/dev-portal/src/events/37c3/images/image3.png b/documentation/dev-portal/src/events/37c3/images/image3.png new file mode 100644 index 0000000000..51d79d42d7 Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/image3.png differ diff --git a/documentation/dev-portal/src/events/37c3/images/image4.png b/documentation/dev-portal/src/events/37c3/images/image4.png new file mode 100644 index 0000000000..52f4d7dd20 Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/image4.png differ diff --git a/documentation/dev-portal/src/events/37c3/images/image5.png b/documentation/dev-portal/src/events/37c3/images/image5.png new file mode 100644 index 0000000000..40b9c3aab8 Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/image5.png differ diff --git a/documentation/dev-portal/src/events/37c3/images/json.png b/documentation/dev-portal/src/events/37c3/images/json.png new file mode 100644 index 0000000000..a64c288169 Binary files /dev/null and b/documentation/dev-portal/src/events/37c3/images/json.png differ diff --git a/documentation/dev-portal/src/events/37c3/nym-vpn.md b/documentation/dev-portal/src/events/37c3/nym-vpn.md new file mode 100644 index 0000000000..6b94d26a41 --- /dev/null +++ b/documentation/dev-portal/src/events/37c3/nym-vpn.md @@ -0,0 +1,457 @@ +# NymVPN alpha + +Nym has announced [NymVPN](https://nymvpn.com/en) and presented the [NymVPN Litepaper](https://nymvpn.com/nymVPN-litepaper.pdf). At CCC 2023 we have the unique opportunity to do the first alpha public testing. This page provides a how to guide, explaining steps to install and run NymVPN CLI and GUI on the Nym Sandbox testnet. + +NymVPN is a client that uses [Nym Mixnet](https://nymtech.net) to anonymise users entire internet traffic. + +The default is to run in 5-hop mode: + +``` + ┌─►mix──┐ mix mix + │ │ + Entry │ │ Exit +client ───► Gateway ──┘ mix │ mix ┌─►mix ───► Gateway ───► internet + │ │ + │ │ + mix └─►mix──┘ mix +``` + +Users can switch to 2-hop only mode, which is a faster but less private option. In this mode traffic is only sent between the two Gateway's, and is not passed between Mix Nodes. + +The client can optionally do the first connection to the entry gateway using Wireguard. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device. + +```admonish warning +NymVPN is an experimental software and it's for [testing](./nym-vpn.md#testing) purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the event), and follow the steps listed in the form [*NymVPN User research at 37c3*](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko). +``` + +## Goals + +This alpha testing will help: + +* Stabilise NymVPN client +* Understand NymVPN client behavior in various setups (OS, connectivity, etc.) +* Stabilize the VPN infrastructure and improve its reliability / speed / features (e.g. IPv6 support) +* Load test the network in Sandbox environment and identify / anticipate potential weaknesses + + +```admonish info +Our alpha testing round is done live with some participants at CCC 2023. This guide will not work for everyone, as the NymVPN binaries aren't publicly accessible yet. Note that this setup of Nym testnet Sandbox environment is limited for CCC 2023 event and some of the configurations will not work in the future. +``` + +> **If you commit to test NymVPN alpha, please start with the [user research form](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) where all the steps will be provided**. + +## Preparation + +> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets. + +We have CLI and UI binaries available for Linux (Debian based) and macOS. + +![](images/image1.png) + +* Visit the [releases page](https://github.com/nymtech/nym/releases/tag/ccc) to download the binary for your system. +* Open terminal in the same directory and make executable by running: + +```sh +# for CLI +chmod +x ./nym-vpn-cli + +# for GUI +chmod +x ./nym-vpn_0.0.0_amd64.AppImage +# make sure your path to package is correct and the package name as well +``` + +* If you prefer to use the `.deb` version for installation (Linux only), open terminal in the same directory and run: +``` +sudo dpkg -i ./.deb +# or +sudo apt-get install -f ./.deb +``` + +* For CLI: Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries. In case of GUI setup, see the steps below. + +### GUI configuration + +* Create a NymVPN config directory called `nym-vpn` in your `~/.config`, either manually or by a command: +```sh +# for Linux +mkdir $HOME/.config/nym-vpn/ + +# for macOS +mkdir $HOME/Library/Application Support/nym-vpn/ +``` +* Create the network config by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the directory `nym-vpn` you just created +* Create the main config file called `config.toml` in the same directory with this content: +```toml +# for Linux +env_config_file = "/home//.config/nym-vpn/sandbox.env" # change to your username +entry_node_location = "DE" # two letters country code + +# for macOS +env_config_file = "$HOME/Library/Application Support/nym-vpn/" +entry_node_location = "DE" # two letters country code +``` + +## Running + +***For NymVPN to work, all other VPNs must be switched off!*** + +**Note:** At this alpha stage of NymVPN, network connection (wifi) must be re-connected after or in-between NymVPN testing rounds. + +* Get your private key for wireguard setup [here](https://nymvpn.com/en/37c3) +* See a JSON list of all Gateways [here](https://nymvpn.com/en/ccc/api/gateways) +* As NymVPN need a permission for network settings, it is run with `sudo` root command + +### CLI + +Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary. + +```admonish info +The entire command syntax with all the needed arguments' values and your private key for testing purposes can be found on our event webpage [nymvpn.com/en/37c3](https://nymvpn.com/en/37c3). +``` + +Running a help command: + +```sh +./nym-vpn-cli --help +``` + +~~~admonish example collapsible=true title="Console output" +``` +Usage: nym-vpn-cli [OPTIONS] + +Options: + -c, --config-env-file + Path pointing to an env file describing the network + --mixnet-client-path + Path to the data directory of a previously initialised mixnet client, where the keys reside + --entry-gateway-id + Mixnet public ID of the entry gateway + --entry-gateway-country + Auto-select entry gateway by country ISO + --exit-router-address + Mixnet recipient address + --exit-gateway-id + + --exit-router-country + Mixnet recipient address + --enable-wireguard + Enable the wireguard traffic between the client and the entry gateway + --private-key + Associated private key + --wg-ip + The IP address of the wireguard interface + --ip + The IP address of the TUN device + --mtu + The MTU of the TUN device + --disable-routing + Disable routing all traffic through the VPN TUN device + --enable-two-hop + Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway + --enable-poisson-rate + Enable Poisson process rate limiting of outbound traffic + -h, --help + Print help + -V, --version + Print version + + +``` +~~~ + +To run the CLI a few things need to be specified: + +```sh +sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id --exit-router-address --enable-wireguard --private-key --wg-ip +``` + +### Options Flags + +Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to adjusted by users: + +- `-c` is a path to the [Sandbox config](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) file saved as `sandbox.env` +- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`) from [here](https://nymvpn.com/en/ccc/api/gateways) +- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`) from here [here](https://nymvpn.com/en/ccc/api/gateways) +- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device +- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/37c3) +- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/37c3) +- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops) + +**More advanced options** + +- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead. +- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway. +- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway. +- `--disable-routing`: Disable routing all traffic through the VPN TUN device. + +### GUI + +If you installed the `.deb` package you may be able to have a NymVPN application icon in your app menu. However this may not work as the application needs root permission. + +Make sure you went through the GUI configuration in the [preparation section](./nym-vpn.md#gui-configuration). Then open terminal in the same directory where you [installed](./nym-vpn.md#preparation) the binary run: + +```sh +sudo -E ./nym-vpn_0.0.0_amd64.AppImage + +# In case you use another binary, just add the correct name as: +sudo -E ./ +``` + +In case of errors, see [troubleshooting section](./nym-vpn.md#macos-alert-on-nymvpn-ui-startup). + + +## Testing + +One of the main aims of the demo is testing; your results will help us to make NymVPN robust and stabilise both the client and the network through provided measurements. Before you go into testing, make sure you went through [preparation steps](./nym-vpn.md#preparation) and the [CLI binary](https://github.com/nymtech/nym/releases/tag/ccc) for your system is ready to run. + +1. Create a directory called `nym-vpn-tests` and copy your `nym-vpn-cli` binary and `sandbox.env` to that directory +2. Copy the [block below](./nym-vpn.md#testssh) and save it to the same folder as `tests.sh` +3. Open terminal in the same directory +4. Turn off any existing VPN's (including the NymVPN instances) and make the script executable by running `chmod +x ./tests.sh` +5. Run the tests: `sudo ./tests.sh` +6. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below +7. The script will print a JSON view of existing Gateways and prompt you to: + - ***(Make sure to use two different Gateways for entry and exit!)*** + - `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`) + - `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`) + - `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/37c3) +8. Note that the testing script doesn't print many logs, in case of doubts you can check logs in the log file `temp_log.txt` located in the same directory. +9. The script shall run the tests and generate a folder called `tests_` and files `perf_test_results.log` or `two_hop_perf_test_results.log` as well as some temp files. This is how the directory structure will look like: +```sh +nym-vpn-tests +├── tests.sh +├── nym-vpn-cli +├── sandbox.env +├── perf_test_results.log +├── tests_ +│   ├── api_response_times.txt +│   ├── download_time.txt +│   └── ping_results.txt +├── timeout +└── two_hop_perf_test_results.log +``` +10. In case of errors, see [troubleshooting section](./nym-vpn.md#missing-jq-error) below +11. When the tests are finished, remove the `nym-vpn-cli` binary from the folder and compress it as `nym-vpn-tests.zip` +12. Upload this compressed file to the [questionnaire](https://opnform.com/forms/nymvpn-user-research-at-37c3-yccqko) upload field when prompted. + +#### tests.sh + +```sh +#!/bin/bash + +ENDPOINT="https://sandbox-nym-api1.nymtech.net/api/v1/gateways/described" +json_array=() +echo "🚀 🏎 - please be patient, i'm fetching you your entry points - 🚀 🏎 " + +data=$(curl -s "$ENDPOINT" | jq -c '.[] | {host: .bond.gateway.host, hostname: .self_described.host_information.hostname, identity_key: .bond.gateway.identity_key, exitGateway: .self_described.ip_packet_router.address}') + +while IFS= read -r entry; do + host=$(echo "$entry" | jq -r '.host') + hostname=$(echo "$entry" | jq -r '.hostname') + identity_key=$(echo "$entry" | jq -r '.identity_key') + exit_gateway_address=$(echo "$entry" | jq -r '.exitGateway // empty') + valid_ip=$(echo "$host") + + if [ -n "$exit_gateway_address" ]; then + exit_gateway="{\"address\": \"${exit_gateway_address}\"}" + else + exit_gateway="{}" + fi + if [[ $valid_ip =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + country_info=$(curl -s "http://ipinfo.io/${valid_ip}/country" | tr -d '\n') + country_info_escaped=$(echo "$country_info" | tr -d '\n' | jq -aRs . | tr -d '"') + else + country_info_escaped="" + fi + json_object="{\"hostname\": \"${hostname}\", \"identityKey\": \"${identity_key}\", \"exitGateway\": ${exit_gateway}, \"location\": \"${country_info_escaped}\"}" + json_array+=("$json_object") +done < <(echo "$data") + +if [ $? -ne 0 ]; then + echo "error fetching data from endpoint" + exit 1 +fi + +download_file() { + local file_url=$1 + local output_file=$2 + local time_file=$3 + + echo "starting download speed test..." + local start_time=$(date +%s) + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + wget -O $output_file $file_url + elif [[ "$OSTYPE" == "darwin"* ]]; then + curl -o $output_file $file_url + fi + local end_time=$(date +%s) + local elapsed_time=$((end_time - start_time)) + echo "download speed test completed in $elapsed_time seconds." >"$time_file" +} + +if ! command -v jq &>/dev/null; then + echo "jq is not installed. Please install jq to proceed." + exit 1 +fi + +temp_log_file="temp_log.txt" + +perform_tests() { + local gateway_id=$1 + local exit_address=$2 + local test_directory="tests_${gateway_id}_${exit_address}" + local file_url="http://ipv4.download.thinkbroadband.com/2MB.zip" + + mkdir -p "$test_directory" + local ping_results_file="${test_directory}/ping_results.txt" + local download_time_file="${test_directory}/download_time.txt" + local api_response_file="${test_directory}/api_response_times.txt" + + # ping test + echo "starting ping test..." + for site in google.com youtube.com facebook.com baidu.com wikipedia.org amazon.com twitter.com instagram.com yahoo.com ebay.com netflix.com; do + ping -c 4 $site >>"$ping_results_file" + done + echo "ping test completed. Results saved in $ping_results_file" + + # download speed test + download_file $file_url /dev/null "$download_time_file" + + # api test + local api_endpoint="https://validator.nymtech.net/api/v1/mixnodes" + local iterations=10 + >"$api_response_file" + for i in $(seq 1 $iterations); do + local start_time=$(date +%s) + local response=$(curl -s -o /dev/null -w '%{http_code}' $api_endpoint) + local end_time=$(date +%s) + + local elapsed_seconds=$((end_time - start_time)) + local hours=$((elapsed_seconds / 3600)) + local minutes=$(((elapsed_seconds % 3600) / 60)) + local seconds=$((elapsed_seconds % 60)) + + local human_readable_time=$(printf "%02dh:%02dm:%02ds" $hours $minutes $seconds) + echo "iteration $i: response Time = ${human_readable_time}, status code = $response" >>"$api_response_file" + done + echo "api response test completed. Results saved in $api_response_file." +} + +printf "%s\n" "${json_array[@]}" | jq -s . + +read -p "enter a gateway ID: " identity_key +read -p "enter an exit address: " exit_address + +while true; do + read -p "enable WireGuard? (yes/no): " enable_wireguard + enable_wireguard=$(echo "$enable_wireguard" | tr '[:upper:]' '[:lower:]') + + case "$enable_wireguard" in + "yes") + read -p "enter your WireGuard private key: " priv_key + read -p "enter your WireGuard IP: " wg_ip + wireguard_options="--enable-wireguard --private-key $priv_key --wg-ip $wg_ip" + break + ;; + "no") + wireguard_options="" + break + ;; + *) + echo "invalid response. please enter 'yes' or 'no'." + ;; + esac +done + +sudo ./nym-vpn-cli -c sandbox.env --entry-gateway-id ${identity_key} --exit-router-address ${exit_address} --enable-two-hop $wireguard_options >"$temp_log_file" 2>&1 & + +timeout=15 +start_time=$(date +%s) +while true; do + current_time=$(date +%s) + if grep -q "received plain" "$temp_log_file"; then + echo "successful configuration with identity_key: $identity_key and exit address: $exit_address" >>perf_test_results.log + perform_tests "$identity_key" "$exit_address" + break + fi + if ((current_time - start_time > timeout)); then + echo "failed to connect with identity_key: $identity_key using the exit address: $exit_address" >>perf_test_results.log + break + fi + sleep 1 +done + +echo "terminating nym-vpn-cli..." +pkill -f './nym-vpn-cli' +sleep 5 +rm -f "$temp_log_file" + +``` + +## Troubleshooting + +Below are listed some points which may need to be addressed when testing NymVPN alpha. If you crashed into any errors which are not listed, please contact us at the event. + +#### Thread `main` panicked + +If you see a message like: +```sh +thread 'main' panicked at /Users/runner/.cargo/git/checkouts/mullvadvpn-app-a575cf705b5dfd76/ccfbaa2/talpid-routing/src/unix.rs:301:30: +``` +Restart your wifi connection and start again. + +#### macOS alert on NymVPN UI startup + +If you are running NymVPN on mac OS for the first time, you may see this alert message: + +![](images/image3.png) + +1. Head to System Settings -> Privacy & Security and click `Allow anyway` + +![](images/image5.png) + +2. Confirm with your password or TouchID + +3. Possibly you may have to confirm again upon running the application + +#### Missing `jq` error + +In case of missing `jq` on Linux (Debian) install it with: +```sh +# Linux (Debian) +sudo apt-get install jq +# macOS +brew install jq +``` +On some Linux distributions however the [script](./nym-vpn.md#testssh) returns `jq` error even if your system claims that `jq is already the newest version`. +In that case, comment the `jq` check in the script as follows: +```sh +#if ! command -v jq &>/dev/null; then +# echo "jq is not installed. Please install jq to proceed." +# exit 1 +#fi +``` + +#### Error current_time: not found + +When running `sudo sh ./test.sh` you may see an error like: `93: current_time: not found`. This has something to do with the `current_time` setup of your system and on itself shall not have a negative impact on the test. It has nothing to do with the client at all as it only relates to the code in our testing script. + +#### Not connecting to the endpoint + +In case the automatic download of all the Gateways fail (and it shouldn't), you do an easy manual work around: + +1. Open the list of Gateways created by API [here](https://nymvpn.com/en/ccc/api/gateways) +2. On top click on `JSON` option (shall be default view) and `Save` +3. Save it as `data.json` to the `nym-vpn-tests` folder +4. Replace line 3 in the [script `tests.sh`](./nym-vpn.md#testssh) with: +```sh +NEW_ENDPOINT="http://localhost:8000/data.json" +``` +5. In a new terminal window run: +```sh +python3 -m http.server 8000 +``` +6. Continue with the steps listed in [testing section](./nym-vpn.md#testing) + + + diff --git a/documentation/dev-portal/src/events/37c3/welcome.md b/documentation/dev-portal/src/events/37c3/welcome.md new file mode 100644 index 0000000000..f7ead77c08 --- /dev/null +++ b/documentation/dev-portal/src/events/37c3/welcome.md @@ -0,0 +1,14 @@ +# Chaos Communication Congress 2023 + +At [CCC 2019](https://constructiveproof.com/posts/2020-01-27-nym-testnet-up-and-running/) we announced the first demo of the [Nym Mixnet](https://github.com/nymtech/nym). Now, four years later we are honoured to present our new product: [NymVPN](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d). NymVPN is a client/application which captures the entire network stack of a user's device, sending this traffic through either a 2-hop VPN or through the 5-hop Mixnet. CCC 2023 is the first public demo of both the GUI and CLI versions of NymVPN. + +At first glance Nym platform may seem pretty complex. To make the connection between Nym and the community of developers, operators and users participating on the event as smooth as possible, we created a segment of documentation tailored specifically for the conference. The following pages provide an easy introduction to the NymVPN alpha client. We recommend interested developers to begin with [Nym network overview](https://nymtech.net/docs/architecture/network-overview.html) and the [Mixnet traffic flow](https://nymtech.net/docs/architecture/traffic-flow.html) pages. + +**See pages below for more content:** + +* [NymVPN alpha guide](nym-vpn.md) +* [FAQ @ 37c3](faq.md) + +
+ + diff --git a/documentation/dev-portal/src/faq/integrations-faq.md b/documentation/dev-portal/src/faq/integrations-faq.md index e02cc0dd2e..649d1ff896 100644 --- a/documentation/dev-portal/src/faq/integrations-faq.md +++ b/documentation/dev-portal/src/faq/integrations-faq.md @@ -198,5 +198,7 @@ For the moment then yes, the mixnet is free to use. There are no limits on the a No, although we do recommend that apps that wish to integrate look into running some of their own infrastructure such as gateways in order to assure uptime. ### How can I find out if an application is already supported by network requester services? -You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet. +You can check the [default allowed list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) file to see which application traffic is whitelisted by default. If the domain is present on that list, it means that existing [network requesters](https://nymtech.net/docs/nodes/network-requester.html) can be used to privacy-protect your application traffic. Simply use [NymConnect](../quickstart/nymconnect-gui.md) to connect to this service through the mixnet. + +Currently we are undergoing changes on this policy under the name [Project Smoosh](https://nymtech.net/operators/faq/smoosh-faq.html) where a new type of node [Exit Gateway](https://nymtech.net/operators/legal/exit-gateway.html) will allow users to connect to much wider range of domains, restricted by our new [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). Follow the changes [here](https://nymtech.net/operators/faq/smoosh-faq.html#what-are-the-changes). diff --git a/documentation/dev-portal/src/images/firo_tutorial/firo.png b/documentation/dev-portal/src/images/firo_tutorial/firo.png new file mode 100644 index 0000000000..ed1d7a0789 Binary files /dev/null and b/documentation/dev-portal/src/images/firo_tutorial/firo.png differ diff --git a/documentation/dev-portal/src/introduction.md b/documentation/dev-portal/src/introduction.md index 6085a3038b..742853c56c 100644 --- a/documentation/dev-portal/src/introduction.md +++ b/documentation/dev-portal/src/introduction.md @@ -4,6 +4,6 @@ Welcome to the Nym Developer Portal, containing quickstart resources, user manua For more in-depth information about nodes, network traffic flows, clients, coconut etc check out the [docs](https://nymtech.net/docs). -If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book. +If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the [Operators Guides](https://nymtech.net/operators) book. -If you're looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more, make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** ! \ No newline at end of file +If you're looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more, make sure to check out the [TS SDK Handbook](https://sdk.nymtech.net/). diff --git a/documentation/dev-portal/src/licensing.md b/documentation/dev-portal/src/licensing.md index 73567d9f75..b569231b75 100644 --- a/documentation/dev-portal/src/licensing.md +++ b/documentation/dev-portal/src/licensing.md @@ -1,5 +1,11 @@ # Licensing -Nym is Free Software released under the Apache License V2. +As a general approach, licensing is as follows this pattern: -All of the contributions of the Nym core developers are © Nym Technologies SA. If you are interested in talking to us about other licenses, please get in touch. \ No newline at end of file +*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+ +* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) + +* Used libraries and different components are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) or [MIT](https://mit-license.org/) + +For accurate information, please check individual files. diff --git a/documentation/dev-portal/src/shipyard/guidelines.md b/documentation/dev-portal/src/shipyard/guidelines.md index ac8a7b18a8..1f4a901f7a 100644 --- a/documentation/dev-portal/src/shipyard/guidelines.md +++ b/documentation/dev-portal/src/shipyard/guidelines.md @@ -10,3 +10,6 @@ We expect to see the following for submissions: - If a UI-based solution: - How to run it locally? We are happy to also accept staging deployments as part of the submission (e.g. via Vercel) but this does not replace being able to run it locally. - Please make sure that your application works on commonly reproducible system environments (e.g. if you’re developing on Artix Linux please check for the necessary dependencies for more common-place OSes such as Debian, or Arch). If you are developing on Windows please make sure that it works on non-Windows machines also. Where possible please try to include build and install instructions for a variety of OSes. + +## How to submit? +Please follow the instructions [here](https://github.com/nymtech/nym/discussions/4143). \ No newline at end of file diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md index 9982403657..0e8e2ad050 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/lib.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/lib.md @@ -26,7 +26,7 @@ Since this is the file where client creation and message parsing are handled, th Below this are the chain-related `const` variables. These have been hardcoded for this demo. ```rust -pub const DEFAULT_VALIDATOR_RPC: &str = "https://sandbox-validator1.nymtech.net"; +pub const DEFAULT_VALIDATOR_RPC: &str = "https://rpc.sandbox.nymtech.net"; pub const DEFAULT_DENOM: &str = "unym"; pub const DEFAULT_PREFIX: &str = "n"; ``` diff --git a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md index 9192903001..e970821148 100644 --- a/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md +++ b/documentation/dev-portal/src/tutorials/cosmos-service/preparing-env.md @@ -29,7 +29,7 @@ cargo new nym-cosmos-service ``` [dependencies] clap = { version = "4.0", features = ["derive"] } -cosmrs = "=0.14.0" +cosmrs = "=0.15.0" tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } serde = "1.0.152" serde_json = "1.0.91" diff --git a/documentation/dev-portal/src/tutorials/electrum.md b/documentation/dev-portal/src/tutorials/electrum.md new file mode 100644 index 0000000000..bc5e9b0022 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/electrum.md @@ -0,0 +1,36 @@ +# Electrum Wallet NymConnect Integration + +Electrum is one of the most favorite Bitcoin wallet for desktop users and it is used as a backend wallet for various crypto aplications in smart phones. Electrum was among the first integrations of Nym. This easy setup allows users to enhance privacy when managing the flagship of blochain cryptocurencies Bitcoin. + +## How can I use Bitcoin over the Nym mixnet? + +> Any syntax in `<>` brackets is a user’s unique variable. Exchange with a corresponding name without the `<>` brackets. + +### NymConnect Installation + +NymConnect application is for everyone who does not want to install and run `nym-socks5-client`. NymConnect is plug-and-play, fast and easy use. Electrum Bitcoin wallet, Monero wallet (desktop and CLI) and Matrix (Element app) connects through NymConnect automatically to the Mixnet. + +1. [Download](https://nymtech.net/download/nymconnect) NymConnect +2. On Linux and Mac, make executable by opening terminal in the same directory and run: + +```sh +chmod +x ./nym-connect_ +``` + +3. Start the application +4. Click on `Connect` button to initialise the connection with the Mixnet +5. Anytime you'll need to setup Host and Port in your applications, click on `IP` and `Port` to copy the values to clipboard +6. In case you have problems such as `Gateway Issues`, try to reconnect or restart the application + +### Electrum Bitcoin wallet via NymConnect + + +To download Electrum visit the [official webpage](https://electrum.org/#download). To connect to the Mixnet follow these steps: + +7. Start and connect [NymConnect](./electrum.md#nymconnect-installation) (or [`nym-socks5-client`](https://nymtech.net/docs/clients/socks5-client.html)) +2. Start your Electrum Bitcoin wallet +3. Go to: *Tools* -> *Network* -> *Proxy* +4. Set *Use proxy* to ✅, choose `SOCKS5` from the drop-down and add the values from your NymConnect application +5. Now your Electrum Bitcoin wallet runs through the Mixnet and it will be connected only if your NymConnect or `nym-socks5-client` are connected. + +![Electrum Bitcoin wallet setup](../images/electrum_tutorial/electrum.gif) diff --git a/documentation/dev-portal/src/tutorials/firo.md b/documentation/dev-portal/src/tutorials/firo.md new file mode 100644 index 0000000000..db09a87d79 --- /dev/null +++ b/documentation/dev-portal/src/tutorials/firo.md @@ -0,0 +1,36 @@ +# Firo-Electrum Wallet NymConnect Integration + +[Firo](https://github.com/firoorg/firo#firo) (formerly Zcoin) is a privacy focused, zk-proof based cryptocurrency. Now users can enjoy Firo with network privacy by Nym as Firo's fork of Electrum wallet was integrated to work behind the Mixnet. Read more about Firo on their [official webpage](https://firo.org/). + +## How can I use Firo over the Nym Mixnet? + +> Any syntax in `<>` brackets is a user’s unique variable. Exchange with a corresponding name without the `<>` brackets. + +### NymConnect Installation + +NymConnect application is for everyone who does not want to install and run `nym-socks5-client`. NymConnect is plug-and-play, fast and easy use. Electrum Bitcoin wallet, Monero wallet (desktop and CLI) and Matrix (Element app) connects through NymConnect automatically to the Mixnet. + +1. [Download](https://nymtech.net/download/nymconnect) NymConnect +2. On Linux and Mac, make executable by opening terminal in the same directory and run: + +```sh +chmod +x ./nym-connect_ +``` + +3. Start the application +4. Click on `Connect` button to initialise the connection with the Mixnet +5. Anytime you'll need to setup Host and Port in your applications, click on `IP` and `Port` to copy the values to clipboard +6. In case you have problems such as `Gateway Issues`, try to reconnect or restart the application + +### Firo Electrum wallet via NymConnect + + +To download Firo Electrum wallet visit the [Firo's repository](https://github.com/firoorg/firo) or [Github release page](https://github.com/firoorg/electrum-firo/releases/tag/4.1.5.2). To connect to the Mixnet follow these steps: + +7. Start and connect [NymConnect](./firo.md#nymconnect-installation) (or [`nym-socks5-client`](https://nymtech.net/docs/clients/socks5-client.html)) +8. Start your Firo Electrum wallet +9. Go to: *Tools* -> *Network* -> *Proxy* +10. Set *Use proxy* to ✅, choose `SOCKS5` from the drop-down and add the values from your NymConnect application +11. Now your Firo Electrum wallet runs through the Mixnet and it will be connected only if your NymConnect or `nym-socks5-client` are connected. + +![Firo Electrum wallet setup](../images/firo_tutorial/firo.png) diff --git a/documentation/dev-portal/src/tutorials/rust-sdk.md b/documentation/dev-portal/src/tutorials/rust-sdk.md index f8413de28b..48bdb1dd3b 100644 --- a/documentation/dev-portal/src/tutorials/rust-sdk.md +++ b/documentation/dev-portal/src/tutorials/rust-sdk.md @@ -2,4 +2,4 @@ The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a seperate process on their machine. -[Read the docs](https://nymtech.net/docs/sdk/rust.html). \ No newline at end of file +[Read the docs](https://nymtech.net/docs/sdk/rust/rust.html). diff --git a/documentation/dev-portal/src/tutorials/telegram.md b/documentation/dev-portal/src/tutorials/telegram.md index 731dc0d163..adfd22adc5 100644 --- a/documentation/dev-portal/src/tutorials/telegram.md +++ b/documentation/dev-portal/src/tutorials/telegram.md @@ -16,7 +16,7 @@ Here’s how to configure Telegram with NymConnect: For more releases, check out [Github](https://github.com/nymtech/nym/tags). NymConnect is available for Linux, Windows, and MacOS. On Linux make sure NymConnect is executable. Opening a terminal in the same directory and run: ```sh - chmod +x ./.AppImage + chmod +x ./ ``` 2. **Start NymConnect** Telegram is added to NymConnect by default. diff --git a/documentation/docs/LICENSE b/documentation/docs/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/documentation/docs/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/documentation/docs/src/bandwidth-credentials.md b/documentation/docs/src/bandwidth-credentials.md index f267cb045d..f7ee0ff087 100644 --- a/documentation/docs/src/bandwidth-credentials.md +++ b/documentation/docs/src/bandwidth-credentials.md @@ -28,7 +28,7 @@ COCONUT_DKG_CONTRACT_ADDRESS="nymt1gwk6muhmzeuxje7df7rjvqwl2vex0kj4t2hwuzmyx5k62 GROUP_CONTRACT_ADDRESS="nymt14ry36mwauycz08v8ndcujghxz4hmua5epxcn0mamlr3suqe0l2qsqx5ya2" STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" -NYXD="https://sandbox-validator1.nymtech.net" +NYXD="https://rpc.sandbox.nymtech.net" NYM_API="https://sandbox-validator1-api.nymtech.net/api" ``` @@ -44,7 +44,7 @@ Next, you init the nym-client with the enabled credentials mode set to true: Using the new credentials binary, purchase some credentials for the client. The recovery directory is a directory where the credentials will be temporarily stored in case the request fails. -```./credential --config-env-file sandbox.env run --client-home-directory --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic "" --amount 50 --recovery-dir ``` +```./credential --config-env-file sandbox.env run --client-home-directory --nyxd-url https://rpc.sandbox.nymtech.net --mnemonic "" --amount 50 --recovery-dir ``` You can redeem this now by running the nym-client, in enabled credentials mode: @@ -62,7 +62,7 @@ Now time to init the socks5 client: Purchase credentials for this now too: -`./credential --config-env-file sandbox.env run --client-home-directory --nyxd-url https://sandbox-validator1.nymtech.net --mnemonic "" --amount 100 --recovery-dir ` +`./credential --config-env-file sandbox.env run --client-home-directory --nyxd-url https://rpc.sandbox.nymtech.net --mnemonic "" --amount 100 --recovery-dir ` Run the socks5 client: diff --git a/documentation/docs/src/binaries/building-nym.md b/documentation/docs/src/binaries/building-nym.md index 15b152e4fa..9b7da00a98 100644 --- a/documentation/docs/src/binaries/building-nym.md +++ b/documentation/docs/src/binaries/building-nym.md @@ -60,6 +60,9 @@ Quite a bit of stuff gets built. The key working parts are: * [socks5 client](../clients/socks5-client.md): `nym-socks5-client` * [network requester](../nodes/network-requester.md): `nym-network-requester` * [nym-cli tool](../tools/nym-cli.md): `nym-cli` +* [nym-api](https://nymtech.net/operators/nodes/nym-api.html): `nym-api` + +[//]: # (* [nymvisor](https://nymtech.net/operators/nodes/nymvisor-upgrade.html): `nymvisor`) The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. * [Nym Wallet](../wallet/desktop-wallet.md) diff --git a/documentation/docs/src/clients/socks5-client.md b/documentation/docs/src/clients/socks5-client.md index 5e45e5d1f1..f34928e1a5 100644 --- a/documentation/docs/src/clients/socks5-client.md +++ b/documentation/docs/src/clients/socks5-client.md @@ -89,7 +89,7 @@ You can check the necessary parameters for the available commands by running: Before you can use the client, you need to initalise a new instance of it, which can be done with the following command: ``` -./nym-socks5-client init --id docs-example --provider Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf +./nym-socks5-client init --id docs-example --use-reply-surbs true --provider Entztfv6Uaz2hpYHQJ6JKoaCTpDL5dja18SuQWVJAmmx.Cvhn9rBJw5Ay9wgHcbgCnVg89MPSV5s2muPV2YF1BXYu@Fo4f4SQLdoyoGkFae5TpVhRVoXCF8UiypLVGtGjujVPf ``` ~~~admonish example collapsible=true title="Console output" @@ -100,6 +100,8 @@ Before you can use the client, you need to initalise a new instance of it, which The `--id` in the example above is a local identifier so that you can name your clients and keep track of them on your local system; it is **never** transmitted over the network. +The `--use-reply-surbs` field denotes whether you wish to send [SURBs](../architecture/traffic-flow.md#private-replies-using-surbs) along with your request. It defaults to `false`, we are explicitly setting it as `true`. It defaults to `false` for compatibility with older versions of the [Network Requester](../nodes/network-requester.md). + The `--provider` field needs to be filled with the Nym address of a Network Requester that can make network requests on your behalf. If you don't want to [run your own](../nodes/network-requester.md) you can select one from the [mixnet explorer](https://explorer.nymtech.net/network-components/service-providers) by copying its `Client ID` and using this as the value of the `--provider` flag. Alternatively, you could use [this list](https://harbourmaster.nymtech.net/). Since the nodes on this list are the infrastructure for [Nymconnect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) they will support all apps on the [default whitelist](../nodes/network-requester.md#network-requester-whitelist): Keybase, Telegram, Electrum, Blockstream Green, and Helios. @@ -148,7 +150,7 @@ Alternatively, a custom host can be set in the `config.toml` file under the `soc ### Running the socks5 client -You can run the initalised client by doing this: +You can run the initialised client by doing this: ``` ./nym-socks5-client run --id docs-example diff --git a/documentation/docs/src/introduction.md b/documentation/docs/src/introduction.md index c9b4c4a7d5..189badbd0e 100644 --- a/documentation/docs/src/introduction.md +++ b/documentation/docs/src/introduction.md @@ -1,12 +1,12 @@ # Introduction -This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and mixnet explorer. +This is Nym's technical documentation, containing information and setup guides about the various pieces of Nym software such as different Mixnet infrastructure nodes, application clients, and existing applications like the desktop wallet and Mixnet explorer. -If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.md). +If you are new to Nym and want to learn about the Mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/) where you can find also our [FAQ section](https://nymtech.net/developers/faq/general-faq.html). -If you are looking for information and setup guides for the various pieces of Nym mixnet infrastructure (mix nodes, gateways and network requesters) and Nyx blockchain validators see the **new [Operators Guides](https://nymtech.net/operators)** book. +If you are looking for information and setup guides for the various pieces of Nym Mixnet infrastructure (Mix Nodes, Gateways and Network Requesters) and Nyx blockchain validators see the [Operators Guides](https://nymtech.net/operators) book. -If you're specically looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more - make sure to check out the **new [TS SDK Handbook](https://sdk.nymtech.net/)** ! +If you're specifically looking for TypeScript/JavaScript related information such as SDKs to build your own tools, step-by-step tutorials, live playgrounds and more - check out the [TS SDK Handbook](https://sdk.nymtech.net/). ## Popular pages **Network Architecture:** diff --git a/documentation/docs/src/licensing.md b/documentation/docs/src/licensing.md index 2e8612d4d4..b569231b75 100644 --- a/documentation/docs/src/licensing.md +++ b/documentation/docs/src/licensing.md @@ -1,5 +1,11 @@ # Licensing -Nym is Free Software released under the Apache License V2. +As a general approach, licensing is as follows this pattern: -All of the contributions of the Nym core developers are © Nym Technologies SA. If you are interested in talking to us about other licenses, please get in touch. +*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+ +* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) + +* Used libraries and different components are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) or [MIT](https://mit-license.org/) + +For accurate information, please check individual files. diff --git a/documentation/docs/src/nyx/interacting-with-chain.md b/documentation/docs/src/nyx/interacting-with-chain.md index 98a0fbaad0..5ebd5484bd 100644 --- a/documentation/docs/src/nyx/interacting-with-chain.md +++ b/documentation/docs/src/nyx/interacting-with-chain.md @@ -7,11 +7,11 @@ There are two options for interacting with the blockchain to send tokens or inte ## Nym-CLI tool (recommended in most cases) The `nym-cli` tool is a binary offering a simple interface for interacting with deployed smart contract (for instance, bonding and unbonding a mix node from the CLI), as well as creating and managing accounts and keypairs, sending tokens, and querying the blockchain. -Instructions on how to do so can be found on the [`nym-cli` docs page](../tools/nym-cli.md), and there are example commands in the [integrations FAQ](https://nymtech.net/developers/integrations/faq.html). +Instructions on how to do so can be found on the [`nym-cli` docs page](../tools/nym-cli.md), and there are example commands in the [integrations FAQ](https://nymtech.net/developers/faq/integrations-faq.html). ## Nyxd binary The `nyxd` binary, although more complex to compile and use, offers the full range of commands availiable to users of CosmosSDK chains. Use this if you are (e.g.) wanting to perform more granular queries about transactions from the CLI. -You can use the instructions on how to do this on from the [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state), and there are example commands in the [integrations FAQ](https://nymtech.net/developers/integrations/faq.html). +You can use the instructions on how to do this on from the [`gaiad` docs page](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#querying-the-state), and there are example commands in the [integrations FAQ](https://nymtech.net/developers/faq/integrations-faq.html). diff --git a/documentation/docs/src/sdk/rust/examples/surbs.md b/documentation/docs/src/sdk/rust/examples/surbs.md index 404d74ba1d..13de2ec98c 100644 --- a/documentation/docs/src/sdk/rust/examples/surbs.md +++ b/documentation/docs/src/sdk/rust/examples/surbs.md @@ -12,5 +12,5 @@ You can read more about how SURBs function under the hood [here](../../../archit In order to reply to an incoming message using SURBs, you can construct a `recipient` from the `sender_tag` sent along with the message you wish to reply to: ```rust,noplayground -{{#include ../../../../../../sdk/rust/nym-sdk/examples/surb-reply.rs}} +{{#include ../../../../../../sdk/rust/nym-sdk/examples/surb_reply.rs}} ``` diff --git a/documentation/docs/src/wallet/cli-wallet.md b/documentation/docs/src/wallet/cli-wallet.md index 36670190a3..a619564672 100644 --- a/documentation/docs/src/wallet/cli-wallet.md +++ b/documentation/docs/src/wallet/cli-wallet.md @@ -1,8 +1,8 @@ # CLI Wallet -If you have already read our validator setup and maintenance [documentation](../nodes/validator.md) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. +If you have already read our validator setup and maintenance [documentation](https://nymtech.net/operators/nodes/validator-setup.html) you will have seen that we compile and use the `nyxd` binary primarily for our validators. This binary can however be used for many other tasks, such as creating and using keypairs for wallets, or automated setups that require the signing and broadcasting of transactions. ### Using `nyxd` binary as a CLI wallet -You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](../nodes/validator.md) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. +You can use the `nyxd` as a minimal CLI wallet if you want to set up an account (or multiple accounts). Just compile the binary as per the documentation, **stopping after** the [building your validator](https://nymtech.net/operators/nodes/validator-setup.html#building-your-validator) step is complete. You can then run `nyxd keys --help` to see how you can set up and store different keypairs with which to interact with the Nyx blockchain. For more on interacting with the chain, see the [Interacting with Nyx Chain and Smart Contracts](../nyx/interacting-with-chain.md) page. diff --git a/documentation/install_mdbook_deps.sh b/documentation/install_mdbook_deps.sh new file mode 100755 index 0000000000..7344b1e586 --- /dev/null +++ b/documentation/install_mdbook_deps.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# simple script to automate cleaning an existing mdbook install then installing it fresh for each deploy. + +# pinning minor version allows for updates but no breaking changes +MINOR_VERSION=0.4 +# if a new plugin is added to the books it needs to be added here also +declare -a plugins=("admonish" "linkcheck" "last-changed" "theme" "variables" "cmdrun") + +# install mdbook + plugins +install_mdbook_deps() { + printf "\ninstalling mdbook..." + # installing mdbook with only specific features for speed +# cargo install mdbook --no-default-features --features search --vers "^$MINOR_VERSION" + cargo install mdbook --vers "^$MINOR_VERSION" + + printf "\ninstalling plugins..." + for i in "${plugins[@]}" + do + cargo install mdbook-$i + done + +# mdbook-admonish config +# if [ $(pwd | awk -F/ '{print $NF}') != "documentation" ]; then +# printf "not in documentation/ - changing dir but something isn't right in the workflow file" +# cd documentation/ +# mdbook-admonish install dev-portal +# mdbook-admonish install docs +# mdbook-admonish install operators +# else +# mdbook-admonish install dev-portal +# mdbook-admonish install docs +# mdbook-admonish install operators +# fi +} + +# uninstall mdbook + plugins +uninstall_mdbook_deps() { + # mdbook + printf "\nuninstalling existing mdbook installation...\n" + cargo uninstall mdbook + # check it worked + if [ $? -ne 0 ]; then + printf "\nsomething went wrong, exiting" + exit 1 + else + printf "\nmdbook deleted\n" + fi + + # plugins + printf "\nuninstalling existing plugins...\n" + for i in "${plugins[@]}" + do + cargo uninstall mdbook-$i + # check it worked + if [ $? -ne 0 ]; then + printf "\nsomething went wrong, exiting" + exit 1 + else + printf "\nmdbook-$i deleted\n" + fi + done +} + +main() { + if test -f ~/.cargo/bin/mdbook; then + printf "mdbook already installed (located at: $(which mdbook))" + uninstall_mdbook_deps; + install_mdbook_deps; + else + printf "mdbook not installed" + install_mdbook_deps; + fi +} + +main; diff --git a/documentation/operators/LICENSE b/documentation/operators/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/documentation/operators/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/documentation/operators/book.toml b/documentation/operators/book.toml index 56a5b00162..5ede8ee22a 100644 --- a/documentation/operators/book.toml +++ b/documentation/operators/book.toml @@ -83,7 +83,7 @@ curly-quotes = true # mathjax-support = false # useful if we want to pull equations in copy-fonts = true no-section-label = false -additional-css = ["theme/pagetoc.css", "././mdbook-admonish.css","./custom.css", "./mdbook-admonish.css"] +additional-css = ["theme/pagetoc.css", "./custom.css", "./mdbook-admonish.css"] additional-js = ["theme/pagetoc.js"] git-repository-url = "https://github.com/nymtech/nym" git-repository-icon = "fa-github" diff --git a/documentation/operators/src/SUMMARY.md b/documentation/operators/src/SUMMARY.md index 5d1a7049bb..fbe85719a0 100644 --- a/documentation/operators/src/SUMMARY.md +++ b/documentation/operators/src/SUMMARY.md @@ -5,7 +5,7 @@ # Binaries - [Pre-built Binaries](binaries/pre-built-binaries.md) - - [Binary Initialisation and Configuration](binaries/init-and-config.md) + - [Building from Source](binaries/building-nym.md) @@ -17,7 +17,11 @@ - [Gateway](nodes/gateway-setup.md) - [Network Requester](nodes/network-requester-setup.md) - [Nyx Validator Setup](nodes/validator-setup.md) + - [Nym API Setup](nodes/nym-api.md) - [Maintenance](nodes/maintenance.md) + - [Manual Node Upgrade](nodes/manual-upgrade.md) + +[//]: # ( - [Automatic Node Upgrade: Nymvisor Setup and Usage](nodes/nymvisor-upgrade.md)) - [Troubleshooting](nodes/troubleshooting.md) # FAQ diff --git a/documentation/operators/src/binaries/building-nym.md b/documentation/operators/src/binaries/building-nym.md index a3ff55c9b2..7d7b1a506a 100644 --- a/documentation/operators/src/binaries/building-nym.md +++ b/documentation/operators/src/binaries/building-nym.md @@ -61,7 +61,10 @@ Quite a bit of stuff gets built. The key working parts are: * [webassembly client](https://nymtech.net/docs/clients/webassembly-client.html): `webassembly-client` * [network requester](../nodes/network-requester-setup.md): `nym-network-requester` * [nym-cli tool](https://nymtech.net/docs/tools/nym-cli.html): `nym-cli` +* [nym-api](../nodes/nym-api.md): `nym-api` +[//]: # (* [nymvisor](../nodes/nymvisor-upgrade.md): `nymvisor`) + The repository also contains Typescript applications which aren't built in this process. These can be built by following the instructions on their respective docs pages. * [Nym Wallet](https://nymtech.net/docs/wallet/desktop-wallet.html) * [Nym Connect](https://nymtech.net/developers/quickstart/nymconnect-gui.html) diff --git a/documentation/operators/src/binaries/pre-built-binaries.md b/documentation/operators/src/binaries/pre-built-binaries.md index 269c26276c..1256df3d19 100644 --- a/documentation/operators/src/binaries/pre-built-binaries.md +++ b/documentation/operators/src/binaries/pre-built-binaries.md @@ -4,3 +4,31 @@ The [Github releases page](https://github.com/nymtech/nym/releases) has pre-buil If the pre-built binaries don't work or are unavailable for your system, you will need to build the platform yourself. +## Setup Binaries + +> Any syntax in `<>` brackets is a user’s unique variable. Exchange with a corresponding name without the `<>` brackets. + +### Download Binary + +1. Open [Github releases page](https://github.com/nymtech/nym/releases) and right click on the binary you want +2. Select `Copy Link` +3. Open your VPS terminal in a directory where you want to download Nym binaries. +4. Download binary by running `wget ` where `` shall be in your clipboard from point \# 2. + +### Make Executable + +5. Run command: +```sh +chmod +x +# for example: chmod +x nym-mixnode +``` +### Run Binary + +Now you can use your binary, initialise and run your Nym Node. Follow the guide according to the type of your binary. + +**Node setup and usage guides:** + +* [Mix nodes](../nodes/mix-node-setup.md) +* [Gateways](../nodes/gateway-setup.md) +* [Network requesters](../nodes/network-requester-setup.md) +* [Validators](../nodes/validator-setup.md) diff --git a/documentation/operators/src/faq/mixnodes-faq.md b/documentation/operators/src/faq/mixnodes-faq.md index 38c62154b5..c20d4d26cf 100644 --- a/documentation/operators/src/faq/mixnodes-faq.md +++ b/documentation/operators/src/faq/mixnodes-faq.md @@ -1,20 +1,20 @@ # Frequently Asked Questions -## Mixnet nodes +## Nym Nodes -### What determines the rewards when running a mix node? +### What determines the rewards when running a Mix Node? -The stake required for a mix node to achieve maximum rewards is called mix node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in mix nodes. +The stake required for a Mix Node to achieve maximum rewards is called Mix Node saturation point. This is calculated from the staking supply (all circulating supply + part of unlocked tokens). The target level of staking is to have 50% of the staking supply locked in Mix Nodes. The node stake saturation point, which we denote by Nsat, is given by the stake supply, target level of staking divided by the number of rewarded (active) nodes. -This design ensures the nodes aim to have a same size of stake (reputation) which can be done by delegation staking, as well as it ensures that there is a decentralization of staking as any higher level of staked tokens per node results in worse rewards. On the contrary, the more mix nodes are active, the lower is Nsat. The equilibrium is reached when the staked tokens are delegated equally across the active mix-nodes and that's our basis for this incentive system. +This design ensures the nodes aim to have a same size of stake (reputation) which can be done by delegation staking, as well as it ensures that there is a decentralization of staking as any higher level of staked tokens per node results in worse rewards. On the contrary, the more Mix Nodes are active, the lower is Nsat. The equilibrium is reached when the staked tokens are delegated equally across the active mix-nodes and that's our basis for this incentive system. -For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). And [here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a mix node for delegation. And finally an [update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. +For more detailed calculation, read our blog post [Nym Token Economics update](https://blog.nymtech.net/nym-token-economics-update-fedff0ed5267). More info on staking can be found [here](https://blog.nymtech.net/staking-in-nym-introducing-mainnet-mixmining-f9bb1cbc7c36). And [here](https://blog.nymtech.net/want-to-stake-in-nym-here-is-how-to-choose-a-mix-node-to-delegate-nym-to-c3b862add165) is more info on how to choose a Mix Node for delegation. And finally an [update](https://blog.nymtech.net/quarterly-token-economic-parameter-update-b2862948710f) on token economics from July 2023. ### Which VPS providers would you recommend? -Consider in which jurisdiction you reside and where do you want to run a mix node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to choose smaller and decentralised VPS providers over the most known ones controlling a majority of the internet. We receive some good feedback on these: Linode, Ghandi, Flokinet and Exoscale. Do your own research and share with the community. +Consider in which jurisdiction you reside and where do you want to run a Mix Node. Do you want to pay by crypto or not and what are the other important particularities for your case? We always recommend operators to try to choose smaller and decentralised VPS providers over the most known ones controlling a majority of the internet. We receive some good feedback on these: Linode, Ghandi, Flokinet and Exoscale. Do your own research and share with the community. @@ -22,17 +22,17 @@ Consider in which jurisdiction you reside and where do you want to run a mix nod The sizes are shown in the configs [here](https://github.com/nymtech/nym/blob/1ba6444e722e7757f1175a296bed6e31e25b8db8/common/nymsphinx/params/src/packet_sizes.rs#L12) (default is the one clients use, the others are for research purposes, not to be used in production as this would fragment the anonymity set). More info can be found [here](https://github.com/nymtech/nym/blob/4844ac953a12b29fa27688609ec193f1d560c996/common/nymsphinx/anonymous-replies/src/reply_surb.rs#L80). -### Why a mix node and a gateway cannot be bond to the same wallet? +### Why a Mix Node and a Gateway cannot be bonded with the same wallet? Because of the way the smart contract works we keep it one-node one-address at the moment. ### Which nodes are the most needed to be setup to strengthen Nym infrastructure and which ones bring rewards? -Right now only mix nodes are rewarded. We're working on gateway and service payments. Gateways are the weak link right now due mostly to lack of incentivisation. Services like Network Requesters are obviously the most necessary for people to start using the platform, and we're working on smart contracts to allow for people to start advertising them the same way they do mix nodes. +Right now only Mix Nodes are rewarded. We're working on Gateway and service payments. Gateways are the weak link right now due mostly to lack of incentivisation. Services like Network Requesters are obviously the most necessary for people to start using the platform, and we're working on smart contracts to allow for people to start advertising them the same way they do Mix Nodes. -### Are mixnodes whitelisted? +### Are Mix Nodes whitelisted? -Nope, anyone can run a mix node. Purely reliant on the node's reputation (self stake + delegations) & routing score. +Nope, anyone can run a Mix Node. Purely reliant on the node's reputation (self stake + delegations) & routing score. ## Validators and tokens @@ -44,6 +44,14 @@ Nope, anyone can run a mix node. Purely reliant on the node's reputation (self s ### What's the difference between NYM and NYX? ---> +### Why some Nyx blockchain operations take one hour and others are instant? + +This is based on the definition in [Nym's CosmWasm](https://github.com/nymtech/nym/tree/develop/common/cosmwasm-smart-contracts) smart contracts code. + +Whatever is defined as [a pending epoch event](https://github.com/nymtech/nym/blob/b07627d57e075b6de35b4b1a84927578c3172811/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs#L35-L103) will get resolved at the end of the current epoch. + +And whatever is defined as [a pending interval event](https://github.com/nymtech/nym/blob/b07627d57e075b6de35b4b1a84927578c3172811/common/cosmwasm-smart-contracts/mixnet-contract/src/pending_events.rs#L145-L172) will get resolved at the end of the current interval. + ### Can I run a validator? We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. diff --git a/documentation/operators/src/faq/smoosh-faq.md b/documentation/operators/src/faq/smoosh-faq.md index 5c8705eb60..32774a65e6 100644 --- a/documentation/operators/src/faq/smoosh-faq.md +++ b/documentation/operators/src/faq/smoosh-faq.md @@ -4,7 +4,8 @@ > -- Harry Halpin, Nym CEO
-This page refer to the changes which are planned to take place over Q3 and Q4 2023. As this is a transition period in the beginning (Q3 2023) the [Mix Nodes FAQ page](./mixnodes-faq.md) holds more answers to the current setup as project Smoosh refers to the eventual setup. As project Smoosh gets progressively implemented the answers on this page will become to be more relevant to the current state and eventually this FAQ page will be merged with the still relevant parts of the main Mix Nodes FAQ page. + +This page refer to the changes which are planned to take place over Q3 and Q4 2023. As this is a transition period in the beginning (Q3 2023) the [Mix Nodes FAQ page](mixnodes-faq.md) holds more answers to the current setup as project Smoosh refers to the eventual setup. As project Smoosh gets progressively implemented the answers on this page will become to be more relevant to the current state and eventually this FAQ page will be merged with the still relevant parts of the main Mix Nodes FAQ page. If any questions are not answered or it's not clear for you in which stage project Smoosh is right now, please reach out in Node Operators [Matrix room](https://matrix.to/#/#operators:nymtech.chat). @@ -14,33 +15,36 @@ If any questions are not answered or it's not clear for you in which stage proje As we shared in our blog post article [*What does it take to build the wolds most powerful VPN*](https://blog.nymtech.net/what-does-it-take-to-build-the-worlds-most-powerful-vpn-d351a76ec4e6), project Smoosh is: -> A nick-name by CTO Dave Hrycyszyn and Chief Scientist Claudia Diaz for the work they are currently doing to “smoosh” Nym nodes so that the same operator can serve alternately as mix node, gateway or VPN node. This requires careful calibration of the Nym token economics, for example, only nodes with the highest reputation for good quality service will be in the VPN set and have the chance to earn higher rewards. +> A nick-name by CTO Dave Hrycyszyn and Chief Scientist Claudia Diaz for the work they are currently doing to “smoosh” Nym Nodes so that the same operator can serve alternately as Mix Node, Gateway or VPN node. This requires careful calibration of the Nym token economics, for example, only nodes with the highest reputation for good quality service will be in the VPN set and have the chance to earn higher rewards. > By simplifying the components, adding VPN features and supporting new node operators, the aim is to widen the geographical coverage of nodes and have significant redundancy, meaning plenty of operators to be able to meet demand. This requires strong token economic incentives as well as training and support for new node operators. ## Technical Questions ### What are the changes? -Project smoosh will have three steps: +Project Smoosh will have four steps, please follow the table below to track the dynamic progress: -1. Combine the `gateway` and `network-requester` into one binary ✅ -2. Create [Exit Gateway](../legal/exit-gateway.md): Take the gateway binary including network requester combined in \#1 and switch from [`allowed.list`](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to a new [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) ✅ -3. Combine all the nodes in the Nym Mixnet into one binary, that is `mixnode`, `gateway` (entry and exit) and `network-requester`. +| **Step** | **Status** | +| :--- | :--- | +| **1.** Combine the `nym-gateway` and `nym-network-requester` into one binary | ✅ done | +| **2.** Create [Exit Gateway](../legal/exit-gateway.md): Take the `nym-gateway` binary including `nym-network-requester` combined in \#1 and switch from [`allowed.list`](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) to a new [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) | ✅ done | +| **3.** Combine all the nodes in the Nym Mixnet into one binary, that is `nym-mixnode`, `nym-gateway` (entry and exit) and `nym-network-requester`. | 🛠️ in progress | +| **4.** Adjust reward scheme to incentivise and reward Exit Gateways as a part of `nym-node` binary, implementing [zkNym credentials](https://youtu.be/nLmdsZ1BsQg?t=1717). | 🛠️ in progress | -These three steps will be staggered over time - period of several months, and will be implemented one by one with enough time to take in feedback and fix bugs in between. -Generally, the software will be the same, just instead of multiple binaries, there will be one Nym Mixnet node binary. Delegations will remain on as they are now, per our token economics (staking, saturation etc) +These steps will be staggered over time - period of several months, and will be implemented one by one with enough time to take in feedback and fix bugs in between. +Generally, the software will be the same, just instead of multiple binaries, there will be one Nym Node (`nym-node`) binary. Delegations will remain on as they are now, per our token economics (staking, saturation etc) ### What does it mean for Nym nodes operators? We are exploring two potential methods for implementing binary functionality in practice and will provide information in advance. The options are: -1. Make a selection button (command/argument/flag) for operators to choose whether they want their node to provide all or just some of the functions nodes have in the Nym Mixnet. Nodes functioning as exit gateways (in that epoch) will then have bigger rewards due to their larger risk exposure and overhead work with the setup. +1. Make a selection button (command/argument/flag) for operators to choose whether they want their node to provide all or just some of the functions nodes have in the Nym Mixnet. Nodes functioning as Exit Gateways (in that epoch) will then have bigger rewards due to their larger risk exposure and overhead work with the setup. -2. All nodes will be required to have the exit gateway functionality. All nodes are rewarded the same as now, and the difference is that a node sometimes (some epochs) may be performing as exit gateway sometimes as mix node or entry gateway adjusted according the network demand by an algorithm. +2. All nodes will be required to have the Exit Gateway functionality. All nodes are rewarded the same as now, and the difference is that a node sometimes (some epochs) may be performing as Exit Gateway sometimes as Mix node or Entry Gateway adjusted according the network demand by an algorithm. -### Where can I read more about the exit gateway setup? +### Where can I read more about the Exit Gateway setup? -We created an [entire page](../legal/exit-gateway.md) about the technical and legal questions around exit gateway. +We created an [entire page](../legal/exit-gateway.md) about the technical and legal questions around Exit Gateway. ### What is the change from allow list to deny list? @@ -48,15 +52,17 @@ The operators running Gateways would have to “open” their nodes to a wider r ### How will the Exit policy be implemented? -The progression of exit policy on Gateways will have three steps: +Follow the dynamic progress of exit policy implementation on Gateways below: -1. By default the [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) filtering will be disabled and the current [`allowed.list`](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) filtering is going to continue be used. This is to prevent operators getting surprised by upgrading their Gateways (or Network requesters) and suddenly be widely open to the internet. To enable the new exit policy, operators must use `--with-exit-policy` flag or modify the `config.toml` file. ✅ -2. Relatively soon the exit policy will be part of the Gateway setup by default. To disable this exit policy, operators must use `--disable-exit-policy` flag. -3. Further down the line, it will be the only option. Then the `allowed.list` will be completely removed. +| **Step** | **Status** | +| :--- | :--- | +| **1.** By default the [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) filtering is disabled and the [`allowed.list`](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) filtering is going to continue be used. This is to prevent operators getting surprised by upgrading their Gateways (or Network Requesters) and suddenly be widely open to the internet. To enable the new exit policy, operators must use `--with-exit-policy` flag or modify the `config.toml` file. | ✅ done | +| **2.** The exit policy is part of the Gateway setup by default. To disable this exit policy, operators must use `--disable-exit-policy` flag. | 🛠️ in progress | +| **3.** The exit policy is the only option. The `allowed.list` is completely removed. | 🛠️ in progress | -Keep in mind this only relates to changes happening on Gateway and Network Requester side. Whether this will be optional or mandatory depends on the chosen [design](./smoosh-faq.md#what-does-it-mean-for-nym-nodes-operators). +Keep in mind the table above only relates to changes happening on Gateways. For the Project Smoosh progress refer to the [table above](./smoosh-faq.md#what-are-the-changes). Whether Exit Gateway functionality will be optional or mandatory part of every active Nym Node depends on the chosen [design](./smoosh-faq.md#what-does-it-mean-for-nym-nodes-operators). -### Can I run a mix node only? +### Can I run a Mix Node only? It depends which [design](./smoosh-faq.md#what-does-it-mean-for-nym-nodes-operators) will ultimately be used. In case of the first - yes. In case of the second option, all the nodes will be setup with Exit Gateway functionality turned on. @@ -68,7 +74,7 @@ For any specifics on Nym token economics and Nym Mixnet reward system, please re ### What are the incentives for the node operator? -In the original setup there were no incentives to run a `network-requester`. After the transition all the users will buy multiple tickets of zkNyms credentials and use those as [anonymous e-cash](https://arxiv.org/abs/2303.08221) to pay for their data traffic ([`Nym API`](https://github.com/nymtech/nym/tree/master/nym-api) will do the do cryptographical checks to prevent double-spending). All collected fees get distributed to all active nodes proportionally to their work by the end of each epoch. +In the original setup there were no incentives to run a `nym-network-requester` binary. After the transition all the users will buy multiple tickets of zkNyms credentials and use those as [anonymous e-cash](https://arxiv.org/abs/2303.08221) to pay for their data traffic ([`Nym API`](https://github.com/nymtech/nym/tree/master/nym-api) will do the do cryptographical checks to prevent double-spending). All collected fees get distributed to all active nodes proportionally to their work by the end of each epoch. ### How does this change the token economics? @@ -78,9 +84,9 @@ The token economics will stay the same as they are, same goes for the reward alg This depends on [design](./smoosh-faq.md#what-does-it-mean-for-nym-nodes-operators) chosen. In case of \#1, it will look like this: -As each operator can choose what roles their nodes provide, the nodes which work as open gateways will have higher rewards because they are the most important to keep up and stable. Besides that the operators of gateways may be exposed to more complication and possible legal risks. +As each operator can choose what roles their nodes provide, the nodes which work as open Gateways will have higher rewards because they are the most important to keep up and stable. Besides that the operators of Gateways may be exposed to more complication and possible legal risks. -The nodes which are initialized to run as mix nodes and gateways will be chosen to be on top of the active set before the ones working only as a mix node. +The nodes which are initialized to run as Mix Nodes and Gateways will be chosen to be on top of the active set before the ones working only as a Mix Node. I case we go with \#2, all nodes active in the epoch will be rewarded proportionally according their work. @@ -88,21 +94,21 @@ In either way, Nym will share all the specifics beforehand. ### How will be the staking and inflation after project Smoosh? -Nym will run tests to count how much payment comes from the users of the Mixnet and if that covers the reward payments. If not, we may need to keep inflation on to secure incentives for high quality gateways in the early stage of the transition. +Nym will run tests to count how much payment comes from the users of the Mixnet and if that covers the reward payments. If not, we may need to keep inflation on to secure incentives for high quality Gateways in the early stage of the transition. -### When project smooth will be launched, it would be the mixmining pool that will pay for the gateway rewards based on amount of traffic routed ? +### When project smooth will be launched, it would be the mixmining pool that will pay for the Gateway rewards based on amount of traffic routed ? -Yes, the same pool. Nym's aim is to do minimal modifications. The only real modification on the smart contract side will be to get into top X of 'active set' operators will need to have open gateway function enabled. +Yes, the same pool. Nym's aim is to do minimal modifications. The only real modification on the smart contract side will be to get into top X of 'active set' operators will need to have open Gateway function enabled. ### What does this mean for the current delegators? -From an operator standpoint, it shall just be a standard Nym upgrade, a new option to run the gateway software on your node. Delegators should not have to re-delegate. +From an operator standpoint, it shall just be a standard Nym upgrade, a new option to run the Gateway software on your node. Delegators should not have to re-delegate. ## Legal Questions ### Are there any legal concerns for the operators? -So far the general line is that running a gateway is not illegal (unless you are in Iran, China, and a few other places) and due to encryption/mixing less risky than running a normal VPN node. For mix nodes, it's very safe as they have "no idea" what packets they are mixing. +So far the general line is that running a Gateway is not illegal (unless you are in Iran, China, and a few other places) and due to encryption/mixing less risky than running a normal VPN node. For Mix Nodes, it's very safe as they have "no idea" what packets they are mixing. There are several legal questions and analysis to be made for different jurisdictions. To be able to share resources and findings between the operators themselves we created a [Community Legal Forum](../legal/exit-gateway.md). diff --git a/documentation/operators/src/introduction.md b/documentation/operators/src/introduction.md index 27d9ab63db..5d1df7d880 100644 --- a/documentation/operators/src/introduction.md +++ b/documentation/operators/src/introduction.md @@ -1,6 +1,6 @@ # Introduction -This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym mixnet infrastructure (mix node, gateway and network requester) and Nyx blockchain validators. +This is Nym's Operators guide, containing information and setup guides for the various pieces of Nym Mixnet infrastructure (Mix Node, Gateway and Network Requester) and Nyx blockchain validators. If you are new to Nym and want to learn about the mixnet, explore kickstart options and demos, learn how to integrate with the network, and follow developer tutorials check out the [Developer Portal](https://nymtech.net/developers/). @@ -9,18 +9,17 @@ If you want to dive deeper into Nym's architecture, clients, nodes, and SDK exam ## Popular pages **Binary Information** -* [Building Nym](./binaries/building-nym.md) -* [Pre-built Binaries](./binaries/pre-built-binaries.md) -* [Init & Configuration](./binaries/init-and-config.md) +* [Building Nym](binaries/building-nym.md) +* [Pre-built Binaries](binaries/pre-built-binaries.md) **Node setup and usage guides:** -* [Mix nodes](./nodes/mix-node-setup.md) -* [Gateways](./nodes/gateway-setup.md) -* [Network requesters](./nodes/network-requester-setup.md) -* [Validators](./nodes/validator-setup.md) +* [Mix nodes](nodes/mix-node-setup.md) +* [Gateways](nodes/gateway-setup.md) +* [Network requesters](nodes/network-requester-setup.md) +* [Validators](nodes/validator-setup.md) **Maintenance, troubleshooting and FAQ** -* [Maintenance](./nodes/maintenance.md) -* [Troubleshooting](./nodes/troubleshooting.md) -* [FAQ](./faq/mixnodes-faq.md) +* [Maintenance](nodes/maintenance.md) +* [Troubleshooting](nodes/troubleshooting.md) +* [FAQ](faq/mixnodes-faq.md) diff --git a/documentation/operators/src/legal/exit-gateway.md b/documentation/operators/src/legal/exit-gateway.md index 4cac80d7de..cc1e6bb81b 100644 --- a/documentation/operators/src/legal/exit-gateway.md +++ b/documentation/operators/src/legal/exit-gateway.md @@ -8,7 +8,7 @@ This page is a part of Nym Community Legal Forum and its content is composed by This document presents an initiative to further support Nym’s mission of allowing privacy for everyone everywhere. This would be achieved with the support of Nym node operators operating Gateways and opening these to any online service. Such setup needs a **clear policy**, one which will remain the **same for all operators** running Nym nodes. The [proposed **Exit policy**](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) is a combination of two existing safeguards: [Tor Null ‘deny’ list](https://tornull.org/) and [Tor reduced policy](https://tornull.org/tor-reduced-reduced-exit-policy.php). -All the technical changes on the side of Nym nodes - ***Project Smoosh** - are described in the [FAQ section](../faq/smoosh-faq.md). +All the technical changes on the side of Nym nodes - ***Project Smoosh*** - are described in the [FAQ section](../faq/smoosh-faq.md). ```admonish warning Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.com/invite/nym), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences. @@ -17,13 +17,13 @@ Nym core team cannot provide comprehensive legal advice across all jurisdictions ## Summary -* This document outlines a plan to change Nym Gateways from operating with an ‘allow’ to a ‘deny’ list to enable broader uptake and usage of the Nym mixnet. It provides operators with an overview of the plan, pros and cons, legal as well as technical advice. +* This document outlines a plan to change Nym Gateways from operating with an ‘allow’ to a ‘deny’ list to enable broader uptake and usage of the Nym Mixnet. It provides operators with an overview of the plan, pros and cons, legal as well as technical advice. -* Nym is committed to ensuring privacy for all users, regardless of their location and for the broadest possible range of online services. In order to achieve this aim, the Nym mixnet needs to increase its usability across a broad range of apps and services. +* Nym is committed to ensuring privacy for all users, regardless of their location and for the broadest possible range of online services. In order to achieve this aim, the Nym Mixnet needs to increase its usability across a broad range of apps and services. * Currently, Nym Gateway nodes only enable access to apps and services that are on an ‘allow’ list that is maintained by the core team. -* To decentralise and enable privacy for a broader range of services, this initiative will have to transition from the current ‘allow’ list to a ‘deny’ list - [Exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). In accordance with the [Tor Null 'deny' list](https://tornull.org/) and [Tor reduced policy](https://tornull.org/tor-reduced-reduced-exit-policy.php), which are two established safeguards. +* To decentralise and enable privacy for a broader range of services, this initiative will have to transition from the current ‘allow’ list to a ‘deny’ list - [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). In accordance with the [Tor Null 'deny' list](https://tornull.org/) and [Tor reduced policy](https://tornull.org/tor-reduced-reduced-exit-policy.php), which are two established safeguards. * This will enhance the usage and appeal of Nym products for end users. As a result, increased usage will ultimately lead to higher revenues for Nym operators. @@ -40,7 +40,7 @@ Nym core team cannot provide comprehensive legal advice across all jurisdictions To offer a better and more private everyday experience for its users, Nym would like them to use any online services they please, without limiting its access to a few messaging apps or crypto wallets. -To achieve this, operators running exit gateways would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays following this [Exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). +To achieve this, operators running Exit Gateways would have to “open” their nodes to a wider range of online services, in a similar fashion to Tor exit relays following this [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). ## Pros and cons of the initiative @@ -64,23 +64,23 @@ The new setup: Running nodes supporting traffic of any online service (with safe | Operational | | - Higher operational overhead, such as dealing with DMCA / abuse complaints, managing the VPS provider questions, or helping the community to maintain the denylist
- Administrative overhead if running nodes as a company or an entity | | Legal | | - Ideally requires to check legal environment with local privacy association or lawyer | Financial | - Higher revenue potential for operators due to the increase in network usage | - If not running VPS with an unlimited bandwidth plan, higher costs due to higher network usage | -## Exit gateways: New setup +## Exit Gateways: New setup -In our previous technical setup, network requesters acted as a proxy, and only made requests that match an allow list. That was a default IP based list of allowed domains stored at Nym page in a centralised fashion possibly re-defined by any Network requester operator. +In our previous technical setup, Network Requesters acted as a proxy, and only made requests that match an allow list. That was a default IP based list of allowed domains stored at Nym page in a centralised fashion possibly re-defined by any Network Requester operator. -This restricts the hosts that the NymConnect app can connect to and has the effect of selectively supporting messaging services (e.g. Telegram, Matrix) or crypto wallets (e.g. Electrum or Monero). Operators of network requesters can have confidence that the infrastructure they run only connects to a limited set of public internet hosts. +This restricts the hosts that the NymConnect app can connect to and has the effect of selectively supporting messaging services (e.g. Telegram, Matrix) or crypto wallets (e.g. Electrum or Monero). Operators of Network Requesters can have confidence that the infrastructure they run only connects to a limited set of public internet hosts. -The principal change in the new configuration is to make this short allow list more permissive. Nym's [Exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) will restrict the hosts to which Nym Mixnet and Nym VPN users are permitted to connect. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym Mixnet VPN and VPN clients (both wrapped in the same app). +The principal change in the new configuration is to make this short allow list more permissive. Nym's [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) will restrict the hosts to which Nym Mixnet and Nym VPN users are permitted to connect. This will be done in an effort to protect the operators, as Gateways will act both as SOCKS5 Network Requesters, and exit nodes for IP traffic from Nym Mixnet VPN and VPN clients (both wrapped in the same app). -As of now we the gateways will be defaulted to a combination of [Tor Null ‘deny’ list](https://tornull.org/) (note: Not affiliated with Tor) - reproduction permitted under Creative Commons Attribution 3.0 United States License which is IP-based, e.g., `ExitPolicy reject 5.188.10.0/23:*` and [Tor reduced policy](https://tornull.org/tor-reduced-reduced-exit-policy.php). Whether we will stick with this list, do modifications or compile another one is still a subject of discussion. In all cases, this policy will remain the same for all the nodes, without any option to modify it by Nym node operators to secure stable and reliable service for the end users. +As of now we the Gateways will be defaulted to a combination of [Tor Null ‘deny’ list](https://tornull.org/) (note: Not affiliated with Tor) - reproduction permitted under Creative Commons Attribution 3.0 United States License which is IP-based, e.g., `ExitPolicy reject 5.188.10.0/23:*` and [Tor reduced policy](https://tornull.org/tor-reduced-reduced-exit-policy.php). Whether we will stick with this list, do modifications or compile another one is still a subject of discussion. In all cases, this policy will remain the same for all the nodes, without any option to modify it by Nym node operators to secure stable and reliable service for the end users. -For exit relays on ports 80 and 443, the gateways will exhibit an HTML page resembling the one proposed by [Tor](https://gitlab.torproject.org/tpo/core/tor/-/raw/HEAD/contrib/operator-tools/tor-exit-notice.html). By doing so, the operator will be able to disclose details regarding their gateway, including the currently configured exit policy, all without the need for direct correspondence with regulatory or law enforcement agencies. It also makes the behaviour of exit gateways transparent and even computable (a possible feature would be to offer a machine readable form of the notice in JSON or YAML). +For exit relays on ports 80 and 443, the Gateways will exhibit an HTML page resembling the one proposed by [Tor](https://gitlab.torproject.org/tpo/core/tor/-/raw/HEAD/contrib/operator-tools/tor-exit-notice.html). By doing so, the operator will be able to disclose details regarding their Gateway, including the currently configured exit policy, all without the need for direct correspondence with regulatory or law enforcement agencies. It also makes the behavior of Exit Gateways transparent and even computable (a possible feature would be to offer a machine readable form of the notice in JSON or YAML). We also recommend operators to check the technical advice from [Tor](https://community.torproject.org/relay/setup/exit/). ## Tor legal advice -Giving the legal similarity between Nym exit gateways and Tor exit relays, it is helpful to have a look in [Tor community Exit Guidelines](https://community.torproject.org/relay/community-resources/tor-exit-guidelines/). This chapter is an exert of tor page. +Giving the legal similarity between Nym Exit Gateways and Tor Exit Relays, it is helpful to have a look in [Tor community Exit Guidelines](https://community.torproject.org/relay/community-resources/tor-exit-guidelines/). This chapter is an exert of tor page. Note that Tor states: > This FAQ is for informational purposes only and does not constitute legal advice. @@ -88,7 +88,7 @@ Note that Tor states: *Check legal advice prior to running an exit relay* * Understand the risks associated with running an exit relay; E.g., know legal paragraphs relevant in the country of operations: - - US [DMCA 512](https://www.law.cornell.edu/uscode/text/17/512); see [EFF's Legal FAQ for TOr Operators](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq) (a very good and relevant read for other countries as well) + - US [DMCA 512](https://www.law.cornell.edu/uscode/text/17/512); see [EFF's Legal FAQ for Tor Operators](https://community.torproject.org/relay/community-resources/eff-tor-legal-faq) (a very good and relevant read for other countries as well) - Germany’s [TeleMedienGesetz 8](http://www.gesetze-im-internet.de/tmg/__8.html) and [15](http://www.gesetze-im-internet.de/tmg/__15.html) - Netherlands: [Artikel 6:196c BW](http://wetten.overheid.nl/BWBR0005289/Boek6/Titel3/Afdeling4A/Artikel196c/) - Austria: [E-Commerce-Gesetz 13](http://www.ris.bka.gv.at/Dokument.wxe?Abfrage=Bundesnormen&Dokumentnummer=NOR40025809) @@ -96,7 +96,7 @@ Note that Tor states: * Top 3 advice - Have an abuse response letter - Run relay from a location that is not home - - Read through the legal resources that Tor-supportive lawyers put together: https://www.eff.org/pages/legal-faq-tor-relay-operators or https://www.noisebridge.net/wiki/Noisebridge_Tor/FBI + - Read through the legal resources that Tor-supportive lawyers put together: [www.eff.org/pages/legal-faq-tor-relay-operators](https://www.eff.org/pages/legal-faq-tor-relay-operators) or [www.noisebridge.net/wiki/Noisebridge_Tor/FBI](https://www.noisebridge.net/wiki/Noisebridge_Tor/FBI) * Consult a lawyer / local digital rights association / the EFF prior to operating an exit relay, especially in a place where exit relay operators have been harassed or not operating before. Note that Tor DOES NOT provide legal advice for specific countries. It only provides general advice (itself or in partnership), eventually skewed towards [US audiences](https://www.eff.org/pages/legal-faq-tor-relay-operators). diff --git a/documentation/operators/src/licensing.md b/documentation/operators/src/licensing.md index 2e8612d4d4..b569231b75 100644 --- a/documentation/operators/src/licensing.md +++ b/documentation/operators/src/licensing.md @@ -1,5 +1,11 @@ # Licensing -Nym is Free Software released under the Apache License V2. +As a general approach, licensing is as follows this pattern: -All of the contributions of the Nym core developers are © Nym Technologies SA. If you are interested in talking to us about other licenses, please get in touch. +*

Nym Documentation by Nym Technologies is licensed under CC BY-NC-SA 4.0

+ +* Nym applications and binaries are [GPL-3.0-only](https://www.gnu.org/licenses/) + +* Used libraries and different components are [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0.html) or [MIT](https://mit-license.org/) + +For accurate information, please check individual files. diff --git a/documentation/operators/src/nodes/gateway-setup.md b/documentation/operators/src/nodes/gateway-setup.md index 93ca3e7735..b77c5c2924 100644 --- a/documentation/operators/src/nodes/gateway-setup.md +++ b/documentation/operators/src/nodes/gateway-setup.md @@ -16,7 +16,7 @@ As a result of [Project Smoosh](../faq/smoosh-faq.md), the current version of `n ## Preliminary steps -Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your gateway. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your Gateway. ## Gateway setup @@ -50,7 +50,7 @@ You can also check the various arguments required for individual commands with: ## Initialising your Gateway -As Nym developers build towards [Exit Gateway](../legal/exit-gateway.md) functionality, operators can now run their `nym-gateway` binary with in-build Network requester and include the our new [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). Considering the plan to [*smoosh*](../faq/smoosh-faq.md) all the nodes into one binary and have wide opened Exit Gateways, we recommend this setup, instead of operating two separate binaries. +As Nym developers build towards [Exit Gateway](../legal/exit-gateway.md) functionality, operators can now run their `nym-gateway` binary with inbuilt Network Requester and include the our new [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt). Considering the plan to [*smoosh*](../faq/smoosh-faq.md) all the nodes into one binary and have wide opened Exit Gateways, we recommend this setup, instead of operating two separate binaries. ```admonish warning Before you start an Exit Gateway, read our [Operators Legal Forum](../legal/exit-gateway.md) page and [*Project Smoosh FAQ*](../faq/smoosh-faq.md). @@ -60,19 +60,21 @@ Before you start an Exit Gateway, read our [Operators Legal Forum](../legal/exit There has been an ongoing development with dynamic upgrades. Follow the status of the Project Smoosh [changes](../faq/smoosh-faq.md#what-are-the-changes) and the progression state of exit policy [implementation](../faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented) to be up to date with the current design. ``` +**Note:** Due to the development towards Exit Gateway functionality the `--host` flag has been replaced with `--listening-address`, this is the IP address which is used for receiving sphinx packets and listening to client data. Another flag `--public-ips` is required; it’s a comma separated list of IP’s that are announced to the `nym-api`, it is usually the address which is used for bonding. + ### Initialising Exit Gateway -An operator can initialise the Exit Gateway functionality by adding Network requester with the new exit policy option: +An operator can initialise the Exit Gateway functionality by adding Network Requester with the new exit policy option: ``` -./nym-gateway init --id --host $(curl -4 https://ifconfig.me) --with-network-requester --with-exit-policy true +./nym-gateway init --id --listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)" --with-network-requester --with-exit-policy true ``` If we follow the previous example with `` chosen `superexitgateway`, adding the `--with-network-requester` and `--with-exit-policy` flags, the outcome will be: ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ @@ -80,9 +82,9 @@ You can see that the printed information besides *identity* and *sphinx keys* al Additionally -#### Add Network requester to an existing Gateway +#### Add Network Requester to an existing Gateway -If you already [upgraded](./maintenance.md#upgrading-your-node) your Gateway to the [latest version](./gateway-setup.md#current-version) and initialised without a Network requester, you can easily change its functionality to Exit Gateway with a command `setup-network-requester`. +If you already [upgraded](./manual-upgrade.md) your Gateway to the [latest version](./gateway-setup.md#current-version) and initialised without a Network Requester, you can easily change its functionality to Exit Gateway with a command `setup-network-requester`. See the options: @@ -102,7 +104,7 @@ To setup Exit Gateway functionality with our new [exit policy](https://nymtech.n ./nym-gateway setup-network-requester --enabled true --with-exit-policy true --id ``` -Say we have a gateway with `` as `new-gateway`, originally initialised and ran without the Exit Gateway functionality. To change the setup, run: +Say we have a Gateway with `` as `new-gateway`, originally initialised and ran without the Exit Gateway functionality. To change the setup, run: ``` @@ -112,7 +114,7 @@ Say we have a gateway with `` as `new-gateway`, originally initialised and r ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ @@ -120,13 +122,13 @@ In case there are any unexpected problems, you can also change it manually by ed ``` [network_requester] -# Specifies whether network requester service is enabled in this process. +# Specifies whether Network Requester service is enabled in this process. enabled = true ``` -Save, exit and restart your gateway. Now you are an operator of post-smooshed Exit gateway. +Save, exit and restart your Gateway. Now you are an operator of post-smooshed Exit Gateway. -#### Enable Nym exit policy to an existing Gateway with Network requester functionality +#### Enable Nym exit policy to an existing Gateway with Network Requester functionality In case you already added Network Requester functionality to your Gateway as described above but haven't enabled the [exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) there is an easy tweak to do so and turn your node into [Nym Exit Gateway](../faq/smoosh-faq.md#what-are-the-changes). @@ -134,18 +136,18 @@ Open the config file stored at `.nym/gateways//config/network_requester_conf ```sh use_deprecated_allow_list = false ``` -Save, exit and restart your gateway. Now you are an operator of post-smooshed Exit gateway. +Save, exit and restart your Gateway. Now you are an operator of post-smooshed Exit gateway. ```admonish info -All information about network requester part of your Exit Gateway is in `/home/user/.nym/gateways//config/network_requester_config.toml`. +All information about Network Requester part of your Exit Gateway is in `/home/user/.nym/gateways//config/network_requester_config.toml`. ``` -For now you can run Gateway without Network requester or with and without the new exit policy. This will soon change as we inform in our [Project Smoosh FAQ](../faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented). +For now you can run Gateway without Network Requester or with and without the new exit policy. This will soon change as we inform in our [Project Smoosh FAQ](../faq/smoosh-faq.html#how-will-the-exit-policy-be-implemented). -To read more about the configuration like whitelisted outbound requesters in `allowed.list` and other useful information, see the page [*Network requester whitelist*](network-requester-setup.md#using-your-network-requester). +To read more about the configuration like whitelisted outbound requesters in `allowed.list` and other useful information, see the page [*Network Requester whitelist*](network-requester-setup.md#using-your-network-requester). -#### Initialising Gateway without Network requester +#### Initialising Gateway without Network Requester In case you don't want to run your Gateway with the Exit Gateway functionality, you still can run a simple Gateway. @@ -161,42 +163,50 @@ To check available configuration options use: ``` ~~~ -The following command returns a gateway on your current IP with the `` of `simple-gateway`: +The following command returns a Gateway on your current IP with the `` of `simple-gateway`: ``` -./nym-gateway init --id simple-gateway --host $(curl -4 https://ifconfig.me) +./nym-gateway init --id simple-gateway --listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)" ``` ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ The `$(curl -4 https://ifconfig.me)` command above returns your IP automatically using an external service. Alternatively, you can enter your IP manually if you wish. If you do this, remember to enter your IP **without** any port information. +## Running your Gateway -### Bonding your gateway +The `run` command starts the Gateway: -```admonish info -Before you bond and re-run your Gateway, please make sure the [firewall configuration](./maintenance.md#configure-your-firewall) is setup so your gateway can be reached from the outside. You can also setup WSS on your Gateway, the steps are on the [Maintenance page](./maintenance.md#configure-your-firewall) below. +``` +./nym-gateway run --id ``` -#### Via the Desktop wallet -You can bond your gateway via the Desktop wallet. +## Bonding your Gateway + +```admonish info +Before you bond your Gateway, please make sure the [firewall configuration](./maintenance.md#configure-your-firewall) is setup so your Gateway can be reached from the outside. You can also setup [WSS on your Gateway](./maintenance.md#run-web-secure-socket-wss-on-gateway) and [automate](./maintenance.md#vps-setup-and-automation) your Gateway to simplify the operation overhead. We highly recommend to run any of these steps before bonding to prevent disruption of your Gateway's routing score later on. +``` + +### Via the Desktop wallet (recommended) + +You can bond your Gateway via the Desktop wallet. **Make sure your Gateway is running first**, then follow the steps below: 1. Open your wallet, and head to the `Bonding` page, then select the node type `Gateway` and input your node details. Press `Next`. 2. Enter the `Amount`, `Operating cost` and press `Next`. -3. You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. +3. You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature `` and paste it as a value of `--contract-msg` in the following command: ``` ./nym-gateway sign --id --contract-msg ``` -It will look something like this: +It will look something like this (as `` we used `supergateway`): ~~~admonish example collapsible=true title="Console output" ``` @@ -209,40 +219,38 @@ It will look something like this: |_| |_|\__, |_| |_| |_| |___/ - (nym-gateway - version v1.1.31) + (nym-gateway - version v1.1.) >>> attempting to sign 2Mf8xYytgEeyJke9LA7TjhHoGQWNBEfgHZtTyy2krFJfGHSiqy7FLgTnauSkQepCZTqKN5Yfi34JQCuog9k6FGA2EjsdpNGAWHZiuUGDipyJ6UksNKRxnFKhYW7ri4MRduyZwbR98y5fQMLAwHne1Tjm9cXYCn8McfigNt77WAYwBk5bRRKmC34BJMmWcAxphcLES2v9RdSR68tkHSpy2C8STfdmAQs3tZg8bJS5Qa8pQdqx14TnfQAPLk3QYCynfUJvgcQTrg29aqCasceGRpKdQ3Tbn81MLXAGAs7JLBbiMEAhCezAr2kEN8kET1q54zXtKz6znTPgeTZoSbP8rzf4k2JKHZYWrHYF9JriXepuZTnyxAKAxvGFPBk8Z6KAQi33NRQkwd7MPyttatHna6kG9x7knffV6ebGzgRBf7NV27LurH8x4L1uUXwm1v1UYCA1WSBQ9Pp2JW69k5v5v7G9gBy8RUcZnMbeL26Qqb8WkuGcmuHhaFfoqSfV7PRHPpPT4M8uRqUyR4bjUtSJJM1yh6QSeZk9BEazzoJqPeYeGoiFDZ3LMj2jesbJweQR4caaYuRczK92UGSSqu9zBKmE45a >>> decoding the message... >>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"gateway-bonding","content":{"sender":"n1ewmme88q22l8syvgshqma02jv0vqrug9zq9dy8","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"gateway":{"host":"62.240.134.189","mix_port":1789,"clients_port":9000,"location":"62.240.134.189","sphinx_key":"FKbuN7mPdoCG9jA3CkAfXxC5X4rHhqeMVtmfRtJ3cFZd","identity_key":"3RoAhR8gEdfBETMjm2vbMFzKddxXDdE9ygBAnJHWqSzD","version":"1.1.13"}}}} +>>> The base58-encoded signature is: +2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi ``` ~~~ * Copy the resulting signature: -``` ->>> The base58-encoded signature is: +```sh +# >>> The base58-encoded signature is: 2SPDjLjX4b6XEtkgG7yD8Znsb1xycL1edFvRK4JcVnPsM9k6HXEUUeVS6rswRiYxoj1bMgiRKyPDwiksiuyxu8Xi ``` * And paste it into the wallet nodal, press `Next` and confirm the transaction. -![Paste Signature](../images/wallet-screenshots/wallet-gateway-sign.png) +![Paste Signature](../images/wallet-screenshots/wallet-gateway-sign.png) +*This image is just an example, copy-paste your own base58-encoded signature.* -* Your gateway is now bonded. +* Your Gateway is now bonded. > You are asked to `sign` a transaction on bonding so that the Mixnet smart contract is able to map your Nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. -#### Via the CLI (power users) -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. +### Via the CLI (power users) -### Running your gateway -The `run` command starts the gateway: +If you want to bond your Gateway via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. -``` -./nym-gateway run --id -``` ## Maintenance -For gateway upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) +For Gateway upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation, WSS setup and more, see the [maintenance page](./maintenance.md) diff --git a/documentation/operators/src/nodes/maintenance.md b/documentation/operators/src/nodes/maintenance.md index 5a08724d61..5d472fc226 100644 --- a/documentation/operators/src/nodes/maintenance.md +++ b/documentation/operators/src/nodes/maintenance.md @@ -14,99 +14,37 @@ For example `./target/debug/nym-network-requester --no-banner build-info --outpu {"binary_name":"nym-network-requester","build_timestamp":"2023-07-24T15:38:37.00657Z","build_version":"1.1.23","commit_sha":"c70149400206dce24cf20babb1e64f22202672dd","commit_timestamp":"2023-07-24T14:45:45Z","commit_branch":"feature/simplify-cli-parsing","rustc_version":"1.71.0","rustc_channel":"stable","cargo_profile":"debug"} ``` -## Upgrading your node - -> The process is the similar for mix node, gateway and network requester. In the following steps we use a placeholder `` in the commands, please change it for the type of node you want to upgrade. Any particularities for the given type of node are included. - -Upgrading your node is a two-step process: -* Updating the binary and `~/.nym///config/config.toml` on your VPS -* Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. - -### Step 1: Upgrading your binary -Follow these steps to upgrade your mix node binary and update its config file: -* pause your mix node process. -* replace the existing binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). -* re-run `init` with the same values as you used initially. **This will just update the config file, it will not overwrite existing keys**. -* restart your mix node process with the new binary. - -> In case of a network requester this is all all, the following step is only for mix nodes and gateways. - -### Step 2: Updating your node information in the smart contract -Follow these steps to update the information about your `` which is publicly available from the [Nym API](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [mixnet explorer](https://explorer.nymtech.net). - -You can either do this graphically via the Desktop Wallet, or the CLI. - -### Updating node information via the Desktop Wallet -* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: - -![Bonding page](../images/wallet-screenshots/bonding.png) - -* Update the fields in the `Node Settings` page and click `Submit changes to the blockchain`. - -![Node Settings Page](../images/wallet-screenshots/node_settings.png) - -### Updating node information via the CLI -If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#upgrade-a-mix-node) docs. - - -### Upgrading Network Requester to >= v1.1.10 from -``` - -Copy the old keys from your client to the network-requester configuration that was created above: - -```sh -cp -vr ~/.nym/clients/myoldclient/data/* ~/.nym/service-providers/network-requester//data -``` - -Edit the configuration to match what you used on your client. Specifically, edit the configuration file at: - -```sh -~/.nym/service-providers/network-requester//config/config.toml -``` - -Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the new config match those in the old client config at: - -```sh -~/.nym/clients/myoldclient/config/config.toml -``` - -### Upgrading your validator - -Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: - -- the new validator (`nyxd`) v0.32.0 -- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) - -Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: - -* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. -* swap in your new `nyxd` binary and restart. - -You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). - -Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. - -#### Common reasons for your validator being jailed - -The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. - -Running the command `df -H` will return the size of the various partitions of your VPS. - -If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. - ## Run Web Secure Socket (WSS) on Gateway -Now you can run WSS on your Gateway. +Now you can run WSS on your Gateway. + +### WSS on a new Gateway + +These steps are for an operator who is setting up a [Gateway](gateway-setup.md) for the first time and wants to run it with WSS. + +1. New flags will need to be added to the `init` and `run` command. The `--host` option should be replaced with these flags: + +- `--listening-address`: The IP address which is used for receiving sphinx packets and listening to client data. +- `--public-ips`: A comma separated list of IP’s that are announced to the `nym-api`. In the most cases `--public-ips` **is the address used for bonding.** + +```sh +--listening-address 0.0.0.0 --public-ips "$(curl -4 https://ifconfig.me)" +``` + +- `--hostname` (optional): This flag is required if the operator wishes to run WSS. It can be something like `mainnet-gateway2.nymtech.net`. + +2. Make sure to enable all necessary [ports](maintenance.md#configure-your-firewall) on the Gateway: + +```sh +sudo ufw allow 1789,1790,8000,9000,9001,22/tcp, 9001/tcp +``` + +The Gateway will then be accessible on something like: *http://85.159.211.99:8080/api/v1/swagger/index.html* + +Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*? + +Please modify `[host.public_ips]` section of your config file stored as `~/.nym/gateways//config/config.toml`. ### WSS on an existing Gateway @@ -154,7 +92,7 @@ curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo apt update apt install caddy -systemctl enable caddy.service +systemctl enable caddy.service cd /etc/caddy @@ -164,7 +102,7 @@ if [ -f Caddyfile ]; then rm -f Caddyfile fi -cat <> Caddyfile +cat <> Caddyfile ${host}:${port_value} { @websockets { header Connection *Upgrade* @@ -184,36 +122,13 @@ exit 0 ``` -Although your gateway is Now ready to use its `wss_port`, your server may not be ready - the following commands will allow you to set up a properly configured firewall using `ufw`: +Although your Gateway is Now ready to use its `wss_port`, your server may not be ready - the following commands will allow you to set up a properly configured firewall using `ufw`: ```sh ufw allow 9001/tcp ``` -Lastly don't forget to restart your Gateway, now the API will render the WSS details for this gateway: - - -### WSS on a new Gateway - -These steps are for an operator who is setting up a Gateway for the first time and wants to run it with WSS. - -New flags will need to be added to the `init` and `run` command. The `--host` option is still accepted for now, but can and should be replaced with `--listening-address`, this is the IP address which is used for receiving sphinx packets and listening to client data. - -Another flag `--public-ips` is required; it's a comma separated list of IP’s that are announced to the `nym-api`, it is usually the address which is used for bonding. - -If the operator wishes to run WSS, an optional `--hostname` flag is also required, that can be something like `mainnet-gateway2.nymtech.net`. Make sure to enable all necessary [ports](maintenance.md#configure-your-firewall) on the Gateway. - -The gateway will then be accessible on something like: *http://85.159.211.99:8080/api/v1/swagger/index.html* - -Are you seeing something like: *this node attempted to announce an invalid public address: 0.0.0.0.*? - -Please modify `[host.public_ips]` section of your config file stored as `~/.nym/gateways//config/config.toml`. - -If so the flags are going to be slightly different: - -``` ---listening-address "0.0.0.0" --public-ips "$(curl -4 https://ifconfig.me)" -``` +Lastly don't forget to restart your Gateway, now the API will render the WSS details for this Gateway: ## Configure your firewall @@ -236,9 +151,12 @@ sudo ufw status Finally open your `` p2p port, as well as ports for ssh and ports for verloc and measurement pings: ```sh -# for mix node, gateway and network requester +# for Mix Node, Gateway and Network Requester sudo ufw allow 1789,1790,8000,9000,9001,22/tcp +# in case of setting up WSS on Gateway add: +sudo ufw allow 9001/tcp + # In case of reverse proxy for the Gateway swagger page add: sudo ufw allow 8080,80/443 @@ -255,9 +173,13 @@ For more information about your node's port configuration, check the [port refer ## VPS Setup and Automation +> Replace `` variable with `nym-mixnode`, `nym-gateway` or `nym-network-requester` according the node you running on your machine. + ### Automating your node with nohup, tmux and systemd -Although it’s not totally necessary, it's useful to have the mix node automatically start at system boot time. +Although it’s not totally necessary, it's useful to have the Mix Node automatically start at system boot time. We recommend to run your remote operation via [`tmux`](maintenance.md#tmux) for easier management and a handy return to your previous session. For full automation, including a failed node auto-restart and `ulimit` setup, [`systemd`](maintenance.md#systemd) is a good choice. + +> Do any of these steps and run your automated node before you start bonding process! #### nohup @@ -269,9 +191,9 @@ nohup ./ run --id # where `` is the id you set during t #### tmux -One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. +One way is to use `tmux` shell on top of your current VPS terminal. Tmux is a terminal multiplexer, it allows you to create several terminal windows and panes from a single terminal. Processes started in `tmux` keep running after closing the terminal as long as the given `tmux` window was not terminated. -Use the following command to get `tmux`. +Use the following command to get `tmux`. Platform|Install Command ---|--- @@ -282,15 +204,15 @@ RHEL or CentOS|`yum install tmux` macOS (using Homebrew|`brew install tmux` macOS (using MacPorts)|`port install tmux` openSUSE|`zypper install tmux` - -In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). + +In case it didn't work for your distribution, see how to build `tmux` from [version control](https://github.com/tmux/tmux#from-version-control). **Running tmux** -No when you installed tmux on your VPS, let's run a mix node on tmux, which allows you to detach your terminal and let your `` run on its own on the VPS. +Now you have installed tmux on your VPS, let's run a Mix Node on tmux, which allows you to detach your terminal and let your `` run on its own on the VPS. * Pause your `` -* Start tmux with the command +* Start tmux with the command ```sh tmux ``` @@ -308,9 +230,9 @@ tmux attach-session #### systemd -Here's a systemd service file to do that: +To automate with `systemd` use this init service file and follow the steps below. -##### For mix node +##### For Mix Node ```ini [Unit] @@ -330,7 +252,7 @@ RestartSec=30 WantedBy=multi-user.target ``` -* Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service`. +* Put the above file onto your system at `/etc/systemd/system/nym-mixnode.service` and follow the [next steps](maintenance.md#following-steps-for-nym-nodes-running-as-systemd-service). ##### For Gateway @@ -352,9 +274,9 @@ RestartSec=30 WantedBy=multi-user.target ``` -* Put the above file onto your system at `/etc/systemd/system/nym-gateway.service`. +* Put the above file onto your system at `/etc/systemd/system/nym-gateway.service` and follow the [next steps](maintenance.md#following-steps-for-nym-nodes-running-as-systemd-service). -##### For Network requester +##### For Network Requester ```ini [Unit] @@ -374,21 +296,108 @@ RestartSec=30 [Install] WantedBy=multi-user.target ``` +* Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service` and follow the [next steps](maintenance.md#following-steps-for-nym-nodes-running-as-systemd-service). -Now enable and start your requester: +##### For Nymvisor +> Since you're running your node via a Nymvisor instance, as well as creating a Nymvisor `.service` file, you will also want to **stop any previous node automation process you already have running**. + +``` +[Unit] +Description=Nymvisor +StartLimitInterval=350 +StartLimitBurst=10 + +[Service] +User=nym # replace this with whatever user you wish +LimitNOFILE=65536 +ExecStart=/home///nymvisor run run --id +KillSignal=SIGINT +Restart=on-failure +RestartSec=30 + +[Install] +WantedBy=multi-user.target +``` + +* Put the above file onto your system at `/etc/systemd/system/nymvisor.service` and follow the [next steps](maintenance.md#following-steps-for-nym-nodes-running-as-systemd-service). + +#### Following steps for Nym nodes running as `systemd` service + +Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode`, `nym-gateway` or `nym-network-requester`), and the `` so it is the user you are running as. + +Example: If you have built nym in the `$HOME` directory on your server, your username is `jetpanther`, and node `` is `puma`, then the `ExecStart` line (command) in the script located in `/etc/systemd/system/nym-mixnode.service` for Nym Mixnode might look like this: + +`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id puma`. + +Basically, you want the full `///nym-mixnode run --id `. If you are unsure about your `///`, then `cd` to your directory where you run your `` from and run `pwd` command which returns the full path for you. + +Once done, save the script and follow these steps: ```sh -systemctl enable nym-network-requester.service -systemctl start nym-network-requester.service - -# you can always check your requester has succesfully started with: -systemctl status nym-network-requester.service +systemctl daemon-reload +# to pickup the new unit file ``` -* Put the above file onto your system at `/etc/systemd/system/nym-network-requester.service`. + +Enable the newly created service: + +```sh +# for Mix Node +systemctl enable nym-mixnode.service + +# for Gateway +systemctl enable nym-gateway.service + +# for Network Requester +systemctl enable nym-network-requester.service + +# for Nymvisor +systemctl enable nymvisor.service +``` + +Start your `` as a `systemd` service: + +```sh +# for Mix Node +service nym-mixnode start + +# for Gateway +service nym-gateway start + +# for Network Requester +service nym-network-requester.service + +# for Nymvisor +service nymvisor.service start +``` + +This will cause your `` to start at system boot time. If you restart your machine, your `` will come back up automatically. + +You can monitor system logs of your node by running: +```sh +journalctl -f -u .service +# for example journalctl -f -u nym-mixnode.service +``` + +Or check a status by running: +```sh +systemctl status .service +# for example systemctl status nym-mixnode.service +``` + +You can also do `service stop` or `service restart`. + +Note: if you make any changes to your `systemd` script after you've enabled it, you will need to run: + +```sh +systemctl daemon-reload +``` + +This lets your operating system know it's ok to reload the service configuration. Then restart your ``. + ##### For Validator -Below is a systemd unit file to place at `/etc/systemd/system/nymd.service`: +Below is a `systemd` unit file to place at `/etc/systemd/system/nymd.service` to automate your validator: ```ini [Unit] @@ -418,66 +427,43 @@ systemctl start nymd # to actually start the service journalctl -f -u nymd # to monitor system logs showing the service start ``` -##### Following steps for Nym Mixnet nodes +##### For Nym API -Change the `` in `ExecStart` to point at your `` binary (`nym-mixnode`, `nym-gateway` or `nym-network-requester`), and the `` so it is the user you are running as. +Below is a `systemd` unit file to place at `/etc/systemd/system/nym-api.service` to automate your API instance: -If you have built nym in the `$HOME` directory on your server, and your username is `jetpanther`, then the start command for nym mixnode might look like this: +```ini +[Unit] +Description=NymAPI +StartLimitInterval=350 +StartLimitBurst=10 -`ExecStart=/home/jetpanther/nym/target/release/nym-mixnode run --id `. Basically, you want the full `/path/to/nym-mixnode run --id whatever-your-node-id-is` +[Service] +User= # change to your user +Type=simple +ExecStart=/home///nym-api start # change to correct path +Restart=on-failure +RestartSec=30 +LimitNOFILE=infinity -Then run: +[Install] +WantedBy=multi-user.target +``` + +Proceed to start it with: ```sh -systemctl daemon-reload # to pickup the new unit file +systemctl daemon-reload # to pickup the new unit file +systemctl enable nym-api # to enable the service +systemctl start nym-api # to actually start the service +journalctl -f -u nym-api # to monitor system logs showing the service start ``` -```sh -# for mix node -systemctl enable nym-mixnode.service - -# for gateway -systemctl enable nym-gateway.service -``` - -Start your node: - -```sh -# for mix node -service nym-mixnode start - -# for gateway -service nym-gateway start - -``` - -This will cause your node to start at system boot time. If you restart your machine, the node will come back up automatically. - -You can monitor system logs of your node by running: -```sh -journalctl -f -u .service -``` - -Or check a status by running: -```sh -systemctl status .service -``` - -You can also do `service stop` or `service restart`. - -Note: if you make any changes to your systemd script after you've enabled it, you will need to run: - -``` -systemctl daemon-reload -``` - -This lets your operating system know it's ok to reload the service configuration. ### Setting the ulimit Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. -`ulimit` is 1024 by default on most systems. It needs to be set higher, because mix nodes make and receive a lot of connections to other nodes. +`ulimit` is 1024 by default on most systems. It needs to be set higher, because Mix Nodes make and receive a lot of connections to other nodes. If you see errors such as: @@ -491,12 +477,12 @@ This means that the operating system is preventing network connections from bein > Replace `` variable with `nym-mixnode`, `nym-gateway` or `nym-network-requester` according the node you running on your machine. -The ulimit setup is relevant for maintenance of nym mix node only. +The ulimit setup is relevant for maintenance of Nym Mix Node only. Query the `ulimit` of your `` with: ```sh -# for nym-mixnode, nym-gateway and nym-network requester: +# for nym-mixnode, nym-gateway and nym-network-requester: grep -i "open files" /proc/$(ps -A -o pid,cmd|grep | grep -v grep |head -n 1 | awk '{print $1}')/limits # for nyx validator: @@ -533,7 +519,7 @@ echo "DefaultLimitNOFILE=65535" >> /etc/systemd/system.conf Reboot your machine and restart your node. When it comes back, use: ```sh -# for nym-mixnode, nym-gateway and nym-network requester: +# for nym-mixnode, nym-gateway and nym-network-requester: cat /proc/$(pidof )/limits | grep "Max open files" # for validator @@ -543,13 +529,13 @@ Make sure the limit has changed to 65535. #### Set the ulimit on `non-systemd` based distributions -In case you chose tmux option for mix node automatization, see your `ulimit` list by running: +In case you chose tmux option for Mix Node automation, see your `ulimit` list by running: ```sh ulimit -a # watch for the output line -n --n: file descriptors 1024 +-n: file descriptors 1024 ``` You can change it either by running a command: @@ -568,13 +554,13 @@ username hard nofile 4096 username soft nofile 4096 ``` -Then reboot your server and restart your mix node. +Then reboot your server and restart your Mix Node. ## Moving a node In case of a need to move a node from one machine to another and avoiding to lose the delegation, here are few steps how to do it. -The following examples transfers a mix node (in case of other nodes, change the `mixnodes` in the command for the `` of your desire. +The following examples transfers a Mix Node (in case of other nodes, change the `mixnodes` in the command for the `` of your desire. * Pause your node process. @@ -587,7 +573,7 @@ Assuming both machines are remote VPS. # in case none of the nym configs was created previously mkdir ~/.nym -#in case no nym mix node was initialized previously +#in case no nym Mix Node was initialized previously mkdir ~/.nym/mixnodes ``` * Move the node data (keys) and config file to the new machine by opening a local terminal (as that one's ssh key is authorized in both of the machines) and running: @@ -596,15 +582,16 @@ scp -r -3 @:~/.nym/mixnodes/ ` there, please read the following. -On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl ifconfig.me)`, but also specify `--announce-host` with the public IP. Please make sure that you pass the correct, routable `--announce-host`. +On some services (AWS, Google, etc) the machine's available bind address is not the same as the public IP address. In this case, bind `--host` to the local machine address returned by `$(curl -4 https://ifconfig.me)`, but that may not the public IP address to bond your `` in the wallet. -For example, on a Google machine, you may see the following output from the `ifconfig` command: +You can run `ifconfig` command. For example, on a Google machine, you may see the following output: ```sh ens4: flags=4163 mtu 1460 @@ -614,29 +601,23 @@ ens4: flags=4163 mtu 1460 The `ens4` interface has the IP `10.126.5.7`. But this isn't the public IP of the machine, it's the IP of the machine on Google's internal network. Google uses virtual routing, so the public IP of this machine is something else, maybe `36.68.243.18`. -`./nym-mixnode init --host 10.126.5.7`, initalises the mix node, but no packets will be routed because `10.126.5.7` is not on the public internet. +To find the right IP configuration, contact your VPS provider for support to find the right public IP and use it to bond your `` with the `nym-api` via Nym wallet. -Trying `nym-mixnode init --host 36.68.243.18`, you'll get back a startup error saying `AddrNotAvailable`. This is because the mix node doesn't know how to bind to a host that's not in the output of `ifconfig`. +On self-hosted machine it's a bit more tricky. In that case as an operator you must be sure that your ISP allows for public IPv4 and IPv6 and then it may be a bit of playing around to find the right configuration. One way may be to bind your binary with the `--host` flag to local address `127.0.0.1` and run `echo "$(curl -4 https://ifconfig.me)"` to get a public address which you use to bond your Mix Node to `nym-api` via Nym wallet. -The right thing to do in this situation is to init with a command: -```sh -./nym-mixnode init --host 10.126.5.7 --announce-host 36.68.243.18 -``` - -This will bind the mix node to the available host `10.126.5.7`, but announce the mix node's public IP to the directory server as `36.68.243.18`. It's up to you as a node operator to ensure that your public and private IPs match up properly. - -To find the right IP configuration, contact your VPS provider for support. +It's up to you as a node operator to ensure that your public and private IPs match up properly. ## Nym API (previously 'Validator API') endpoints + Numerous API endpoints are documented on the Nym API (previously 'Validator API')'s [Swagger Documentation](https://validator.nymtech.net/api/swagger/index.html). There you can also try out various requests from your browser, and download the response from the API. Swagger will also show you what commands it is running, so that you can run these from an app or from your CLI if you prefer. -### Mix node Reward Estimation API endpoint +### Mix Node Reward Estimation API endpoint -The Reward Estimation API endpoint allows mix node operators to estimate the rewards they could earn for running a Nym mix node with a specific `MIX_ID`. +The Reward Estimation API endpoint allows Mix Node operators to estimate the rewards they could earn for running a Nym Mix Node with a specific `MIX_ID`. > The `` can be found in the "Mix ID" column of the [Network Explorer](https://explorer.nymtech.net/network-components/mixnodes/active). -The endpoint is a particularly common for mix node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the mix node, the quality of the mix node's performance, and the overall demand for mixnodes in the network. This information can be useful for mix node operators in deciding whether or not to run a mix node and in optimizing its operations for maximum profitability. +The endpoint is a particularly common for Mix Node operators as it can provide an estimate of potential earnings based on factors such as the amount of traffic routed through the Mix Node, the quality of the Mix Node's performance, and the overall demand for Mix Nodes in the network. This information can be useful for Mix Node operators in deciding whether or not to run a Mix Node and in optimizing its operations for maximum profitability. Using this API endpoint returns information about the Reward Estimation: @@ -657,19 +638,19 @@ Query Response: > The unit of value is measured in `uNYM`. -- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular mix node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of mix nodes currently active in the network, and the amount of network traffic being processed by the mix node. +- `estimated_total_node_reward` - An estimate of the total amount of rewards that a particular Mix Node can expect to receive during the current epoch. This value is calculated by the Nym Validator based on a number of factors, including the current state of the network, the number of Mix Nodes currently active in the network, and the amount of network traffic being processed by the Mix Node. -- `estimated_operator_reward` - An estimate of the amount of rewards that a particular mix node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the operator's stake in the network. +- `estimated_operator_reward` - An estimate of the amount of rewards that a particular Mix Node operator can expect to receive. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the Mix Node, the quality of service provided by the Mix Node, and the operator's stake in the network. -- `estimated_delegators_reward` - An estimate of the amount of rewards that mix node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the mix node, the quality of service provided by the mix node, and the delegator's stake in the network. +- `estimated_delegators_reward` - An estimate of the amount of rewards that Mix Node delegators can expect to receive individually. This value is calculated by the Nym Validator based on a number of factors, including the amount of traffic being processed by the Mix Node, the quality of service provided by the Mix Node, and the delegator's stake in the network. -- `estimated_node_profit` - An estimate of the profit that a particular mix node operator can expect to earn. This value is calculated by subtracting the mix node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. +- `estimated_node_profit` - An estimate of the profit that a particular Mix node operator can expect to earn. This value is calculated by subtracting the Mix Node operator's `operating_costs` from their `estimated_operator_reward` for the current epoch. -- `estimated_operator_cost` - An estimate of the total cost that a particular mix node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a mix node, such as server hosting fees, and other expenses associated with operating the mix node. +- `estimated_operator_cost` - An estimate of the total cost that a particular Mix Node operator can expect to incur for their participation. This value is calculated by the Nym Validator based on a number of factors, including the cost of running a Mix Node, such as server hosting fees, and other expenses associated with operating the Mix Node. ### Validator: Installing and configuring nginx for HTTPS #### Setup -[Nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. +[Nginx](https://www.nginx.com/resources/glossary/nginx) is an open source software used for operating high-performance web servers. It allows us to set up reverse proxying on our validator server to improve performance and security. Install `nginx` and allow the 'Nginx Full' rule in your firewall: @@ -697,15 +678,16 @@ Which should return: └─2380 nginx: worker process ``` -#### Configuration +#### Full Node Configuration -Proxying your validator's port `26657` to nginx port `80` can then be done by creating a file with the following at `/etc/nginx/conf.d/validator.conf`: +Proxying various full node services through port 80 can then be done by creating a file with the following at `/etc/nginx/sites-enabled/nyxd-webrequests.conf`: ```sh +### To expose RPC server server { listen 80; listen [::]:80; - server_name "domain_name"; + server_name ""; location / { proxy_pass http://127.0.0.1:26657; @@ -714,20 +696,58 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } + +### To expose Cosmos API server +server { + server_name ""; + location / { + proxy_pass http://127.0.0.1:1317; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $http_host; + proxy_set_header Upgrade websocket; + proxy_set_header Connection Upgrade; + } +} + +### To expose GRPC endpoint +server { + server_name ""; + location / { + grpc_pass 127.0.0.1:9090; + } +} +``` + +#### nym-api Configuration + +```sh +### To expose nym-api webserver +server { + listen 80; + listen [::]:80; + server_name ""; + + location / { + proxy_pass http://127.0.0.1:8000; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} ``` Followed by: ```sh sudo apt install certbot nginx python3 -certbot --nginx -d nym-validator.yourdomain.com -m you@yourdomain.com --agree-tos --noninteractive --redirect +certbot --nginx -m --agree-tos ``` ```admonish caution title="" If using a VPS running Ubuntu 20: replace `certbot nginx python3` with `python3-certbot-nginx` ``` -These commands will get you an https encrypted nginx proxy in front of the API. +These commands will get you an https encrypted nginx proxy in front of the various endpoints. ### Configuring Prometheus metrics (optional) @@ -785,7 +805,7 @@ go_memstats_gc_sys_bytes 1.3884192e+07 ## Ports All ``-specific port configuration can be found in `$HOME/.nym///config/config.toml`. If you do edit any port configs, remember to restart your client and node processes. -### Mix node port reference +### Mix Node port reference | Default port | Use | | ------------ | ------------------------- | | `1789` | Listen for Mixnet traffic | @@ -800,7 +820,7 @@ All ``-specific port configuration can be found in `$HOME/.nym// The process here is similar for the Mix Node, Gateway and Network Requester binaries. In the following steps we use a placeholder `` in the commands, please change it for the binary name you want to upgrade (e.g.`nym-mixnode`). Any particularities for the given type of node are included. + +Upgrading your node is a two-step process: + +1. Updating the binary and `~/.nym///config/config.toml` on your VPS +2. Updating the node information in the [mixnet smart contract](https://nymtech.net/docs/nyx/mixnet-contract.html). **This is the information that is present on the [mixnet explorer](https://explorer.nymtech.net)**. + +## Step 1: Upgrading your binary +Follow these steps to upgrade your Node binary and update its config file: +* Pause your node process. + - if you see the terminal window with your node, press `ctrl + c` + - if you run it as `systemd` service, run: `systemctl stop .service` +* Replace the existing `` binary with the newest binary (which you can either [compile yourself](https://nymtech.net/docs/binaries/building-nym.html) or grab from our [releases page](https://github.com/nymtech/nym/releases)). +* Re-run `init` with the same values as you used initially for your `` ([Mix Node](./mix-node-setup.md#initialising-your-mix-node), [Gateway](./gateway-setup.md#initialising-your-gateway)) . **This will just update the config file, it will not overwrite existing keys**. +* Restart your node process with the new binary: + - if your node is *not automated*, just `run` your `` with `./ run --id `. Here are exact guidelines for [Mix Node](./mix-node-setup.md#running-your-mix-node) and [Gateway](./gateway-setup.md#running-your-gateway). + - if you *automated* your node with systemd (recommended) run: +```sh +systemctl daemon-reload # to pickup the new unit file +systemctl start .service +journalctl -f -u .service # to monitor log of you node +``` + +If these steps are too difficult and you prefer to just run a script, you can use [ExploreNYM script](https://github.com/ExploreNYM/bash-tool) or one done by [Nym developers](https://gist.github.com/tommyv1987/4dca7cc175b70742c9ecb3d072eb8539). + +> In case of a Network Requester this is all, the following step is only for Mix Nodes and Gateways. + +## Step 2: Updating your node information in the smart contract +Follow these steps to update the information about your `` which is publicly available from the [`nym-api`](https://validator.nymtech.net/api/swagger/index.html) and information displayed on the [Mixnet explorer](https://explorer.nymtech.net). + +You can either do this graphically via the Desktop Wallet, or the CLI. + +### Updating node information via the Desktop Wallet (recommended) +* Navigate to the `Bonding` page and click the `Node Settings` link in the top right corner: + +![Bonding page](../images/wallet-screenshots/bonding.png) + +* Update the fields in the `Node Settings` page (usually the field `Version` is the only one to change) and click `Submit changes to the blockchain`. + +![Node Settings Page](../images/wallet-screenshots/node_settings.png) + +### Updating node information via the CLI +If you want to bond your `` via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#upgrade-a-mix-node) docs. + + +## Upgrading Network Requester to >= v1.1.10 from +``` + +Copy the old keys from your client to the network-requester configuration that was created above: + +```sh +cp -vr ~/.nym/clients/myoldclient/data/* ~/.nym/service-providers/network-requester//data +``` + +Edit the configuration to match what you used on your client. Specifically, edit the configuration file at: + +```sh +~/.nym/service-providers/network-requester//config/config.toml +``` + +Ensure that the fields `gateway_id`, `gateway_owner`, `gateway_listener` in the new config match those in the old client config at: + +```sh +~/.nym/clients/myoldclient/config/config.toml +``` + +## Upgrading your validator + +Upgrading from `v0.31.1` -> `v0.32.0` process is fairly simple. Grab the `v0.32.0` release tarball from the [`nyxd` releases page](https://github.com/nymtech/nyxd/releases), and untar it. Inside are two files: + +- the new validator (`nyxd`) v0.32.0 +- the new wasmvm (it depends on your platform, but most common filename is `libwasmvm.x86_64.so`) + +Wait for the upgrade height to be reached and the chain to halt awaiting upgrade, then: + +* copy `libwasmvm.x86_64.so` to the default LD_LIBRARY_PATH on your system (on Ubuntu 20.04 this is `/lib/x86_64-linux-gnu/`) replacing your existing file with the same name. +* swap in your new `nyxd` binary and restart. + +You can also use something like [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor) - grab the relevant information from the current upgrade proposal [here](https://nym.explorers.guru/proposal/9). + +Note: Cosmovisor will swap the `nyxd` binary, but you'll need to already have the `libwasmvm.x86_64.so` in place. + +### Common reasons for your validator being jailed + +The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. + +If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. + diff --git a/documentation/operators/src/nodes/mix-node-setup.md b/documentation/operators/src/nodes/mix-node-setup.md index 15a5a21ef0..55ace09e71 100644 --- a/documentation/operators/src/nodes/mix-node-setup.md +++ b/documentation/operators/src/nodes/mix-node-setup.md @@ -1,6 +1,6 @@ # Mix Nodes -> The Nym mix node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> The Nym Mix Node binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. @@ -13,11 +13,11 @@ The `nym-mix node` binary is currently one point version ahead of the rest of th ## Preliminary steps -Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your mix node. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your Mix Node. ## Mix node setup -Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mix node` binary, you can set up your mix node with the instructions below. +Now that you have built the [codebase](../binaries/building-nym.md), set up your [wallet](https://nymtech.net/docs/wallet/desktop-wallet.html), and have a VPS with the `nym-mix node` binary, you can set up your Mix Node with the instructions below. To begin, move to `/target/release` directory from which you run the node commands: @@ -49,7 +49,7 @@ You can also check the various arguments required for individual commands with: > Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. -### Initialising your mix node +### Initialising your Mix Node To check available configuration options for initializing your node use: @@ -59,64 +59,79 @@ To check available configuration options for initializing your node use: ~~~admonish example collapsible=true title="Console output" ``` - + ``` ~~~ -Initalise your mix node with the following command, replacing the value of `--id` with the moniker you wish to give your mix node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl -4 https://ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. +Initialise your Mix Node with the following command, replacing the value of `--id` with the moniker you wish to give your Mix Node. Your `--host` must be publicly routable on the internet in order to mix packets, and can be either an Ipv4 or IPv6 address. The `$(curl -4 https://ifconfig.me)` command returns your IP automatically using an external service. If you enter your IP address manually, enter it **without** any port information. ``` -./nym-mixnode init --id --host $(curl -4 https://ifconfig.me) +./nym-mixnode init --id --host $(curl -4 https://ifconfig.me) ``` +If `` was `my-node`, the output will look like this: - ~~~admonish example collapsible=true title="Console output" ``` -.nym-mixnode init --id --host $(curl -4 https://ifconfig.me) --wallet-address - - -Initialising mixnode ... -Saved mixnet identity and sphinx keypairs - 2023-06-04T08:20:32.862Z INFO nym_config > Configuration file will be saved to "/home//.nym/mixnodes//config/config.toml" -Saved configuration file to "/home//.nym/mixnodes//config/config.toml" -Mixnode configuration completed. - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (nym-mixnode - version v1.1.29) - - -Identity Key: DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"v1.1.29"},"cost_params -Sphinx Key: CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z -Host: 62.240.134.189 (bind address: 62.240.134.189) -Version: v1.1.29 -Mix Port: 1789, Verloc port: 1790, Http Port: 8000 + ``` ~~~ -> The `init` command will refuse to destroy existing mix node keys. +> The `init` command will refuse to destroy existing Mix Node keys. During the `init` process you will have the option to change the `http_api`, `verloc` and `mixnode` ports from their default settings. If you wish to change these in the future you can edit their values in the `config.toml` file created by the initialization process, which is located at `~/.nym/mixnodes//`. -### Bonding your mix node +## Node Description (optional) -```admonish caution -From `v1.1.3`, if you unbond your mix node that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +In order to easily identify your node via human-readable information later on, you can `describe` your Mix Node with the following command: + +``` +./nym-mixnode describe --id +``` +Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details --id ` command. It also shows up in the node explorer to let people know what your node is about and link to your website. + +You can set your node description, by creating a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//config/description.toml`). The file should look like this example: + +```toml +name = "Winston Smith" +description = "I am the Sphinx" +link = "https://nymtech.net" +location = "Giza, Egypt" ``` -#### Bond via the Desktop wallet (recommended) +> Remember to restart your `nym-mixnode` process in order for the new description to be propagated. -You can bond your mix node via the Desktop wallet. +## Running your Mix Node + +Run your Mix Node with: + +``` +./nym-mixnode run --id +``` + +Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. + +## Bonding your Mix Node + +```admonish caution +From `v1.1.3`, if you unbond your Mix Node that means you are leaving the mixnet and you will lose all your delegations (permanently). You can join again with the same identity key, however, you will start with **no delegations**. +``` + +To initialise, run and bond your Mix Node are the minimum steps to do in order for your Mix Node to work. However we recommend to do a few more steps before bonding. These steps will make it easier for you as a node operator on a long run as well as for others to possibly delegate Nym tokens to your Mix Node. These steps are: + +- [Describe your Mix Node](./mix-node-setup.md#node-description-optional) +- [Configure your firewall](./maintenance.md#configure-your-firewall) +- [Automate your Mix Node](./maintenance.md#vps-setup-and-automation) +- Set the [ulimit](./maintenance.md#set-the-ulimit-via-systemd-service-file), in case you haven't automated with [systemd](./maintenance.md#set-the-ulimit-on-non-systemd-based-distributions) + +### Bond via the Desktop wallet (recommended) + +You can bond your Mix Node via the Desktop wallet. * Open your wallet, and head to the `Bond` page, then select the node type `Mixnode` and input your node details. Press `Next`. * Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`. -* You will be asked to run a the `sign` command with your `gateway` - copy and paste the long signature as the value of `--contract-msg` and run it. +* You will be asked to run a the `sign` command with your `mixnode` - copy and paste the long signature as the value of `--contract-msg` and run it. ``` ./nym-mixnode sign --id --contract-msg @@ -126,82 +141,42 @@ It will look something like this: ~~~admonish example collapsible=true title="Console output" ``` -./nym-mixnode sign --id upgrade_test --contract-msg 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 - - _ __ _ _ _ __ ___ - | '_ \| | | | '_ \ _ \ - | | | | |_| | | | | | | - |_| |_|\__, |_| |_| |_| - |___/ - - (nym-mixnode - version v1.1.29) - - ->>> attempting to sign 22Z9wt4PyiBCbMiErxj5bBa4VCCFsjNawZ1KnLyMeV9pMUQGyksRVANbXHjWndMUaXNRnAuEVJW6UCxpRJwZe788hDt4sicsrv7iAXRajEq19cWPVybbUqgeo76wbXbCbRdg1FvVKgYZGZZp8D72p5zWhKSBRD44qgCrqzfV1SkiFEhsvcLUvZATdLRocAUL75KmWivyRiQjCE1XYEWyRH9yvRYn4TymWwrKVDtEB63zhHjATN4QEi2E5qSrSbBcmmqatXsKakbgSbQoLsYygcHx7tkwbQ2HDYzeiKP1t16Rhcjn6Ftc2FuXUNnTcibk2LQ1hiqu3FAq31bHUbzn2wiaPfm4RgqTwGM4eqnjBofwR3251wQSxbYwKUYwGsrkweRcoPuEaovApR9R19oJ7GVG5BrKmFwZWX3XFVuECe8vt1x9MY7DbQ3xhAapsHhThUmzN6JPPU4qbQ3PdMt3YVWy6oRhap97ma2dPMBaidebfgLJizpRU3Yu7mtb6E8vgi5Xnehrgtd35gitoJqJUY5sB1p6TDPd6vk3MVU1zqusrke7Lvrud4xKfCLqp672Bj9eGb2wPwow643CpHuMkhigfSWsv9jDq13d75EGTEiprC2UmWTzCJWHrDH7ka68DZJ5XXAW67DBewu7KUm1jrJkNs55vS83SWwm5RjzQLVhscdtCH1Bamec6uZoFBNVzjs21o7ax2WHDghJpGMxFi6dmdMCZpqn618t4 ->>> decoding the message... ->>> message to sign: {"nonce":0,"algorithm":"ed25519","message_type":"mixnode-bonding","content":{"sender":"n1eufxdlgt0puwrwptgjfqne8pj4nhy2u5ft62uq","proxy":null,"funds":[{"denom":"unym","amount":"100000000"}],"data":{"mix_node":{"host":"62.240.134.189","mix_port":1789,"verloc_port":1790,"http_api_port":8000,"sphinx_key":"CfZSy1jRfrfiVi9JYexjFWPqWkKoY72t7NdpWaq37K8Z","identity_key":"DhmUYedPZvhP9MMwXdNpPaqCxxTQgjAg78s2nqtTTiNF","version":"1.1.14"},"cost_params":{"profit_margin_percent":"0.1","interval_operating_cost":{"denom":"unym","amount":"40000000"}}}}} + + ``` ~~~ * Copy the resulting signature: -``` ->>> The base58-encoded signature is: -2GbKcZVKFdpi3sR9xoJWzwPuGdj3bvd7yDtDYVoKfbTWdpjqAeU8KS5bSftD5giVLJC3gZiCg2kmEjNG5jkdjKUt +```sh +# >>> The base58-encoded signature is: +2bbDJSmSo9r9qdamTNygY297nQTVRyQaxXURuomVcRd7EvG9oEC8uW8fvZZYnDeeC9iWyG9mAbX2K8rWEAxZBro1 ``` * And paste it into the wallet nodal, press `Next` and confirm the transaction. -![Paste Signature](../images/wallet-screenshots/wallet-sign.png) +![Paste Signature](../images/wallet-screenshots/wallet-sign.png) +*This image is just an example, copy-paste your own base58-encoded signature* * Your node will now be bonded and ready to mix at the beginning of the next epoch (at most 1 hour). -> You are asked to `sign` a transaction on bonding so that the mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. - -#### Bond via the CLI (power users) -If you want to bond your mix node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. - -### Running your mix node - -Now you've bonded your mix node, run it with: - -``` -./nym-mixnode run --id -``` +> You are asked to `sign` a transaction on bonding so that the Mixnet smart contract is able to map your nym address to your node. This allows us to create a nonce for each account and defend against replay attacks. If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running. Note that your node's public identity key is displayed during startup, you can use it to identify your node in the list. -Have a look at the saved configuration files in `$HOME/.nym/mixnodes/` to see more configuration options. +### Bond via the CLI (power users) +If you want to bond your Mix Node via the CLI, then check out the [relevant section in the Nym CLI](https://nymtech.net/docs/tools/nym-cli.html#bond-a-mix-node) docs. -## Node Description (optional) - -In order to easily identify your node via human-readable information later on in the development of the testnet when delegated staking is implemented, you can `describe` your mix node with the following command: - -``` -./nym-mixnode describe --id -``` -Node description is a short text that describes your node. It is displayed in the `./nym-mixnode list` command and in the `./nym-mixnode node-details --id ` command. It also shows up in the node explorer to let people know what your node is about and link to your website. - -You can set your node description, by creating a file called `description.toml` and put it in the same directory as your `config.toml` file (`~/.nym/mixnodes//description.toml`). The file should look like this example: - -```toml -name = "Winston Smith" -description = "I am the Sphinx" -link = "https://nymtech.net" -location = "Giza, Egypt" -``` - -> Remember to restart your mix node process in order for the new description to be propagated. ## Node Families -Node family involves setting up a group of mix nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic. +Node family involves setting up a group of Mix Nodes that work together to provide greater privacy and security for network communications. This is achieved by having the nodes in the family share information and routes, creating a decentralized network that makes it difficult for third parties to monitor or track communication traffic. ### Create a Node Family -To create a Node family, you will need to install and configure multiple mix nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication. +To create a Node family, you will need to install and configure multiple Mix Nodes, and then use the CLI to link them together into a family. Once your Node family is up and running, you can use it to route your network traffic through a series of nodes, obscuring the original source and destination of the communication. You can use either `nym-cli` which can be downloaded from the [release page](https://github.com/nymtech/nym/releases) or compiling `nyxd`. @@ -214,7 +189,8 @@ Change directory by `cd ///` and run the following on th ~~~admonish example collapsible=true title="Console output" ``` - + + ``` ~~~ @@ -248,7 +224,8 @@ Change directory by `cd ///` and run the following on th ~~~admonish example collapsible=true title="Console output" ``` - + + ``` ~~~ @@ -274,7 +251,7 @@ To get the node owner signature, use: If wanting to leave, run the same initial command as above, followed by: Using `nym-cli`: - + ``` ./nym-cli cosmwasm execute '{"leave_family": {"signature": "","family_head": "","owner_signautre": ""}}' --mnemonic ``` @@ -287,7 +264,7 @@ Using `nyxd`: ## Checking that your node is mixing correctly ### Network explorers -Once you've started your mix node and it connects to the validator, your node will automatically show up in the 'Mix nodes' section of either the Nym Network Explorers: +Once you've started your Mix Node and it connects to the validator, your node will automatically show up in the 'Mix Nodes' section of either the Nym Network Explorers: - [Mainnet](https://explorer.nymtech.net/overview) - [Sandbox testnet](https://sandbox-explorer.nymtech.net/) @@ -301,9 +278,7 @@ There are also 2 community explorers which have been created by [Nodes Guru](htt For more details see [Troubleshooting FAQ](../nodes/troubleshooting.md) - - ## Maintenance -For mix node upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) +For Mix Node upgrade, firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md) diff --git a/documentation/operators/src/nodes/network-requester-setup.md b/documentation/operators/src/nodes/network-requester-setup.md index 9945bf0d0f..90bd37d3fe 100644 --- a/documentation/operators/src/nodes/network-requester-setup.md +++ b/documentation/operators/src/nodes/network-requester-setup.md @@ -1,9 +1,9 @@ # Network Requesters -> The Nym network requester was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. +> Nym Network Requester was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. ```admonish info -As a result of [Project Smoosh](../faq/smoosh-faq.md), the current version of `nym-gateway` binary also contains `nym-network-requester` functionality which can be enabled [by the operator](./gateway-setup.md#initialising-gateway-with-network-requester). This combination is a basis of Nym exit gateway node - an essential piece in our new setup. Please read more in our [Project Smoosh FAQ](../faq/smoosh-faq.md) and [Exit Gateways Page](../legal/exit-gateway.md). We recommend operators begin to shift their setups to this new combined node, instead of operating two separate binaries. +As a result of [Project Smoosh](../faq/smoosh-faq.md), the current version of `nym-gateway` binary also contains `nym-network-requester` functionality which can be enabled [by the operator](./gateway-setup.md#initialising-gateway-with-network-requester). This combination is a basis of Nym Exit Gateway node - an essential piece in our new setup. Please read more in our [Project Smoosh FAQ](../faq/smoosh-faq.md) and [Exit Gateways Page](../legal/exit-gateway.md). We recommend operators begin to shift their setups to this new combined node, instead of operating two separate binaries. ``` > Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. @@ -15,23 +15,23 @@ As a result of [Project Smoosh](../faq/smoosh-faq.md), the current version of `n ## Preliminary steps -Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your network requester. +Make sure you do the preparation listed in the [preliminary steps page](../preliminary-steps.md) before setting up your Network Requester. ## Network Requester Whitelist -If you have access to a server, you can run the network requester, which allows Nym users to send outbound requests from their local machine through the mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. +If you have access to a server, you can run the Network Requester, which allows Nym users to send outbound requests from their local machine through the Mixnet to a server, which then makes the request on their behalf, shielding them (and their metadata) from clearnet, untrusted and unknown infrastructure, such as email or message client servers. -By default the network requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester//`) as a whitelist for outbound requests. +By default the Network Requester is **not** an open proxy (although it can be used as one). It uses a file called `allowed.list` (located in `~/.nym/service-providers/network-requester//`) as a whitelist for outbound requests. -**Note:** If you run network requester as a part of the exit gateway (suggested setup) the `allowed.list` will be stored in `~/.nym/gateways//data/network-requester-data/allowed.list`. +**Note:** If you run Network Requester as a part of the Exit Gateway (suggested setup) the `allowed.list` will be stored in `~/.nym/gateways//data/network-requester-data/allowed.list`. Any request to a URL which is not on this list will be blocked. On startup, if this file is not present, the requester will grab the default whitelist from [Nym's default list](https://nymtech.net/.wellknown/network-requester/standard-allowed-list.txt) automatically. -This default whitelist is useful for knowing that the majority of Network requesters are able to support certain apps 'out of the box'. +This default whitelist is useful for knowing that the majority of Network Requesters are able to support certain apps 'out of the box'. -**Operators of a network requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. +**Operators of a Network Requester are of course free to edit this file and add the URLs of services they wish to support to it!** You can find instructions below on adding your own URLs or IPs to this list. The domains and IPs on the default whitelist can be broken down by application as follows: @@ -109,14 +109,14 @@ alephium.org ## Network Requester Directory -You can find a list of Network requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. +You can find a list of Network Requesters running the default whitelist in the [explorer](https://explorer.nymtech.net/network-components/service-providers). This list comprises of the NRs running as infrastructure for NymConnect. -> We are currently working on a smart-contract based solution more in line with how Mix nodes and Gateways announce themselves to the network. +> We are currently working on a smart-contract based solution more in line with how Mix Nodes and Gateways announce themselves to the network. ## Viewing command help ```admonish info -If you run your network requester as a part of your exit gateway according to the suggested setup, please skip this part of the page and read about [exit gateway setup](./gateway-setup.md#initialising-gateway-with-network-requester) instead. +If you run your Network Requester as a part of your Exit Gateway according to the suggested setup, please skip this part of the page and read about [Exit Gateway setup](./gateway-setup.md#initialising-gateway-with-network-requester) instead. ``` To begin, move to `/target/release` directory from which you run the node commands: @@ -141,9 +141,9 @@ You can check the required parameters for available commands by running: > Adding `--no-banner` startup flag will prevent Nym banner being printed even if run in tty environment. -## Initializing and running your network requester +## Initializing and running your Network Requester -The network-requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: +The Network Requester needs to be initialized before it can be run. This is required for the embedded nym-client to connect successfully to the Mixnet. We want to specify an `` using the `--id` command and give it a value of your choice. The following command will achieve that: ``` ./nym-network-requester init --id @@ -164,11 +164,11 @@ Now that we have initialized our network-requester, we can start it with the fol ./nym-network-requester run --id ``` -## Using your network requester +## Using your Network Requester The next thing to do is use your requester, share its address with friends (or whoever you want to help privacy-enhance their app traffic). Is this safe to do? If it was an open proxy, this would be unsafe, because any Nym user could make network requests to any system on the internet. -To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list` or if network requester is ran as a part of [exit gateway](./gateway-setup.md#initialising-gateway-with-network-requester), the `allowed.list` will be stored in `~/.nym/gateways//data/network-requester-data/allowed.list` +To make things a bit less stressful for administrators, the Network Requester drops all incoming requests by default. In order for it to make requests, you need to add specific domains to the `allowed.list` file at `$HOME/.nym/service-providers/network-requester/allowed.list` or if Network Requester is ran as a part of [Exit Gateway](./gateway-setup.md#initialising-gateway-with-network-requester), the `allowed.list` will be stored in `~/.nym/gateways//data/network-requester-data/allowed.list` ### Global vs local allow lists Your Network Requester will check for a domain against 2 lists before allowing traffic through for a particular domain or IP. @@ -177,34 +177,34 @@ Your Network Requester will check for a domain against 2 lists before allowing t * The second is the local `allowed.list` file. -### Supporting custom domains with your network requester -It is easy to add new domains and services to your network requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`. +### Supporting custom domains with your Network Requester +It is easy to add new domains and services to your Network Requester - simply find out which endpoints (both URLs and raw IP addresses are supported) you need to whitelist, and then add these endpoints to your `allowed.list`. > In order to keep things more organised, you can now use comments in the `allow.list` like the example at the top of this page. -How to go about this? Have a look in your nym-network-requester config directory: +How to go about this? Have a look in your `nym-network-requester` config directory: ``` -# network requester binary +# nym-network-requester binary ls -lt $HOME/.nym/service-providers/network-requester/*/data | grep "list" -# exit gateway binary +# nym-gateway binary ls -lt $HOME/.nym/gateways/*/data/network-requester-data | grep "list" # returns: allowed.list unknown.list ``` -We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](https://nymtech.net/docs/clients/socks5-client.html) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to delete the copied ones in `unknown.list` and restart your exit gateway or standalone network requester. +We already know that `allowed.list` is what lets requests go through. All unknown requests are logged to `unknown.list`. If you want to try using a new client type, just start the new application, point it at your local [socks client](https://nymtech.net/docs/clients/socks5-client.html) (configured to use your remote `nym-network-requester`), and keep copying URLs from `unknown.list` into `allowed.list` (it may take multiple tries until you get all of them, depending on the complexity of the application). Make sure to delete the copied ones in `unknown.list` and restart your Exit Gateway or standalone Network Requester. > If you are adding custom domains, please note that whilst they may appear in the logs of your network-requester as something like `api-0.core.keybaseapi.com:443`, you **only need** to include the main domain name, in this instance `keybaseapi.com` ### Running an open proxy -If you *really* want to run an open proxy, perhaps for testing purposes for your own use or among a small group of trusted friends, it is possible to do so. You can disable network checks by passing the flag `--open-proxy` flag when you run it. If you run in this configuration, you do so at your own risk. +If you *really* want to run an open proxy, perhaps for testing purposes for your own use or among a small group of trusted friends, it is possible to do so. You can disable Network checks by passing the flag `--open-proxy` flag when you run it. If you run in this configuration, you do so at your own risk. -## Testing your network requester -1. Make sure `nymtech.net` is in your `allowed.list` (remember to restart your network requester). +## Testing your Network Requester +1. Make sure `nymtech.net` is in your `allowed.list` (remember to restart your Network Requester). -2. Ensure that your network-requester is initialized and running. +2. Ensure that your `nym-network-requester` is initialized and running. 3. In another terminal window, run the following: @@ -220,5 +220,5 @@ This command should return the following: ## Maintenance -For network requester upgrade (including an upgrade from `= v1.1.10`), firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md). +For Network Requester upgrade (including an upgrade from `= v1.1.10`), firewall setup, port configuration, API endpoints, VPS suggestions, automation and more, see the [maintenance page](./maintenance.md). diff --git a/documentation/operators/src/nodes/nym-api.md b/documentation/operators/src/nodes/nym-api.md new file mode 100644 index 0000000000..8c88cc4b22 --- /dev/null +++ b/documentation/operators/src/nodes/nym-api.md @@ -0,0 +1,182 @@ +# Nym API Setup + +[//]: # (> The nym-api binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just the API with `cargo build --release --bin nym-api`.) + +> The `nym-api` binary will be released in the immediate future - we're releasing this document beforehand so that Validators have information as soon as possible and get an idea of what to expect. This doc will be expanded over time as we release the API binary itself as well as start enabling functionality. +> +> You can build the API with `cargo build --release --bin nym-api`. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## What is the Nym API? +The Nym API is a binary that will be operated by the Nyx validator set. This binary can be run in several different modes, and has two main bits of functionality: +* network monitoring (calculating the routing score of Mixnet nodes) +* generation and validation of [zk-Nyms](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a), our implementation of the Coconut Selective Disclosure Credential Scheme. + +This is important for both the proper decentralisation of the network uptime calculation and, more pressingly, enabling the NymVPN to utilise privacy preserving payments. + +The process of enabling these different aspects of the system will take time. At the moment, Nym API operators will only have to run the binary in a minimal 'caching' mode in order to get used to maintaining an additional process running alongside a full node. + +```admonish warning +It is highly recommended to run `nym-api` alongside a full node since you will be exposing HTTP port(s) to the Internet. We also observed degradation in p2p and block signing operations when `nym-api` was run alongside a signing validator. +``` + +### Rewards +Operators of Nym API will be rewarded for performing the extra work of taking part in credential generation. These rewards will be calculated **separately** from rewards for block production. + +Rewards for credential signing will be calculated hourly, with API operators receiving a proportional amount of the reward pool (333NYM per hour / 237,600 NYM per month), proportional to the percentage of credentials they have signed. + +### (Coming Soon) Machine Specs +We are working on load testing currently in order to get good specs for a full node + Nym API setup. Bear in mind that credential signing is primarily CPU-bound. + +### (Coming Soon) Credential Generation +Validators that take part in the DKG ceremony will become part of the 'quorum' generating and verifying zk-Nym credentials. These will initially be used for private proof of payment for NymVPN (see our blogposts [here](https://blog.nymtech.net/nymvpn-an-invitation-for-privacy-experts-and-enthusiasts-63644139d09d) and [here](https://blog.nymtech.net/zk-nyms-are-here-a-major-milestone-towards-a-market-ready-mixnet-a3470c9ab10a) for more on this), and in the future will be expanded into more general usecases such as [offline ecash](https://arxiv.org/abs/2303.08221). + +The DKG ceremony will be used to create a subset of existing validators - referred to as the quorum. As outlined above, they will be the ones taking part in the generation and verification of zk-Nym credentials. The size of the 'minimum viable quorum' is 10 - we are aiming for a larger number than this for the initial quorum in order to have some redundancy in the case of a Validator dropping or going offline. + +We will be releasing more detailed step-by-step documentation for involved validators nearer to the ceremony itself, but at a high level it will involve: +* the deployment and initialisation of [`group`](https://github.com/nymtech/nym/tree/develop/contracts/multisig/cw4-group) and [`multisig`](https://github.com/nymtech/nym/tree/develop/contracts/multisig) contracts by Nym. Validators that are members of the `group` contract are the only ones that will be able to take part in the ceremony. +* the deployment and initialisation of an instance of the [DKG contract](https://github.com/nymtech/nym/tree/develop/contracts/coconut-dkg) by Nym. +* Validators will update their `nym-api` configs with the address of the deployed contracts. They will also stop running their API instance in caching only mode, instead switching over run with the `--enabled-credentials-mode`. +* From the perspective of operators, this is all they have to do. Under the hood, each `nym-api` instance will then take part in several rounds of key submission, verification, and derivation. This will continue until quorum is acheived. More information on this will be released closer to the time of the ceremony. + +**We will be communicating individually with members of the existing Validator set who have expressed interest in joining the quorum concerning the timing and specifics of the ceremony**. + +## Current version +``` + +``` + +## Setup and Usage +### Viewing command help +You can check that your binary is properly compiled with: + +``` +./nym-api --help +``` + +Which should return a list of all available commands. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nym-api --help +``` + +### Initialising your Nym API Instance +Initialise your API instance with: + +``` +./nym-api init +``` + +You can optionally pass a local identifier for this instance with the `--id` flag. Otherwise the ID of your instance defaults to `default`. + +### Running your Nym API Instance +The API binary currently defaults to running in caching mode. You can run your API with: + +``` +./nym-api run +``` + +By default the API will be trying to query a running `nyxd` process (either a validator or RPC node) on `localhost:26657`. This value can be modified either via the `--nyxd-validator ` flag on `run`: + +``` +./nym-api run --nyxd-validator https://rpc.nymtech.net:443 +``` + +> You can also change the value of `local_validator` in the config file found by default in `$HOME/.nym/nym-api//config/config.toml`. + +This process is quite noisy, but informative: + +~~~admonish example collapsible=true title="Console output" +``` +Starting nym api... + 2023-12-12T14:29:55.800Z INFO rocket::launch > 🔧 Configured for release. + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > address: 127.0.0.1 + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > port: 8000 + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > workers: 4 + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > max blocking threads: 512 + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > ident: Rocket + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > IP header: X-Real-IP + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > temp dir: /tmp + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > http/2: true + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > keep-alive: 5s + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > tls: disabled + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > log level: critical + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > cli colors: true + 2023-12-12T14:29:55.800Z INFO rocket::launch > 📬 Routes: + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_registered_names) GET /v1/names + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnodes) GET /v1/mixnodes + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_gateways) GET /v1/gateways + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_services) GET /v1/services + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /v1/openapi.json + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_full_circulating_supply) GET /v1/circulating-supply + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_current_epoch) GET /v1/epoch/current + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_active_set) GET /v1/mixnodes/active + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnodes_detailed) GET /v1/mixnodes/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_rewarded_set) GET /v1/mixnodes/rewarded + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_gateways_described) GET /v1/gateways/described + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_interval_reward_params) GET /v1/epoch/reward_params + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_blacklisted_mixnodes) GET /v1/mixnodes/blacklisted + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_blacklisted_gateways) GET /v1/gateways/blacklisted + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_total_supply) GET /v1/circulating-supply/total-supply-value + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_circulating_supply) GET /v1/circulating-supply/circulating-supply-value + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_active_set_detailed) GET /v1/mixnodes/active/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_rewarded_set_detailed) GET /v1/mixnodes/rewarded/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /cors/ + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/ + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/index.css + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/index.html + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/swagger-ui.css + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/oauth2-redirect.html + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/swagger-ui-bundle.js + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/swagger-ui-config.json + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/swagger-initializer.js + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > GET /swagger/swagger-ui-standalone-preset.js + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnodes_detailed) GET /v1/status/mixnodes/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnode_inclusion_probabilities) GET /v1/status/mixnodes/inclusion_probability + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnode_status) GET /v1/status/mixnode//status + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_active_set_detailed) GET /v1/status/mixnodes/active/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_rewarded_set_detailed) GET /v1/status/mixnodes/rewarded/detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnode_stake_saturation) GET /v1/status/mixnode//stake-saturation + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (get_mixnode_inclusion_probability) GET /v1/status/mixnode//inclusion-probability + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (network_details) GET /v1/network/details + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (nym_contracts) GET /v1/network/nym-contracts + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > (nym_contracts_detailed) GET /v1/network/nym-contracts-detailed + 2023-12-12T14:29:55.800Z INFO rocket::launch > 📡 Fairings: + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > Validator Cache Stage (ignite) + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > Circulating Supply Cache Stage (ignite) + 2023-12-12T14:29:55.800Z INFO rocket::launch::_ > Shield (liftoff, response, singleton) + 2023-12-12T14:29:55.801Z INFO rocket::launch::_ > CORS (ignite, request, response) + 2023-12-12T14:29:55.801Z INFO rocket::launch::_ > Node Status Cache (ignite) + 2023-12-12T14:29:55.801Z INFO rocket::shield::shield > 🛡️ Shield: + 2023-12-12T14:29:55.801Z INFO rocket::shield::shield::_ > X-Content-Type-Options: nosniff + 2023-12-12T14:29:55.801Z INFO rocket::shield::shield::_ > X-Frame-Options: SAMEORIGIN + 2023-12-12T14:29:55.801Z INFO rocket::shield::shield::_ > Permissions-Policy: interest-cohort=() + 2023-12-12T14:29:55.801Z WARN rocket::launch > 🚀 Rocket has launched from http://127.0.0.1:8000 + 2023-12-12T14:29:56.375Z INFO nym_api::nym_contract_cache::cache::refresher > Updating validator cache. There are 888 mixnodes and 105 gateways + 2023-12-12T14:29:56.375Z INFO nym_api::node_status_api::cache::refresher > Updating node status cache + 2023-12-12T14:29:57.359Z INFO nym_api::circulating_supply_api::cache > Updating circulating supply cache + 2023-12-12T14:29:57.359Z INFO nym_api::circulating_supply_api::cache > the mixmining reserve is now 220198535489690unym + 2023-12-12T14:29:57.359Z INFO nym_api::circulating_supply_api::cache > the number of tokens still vesting is now 145054386857730unym + 2023-12-12T14:29:57.359Z INFO nym_api::circulating_supply_api::cache > the circulating supply is now 634747077652580unym + 2023-12-12T14:30:00.803Z INFO nym_api::support::caching::refresher > node-self-described-data-refresher: refreshing cache state + 2023-12-12T14:31:56.290Z INFO nym_api::nym_contract_cache::cache::refresher > Updating validator cache. There are 888 mixnodes and 105 gateways + 2023-12-12T14:31:56.291Z INFO nym_api::node_status_api::cache::refresher > Updating node status cache +``` +~~~ + +## Automation +You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md) for an example `service` file. + +## Exposing web endpoint using HTTPS +It is recommended to expose the webserver over HTTPS by using a webserver like Nginx. An example configuration for configuring Nginx is listed [on the maintenance page](maintenance.md#nym-api-configuration) diff --git a/documentation/operators/src/nodes/nymvisor-upgrade.md b/documentation/operators/src/nodes/nymvisor-upgrade.md new file mode 100644 index 0000000000..e3722634ab --- /dev/null +++ b/documentation/operators/src/nodes/nymvisor-upgrade.md @@ -0,0 +1,299 @@ +# Automatic Node Upgrade: Nymvisor Setup and Usage + +> The Nymvisor binary was built in the [building nym](../binaries/building-nym.md) section. If you haven't yet built Nym and want to run the code, go there first. You can build just Nymvisor with `cargo build --release --bin nymvisor`. + +> Any syntax in `<>` brackets is a user's unique variable. Exchange with a corresponding name without the `<>` brackets. + +## What is Nymvisor? +Nymvisor is a process manager for Nym binaries that monitors the Nym release information for any newly released binaries. If it detects any changes, Nymvisor can automatically download the binary, stop the current binary, switch from the old binary to the new one, and finally restart the underlying process with the new binary. + +In essence, it tries to mirror the behaviour of [Cosmovisor](https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor), a tool used by Cosmos blockchain operators for managing/automating chain upgrades. Nymvisor, however, introduces some Nym-specific changes since, for example, upgrade information is obtained from our GitHub [releases page](https://github.com/nymtech/nym/releases) instead of (in the case of Cosmos blockchains) governance proposals. + +You can use Nymvisor to automate the upgrades of the following binaries: +* `nym-api` +* `nym-mixnode` +* `nym-gateway` +* `nym-network-requester` +* `nym-client` +* `nym-socks5-client` + +```admonish warning +Nymvisor is an early and experimental software. Users should use it at their own risk. +``` + +## Current version +``` + +``` + +## Preliminary steps +You need to have at least one Mixnet node / client / Nym API instance already set up on the **same VPS** that you wish to run Nymvisor on. + +> Using Nymvisor presumes your VPS is running an operating system that is compatible with the pre-compiled binaries avaliable on the [Github releases page](https://github.com/nymtech/nym/releases). If you're not, then until we're packaging for a greater variety of operating systems, you're stuck with [manually upgrading your node](manual-upgrade.md). + +## Setup and Usage +### Viewing command help +You can check that your binaries are properly compiled with: + +``` +./nymvisor --help +``` + +Which should return a list of all available commands. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +You can also check the various arguments required for individual commands with: + +``` +./nymvisor --help +``` + +### Initialising your Nymvisor Instance +> This example will use the Mix Node binary as an example - however replacing `nym-mixnode` with any other supported binary will work the same. + +Initialise your Nymvisor instance with the following command. You must initialise Nymvisor with the binary you wish to add upgrades for: + +``` +./nymvisor init --daemon-home ~/.nym// +``` + +Where the value of `--daemon-home` might be `~/.nym/mixnodes/my-node` and `` might be `/home/my_user/nym/target/release/nym-mixnode`, or wherever your node binary is located. + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +By default this will create config files at `~/.nym/nymvisors/instances/-default/config/config.toml` as shown in the console output above. For config options look at the different `--flags` available, or the [environment variables](nymvisor-upgrade.md#environment-variables) section below. + +### Running your Nymvisor Instance +Nymvisor acts as a wrapper around the specified node process - it has to do this in order to be able to pause and restart this process. As such, you need to run your node _via_ Nymvisor! + +The interface to the `nymvisor run ` command is quite simple. Any argument passed after the `run` command will be passed directly to the underlying daemon, for example: `nymvisor run run --id my-mixnode` will run the `$DAEMON_NAME run --id my-mixnode` command (where `DAEMON_NAME` is the name of the binary itself (e.g. `nym-api`, `nym-mixnode`, etc.)). + +`run` Nymvisor and start your node via the following command. Make sure to stop any existing node before running this command. + +``` +./nymvisor run run --id +``` + +~~~admonish example collapsible=true title="Console output" +``` + +``` +~~~ + +Nymvisor will now manage your node process (for an in-depth overview of this command check the [in-depth command information](./nymvisor-upgrade.md#commands-in-depth) below). It will periodically poll [this endpoint](https://nymtech.net/.wellknown/nym-mixnode/upgrade-info.json) (replace `nym-mixnode` with whatever node you may actually be running via Nymvisor) and check for a new `version` of the binary it is watching. If this exists, it will then, using the information there: +* pause your node process +* grab the new binary (`version`) +* verify it against the provided `checksum` +* perform a data backup of the existing node +* replace the old binary with the new one +* restart the process + +And that's it! Check the [maintenance page](./maintenance.md#for-nymvisor) for information on Nymvisor process maintenance and automation, and you can find more in-depth information about the various aspects of Nymvisor such as what happens [under the hood](#commands-in-depth) for various commands. + +### Creating an Adhoc Upgrade +`nymvisor add-upgrade --upgrade-name= --arg1=value1 --arg2=value2 ...` can be used to amend an existing `upgrade-plan.json` by creating new entries or to add an executable to an existing scheduled upgrade so that it would not have to be downloaded. + +>Generally users **will not have to use this command**. Situations in which this command might be used are: +> - an adhoc upgrade if e.g. a patched version of a binary was required +> - if a user doesn't trust the verification process of Nymvisor's pipeline and wishes to build/verify the binary themselves before using Nymvisor to perform the upgrade +> - if a user isn't using a currently supported operating system and needs to manually specify a binary they have built themselves + +Similarly to `init`, `add-upgrade` requires a positional argument specifying a valid path to the upgrade binary. Furthermore, the `--upgrade-name` keyword argument must be set in order to declare the upgrade name. The remaining arguments are optional. They include: +- `--force` - if specified, will allow Nymvisor to overwrite existing upgrade binary / `upgrade-info.json` files if they already exist +- `--add-binary` - indicate that this command should only add binary to an existing scheduled upgrade +- `--now` - if specified will force the upgrade to be performed immediately (technically not 'immediately' within few seconds). It can't be specified alongside either `--upgrade-time` or `--upgrade-delay` arguments +- `--publish-date` - if a new `upgrade-info.json` file is going to be created, this argument will specify the `publish_date` metadata field. Otherwise, the current time will be used. The [RFC3339 datetime](https://www.rfc-editor.org/rfc/rfc3339) format is expected +- `--upgrade-time` - specifies the time at which the provided upgrade will be performed (RFC3339 formatted). If left unset, the upgrade will be performed in 15 minutes. It can't be specified alongside either `--now` or `--upgrade-delay` arguments. +- `--upgrade-delay` - specifies delay until the provided upgrade is going to get performed. If let unset, the upgrade will be performed in 15 minutes. It can't be specified alongside either `--upgrade_time` or `--now` arguments. + +## Config +The output format of `nymvisor config` can be further configured with `--output` argument. By default a human-readable text representation is used: +``` +id: nym-mixnode-default +daemon name: nym-mixnode +daemon home: /home/nym/.nym/mixnodes/my-mixnode +upstream base upgrade url: https://nymtech.net/.wellknown/ +disable nymvisor logs: false +CUSTOM upgrade data directory "" +upstream absolute upgrade url: "" +allow binaries download: true +enforce download checksum: true +restart after upgrade: true +restart on failure: false +on failure restart delay: 10s +max startup failures: 10 +startup period duration: 2m +shutdown grace period: 10s +CUSTOM backup data directory "" +UNSAFE skip backups false +``` + +Adding `--output=json` would format the same data into JSON which can be more easily parsed programmatically to e.g. pipe the output into `jq` for further processing. +``` +nymvisor config --output=json +``` +outputs: +``` +{"nymvisor":{"id":"nym-mixnode-default","upstream_base_upgrade_url":"https://nymtech.net/.wellknown/","upstream_polling_rate":"1h","disable_logs":false,"upgrade_data_directory":null},"daemon":{"name":"nym-mixnode","home":"/home/nym/.nym/mixnodes/my-mixnode","absolute_upstream_upgrade_url":null,"allow_binaries_download":true,"enforce_download_checksum":true,"restart_after_upgrade":true,"restart_on_failure":false,"failure_restart_delay":"10s","max_startup_failures":10,"startup_period_duration":"2m","shutdown_grace_period":"10s","backup_data_directory":null,"unsafe_skip_backup":false}} +``` + +## CLI Overview +Command options are: +- `help`, `--help`, or `-h` - Output Nymvisor help information and display the available commands. +- `config` - Display the current Nymvisor configuration, that means displaying the current configuration file that might have been overridden with environment variables value that Nymvisor is using. +- `init` - Generate a `config.toml` file for this instance of Nymvisor that will use the provided arguments alongside any environmental variables that are set. +- `add-upgrade` - Add an upgrade manually to Nymvisor. This command allows you to easily add the binary corresponding to an upgrade or amend the existing `upgrade-plan.json` whilst creating new `upgrade-info.json` file. +- `build-info` - Output the build information. +- `daemon-build-info` - Output the build information of the current binary used by the associated daemon. +- `run` - Run the configured binary using the rest of the provided arguments. +- `-V` or `--version` - Output the Nymvisor version + +Similarly to other Nym binaries, Nymvisor supports a global `--config-env-file` or `-c` flag that allows specifying path to a `.env` file defining any relevant environmental variables that are going to be applied to any of the Nymvisor commands as described in the [Environment section](./nymvisor-upgrade.md#environment-variables). + +For commands that depend on Nymvisor config file (i.e. all but `init`), the configuration file is loaded as follows: +- if available, reading `$NYMVISOR_CONFIG_PATH` +- otherwise, if `$NYMVISOR_ID` is set, a default path will be used, i.e. `$HOME/.nym/nymvisors/instances/$NYMVISOR_ID/config/config.toml` +- finally, if there's only a single `nymvisor` instance instantiated (as defined by directories in `$HOME/.nym/nymvisors/instances`), that one will be loaded +- if all of the above fails, an error is returned + +Nymvisor attempts to load the file from the derived path. If it fails, it attempts to use one of the older schemas to and upgrade it as it goes, the loaded configuration is then overridden with any value that might have been set in the environment. + +## Environment Variables + +> Please note environmental variables take precedence over any arguments passed, i.e. if one were to specify `--daemon_home="/foo"` and set `DAEMON_HOME="bar"`, the value of `"bar"` would end up being used. + +For any of its commands as described in [CLI Overview section](./nymvisor-upgrade.md#cli-overview), Nymvisor reads its configuration from the following environment variables: + +- `NYMVISOR_ID` is the human-readable identifier of the particular Nymvisor instance. +- `NYMVISOR_CONFIG_PATH` is used to manually override path to the configuration file of the Nymvisor instance. +- `NYMVISOR_UPSTREAM_BASE_UPGRADE_URL` (defaults to https://nymtech.net/.wellknown/) is the base url of the upstream source for obtaining upgrade information for the daemon. It will be used fo constructing the full url, i.e. `$NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json`. +- `NYMVISOR_UPSTREAM_POLLING_RATE` (defaults to 1h) is polling rate the upstream url for upgrade information. +- `NYMVISOR_DISABLE_LOGS` (defaults to `false`). If set to `true`, this will disable Nymvisor logs (but not the underlying process) completely. +- `NYMVISOR_UPGRADE_DATA_DIRECTORY` is the custom directory for upgrade data - binaries and upgrade plans. If not set, the global Nymvisors' data directory will be used instead. +- `DAEMON_NAME` is the name of the binary itself (e.g. `nym-api`, `nym-mixnode`, etc.). +- `DAEMON_HOME` is the location where the `nymvisor/` directory is kept that contains the auxiliary files associated with the underlying daemon instance, such as any backups or current version information, e.g. `$HOME/.nym/nym-api/my-nym-api`, `$HOME/.nym/mixnodes/my-mixnode`, etc. +- `DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL` is the absolute (i.e. the full url) upstream source for upgrade plans for this daemon. The url has to point to an endpoint containing a valid `UpgradeInfo` json file. If set it takes precedence over `NYMVISOR_UPSTREAM_BASE_UPGRADE_URL`. +- `DAEMON_ALLOW_BINARIES_DOWNLOAD` (defaults to `true`), if set to `true`, it will enable auto-downloading of new binaries (as declared by urls in corresponding `upgrade-info.json` files). For security reasons one might wish to disable it and instead manually provide binaries by either placing them in the appropriate directory or by invoking `add-upgrade` command. +- `DAEMON_ENFORCE_DOWNLOAD_CHECKSUM` (defaults to `true`), if set to `true` Nymvisor will require that a checksum is provided in the upgrade plan for the upgrade binary to be downloaded. If disabled, Nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. +- `DAEMON_RESTART_AFTER_UPGRADE` (defaults to `true`), if set to `true` Nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), Nymvisor stops running after an upgrade and requires the system administrator to manually restart it. **Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs.** That is controlled via `DAEMON_RESTART_ON_FAILURE`. +- `DAEMON_RESTART_ON_FAILURE` (defaults to `true`), if set to `true`, Nymvisor will restart the subprocess with the same command-line arguments and flags if it has terminated with a non-zero exit code. +- `DAEMON_FAILURE_RESTART_DELAY` (defaults to 10s), if `DAEMON_RESTART_ON_FAILURE` is set to `true`, this will specify a delay between the process shutdown (with a non-zero exit code) and it being restarted. +- `DAEMON_MAX_STARTUP_FAILURES` (defaults to 10) if `DAEMON_RESTART_ON_FAILURE` is set to `true`, this defines the maximum number of startup failures the subprocess can experience in a quick succession before no further restarts will be attempted and Nymvisor will terminate. +- `DAEMON_STARTUP_PERIOD_DURATION` (defaults to 120s) if `DAEMON_RESTART_ON_FAILURE` is set to `true`, this defines the length of time during which the subprocess is still considered to be in the startup phase when its failures are going to be counted towards the limit defined in `DAEMON_MAX_STARTUP_FAILURES`. +- `DAEMON_SHUTDOWN_GRACE_PERIOD` (defaults to 10s), specifies the amount of time Nymvisor is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt before it sends a kill signal. +- `DAEMON_BACKUP_DATA_DIRECTORY` specifies custom backup directory for daemon data. If not set, `DAEMON_HOME/nymvisor/backups` is used instead. +- `DAEMON_UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, all upgrades will be performed directly without performing any backups. Otherwise (`false`), Nymvisor will back up the contents of `DAEMON_HOME` before trying the upgrade. + +## Dir structure +The folder structure of Nymvisor is heavily inspired by Cosmovisor, but with some notable changes to accommodate our binaries having possibly multiple instances due to their different `--id` flags. The data is spread through three main directories: +- in a global `nymvisors` data directory shared by all Nymvisor instances (default: `$HOME/.nym/nymvisors/data`) that contains daemon upgrade plans, binaries and upgrades histories. It includes a subdirectory for each version of the application (i.e. `genesis` or `upgrades`). Within each subdirectory is the application binary (i.e. `bin/$DAEMON_NAME`), the associated `upgrade-info.json` and any additional auxiliary files associated with each binary. `current` is a symbolic link to the currently active directory (i.e. `genesis` or `upgrades/`) +- in a home directory of a particular `nymvisor` instance (e.g. `$HOME/.nym/nymvisors/instances//`). It includes subdirectories for its configuration file (i.e. `config/config.toml`), that preconfigures the instance, and for any additional persistent data that might be added in the future (i.e. `data`) +- in a `nymvisor` data directory inside the home directory of the managed daemon instance (default: `$HOME/.nym/$DAEMON_NAME/nymvisor`) that contains subdirectories for data backups (i.e. `backups/`) and current version information (`current-version-info.json`) + +A sample full structure looks as follows: + +``` +~/.nym +├── nymvisors +│ ├── instances +│ │ ├── +│ │ │ ├── config +│ │ │ │ └── config.toml +│ │ │ └── data +│ │ │ └── ... +│ │ └── +│ │ └── ... +│ └── data +│ ├── nym-api +│ │ ├── current -> genesis or upgrades/ +│ │ ├── genesis +│ │ │ ├── bin +│ │ │ │ └── nym-api +│ │ │ └── upgrade-info.json +│ │ ├── upgrades +│ │ │ └── +│ │ │ ├── bin +│ │ │ │ └── nym-api +│ │ │ └── upgrade-info.json +│ │ ├── upgrade-history.json +│ │ └── upgrade-plan.json +│ ├── nym-mixnode +│ │ └── ... +│ └── $DAEMON_NAME +│ └── ... +└── nym-api +├── +│ ├── config +│ │ └── +│ ├── data +│ │ └── +│ └── nymvisor +│ ├── backups +│ │ └── +│ │ └── .... +│ └── current-version-info.json +└── +└── ... +``` + +## Commands In-Depth +This section outlines what happens under the hood with the following commands: + +### Init +`init` does the following: +- executes the `$DAEMON_NAME build-info` command on the daemon executable to check its validity and obtain its name +- creates the required directory structure: + - `$DAEMON_HOME/nymvisor` folder if it doesn't yet exist + - `$DAEMON_BACKUP_DATA_DIRECTORY` folder if it doesn't yet exist + - `$NYMVISOR_UPGRADE_DATA_DIRECTORY` folder if it doesn't yet exist + - `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/genesis/bin` folder if it doesn't yet exist + - `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/upgrades` folder if it doesn't yet exist +- copies the provided executable to `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/genesis/bin/$DAEMON_NAME` +- generates initial `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/genesis/upgrade-info.json` file based on the provided binary +- generates initial `$DAEMON_HOME/nymvisor/current-version-info.json` file based on the provided binary +- creates a `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/current` symlink pointing to `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/genesis` +- saves the Nymvisor instance's config file to `$NYMVISOR_CONFIG_PATH` and creates the full directory structure for the file +- outputs (to `stdout`) the full configuration used + +> `nymvisor init` is specifically for initializing Nymvisor, and should **not** be confused with a daemon's `init` command - such as `nym-mixnode init` (e.g. `cosmovisor run init`). + +### Run +`nymvisor run` is a lightweight wrapper around the underlying daemon. It uses only a single thread and spawns three simple tasks: +- an upstream poller that checks the upstream source (as defined by `DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL` or derived from `NYMVISOR_UPSTREAM_BASE_UPGRADE_URL`) for any recently released upgrades. It then creates appropriate `upgrade-info.json` file and updates the `upgrade-plan.json` +- a file watcher for the `upgrade-plan.json` file that can notify the main run loop of upgrades that were added by either the above upstream poller task or via the `add-upgrade` command, +- the daemon run loop that: + - runs the `DAEMON_NAME` with the provided arguments until: + - it completes the execution (with any exit code), + - Nymvisor receives a `SIGINT`, `SIGTERM` or `SIGQUIT`, + - a new upgrade is scheduled to be performed (by other task watching for changes in `upgrade-plan.json` and waiting until the `upgrade_time`, + - if `DAEMON_UNSAFE_SKIP_BACKUP` is not set to `true`, it backups the content of `DAEMON_HOME` directory, + - performs the binary upgrade by: + - creating a temporary, exclusive and non-blocking, `upgrade.lock` file for the `DAEMON_NAME`. `flock` with `LOCK_EX | LOCK_NB` is used for that purpose. The file is created in case users didn't read any warnings and attempted to run multiple instances of `nymvisor` managing the same `DAEMON_NAME`, + - downloading the upgrade binary for the runners architecture using one of the urls defined in `upgrade-info.json`. Note, however, that this is only done if the binary associated with the `` does not already exist and `DAEMON_ALLOW_DOWNLOAD_BINARIES` is set to `true`, + - if the binary has been downloaded and `DAEMON_ENFORCE_DOWNLOAD_CHECKSUM` is set to true, the file checksum is verified using the specified algorithm, + - verifying the upgrade binary - checking if it's a valid executable with expected `build-info`. Note that this will also set `a+x` bits on the file if those permissions have not already been set, + - removing the queued upgrade from `upgrade-plan.json`, + - inserting new upgrade into the `upgrade-history.json`, + - updating the `current-version-info.json`, + - updating the `$NYMVISOR_UPGRADE_DATA_DIRECTORY/$DAEMON_NAME/current` symlink to the upgrade directory, + - removing the `upgrade.lock` file. + - the above loop is repeated if either: + - the daemon has crashed and `DAEMON_MAX_STARTUP_FAILURES` has not been reached yet, + - the daemon has successfully been upgraded, `DAEMON_RESTART_AFTER_UPGRADE` has been set to `true` and the manual flag on the performed upgrade has been set to `false`. + +### Add-Upgrade +`nymvisor add-upgrade` does the following: +- executes the `$DAEMON_NAME build-info` command on the daemon executable to check its validity and obtain its name +- attempts to load existing `upgrade-info.json` for the provided ``. If it already exists and neither `--force` nor `--add-binary` was specified, Nymvisor will terminate +- checks if `upgrades//$DAEMON_NAME` binary already exists. If it does and `--force` flag was not specified, Nymvisor will terminate the provided upgrade binary is copied to its appropriate location +- if applicable, new `upgrade-info.json` is created and written to its appropriate location +- `upgrade-plan.json` is updated with the new upgrade details. If there's an active Nymvisor instance running, this change will be detected to initialise upgrade process diff --git a/documentation/operators/src/nodes/setup-guides.md b/documentation/operators/src/nodes/setup-guides.md index ca86dd99f4..52fdbf2ee8 100644 --- a/documentation/operators/src/nodes/setup-guides.md +++ b/documentation/operators/src/nodes/setup-guides.md @@ -3,7 +3,7 @@ To setup any type of Nym's node, start with building [Nym's platform](../binaries/building-nym.md) on the machine (VPS) where you want to run the node. Nodes will need to be bond to Nym's wallet, setup one [here](https://nymtech.net/docs/wallet/desktop-wallet.html). This section contains setup guides for the following node types: -* [Mix node](./mix-node-setup.md) +* [Mix Node](./mix-node-setup.md) * [Gateway](./gateway-setup.md) * [Network Requester](./network-requester-setup.md) * [Validator](./validator-setup.md) diff --git a/documentation/operators/src/nodes/troubleshooting.md b/documentation/operators/src/nodes/troubleshooting.md index 7e2f37503d..fc201639c4 100644 --- a/documentation/operators/src/nodes/troubleshooting.md +++ b/documentation/operators/src/nodes/troubleshooting.md @@ -78,7 +78,7 @@ Additional details can be obtained via various methods after you connect to your ##### Socket statistics with `ss` ``` -sudo ss -s -t | grep 1789 # if you have specified a different port in your mix node config, change accordingly +sudo ss -s -t | grep 1789 # if you have specified a different port in your Mix Node config, change accordingly ``` This command should return a lot of data containing `ESTAB`. This command should work on every unix based system. @@ -90,7 +90,7 @@ This command should return a lot of data containing `ESTAB`. This command should lsof -v # install if not installed sudo apt install lsof -# run against mix node port +# run against nym-mix-node node port sudo lsof -i TCP:1789 # if you have specified a different port in your mixnode config, change accordingly ``` @@ -110,7 +110,7 @@ nym-mixno 103349 root 57u IPv6 1333229976 0t0 TCP [2a03:b0c0:3:d0::ff3: sudo journalctl -u nym-mixnode -o cat | grep "Since startup mixed" ``` -If you have created `nym-mixnode.service` file (i.e. you are running your mix node via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: +If you have created `nym-mixnode.service` file (i.e. you are running your Mix Node via `systemd`) then this command shows you how many packets have you mixed so far, and should return a list of messages like this: ``` 2021-05-18T12:35:24.057Z INFO nym_mixnode::node::metrics > Since startup mixed 233639 packets! @@ -140,7 +140,7 @@ For example `./target/debug/nym-network-requester --no-banner build-info --outpu nmap -p 1789 -Pn ``` -If your mix node is configured properly it should output something like this: +If your Mix Node is configured properly it should output something like this: ``` bob@desktop:~$ nmap -p 1789 95.296.134.220 -Pn @@ -159,12 +159,12 @@ curl --location --request GET 'https://validator.nymtech.net/api/v1/mixnodes/' Will return a list all nodes currently online. -You can query gateways by replacing `mixnodes` with `gateways` in the above command, and can query for the mixnodes and gateways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. +You can query Gateways by replacing `nym-mixnodes` with `nym-gateways` in the above command, and can query for the Mix Nodes and Gateways on the Sandbox testnet by replacing `validator` with `sandbox-validator`. #### Check with Network API -We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your mix node, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). +We currently have an API set up returning our metrics tests of the network. There are two endpoints to ping for information about your Mix Node, `report` and `history`. Find more information about this in the [Mixnodes metrics documentation](./maintenance.md#metrics--api-endpoints). ### Why is my node not mixing any packets? @@ -172,24 +172,24 @@ If you are still unable to see your node on the dashboard, or your node is decla - The firewall on your host machine is not configured properly. Checkout the [instructions](./maintenance.md#configure-your-firewall). - You provided incorrect information when bonding your node. -- You are running your mix node from a VPS without IPv6 support. -- You did not use the `--announce-host` flag while running the mix node from your local machine behind NAT. -- You did not configure your router firewall while running the mix node from your local machine behind NAT, or you are lacking IPv6 support. -- Your mix node is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). +- You are running your Mix Node from a VPS without IPv6 support. +- You did not use the `--announce-host` flag while running the Mix Node from your local machine behind NAT. +- You did not configure your router firewall while running the Mix Node from your local machine behind NAT, or you are lacking IPv6 support. +- Your Mix Node is not running at all, it has either exited / panicked or you closed the session without making the node persistent. Check out the [instructions](./maintenance.md#automating-your-node-with-tmux-and-systemd). ```admonish caution -Your mix node **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! +Your Mix Node **must speak both IPv4 and IPv6** in order to cooperate with other nodes and route traffic. This is a common reason behind many errors we are seeing among node operators, so check with your provider that your VPS is able to do this! ``` #### Incorrect bonding information -Check that you have provided the correct information when bonding your mix node in the web wallet interface. When in doubt, un-bond and then re-bond your node! +Check that you have provided the correct information when bonding your Mix Node in the web wallet interface. When in doubt, un-bond and then re-bond your node! -> All delegated stake will be lost when un-bonding! However the mix node must be operational in the first place for the delegation to have any effect. +> All delegated stake will be lost when un-bonding! However the Mix Node must be operational in the first place for the delegation to have any effect. #### Missing `announce-host` flag -On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your mix node host. +On certain cloud providers such as AWS and Google Cloud, you need to do some additional configuration of your firewall and use `--host` with your **local ip** and `--announce-host` with the **public ip** of your Mix Node host. If the difference between the two is unclear, contact the help desk of your VPS provider. @@ -222,15 +222,15 @@ bob@nym:~$ hostname -I ### Running on a local machine behind NAT with no fixed IP address -Your ISP has to be IPv6 ready if you want to run a mix node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is an extra paid service or they simply don't offer it. +Your ISP has to be IPv6 ready if you want to run a Mix Node on your local machine. Sadly, in 2020, most of them are not and you won't get an IPv6 address by default from your ISP. Usually it is an extra paid service or they simply don't offer it. Before you begin, check if you have IPv6 [here](https://test-ipv6.cz/) or by running command explained in the [section above](./troubleshooting.md#no-ipv6-connectivity). If not, then don't waste your time to run a node which won't ever be able to mix any packet due to this limitation. Call your ISP and ask for IPv6, there is a plenty of it for everyone! -If all goes well and you have IPv6 available, then you will need to `init` the mix node with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). +If all goes well and you have IPv6 available, then you will need to `init` the Mix Node with an extra flag, `--announce-host`. You will also need to edit your `config.toml` file each time your IPv4 address changes, that could be a few days or a few weeks. Check the your IPv4 in the [section above](./troubleshooting.md#no-ipv6-connectivity). Additional configuration on your router might also be needed to allow traffic in and out to port 1789 and IPv6 support. -Here is a sample of the `init` command example to create the mix node config. +Here is a sample of the `init` command example to create the Mix Node config. ``` ./nym-mixnode init --id --host 0.0.0.0 --announce-host 85.160.12.13 @@ -244,7 +244,7 @@ Make sure you check if your node is really mixing. We are aiming to improve the ### Accidentally killing your node process on exiting session -When you close your current terminal session, you need to make sure you don't kill the mix node process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). +When you close your current terminal session, you need to make sure you don't kill the Mix Node process! There are multiple ways on how to make it persistent even after exiting your ssh session, the easiest solution is to use `tmux` or `nohup`, and the more elegant solution is to run the node with `systemd`. Read the automation manual [here](./maintenance.md#automating-your-node-with-tmux-and-systemd). ### Common errors and warnings @@ -266,14 +266,14 @@ Then you need to `--announce-host ` and `--host ` on startu Yes! Here is what you will need to do: -Assuming you would like to use port `1337` for your mix node, you need to open the new port (and close the old one): +Assuming you would like to use port `1337` for your Mix Node, you need to open the new port (and close the old one): ``` sudo ufw allow 1337 sudo ufw deny 1789 ``` -And then edit the mix node's config. +And then edit the Mix Node's config. > If you want to change the port for an already running node, you need to stop the process before editing your config file. @@ -287,25 +287,25 @@ nano ~/.nym/mixnodes/alice-node/config/config.toml You will need to edit two parts of the file. `announce_address` and `listening_address` in the config.toml file. Simply replace `:1789` (the default port) with `:1337` (your new port) after your IP address. -Finally, restart your node. You should see if the mix node is using the port you have changed in the config.toml file right after you run the node. +Finally, restart your node. You should see if the Mix Node is using the port you have changed in the config.toml file right after you run the node. -### What is `verloc` and do I have to configure my mix node to implement it? +### What is `verloc` and do I have to configure my Mix Node to implement it? -`verloc` is short for _verifiable location_. Mixnodes and gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fake-able and trustworthy manner. +`verloc` is short for _verifiable location_. Mix Nodes and Gateways now measure speed-of-light distances to each other, in an attempt to verify how far apart they are. In later releases, this will allow us to algorithmically verify node locations in a non-fake-able and trustworthy manner. You don't have to do any additional configuration for your node to implement this, it is a passive process that runs in the background of the mixnet from version `0.10.1` onward. -## Gateways & Network requesters +## Gateways & Network Requesters -### My gateway seems to be running but appears offline +### My Gateway seems to be running but appears offline Check your [firewall](./maintenance.md#configure-your-firewall) is active and if the necessary ports are open / allowed. -### My exit gateway "is still not online..." +### My exit Gateway "is still not online..." -The Nyx chain epoch takes up to 60 min. To prevent the gateway getting blacklisted, it's important to run it right after the bonding process to return positive response our API testing it's routing score. +The Nyx chain epoch takes up to 60 min. To prevent the Gateway getting blacklisted, it's important to run it before and during the bonding process. In case it already got blacklisted run it for at several hours. During this time your node is tested by `nym-api` and every positive response picks up your Gateway's routing score. -You may want to disconnect the network requester and let it run as a gatewy alone for some time to regain better routing score and then areturn to the full [exit gateway finctionality](./gateway-setup.md#initialising-gateway-with-network-requester). +You may want to disconnect the Network Requester and let it run as a Gateway alone for some time to regain better routing score and then return to the full [Exit Gateway finctionality](./gateway-setup.md#initialising-gateway-with-network-requester). ## Validators diff --git a/documentation/operators/src/nodes/validator-setup.md b/documentation/operators/src/nodes/validator-setup.md index ca35c0b2b9..384d01f15c 100644 --- a/documentation/operators/src/nodes/validator-setup.md +++ b/documentation/operators/src/nodes/validator-setup.md @@ -1,12 +1,12 @@ # Validators -> Nym has two main codebases: +> Nym has two main codebases: > - the [Nym platform](https://github.com/nymtech/nym), written in Rust. This contains all of our code except for the validators. -> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go. +> - the [Nym validators](https://github.com/nymtech/nyxd), written in Go & maintained as a no-modification fork of [wasmd](https://github.com/CosmWasm/wasmd) -The validator is built using [Cosmos SDK](https://cosmos.network) and [Tendermint](https://tendermint.com), with a [CosmWasm](https://cosmwasm.com) smart contract controlling the directory service, node bonding, and delegated mixnet staking. +The validator is a Go application which implements it's functionalities using [Cosmos SDK](https://v1.cosmos.network/sdk). The underlying state-replication engine is powered by [CometBFT](https://cometbft.com/), where the consensus mechanism is based on the [Tendermint Consensus Algorithm](https://arxiv.org/abs/1807.04938). Finally, a [CosmWasm](https://cosmwasm.com) smart contract module controls crucial mixnet functionalities like decentralised directory service, node bonding, and delegated mixnet staking. -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. +> We are currently running mainnet with a closed set of reputable validators. To ensure decentralisation of Nyx chain, we are working on a mechanism to onboard new validators to the network. To join the waitlist, please drop an email to `validators [at] nymtech.net` with details of your setup, experience and any other relevent information ## Building your validator @@ -33,9 +33,9 @@ pacman -S git gcc jq `Go` can be installed via the following commands (taken from the [Go Download and install page](https://go.dev/doc/install)): ``` -# First remove any existing old Go installation and extract the archive you just downloaded into /usr/local: +# First remove any existing old Go installation and extract the archive you just downloaded into /usr/local: # You may need to run the command as root or through sudo -rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.6.linux-amd64.tar.gz +rm -rf /usr/local/go && tar -C /usr/local -xzf go1.20.10.linux-amd64.tar.gz # Add /usr/local/go/bin to the PATH environment variable export PATH=$PATH:/usr/local/go/bin @@ -47,16 +47,12 @@ Verify `Go` is installed with: ``` go version # Should return something like: -go version go1.20.4 linux/amd64 +go version go1.20.10 linux/amd64 ``` ### Download a precompiled validator binary You can find pre-compiled binaries for Ubuntu `22.04` and `20.04` [here](https://github.com/nymtech/nyxd/releases). -```admonish caution title="" -There are seperate releases for Mainnet and the Sandbox testnet - make sure to download the correct binary to avoid `bech32Prefix` mismatches. -``` - ### Manually compiling your validator binary The codebase for the Nyx validators can be found [here](https://github.com/nymtech/nyxd). @@ -64,15 +60,13 @@ The validator binary can be compiled by running the following commands: ``` git clone https://github.com/nymtech/nyxd.git cd nyxd + +# Make sure to check releases for the latest version information git checkout release/ -# Mainnet +# Build the binaries make build - -# Sandbox testnet -BECH32_PREFIX=nymt make build ``` - At this point, you will have a copy of the `nyxd` binary in your `build/` directory. Test that it's compiled properly by running: ``` @@ -83,50 +77,49 @@ You should see a similar help menu printed to you: ~~~admonish example collapsible=true title="Console output" ``` -Wasm Daemon (server) +Nyx Daemon (server) Usage: nyxd [command] Available Commands: - add-genesis-account Add a genesis account to genesis.json - add-wasm-genesis-message Wasm genesis subcommands - collect-gentxs Collect genesis txs and output a genesis.json file - config Create or query an application CLI configuration file - debug Tool for helping with debugging your application - export Export state to JSON - gentx Generate a genesis tx carrying a self delegation - help Help about any command - init Initialize private validator, p2p, genesis, and application configuration files - keys Manage your application's keys - query Querying subcommands - rollback rollback cosmos-sdk and tendermint state by one height - start Run the full node - status Query remote node for status - tendermint Tendermint subcommands - tx Transactions subcommands - validate-genesis validates the genesis file at the default location or at the location passed as an arg - version Print the application binary version information + completion Generate the autocompletion script for the specified shell + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + genesis Application's genesis-related subcommands + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + prune Prune app history states by keeping the recent heights and deleting old heights + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + rosetta spin up a rosetta server + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + testnet subcommands for starting or configuring local testnets + tx Transactions subcommands + version Print the application binary version information Flags: -h, --help help for nyxd - --home string directory for config and data (default "/home/willow/.nyxd") + --home string directory for config and data --log_format string The logging format (json|plain) (default "plain") --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") --trace print out full stack trace on errors Use "nyxd [command] --help" for more information about a command. - ``` ~~~ ### Linking `nyxd` to `libwasmvm.so` -`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1`. +`libwasmvm.so` is the wasm virtual machine which is needed to execute smart contracts in `v0.26.1`. This file is renamed in `libwasmvm.x86_64.so` in `v0.31.1` and above. If you downloaded your `nyxd` binary from Github, you will have seen this file when un-`tar`-ing the `.tar.gz` file from the releases page. -If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.so` file to correct location. +If you are seeing an error concerning this file when trying to run `nyxd`, then you need to move the `libwasmvm.x86_64.so` file to correct location. Simply `cp` or `mv` that file to `/lib/x86_64-linux-gnu/` and re-run `nyxd`. @@ -152,40 +145,39 @@ This should return the regular help menu: ~~~admonish example collapsible=true title="Console output" ``` -Wasm Daemon (server) +Nyx Daemon (server) Usage: nyxd [command] Available Commands: - add-genesis-account Add a genesis account to genesis.json - add-wasm-genesis-message Wasm genesis subcommands - collect-gentxs Collect genesis txs and output a genesis.json file - config Create or query an application CLI configuration file - debug Tool for helping with debugging your application - export Export state to JSON - gentx Generate a genesis tx carrying a self delegation - help Help about any command - init Initialize private validator, p2p, genesis, and application configuration files - keys Manage your application's keys - query Querying subcommands - rollback rollback cosmos-sdk and tendermint state by one height - start Run the full node - status Query remote node for status - tendermint Tendermint subcommands - tx Transactions subcommands - validate-genesis validates the genesis file at the default location or at the location passed as an arg - version Print the application binary version information + completion Generate the autocompletion script for the specified shell + config Create or query an application CLI configuration file + debug Tool for helping with debugging your application + export Export state to JSON + genesis Application's genesis-related subcommands + help Help about any command + init Initialize private validator, p2p, genesis, and application configuration files + keys Manage your application's keys + prune Prune app history states by keeping the recent heights and deleting old heights + query Querying subcommands + rollback rollback cosmos-sdk and tendermint state by one height + rosetta spin up a rosetta server + start Run the full node + status Query remote node for status + tendermint Tendermint subcommands + testnet subcommands for starting or configuring local testnets + tx Transactions subcommands + version Print the application binary version information Flags: -h, --help help for nyxd - --home string directory for config and data (default "/home/willow/.nyxd") + --home string directory for config and data --log_format string The logging format (json|plain) (default "plain") --log_level string The logging level (trace|debug|info|warn|error|fatal|panic) (default "info") --trace print out full stack trace on errors Use "nyxd [command] --help" for more information about a command. - ``` ~~~ @@ -224,7 +216,7 @@ You can use the following command to download them for the correct network: wget -O $HOME/.nyxd/config/genesis.json https://nymtech.net/genesis/genesis.json # Sandbox testnet -wget -O $HOME/.nyxd/config/genesis.json https://sandbox-validator1.nymtech.net/snapshots/genesis.json +curl https://rpc.sandbox.nymtech.net/snapshots/genesis.json | jq '.result.genesis' > $HOME/.nyxd/config/genesis.json ``` ### `config.toml` configuration @@ -234,21 +226,18 @@ Edit the following config options in `$HOME/.nyxd/config/config.toml` to match t ``` # Mainnet persistent_peers = "ee03a6777fb76a2efd0106c3769daaa064a3fcb5@51.79.21.187:26656" -create_empty_blocks = false laddr = "tcp://0.0.0.0:26656" ``` ``` # Sandbox testnet cors_allowed_origins = ["*"] -persistent_peers = "8421c0a3d90d490e27e8061f2abcb1276c8358b6@sandbox-validator1.nymtech.net:26666" -create_empty_blocks = false +persistent_peers = "26f7782aff699457c8e6dd9a845e5054c9b0707e@:3.72.19.120:26656" laddr = "tcp://0.0.0.0:26656" ``` -These affect the following: +These affect the following: * `persistent_peers = "@.nymtech.net:26666"` allows your validator to start pulling blocks from other validators. **The main sandbox validator listens on `26666` instead of the default `26656` for debugging**. It is recommended you do not change your port from `26656`. -* `create_empty_blocks = false` will save space * `laddr = "tcp://0.0.0.0:26656"` is in your p2p configuration options Optionally, if you want to enable [Prometheus](https://prometheus.io/) metrics then the following must also match in the `config.toml`: @@ -265,17 +254,16 @@ And if you wish to add a human-readable moniker to your node: Finally, if you plan on using [Cockpit](https://cockpit-project.org/documentation.html) on your server, change the `grpc` port from `9090` as this is the port used by Cockpit. ### `app.toml` configuration -In the file `$HOME/nyxd/config/app.toml`, set the following values: +In the file `$HOME/.nyxd/config/app.toml`, set the following values: ``` # Mainnet minimum-gas-prices = "0.025unym,0.025unyx" -enable = true in the `[api]` section to get the API server running ``` + ``` # Sandbox Testnet -minimum-gas-prices = "0.025unymt,0.025unyxt" -enable = true` in the `[api]` section to get the API server running +minimum-gas-prices = "0.025unym,0.025unyx" ``` ### Setting up your validator's admin user @@ -285,9 +273,13 @@ You'll need an admin account to be in charge of your validator. Set that up with nyxd keys add nyxd-admin ``` -This will add keys for your administrator account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. +```admonish tip title="Key Backends" +Cosmos SDK offers multiple backends for securing your keys. Please refer to the Cosmos SDK [docs on available keyring backends](https://docs.cosmos.network/main/user/run-node/keyring#available-backends-for-the-keyring) to learn more +``` -You can get the admin account's address with: +While using the default settings, this will add keys for your account to your system's keychain and log your name, address, public key, and mnemonic. As the instructions say, remember to **write down your mnemonic**. + +You can get the current account address with: ``` nyxd keys show nyxd-admin -a @@ -297,10 +289,6 @@ Type in your keychain **password**, not the mnemonic, when asked. ## Starting your validator -```admonish caution title="" -If you are running a Sandbox testnet validator, please skip the `validate-genesis` command: it will fail due to the size of the genesis file as this is a fork of an existing chain state. -``` - Everything should now be ready to go. You've got the validator set up, all changes made in `config.toml` and `app.toml`, the Nym genesis file copied into place (replacing the initial auto-generated one). Now let's validate the whole setup: ``` @@ -313,7 +301,32 @@ If this check passes, you should receive the following output: File at /path/to/genesis.json is a valid genesis file ``` -> If this test did not pass, check that you have replaced the contents of `//.nymd/config/genesis.json` with that of the correct genesis file. +> If this test did not pass, check that you have replaced the contents of `//.nyxd/config/genesis.json` with that of the correct genesis file. + +### Setting up nyxd as full node (non-signing) + +```admonish danger title="" +Skip this section if you're planning to run a validator node to join network consensus. To ensure security & maximum availability of validators, do not expose additional services to the Internet +``` + +Unlike signing validators, full nodes do not propose / sign blocks. A full node is typically used for indexing blocks produced on the chain and for exposing web interfaces such as RPC, API and gRPC endpoints required for external applications/services to interact with the blockchain. + +By default, API server is disabled and RPC/gRPC servers listen to the loopback address only. In a production setup, it is recommended to use a webserver such as Nginx or caddy to proxy requests to the endpoints as required. + +To enable Cosmos REST API, you can enable it in `$HOME/.nyxd/config/app.toml` like : + +``` +[api] + +# Enable defines if the API server should be enabled. Toggle this to `true` +enable = true + +# Swagger defines if swagger documentation should automatically be registered. +# You can also expose swagger documentation by toggling the below configuration to true +swagger = true +``` + +For more information on enabling access to various endpoints via Nginx, refer to the [example configuration here](./maintenance.md#setup) ### Open firewall ports @@ -323,14 +336,21 @@ Before starting the validator, we will need to open the firewall ports: # if ufw is not already installed: sudo apt install ufw sudo ufw enable -sudo ufw allow 1317,26656,26660,22,80,443/tcp + +# Customise according to your port bindings. This is only for reference +# 26656 : p2p gossip port +# 26660: If prometheus is enabled +# 22 : Default SSH port +sudo ufw allow 26656,26660,22 + +## !! FOR FULL NODES ONLY !! - exposing Nginx for serving web requests +sudo ufw allow 80,443 + # to check everything worked sudo ufw status ``` -Ports `22`, `80`, and `443` are for ssh, http, and https connections respectively. The rest of the ports are documented [here](https://docs.cosmos.network/main/core/grpc_rest). - -For more information about your validator's port configuration, check the [validator port reference table](./maintenance.md#ports) below. +For more information about your validator's port configuration, check the [validator port reference table](./maintenance.md#ports) below. These can be customised in `app.toml` and `config.toml` files. > If you are planning to use [Cockpit](https://cockpit-project.org/) on your validator server then you will have defined a different `grpc` port in your `config.toml` above: remember to open this port as well. @@ -353,7 +373,7 @@ sudo apt install snapd -y sudo snap install lz4 # download the snapshot -wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://sandbox-validator1.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 +wget -O nyxd-sandbox-snapshot-data.tar.lz4 https://rpc.sandbox.nymtech.net/snapshots/nyxd-sandbox-snapshot-data.tar.lz4 # reset your validator state nyxd tendermint unsafe-reset-all @@ -365,10 +385,8 @@ lz4 -c -d nyxd-sandbox-snapshot-data.tar.lz4 | tar -x -C $HOME/.nyxd You can then restart `nyxd` - it should start syncing from a block > 2000000. ### Joining Consensus -```admonish caution title="" -When joining consensus, make sure that you do not disrupt (or worse - halt) the network by coming in with a disproportionately large amount of staked tokens. - -Please initially stake a small amount of tokens compared to existing validators, then delegate to yourself in tranches over time. +```admonish info title="" +You can skip this section if you are planning to run a full-node. This step will make your node a signing validator which joins network consensus ``` Once your validator has synced and you have received tokens, you can join consensus and produce blocks. @@ -376,8 +394,7 @@ Once your validator has synced and you have received tokens, you can join consen ``` # Mainnet nyxd tx staking create-validator - --amount=10000000unyx - --fees=0unyx + --amount=<10000000unyx> --pubkey=$(/home///nyxd/binaries/nyxd tendermint show-validator) --moniker="" --chain-id=nyx @@ -387,14 +404,14 @@ nyxd tx staking create-validator --min-self-delegation="1" --gas="auto" --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://rpc-1.nyx.nodes.guru:443 + --gas-prices=0.025unyx + --from=<"KEYRING_NAME"> + --node=https://rpc.nymtech.net:443 ``` ``` # Sandbox Testnet nyxd tx staking create-validator - --amount=10000000unyxt - --fees=5000unyxt + --amount=<10000000unyx> --pubkey=$(/home///nym/binaries/nyxd tendermint show-validator) --moniker="" --chain-id=sandbox @@ -404,13 +421,13 @@ nyxd tx staking create-validator --min-self-delegation="1" --gas="auto" --gas-adjustment=1.15 - --from="KEYRING_NAME" - --node https://sandbox-validator1.nymtech.net:443 + --gas-prices=0.025unyx + --from=<"KEYRING_NAME"> + --node https://rpc.sandbox.nymtech.net:443 ``` -You'll need either `unyxt` tokens on Sandbox, or `unyx` tokens on mainnet to perform this command. +You'll need Nyx tokens on mainnet / sandbox to perform the above tasks. -> We are currently working towards building up a closed set of reputable validators. You can ask us for coins to get in, but please don't be offended if we say no - validators are part of our system's core security and we are starting out with people we already know or who have a solid reputation. If you want to edit some details for your node you will use a command like this: @@ -424,8 +441,8 @@ nyxd tx staking edit-validator --identity="" --gas="auto" --gas-adjustment=1.15 + --gas-prices=0.025unyx --from="KEYRING_NAME" - --fees 2000unyx ``` ``` # Sandbox testnet @@ -437,8 +454,8 @@ nyxd tx staking edit-validator --identity="" --gas="auto" --gas-adjustment=1.15 + --gas-prices=0.025unyx --from="KEYRING_NAME" - --fees 2000unyxt ``` With above command you can specify the `gpg` key last numbers (as used in `keybase`) as well as validator details and your email for security contact. @@ -446,10 +463,6 @@ With above command you can specify the `gpg` key last numbers (as used in `keyba ### Automating your validator with systemd You will most likely want to automate your validator restarting if your server reboots. Checkout the [maintenance page](./maintenance.md#systemd) with a quick tutorial. -### Installing and configuring nginx for HTTPS - -If you want to set up a reverse proxying on the validator server to improve security and performance, using [nginx](https://www.nginx.com/resources/glossary/nginx/#:~:text=NGINX%20is%20open%20source%20software,%2C%20media%20streaming%2C%20and%20more.&text=In%20addition%20to%20its%20HTTP,%2C%20TCP%2C%20and%20UDP%20servers.), follow the manual on the [maintenance page](./maintenance.md#setup). - ### Setting the ulimit Linux machines limit how many open files a user is allowed to have. This is called a `ulimit`. We need to set it to a higher value than the default 1024. Follow the instructions in the [maintenance page](./maintenance.md#Setting-the-ulimit) to change the `ulimit` value for validators. @@ -466,7 +479,7 @@ nyxd tx slashing unjail --chain-id=nyx --gas=auto --gas-adjustment=1.4 - --fees=7000unyx + --gas-prices=0.025unyx ``` ``` # Sandbox Testnet @@ -476,7 +489,7 @@ nyxd tx slashing unjail --chain-id=sandbox --gas=auto --gas-adjustment=1.4 - --fees=7000unyxt + --gas-prices=0.025unyx ``` ### Upgrading your validator @@ -485,18 +498,22 @@ To upgrade your validator, follow the steps on the [maintenance page](./maintena #### Common reasons for your validator being jailed -The most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. +Your validator will be jailed if your node: + - misses _`x`_ amount of blocks in _`y`_ interval, where _`x`_ and _`y`_ are parameters set by chain governance + - performs double signing (two conflicting signatures on the same block using the same key) -Running the command `df -H` will return the size of the various partitions of your VPS. +Double signing is a serious infraction. If a node double signs, all the delegators to the node (including self-delegation) will be slashed by 5%. Additionally, the node will be permanently jailed and removed from consensus (called _tombstoning_) -If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog archives and restart your validator process. +One of the most common reason for your validator being jailed is that your validator is out of memory because of bloated syslogs. + +Running the command `df -H` will return the size of the various partitions of your VPS. If the partition with blockchain data is almost full, try pruning the blockchain data or expanding the storage size. ### Day 2 operations with your validator You can check your current balances with: ``` -nymd query bank balances ${ADDRESS} +nyxd query bank balances ${ADDRESS} ``` For example, on the Sandbox testnet this would return: @@ -504,7 +521,7 @@ For example, on the Sandbox testnet this would return: ```yaml balances: - amount: "919376" -denom: unymt +denom: unym pagination: next_key: null total: "0" @@ -516,22 +533,21 @@ You can, of course, stake back the available balance to your validator with the ``` # Mainnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunym +nyxd tx staking delegate VALOPERADDRESS AMOUNTunyx --from="KEYRING_NAME" --keyring-backend=os --chain-id=nyx --gas="auto" --gas-adjustment=1.15 - --fees 5000unyx -``` -``` + --gas-prices=0.025unyx + # Sandbox Testnet -nyxd tx staking delegate VALOPERADDRESS AMOUNTunymt +nyxd tx staking delegate VALOPERADDRESS AMOUNTunyx --from="KEYRING_NAME" --keyring-backend=os --chain-id=sandbox --gas="auto" --gas-adjustment=1.15 - --fees 5000unyxt + --gas-prices=0.025unyx ``` diff --git a/documentation/remove_existing_config.sh b/documentation/remove_existing_config.sh new file mode 100755 index 0000000000..ae2b767512 --- /dev/null +++ b/documentation/remove_existing_config.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# Script to remove existing ~'/.nym/ files on docs deployment. Used to avoid issues with `mdbook-cmdrun` output when +# e.g. erroring about overwriting existing keys. `mdbook-cmdrun` output for the moment has to be checked manually. + +DIR=~/.nym + +# check for config directory +if [ ! -d $DIR ]; then + echo "config dir doesn't exist: nothing to do" +else + echo "config dir exists - deleting" + rm -rf $DIR + # check exit code of rm -rf - if !0 then exit + if [ $? -ne 0 ]; then + echo "exit code was $?. looks like the something went wrong with deleting the directory" + exit 1 + fi +fi \ No newline at end of file diff --git a/envs/sandbox.env b/envs/sandbox.env index 2ce9298766..856550cca0 100644 --- a/envs/sandbox.env +++ b/envs/sandbox.env @@ -23,5 +23,5 @@ SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzq STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" EXPLORER_API=https://sandbox-explorer.nymtech.net/api -NYXD="https://sandbox-validator1.nymtech.net" +NYXD="https://rpc.sandbox.nymtech.net" NYM_API="https://sandbox-nym-api1.nymtech.net/api" diff --git a/ephemera/Cargo.toml b/ephemera/Cargo.toml index f0af6af3a9..e32fec2746 100644 --- a/ephemera/Cargo.toml +++ b/ephemera/Cargo.toml @@ -47,7 +47,7 @@ serde_json = "1.0.91" thiserror = { workspace = true } tokio = { version = "1", features = ["macros", "net","rt-multi-thread"] } tokio-tungstenite = { workspace = true } -tokio-util = { version = "0.7.4", features = ["full"] } +tokio-util = { workspace = true, features = ["full"] } toml = "0.7.0" unsigned-varint = "0.7.1" utoipa = { workspace = true, features = ["actix_extras"] } diff --git a/ephemera/src/block/manager.rs b/ephemera/src/block/manager.rs index 7fd056f6cf..a0538479db 100644 --- a/ephemera/src/block/manager.rs +++ b/ephemera/src/block/manager.rs @@ -43,6 +43,7 @@ pub(crate) enum BlockManagerError { BlockManager(#[from] anyhow::Error), } +#[allow(clippy::struct_field_names)] // this should get resolved properly at some point, but not now... /// It helps to use atomic state management for new blocks. pub(crate) struct BlockChainState { pub(crate) last_blocks: LruCache, @@ -580,7 +581,9 @@ mod test { } #[tokio::test] - #[should_panic] + #[should_panic( + expected = "Received committed block which isn't last produced block, this is a bug!" + )] async fn test_on_committed_with_invalid_pending_block() { let (mut manager, _) = block_manager_with_defaults(); diff --git a/ephemera/src/config/mod.rs b/ephemera/src/config/mod.rs index bf30850928..703aed14a7 100644 --- a/ephemera/src/config/mod.rs +++ b/ephemera/src/config/mod.rs @@ -136,7 +136,7 @@ pub enum Error { NotFound(String), #[error("Configuration file does not exist")] /// This is returned if IoError happens during configuration file read/write. - IoError(#[from] std::io::Error), + Io(#[from] std::io::Error), /// This is returned if configuration file is invalid. #[error("Configuration file is invalid: '{0}'")] InvalidFormat(String), diff --git a/ephemera/src/network/mod.rs b/ephemera/src/network/mod.rs index 205e147903..ece4db4e67 100644 --- a/ephemera/src/network/mod.rs +++ b/ephemera/src/network/mod.rs @@ -102,6 +102,8 @@ pub(crate) struct Peer { /// assert_eq!(peer_id, public_key.peer_id()); /// /// ``` + #[allow(clippy::struct_field_names)] + // this should get resolved properly at some point, but not now... pub peer_id: PeerId, /// The peer's public key. It matches PeerId. pub public_key: PublicKey, diff --git a/explorer-api/.env.dev b/explorer-api/.env.dev index 0020aba233..7cb2d5a1db 100644 --- a/explorer-api/.env.dev +++ b/explorer-api/.env.dev @@ -22,7 +22,7 @@ NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y8 SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4 STATISTICS_SERVICE_DOMAIN_ADDRESS="http://0.0.0.0" -NYXD="https://sandbox-validator1.nymtech.net" +NYXD="https://rpc.sandbox.nymtech.net" NYM_API="https://sandbox-nym-api1.nymtech.net/api" GEOIP_DB_PATH=geo_ip/GeoLite2-City.mmdb diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index aadfc916d7..0ee85478ac 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "explorer-api" -version = "1.1.31" +version = "1.1.32" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] chrono = { version = "0.4.31", features = ["serde"] } clap = { workspace = true, features = ["cargo", "derive"] } -dotenvy = "0.15.6" +dotenvy = { workspace = true } humantime-serde = "1.0" isocountry = "0.3.2" itertools = "0.10.3" diff --git a/explorer-api/explorer-api-requests/Cargo.toml b/explorer-api/explorer-api-requests/Cargo.toml index 825af93a91..107edb28b8 100644 --- a/explorer-api/explorer-api-requests/Cargo.toml +++ b/explorer-api/explorer-api-requests/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-explorer-api-requests" version = "0.1.0" edition = "2021" +license.workspace = true [dependencies] nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" } diff --git a/explorer-api/explorer-client/Cargo.toml b/explorer-api/explorer-client/Cargo.toml index 769813b3b0..2397c83f7b 100644 --- a/explorer-api/explorer-client/Cargo.toml +++ b/explorer-api/explorer-client/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-explorer-client" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/explorer-api/src/country_statistics/http.rs b/explorer-api/src/country_statistics/http.rs index 45259f11dc..f51b8d242e 100644 --- a/explorer-api/src/country_statistics/http.rs +++ b/explorer-api/src/country_statistics/http.rs @@ -1,3 +1,6 @@ +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use crate::country_statistics::country_nodes_distribution::CountryNodesDistribution; use crate::state::ExplorerApiStateContext; use rocket::serde::json::Json; diff --git a/explorer-api/src/gateways/http.rs b/explorer-api/src/gateways/http.rs index ac0d85c0b1..f47ea51146 100644 --- a/explorer-api/src/gateways/http.rs +++ b/explorer-api/src/gateways/http.rs @@ -1,6 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use nym_explorer_api_requests::PrettyDetailedGatewayBond; use rocket::response::status::NotFound; use rocket::serde::json::Json; diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index e00ed8ff82..f1d2738db7 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -1,6 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use crate::mix_node::delegations::{ get_single_mixnode_delegations, get_single_mixnode_delegations_summed, }; diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs index a36843b66f..5043a559e6 100644 --- a/explorer-api/src/mix_nodes/http.rs +++ b/explorer-api/src/mix_nodes/http.rs @@ -1,3 +1,6 @@ +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use crate::mix_nodes::models::{MixNodeActiveSetSummary, MixNodeSummary}; use crate::state::ExplorerApiStateContext; use nym_explorer_api_requests::{MixnodeStatus, PrettyDetailedMixNodeBond}; diff --git a/explorer-api/src/overview/http.rs b/explorer-api/src/overview/http.rs index a5cfb127c5..1f130e68c6 100644 --- a/explorer-api/src/overview/http.rs +++ b/explorer-api/src/overview/http.rs @@ -1,3 +1,6 @@ +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index c3cd4107f1..a486c5c7a6 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -1,6 +1,9 @@ // Copyright 2021-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use nym_mixnet_contract_common::{MixId, MixNode}; use rocket::serde::json::Json; use rocket::{Route, State}; diff --git a/explorer-api/src/service_providers/http.rs b/explorer-api/src/service_providers/http.rs index a942a68755..5da66d9958 100644 --- a/explorer-api/src/service_providers/http.rs +++ b/explorer-api/src/service_providers/http.rs @@ -1,3 +1,6 @@ +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use crate::service_providers::models::{ DirectoryService, DirectorySpDetailed, HarbourMasterService, PagedResult, }; diff --git a/explorer-api/src/validators/http.rs b/explorer-api/src/validators/http.rs index 88a06eea01..ee42d2f14b 100644 --- a/explorer-api/src/validators/http.rs +++ b/explorer-api/src/validators/http.rs @@ -1,6 +1,9 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + use rocket::response::status::NotFound; use rocket::serde::json::Json; use rocket::{Route, State}; diff --git a/explorer/.env.dev b/explorer/.env.dev index fe4119f671..3cc86ae00c 100644 --- a/explorer/.env.dev +++ b/explorer/.env.dev @@ -3,7 +3,7 @@ EXPLORER_API_URL=https://sandbox-explorer.nymtech.net/api/v1 NYM_API_URL=https://sandbox-nym-api1.nymtech.net -VALIDATOR_URL=https://sandbox-validator1.nymtech.net +VALIDATOR_URL=https://rpc.sandbox.nymtech.net BIG_DIPPER_URL=https://sandbox-blocks.nymtech.net CURRENCY_DENOM=unym CURRENCY_STAKING_DENOM=unyx diff --git a/explorer/src/components/Footer.tsx b/explorer/src/components/Footer.tsx index c85ba47be4..2902cbd799 100644 --- a/explorer/src/components/Footer.tsx +++ b/explorer/src/components/Footer.tsx @@ -1,8 +1,11 @@ -import * as React from 'react'; +import React from 'react'; import Box from '@mui/material/Box'; -import { Typography } from '@mui/material'; +import MuiLink from '@mui/material/Link'; +import { Link } from 'react-router-dom'; +import Typography from '@mui/material/Typography'; import { Socials } from './Socials'; import { useIsMobile } from '../hooks/useIsMobile'; +import { NymVpnIcon } from '../icons/NymVpn'; export const Footer: FCWithChildren = () => { const isMobile = useIsMobile(); @@ -31,6 +34,9 @@ export const Footer: FCWithChildren = () => { mb: 2, }} > + + + )} diff --git a/explorer/src/components/Nav.tsx b/explorer/src/components/Nav.tsx index fbfe4b7ee5..c4d065aea6 100644 --- a/explorer/src/components/Nav.tsx +++ b/explorer/src/components/Nav.tsx @@ -22,6 +22,7 @@ import { useMainContext } from '../context/main'; import { MobileDrawerClose } from '../icons/MobileDrawerClose'; import { Socials } from './Socials'; import { Footer } from './Footer'; +import { NymVpnIcon } from '../icons/NymVpn'; import { DarkLightSwitchDesktop } from './Switch'; import { NavOptionType } from '../context/nav'; @@ -341,6 +342,9 @@ export const Nav: FCWithChildren = ({ children }) => { alignItems: 'center', }} > + + + diff --git a/explorer/src/icons/NymVpn.tsx b/explorer/src/icons/NymVpn.tsx new file mode 100644 index 0000000000..788b78a4f9 --- /dev/null +++ b/explorer/src/icons/NymVpn.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; + +interface DiscordIconProps { + size?: { width: number; height: number }; +} + +export const NymVpnIcon: FCWithChildren = ({ size }) => ( + + + + + + + + + + + +); + +NymVpnIcon.defaultProps = { + size: { width: 80, height: 12 }, +}; diff --git a/gateway/Cargo.toml b/gateway/Cargo.toml index cec0929f6d..a12ce97bb7 100644 --- a/gateway/Cargo.toml +++ b/gateway/Cargo.toml @@ -1,9 +1,10 @@ # Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: GPL-3.0-only [package] name = "nym-gateway" -version = "1.1.31" +license = "GPL-3.0" +version = "1.1.32" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", @@ -27,6 +28,7 @@ dirs = "4.0" dotenvy = { workspace = true } futures = { workspace = true } humantime-serde = "1.0.1" +ipnetwork = "0.16" lazy_static = "1.4.0" log = { workspace = true } once_cell = "1.7.2" @@ -34,7 +36,7 @@ pretty_env_logger = "0.4" rand = "0.7" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -sqlx = { version = "0.5", features = [ +sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", "macros", @@ -51,7 +53,7 @@ tokio = { workspace = true, features = [ ] } tokio-stream = { version = "0.1.11", features = ["fs"] } tokio-tungstenite = { version = "0.20.1" } -tokio-util = { version = "0.7.4", features = ["codec"] } +tokio-util = { workspace = true, features = ["codec"] } url = { workspace = true, features = ["serde"] } zeroize = { workspace = true } @@ -75,9 +77,11 @@ nym-statistics-common = { path = "../common/statistics" } nym-task = { path = "../common/task" } nym-types = { path = "../common/types" } nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-wireguard = { path = "../common/wireguard", optional = true } nym-ip-packet-router = { path = "../service-providers/ip-packet-router" } +nym-wireguard = { path = "../common/wireguard", optional = true } +defguard_wireguard_rs = { git = "https://github.com/neacsu/wireguard-rs.git", rev = "c2cd0c1119f699f4bc43f5e6ffd6fc242caa42ed", optional = true } + [dev-dependencies] tower = "0.4.13" rand = "0.8.5" @@ -85,7 +89,7 @@ hyper = "0.14.27" [build-dependencies] tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] } -sqlx = { version = "0.5", features = [ +sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", "macros", @@ -93,4 +97,4 @@ sqlx = { version = "0.5", features = [ ] } [features] -wireguard = ["nym-wireguard"] +wireguard = ["nym-wireguard", "defguard_wireguard_rs"] diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000000..bc4645252e --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,25 @@ + + +# Nym Gateway + +A Rust gateway implementation. + +## License + +Copyright (C) 2023 Nym Technologies SA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . \ No newline at end of file diff --git a/gateway/build.rs b/gateway/build.rs index 98ad043036..27d55fccd2 100644 --- a/gateway/build.rs +++ b/gateway/build.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use sqlx::{Connection, SqliteConnection}; use std::env; diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index fc46005eba..7916fa3038 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -6,6 +6,7 @@ name = "nym-gateway-requests" version = "0.1.0" authors = ["Jedrzej Stuczynski "] edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/gateway/src/commands/build_info.rs b/gateway/src/commands/build_info.rs index e3385bb933..3b763adf3f 100644 --- a/gateway/src/commands/build_info.rs +++ b/gateway/src/commands/build_info.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use clap::Args; use nym_bin_common::bin_info_owned; diff --git a/gateway/src/commands/helpers.rs b/gateway/src/commands/helpers.rs index 23ebd77afd..3b97212ae6 100644 --- a/gateway/src/commands/helpers.rs +++ b/gateway/src/commands/helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::upgrade_helpers; use crate::config::default_config_filepath; @@ -241,9 +241,17 @@ pub(crate) fn override_network_requester_config( } pub(crate) fn override_ip_packet_router_config( - cfg: nym_ip_packet_router::Config, - _opts: Option, + mut cfg: nym_ip_packet_router::Config, + opts: Option, ) -> nym_ip_packet_router::Config { + let Some(_opts) = opts else { return cfg }; + + // disable poisson rate in the BASE client if the IPR option is enabled + if cfg.ip_packet_router.disable_poisson_rate { + log::info!("Disabling poisson rate for ip packet router"); + cfg.set_no_poisson_process(); + } + cfg } diff --git a/gateway/src/commands/init.rs b/gateway/src/commands/init.rs index 8bcfca93b7..bfa12bd66d 100644 --- a/gateway/src/commands/init.rs +++ b/gateway/src/commands/init.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ initialise_local_ip_packet_router, initialise_local_network_requester, diff --git a/gateway/src/commands/mod.rs b/gateway/src/commands/mod.rs index 99c10869a5..34ecc95b47 100644 --- a/gateway/src/commands/mod.rs +++ b/gateway/src/commands/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::Cli; use clap::CommandFactory; diff --git a/gateway/src/commands/node_details.rs b/gateway/src/commands/node_details.rs index 2d37617338..02c0ca9875 100644 --- a/gateway/src/commands/node_details.rs +++ b/gateway/src/commands/node_details.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::try_load_current_config; use crate::node::helpers::node_details; diff --git a/gateway/src/commands/run.rs b/gateway/src/commands/run.rs index 8e57b7b998..76f7629de5 100644 --- a/gateway/src/commands/run.rs +++ b/gateway/src/commands/run.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ ensure_config_version_compatibility, OverrideConfig, OverrideNetworkRequesterConfig, diff --git a/gateway/src/commands/setup_ip_packet_router.rs b/gateway/src/commands/setup_ip_packet_router.rs index 633c2a4bab..a08f94dea3 100644 --- a/gateway/src/commands/setup_ip_packet_router.rs +++ b/gateway/src/commands/setup_ip_packet_router.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ initialise_local_ip_packet_router, try_load_current_config, OverrideIpPacketRouterConfig, diff --git a/gateway/src/commands/setup_network_requester.rs b/gateway/src/commands/setup_network_requester.rs index 6c45c05d19..c9de302200 100644 --- a/gateway/src/commands/setup_network_requester.rs +++ b/gateway/src/commands/setup_network_requester.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ initialise_local_network_requester, try_load_current_config, OverrideNetworkRequesterConfig, diff --git a/gateway/src/commands/sign.rs b/gateway/src/commands/sign.rs index ad8061e597..552315614c 100644 --- a/gateway/src/commands/sign.rs +++ b/gateway/src/commands/sign.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{ ensure_config_version_compatibility, ensure_correct_bech32_prefix, OverrideConfig, diff --git a/gateway/src/commands/upgrade_helpers.rs b/gateway/src/commands/upgrade_helpers.rs index e09e6f827d..f93350caaa 100644 --- a/gateway/src/commands/upgrade_helpers.rs +++ b/gateway/src/commands/upgrade_helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_20::ConfigV1_1_20; use crate::config::old_config_v1_1_28::ConfigV1_1_28; diff --git a/gateway/src/config/mod.rs b/gateway/src/config/mod.rs index 4737f38c58..b872a1dffd 100644 --- a/gateway/src/config/mod.rs +++ b/gateway/src/config/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::persistence::paths::GatewayPaths; use crate::config::template::CONFIG_TEMPLATE; diff --git a/gateway/src/config/old_config_v1_1_20.rs b/gateway/src/config/old_config_v1_1_20.rs index f7a83fc2c0..2ab7431a31 100644 --- a/gateway/src/config/old_config_v1_1_20.rs +++ b/gateway/src/config/old_config_v1_1_20.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_28::{ ConfigV1_1_28, DebugV1_1_28, GatewayPathsV1_1_28, GatewayV1_1_28, KeysPathsV1_1_28, diff --git a/gateway/src/config/old_config_v1_1_28.rs b/gateway/src/config/old_config_v1_1_28.rs index dff9cc896c..42311d36de 100644 --- a/gateway/src/config/old_config_v1_1_28.rs +++ b/gateway/src/config/old_config_v1_1_28.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_29::{ ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29, diff --git a/gateway/src/config/old_config_v1_1_29.rs b/gateway/src/config/old_config_v1_1_29.rs index bbd939b16a..48ca3d14fb 100644 --- a/gateway/src/config/old_config_v1_1_29.rs +++ b/gateway/src/config/old_config_v1_1_29.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_config::{ must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR, diff --git a/gateway/src/config/old_config_v1_1_31.rs b/gateway/src/config/old_config_v1_1_31.rs index 6391b18115..18841a4993 100644 --- a/gateway/src/config/old_config_v1_1_31.rs +++ b/gateway/src/config/old_config_v1_1_31.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::persistence::paths::GatewayPaths; use nym_bin_common::logging::LoggingSettings; @@ -128,6 +128,7 @@ impl From for Config { enabled: value.wireguard.enabled, bind_address: value.wireguard.bind_address, announced_port: value.wireguard.announced_port, + private_network_prefix: Default::default(), storage_paths: nym_node::config::persistence::WireguardPaths { // no fields (yet) }, diff --git a/gateway/src/config/persistence/mod.rs b/gateway/src/config/persistence/mod.rs index cbaa92be3d..d2b55c30f5 100644 --- a/gateway/src/config/persistence/mod.rs +++ b/gateway/src/config/persistence/mod.rs @@ -1,4 +1,4 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub mod paths; diff --git a/gateway/src/config/persistence/paths.rs b/gateway/src/config/persistence/paths.rs index 340b4a6495..b4b79241c6 100644 --- a/gateway/src/config/persistence/paths.rs +++ b/gateway/src/config/persistence/paths.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::{default_config_directory, default_data_directory}; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/gateway/src/config/template.rs b/gateway/src/config/template.rs index 040a92bddd..1b6ac71262 100644 --- a/gateway/src/config/template.rs +++ b/gateway/src/config/template.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only // While using normal toml marshalling would have been way simpler with less overhead, // I think it's useful to have comments attached to the saved config file to explain behaviour of diff --git a/gateway/src/error.rs b/gateway/src/error.rs index 470e5421c4..61f75981ef 100644 --- a/gateway/src/error.rs +++ b/gateway/src/error.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::error::StorageError; use nym_ip_packet_router::error::IpPacketRouterError; @@ -134,6 +134,12 @@ pub(crate) enum GatewayError { // TODO: in the future this should work the other way, i.e. NymNode depending on Gateway errors #[error(transparent)] NymNodeError(#[from] nym_node::error::NymNodeError), + + #[error("there was an issue with wireguard IP network: {source}")] + IpNetworkError { + #[from] + source: ipnetwork::IpNetworkError, + }, } impl From for GatewayError { diff --git a/gateway/src/http/mod.rs b/gateway/src/http/mod.rs index d27b7873c6..7b63a522fb 100644 --- a/gateway/src/http/mod.rs +++ b/gateway/src/http/mod.rs @@ -1,9 +1,10 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::GatewayError; use crate::node::helpers::load_public_key; +use ipnetwork::IpNetwork; use log::warn; use nym_bin_common::bin_info_owned; use nym_crypto::asymmetric::{encryption, identity}; @@ -16,6 +17,7 @@ use nym_node::http::router::WireguardAppState; use nym_node::wireguard::types::GatewayClientRegistry; use nym_sphinx::addressing::clients::Recipient; use nym_task::TaskClient; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; fn load_gateway_details( @@ -101,10 +103,50 @@ fn load_network_requester_details( ) } +fn load_ip_packet_router_details( + config: &Config, + ip_packet_router_config: &nym_ip_packet_router::Config, +) -> Result { + let identity_public_key: identity::PublicKey = load_public_key( + &ip_packet_router_config + .storage_paths + .common_paths + .keys + .public_identity_key_file, + "ip packet router identity", + )?; + + let dh_public_key: encryption::PublicKey = load_public_key( + &ip_packet_router_config + .storage_paths + .common_paths + .keys + .public_encryption_key_file, + "ip packet router diffie hellman", + )?; + + let gateway_identity_public_key: identity::PublicKey = load_public_key( + &config.storage_paths.keys.public_identity_key_file, + "gateway identity", + )?; + + Ok(api_requests::v1::ip_packet_router::models::IpPacketRouter { + encoded_identity_key: identity_public_key.to_base58_string(), + encoded_x25519_key: dh_public_key.to_base58_string(), + address: Recipient::new( + identity_public_key, + dh_public_key, + gateway_identity_public_key, + ) + .to_string(), + }) +} + pub(crate) struct HttpApiBuilder<'a> { gateway_config: &'a Config, network_requester_config: Option<&'a nym_network_requester::Config>, exit_policy: Option, + ip_packet_router_config: Option<&'a nym_ip_packet_router::Config>, identity_keypair: &'a identity::KeyPair, // TODO: this should be a wg specific key and not re-used sphinx @@ -122,6 +164,7 @@ impl<'a> HttpApiBuilder<'a> { HttpApiBuilder { gateway_config, network_requester_config: None, + ip_packet_router_config: None, exit_policy: None, identity_keypair, sphinx_keypair, @@ -185,6 +228,15 @@ impl<'a> HttpApiBuilder<'a> { self } + #[must_use] + pub(crate) fn with_maybe_ip_packet_router( + mut self, + ip_packet_router_config: Option<&'a nym_ip_packet_router::Config>, + ) -> Self { + self.ip_packet_router_config = ip_packet_router_config; + self + } + #[must_use] pub(crate) fn with_wireguard_client_registry( mut self, @@ -223,13 +275,25 @@ impl<'a> HttpApiBuilder<'a> { } } - let wg_state = self.client_registry.map(|client_registry| { + if let Some(ipr_config) = self.ip_packet_router_config { + config = config.with_ip_packet_router(load_ip_packet_router_details( + self.gateway_config, + ipr_config, + )?); + } + + let wireguard_private_network = IpNetwork::new( + IpAddr::from(Ipv4Addr::new(10, 1, 0, 0)), + self.gateway_config.wireguard.private_network_prefix, + )?; + let wg_state = self.client_registry.and_then(|client_registry| { WireguardAppState::new( - self.sphinx_keypair, client_registry, Default::default(), self.gateway_config.wireguard.bind_address.port(), + wireguard_private_network, ) + .ok() }); let router = nym_node::http::NymNodeRouter::new(config, wg_state); diff --git a/gateway/src/main.rs b/gateway/src/main.rs index 16ef053776..0ec4b7865a 100644 --- a/gateway/src/main.rs +++ b/gateway/src/main.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] diff --git a/gateway/src/node/client_handling/active_clients.rs b/gateway/src/node/client_handling/active_clients.rs index a44de399ba..ef9b995a88 100644 --- a/gateway/src/node/client_handling/active_clients.rs +++ b/gateway/src/node/client_handling/active_clients.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::websocket::message_receiver::{IsActiveRequestSender, MixMessageSender}; use crate::node::client_handling::embedded_network_requester::LocalNetworkRequesterHandle; diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 99f1cf095b..e1b4d831e0 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_coconut_interface::Credential; diff --git a/gateway/src/node/client_handling/embedded_network_requester/mod.rs b/gateway/src/node/client_handling/embedded_network_requester/mod.rs index 8ed7e2f978..8070145cf0 100644 --- a/gateway/src/node/client_handling/embedded_network_requester/mod.rs +++ b/gateway/src/node/client_handling/embedded_network_requester/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::websocket::message_receiver::{ MixMessageReceiver, MixMessageSender, diff --git a/gateway/src/node/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs index bc63605d58..ff4691e256 100644 --- a/gateway/src/node/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod active_clients; mod bandwidth; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs index ff779cad36..103c1ad102 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/authenticated.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use futures::{ future::{FusedFuture, OptionFuture}, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs index 32806da385..d4929d378e 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/coconut.rs @@ -1,10 +1,11 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::authenticated::RequestHandlingError; use log::*; use nym_coconut_interface::Credential; use nym_validator_client::coconut::all_coconut_api_clients; +use nym_validator_client::nyxd::AccountId; use nym_validator_client::{ nyxd::{ contract_traits::{ @@ -12,26 +13,27 @@ use nym_validator_client::{ MultisigSigningClient, }, cosmwasm_client::logs::{find_attribute, BANDWIDTH_PROPOSAL_ID}, - Coin, Fee, + Coin, }, CoconutApiClient, DirectSigningHttpRpcNyxdClient, }; -use std::time::{Duration, SystemTime}; - -const ONE_HOUR_SEC: u64 = 3600; -const MAX_FEEGRANT_UNYM: u128 = 10000; +use std::ops::Deref; +use tokio::sync::RwLock; pub(crate) struct CoconutVerifier { - nyxd_client: DirectSigningHttpRpcNyxdClient, + address: AccountId, + nyxd_client: RwLock, mix_denom_base: String, } impl CoconutVerifier { - pub fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self { + pub async fn new(nyxd_client: DirectSigningHttpRpcNyxdClient) -> Self { let mix_denom_base = nyxd_client.current_chain_details().mix_denom.base.clone(); + let address = nyxd_client.address(); CoconutVerifier { - nyxd_client, + address, + nyxd_client: RwLock::new(nyxd_client), mix_denom_base, } } @@ -39,7 +41,13 @@ impl CoconutVerifier { pub async fn all_current_coconut_api_clients( &self, ) -> Result, RequestHandlingError> { - let epoch_id = self.nyxd_client.get_current_epoch().await?.epoch_id; + let epoch_id = self + .nyxd_client + .read() + .await + .get_current_epoch() + .await? + .epoch_id; self.all_coconut_api_clients(epoch_id).await } @@ -47,7 +55,7 @@ impl CoconutVerifier { &self, epoch_id: u64, ) -> Result, RequestHandlingError> { - Ok(all_coconut_api_clients(&self.nyxd_client, epoch_id).await?) + Ok(all_coconut_api_clients(self.nyxd_client.read().await.deref(), epoch_id).await?) } pub async fn release_funds( @@ -55,19 +63,17 @@ impl CoconutVerifier { api_clients: Vec, credential: &Credential, ) -> Result<(), RequestHandlingError> { - // Use a custom multiplier for revoke, as the default one (1.3) - // isn't enough - let revoke_fee = Some(Fee::Auto(Some(1.5))); - let res = self .nyxd_client + .write() + .await .spend_credential( Coin::new( credential.voucher_value().into(), self.mix_denom_base.clone(), ), credential.blinded_serial_number(), - self.nyxd_client.address().to_string(), + self.address.to_string(), None, ) .await?; @@ -81,7 +87,12 @@ impl CoconutVerifier { reason: String::from("proposal id could not be parsed to u64"), })?; - let proposal = self.nyxd_client.query_proposal(proposal_id).await?; + let proposal = self + .nyxd_client + .read() + .await + .query_proposal(proposal_id) + .await?; if !credential.has_blinded_serial_number(&proposal.description)? { return Err(RequestHandlingError::ProposalIdError { reason: String::from("proposal has different serial number"), @@ -91,28 +102,10 @@ impl CoconutVerifier { let req = nym_api_requests::coconut::VerifyCredentialBody::new( credential.clone(), proposal_id, - self.nyxd_client.address(), + self.address.clone(), ); for client in api_clients { - self.nyxd_client - .grant_allowance( - &client.cosmos_address, - vec![Coin::new(MAX_FEEGRANT_UNYM, self.mix_denom_base.clone())], - SystemTime::now().checked_add(Duration::from_secs(ONE_HOUR_SEC)), - // It would be nice to be able to filter deeper, but for now only the msg type filter is avaialable - vec![String::from("/cosmwasm.wasm.v1.MsgExecuteContract")], - "Create allowance to vote the release of funds".to_string(), - None, - ) - .await?; let ret = client.api_client.verify_bandwidth_credential(&req).await; - self.nyxd_client - .revoke_allowance( - &client.cosmos_address, - "Cleanup the previous allowance for releasing funds".to_string(), - revoke_fee.clone(), - ) - .await?; match ret { Ok(res) => { if !res.verification_result { @@ -125,7 +118,11 @@ impl CoconutVerifier { } } - self.nyxd_client.execute_proposal(proposal_id, None).await?; + self.nyxd_client + .write() + .await + .execute_proposal(proposal_id, None) + .await?; Ok(()) } diff --git a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs index d6a8d284d3..aa20f312d0 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/eth_events.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::websocket::connection_handler::authenticated::RequestHandlingError; use nym_bandwidth_claim_contract::msg::ExecuteMsg; @@ -7,9 +7,9 @@ use nym_bandwidth_claim_contract::payment::LinkPaymentData; use nym_credentials::token::bandwidth::TokenCredential; use nym_crypto::asymmetric::identity::{PublicKey, Signature, SIGNATURE_LENGTH}; use nym_network_defaults::{ETH_EVENT_NAME, ETH_MIN_BLOCK_DEPTH}; -use std::str::FromStr; use nym_validator_client::nxmd::traits::MixnetQueryClient; use nym_validator_client::nyxd::{AccountId, NyxdClient, SigningNyxdClient}; +use std::str::FromStr; use web3::contract::tokens::Detokenize; use web3::contract::{Contract, Error}; use web3::transports::Http; diff --git a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs index f4389fb433..2d08c85709 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/fresh.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use futures::{ channel::{mpsc, oneshot}, diff --git a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs index dc5fc1f89c..1bc27a6e2f 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler/mod.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::Storage; use log::{trace, warn}; diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 01463e12c6..27a03aacfc 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier; diff --git a/gateway/src/node/client_handling/websocket/message_receiver.rs b/gateway/src/node/client_handling/websocket/message_receiver.rs index fb41083803..d1f8ffff38 100644 --- a/gateway/src/node/client_handling/websocket/message_receiver.rs +++ b/gateway/src/node/client_handling/websocket/message_receiver.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use futures::channel::{mpsc, oneshot}; diff --git a/gateway/src/node/client_handling/websocket/mod.rs b/gateway/src/node/client_handling/websocket/mod.rs index df45a32a69..673f14b8a8 100644 --- a/gateway/src/node/client_handling/websocket/mod.rs +++ b/gateway/src/node/client_handling/websocket/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) use listener::Listener; diff --git a/gateway/src/node/helpers.rs b/gateway/src/node/helpers.rs index d3cf2fdae4..b6765cc7bb 100644 --- a/gateway/src/node/helpers.rs +++ b/gateway/src/node/helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::GatewayError; @@ -8,7 +8,9 @@ use nym_crypto::asymmetric::{encryption, identity}; use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair}; use nym_pemstore::KeyPairPath; use nym_sphinx::addressing::clients::Recipient; -use nym_types::gateway::{GatewayNetworkRequesterDetails, GatewayNodeDetailsResponse}; +use nym_types::gateway::{ + GatewayIpPacketRouterDetails, GatewayNetworkRequesterDetails, GatewayNodeDetailsResponse, +}; use std::path::Path; fn display_maybe_path>(path: Option

) -> String { @@ -71,6 +73,40 @@ pub(crate) fn node_details(config: &Config) -> Result Result -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod receiver; diff --git a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs index 00fd98033b..d07dc6489e 100644 --- a/gateway/src/node/mixnet_handling/receiver/connection_handler.rs +++ b/gateway/src/node/mixnet_handling/receiver/connection_handler.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::client_handling::active_clients::ActiveClientsStore; use crate::node::client_handling::websocket::message_receiver::MixMessageSender; diff --git a/gateway/src/node/mixnet_handling/receiver/listener.rs b/gateway/src/node/mixnet_handling/receiver/listener.rs index 6714906ed7..9ca22c77af 100644 --- a/gateway/src/node/mixnet_handling/receiver/listener.rs +++ b/gateway/src/node/mixnet_handling/receiver/listener.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::mixnet_handling::receiver::connection_handler::ConnectionHandler; use crate::node::storage::Storage; diff --git a/gateway/src/node/mixnet_handling/receiver/mod.rs b/gateway/src/node/mixnet_handling/receiver/mod.rs index b733ce4e21..2304ed90f0 100644 --- a/gateway/src/node/mixnet_handling/receiver/mod.rs +++ b/gateway/src/node/mixnet_handling/receiver/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod connection_handler; pub(crate) mod listener; diff --git a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs index e7416c415c..7ac90bb174 100644 --- a/gateway/src/node/mixnet_handling/receiver/packet_processing.rs +++ b/gateway/src/node/mixnet_handling/receiver/packet_processing.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_crypto::asymmetric::encryption; use nym_mixnode_common::packet_processor::error::MixProcessingError; diff --git a/gateway/src/node/mod.rs b/gateway/src/node/mod.rs index d3764c635a..44da516aea 100644 --- a/gateway/src/node/mod.rs +++ b/gateway/src/node/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use self::helpers::load_ip_packet_router_config; use self::storage::PersistentStorage; @@ -22,6 +22,8 @@ use crate::node::statistics::collector::GatewayStatisticsCollector; use crate::node::storage::Storage; use anyhow::bail; use dashmap::DashMap; +#[cfg(feature = "wireguard")] +use defguard_wireguard_rs::{WGApi, WireguardInterfaceApi}; use futures::channel::{mpsc, oneshot}; use log::*; use nym_crypto::asymmetric::{encryption, identity}; @@ -204,8 +206,7 @@ impl Gateway { async fn start_wireguard( &self, shutdown: TaskClient, - ) -> Result<(), Box> { - // TODO: possibly we should start the UDP listener and TUN device explicitly here + ) -> Result> { nym_wireguard::start_wireguard(shutdown, Arc::clone(&self.client_registry)).await } @@ -346,19 +347,19 @@ impl Gateway { // TODO: well, wire it up internally to gateway traffic, shutdowns, etc. let (on_start_tx, on_start_rx) = oneshot::channel(); - let mut ip_builder = - nym_ip_packet_router::IpPacketRouterBuilder::new(ip_opts.config.clone()) + let mut ip_packet_router = + nym_ip_packet_router::IpPacketRouter::new(ip_opts.config.clone()) .with_shutdown(shutdown) .with_custom_gateway_transceiver(Box::new(transceiver)) .with_wait_for_gateway(true) .with_on_start(on_start_tx); if let Some(custom_mixnet) = &ip_opts.custom_mixnet_path { - ip_builder = ip_builder.with_stored_topology(custom_mixnet)? + ip_packet_router = ip_packet_router.with_stored_topology(custom_mixnet)? } tokio::spawn(async move { - if let Err(err) = ip_builder.run_service_provider().await { + if let Err(err) = ip_packet_router.run_service_provider().await { // no need to panic as we have passed a task client to the ip packet router so // we're most likely already in the process of shutting down error!("ip packet router has failed: {err}") @@ -450,7 +451,7 @@ impl Gateway { let coconut_verifier = { let nyxd_client = self.random_nyxd_client()?; - CoconutVerifier::new(nyxd_client) + CoconutVerifier::new(nyxd_client).await }; let mix_forwarding_channel = @@ -476,6 +477,13 @@ impl Gateway { }); } + self.start_client_websocket_listener( + mix_forwarding_channel.clone(), + active_clients_store.clone(), + shutdown.subscribe().named("websocket::Listener"), + Arc::new(coconut_verifier), + ); + let nr_request_filter = if self.config.network_requester.enabled { let embedded_nr = self .start_network_requester( @@ -496,7 +504,7 @@ impl Gateway { if self.config.ip_packet_router.enabled { let embedded_ip_sp = self .start_ip_packet_router( - mix_forwarding_channel.clone(), + mix_forwarding_channel, shutdown.subscribe().named("ip_service_provider"), ) .await?; @@ -511,25 +519,16 @@ impl Gateway { .with_wireguard_client_registry(self.client_registry.clone()) .with_maybe_network_requester(self.network_requester_opts.as_ref().map(|o| &o.config)) .with_maybe_network_request_filter(nr_request_filter) + .with_maybe_ip_packet_router(self.ip_packet_router_opts.as_ref().map(|o| &o.config)) .start(shutdown.subscribe().named("http-api"))?; - self.start_client_websocket_listener( - mix_forwarding_channel, - active_clients_store, - shutdown.subscribe().named("websocket::Listener"), - Arc::new(coconut_verifier), - ); - // Once this is a bit more mature, make this a commandline flag instead of a compile time // flag #[cfg(feature = "wireguard")] - if let Err(err) = self + let wg_api = self .start_wireguard(shutdown.subscribe().named("wireguard")) .await - { - // that's a nasty workaround, but anyhow errors are generally nicer, especially on exit - bail!("{err}") - } + .ok(); info!("Finished nym gateway startup procedure - it should now be able to receive mix and client traffic!"); @@ -537,6 +536,10 @@ impl Gateway { // that's a nasty workaround, but anyhow errors are generally nicer, especially on exit bail!("{err}") } + #[cfg(feature = "wireguard")] + if let Some(wg_api) = wg_api { + wg_api.remove_interface()?; + } Ok(()) } } diff --git a/gateway/src/node/statistics/collector.rs b/gateway/src/node/statistics/collector.rs index 4f1035d979..d82847509f 100644 --- a/gateway/src/node/statistics/collector.rs +++ b/gateway/src/node/statistics/collector.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use async_trait::async_trait; use sqlx::types::chrono::{DateTime, Utc}; diff --git a/gateway/src/node/statistics/mod.rs b/gateway/src/node/statistics/mod.rs index 3248ece682..eea54a1a85 100644 --- a/gateway/src/node/statistics/mod.rs +++ b/gateway/src/node/statistics/mod.rs @@ -1,4 +1,4 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub mod collector; diff --git a/gateway/src/node/storage/bandwidth.rs b/gateway/src/node/storage/bandwidth.rs index e2367b2ee1..536f9b69f0 100644 --- a/gateway/src/node/storage/bandwidth.rs +++ b/gateway/src/node/storage/bandwidth.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::models::PersistedBandwidth; diff --git a/gateway/src/node/storage/error.rs b/gateway/src/node/storage/error.rs index 306dafa557..b2feb1becf 100644 --- a/gateway/src/node/storage/error.rs +++ b/gateway/src/node/storage/error.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use thiserror::Error; diff --git a/gateway/src/node/storage/inboxes.rs b/gateway/src/node/storage/inboxes.rs index f67fac94b4..c94459fb88 100644 --- a/gateway/src/node/storage/inboxes.rs +++ b/gateway/src/node/storage/inboxes.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::models::StoredMessage; @@ -75,7 +75,11 @@ impl InboxManager { sqlx::query_as!( StoredMessage, r#" - SELECT * FROM message_store + SELECT + id as "id!", + client_address_bs58 as "client_address_bs58!", + content as "content!" + FROM message_store WHERE client_address_bs58 = ? AND id > ? ORDER BY id ASC LIMIT ?; @@ -90,7 +94,11 @@ impl InboxManager { sqlx::query_as!( StoredMessage, r#" - SELECT * FROM message_store + SELECT + id as "id!", + client_address_bs58 as "client_address_bs58!", + content as "content!" + FROM message_store WHERE client_address_bs58 = ? ORDER BY id ASC LIMIT ?; diff --git a/gateway/src/node/storage/mod.rs b/gateway/src/node/storage/mod.rs index e4ae31ca19..77f87437ea 100644 --- a/gateway/src/node/storage/mod.rs +++ b/gateway/src/node/storage/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::bandwidth::BandwidthManager; use crate::node::storage::error::StorageError; diff --git a/gateway/src/node/storage/models.rs b/gateway/src/node/storage/models.rs index 58ea7e3306..c98fb4c26b 100644 --- a/gateway/src/node/storage/models.rs +++ b/gateway/src/node/storage/models.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) struct PersistedSharedKeys { pub(crate) client_address_bs58: String, diff --git a/gateway/src/node/storage/shared_keys.rs b/gateway/src/node/storage/shared_keys.rs index d40823fe96..d04ab12492 100644 --- a/gateway/src/node/storage/shared_keys.rs +++ b/gateway/src/node/storage/shared_keys.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::storage::models::PersistedSharedKeys; diff --git a/gateway/src/support/config.rs b/gateway/src/support/config.rs index ff6cc91789..deae3240de 100644 --- a/gateway/src/support/config.rs +++ b/gateway/src/support/config.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::helpers::{try_load_current_config, OverrideConfig}; use crate::config::Config; diff --git a/gateway/src/support/mod.rs b/gateway/src/support/mod.rs index 6c7248da0b..42dab6772b 100644 --- a/gateway/src/support/mod.rs +++ b/gateway/src/support/mod.rs @@ -1,4 +1,4 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod config; diff --git a/mixnode/Cargo.toml b/mixnode/Cargo.toml index 872ca55b92..25a6a8d5a7 100644 --- a/mixnode/Cargo.toml +++ b/mixnode/Cargo.toml @@ -1,9 +1,10 @@ # Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: GPL-3.0-only [package] name = "nym-mixnode" -version = "1.1.33" +license = "GPL-3.0" +version = "1.1.34" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", @@ -32,8 +33,8 @@ rand = "0.7.3" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } sysinfo = "0.27.7" -tokio = { version = "1.21.2", features = ["rt-multi-thread", "net", "signal"] } -tokio-util = { version = "0.7.3", features = ["codec"] } +tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } +tokio-util = { workspace = true, features = ["codec"] } toml = "0.5.8" url = { workspace = true, features = ["serde"] } cfg-if = "1.0.0" @@ -81,3 +82,8 @@ cpucycles = [ "opentelemetry", "nym-bin-common/tracing", ] + +[package.metadata.deb] +name = "nym-mixnode" +maintainer-scripts = "debian" +systemd-units = { enable = false } diff --git a/mixnode/README.md b/mixnode/README.md index 3fb65bc916..1e910946d6 100644 --- a/mixnode/README.md +++ b/mixnode/README.md @@ -1,12 +1,29 @@ # Nym Mixnode A Rust mixnode implementation. +## License + +Copyright (C) 2020 Nym Technologies SA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + ## Usage * `nym-mixnode` prints a help message showing usage options @@ -14,3 +31,45 @@ A Rust mixnode implementation. * `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 and bind to the specified host IP address. Coordinate with other people in your network to find out which layer needs coverage. By default, the Nym Mixnode will start on port 1789. If desired, you can change the port using the `--port` option. + +## Install debian + +```bash +sudo curl -s --compressed "https://nymtech.github.io/nym/nymtech.gpg" | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/nymtech.gpg > /dev/null +sudo echo "deb [signed-by=/etc/apt/trusted.gpg.d/nymtech.gpg] https://nymtech.github.io/nym/ /" > nymtech.list + +sudo apt-get update +sudo apt-get install nym-mixnode + +# See below for starting and managing the node +``` + +## Systemd support + +```bash +sudo systemctl enable nym-mixnode + +# Run +sudo systemctl start nym-mixnode + +# Check status +sudo systemctl status nym-mixnode + +# Logs +journalctl -f -u nym-mixnode + +``` + +## Build debian package + +```bash +# cargo install cargo-deb + +# Build package +cargo deb -p nym-mixnode + +# Install + +# This will init the mixnode to `/etc/nym` as `nym` user, and create a systemd service +sudo dpkg -i target/debian/ +``` \ No newline at end of file diff --git a/mixnode/debian/postinst b/mixnode/debian/postinst new file mode 100644 index 0000000000..cda1db59b0 --- /dev/null +++ b/mixnode/debian/postinst @@ -0,0 +1,6 @@ +#DEBHELPER# + +useradd nym +mkdir -p /etc/nym +chown -R nym /etc/nym +su nym -c 'NYM_HOME_DIR=/etc/nym nym-mixnode init --host 0.0.0.0 --id nym-mixnode' diff --git a/mixnode/debian/service b/mixnode/debian/service new file mode 100644 index 0000000000..ad533b32c7 --- /dev/null +++ b/mixnode/debian/service @@ -0,0 +1,11 @@ +[Unit] +Description=Nym Mixnode +After=network-online.target + +[Service] +ExecStart=/usr/bin/nym-mixnode run --id nym-mixnode +User=nym +Environment="NYM_HOME_DIR=/etc/nym" + +[Install] +WantedBy=multi-user.target diff --git a/mixnode/src/commands/build_info.rs b/mixnode/src/commands/build_info.rs index e3385bb933..3b763adf3f 100644 --- a/mixnode/src/commands/build_info.rs +++ b/mixnode/src/commands/build_info.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use clap::Args; use nym_bin_common::bin_info_owned; diff --git a/mixnode/src/commands/describe.rs b/mixnode/src/commands/describe.rs index 164ef9d740..5576296bcd 100644 --- a/mixnode/src/commands/describe.rs +++ b/mixnode/src/commands/describe.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::try_load_current_config; use crate::node::node_description::NodeDescription; diff --git a/mixnode/src/commands/init.rs b/mixnode/src/commands/init.rs index 31a45dfe96..3e678d3d4a 100644 --- a/mixnode/src/commands/init.rs +++ b/mixnode/src/commands/init.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::OverrideConfig; use crate::commands::override_config; diff --git a/mixnode/src/commands/mod.rs b/mixnode/src/commands/mod.rs index 068089f9d9..dfa1fcbd42 100644 --- a/mixnode/src/commands/mod.rs +++ b/mixnode/src/commands/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::default_config_filepath; use crate::error::MixnodeError; diff --git a/mixnode/src/commands/node_details.rs b/mixnode/src/commands/node_details.rs index c4b17c645f..24dc71cb2f 100644 --- a/mixnode/src/commands/node_details.rs +++ b/mixnode/src/commands/node_details.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::try_load_current_config; use crate::node::MixNode; diff --git a/mixnode/src/commands/run.rs b/mixnode/src/commands/run.rs index 0ca0550679..c4ab5125fb 100644 --- a/mixnode/src/commands/run.rs +++ b/mixnode/src/commands/run.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::OverrideConfig; use crate::commands::{override_config, try_load_current_config, version_check}; diff --git a/mixnode/src/commands/sign.rs b/mixnode/src/commands/sign.rs index 7864ef5504..2604a72d1c 100644 --- a/mixnode/src/commands/sign.rs +++ b/mixnode/src/commands/sign.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::commands::{try_load_current_config, validate_bech32_address_or_exit}; use crate::node::helpers::load_identity_keys; diff --git a/mixnode/src/commands/upgrade_helpers.rs b/mixnode/src/commands/upgrade_helpers.rs index cf64c27158..66b4a6ef4d 100644 --- a/mixnode/src/commands/upgrade_helpers.rs +++ b/mixnode/src/commands/upgrade_helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_21::ConfigV1_1_21; use crate::config::old_config_v1_1_32::ConfigV1_1_32; diff --git a/mixnode/src/config/mod.rs b/mixnode/src/config/mod.rs index 97ad26d699..a1ab25a752 100644 --- a/mixnode/src/config/mod.rs +++ b/mixnode/src/config/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::persistence::paths::MixNodePaths; use crate::config::template::CONFIG_TEMPLATE; @@ -164,6 +164,7 @@ impl Config { save_formatted_config_to_file(self, config_save_location) } + #[allow(unused)] pub fn try_save(&self) -> io::Result<()> { if let Some(save_location) = &self.save_path { save_formatted_config_to_file(self, save_location) diff --git a/mixnode/src/config/old_config_v1_1_21.rs b/mixnode/src/config/old_config_v1_1_21.rs index fc4ae148a0..b8f5252893 100644 --- a/mixnode/src/config/old_config_v1_1_21.rs +++ b/mixnode/src/config/old_config_v1_1_21.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_32::{ ConfigV1_1_32, DebugV1_1_32, MixNodeV1_1_32, VerlocV1_1_32, diff --git a/mixnode/src/config/old_config_v1_1_32.rs b/mixnode/src/config/old_config_v1_1_32.rs index 92947a3234..a17cdeadcd 100644 --- a/mixnode/src/config/old_config_v1_1_32.rs +++ b/mixnode/src/config/old_config_v1_1_32.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::persistence::paths::MixNodePaths; use crate::config::{Config, Debug, MixNode, Verloc}; diff --git a/mixnode/src/config/persistence/mod.rs b/mixnode/src/config/persistence/mod.rs index cbaa92be3d..d2b55c30f5 100644 --- a/mixnode/src/config/persistence/mod.rs +++ b/mixnode/src/config/persistence/mod.rs @@ -1,4 +1,4 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub mod paths; diff --git a/mixnode/src/config/persistence/paths.rs b/mixnode/src/config/persistence/paths.rs index 95193e4212..ca5a571b35 100644 --- a/mixnode/src/config/persistence/paths.rs +++ b/mixnode/src/config/persistence/paths.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::{default_config_directory, default_data_directory}; use serde::{Deserialize, Serialize}; diff --git a/mixnode/src/config/template.rs b/mixnode/src/config/template.rs index b7f9433ae5..79a4dd72a8 100644 --- a/mixnode/src/config/template.rs +++ b/mixnode/src/config/template.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only // While using normal toml marshalling would have been way simpler with less overhead, // I think it's useful to have comments attached to the saved config file to explain behaviour of diff --git a/mixnode/src/error.rs b/mixnode/src/error.rs index a5f80b10d4..57c4594747 100644 --- a/mixnode/src/error.rs +++ b/mixnode/src/error.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use std::io; use std::path::PathBuf; diff --git a/mixnode/src/main.rs b/mixnode/src/main.rs index 34ca2d613f..d6383477cc 100644 --- a/mixnode/src/main.rs +++ b/mixnode/src/main.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use ::nym_config::defaults::setup_env; use clap::{crate_name, crate_version, Parser}; diff --git a/mixnode/src/node/helpers.rs b/mixnode/src/node/helpers.rs index 955f4f98fe..54515c053b 100644 --- a/mixnode/src/node/helpers.rs +++ b/mixnode/src/node/helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::MixnodeError; diff --git a/mixnode/src/node/http/legacy/description.rs b/mixnode/src/node/http/legacy/description.rs index 7c6ec2b580..29a648c0f2 100644 --- a/mixnode/src/node/http/legacy/description.rs +++ b/mixnode/src/node/http/legacy/description.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::node::node_description::NodeDescription; use axum::extract::Query; use nym_node::http::api::{FormattedResponse, OutputParams}; diff --git a/mixnode/src/node/http/legacy/hardware.rs b/mixnode/src/node/http/legacy/hardware.rs index c4e24b4911..496d034b0b 100644 --- a/mixnode/src/node/http/legacy/hardware.rs +++ b/mixnode/src/node/http/legacy/hardware.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use axum::extract::Query; use cupid::TopologyType; use nym_node::http::api::{FormattedResponse, OutputParams}; diff --git a/mixnode/src/node/http/legacy/mod.rs b/mixnode/src/node/http/legacy/mod.rs index 49230ebbec..94ed29fc6c 100644 --- a/mixnode/src/node/http/legacy/mod.rs +++ b/mixnode/src/node/http/legacy/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::http::legacy::description::description; use crate::node::http::legacy::hardware::hardware; diff --git a/mixnode/src/node/http/legacy/state.rs b/mixnode/src/node/http/legacy/state.rs index 2b3016185c..80cdaa01c0 100644 --- a/mixnode/src/node/http/legacy/state.rs +++ b/mixnode/src/node/http/legacy/state.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::http::legacy::verloc::VerlocState; use crate::node::node_statistics::SharedNodeStats; diff --git a/mixnode/src/node/http/legacy/stats.rs b/mixnode/src/node/http/legacy/stats.rs index 093932288a..b48358799b 100644 --- a/mixnode/src/node/http/legacy/stats.rs +++ b/mixnode/src/node/http/legacy/stats.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::node::node_statistics::{NodeStats, NodeStatsSimple, SharedNodeStats}; use axum::extract::{Query, State}; use nym_node::http::api::{FormattedResponse, Output}; diff --git a/mixnode/src/node/http/legacy/verloc.rs b/mixnode/src/node/http/legacy/verloc.rs index 18a263e713..eb5800bccc 100644 --- a/mixnode/src/node/http/legacy/verloc.rs +++ b/mixnode/src/node/http/legacy/verloc.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use axum::extract::{Query, State}; use nym_mixnode_common::verloc::{AtomicVerlocResult, VerlocResult}; use nym_node::http::api::{FormattedResponse, OutputParams}; diff --git a/mixnode/src/node/http/mod.rs b/mixnode/src/node/http/mod.rs index a3a0d49ce2..5147664c70 100644 --- a/mixnode/src/node/http/mod.rs +++ b/mixnode/src/node/http/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::config::Config; use crate::error::MixnodeError; use crate::node::http::legacy::verloc::VerlocState; diff --git a/mixnode/src/node/listener/connection_handler/mod.rs b/mixnode/src/node/listener/connection_handler/mod.rs index 8d0895cbea..c16a700bfd 100644 --- a/mixnode/src/node/listener/connection_handler/mod.rs +++ b/mixnode/src/node/listener/connection_handler/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::listener::connection_handler::packet_processing::{ MixProcessingResult, PacketProcessor, diff --git a/mixnode/src/node/listener/connection_handler/packet_processing.rs b/mixnode/src/node/listener/connection_handler/packet_processing.rs index 1d52602b1d..f849632f0f 100644 --- a/mixnode/src/node/listener/connection_handler/packet_processing.rs +++ b/mixnode/src/node/listener/connection_handler/packet_processing.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::node_statistics; use nym_crypto::asymmetric::encryption; diff --git a/mixnode/src/node/listener/mod.rs b/mixnode/src/node/listener/mod.rs index fc553b41ed..995a6e7c73 100644 --- a/mixnode/src/node/listener/mod.rs +++ b/mixnode/src/node/listener/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::listener::connection_handler::ConnectionHandler; use log::{error, info, warn}; diff --git a/mixnode/src/node/mod.rs b/mixnode/src/node/mod.rs index f1b330e69a..5dca2113dc 100644 --- a/mixnode/src/node/mod.rs +++ b/mixnode/src/node/mod.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::Config; use crate::error::MixnodeError; diff --git a/mixnode/src/node/node_description.rs b/mixnode/src/node/node_description.rs index 0c31a3e004..6c41b28f24 100644 --- a/mixnode/src/node/node_description.rs +++ b/mixnode/src/node/node_description.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use serde::Deserialize; use serde::Serialize; use std::path::Path; diff --git a/mixnode/src/node/node_statistics.rs b/mixnode/src/node/node_statistics.rs index 78895b85ce..be367dea96 100644 --- a/mixnode/src/node/node_statistics.rs +++ b/mixnode/src/node/node_statistics.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use super::TaskClient; use futures::channel::mpsc; use futures::lock::Mutex; diff --git a/mixnode/src/node/packet_delayforwarder.rs b/mixnode/src/node/packet_delayforwarder.rs index 0e7abfa793..2aa85d714c 100644 --- a/mixnode/src/node/packet_delayforwarder.rs +++ b/mixnode/src/node/packet_delayforwarder.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node::node_statistics::UpdateSender; use futures::channel::mpsc; diff --git a/nym-api/Cargo.toml b/nym-api/Cargo.toml index 47ea4d056d..3b19c6215b 100644 --- a/nym-api/Cargo.toml +++ b/nym-api/Cargo.toml @@ -1,9 +1,10 @@ # Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: GPL-3.0-only [package] name = "nym-api" -version = "1.1.32" +license = "GPL-3.0" +version = "1.1.34" authors = [ "Dave Hrycyszyn ", "Jędrzej Stuczyński ", @@ -23,6 +24,7 @@ clap = { workspace = true, features = ["cargo", "derive"] } console-subscriber = { version = "0.1.1", optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable" dirs = "4.0" futures = { workspace = true } +itertools = "0.12.0" humantime-serde = "1.0" lazy_static = "1.4.0" log = { workspace = true } @@ -36,7 +38,7 @@ serde = { workspace = true } serde_json = { workspace = true } tap = "1.0" thiserror = { workspace = true } -time = { version = "0.3.14", features = ["serde-human-readable", "parsing"] } +time = { workspace = true, features = ["serde-human-readable", "parsing"] } tokio = { version = "1.24.1", features = [ "rt-multi-thread", "macros", @@ -51,7 +53,7 @@ ts-rs = { workspace = true, optional = true} anyhow = { workspace = true } getset = "0.1.1" -sqlx = { version = "0.6.2", features = [ +sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", "macros", @@ -96,14 +98,14 @@ nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-co nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" } nym-service-provider-directory-common = { path = "../common/cosmwasm-smart-contracts/service-provider-directory" } nym-name-service-common = { path = "../common/cosmwasm-smart-contracts/name-service" } -nym-coconut = { path = "../common/nymcoconut" } +nym-coconut = { path = "../common/nymcoconut", features = ["key-zeroize"] } nym-sphinx = { path = "../common/nymsphinx" } nym-pemstore = { path = "../common/pemstore" } nym-task = { path = "../common/task" } nym-topology = { path = "../common/topology" } nym-api-requests = { path = "nym-api-requests" } nym-validator-client = { path = "../common/client-libs/validator-client" } -nym-bin-common = { path = "../common/bin-common" } +nym-bin-common = { path = "../common/bin-common", features = ["output_format"] } nym-node-tester-utils = { path = "../common/node-tester-utils" } nym-node-requests = { path = "../nym-node/nym-node-requests" } @@ -113,7 +115,7 @@ generate-ts = ["ts-rs"] [build-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } -sqlx = { version = "0.6.2", features = [ +sqlx = { workspace = true, features = [ "runtime-tokio-rustls", "sqlite", "macros", diff --git a/nym-api/README.md b/nym-api/README.md index 7d3d6c3b49..98c0be85ac 100644 --- a/nym-api/README.md +++ b/nym-api/README.md @@ -3,6 +3,23 @@ Nym API The Nym API provides multiple services to the Nym network, and is designed to be run alongside Nyxd validators. From a logical perspective, there are multiple applications, but they are bundled together for ease of deployment. +License +------- + +Copyright (C) 2023 Nym Technologies SA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . Nym Directory Caching ---------------------- diff --git a/nym-api/migrations/20231127120000_partial_credentials_info.sql b/nym-api/migrations/20231127120000_partial_credentials_info.sql new file mode 100644 index 0000000000..1c01a3f30d --- /dev/null +++ b/nym-api/migrations/20231127120000_partial_credentials_info.sql @@ -0,0 +1,28 @@ +/* + * Copyright 2023 - Nym Technologies SA + * SPDX-License-Identifier: GPL-3.0-only + */ + + +DROP TABLE signed_deposit; + +-- represents information about credentials issued in that epoch so that the other parties could make their appropriate queries +-- note: before this information can be returned to a client (of the API), it needs to be signed first +CREATE TABLE epoch_credentials +( + epoch_id INTEGER NOT NULL PRIMARY KEY UNIQUE, + start_id INTEGER NOT NULL, + total_issued INTEGER NOT NULL +); + +-- particular credential issued in this epoch +CREATE TABLE issued_credential +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + epoch_id INTEGER NOT NULL, + tx_hash VARCHAR NOT NULL UNIQUE, + bs58_partial_credential VARCHAR NOT NULL, + bs58_signature VARCHAR NOT NULL, + joined_private_commitments VARCHAR NOT NULL, + joined_public_attributes VARCHAR NOT NULL +); \ No newline at end of file diff --git a/nym-api/nym-api-requests/Cargo.toml b/nym-api/nym-api-requests/Cargo.toml index d9869bd7ae..1befc394cf 100644 --- a/nym-api/nym-api-requests/Cargo.toml +++ b/nym-api/nym-api-requests/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-api-requests" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -13,8 +14,11 @@ getset = "0.1.1" schemars = { workspace = true, features = ["preserve_order"] } serde = { workspace = true, features = ["derive"] } ts-rs = { workspace = true, optional = true } +tendermint = { workspace = true } nym-coconut-interface = { path = "../../common/coconut-interface" } +nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"]} + nym-mixnet-contract-common = { path= "../../common/cosmwasm-smart-contracts/mixnet-contract" } nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false } diff --git a/nym-api/nym-api-requests/src/coconut.rs b/nym-api/nym-api-requests/src/coconut.rs deleted file mode 100644 index 6f79bc2f21..0000000000 --- a/nym-api/nym-api-requests/src/coconut.rs +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use cosmrs::AccountId; -use getset::{CopyGetters, Getters}; -use serde::{Deserialize, Serialize}; - -use nym_coconut_interface::{ - error::CoconutInterfaceError, Attribute, Base58, BlindSignRequest, Credential, VerificationKey, -}; - -#[derive(Serialize, Deserialize, Getters, CopyGetters)] -pub struct VerifyCredentialBody { - #[getset(get = "pub")] - credential: Credential, - #[getset(get = "pub")] - proposal_id: u64, - #[getset(get = "pub")] - gateway_cosmos_addr: AccountId, -} - -impl VerifyCredentialBody { - pub fn new( - credential: Credential, - proposal_id: u64, - gateway_cosmos_addr: AccountId, - ) -> VerifyCredentialBody { - VerifyCredentialBody { - credential, - proposal_id, - gateway_cosmos_addr, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct VerifyCredentialResponse { - pub verification_result: bool, -} - -impl VerifyCredentialResponse { - pub fn new(verification_result: bool) -> Self { - VerifyCredentialResponse { - verification_result, - } - } -} - -// All strings are base58 encoded representations of structs -#[derive(Clone, Serialize, Deserialize, Debug, Getters, CopyGetters)] -pub struct BlindSignRequestBody { - #[getset(get = "pub")] - blind_sign_request: BlindSignRequest, - #[getset(get = "pub")] - tx_hash: String, - #[getset(get = "pub")] - signature: String, - public_attributes: Vec, - #[getset(get = "pub")] - public_attributes_plain: Vec, - #[getset(get = "pub")] - total_params: u32, -} - -impl BlindSignRequestBody { - pub fn new( - blind_sign_request: &BlindSignRequest, - tx_hash: String, - signature: String, - public_attributes: &[Attribute], - public_attributes_plain: Vec, - total_params: u32, - ) -> BlindSignRequestBody { - BlindSignRequestBody { - blind_sign_request: blind_sign_request.clone(), - tx_hash, - signature, - public_attributes: public_attributes - .iter() - .map(|attr| attr.to_bs58()) - .collect(), - public_attributes_plain, - total_params, - } - } - - pub fn public_attributes(&self) -> Vec { - self.public_attributes - .iter() - .map(|x| Attribute::try_from_bs58(x).unwrap()) - .collect() - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct BlindedSignatureResponse { - pub remote_key: [u8; 32], - pub encrypted_signature: Vec, -} - -impl BlindedSignatureResponse { - pub fn new(encrypted_signature: Vec, remote_key: [u8; 32]) -> BlindedSignatureResponse { - BlindedSignatureResponse { - encrypted_signature, - remote_key, - } - } - - pub fn to_base58_string(&self) -> String { - bs58::encode(&self.to_bytes()).into_string() - } - - pub fn from_base58_string>(val: I) -> Result { - let bytes = bs58::decode(val).into_vec()?; - Self::from_bytes(&bytes) - } - - pub fn to_bytes(&self) -> Vec { - let mut bytes = self.remote_key.to_vec(); - bytes.extend_from_slice(&self.encrypted_signature); - bytes - } - - pub fn from_bytes(bytes: &[u8]) -> Result { - if bytes.len() < 32 { - return Err(CoconutInterfaceError::InvalidByteLength(bytes.len(), 32)); - } - let mut remote_key = [0u8; 32]; - remote_key.copy_from_slice(&bytes[..32]); - let encrypted_signature = bytes[32..].to_vec(); - Ok(BlindedSignatureResponse { - remote_key, - encrypted_signature, - }) - } -} - -#[derive(Serialize, Deserialize)] -pub struct VerificationKeyResponse { - pub key: VerificationKey, -} - -impl VerificationKeyResponse { - pub fn new(key: VerificationKey) -> VerificationKeyResponse { - VerificationKeyResponse { key } - } -} - -#[derive(Serialize, Deserialize)] -pub struct CosmosAddressResponse { - pub addr: AccountId, -} - -impl CosmosAddressResponse { - pub fn new(addr: AccountId) -> CosmosAddressResponse { - CosmosAddressResponse { addr } - } -} diff --git a/nym-api/nym-api-requests/src/coconut/helpers.rs b/nym-api/nym-api-requests/src/coconut/helpers.rs new file mode 100644 index 0000000000..58aec3eca0 --- /dev/null +++ b/nym-api/nym-api-requests/src/coconut/helpers.rs @@ -0,0 +1,33 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use nym_coconut_interface::BlindedSignature; +use tendermint::hash::Hash; + +// recomputes plaintext on the credential nym-api has used for signing +// +// note: this method doesn't have to be reversible so just naively concatenate everything +pub fn issued_credential_plaintext( + epoch_id: u32, + tx_hash: Hash, + blinded_partial_credential: &BlindedSignature, + bs58_encoded_private_attributes_commitments: &[String], + public_attributes: &[String], +) -> Vec { + epoch_id + .to_be_bytes() + .into_iter() + .chain(tx_hash.as_bytes().iter().copied()) + .chain(blinded_partial_credential.to_bytes()) + .chain( + bs58_encoded_private_attributes_commitments + .iter() + .flat_map(|attr| attr.as_bytes().iter().copied()), + ) + .chain( + public_attributes + .iter() + .flat_map(|attr| attr.as_bytes().iter().copied()), + ) + .collect() +} diff --git a/nym-api/nym-api-requests/src/coconut/mod.rs b/nym-api/nym-api-requests/src/coconut/mod.rs new file mode 100644 index 0000000000..fb5740928c --- /dev/null +++ b/nym-api/nym-api-requests/src/coconut/mod.rs @@ -0,0 +1,10 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod helpers; +pub mod models; + +pub use models::{ + BlindSignRequestBody, BlindedSignatureResponse, CredentialsRequestBody, + VerificationKeyResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; diff --git a/nym-api/nym-api-requests/src/coconut/models.rs b/nym-api/nym-api-requests/src/coconut/models.rs new file mode 100644 index 0000000000..79db872288 --- /dev/null +++ b/nym-api/nym-api-requests/src/coconut/models.rs @@ -0,0 +1,233 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::coconut::helpers::issued_credential_plaintext; +use cosmrs::AccountId; +use nym_coconut_interface::{ + error::CoconutInterfaceError, hash_to_scalar, Attribute, BlindSignRequest, BlindedSignature, + Bytable, Credential, VerificationKey, +}; +use nym_crypto::asymmetric::identity; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; +use tendermint::hash::Hash; + +#[derive(Serialize, Deserialize)] +pub struct VerifyCredentialBody { + pub credential: Credential, + + pub proposal_id: u64, + + pub gateway_cosmos_addr: AccountId, +} + +impl VerifyCredentialBody { + pub fn new( + credential: Credential, + proposal_id: u64, + gateway_cosmos_addr: AccountId, + ) -> VerifyCredentialBody { + VerifyCredentialBody { + credential, + proposal_id, + gateway_cosmos_addr, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct VerifyCredentialResponse { + pub verification_result: bool, +} + +impl VerifyCredentialResponse { + pub fn new(verification_result: bool) -> Self { + VerifyCredentialResponse { + verification_result, + } + } +} + +// All strings are base58 encoded representations of structs +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct BlindSignRequestBody { + pub inner_sign_request: BlindSignRequest, + + /// Hash of the deposit transaction + pub tx_hash: Hash, + + /// Signature on the inner sign request and the tx hash + pub signature: identity::Signature, + + // public_attributes: Vec, + pub public_attributes_plain: Vec, +} + +impl BlindSignRequestBody { + pub fn new( + inner_sign_request: BlindSignRequest, + tx_hash: Hash, + signature: identity::Signature, + public_attributes_plain: Vec, + ) -> BlindSignRequestBody { + BlindSignRequestBody { + inner_sign_request, + tx_hash, + signature, + public_attributes_plain, + } + } + + pub fn attributes(&self) -> u32 { + (self.public_attributes_plain.len() + self.inner_sign_request.num_private_attributes()) + as u32 + } + + pub fn public_attributes_hashed(&self) -> Vec { + self.public_attributes_plain + .iter() + .map(hash_to_scalar) + .collect() + } + + pub fn encode_commitments(&self) -> Vec { + use nym_coconut_interface::Base58; + + self.inner_sign_request + .get_private_attributes_pedersen_commitments() + .iter() + .map(|c| c.to_bs58()) + .collect() + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BlindedSignatureResponse { + pub blinded_signature: BlindedSignature, +} + +impl BlindedSignatureResponse { + pub fn new(blinded_signature: BlindedSignature) -> BlindedSignatureResponse { + BlindedSignatureResponse { blinded_signature } + } + + pub fn to_base58_string(&self) -> String { + bs58::encode(&self.to_bytes()).into_string() + } + + pub fn from_base58_string>(val: I) -> Result { + let bytes = bs58::decode(val).into_vec()?; + Self::from_bytes(&bytes) + } + + pub fn to_bytes(&self) -> Vec { + self.blinded_signature.to_byte_vec() + } + + pub fn from_bytes(bytes: &[u8]) -> Result { + Ok(BlindedSignatureResponse { + blinded_signature: BlindedSignature::from_bytes(bytes)?, + }) + } +} + +#[derive(Serialize, Deserialize)] +pub struct VerificationKeyResponse { + pub key: VerificationKey, +} + +impl VerificationKeyResponse { + pub fn new(key: VerificationKey) -> VerificationKeyResponse { + VerificationKeyResponse { key } + } +} + +#[derive(Serialize, Deserialize)] +pub struct CosmosAddressResponse { + pub addr: AccountId, +} + +impl CosmosAddressResponse { + pub fn new(addr: AccountId) -> CosmosAddressResponse { + CosmosAddressResponse { addr } + } +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +pub struct Pagination { + /// last_key is the last value returned in the previous query. + /// it's used to indicate the start of the next (this) page. + /// the value itself is not included in the response. + pub last_key: Option, + + /// limit is the total number of results to be returned in the result page. + /// If left empty it will default to a value to be set by each app. + pub limit: Option, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CredentialsRequestBody { + /// Explicit ids of the credentials to retrieve. Note: it can't be set alongside pagination. + pub credential_ids: Vec, + + /// Pagination settings for retrieving credentials. Note: it can't be set alongside explicit ids. + pub pagination: Option>, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EpochCredentialsResponse { + pub epoch_id: u64, + pub first_epoch_credential_id: Option, + pub total_issued: u32, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct IssuedCredentialsResponse { + // note: BTreeMap returns ordered results so it's fine to use it with pagination + pub credentials: BTreeMap, +} + +#[derive(Clone, Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct IssuedCredentialResponse { + pub credential: Option, +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IssuedCredentialInner { + pub credential: IssuedCredential, + + pub signature: identity::Signature, +} + +#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct IssuedCredential { + pub id: i64, + pub epoch_id: u32, + pub tx_hash: Hash, + + // NOTE: if we find creation of this guy takes too long, + // change `BlindedSignature` to `BlindedSignatureBytes` + // so that nym-api wouldn't need to parse the value out of its storage + pub blinded_partial_credential: BlindedSignature, + pub bs58_encoded_private_attributes_commitments: Vec, + pub public_attributes: Vec, +} + +impl IssuedCredential { + // this method doesn't have to be reversible so just naively concatenate everything + pub fn signable_plaintext(&self) -> Vec { + issued_credential_plaintext( + self.epoch_id, + self.tx_hash, + &self.blinded_partial_credential, + &self.bs58_encoded_private_attributes_commitments, + &self.public_attributes, + ) + } +} diff --git a/nym-api/nym-api-requests/src/models.rs b/nym-api/nym-api-requests/src/models.rs index 3ab82c320a..142a607e5a 100644 --- a/nym-api/nym-api-requests/src/models.rs +++ b/nym-api/nym-api-requests/src/models.rs @@ -363,11 +363,17 @@ pub struct NymNodeDescription { // TODO: do we really care about ALL build info or just the version? pub build_information: BinaryBuildInformationOwned, + #[serde(default)] + pub network_requester: Option, + + #[serde(default)] + pub ip_packet_router: Option, + // for now we only care about their ws/wss situation, nothing more pub mixnet_websockets: WebSockets, } -#[derive(Clone, Serialize, Deserialize, schemars::JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)] pub struct DescribedGateway { pub bond: GatewayBond, pub self_described: Option, @@ -381,3 +387,18 @@ impl From for DescribedGateway { } } } + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)] +pub struct NetworkRequesterDetails { + /// address of the embedded network requester + pub address: String, + + /// flag indicating whether this network requester uses the exit policy rather than the deprecated allow list + pub uses_exit_policy: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)] +pub struct IpPacketRouterDetails { + /// address of the embedded ip packet router + pub address: String, +} diff --git a/nym-api/src/circulating_supply_api/cache/data.rs b/nym-api/src/circulating_supply_api/cache/data.rs index 0ca5b7a23a..77ddbaaee2 100644 --- a/nym-api/src/circulating_supply_api/cache/data.rs +++ b/nym-api/src/circulating_supply_api/cache/data.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::Cache; use nym_api_requests::models::CirculatingSupplyResponse; diff --git a/nym-api/src/circulating_supply_api/cache/mod.rs b/nym-api/src/circulating_supply_api/cache/mod.rs index a9f6279e8c..af5568b158 100644 --- a/nym-api/src/circulating_supply_api/cache/mod.rs +++ b/nym-api/src/circulating_supply_api/cache/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use self::data::CirculatingSupplyCacheData; use cosmwasm_std::Addr; diff --git a/nym-api/src/circulating_supply_api/cache/refresher.rs b/nym-api/src/circulating_supply_api/cache/refresher.rs index a5a301b0a6..f983390356 100644 --- a/nym-api/src/circulating_supply_api/cache/refresher.rs +++ b/nym-api/src/circulating_supply_api/cache/refresher.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::CirculatingSupplyCache; use crate::circulating_supply_api::cache::CirculatingSupplyCacheError; diff --git a/nym-api/src/circulating_supply_api/mod.rs b/nym-api/src/circulating_supply_api/mod.rs index ea8d4caea7..0449e9f09f 100644 --- a/nym-api/src/circulating_supply_api/mod.rs +++ b/nym-api/src/circulating_supply_api/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_task::TaskManager; use okapi::openapi3::OpenApi; diff --git a/nym-api/src/circulating_supply_api/routes.rs b/nym-api/src/circulating_supply_api/routes.rs index 2e590e4c8d..fcf23b67fb 100644 --- a/nym-api/src/circulating_supply_api/routes.rs +++ b/nym-api/src/circulating_supply_api/routes.rs @@ -1,5 +1,8 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use crate::circulating_supply_api::cache::CirculatingSupplyCache; use crate::node_status_api::models::ErrorResponse; diff --git a/nym-api/src/coconut/api_routes/helpers.rs b/nym-api/src/coconut/api_routes/helpers.rs new file mode 100644 index 0000000000..6589499767 --- /dev/null +++ b/nym-api/src/coconut/api_routes/helpers.rs @@ -0,0 +1,28 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::error::Result; +use crate::coconut::storage::models::IssuedCredential; +use nym_api_requests::coconut::models::IssuedCredentialInner; +use nym_api_requests::coconut::models::IssuedCredentialsResponse; +use std::collections::BTreeMap; + +pub(crate) fn build_credentials_response( + raw: Vec, +) -> Result { + let mut credentials = BTreeMap::new(); + + for raw_credential in raw { + let id = raw_credential.id; + let api_issued = IssuedCredentialInner::try_from(raw_credential)?; + let old = credentials.insert(id, api_issued); + if old.is_some() { + // why do we panic here rather than return an error? because it's a critical failure because + // since the raw values came directly from the database with the PRIMARY KEY constraint + // it should be IMPOSSIBLE to have duplicate values here + panic!("somehow retrieved multiple credentials with id {id} from the database!") + } + } + + Ok(IssuedCredentialsResponse { credentials }) +} diff --git a/nym-api/src/coconut/api_routes/mod.rs b/nym-api/src/coconut/api_routes/mod.rs new file mode 100644 index 0000000000..6e505d0479 --- /dev/null +++ b/nym-api/src/coconut/api_routes/mod.rs @@ -0,0 +1,209 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] + +use crate::coconut::api_routes::helpers::build_credentials_response; +use crate::coconut::error::{CoconutError, Result}; +use crate::coconut::helpers::{accepted_vote_err, blind_sign}; +use crate::coconut::state::State; +use crate::coconut::storage::CoconutStorageExt; +use nym_api_requests::coconut::models::{ + CredentialsRequestBody, EpochCredentialsResponse, IssuedCredentialResponse, + IssuedCredentialsResponse, +}; +use nym_api_requests::coconut::{ + BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, +}; +use nym_coconut_bandwidth_contract_common::spend_credential::{ + funds_from_cosmos_msgs, SpendCredentialStatus, +}; +use nym_coconut_dkg_common::types::EpochId; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_validator_client::nyxd::Coin; +use rocket::serde::json::Json; +use rocket::State as RocketState; + +mod helpers; + +#[post("/blind-sign", data = "")] +// Until we have serialization and deserialization traits we'll be using a crutch +pub async fn post_blind_sign( + blind_sign_request_body: Json, + state: &RocketState, +) -> Result> { + debug!("Received blind sign request"); + trace!("body: {:?}", blind_sign_request_body); + + // early check: does the request have the expected number of public attributes? + debug!("performing basic request validation"); + if blind_sign_request_body.public_attributes_plain.len() + != BandwidthVoucher::PUBLIC_ATTRIBUTES as usize + { + return Err(CoconutError::InconsistentPublicAttributes); + } + + // check if we already issued a credential for this tx hash + debug!( + "checking if we have already issued credential for this tx_hash (hash: {})", + blind_sign_request_body.tx_hash + ); + if let Some(blinded_signature) = state + .already_issued(blind_sign_request_body.tx_hash) + .await? + { + return Ok(Json(BlindedSignatureResponse { blinded_signature })); + } + + // check if we have the signing key available + debug!("checking if we actually have coconut keys derived..."); + let keypair_guard = state.coconut_keypair.get().await; + let Some(signing_key) = keypair_guard.as_ref() else { + return Err(CoconutError::KeyPairNotDerivedYet); + }; + + // get the transaction details of the claimed deposit + debug!("getting transaction details from the chain"); + let tx = state + .get_transaction(blind_sign_request_body.tx_hash) + .await?; + + // check validity of the request + debug!("fully validating received request"); + state.validate_request(&blind_sign_request_body, tx).await?; + + // produce the partial signature + debug!("producing the partial credential"); + let blinded_signature = blind_sign(&blind_sign_request_body, signing_key.secret_key())?; + + // store the information locally + debug!("storing the issued credential in the database"); + state + .store_issued_credential(blind_sign_request_body.into_inner(), &blinded_signature) + .await?; + + // finally return the credential to the client + Ok(Json(BlindedSignatureResponse { blinded_signature })) +} + +#[post("/verify-bandwidth-credential", data = "")] +pub async fn verify_bandwidth_credential( + verify_credential_body: Json, + state: &RocketState, +) -> Result> { + let proposal_id = verify_credential_body.proposal_id; + let proposal = state.client.get_proposal(proposal_id).await?; + + // TODO: introduce a check to make sure we haven't already voted for this proposal to prevent DDOS + + // Proposal description is the blinded serial number + if !verify_credential_body + .credential + .has_blinded_serial_number(&proposal.description)? + { + return Err(CoconutError::IncorrectProposal { + reason: String::from("incorrect blinded serial number in description"), + }); + } + let proposed_release_funds = + funds_from_cosmos_msgs(proposal.msgs).ok_or(CoconutError::IncorrectProposal { + reason: String::from("action is not to release funds"), + })?; + // Credential has not been spent before, and is on its way of being spent + let credential_status = state + .client + .get_spent_credential(verify_credential_body.credential.blinded_serial_number()) + .await? + .spend_credential + .ok_or(CoconutError::InvalidCredentialStatus { + status: String::from("Inexistent"), + })? + .status(); + if credential_status != SpendCredentialStatus::InProgress { + return Err(CoconutError::InvalidCredentialStatus { + status: format!("{:?}", credential_status), + }); + } + let verification_key = state + .verification_key(*verify_credential_body.credential.epoch_id()) + .await?; + let mut vote_yes = verify_credential_body.credential.verify(&verification_key); + + vote_yes &= Coin::from(proposed_release_funds) + == Coin::new( + verify_credential_body.credential.voucher_value() as u128, + state.mix_denom.clone(), + ); + + // Vote yes or no on the proposal based on the verification result + let ret = state + .client + .vote_proposal(proposal_id, vote_yes, None) + .await; + accepted_vote_err(ret)?; + + Ok(Json(VerifyCredentialResponse::new(vote_yes))) +} + +#[get("/epoch-credentials/")] +pub async fn epoch_credentials( + epoch: EpochId, + state: &RocketState, +) -> Result> { + let issued = state.storage.get_epoch_credentials(epoch).await?; + + let response = if let Some(issued) = issued { + issued.into() + } else { + EpochCredentialsResponse { + epoch_id: epoch, + first_epoch_credential_id: None, + total_issued: 0, + } + }; + + Ok(Json(response)) +} + +#[get("/issued-credential/")] +pub async fn issued_credential( + id: i64, + state: &RocketState, +) -> Result> { + let issued = state.storage.get_issued_credential(id).await?; + + let credential = if let Some(issued) = issued { + Some(issued.try_into()?) + } else { + None + }; + + Ok(Json(IssuedCredentialResponse { credential })) +} + +#[post("/issued-credentials", data = "")] +pub async fn issued_credentials( + params: Json, + state: &RocketState, +) -> Result> { + let params = params.into_inner(); + + if params.pagination.is_some() && !params.credential_ids.is_empty() { + return Err(CoconutError::InvalidQueryArguments); + } + + let credentials = if let Some(pagination) = params.pagination { + state + .storage + .get_issued_credentials_paged(pagination) + .await? + } else { + state + .storage + .get_issued_credentials(params.credential_ids) + .await? + }; + + build_credentials_response(credentials).map(Json) +} diff --git a/nym-api/src/coconut/client.rs b/nym-api/src/coconut/client.rs index 5849556c7b..8ff1f5ed79 100644 --- a/nym-api/src/coconut/client.rs +++ b/nym-api/src/coconut/client.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::Result; use cw3::ProposalResponse; @@ -13,12 +13,12 @@ use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyS use nym_contracts_common::dealings::ContractSafeBytes; use nym_dkg::Threshold; use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult; -use nym_validator_client::nyxd::{AccountId, Fee, TxResponse}; +use nym_validator_client::nyxd::{AccountId, Fee, Hash, TxResponse}; #[async_trait] pub trait Client { async fn address(&self) -> AccountId; - async fn get_tx(&self, tx_hash: &str) -> Result; + async fn get_tx(&self, tx_hash: Hash) -> Result; async fn get_proposal(&self, proposal_id: u64) -> Result; async fn list_proposals(&self) -> Result>; async fn get_spent_credential( diff --git a/nym-api/src/coconut/comm.rs b/nym-api/src/coconut/comm.rs index cf671defa1..5bceaa7627 100644 --- a/nym-api/src/coconut/comm.rs +++ b/nym-api/src/coconut/comm.rs @@ -1,35 +1,113 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::Result; use crate::nyxd; -use nym_coconut_dkg_common::types::EpochId; +use crate::support::nyxd::ClientInner; +use nym_coconut_dkg_common::types::{Epoch, EpochId}; use nym_coconut_interface::VerificationKey; use nym_credentials::coconut::utils::obtain_aggregate_verification_key; use nym_validator_client::coconut::all_coconut_api_clients; +use nym_validator_client::nyxd::contract_traits::DkgQueryClient; +use std::cmp::min; +use std::collections::HashMap; use std::ops::Deref; +use time::OffsetDateTime; +use tokio::sync::RwLock; #[async_trait] pub trait APICommunicationChannel { + async fn current_epoch(&self) -> Result; async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result; } +struct CachedEpoch { + valid_until: OffsetDateTime, + current_epoch_id: EpochId, +} + +impl Default for CachedEpoch { + fn default() -> Self { + CachedEpoch { + valid_until: OffsetDateTime::UNIX_EPOCH, + current_epoch_id: 0, + } + } +} + +impl CachedEpoch { + fn is_valid(&self) -> bool { + self.valid_until > OffsetDateTime::now_utc() + } + + async fn update(&mut self, epoch: Epoch) -> Result<()> { + let now = OffsetDateTime::now_utc(); + let state_end = + OffsetDateTime::from_unix_timestamp(epoch.finish_timestamp.seconds() as i64).unwrap(); + let until_epoch_state_end = state_end - now; + + // make it valid until the next epoch transition or next 5min, whichever is smaller + self.valid_until = now + min(until_epoch_state_end, 5 * time::Duration::MINUTE); + self.current_epoch_id = epoch.epoch_id; + + Ok(()) + } +} + pub(crate) struct QueryCommunicationChannel { nyxd_client: nyxd::Client, + + epoch_keys: RwLock>, + cached_epoch: RwLock, } impl QueryCommunicationChannel { pub fn new(nyxd_client: nyxd::Client) -> Self { - QueryCommunicationChannel { nyxd_client } + QueryCommunicationChannel { + nyxd_client, + epoch_keys: Default::default(), + cached_epoch: Default::default(), + } } } #[async_trait] impl APICommunicationChannel for QueryCommunicationChannel { + async fn current_epoch(&self) -> Result { + let guard = self.cached_epoch.read().await; + if guard.is_valid() { + return Ok(guard.current_epoch_id); + } + + // update cache + drop(guard); + let mut guard = self.cached_epoch.write().await; + + let epoch = match self.nyxd_client.read().await.deref() { + ClientInner::Query(client) => client.get_current_epoch().await?, + ClientInner::Signing(client) => client.get_current_epoch().await?, + }; + + guard.update(epoch).await?; + + return Ok(guard.current_epoch_id); + } + async fn aggregated_verification_key(&self, epoch_id: EpochId) -> Result { - let client = self.nyxd_client.0.read().await; - let coconut_api_clients = all_coconut_api_clients(client.deref(), epoch_id).await?; + if let Some(vk) = self.epoch_keys.read().await.get(&epoch_id) { + return Ok(vk.clone()); + } + + let mut guard = self.epoch_keys.write().await; + let coconut_api_clients = match self.nyxd_client.read().await.deref() { + ClientInner::Query(client) => all_coconut_api_clients(client, epoch_id).await?, + ClientInner::Signing(client) => all_coconut_api_clients(client, epoch_id).await?, + }; + let vk = obtain_aggregate_verification_key(&coconut_api_clients).await?; + + guard.insert(epoch_id, vk.clone()); + Ok(vk) } } diff --git a/nym-api/src/coconut/deposit.rs b/nym-api/src/coconut/deposit.rs index 3d6b8fb2de..22f59fc969 100644 --- a/nym-api/src/coconut/deposit.rs +++ b/nym-api/src/coconut/deposit.rs @@ -1,189 +1,112 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only +use crate::coconut::error::{CoconutError, Result}; use nym_api_requests::coconut::BlindSignRequestBody; use nym_coconut_bandwidth_contract_common::events::{ - DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, - DEPOSIT_VALUE, + COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, + DEPOSIT_INFO, DEPOSIT_VALUE, }; use nym_credentials::coconut::bandwidth::BandwidthVoucher; -use nym_crypto::asymmetric::encryption; -use nym_crypto::asymmetric::identity::{self, Signature}; +use nym_crypto::asymmetric::identity; +use nym_validator_client::nyxd::helpers::find_tx_attribute; use nym_validator_client::nyxd::TxResponse; -use super::error::{CoconutError, Result}; - -pub async fn extract_encryption_key( - blind_sign_request_body: &BlindSignRequestBody, - tx: TxResponse, -) -> Result { - let blind_sign_request = blind_sign_request_body.blind_sign_request(); - let public_attributes = blind_sign_request_body.public_attributes(); - let public_attributes_plain = blind_sign_request_body.public_attributes_plain(); - - if !BandwidthVoucher::verify_against_plain(&public_attributes, public_attributes_plain) { +pub async fn validate_deposit_tx(request: &BlindSignRequestBody, tx: TxResponse) -> Result<()> { + if request.public_attributes_plain.len() != BandwidthVoucher::PUBLIC_ATTRIBUTES as usize { return Err(CoconutError::InconsistentPublicAttributes); } - let tx_hash_str = blind_sign_request_body.tx_hash(); - let mut message = blind_sign_request.to_bytes(); - message.extend_from_slice(tx_hash_str.as_bytes()); + // extract actual public attributes + associated x25519 public key + let deposit_value = find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_VALUE) + .ok_or(CoconutError::DepositValueNotFound)?; - let signature = Signature::from_base58_string(blind_sign_request_body.signature())?; + let deposit_info = find_tx_attribute(&tx, COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_INFO) + .ok_or(CoconutError::DepositInfoNotFound)?; - let attributes: &Vec<_> = tx - .tx_result - .events - .iter() - .find(|event| event.kind == format!("wasm-{}", DEPOSITED_FUNDS_EVENT_TYPE)) - .ok_or(CoconutError::DepositEventNotFound)? - .attributes - .as_ref(); + let x25519_raw = find_tx_attribute( + &tx, + COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, + DEPOSIT_IDENTITY_KEY, + ) + .ok_or(CoconutError::DepositVerifKeyNotFound)?; - let deposit_value: &str = attributes - .iter() - .find(|tag| tag.key == DEPOSIT_VALUE) - .ok_or(CoconutError::DepositValueNotFound)? - .value - .as_ref(); - let deposit_value_plain = public_attributes_plain.get(0).cloned().unwrap_or_default(); - if deposit_value != deposit_value_plain { - return Err(CoconutError::DifferentPublicAttributes( - deposit_value.to_string(), - deposit_value_plain, - )); + // we're not using it anymore, but for legacy reasons it must be present + let _ed25519_raw = find_tx_attribute( + &tx, + COSMWASM_DEPOSITED_FUNDS_EVENT_TYPE, + DEPOSIT_ENCRYPTION_KEY, + ) + .ok_or(CoconutError::DepositEncrKeyNotFound)?; + + // check public attributes against request data + // (thinking about it attaching that data might be redundant since we have the source of truth on the chain) + // safety: we won't read data out of bounds since we just checked we have BandwidthVoucher::PUBLIC_ATTRIBUTES values in the vec + if deposit_value != request.public_attributes_plain[0] { + return Err(CoconutError::InconsistentDepositValue { + request: request.public_attributes_plain[0].clone(), + on_chain: deposit_value, + }); } - let deposit_info: &str = attributes - .iter() - .find(|tag| tag.key == DEPOSIT_INFO) - .ok_or(CoconutError::DepositInfoNotFound)? - .value - .as_ref(); - let deposit_info_plain = public_attributes_plain.get(1).cloned().unwrap_or_default(); - if deposit_info != deposit_info_plain { - return Err(CoconutError::DifferentPublicAttributes( - deposit_info.to_string(), - deposit_info_plain, - )); + if deposit_info != request.public_attributes_plain[1] { + return Err(CoconutError::InconsistentDepositInfo { + request: request.public_attributes_plain[1].clone(), + on_chain: deposit_info, + }); } - let verification_key = identity::PublicKey::from_base58_string( - &attributes - .iter() - .find(|tag| tag.key == DEPOSIT_IDENTITY_KEY) - .ok_or(CoconutError::DepositVerifKeyNotFound)? - .value, - )?; + // verify signature + let x25519 = identity::PublicKey::from_base58_string(x25519_raw)?; + let plaintext = + BandwidthVoucher::signable_plaintext(&request.inner_sign_request, request.tx_hash); + x25519.verify(plaintext, &request.signature)?; - let encryption_key = encryption::PublicKey::from_base58_string( - &attributes - .iter() - .find(|tag| tag.key == DEPOSIT_ENCRYPTION_KEY) - .ok_or(CoconutError::DepositEncrKeyNotFound)? - .value, - )?; - - verification_key.verify(&message, &signature)?; - - Ok(encryption_key) + Ok(()) } #[cfg(test)] mod test { use super::*; - use crate::coconut::tests::tx_entry_fixture; - use nym_coconut::{prepare_blind_sign, BlindSignRequest, Parameters}; + use crate::coconut::tests::{tx_entry_fixture, voucher_request_fixture}; + use cosmwasm_std::coin; + use nym_api_requests::coconut::BlindSignRequestBody; + use nym_coconut::BlindSignRequest; + use nym_coconut_bandwidth_contract_common::events::DEPOSITED_FUNDS_EVENT_TYPE; use nym_config::defaults::VOUCHER_INFO; - use nym_validator_client::nyxd::Hash; use nym_validator_client::nyxd::{Event, EventAttribute}; - use rand_07::rngs::OsRng; - use std::str::FromStr; #[tokio::test] - async fn extract_encryption_key_test() { - let tx_hash = - Hash::from_str("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E") - .unwrap(); - let mut tx_entry = tx_entry_fixture(&tx_hash.to_string()); - let params = Parameters::new(4).unwrap(); - let mut rng = OsRng; - let voucher = BandwidthVoucher::new( - ¶ms, - "1234".to_string(), - VOUCHER_INFO.to_string(), - tx_hash, - identity::PrivateKey::from_base58_string( - identity::KeyPair::new(&mut rng) - .private_key() - .to_base58_string(), - ) - .unwrap(), - encryption::PrivateKey::from_bytes( - &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), - ) - .unwrap(), - ); - let (_, blind_sign_req) = prepare_blind_sign( - ¶ms, - &voucher.get_private_attributes(), - &voucher.get_public_attributes(), - ) - .unwrap(); - let signature = "2DHbEZ6pzToGpsAXJrqJi7Wj1pAXeT18283q2YEEyNH5gTymwRozWBdja6SMAVt1dyYmUnM4ZNhsJ4wxZyGh4Z6J".to_string(); + async fn validate_deposit_tx_test() { + let (voucher, correct_request) = voucher_request_fixture(coin(1234, "unym"), None); - let req = BlindSignRequestBody::new( - &blind_sign_req, - tx_hash.to_string(), - signature.clone(), - &voucher.get_public_attributes(), - vec![ - String::from("First wrong plain"), - String::from("Second wrong plain"), - ], - 4, - ); - let err = extract_encryption_key(&req, tx_entry.clone()) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - CoconutError::InconsistentPublicAttributes.to_string() - ); - - let req = BlindSignRequestBody::new( - &blind_sign_req, - tx_hash.to_string(), - String::from("Invalid signature"), - &voucher.get_public_attributes(), - voucher.get_public_attributes_plain(), - 4, - ); - let err = extract_encryption_key(&req, tx_entry.clone()) - .await - .unwrap_err(); - - assert!(matches!( - err, - CoconutError::Ed25519ParseError( - nym_crypto::asymmetric::identity::Ed25519RecoveryError::MalformedSignatureString { .. } - ) - )); - - let correct_request = BlindSignRequestBody::new( - &blind_sign_req, - tx_hash.to_string(), - signature.clone(), - &voucher.get_public_attributes(), - voucher.get_public_attributes_plain(), - 4, - ); + let mut tx_entry = tx_entry_fixture(correct_request.tx_hash); + let good_deposit_attribute = EventAttribute { + key: DEPOSIT_VALUE.to_string(), + value: correct_request.public_attributes_plain[0].clone(), + index: false, + }; + let good_info_attribute = EventAttribute { + key: DEPOSIT_INFO.to_string(), + value: correct_request.public_attributes_plain[1].clone(), + index: false, + }; + let good_identity_key_attribute = EventAttribute { + key: DEPOSIT_IDENTITY_KEY.to_string(), + value: "2eSxwquNJb2nZTEW5p4rbqjHfBaz9UaNhjHHiexPN4He".to_string(), + index: false, + }; + let good_encryption_key_attribute = EventAttribute { + key: DEPOSIT_ENCRYPTION_KEY.to_string(), + value: "2eSxwquNJb2nZTEW5p4rbqjHfBaz9UaNhjHHiexPN4He".to_string(), + index: false, + }; tx_entry.tx_result.events.push(Event { kind: format!("wasm-{}", DEPOSITED_FUNDS_EVENT_TYPE), attributes: vec![], }); - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( @@ -191,26 +114,34 @@ mod test { CoconutError::DepositValueNotFound.to_string(), ); - tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "10".parse().unwrap(), - index: false, - }]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ + EventAttribute { + key: DEPOSIT_VALUE.parse().unwrap(), + value: "10".parse().unwrap(), + index: false, + }, + good_info_attribute.clone(), + good_identity_key_attribute.clone(), + good_encryption_key_attribute.clone(), + ]; + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( err.to_string(), - CoconutError::DifferentPublicAttributes("10".to_string(), "1234".to_string()) - .to_string(), + CoconutError::InconsistentDepositValue { + request: "1234".to_string(), + on_chain: "10".to_string() + } + .to_string() ); - tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ + good_deposit_attribute.clone(), + good_identity_key_attribute.clone(), + good_encryption_key_attribute.clone(), + ]; + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( @@ -219,42 +150,33 @@ mod test { ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), EventAttribute { key: DEPOSIT_INFO.parse().unwrap(), value: "bandwidth deposit info".parse().unwrap(), index: false, }, + good_identity_key_attribute.clone(), + good_encryption_key_attribute.clone(), ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( err.to_string(), - CoconutError::DifferentPublicAttributes( - "bandwidth deposit info".to_string(), - VOUCHER_INFO.to_string(), - ) + CoconutError::InconsistentDepositInfo { + on_chain: "bandwidth deposit info".to_string(), + request: VOUCHER_INFO.to_string(), + } .to_string(), ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), + good_encryption_key_attribute.clone(), ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( @@ -263,23 +185,16 @@ mod test { ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), EventAttribute { key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), value: "verification key".parse().unwrap(), index: false, }, + good_encryption_key_attribute.clone(), ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); @@ -291,16 +206,8 @@ mod test { )); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), EventAttribute { key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), value: "2eSxwquNJb2nZTEW5p4rbqjHfBaz9UaNhjHHiexPN4He" @@ -309,7 +216,7 @@ mod test { index: false, }, ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( @@ -318,16 +225,8 @@ mod test { ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), EventAttribute { key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), value: "6EJGMdEq7t8Npz54uPkftGsdmj7DKntLVputAnDfVZB2" @@ -341,29 +240,16 @@ mod test { index: false, }, ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); - assert!(matches!( - err, - CoconutError::X25519ParseError( - nym_crypto::asymmetric::encryption::KeyRecoveryError::MalformedPublicKeyString { .. } - ) - )); + assert!(matches!(err, CoconutError::SignatureVerificationError(..))); let expected_encryption_key = "HxnTpWTkgigSTAysVKLE8pEiUULHdTT1BxFfzfJvQRi6"; tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), EventAttribute { key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), value: "6EJGMdEq7t8Npz54uPkftGsdmj7DKntLVputAnDfVZB2" @@ -377,7 +263,7 @@ mod test { index: false, }, ]; - let err = extract_encryption_key(&correct_request, tx_entry.clone()) + let err = validate_deposit_tx(&correct_request, tx_entry.clone()) .await .unwrap_err(); assert_eq!( @@ -414,28 +300,21 @@ mod test { 66, 137, 17, 32, ]) .unwrap(); + let correct_request = BlindSignRequestBody::new( - &blind_sign_req, - "7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B".to_string(), - "gSFgpma5GAVMcsmZwKieqGNHNd3dPzcfa8eT2Qn2LoBccSeyiJdphREbNrkuh5XWxMe2hUsranaYzLro48L9Qhd".to_string(), - &voucher.get_public_attributes(), + blind_sign_req, + "7C41AF8266D91DE55E1C8F4712E6A952A165ED3D8C27C7B00428CBD0DE00A52B" + .parse() + .unwrap(), + "3vUCc6MCN5AC2LNgDYjRB1QeErZSN1S8f6K14JHjpUcKWXbjGYFExA8DbwQQBki9gyUqrpBF94Drttb4eMcGQXkp".parse().unwrap(), voucher.get_public_attributes_plain(), - 4, ); tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, + good_deposit_attribute.clone(), + good_info_attribute.clone(), EventAttribute { key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), - value: "64auwDkWan7R8yH1Mwe9dS4qXgrDBCUNDg3Q4KFnd2P5" + value: "3xoM5GmUSq7YW4YNQrax1fEFLw1GbZozxe6UUoJcrqLG" .parse() .unwrap(), index: false, @@ -448,9 +327,7 @@ mod test { index: false, }, ]; - let encryption_key = extract_encryption_key(&correct_request, tx_entry.clone()) - .await - .unwrap(); - assert_eq!(encryption_key.to_base58_string(), expected_encryption_key); + let res = validate_deposit_tx(&correct_request, tx_entry.clone()).await; + assert!(res.is_ok()) } } diff --git a/nym-api/src/coconut/dkg/client.rs b/nym-api/src/coconut/dkg/client.rs index cc0c98e4c8..0c62b6e8a4 100644 --- a/nym-api/src/coconut/dkg/client.rs +++ b/nym-api/src/coconut/dkg/client.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::client::Client; use crate::coconut::error::CoconutError; diff --git a/nym-api/src/coconut/dkg/complaints.rs b/nym-api/src/coconut/dkg/complaints.rs index 4ef0d32ce0..8b397f2564 100644 --- a/nym-api/src/coconut/dkg/complaints.rs +++ b/nym-api/src/coconut/dkg/complaints.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use serde::{Deserialize, Serialize}; diff --git a/nym-api/src/coconut/dkg/controller.rs b/nym-api/src/coconut/dkg/controller.rs index 027b8d2800..89275400ca 100644 --- a/nym-api/src/coconut/dkg/controller.rs +++ b/nym-api/src/coconut/dkg/controller.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::{ConsistentState, PersistentState, State}; @@ -13,7 +13,7 @@ use crate::coconut::dkg::{ use crate::coconut::keypair::KeyPair as CoconutKeyPair; use crate::nyxd; use crate::support::config; -use anyhow::Result; +use anyhow::{bail, Result}; use nym_coconut_dkg_common::types::EpochState; use nym_dkg::bte::keys::KeyPair as DkgKeyPair; use nym_task::{TaskClient, TaskManager}; @@ -53,6 +53,10 @@ impl DkgController { coconut_keypair: CoconutKeyPair, rng: R, ) -> Result { + let Some(announce_address) = &config.announce_address else { + bail!("can't start a DKG controller without specifying an announce address!") + }; + let dkg_keypair = nym_pemstore::load_keypair(&nym_pemstore::KeyPairPath::new( &config.storage_paths.decryption_key_path, &config.storage_paths.public_key_with_proof_path, @@ -76,7 +80,7 @@ impl DkgController { state: State::new( config.storage_paths.dkg_persistent_state_path.clone(), persistent_state, - config.announce_address.clone(), + announce_address.clone(), dkg_keypair, coconut_keypair, ), diff --git a/nym-api/src/coconut/dkg/dealing.rs b/nym-api/src/coconut/dkg/dealing.rs index 5ea676033c..266c6db66e 100644 --- a/nym-api/src/coconut/dkg/dealing.rs +++ b/nym-api/src/coconut/dkg/dealing.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::{ConsistentState, State}; @@ -11,6 +11,7 @@ use nym_dkg::bte::setup; use nym_dkg::Dealing; use rand::RngCore; use std::collections::VecDeque; +use zeroize::Zeroize; pub(crate) async fn dealing_exchange( dkg_client: &DkgClient, @@ -39,16 +40,19 @@ pub(crate) async fn dealing_exchange( .keys() .position(|node_index| *node_index == dealer_index); - let prior_resharing_secrets = if let Some(sk) = state.coconut_secret_key().await { + let prior_resharing_secrets = if let Some(mut keypair) = state.take_coconut_keypair().await { // Double check that we are in resharing mode if resharing { - let (x, mut scalars) = sk.into_raw(); - if scalars.len() + 1 != TOTAL_DEALINGS { + let sk = keypair.secret_key(); + if sk.size() + 1 != TOTAL_DEALINGS { return Err(CoconutError::CorruptedCoconutKeyPair); } + + let (x, mut scalars) = sk.into_raw(); + // We can now erase the keypair from memory debug!("Removing coconut keypair from memory"); - state.set_coconut_keypair(None).await; + keypair.zeroize(); scalars.push(x); scalars } else { diff --git a/nym-api/src/coconut/dkg/mod.rs b/nym-api/src/coconut/dkg/mod.rs index 4149a3e236..ac82cf97c3 100644 --- a/nym-api/src/coconut/dkg/mod.rs +++ b/nym-api/src/coconut/dkg/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod client; pub(crate) mod complaints; diff --git a/nym-api/src/coconut/dkg/public_key.rs b/nym-api/src/coconut/dkg/public_key.rs index 802b8a5e43..74f3d5c9f3 100644 --- a/nym-api/src/coconut/dkg/public_key.rs +++ b/nym-api/src/coconut/dkg/public_key.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::state::State; diff --git a/nym-api/src/coconut/dkg/state.rs b/nym-api/src/coconut/dkg/state.rs index b50d6fa3d9..b22cd81f50 100644 --- a/nym-api/src/coconut/dkg/state.rs +++ b/nym-api/src/coconut/dkg/state.rs @@ -1,12 +1,11 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::error::CoconutError; use crate::coconut::keypair::KeyPair as CoconutKeyPair; use cosmwasm_std::Addr; use log::debug; -use nym_coconut::SecretKey; use nym_coconut_dkg_common::dealer::DealerDetails; use nym_coconut_dkg_common::types::EpochState; use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof}; @@ -274,12 +273,15 @@ impl State { self.coconut_keypair.get().await.is_some() } - pub async fn coconut_secret_key(&self) -> Option { - self.coconut_keypair - .get() - .await - .as_ref() - .map(|kp| kp.secret_key()) + pub async fn take_coconut_keypair(&self) -> Option { + self.coconut_keypair.take().await + } + + #[cfg(test)] + pub async fn coconut_keypair( + &self, + ) -> tokio::sync::RwLockReadGuard<'_, Option> { + self.coconut_keypair.get().await } pub fn node_index(&self) -> Option { diff --git a/nym-api/src/coconut/dkg/verification_key.rs b/nym-api/src/coconut/dkg/verification_key.rs index 1e5751eed3..78e490af73 100644 --- a/nym-api/src/coconut/dkg/verification_key.rs +++ b/nym-api/src/coconut/dkg/verification_key.rs @@ -1,21 +1,21 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::dkg::client::DkgClient; use crate::coconut::dkg::complaints::ComplaintReason; use crate::coconut::dkg::state::{ConsistentState, State}; use crate::coconut::error::CoconutError; use crate::coconut::helpers::accepted_vote_err; +use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS; use cosmwasm_std::Addr; use cw3::{ProposalResponse, Status}; use log::debug; use nym_coconut::tests::helpers::transpose_matrix; -use nym_coconut::{check_vk_pairing, Base58, KeyPair, Parameters, SecretKey, VerificationKey}; +use nym_coconut::{check_vk_pairing, Base58, KeyPair, SecretKey, VerificationKey}; use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID; use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS}; use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs; use nym_coconut_interface::KeyPair as CoconutKeyPair; -use nym_credentials::coconut::bandwidth::{PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES}; use nym_dkg::bte::{decrypt_share, setup}; use nym_dkg::error::DkgError; use nym_dkg::{combine_shares, try_recover_verification_keys, Dealing, Threshold}; @@ -140,7 +140,6 @@ fn derive_partial_keypair( } state.set_recovered_vks(recovered_vks); - let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; let x = scalars.pop().ok_or(CoconutError::DkgError( DkgError::NotEnoughDealingsAvailable { available: 0, @@ -148,7 +147,7 @@ fn derive_partial_keypair( }, ))?; let sk = SecretKey::create_from_raw(x, scalars); - let vk = sk.verification_key(¶ms); + let vk = sk.verification_key(&BANDWIDTH_CREDENTIAL_PARAMS); Ok(CoconutKeyPair::from_keys(sk, vk)) } @@ -234,7 +233,7 @@ pub(crate) async fn verification_key_validation( .map(|recovered_vk| recovered_vk.recovered_partials.clone()) .collect(); let recovered_partials = transpose_matrix(recovered_partials); - let params = Parameters::new(PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES)?; + let params = &BANDWIDTH_CREDENTIAL_PARAMS; for contract_share in vk_shares { if let Some(proposal_id) = proposal_ids.get(&contract_share.owner).copied() { match VerificationKey::try_from_bs58(contract_share.share) { @@ -243,7 +242,7 @@ pub(crate) async fn verification_key_validation( .iter() .position(|node_index| contract_share.node_index == *node_index) { - let ret = if !check_vk_pairing(¶ms, &recovered_partials[idx], &vk) { + let ret = if !check_vk_pairing(params, &recovered_partials[idx], &vk) { debug!( "Voting NO to proposal {} because of failed VK pairing", proposal_id @@ -836,16 +835,17 @@ pub(crate) mod tests { for (_, state) in clients_and_states.iter_mut() { state.set_was_in_progress(); } - let params = Parameters::new(4).unwrap(); let mut vks = vec![]; let mut indices = vec![]; for (_, state) in clients_and_states.iter() { let vk = state - .coconut_secret_key() + .coconut_keypair() .await + .as_ref() .unwrap() - .verification_key(¶ms); + .verification_key() + .clone(); let index = state.node_index().unwrap(); vks.push(vk); indices.push(index); @@ -933,10 +933,12 @@ pub(crate) mod tests { let mut indices = vec![]; for (_, state) in clients_and_states.iter() { let vk = state - .coconut_secret_key() + .coconut_keypair() .await + .as_ref() .unwrap() - .verification_key(¶ms); + .verification_key() + .clone(); let index = state.node_index().unwrap(); vks.push(vk); indices.push(index); @@ -1047,16 +1049,16 @@ pub(crate) mod tests { } // DKG in reshare mode - let params = Parameters::new(4).unwrap(); - let mut vks = vec![]; let mut indices = vec![]; for (_, state) in clients_and_states.iter() { let vk = state - .coconut_secret_key() + .coconut_keypair() .await + .as_ref() .unwrap() - .verification_key(¶ms); + .verification_key() + .clone(); let index = state.node_index().unwrap(); vks.push(vk); indices.push(index); @@ -1124,10 +1126,12 @@ pub(crate) mod tests { let mut indices = vec![]; for (_, state) in clients_and_states.iter() { let vk = state - .coconut_secret_key() + .coconut_keypair() .await + .as_ref() .unwrap() - .verification_key(¶ms); + .verification_key() + .clone(); let index = state.node_index().unwrap(); vks.push(vk); indices.push(index); diff --git a/nym-api/src/coconut/error.rs b/nym-api/src/coconut/error.rs index e038e74723..290f448fb1 100644 --- a/nym-api/src/coconut/error.rs +++ b/nym-api/src/coconut/error.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use rocket::http::{ContentType, Status}; use rocket::response::Responder; @@ -13,7 +13,7 @@ use nym_crypto::asymmetric::{ }; use nym_dkg::error::DkgError; use nym_validator_client::coconut::CoconutApiError; -use nym_validator_client::nyxd::error::NyxdError; +use nym_validator_client::nyxd::error::{NyxdError, TendermintError}; use crate::node_status_api::models::NymApiStorageError; @@ -30,82 +30,100 @@ pub enum CoconutError { #[error(transparent)] SerdeJsonError(#[from] serde_json::Error), - #[error("Could not parse Ed25519 data - {0}")] + #[error("could not parse Ed25519 data: {0}")] Ed25519ParseError(#[from] Ed25519RecoveryError), - #[error("Could not parse X25519 data - {0}")] + #[error("could not parse X25519 data: {0}")] X25519ParseError(#[from] KeyRecoveryError), - #[error("Could not parse tx hash in request body")] - TxHashParseError, + #[error("could not parse tx hash in request body: {source}")] + TxHashParseError { + #[source] + source: TendermintError, + }, - #[error("Nyxd error - {0}")] + #[error("could not get transaction details for '{tx_hash}': {source}")] + TxRetrievalFailure { + tx_hash: String, + #[source] + source: NyxdError, + }, + + #[error("nyxd error: {0}")] NyxdError(#[from] NyxdError), - #[error("Validator client error - {0}")] + #[error("validator client error: {0}")] ValidatorClientError(#[from] nym_validator_client::ValidatorClientError), - #[error("Coconut internal error - {0}")] + #[error("coconut internal error: {0}")] CoconutInternalError(#[from] nym_coconut::CoconutError), - #[error("Could not find a deposit event in the transaction provided")] + #[error("could not find a deposit event in the transaction provided")] DepositEventNotFound, - #[error("Could not find the deposit value in the event")] + #[error("could not find the deposit value in the event")] DepositValueNotFound, - #[error("Could not find the deposit info in the event")] + #[error("could not find the deposit info in the event")] DepositInfoNotFound, - #[error("Could not find the verification key in the event")] + #[error("could not find the verification key in the event")] DepositVerifKeyNotFound, - #[error("Could not find the encryption key in the event")] + #[error("could not find the encryption key in the event")] DepositEncrKeyNotFound, - #[error("Signature didn't verify correctly")] + #[error("signature didn't verify correctly")] SignatureVerificationError(#[from] SignatureError), - #[error("Inconsistent public attributes")] + #[error("inconsistent public attributes")] InconsistentPublicAttributes, - #[error( - "Public attributes in request differ from the ones in deposit - Expected {0}, got {1}" - )] + #[error("the provided deposit value is inconsistent. got '{request}' while the value on chain is '{on_chain}'")] + InconsistentDepositValue { request: String, on_chain: String }, + + #[error("the provided deposit info is inconsistent. got '{request}' while the value on chain is '{on_chain}'")] + InconsistentDepositInfo { request: String, on_chain: String }, + + #[error("public attributes in request differ from the ones in deposit: Expected {0}, got {1}")] DifferentPublicAttributes(String, String), - #[error("Error in coconut interface - {0}")] + #[error("error in coconut interface: {0}")] CoconutInterfaceError(#[from] nym_coconut_interface::error::CoconutInterfaceError), - #[error("Storage error - {0}")] + #[error("storage error: {0}")] StorageError(#[from] NymApiStorageError), - #[error("Credentials error - {0}")] + #[error("credentials error: {0}")] CredentialsError(#[from] nym_credentials::error::Error), - #[error("Incorrect credential proposal description: {reason}")] + #[error("incorrect credential proposal description: {reason}")] IncorrectProposal { reason: String }, - #[error("Invalid status of credential: {status}")] + #[error("invalid status of credential: {status}")] InvalidCredentialStatus { status: String }, #[error("DKG error: {0}")] DkgError(#[from] DkgError), - #[error("Failed to recover assigned node index: {reason}")] + #[error("failed to recover assigned node index: {reason}")] NodeIndexRecoveryError { reason: String }, - #[error("Unrecoverable state: {reason}")] + #[error("unrecoverable state: {reason}")] UnrecoverableState { reason: String }, #[error("DKG has not finished yet in order to derive the coconut key")] KeyPairNotDerivedYet, - #[error("The coconut keypair is corrupted")] + #[error("the coconut keypair is corrupted")] CorruptedCoconutKeyPair, - #[error("There was a problem with the proposal id: {reason}")] + #[error("there was a problem with the proposal id: {reason}")] ProposalIdError { reason: String }, + + // I guess we should make this one a bit more detailed + #[error("the provided query arguments were invalid")] + InvalidQueryArguments, } impl<'r, 'o: 'r> Responder<'r, 'o> for CoconutError { diff --git a/nym-api/src/coconut/helpers.rs b/nym-api/src/coconut/helpers.rs index 775e761fea..a7e25d7b46 100644 --- a/nym-api/src/coconut/helpers.rs +++ b/nym-api/src/coconut/helpers.rs @@ -1,7 +1,10 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::CoconutError; +use crate::coconut::state::BANDWIDTH_CREDENTIAL_PARAMS; +use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut::{BlindedSignature, SecretKey}; use nym_validator_client::nyxd::error::NyxdError::AbciError; // If the result is already established, the vote might be redundant and @@ -17,3 +20,18 @@ pub(crate) fn accepted_vote_err(ret: Result<(), CoconutError>) -> Result<(), Coc } Ok(()) } + +pub(crate) fn blind_sign( + request: &BlindSignRequestBody, + signing_key: &SecretKey, +) -> Result { + let public_attributes = request.public_attributes_hashed(); + let attributes_ref = public_attributes.iter().collect::>(); + + Ok(nym_coconut_interface::blind_sign( + &BANDWIDTH_CREDENTIAL_PARAMS, + signing_key, + &request.inner_sign_request, + &attributes_ref, + )?) +} diff --git a/nym-api/src/coconut/keypair.rs b/nym-api/src/coconut/keypair.rs index c1fa548fb4..57b7872971 100644 --- a/nym-api/src/coconut/keypair.rs +++ b/nym-api/src/coconut/keypair.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; @@ -16,6 +16,10 @@ impl KeyPair { } } + pub async fn take(&self) -> Option { + self.inner.write().await.take() + } + pub async fn get(&self) -> RwLockReadGuard<'_, Option> { self.inner.read().await } diff --git a/nym-api/src/coconut/mod.rs b/nym-api/src/coconut/mod.rs index 2f783d2a3a..3f25cba0e1 100644 --- a/nym-api/src/coconut/mod.rs +++ b/nym-api/src/coconut/mod.rs @@ -1,41 +1,17 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use self::comm::APICommunicationChannel; use crate::coconut::client::Client as LocalClient; -use crate::coconut::deposit::extract_encryption_key; -use crate::coconut::error::{CoconutError, Result}; -use crate::coconut::helpers::accepted_vote_err; +use crate::coconut::state::State; use crate::support::storage::NymApiStorage; -use getset::{CopyGetters, Getters}; use keypair::KeyPair; -use nym_api_requests::coconut::{ - BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, -}; -use nym_coconut_bandwidth_contract_common::spend_credential::{ - funds_from_cosmos_msgs, SpendCredentialStatus, -}; -use nym_coconut_dkg_common::types::EpochId; -use nym_coconut_interface::KeyPair as CoconutKeyPair; -use nym_coconut_interface::{ - Attribute, BlindSignRequest, BlindedSignature, Parameters, VerificationKey, -}; use nym_config::defaults::NYM_API_VERSION; -use nym_credentials::coconut::params::{ - NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, -}; -use nym_crypto::asymmetric::encryption; -use nym_crypto::shared_key::new_ephemeral_shared_key; -use nym_crypto::symmetric::stream_cipher; +use nym_crypto::asymmetric::identity; use nym_validator_client::nym_api::routes::{BANDWIDTH, COCONUT_ROUTES}; -use nym_validator_client::nyxd::{Coin, Fee}; -use rand_07::rngs::OsRng; use rocket::fairing::AdHoc; -use rocket::serde::json::Json; -use rocket::State as RocketState; -use std::sync::Arc; -use tokio::sync::Mutex; +pub(crate) mod api_routes; pub(crate) mod client; pub(crate) mod comm; mod deposit; @@ -43,263 +19,45 @@ pub(crate) mod dkg; pub(crate) mod error; pub(crate) mod helpers; pub(crate) mod keypair; +pub(crate) mod state; +pub(crate) mod storage; #[cfg(test)] pub(crate) mod tests; -pub struct State { - client: Arc, +// equivalent of 10nym +pub(crate) const MINIMUM_BALANCE: u128 = 10_000000; + +pub fn stage( + client: C, mix_denom: String, + identity_keypair: identity::KeyPair, key_pair: KeyPair, - comm_channel: Arc, + comm_channel: D, storage: NymApiStorage, - rng: Arc>, -} - -impl State { - pub(crate) fn new( - client: C, - mix_denom: String, - key_pair: KeyPair, - comm_channel: D, - storage: NymApiStorage, - ) -> Self - where - C: LocalClient + Send + Sync + 'static, - D: APICommunicationChannel + Send + Sync + 'static, - { - let client = Arc::new(client); - let comm_channel = Arc::new(comm_channel); - let rng = Arc::new(Mutex::new(OsRng)); - Self { - client, - mix_denom, - key_pair, - comm_channel, - storage, - rng, - } - } - - pub async fn signed_before(&self, tx_hash: &str) -> Result> { - let ret = self.storage.get_blinded_signature_response(tx_hash).await?; - if let Some(blinded_signature_reponse) = ret { - Ok(Some(BlindedSignatureResponse::from_base58_string( - blinded_signature_reponse, - )?)) - } else { - Ok(None) - } - } - - pub async fn encrypt_and_store( - &self, - tx_hash: &str, - remote_key: &encryption::PublicKey, - signature: &BlindedSignature, - ) -> Result { - let (keypair, shared_key) = { - let mut rng = *self.rng.lock().await; - new_ephemeral_shared_key::< - NymApiCredentialEncryptionAlgorithm, - NymApiCredentialHkdfAlgorithm, - _, - >(&mut rng, remote_key) - }; - - let chunk_data = signature.to_bytes(); - - let zero_iv = stream_cipher::zero_iv::(); - let encrypted_data = stream_cipher::encrypt::( - &shared_key, - &zero_iv, - &chunk_data, - ); - - let response = - BlindedSignatureResponse::new(encrypted_data, keypair.public_key().to_bytes()); - - // Atomically insert data, only if there is no signature stored in the meantime - // This prevents race conditions on storing two signatures for the same deposit transaction - if self - .storage - .insert_blinded_signature_response(tx_hash, &response.to_base58_string()) - .await - .is_err() - { - Ok(self - .signed_before(tx_hash) - .await? - .expect("The signature was expected to be there")) - } else { - Ok(response) - } - } - - pub async fn verification_key(&self, epoch_id: EpochId) -> Result { - self.comm_channel - .aggregated_verification_key(epoch_id) - .await - } -} - -#[derive(Getters, CopyGetters, Debug)] -pub(crate) struct InternalSignRequest { - // Total number of parameters to generate for - #[getset(get_copy)] - total_params: u32, - #[getset(get)] - public_attributes: Vec, - #[getset(get)] - blind_sign_request: BlindSignRequest, -} - -impl InternalSignRequest { - pub fn new( - total_params: u32, - public_attributes: Vec, - blind_sign_request: BlindSignRequest, - ) -> InternalSignRequest { - InternalSignRequest { - total_params, - public_attributes, - blind_sign_request, - } - } - - pub fn stage( - client: C, - mix_denom: String, - key_pair: KeyPair, - comm_channel: D, - storage: NymApiStorage, - ) -> AdHoc - where - C: LocalClient + Send + Sync + 'static, - D: APICommunicationChannel + Send + Sync + 'static, - { - let state = State::new(client, mix_denom, key_pair, comm_channel, storage); - AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { - rocket.manage(state).mount( - // this format! is so ugly... - format!("/{}/{}/{}", NYM_API_VERSION, COCONUT_ROUTES, BANDWIDTH), - routes![post_blind_sign, verify_bandwidth_credential], - ) - }) - } -} - -fn blind_sign(request: InternalSignRequest, key_pair: &CoconutKeyPair) -> Result { - let params = Parameters::new(request.total_params())?; - Ok(nym_coconut_interface::blind_sign( - ¶ms, - &key_pair.secret_key(), - request.blind_sign_request(), - request.public_attributes(), - )?) -} - -#[post("/blind-sign", data = "")] -// Until we have serialization and deserialization traits we'll be using a crutch -pub async fn post_blind_sign( - blind_sign_request_body: Json, - state: &RocketState, -) -> Result> { - debug!("{:?}", blind_sign_request_body); - if let Some(response) = state - .signed_before(blind_sign_request_body.tx_hash()) - .await? - { - return Ok(Json(response)); - } - let tx = state - .client - .get_tx(blind_sign_request_body.tx_hash()) - .await?; - let encryption_key = extract_encryption_key(&blind_sign_request_body, tx).await?; - let internal_request = InternalSignRequest::new( - *blind_sign_request_body.total_params(), - blind_sign_request_body.public_attributes(), - blind_sign_request_body.blind_sign_request().clone(), +) -> AdHoc +where + C: LocalClient + Send + Sync + 'static, + D: APICommunicationChannel + Send + Sync + 'static, +{ + let state = State::new( + client, + mix_denom, + identity_keypair, + key_pair, + comm_channel, + storage, ); - let blinded_signature = if let Some(keypair) = state.key_pair.get().await.as_ref() { - blind_sign(internal_request, keypair)? - } else { - return Err(CoconutError::KeyPairNotDerivedYet); - }; - - let response = state - .encrypt_and_store( - blind_sign_request_body.tx_hash(), - &encryption_key, - &blinded_signature, + AdHoc::on_ignite("Internal Sign Request Stage", |rocket| async { + rocket.manage(state).mount( + // this format! is so ugly... + format!("/{NYM_API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}"), + routes![ + api_routes::post_blind_sign, + api_routes::verify_bandwidth_credential, + api_routes::epoch_credentials, + api_routes::issued_credential, + api_routes::issued_credentials, + ], ) - .await?; - - Ok(Json(response)) -} - -#[post("/verify-bandwidth-credential", data = "")] -pub async fn verify_bandwidth_credential( - verify_credential_body: Json, - state: &RocketState, -) -> Result> { - let proposal_id = *verify_credential_body.proposal_id(); - let proposal = state.client.get_proposal(proposal_id).await?; - // Proposal description is the blinded serial number - if !verify_credential_body - .credential() - .has_blinded_serial_number(&proposal.description)? - { - return Err(CoconutError::IncorrectProposal { - reason: String::from("incorrect blinded serial number in description"), - }); - } - let proposed_release_funds = - funds_from_cosmos_msgs(proposal.msgs).ok_or(CoconutError::IncorrectProposal { - reason: String::from("action is not to release funds"), - })?; - // Credential has not been spent before, and is on its way of being spent - let credential_status = state - .client - .get_spent_credential(verify_credential_body.credential().blinded_serial_number()) - .await? - .spend_credential - .ok_or(CoconutError::InvalidCredentialStatus { - status: String::from("Inexistent"), - })? - .status(); - if credential_status != SpendCredentialStatus::InProgress { - return Err(CoconutError::InvalidCredentialStatus { - status: format!("{:?}", credential_status), - }); - } - let verification_key = state - .verification_key(*verify_credential_body.credential().epoch_id()) - .await?; - let mut vote_yes = verify_credential_body - .credential() - .verify(&verification_key); - - vote_yes &= Coin::from(proposed_release_funds) - == Coin::new( - verify_credential_body.credential().voucher_value() as u128, - state.mix_denom.clone(), - ); - - // Vote yes or no on the proposal based on the verification result - let ret = state - .client - .vote_proposal( - proposal_id, - vote_yes, - Some(Fee::new_payer_granter_auto( - None, - None, - Some(verify_credential_body.gateway_cosmos_addr().to_owned()), - )), - ) - .await; - accepted_vote_err(ret)?; - - Ok(Json(VerifyCredentialResponse::new(vote_yes))) + }) } diff --git a/nym-api/src/coconut/state.rs b/nym-api/src/coconut/state.rs new file mode 100644 index 0000000000..0cffc9e752 --- /dev/null +++ b/nym-api/src/coconut/state.rs @@ -0,0 +1,151 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::client::Client as LocalClient; +use crate::coconut::comm::APICommunicationChannel; +use crate::coconut::deposit::validate_deposit_tx; +use crate::coconut::error::Result; +use crate::coconut::keypair::KeyPair; +use crate::coconut::storage::CoconutStorageExt; +use crate::support::storage::NymApiStorage; +use lazy_static::lazy_static; +use nym_api_requests::coconut::helpers::issued_credential_plaintext; +use nym_api_requests::coconut::BlindSignRequestBody; +use nym_coconut::Parameters; +use nym_coconut_dkg_common::types::EpochId; +use nym_coconut_interface::{BlindedSignature, VerificationKey}; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; +use nym_crypto::asymmetric::identity; +use nym_validator_client::nyxd::{Hash, TxResponse}; +use std::sync::Arc; + +// keep it as a global static due to relatively high cost of computing the curve points; +// plus we expect all clients to use the same set of parameters +// +// future note: once we allow for credentials with variable number of attributes, just create Parameters(max_allowed_attributes) +// and take as many hs elements as required (since they will match for all variants) +lazy_static! { + pub(crate) static ref BANDWIDTH_CREDENTIAL_PARAMS: Parameters = + BandwidthVoucher::default_parameters(); +} + +pub struct State { + pub(crate) client: Arc, + pub(crate) mix_denom: String, + pub(crate) coconut_keypair: KeyPair, + pub(crate) identity_keypair: identity::KeyPair, + pub(crate) comm_channel: Arc, + pub(crate) storage: NymApiStorage, +} + +impl State { + pub(crate) fn new( + client: C, + mix_denom: String, + identity_keypair: identity::KeyPair, + key_pair: KeyPair, + comm_channel: D, + storage: NymApiStorage, + ) -> Self + where + C: LocalClient + Send + Sync + 'static, + D: APICommunicationChannel + Send + Sync + 'static, + { + let client = Arc::new(client); + let comm_channel = Arc::new(comm_channel); + + Self { + client, + mix_denom, + coconut_keypair: key_pair, + identity_keypair, + comm_channel, + storage, + } + } + + /// Check if this nym-api has already issued a credential for the provided deposit hash. + /// If so, return it. + pub async fn already_issued(&self, tx_hash: Hash) -> Result> { + self.storage + .get_issued_bandwidth_credential_by_hash(&tx_hash.to_string()) + .await? + .map(|cred| cred.try_into()) + .transpose() + } + + pub async fn get_transaction(&self, tx_hash: Hash) -> Result { + self.client.get_tx(tx_hash).await + } + + pub async fn validate_request( + &self, + request: &BlindSignRequestBody, + tx: TxResponse, + ) -> Result<()> { + validate_deposit_tx(request, tx).await + } + + pub(crate) async fn sign_and_store_credential( + &self, + current_epoch: EpochId, + request_body: BlindSignRequestBody, + blinded_signature: &BlindedSignature, + ) -> Result { + let encoded_commitments = request_body.encode_commitments(); + + let plaintext = issued_credential_plaintext( + current_epoch as u32, + request_body.tx_hash, + blinded_signature, + &encoded_commitments, + &request_body.public_attributes_plain, + ); + + let signature = self.identity_keypair.private_key().sign(plaintext); + + // note: we have a UNIQUE constraint on the tx_hash column of the credential + // and so if the api is processing request for the same hash at the same time, + // only one of them will be successfully inserted to the database + let credential_id = self + .storage + .store_issued_credential( + current_epoch as u32, + request_body.tx_hash, + blinded_signature, + signature, + encoded_commitments, + request_body.public_attributes_plain, + ) + .await?; + + Ok(credential_id) + } + + pub async fn store_issued_credential( + &self, + request_body: BlindSignRequestBody, + blinded_signature: &BlindedSignature, + ) -> Result<()> { + let current_epoch = self.comm_channel.current_epoch().await?; + + // note: we have a UNIQUE constraint on the tx_hash column of the credential + // and so if the api is processing request for the same hash at the same time, + // only one of them will be successfully inserted to the database + let credential_id = self + .sign_and_store_credential(current_epoch, request_body, blinded_signature) + .await?; + self.storage + .update_epoch_credentials_entry(current_epoch, credential_id) + .await?; + debug!("the stored credential has id {credential_id}"); + + Ok(()) + } + + pub async fn verification_key(&self, epoch_id: EpochId) -> Result { + self.comm_channel + .aggregated_verification_key(epoch_id) + .await + } +} diff --git a/nym-api/src/coconut/storage/manager.rs b/nym-api/src/coconut/storage/manager.rs new file mode 100644 index 0000000000..e40b2a4a96 --- /dev/null +++ b/nym-api/src/coconut/storage/manager.rs @@ -0,0 +1,381 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::storage::models::{EpochCredentials, IssuedCredential}; +use crate::support::storage::manager::StorageManager; +use nym_coconut_dkg_common::types::EpochId; + +#[async_trait] +pub trait CoconutStorageManagerExt { + /// Creates new encrypted blinded signature response entry for a given deposit tx hash. + /// + /// # Arguments + /// + /// * `tx_hash`: hash of the deposit transaction. + /// * `blinded_signature_response`: the encrypted blinded signature response. + #[deprecated] + async fn insert_blinded_signature_response( + &self, + _tx_hash: &str, + _blinded_signature_response: &str, + ) -> Result<(), sqlx::Error> { + Ok(()) + } + + /// Tries to obtain encrypted blinded signature response for a given transaction hash. + /// + /// # Arguments + /// + /// * `tx_hash`: transaction hash of the deposit. + #[deprecated] + async fn get_blinded_signature_response( + &self, + _tx_hash: &str, + ) -> Result, sqlx::Error> { + Ok(None) + } + + /// Gets the information about all issued partial credentials in this (coconut) epoch. + /// + /// # Arguments + /// + /// * `epoch_id`: Id of the (coconut) epoch in question. + async fn get_epoch_credentials( + &self, + epoch_id: EpochId, + ) -> Result, sqlx::Error>; + + /// Creates new entry for EpochCredentials for this (coconut) epoch. + /// + /// # Arguments + /// + /// * `epoch_id`: Id of the (coconut) epoch in question. + async fn create_epoch_credentials_entry(&self, epoch_id: EpochId) -> Result<(), sqlx::Error>; + + /// Update the EpochCredentials by incrementing the total number of issued credentials, + /// and setting `start_id` if unset (i.e. this is the first credential issued this epoch) + /// + /// # Arguments + /// * `epoch_id`: Id of the (coconut) epoch in question. + /// * `credential_id`: (database) Id of the coconut credential that triggered the update. + async fn update_epoch_credentials_entry( + &self, + epoch_id: EpochId, + credential_id: i64, + ) -> Result<(), sqlx::Error>; + + /// Attempts to retrieve an issued credential from the data store. + /// + /// # Arguments + /// + /// * `credential_id`: (database) id of the issued credential + async fn get_issued_credential( + &self, + credential_id: i64, + ) -> Result, sqlx::Error>; + + /// Attempts to retrieve an issued credential from the data store. + /// + /// # Arguments + /// + /// * `tx_hash`: transaction hash of the deposit used in the issued bandwidth credential + async fn get_issued_bandwidth_credential_by_hash( + &self, + tx_hash: &str, + ) -> Result, sqlx::Error>; + + /// Store the provided issued credential information and return its (database) id. + /// + /// # Arguments + /// + /// * `credential`: partial credential, alongside any data required for verification. + async fn store_issued_credential( + &self, + epoch_id: u32, + tx_hash: String, + bs58_partial_credential: String, + bs58_signature: String, + joined_private_commitments: String, + joined_public_attributes: String, + ) -> Result; + + /// Attempts to retrieve issued credentials from the data store using provided ids. + /// + /// # Arguments + /// + /// * `credential_ids`: (database) ids of the issued credentials + async fn get_issued_credentials( + &self, + credential_ids: Vec, + ) -> Result, sqlx::Error>; + + /// Attempts to retrieve issued credentials from the data store using pagination specification. + /// + /// # Arguments + /// + /// * `start_after`: the value preceding the first retrieved result + /// * `limit`: the maximum number of entries to retrieve + async fn get_issued_credentials_paged( + &self, + start_after: i64, + limit: u32, + ) -> Result, sqlx::Error>; +} + +#[async_trait] +impl CoconutStorageManagerExt for StorageManager { + /// Gets the information about all issued partial credentials in this (coconut) epoch. + /// + /// # Arguments + /// + /// * `epoch_id`: Id of the (coconut) epoch in question. + async fn get_epoch_credentials( + &self, + epoch_id: EpochId, + ) -> Result, sqlx::Error> { + // even if we were changing epochs every second, it's rather impossible to overflow here + // within any sane amount of time + assert!(epoch_id <= u32::MAX as u64); + let epoch_id_downcasted = epoch_id as u32; + + sqlx::query_as!( + EpochCredentials, + r#" + SELECT epoch_id as "epoch_id: u32", start_id, total_issued as "total_issued: u32" + FROM epoch_credentials + WHERE epoch_id = ? + "#, + epoch_id_downcasted + ) + .fetch_optional(&self.connection_pool) + .await + } + + /// Creates new entry for EpochCredentials for this (coconut) epoch. + /// + /// # Arguments + /// + /// * `epoch_id`: Id of the (coconut) epoch in question. + async fn create_epoch_credentials_entry(&self, epoch_id: EpochId) -> Result<(), sqlx::Error> { + // even if we were changing epochs every second, it's rather impossible to overflow here + // within any sane amount of time + assert!(epoch_id <= u32::MAX as u64); + let epoch_id_downcasted = epoch_id as u32; + + sqlx::query!( + r#" + INSERT INTO epoch_credentials + (epoch_id, start_id, total_issued) + VALUES (?, ?, ?); + "#, + epoch_id_downcasted, + -1, + 0 + ) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + // the logic in this function can be summarised with: + // 1. get the current EpochCredentials for this epoch + // 2. if it exists -> increment `total_issued` + // 3. it has invalid (i.e. -1) `start_id` set it to the provided value + // 4. if it didn't exist, create new entry + /// Update the EpochCredentials by incrementing the total number of issued credentials, + /// and setting `start_id` if unset (i.e. this is the first credential issued this epoch) + /// + /// # Arguments + /// * `epoch_id`: Id of the (coconut) epoch in question. + /// * `credential_id`: (database) Id of the coconut credential that triggered the update. + async fn update_epoch_credentials_entry( + &self, + epoch_id: EpochId, + credential_id: i64, + ) -> Result<(), sqlx::Error> { + // even if we were changing epochs every second, it's rather impossible to overflow here + // within any sane amount of time + assert!(epoch_id <= u32::MAX as u64); + let epoch_id_downcasted = epoch_id as u32; + + // make the atomic transaction in case other tasks are attempting to use the pool + let mut tx = self.connection_pool.begin().await?; + + if let Some(existing) = sqlx::query_as!( + EpochCredentials, + r#" + SELECT epoch_id as "epoch_id: u32", start_id, total_issued as "total_issued: u32" + FROM epoch_credentials + WHERE epoch_id = ? + "#, + epoch_id_downcasted + ) + .fetch_optional(&mut tx) + .await? + { + // the entry has existed before -> update it + if existing.total_issued == 0 { + // no credentials has been issued -> we have to set the `start_id` + sqlx::query!( + r#" + UPDATE epoch_credentials + SET total_issued = 1, start_id = ? + WHERE epoch_id = ? + "#, + credential_id, + epoch_id_downcasted + ) + .execute(&mut tx) + .await?; + } else { + // we have issued credentials in this epoch before -> just increment `total_issued` + sqlx::query!( + r#" + UPDATE epoch_credentials + SET total_issued = total_issued + 1 + WHERE epoch_id = ? + "#, + epoch_id_downcasted + ) + .execute(&mut tx) + .await?; + } + } else { + // the entry has never been created -> probably some race condition; create it instead + sqlx::query!( + r#" + INSERT INTO epoch_credentials + (epoch_id, start_id, total_issued) + VALUES (?, ?, ?); + "#, + epoch_id_downcasted, + credential_id, + 1 + ) + .execute(&mut tx) + .await?; + } + + // finally commit the transaction + tx.commit().await + } + + /// Attempts to retrieve an issued credential from the data store. + /// + /// # Arguments + /// + /// * `credential_id`: (database) id of the issued credential + async fn get_issued_credential( + &self, + credential_id: i64, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + IssuedCredential, + r#" + SELECT id, epoch_id as "epoch_id: u32", tx_hash, bs58_partial_credential, bs58_signature,joined_private_commitments, joined_public_attributes + FROM issued_credential + WHERE id = ? + "#, + credential_id + ) + .fetch_optional(&self.connection_pool) + .await + } + + /// Attempts to retrieve an issued credential from the data store. + /// + /// # Arguments + /// + /// * `tx_hash`: transaction hash of the deposit used in the issued bandwidth credential + async fn get_issued_bandwidth_credential_by_hash( + &self, + tx_hash: &str, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + IssuedCredential, + r#" + SELECT id, epoch_id as "epoch_id: u32", tx_hash, bs58_partial_credential, bs58_signature,joined_private_commitments, joined_public_attributes + FROM issued_credential + WHERE tx_hash = ? + "#, + tx_hash + ) + .fetch_optional(&self.connection_pool) + .await + } + + /// Store the provided issued credential information and return its (database) id. + /// + /// # Arguments + /// + /// * `credential`: partial credential, alongside any data required for verification. + async fn store_issued_credential( + &self, + epoch_id: u32, + tx_hash: String, + bs58_partial_credential: String, + bs58_signature: String, + joined_private_commitments: String, + joined_public_attributes: String, + ) -> Result { + let row_id = sqlx::query!( + r#" + INSERT INTO issued_credential + (epoch_id, tx_hash, bs58_partial_credential, bs58_signature, joined_private_commitments, joined_public_attributes) + VALUES + (?, ?, ?, ?, ?, ?) + "#, + epoch_id, tx_hash, bs58_partial_credential, bs58_signature, joined_private_commitments, joined_public_attributes + ).execute(&self.connection_pool).await?.last_insert_rowid(); + + Ok(row_id) + } + + /// Attempts to retrieve issued credentials from the data store using provided ids. + /// + /// # Arguments + /// + /// * `credential_ids`: (database) ids of the issued credentials + async fn get_issued_credentials( + &self, + credential_ids: Vec, + ) -> Result, sqlx::Error> { + // that sucks : ( + // https://stackoverflow.com/a/70032524 + let params = format!("?{}", ", ?".repeat(credential_ids.len() - 1)); + let query_str = format!("SELECT * FROM issued_credential WHERE id IN ( {params} )"); + let mut query = sqlx::query_as(&query_str); + for id in credential_ids { + query = query.bind(id) + } + + query.fetch_all(&self.connection_pool).await + } + + /// Attempts to retrieve issued credentials from the data store using pagination specification. + /// + /// # Arguments + /// + /// * `start_after`: the value preceding the first retrieved result + /// * `limit`: the maximum number of entries to retrieve + async fn get_issued_credentials_paged( + &self, + start_after: i64, + limit: u32, + ) -> Result, sqlx::Error> { + sqlx::query_as!( + IssuedCredential, + r#" + SELECT id, epoch_id as "epoch_id: u32", tx_hash, bs58_partial_credential, bs58_signature,joined_private_commitments, joined_public_attributes + FROM issued_credential + WHERE id > ? + ORDER BY id + LIMIT ? + "#, + start_after, + limit + ) + .fetch_all(&self.connection_pool) + .await + } +} diff --git a/nym-api/src/coconut/storage/mod.rs b/nym-api/src/coconut/storage/mod.rs new file mode 100644 index 0000000000..5633ba468d --- /dev/null +++ b/nym-api/src/coconut/storage/mod.rs @@ -0,0 +1,166 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::storage::manager::CoconutStorageManagerExt; +use crate::coconut::storage::models::{join_attributes, EpochCredentials, IssuedCredential}; +use crate::node_status_api::models::NymApiStorageError; +use crate::support::storage::NymApiStorage; +use nym_api_requests::coconut::models::Pagination; +use nym_coconut::{Base58, BlindedSignature}; +use nym_coconut_dkg_common::types::EpochId; +use nym_crypto::asymmetric::identity; +use nym_validator_client::nyxd::Hash; + +pub(crate) mod manager; +pub(crate) mod models; + +const DEFAULT_CREDENTIALS_PAGE_LIMIT: u32 = 100; + +#[async_trait] +pub trait CoconutStorageExt { + async fn get_epoch_credentials( + &self, + epoch_id: EpochId, + ) -> Result, NymApiStorageError>; + + async fn create_epoch_credentials_entry( + &self, + epoch_id: EpochId, + ) -> Result<(), NymApiStorageError>; + + async fn update_epoch_credentials_entry( + &self, + epoch_id: EpochId, + credential_id: i64, + ) -> Result<(), NymApiStorageError>; + + async fn get_issued_credential( + &self, + credential_id: i64, + ) -> Result, NymApiStorageError>; + + async fn get_issued_bandwidth_credential_by_hash( + &self, + tx_hash: &str, + ) -> Result, NymApiStorageError>; + + async fn store_issued_credential( + &self, + epoch_id: u32, + tx_hash: Hash, + partial_credential: &BlindedSignature, + signature: identity::Signature, + private_commitments: Vec, + public_attributes: Vec, + ) -> Result; + + async fn get_issued_credentials( + &self, + credential_ids: Vec, + ) -> Result, NymApiStorageError>; + + async fn get_issued_credentials_paged( + &self, + pagination: Pagination, + ) -> Result, NymApiStorageError>; +} + +#[async_trait] +impl CoconutStorageExt for NymApiStorage { + async fn get_epoch_credentials( + &self, + epoch_id: EpochId, + ) -> Result, NymApiStorageError> { + Ok(self.manager.get_epoch_credentials(epoch_id).await?) + } + + async fn create_epoch_credentials_entry( + &self, + epoch_id: EpochId, + ) -> Result<(), NymApiStorageError> { + Ok(self + .manager + .create_epoch_credentials_entry(epoch_id) + .await?) + } + + async fn update_epoch_credentials_entry( + &self, + epoch_id: EpochId, + credential_id: i64, + ) -> Result<(), NymApiStorageError> { + Ok(self + .manager + .update_epoch_credentials_entry(epoch_id, credential_id) + .await?) + } + + async fn get_issued_credential( + &self, + credential_id: i64, + ) -> Result, NymApiStorageError> { + Ok(self.manager.get_issued_credential(credential_id).await?) + } + + async fn get_issued_bandwidth_credential_by_hash( + &self, + tx_hash: &str, + ) -> Result, NymApiStorageError> { + Ok(self + .manager + .get_issued_bandwidth_credential_by_hash(tx_hash) + .await?) + } + + async fn store_issued_credential( + &self, + epoch_id: u32, + tx_hash: Hash, + partial_credential: &BlindedSignature, + signature: identity::Signature, + private_commitments: Vec, + public_attributes: Vec, + ) -> Result { + Ok(self + .manager + .store_issued_credential( + epoch_id, + tx_hash.to_string(), + partial_credential.to_bs58(), + signature.to_base58_string(), + join_attributes(private_commitments), + join_attributes(public_attributes), + ) + .await?) + } + + async fn get_issued_credentials( + &self, + credential_ids: Vec, + ) -> Result, NymApiStorageError> { + Ok(self.manager.get_issued_credentials(credential_ids).await?) + } + + async fn get_issued_credentials_paged( + &self, + pagination: Pagination, + ) -> Result, NymApiStorageError> { + // rows start at 1 + let start_after = pagination.last_key.unwrap_or(0); + let limit = match pagination.limit { + Some(v) => { + if v == 0 || v > DEFAULT_CREDENTIALS_PAGE_LIMIT { + DEFAULT_CREDENTIALS_PAGE_LIMIT + } else { + v + } + } + None => DEFAULT_CREDENTIALS_PAGE_LIMIT, + }; + + Ok(self + .manager + .get_issued_credentials_paged(start_after, limit) + .await?) + } +} diff --git a/nym-api/src/coconut/storage/models.rs b/nym-api/src/coconut/storage/models.rs new file mode 100644 index 0000000000..fe1afbac22 --- /dev/null +++ b/nym-api/src/coconut/storage/models.rs @@ -0,0 +1,119 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::error::CoconutError; +use nym_api_requests::coconut::models::{ + EpochCredentialsResponse, IssuedCredential as ApiIssuedCredential, + IssuedCredentialInner as ApiIssuedCredentialInner, +}; +use nym_api_requests::coconut::BlindedSignatureResponse; +use nym_coconut::{Base58, BlindedSignature}; +use sqlx::FromRow; +use std::fmt::Display; + +pub struct EpochCredentials { + pub epoch_id: u32, + pub start_id: i64, + pub total_issued: u32, +} + +impl From for EpochCredentialsResponse { + fn from(value: EpochCredentials) -> Self { + let first_epoch_credential_id = if value.start_id == -1 { + None + } else { + Some(value.start_id) + }; + + EpochCredentialsResponse { + epoch_id: value.epoch_id as u64, + first_epoch_credential_id, + total_issued: value.total_issued, + } + } +} + +#[derive(FromRow)] +pub struct IssuedCredential { + pub id: i64, + pub epoch_id: u32, + pub tx_hash: String, + + /// base58-encoded issued credential + pub bs58_partial_credential: String, + + /// base58-encoded signature on the issued credential (and the attributes) + pub bs58_signature: String, + + // i.e. "'attr1','attr2',..." + pub joined_private_commitments: String, + + // i.e. "'attr1','attr2',..." + pub joined_public_attributes: String, +} + +impl TryFrom for ApiIssuedCredentialInner { + type Error = CoconutError; + + fn try_from(value: IssuedCredential) -> Result { + Ok(ApiIssuedCredentialInner { + credential: ApiIssuedCredential { + id: value.id, + epoch_id: value.epoch_id, + tx_hash: value + .tx_hash + .parse() + .map_err(|source| CoconutError::TxHashParseError { source })?, + blinded_partial_credential: BlindedSignature::try_from_bs58( + value.bs58_partial_credential, + )?, + bs58_encoded_private_attributes_commitments: split_attributes( + &value.joined_private_commitments, + ), + public_attributes: split_attributes(&value.joined_public_attributes), + }, + signature: value.bs58_signature.parse()?, + }) + } +} + +impl TryFrom for BlindedSignatureResponse { + type Error = CoconutError; + + fn try_from(value: IssuedCredential) -> Result { + Ok(BlindedSignatureResponse { + blinded_signature: BlindedSignature::try_from_bs58(value.bs58_partial_credential)?, + }) + } +} + +impl TryFrom for BlindedSignature { + type Error = CoconutError; + + fn try_from(value: IssuedCredential) -> Result { + Ok(BlindedSignature::try_from_bs58( + value.bs58_partial_credential, + )?) + } +} + +impl IssuedCredential { + // safety: this should only ever be called on sanitized data from the database, + // thus the unwraps are fine (if somebody manually entered their db file and modified it, it's on them) + // pub fn private_attribute_commitments(&self) -> Vec + // pub fn public_attributes(&self) +} + +pub fn join_attributes(attrs: I) -> String +where + I: IntoIterator, + M: Display, +{ + // I could have used `attrs.into_iter().join(",")`, + // but my IDE didn't like it (compiler was fine) + itertools::Itertools::join(&mut attrs.into_iter(), ",") +} + +pub fn split_attributes(attrs: &str) -> Vec { + attrs.split(',').map(|s| s.to_string()).collect() +} diff --git a/nym-api/src/coconut/tests/issued_credentials.rs b/nym-api/src/coconut/tests/issued_credentials.rs new file mode 100644 index 0000000000..d753476235 --- /dev/null +++ b/nym-api/src/coconut/tests/issued_credentials.rs @@ -0,0 +1,276 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::coconut::tests::{voucher_request_fixture, TestFixture}; +use cosmwasm_std::coin; +use nym_api_requests::coconut::models::{ + EpochCredentialsResponse, IssuedCredentialResponse, IssuedCredentialsResponse, Pagination, +}; +use nym_api_requests::coconut::CredentialsRequestBody; +use nym_coconut::Base58; +use nym_validator_client::nym_api::routes::{API_VERSION, BANDWIDTH, COCONUT_ROUTES}; +use rocket::http::Status; +use std::collections::BTreeMap; + +#[tokio::test] +async fn epoch_credentials() { + let route_epoch1 = format!("/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/epoch-credentials/1"); + let route_epoch2 = format!("/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/epoch-credentials/2"); + let route_epoch42 = format!("/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/epoch-credentials/42"); + + let test_fixture = TestFixture::new().await; + + // initially we expect 0 issued + let response = test_fixture.rocket.get(&route_epoch1).dispatch().await; + + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + + assert_eq!(parsed_response.epoch_id, 1); + assert_eq!(parsed_response.total_issued, 0); + assert_eq!(parsed_response.first_epoch_credential_id, None); + + // get credential + test_fixture.issue_dummy_credential().await; + + // now there should be one + let response = test_fixture.rocket.get(&route_epoch1).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + + assert_eq!(parsed_response.epoch_id, 1); + assert_eq!(parsed_response.total_issued, 1); + assert_eq!(parsed_response.first_epoch_credential_id, Some(1)); + + // and another + test_fixture.issue_dummy_credential().await; + + let response = test_fixture.rocket.get(&route_epoch1).dispatch().await; + + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + + // note that first epoch credential didn't change + assert_eq!(parsed_response.epoch_id, 1); + assert_eq!(parsed_response.total_issued, 2); + assert_eq!(parsed_response.first_epoch_credential_id, Some(1)); + + test_fixture.set_epoch(2); + + let response = test_fixture.rocket.get(&route_epoch2).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + + // note the epoch change + assert_eq!(parsed_response.epoch_id, 2); + assert_eq!(parsed_response.total_issued, 0); + assert_eq!(parsed_response.first_epoch_credential_id, None); + + test_fixture.issue_dummy_credential().await; + + let response = test_fixture.rocket.get(&route_epoch2).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + + // note the epoch change + assert_eq!(parsed_response.epoch_id, 2); + assert_eq!(parsed_response.total_issued, 1); + assert_eq!(parsed_response.first_epoch_credential_id, Some(3)); + + // random epoch in the future + let response = test_fixture.rocket.get(&route_epoch42).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: EpochCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + assert_eq!(parsed_response.epoch_id, 42); + assert_eq!(parsed_response.total_issued, 0); + assert_eq!(parsed_response.first_epoch_credential_id, None); +} + +#[tokio::test] +async fn issued_credential() { + fn route(id: i64) -> String { + format!("/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/issued-credential/{id}") + } + + // let test_fixture = TestFixture::new() + let hash1 = "6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E".to_string(); + let hash2 = "9F4DF28B36189B4410BC23D97FD757FC74B919122E80534CC2CA6F3D646F6518".to_string(); + + let (voucher1, req1) = voucher_request_fixture(coin(1234, "unym"), Some(hash1.clone())); + let (voucher2, req2) = voucher_request_fixture(coin(1234, "unym"), Some(hash2.clone())); + + let test_fixture = TestFixture::new().await; + test_fixture.add_deposit_tx(&voucher1); + test_fixture.add_deposit_tx(&voucher2); + + // random credential that was never issued + let response = test_fixture.rocket.get(route(42)).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: IssuedCredentialResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + assert!(parsed_response.credential.is_none()); + + let cred1 = test_fixture.issue_credential(req1).await; + + test_fixture.set_epoch(3); + let cred2 = test_fixture.issue_credential(req2).await; + + let response = test_fixture.rocket.get(route(1)).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: IssuedCredentialResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + let issued1 = parsed_response.credential.unwrap(); + + let response = test_fixture.rocket.get(route(2)).dispatch().await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: IssuedCredentialResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + let issued2 = parsed_response.credential.unwrap(); + + // TODO: currently we have no signature checks + assert_eq!(1, issued1.credential.id); + assert_eq!(1, issued1.credential.epoch_id); + assert_eq!(voucher1.tx_hash(), issued1.credential.tx_hash); + assert_eq!( + cred1.to_bytes(), + issued1.credential.blinded_partial_credential.to_bytes() + ); + let cms: Vec<_> = voucher1 + .blind_sign_request() + .get_private_attributes_pedersen_commitments() + .iter() + .map(|c| c.to_bs58()) + .collect(); + assert_eq!( + cms, + issued1 + .credential + .bs58_encoded_private_attributes_commitments + ); + assert_eq!( + voucher1.get_public_attributes_plain(), + issued1.credential.public_attributes + ); + + assert_eq!(2, issued2.credential.id); + assert_eq!(3, issued2.credential.epoch_id); + assert_eq!(voucher2.tx_hash(), issued2.credential.tx_hash); + assert_eq!( + cred2.to_bytes(), + issued2.credential.blinded_partial_credential.to_bytes() + ); + let cms: Vec<_> = voucher2 + .blind_sign_request() + .get_private_attributes_pedersen_commitments() + .iter() + .map(|c| c.to_bs58()) + .collect(); + assert_eq!( + cms, + issued2 + .credential + .bs58_encoded_private_attributes_commitments + ); + assert_eq!( + voucher2.get_public_attributes_plain(), + issued2.credential.public_attributes + ); +} + +#[tokio::test] +async fn issued_credentials() { + let route = format!("/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/issued-credentials"); + + let test_fixture = TestFixture::new().await; + + // issue some credentials + for _ in 0..20 { + test_fixture.issue_dummy_credential().await; + } + + let issued1 = test_fixture.issued_unchecked(1).await; + let issued2 = test_fixture.issued_unchecked(2).await; + let issued3 = test_fixture.issued_unchecked(3).await; + let issued4 = test_fixture.issued_unchecked(4).await; + let issued5 = test_fixture.issued_unchecked(5).await; + let issued13 = test_fixture.issued_unchecked(13).await; + + let response = test_fixture + .rocket + .post(&route) + .json(&CredentialsRequestBody { + credential_ids: vec![5], + pagination: None, + }) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: IssuedCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + assert_eq!(parsed_response.credentials[&5], issued5); + assert!(parsed_response.credentials.get(&13).is_none()); + + let response = test_fixture + .rocket + .post(&route) + .json(&CredentialsRequestBody { + credential_ids: vec![5, 13], + pagination: None, + }) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + let parsed_response: IssuedCredentialsResponse = + serde_json::from_str(&response.into_string().await.unwrap()).unwrap(); + assert_eq!(parsed_response.credentials[&5], issued5); + assert_eq!(parsed_response.credentials[&13], issued13); + + let response_paginated = test_fixture + .rocket + .post(&route) + .json(&CredentialsRequestBody { + credential_ids: vec![], + pagination: Some(Pagination { + last_key: None, + limit: Some(2), + }), + }) + .dispatch() + .await; + assert_eq!(response_paginated.status(), Status::Ok); + let parsed_response: IssuedCredentialsResponse = + serde_json::from_str(&response_paginated.into_string().await.unwrap()).unwrap(); + + let mut expected = BTreeMap::new(); + expected.insert(1, issued1); + expected.insert(2, issued2); + assert_eq!(expected, parsed_response.credentials); + + let response_paginated = test_fixture + .rocket + .post(&route) + .json(&CredentialsRequestBody { + credential_ids: vec![], + pagination: Some(Pagination { + last_key: Some(2), + limit: Some(3), + }), + }) + .dispatch() + .await; + assert_eq!(response_paginated.status(), Status::Ok); + let parsed_response: IssuedCredentialsResponse = + serde_json::from_str(&response_paginated.into_string().await.unwrap()).unwrap(); + + let mut expected = BTreeMap::new(); + expected.insert(3, issued3); + expected.insert(4, issued4); + expected.insert(5, issued5); + assert_eq!(expected, parsed_response.credentials); +} diff --git a/nym-api/src/coconut/tests.rs b/nym-api/src/coconut/tests/mod.rs similarity index 74% rename from nym-api/src/coconut/tests.rs rename to nym-api/src/coconut/tests/mod.rs index 37153f1370..446a85eab6 100644 --- a/nym-api/src/coconut/tests.rs +++ b/nym-api/src/coconut/tests/mod.rs @@ -1,14 +1,20 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only -use super::InternalSignRequest; use crate::coconut::error::{CoconutError, Result}; -use cosmwasm_std::{to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; +use crate::coconut::storage::CoconutStorageExt; +use crate::coconut::{self, State}; +use crate::support::storage::NymApiStorage; +use async_trait::async_trait; +use cosmwasm_std::{coin, to_binary, Addr, CosmosMsg, Decimal, WasmMsg}; +use cw3::ProposalResponse; +use cw4::MemberResponse; +use nym_api_requests::coconut::models::{IssuedCredentialInner, IssuedCredentialResponse}; use nym_api_requests::coconut::{ BlindSignRequestBody, BlindedSignatureResponse, VerifyCredentialBody, VerifyCredentialResponse, }; use nym_coconut::tests::helpers::theta_from_keys_and_attributes; -use nym_coconut::{prepare_blind_sign, ttp_keygen, Base58, BlindedSignature, Parameters}; +use nym_coconut::{ttp_keygen, Base58, BlindedSignature, Parameters}; use nym_coconut_bandwidth_contract_common::events::{ DEPOSITED_FUNDS_EVENT_TYPE, DEPOSIT_ENCRYPTION_KEY, DEPOSIT_IDENTITY_KEY, DEPOSIT_INFO, DEPOSIT_VALUE, @@ -16,27 +22,6 @@ use nym_coconut_bandwidth_contract_common::events::{ use nym_coconut_bandwidth_contract_common::spend_credential::{ SpendCredential, SpendCredentialResponse, }; -use nym_coconut_interface::{hash_to_scalar, Credential, VerificationKey}; -use nym_config::defaults::VOUCHER_INFO; -use nym_credentials::coconut::bandwidth::BandwidthVoucher; -use nym_credentials::coconut::params::{ - NymApiCredentialEncryptionAlgorithm, NymApiCredentialHkdfAlgorithm, -}; -use nym_crypto::shared_key::recompute_shared_key; -use nym_crypto::symmetric::stream_cipher; -use nym_validator_client::nym_api::routes::{ - API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, -}; -use nym_validator_client::nyxd::Coin; -use nym_validator_client::nyxd::{ - AccountId, Algorithm, DeliverTx, Event, EventAttribute, Fee, Hash, TxResponse, -}; - -use crate::coconut::State; -use crate::support::storage::NymApiStorage; -use async_trait::async_trait; -use cw3::ProposalResponse; -use cw4::MemberResponse; use nym_coconut_dkg_common::dealer::{ ContractDealing, DealerDetails, DealerDetailsResponse, DealerType, }; @@ -45,26 +30,39 @@ use nym_coconut_dkg_common::types::{ EncodedBTEPublicKeyWithProof, Epoch, EpochId, InitialReplacementData, TOTAL_DEALINGS, }; use nym_coconut_dkg_common::verification_key::{ContractVKShare, VerificationKeyShare}; +use nym_coconut_interface::{hash_to_scalar, Credential, VerificationKey}; +use nym_config::defaults::VOUCHER_INFO; use nym_contracts_common::dealings::ContractSafeBytes; +use nym_credentials::coconut::bandwidth::BandwidthVoucher; use nym_crypto::asymmetric::{encryption, identity}; use nym_dkg::Threshold; +use nym_validator_client::nym_api::routes::{ + API_VERSION, BANDWIDTH, COCONUT_BLIND_SIGN, COCONUT_ROUTES, COCONUT_VERIFY_BANDWIDTH_CREDENTIAL, +}; use nym_validator_client::nyxd::cosmwasm_client::logs::Log; use nym_validator_client::nyxd::cosmwasm_client::types::ExecuteResult; +use nym_validator_client::nyxd::Coin; +use nym_validator_client::nyxd::{ + AccountId, Algorithm, Event, EventAttribute, ExecTxResult, Fee, Hash, TxResponse, +}; use rand_07::rngs::OsRng; -use rand_07::Rng; +use rand_07::{Rng, RngCore}; use rocket::http::Status; use rocket::local::asynchronous::Client; use std::collections::HashMap; use std::str::FromStr; +use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, RwLock}; +mod issued_credentials; + const TEST_COIN_DENOM: &str = "unym"; const TEST_REWARDING_VALIDATOR_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0"; #[derive(Clone, Debug)] pub(crate) struct DummyClient { validator_address: AccountId, - tx_db: Arc>>, + tx_db: Arc>>, proposal_db: Arc>>, spent_credential_db: Arc>>, @@ -94,7 +92,7 @@ impl DummyClient { } } - pub fn with_tx_db(mut self, tx_db: &Arc>>) -> Self { + pub fn with_tx_db(mut self, tx_db: &Arc>>) -> Self { self.tx_db = Arc::clone(tx_db); self } @@ -172,13 +170,8 @@ impl super::client::Client for DummyClient { self.validator_address.clone() } - async fn get_tx(&self, tx_hash: &str) -> Result { - self.tx_db - .read() - .unwrap() - .get(tx_hash) - .cloned() - .ok_or(CoconutError::TxHashParseError) + async fn get_tx(&self, tx_hash: Hash) -> Result { + Ok(self.tx_db.read().unwrap().get(&tx_hash).cloned().unwrap()) } async fn get_proposal(&self, proposal_id: u64) -> Result { @@ -467,30 +460,41 @@ impl super::client::Client for DummyClient { #[derive(Clone, Debug)] pub struct DummyCommunicationChannel { + current_epoch: Arc, aggregated_verification_key: VerificationKey, } impl DummyCommunicationChannel { pub fn new(aggregated_verification_key: VerificationKey) -> Self { DummyCommunicationChannel { + current_epoch: Arc::new(AtomicU64::new(1)), aggregated_verification_key, } } + + pub fn with_epoch(mut self, current_epoch: Arc) -> Self { + self.current_epoch = current_epoch; + self + } } #[async_trait] impl super::comm::APICommunicationChannel for DummyCommunicationChannel { + async fn current_epoch(&self) -> Result { + Ok(self.current_epoch.load(Ordering::Relaxed)) + } + async fn aggregated_verification_key(&self, _epoch_id: EpochId) -> Result { Ok(self.aggregated_verification_key.clone()) } } -pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse { +pub fn tx_entry_fixture(hash: Hash) -> TxResponse { TxResponse { - hash: Hash::from_str(tx_hash).unwrap(), + hash, height: Default::default(), index: 0, - tx_result: DeliverTx { + tx_result: ExecTxResult { code: Default::default(), data: Default::default(), log: Default::default(), @@ -505,88 +509,242 @@ pub fn tx_entry_fixture(tx_hash: &str) -> TxResponse { } } -#[tokio::test] -async fn signed_before() { - let tx_hash = - Hash::from_str("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E").unwrap(); - let tx_entry = tx_entry_fixture(&tx_hash.to_string()); - let signature = String::from( - "2DHbEZ6pzToGpsAXJrqJi7Wj1pAXeT18283q2YEEyNH5gTymwRozWBdja6SMAVt1dyYmUnM4ZNhsJ4wxZyGh4Z6J", - ); +pub fn deposit_tx_fixture(voucher: &BandwidthVoucher) -> TxResponse { + TxResponse { + hash: voucher.tx_hash(), + height: Default::default(), + index: 0, + tx_result: ExecTxResult { + code: Default::default(), + data: Default::default(), + log: "".to_string(), + info: "".to_string(), + gas_wanted: 0, + gas_used: 0, + events: vec![Event { + kind: format!("wasm-{}", DEPOSITED_FUNDS_EVENT_TYPE), + attributes: vec![ + EventAttribute { + key: DEPOSIT_VALUE.to_string(), + value: voucher.get_voucher_value(), + index: false, + }, + EventAttribute { + key: DEPOSIT_INFO.to_string(), + value: VOUCHER_INFO.to_string(), + index: false, + }, + EventAttribute { + key: DEPOSIT_IDENTITY_KEY.to_string(), + value: voucher.identity_key().public_key().to_base58_string(), + index: false, + }, + EventAttribute { + key: DEPOSIT_ENCRYPTION_KEY.parse().unwrap(), + value: voucher.encryption_key().public_key().to_base58_string(), + index: false, + }, + ], + }], + codespace: "".to_string(), + }, + tx: vec![], + proof: None, + } +} +pub fn blinded_signature_fixture() -> BlindedSignature { + let gen1_bytes = [ + 151u8, 241, 211, 167, 49, 151, 215, 148, 38, 149, 99, 140, 79, 169, 172, 15, 195, 104, 140, + 79, 151, 116, 185, 5, 161, 78, 58, 63, 23, 27, 172, 88, 108, 85, 232, 63, 249, 122, 26, + 239, 251, 58, 240, 10, 219, 34, 198, 187, + ]; + + let dummy_bytes = gen1_bytes + .iter() + .chain(gen1_bytes.iter()) + .copied() + .collect::>(); + + BlindedSignature::from_bytes(&dummy_bytes).unwrap() +} + +pub fn voucher_request_fixture>( + amount: C, + tx_hash: Option, +) -> (BandwidthVoucher, BlindSignRequestBody) { let params = Parameters::new(4).unwrap(); let mut rng = OsRng; + let tx_hash = if let Some(provided) = &tx_hash { + provided.parse().unwrap() + } else { + Hash::from_str("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E").unwrap() + }; + + let identity_keypair = identity::KeyPair::new(&mut rng); + let encryption_keypair = encryption::KeyPair::new(&mut rng); + let id_priv = + identity::PrivateKey::from_bytes(&identity_keypair.private_key().to_bytes()).unwrap(); + let enc_priv = + encryption::PrivateKey::from_bytes(&encryption_keypair.private_key().to_bytes()).unwrap(); + let voucher = BandwidthVoucher::new( ¶ms, - "1234".to_string(), + amount.into().amount.to_string(), VOUCHER_INFO.to_string(), tx_hash, - identity::PrivateKey::from_base58_string( - identity::KeyPair::new(&mut rng) - .private_key() - .to_base58_string(), - ) - .unwrap(), - encryption::PrivateKey::from_bytes( - &encryption::KeyPair::new(&mut rng).private_key().to_bytes(), - ) - .unwrap(), + id_priv, + enc_priv, ); - let (_, blind_sign_req) = prepare_blind_sign( - ¶ms, - &voucher.get_private_attributes(), - &voucher.get_public_attributes(), - ) - .unwrap(); - let key_pair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); - let mut db_dir = std::env::temp_dir(); - db_dir.push(&key_pair.verification_key().to_bs58()[..8]); - let storage = NymApiStorage::init(db_dir).await.unwrap(); - let tx_db = Arc::new(RwLock::new(HashMap::new())); - tx_db - .write() - .unwrap() - .insert(tx_hash.to_string(), tx_entry.clone()); - let nyxd_client = - DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) - .with_tx_db(&tx_db); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); - let staged_key_pair = crate::coconut::KeyPair::new(); - staged_key_pair.set(Some(key_pair)).await; - - let rocket = rocket::build().attach(InternalSignRequest::stage( - nyxd_client, - TEST_COIN_DENOM.to_string(), - staged_key_pair, - comm_channel, - storage.clone(), - )); - let client = Client::tracked(rocket) - .await - .expect("valid rocket instance"); - - let request_body = BlindSignRequestBody::new( - &blind_sign_req, - tx_hash.to_string(), - signature.clone(), - &voucher.get_public_attributes(), + let request = BlindSignRequestBody::new( + voucher.blind_sign_request().clone(), + tx_hash, + voucher.sign(), voucher.get_public_attributes_plain(), - 4, ); - let encrypted_signature = vec![1, 2, 3, 4]; - let remote_key = [42; 32]; - let expected_response = BlindedSignatureResponse::new(encrypted_signature, remote_key); - storage - .insert_blinded_signature_response( - &tx_hash.to_string(), - &expected_response.to_base58_string(), - ) + (voucher, request) +} + +fn dummy_signature() -> identity::Signature { + "3vUCc6MCN5AC2LNgDYjRB1QeErZSN1S8f6K14JHjpUcKWXbjGYFExA8DbwQQBki9gyUqrpBF94Drttb4eMcGQXkp" + .parse() + .unwrap() +} + +async fn nym_api_storage_fixture(identity: &identity::KeyPair) -> NymApiStorage { + let mut db_dir = std::env::temp_dir(); + db_dir.push(identity.public_key().to_base58_string()); + NymApiStorage::init(db_dir).await.unwrap() +} + +struct TestFixture { + rocket: Client, + storage: NymApiStorage, + tx_db: Arc>>, + epoch: Arc, +} + +impl TestFixture { + async fn new() -> Self { + let mut rng = OsRng; + let params = Parameters::new(4).unwrap(); + let coconut_keypair = ttp_keygen(¶ms, 1, 1).unwrap().remove(0); + let identity = identity::KeyPair::new(&mut rng); + let epoch = Arc::new(AtomicU64::new(1)); + let comm_channel = + DummyCommunicationChannel::new(coconut_keypair.verification_key().clone()) + .with_epoch(epoch.clone()); + let storage = nym_api_storage_fixture(&identity).await; + + let staged_key_pair = crate::coconut::KeyPair::new(); + staged_key_pair.set(Some(coconut_keypair)).await; + + let tx_db = Arc::new(RwLock::new(HashMap::new())); + let nyxd_client = + DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) + .with_tx_db(&tx_db); + + let rocket = rocket::build().attach(coconut::stage( + nyxd_client, + TEST_COIN_DENOM.to_string(), + identity, + staged_key_pair, + comm_channel, + storage.clone(), + )); + + TestFixture { + rocket: Client::tracked(rocket) + .await + .expect("valid rocket instance"), + storage, + tx_db, + epoch, + } + } + + fn set_epoch(&self, epoch: u64) { + self.epoch.store(epoch, Ordering::Relaxed) + } + + fn add_tx(&self, hash: Hash, tx: TxResponse) { + self.tx_db.write().unwrap().insert(hash, tx); + } + + fn add_deposit_tx(&self, voucher: &BandwidthVoucher) { + let mut guard = self.tx_db.write().unwrap(); + let fixture = deposit_tx_fixture(voucher); + guard.insert(voucher.tx_hash(), fixture); + } + + async fn issue_dummy_credential(&self) { + let mut rng = OsRng; + let mut tx_hash = [0u8; 32]; + rng.fill_bytes(&mut tx_hash); + let tx_hash = Hash::from_bytes(Algorithm::Sha256, &tx_hash).unwrap(); + + let (voucher, req) = voucher_request_fixture(coin(1234, "unym"), Some(tx_hash.to_string())); + self.add_deposit_tx(&voucher); + + self.issue_credential(req).await; + } + + async fn issue_credential(&self, req: BlindSignRequestBody) -> BlindedSignatureResponse { + let response = self + .rocket + .post(format!( + "/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/{COCONUT_BLIND_SIGN}", + )) + .json(&req) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + serde_json::from_str(&response.into_string().await.unwrap()).unwrap() + } + + async fn issued_credential(&self, id: i64) -> Option { + let response = self + .rocket + .get(format!( + "/{API_VERSION}/{COCONUT_ROUTES}/{BANDWIDTH}/issued-credential/{id}" + )) + .dispatch() + .await; + assert_eq!(response.status(), Status::Ok); + serde_json::from_str(&response.into_string().await.unwrap()).unwrap() + } + + async fn issued_unchecked(&self, id: i64) -> IssuedCredentialInner { + self.issued_credential(id) + .await + .unwrap() + .credential + .unwrap() + } +} + +#[tokio::test] +async fn already_issued() { + let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); + let tx_hash = request_body.tx_hash; + let tx_entry = tx_entry_fixture(tx_hash); + + let test_fixture = TestFixture::new().await; + test_fixture.add_tx(tx_hash, tx_entry); + + let sig = blinded_signature_fixture(); + let commitments = request_body.encode_commitments(); + let public = request_body.public_attributes_plain.clone(); + test_fixture + .storage + .store_issued_credential(42, tx_hash, &sig, dummy_signature(), commitments, public) .await .unwrap(); - let response = client + let response = test_fixture + .rocket .post(format!( "/{}/{}/{}/{}", API_VERSION, COCONUT_ROUTES, BANDWIDTH, COCONUT_BLIND_SIGN @@ -595,6 +753,7 @@ async fn signed_before() { .dispatch() .await; assert_eq!(response.status(), Status::Ok); + let expected_response = BlindedSignatureResponse::new(sig); // This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893 // let blinded_signature_response = response @@ -612,6 +771,9 @@ async fn signed_before() { #[tokio::test] async fn state_functions() { + let mut rng = OsRng; + let identity = identity::KeyPair::new(&mut rng); + let nyxd_client = DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()); let params = Parameters::new(4).unwrap(); @@ -619,38 +781,49 @@ async fn state_functions() { let mut db_dir = std::env::temp_dir(); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage = NymApiStorage::init(db_dir).await.unwrap(); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); let staged_key_pair = crate::coconut::KeyPair::new(); staged_key_pair.set(Some(key_pair)).await; let state = State::new( nyxd_client, TEST_COIN_DENOM.to_string(), + identity, staged_key_pair, comm_channel, storage.clone(), ); - let tx_hash = String::from("6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E"); - assert!(state.signed_before(&tx_hash).await.unwrap().is_none()); + let tx_hash = "6B27412050B823E58BB38447D7870BBC8CBE3C51C905BEA89D459ACCDA80A00E" + .parse() + .unwrap(); + assert!(state.already_issued(tx_hash).await.unwrap().is_none()); - let encrypted_signature = vec![1, 2, 3, 4]; - let remote_key = [42; 32]; - let expected_response = BlindedSignatureResponse::new(encrypted_signature, remote_key); + let (_, request_body) = voucher_request_fixture(coin(1234, TEST_COIN_DENOM), None); + let commitments = request_body.encode_commitments(); + let public = request_body.public_attributes_plain.clone(); + let sig = blinded_signature_fixture(); storage - .insert_blinded_signature_response(&tx_hash, &expected_response.to_base58_string()) + .store_issued_credential( + 42, + tx_hash, + &sig, + dummy_signature(), + commitments.clone(), + public.clone(), + ) .await .unwrap(); + assert_eq!( state - .signed_before(&tx_hash) + .already_issued(tx_hash) .await .unwrap() .unwrap() .to_bytes(), - expected_response.to_bytes() + blinded_signature_fixture().to_bytes() ); - let encryption_keypair = nym_crypto::asymmetric::encryption::KeyPair::new(&mut OsRng); let blinded_signature = BlindedSignature::from_bytes(&[ 183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240, 12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57, @@ -659,68 +832,46 @@ async fn state_functions() { 222, 119, 93, 146, 116, 229, 0, 152, 51, 232, 2, 102, 204, 147, 202, 254, 243, ]) .unwrap(); + // Check that the new payload is not stored if there was already something signed for tx_hash - assert_eq!( - state - .encrypt_and_store( - &tx_hash, - encryption_keypair.public_key(), - &blinded_signature, - ) - .await - .unwrap() - .to_bytes(), - expected_response.to_bytes() - ); + let storage_err = storage + .store_issued_credential( + 42, + tx_hash, + &blinded_signature, + dummy_signature(), + commitments.clone(), + public.clone(), + ) + .await; + assert!(storage_err.is_err()); // And use a new hash to store a new signature - let tx_hash = String::from("97D64C38D6601B1F0FD3A82E20D252685CB7A210AFB0261018590659AB82B0BF"); - let response = state - .encrypt_and_store( - &tx_hash, - encryption_keypair.public_key(), + let tx_hash = "97D64C38D6601B1F0FD3A82E20D252685CB7A210AFB0261018590659AB82B0BF" + .parse() + .unwrap(); + + storage + .store_issued_credential( + 42, + tx_hash, &blinded_signature, + dummy_signature(), + commitments.clone(), + public.clone(), ) .await .unwrap(); - let remote_key = - nym_crypto::asymmetric::encryption::PublicKey::from_bytes(&response.remote_key).unwrap(); - - let encryption_key = recompute_shared_key::< - NymApiCredentialEncryptionAlgorithm, - NymApiCredentialHkdfAlgorithm, - >(&remote_key, encryption_keypair.private_key()); - let zero_iv = stream_cipher::zero_iv::(); - let blinded_signature_bytes = stream_cipher::decrypt::( - &encryption_key, - &zero_iv, - &response.encrypted_signature, - ); - - let received_blinded_signature = - BlindedSignature::from_bytes(&blinded_signature_bytes).unwrap(); - assert_eq!( - blinded_signature.to_bytes(), - received_blinded_signature.to_bytes() - ); // Check that the same value for tx_hash is returned - - let other_signature = BlindedSignature::from_bytes(&[ - 183, 217, 166, 113, 40, 123, 74, 25, 72, 31, 136, 19, 125, 95, 217, 228, 96, 113, 25, 240, - 12, 102, 125, 11, 174, 20, 216, 82, 192, 71, 27, 194, 48, 20, 17, 95, 243, 179, 82, 21, 57, - 143, 101, 19, 22, 186, 147, 13, 131, 236, 38, 138, 192, 235, 169, 142, 176, 118, 153, 238, - 141, 91, 94, 139, 168, 214, 17, 250, 96, 206, 139, 89, 139, 87, 31, 8, 106, 171, 8, 140, - 201, 158, 18, 152, 24, 98, 153, 170, 141, 35, 190, 200, 19, 148, 71, 197, - ]) - .unwrap(); assert_eq!( state - .encrypt_and_store(&tx_hash, encryption_keypair.public_key(), &other_signature,) + .already_issued(tx_hash) .await .unwrap() + .unwrap() .to_bytes(), - response.to_bytes() + blinded_signature.to_bytes() ); } @@ -731,6 +882,8 @@ async fn blind_sign_correct() { let params = Parameters::new(4).unwrap(); let mut rng = OsRng; + let nym_api_identity = identity::KeyPair::new(&mut rng); + let identity_keypair = identity::KeyPair::new(&mut rng); let encryption_keypair = encryption::KeyPair::new(&mut rng); let voucher = BandwidthVoucher::new( @@ -749,55 +902,20 @@ async fn blind_sign_correct() { let storage = NymApiStorage::init(db_dir).await.unwrap(); let tx_db = Arc::new(RwLock::new(HashMap::new())); - let mut tx_entry = tx_entry_fixture(&tx_hash.to_string()); - tx_entry.tx_result.events.push(Event { - kind: format!("wasm-{}", DEPOSITED_FUNDS_EVENT_TYPE), - attributes: vec![], - }); - tx_entry.tx_result.events.get_mut(0).unwrap().attributes = vec![ - EventAttribute { - key: DEPOSIT_VALUE.parse().unwrap(), - value: "1234".parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_INFO.parse().unwrap(), - value: VOUCHER_INFO.parse().unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_IDENTITY_KEY.parse().unwrap(), - value: identity_keypair - .public_key() - .to_base58_string() - .parse() - .unwrap(), - index: false, - }, - EventAttribute { - key: DEPOSIT_ENCRYPTION_KEY.parse().unwrap(), - value: encryption_keypair - .public_key() - .to_base58_string() - .parse() - .unwrap(), - index: false, - }, - ]; - tx_db - .write() - .unwrap() - .insert(tx_hash.to_string(), tx_entry.clone()); + let tx_entry = deposit_tx_fixture(&voucher); + + tx_db.write().unwrap().insert(tx_hash, tx_entry.clone()); let nyxd_client = DummyClient::new(AccountId::from_str(TEST_REWARDING_VALIDATOR_ADDRESS).unwrap()) .with_tx_db(&tx_db); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); let staged_key_pair = crate::coconut::KeyPair::new(); staged_key_pair.set(Some(key_pair)).await; - let rocket = rocket::build().attach(InternalSignRequest::stage( + let rocket = rocket::build().attach(coconut::stage( nyxd_client, TEST_COIN_DENOM.to_string(), + nym_api_identity, staged_key_pair, comm_channel, storage.clone(), @@ -806,15 +924,13 @@ async fn blind_sign_correct() { .await .expect("valid rocket instance"); + let request_signature = voucher.sign(); + let request_body = BlindSignRequestBody::new( - voucher.blind_sign_request(), - tx_hash.to_string(), - voucher - .sign(voucher.blind_sign_request()) - .to_base58_string(), - &voucher.get_public_attributes(), + voucher.blind_sign_request().clone(), + tx_hash, + request_signature, voucher.get_public_attributes_plain(), - 4, ); let response = client @@ -825,6 +941,7 @@ async fn blind_sign_correct() { .json(&request_body) .dispatch() .await; + assert_eq!(response.status(), Status::Ok); // This is a more direct way, but there's a bug which makes it hang https://github.com/SergioBenitez/Rocket/issues/1893 // assert!(response.into_json::().is_some()); @@ -847,26 +964,32 @@ async fn verification_of_bandwidth_credential() { let mut key_pairs = ttp_keygen(¶ms, 1, 1).unwrap(); let voucher_value = 1234u64; let voucher_info = "voucher info"; - let public_attributes = vec![ + let public_attributes = [ hash_to_scalar(voucher_value.to_string()), hash_to_scalar(voucher_info), ]; + let public_attributes_ref = vec![&public_attributes[0], &public_attributes[1]]; let indices: Vec = key_pairs .iter() .enumerate() .map(|(idx, _)| (idx + 1) as u64) .collect(); let theta = - theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes).unwrap(); + theta_from_keys_and_attributes(¶ms, &key_pairs, &indices, &public_attributes_ref) + .unwrap(); let key_pair = key_pairs.remove(0); db_dir.push(&key_pair.verification_key().to_bs58()[..8]); let storage1 = NymApiStorage::init(db_dir).await.unwrap(); - let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key()); + let comm_channel = DummyCommunicationChannel::new(key_pair.verification_key().clone()); let staged_key_pair = crate::coconut::KeyPair::new(); staged_key_pair.set(Some(key_pair)).await; - let rocket = rocket::build().attach(InternalSignRequest::stage( + let mut rng = OsRng; + let identity = identity::KeyPair::new(&mut rng); + + let rocket = rocket::build().attach(coconut::stage( nyxd_client.clone(), TEST_COIN_DENOM.to_string(), + identity, staged_key_pair, comm_channel.clone(), storage1.clone(), diff --git a/nym-api/src/ephemera/application.rs b/nym-api/src/ephemera/application.rs index 06c1c016d8..d0256710b1 100644 --- a/nym-api/src/ephemera/application.rs +++ b/nym-api/src/ephemera/application.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use ephemera::{ configuration::Configuration, ephemera_api::{ diff --git a/nym-api/src/ephemera/client.rs b/nym-api/src/ephemera/client.rs index 146153759a..907f88000b 100644 --- a/nym-api/src/ephemera/client.rs +++ b/nym-api/src/ephemera/client.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::ephemera::error::Result; use nym_ephemera_common::types::JsonPeerInfo; diff --git a/nym-api/src/ephemera/epoch.rs b/nym-api/src/ephemera/epoch.rs index a588a478c2..2aa0337321 100644 --- a/nym-api/src/ephemera/epoch.rs +++ b/nym-api/src/ephemera/epoch.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::support::nyxd; use chrono::{DateTime, NaiveDateTime, Timelike, Utc}; use log::info; diff --git a/nym-api/src/ephemera/error.rs b/nym-api/src/ephemera/error.rs index e8dd0b6ace..ba2dfc2ce4 100644 --- a/nym-api/src/ephemera/error.rs +++ b/nym-api/src/ephemera/error.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use thiserror::Error; diff --git a/nym-api/src/ephemera/metrics/mod.rs b/nym-api/src/ephemera/metrics/mod.rs index cd408564ea..3f8575b2c1 100644 --- a/nym-api/src/ephemera/metrics/mod.rs +++ b/nym-api/src/ephemera/metrics/mod.rs @@ -1 +1,4 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + pub mod types; diff --git a/nym-api/src/ephemera/metrics/types.rs b/nym-api/src/ephemera/metrics/types.rs index eb549a2360..74e818cd0f 100644 --- a/nym-api/src/ephemera/metrics/types.rs +++ b/nym-api/src/ephemera/metrics/types.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + #[derive(Debug)] pub struct MixnodeResult { pub mix_id: u32, diff --git a/nym-api/src/ephemera/mod.rs b/nym-api/src/ephemera/mod.rs index d6960fcba5..f9134e7e46 100644 --- a/nym-api/src/ephemera/mod.rs +++ b/nym-api/src/ephemera/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only extern crate core; diff --git a/nym-api/src/ephemera/peers/members.rs b/nym-api/src/ephemera/peers/members.rs index c0ff8e286e..22cfccd68c 100644 --- a/nym-api/src/ephemera/peers/members.rs +++ b/nym-api/src/ephemera/peers/members.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::ephemera::client::Client; use crate::ephemera::peers::NymPeer; diff --git a/nym-api/src/ephemera/peers/mod.rs b/nym-api/src/ephemera/peers/mod.rs index 0706c51fde..29613e0060 100644 --- a/nym-api/src/ephemera/peers/mod.rs +++ b/nym-api/src/ephemera/peers/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use std::collections::HashMap; use std::str::FromStr; diff --git a/nym-api/src/ephemera/reward/aggregator.rs b/nym-api/src/ephemera/reward/aggregator.rs index e83119c4f3..9c94e2c518 100644 --- a/nym-api/src/ephemera/reward/aggregator.rs +++ b/nym-api/src/ephemera/reward/aggregator.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use cosmwasm_std::Decimal; use log::{info, trace}; use nym_mixnet_contract_common::reward_params::Performance; diff --git a/nym-api/src/ephemera/reward/mod.rs b/nym-api/src/ephemera/reward/mod.rs index e4e815a9e4..3e4385fc7e 100644 --- a/nym-api/src/ephemera/reward/mod.rs +++ b/nym-api/src/ephemera/reward/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use async_trait::async_trait; use log::{debug, info, trace}; use std::time::Duration; diff --git a/nym-api/src/epoch_operations/error.rs b/nym-api/src/epoch_operations/error.rs index e92d28700f..a9f0c8ad99 100644 --- a/nym-api/src/epoch_operations/error.rs +++ b/nym-api/src/epoch_operations/error.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::models::NymApiStorageError; use nym_mixnet_contract_common::{EpochState, MixId}; diff --git a/nym-api/src/epoch_operations/event_reconciliation.rs b/nym-api/src/epoch_operations/event_reconciliation.rs index 0279f96b21..47230463c9 100644 --- a/nym-api/src/epoch_operations/event_reconciliation.rs +++ b/nym-api/src/epoch_operations/event_reconciliation.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::epoch_operations::error::RewardingError; use crate::RewardedSetUpdater; diff --git a/nym-api/src/epoch_operations/helpers.rs b/nym-api/src/epoch_operations/helpers.rs index 929cbac770..4724f14361 100644 --- a/nym-api/src/epoch_operations/helpers.rs +++ b/nym-api/src/epoch_operations/helpers.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::epoch_operations::RewardedSetUpdater; use cosmwasm_std::{Decimal, Fraction}; diff --git a/nym-api/src/epoch_operations/mod.rs b/nym-api/src/epoch_operations/mod.rs index aa88d0aa66..7f5afbfc9e 100644 --- a/nym-api/src/epoch_operations/mod.rs +++ b/nym-api/src/epoch_operations/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only // there is couple of reasons for putting this in a separate module: // 1. I didn't feel it fit well in nym contract "cache". It seems like purpose of cache is to just keep updating local data diff --git a/nym-api/src/epoch_operations/rewarded_set_assignment.rs b/nym-api/src/epoch_operations/rewarded_set_assignment.rs index 12bc33eaae..08a794768c 100644 --- a/nym-api/src/epoch_operations/rewarded_set_assignment.rs +++ b/nym-api/src/epoch_operations/rewarded_set_assignment.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::helpers::stake_to_f64; @@ -24,7 +24,15 @@ struct MixnodeWithStakeAndPerformance { impl MixnodeWithStakeAndPerformance { fn to_selection_weight(&self) -> f64 { - let scaled_stake = self.total_stake * self.performance; + let scaled_performance = match self.performance.checked_pow(20) { + Ok(perf) => perf, + Err(overflow) => { + warn!("the node's performance ({}) has overflow while scaling it by the factor of 20: {overflow}. Setting it to 0 instead.", self.performance); + return 0.; + } + }; + + let scaled_stake = self.total_stake * scaled_performance; stake_to_f64(scaled_stake) } } diff --git a/nym-api/src/epoch_operations/rewarding.rs b/nym-api/src/epoch_operations/rewarding.rs index 56084a3dfd..2f8ae6160b 100644 --- a/nym-api/src/epoch_operations/rewarding.rs +++ b/nym-api/src/epoch_operations/rewarding.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::helpers::MixnodeWithPerformance; diff --git a/nym-api/src/epoch_operations/transition_beginning.rs b/nym-api/src/epoch_operations/transition_beginning.rs index 4df4ed7dfe..f6e8f87b6a 100644 --- a/nym-api/src/epoch_operations/transition_beginning.rs +++ b/nym-api/src/epoch_operations/transition_beginning.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::epoch_operations::error::RewardingError; use crate::epoch_operations::RewardedSetUpdater; diff --git a/nym-api/src/main.rs b/nym-api/src/main.rs index 1320efa590..e28d0ad37e 100644 --- a/nym-api/src/main.rs +++ b/nym-api/src/main.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only #[macro_use] extern crate rocket; @@ -10,7 +10,6 @@ use crate::node_describe_cache::DescribedNodes; use crate::node_status_api::uptime_updater::HistoricalUptimeUpdater; use crate::support::caching::cache::SharedCache; use crate::support::cli; -use crate::support::cli::CliArgs; use crate::support::config::Config; use crate::support::storage; use crate::support::storage::NymApiStorage; @@ -20,14 +19,13 @@ use anyhow::Result; use circulating_supply_api::cache::CirculatingSupplyCache; use clap::Parser; use coconut::dkg::controller::DkgController; -use log::info; use node_status_api::NodeStatusCache; use nym_bin_common::logging::setup_logging; +use nym_config::defaults::NymNetworkDetails; use nym_contract_cache::cache::NymContractCache; use nym_sphinx::receiver::SphinxMessageReceiver; use nym_task::TaskManager; use rand::rngs::OsRng; -use std::error::Error; use support::{http, nyxd}; mod circulating_supply_api; @@ -48,7 +46,7 @@ struct ShutdownHandles { } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), anyhow::Error> { println!("Starting nym api..."); cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { @@ -57,26 +55,28 @@ async fn main() -> Result<(), Box> { }} setup_logging(); - let args = cli::CliArgs::parse(); + let args = cli::Cli::parse(); + trace!("{:#?}", args); + setup_env(args.config_env_file.as_ref()); - run_nym_api(args).await + args.execute().await } -async fn start_nym_api_tasks( - config: Config, -) -> Result> { +async fn start_nym_api_tasks(config: Config) -> anyhow::Result { let nyxd_client = nyxd::Client::new(&config); let connected_nyxd = config.get_nyxd_url(); - let nym_network_details = config.get_network_details(); + let nym_network_details = NymNetworkDetails::new_from_env(); let network_details = NetworkDetails::new(connected_nyxd.to_string(), nym_network_details); let coconut_keypair = coconut::keypair::KeyPair::new(); + let identity_keypair = config.base.storage_paths.load_identity()?; // let's build our rocket! let rocket = http::setup_rocket( &config, network_details, nyxd_client.clone(), + identity_keypair, coconut_keypair.clone(), ) .await?; @@ -208,31 +208,3 @@ async fn start_nym_api_tasks( rocket_handle: rocket_shutdown_handle, }) } - -async fn run_nym_api(cli_args: CliArgs) -> Result<(), Box> { - let save_to_file = cli_args.save_config; - let config = cli::build_config(cli_args)?; - - // if we just wanted to write data to the config, exit, don't start any tasks - if save_to_file { - info!("Saving the configuration to a file"); - config.save_to_default_location()?; - config - .get_ephemera_args() - .cmd - .clone() - .execute(Some(&config.get_id())); - return Ok(()); - } - - let shutdown_handlers = start_nym_api_tasks(config).await?; - - let res = shutdown_handlers - .task_manager_handle - .catch_interrupt() - .await; - log::info!("Stopping nym API"); - shutdown_handlers.rocket_handle.notify(); - - res -} diff --git a/nym-api/src/network/mod.rs b/nym-api/src/network/mod.rs index b0264f092c..ed60de4a39 100644 --- a/nym-api/src/network/mod.rs +++ b/nym-api/src/network/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use okapi::openapi3::OpenApi; use rocket::Route; diff --git a/nym-api/src/network/models.rs b/nym-api/src/network/models.rs index 518b4352f1..5a86ea6401 100644 --- a/nym-api/src/network/models.rs +++ b/nym-api/src/network/models.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_config::defaults::NymNetworkDetails; use schemars::JsonSchema; diff --git a/nym-api/src/network/routes.rs b/nym-api/src/network/routes.rs index 05dd0b8524..5febb8fd75 100644 --- a/nym-api/src/network/routes.rs +++ b/nym-api/src/network/routes.rs @@ -1,5 +1,8 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use crate::network::models::{ContractInformation, NetworkDetails}; use crate::nym_contract_cache::cache::NymContractCache; diff --git a/nym-api/src/network_monitor/chunker.rs b/nym-api/src/network_monitor/chunker.rs index 1e1f4d0d4d..ec91577866 100644 --- a/nym-api/src/network_monitor/chunker.rs +++ b/nym-api/src/network_monitor/chunker.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::message::NymMessage; diff --git a/nym-api/src/network_monitor/gateways_reader.rs b/nym-api/src/network_monitor/gateways_reader.rs index 37a6fba194..01bec7c52a 100644 --- a/nym-api/src/network_monitor/gateways_reader.rs +++ b/nym-api/src/network_monitor/gateways_reader.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use futures::Stream; use nym_crypto::asymmetric::identity; diff --git a/nym-api/src/network_monitor/mod.rs b/nym-api/src/network_monitor/mod.rs index ae49caf020..29298be436 100644 --- a/nym-api/src/network_monitor/mod.rs +++ b/nym-api/src/network_monitor/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::preparer::PacketPreparer; use crate::network_monitor::monitor::processor::{ diff --git a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs index 9895f18a5a..4a321d57cd 100644 --- a/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs +++ b/nym-api/src/network_monitor/monitor/gateway_clients_cache.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::nyxd; use nym_credential_storage::persistent_storage::PersistentStorage; diff --git a/nym-api/src/network_monitor/monitor/gateways_pinger.rs b/nym-api/src/network_monitor/monitor/gateways_pinger.rs index 4d0a0e7f0e..a114088024 100644 --- a/nym-api/src/network_monitor/monitor/gateways_pinger.rs +++ b/nym-api/src/network_monitor/monitor/gateways_pinger.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::gateway_clients_cache::ActiveGatewayClients; use crate::network_monitor::monitor::receiver::{GatewayClientUpdate, GatewayClientUpdateSender}; diff --git a/nym-api/src/network_monitor/monitor/mod.rs b/nym-api/src/network_monitor/monitor/mod.rs index 126a2f1a5f..e4c35d8f4f 100644 --- a/nym-api/src/network_monitor/monitor/mod.rs +++ b/nym-api/src/network_monitor/monitor/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::preparer::PacketPreparer; use crate::network_monitor::monitor::processor::ReceivedProcessor; diff --git a/nym-api/src/network_monitor/monitor/preparer.rs b/nym-api/src/network_monitor/monitor/preparer.rs index 8b49055476..7ebfe825e0 100644 --- a/nym-api/src/network_monitor/monitor/preparer.rs +++ b/nym-api/src/network_monitor/monitor/preparer.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::sender::GatewayPackets; use crate::network_monitor::test_route::TestRoute; diff --git a/nym-api/src/network_monitor/monitor/processor.rs b/nym-api/src/network_monitor/monitor/processor.rs index 0e38eebd0b..84ac4b3aeb 100644 --- a/nym-api/src/network_monitor/monitor/processor.rs +++ b/nym-api/src/network_monitor/monitor/processor.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::gateways_reader::GatewayMessages; use crate::network_monitor::test_packet::{NodeTestMessage, NymApiTestMessageExt}; diff --git a/nym-api/src/network_monitor/monitor/receiver.rs b/nym-api/src/network_monitor/monitor/receiver.rs index 344f8193b7..be835fcc41 100644 --- a/nym-api/src/network_monitor/monitor/receiver.rs +++ b/nym-api/src/network_monitor/monitor/receiver.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::gateways_reader::{GatewayMessages, GatewaysReader}; use crate::network_monitor::monitor::processor::ReceivedProcessorSender; diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index ef3bda31af..d2d99e7462 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::gateway_clients_cache::{ ActiveGatewayClients, GatewayClientHandle, diff --git a/nym-api/src/network_monitor/monitor/summary_producer.rs b/nym-api/src/network_monitor/monitor/summary_producer.rs index 2a68004e7b..1faca33238 100644 --- a/nym-api/src/network_monitor/monitor/summary_producer.rs +++ b/nym-api/src/network_monitor/monitor/summary_producer.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::preparer::InvalidNode; use crate::network_monitor::test_packet::NodeTestMessage; diff --git a/nym-api/src/network_monitor/test_packet.rs b/nym-api/src/network_monitor/test_packet.rs index 289172d89b..6e5c433ff8 100644 --- a/nym-api/src/network_monitor/test_packet.rs +++ b/nym-api/src/network_monitor/test_packet.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_node_tester_utils::error::NetworkTestingError; use nym_node_tester_utils::TestMessage; diff --git a/nym-api/src/network_monitor/test_route/mod.rs b/nym-api/src/network_monitor/test_route/mod.rs index 48eede18b5..477251f80d 100644 --- a/nym-api/src/network_monitor/test_route/mod.rs +++ b/nym-api/src/network_monitor/test_route/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::test_packet::NymApiTestMessageExt; use crate::network_monitor::ROUTE_TESTING_TEST_NONCE; diff --git a/nym-api/src/node_describe_cache/mod.rs b/nym-api/src/node_describe_cache/mod.rs index fc1c697cac..21b621f428 100644 --- a/nym-api/src/node_describe_cache/mod.rs +++ b/nym-api/src/node_describe_cache/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::nym_contract_cache::cache::NymContractCache; use crate::support::caching::cache::{SharedCache, UninitialisedCache}; @@ -7,8 +7,10 @@ use crate::support::caching::refresher::{CacheItemProvider, CacheRefresher}; use crate::support::config; use crate::support::config::DEFAULT_NODE_DESCRIBE_BATCH_SIZE; use futures_util::{stream, StreamExt}; -use nym_api_requests::models::NymNodeDescription; -use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; +use nym_api_requests::models::{ + IpPacketRouterDetails, NetworkRequesterDetails, NymNodeDescription, +}; +use nym_config::defaults::{mainnet, DEFAULT_NYM_NODE_HTTP_PORT}; use nym_contracts_common::IdentityKey; use nym_mixnet_contract_common::Gateway; use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt}; @@ -152,9 +154,37 @@ async fn get_gateway_description( source: err, })?; + let network_requester = + if let Ok(nr) = client.get_network_requester().await { + let exit_policy = client.get_exit_policy().await.map_err(|err| { + NodeDescribeCacheError::ApiFailure { + gateway: gateway.identity_key.clone(), + source: err, + } + })?; + let uses_nym_exit_policy = exit_policy.upstream_source == mainnet::EXIT_POLICY_URL; + + Some(NetworkRequesterDetails { + address: nr.address, + uses_exit_policy: exit_policy.enabled && uses_nym_exit_policy, + }) + } else { + None + }; + + let ip_packet_router = if let Ok(ipr) = client.get_ip_packet_router().await { + Some(IpPacketRouterDetails { + address: ipr.address, + }) + } else { + None + }; + let description = NymNodeDescription { host_information: host_info.data, build_information: build_info, + network_requester, + ip_packet_router, mixnet_websockets: websockets, }; diff --git a/nym-api/src/node_status_api/cache/data.rs b/nym-api/src/node_status_api/cache/data.rs index b1fb52ea6e..2b828420f6 100644 --- a/nym-api/src/node_status_api/cache/data.rs +++ b/nym-api/src/node_status_api/cache/data.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated}; use crate::support::caching::Cache; diff --git a/nym-api/src/node_status_api/cache/inclusion_probabilities.rs b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs index 7a59de4145..4d53793d36 100644 --- a/nym-api/src/node_status_api/cache/inclusion_probabilities.rs +++ b/nym-api/src/node_status_api/cache/inclusion_probabilities.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use nym_api_requests::models::InclusionProbability; use nym_contracts_common::truncate_decimal; use nym_mixnet_contract_common::{MixId, MixNodeDetails, RewardingParams}; diff --git a/nym-api/src/node_status_api/cache/mod.rs b/nym-api/src/node_status_api/cache/mod.rs index c1f2d93c61..b563afb6a0 100644 --- a/nym-api/src/node_status_api/cache/mod.rs +++ b/nym-api/src/node_status_api/cache/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::Cache; diff --git a/nym-api/src/node_status_api/cache/node_sets.rs b/nym-api/src/node_status_api/cache/node_sets.rs index f871505016..ed20298887 100644 --- a/nym-api/src/node_status_api/cache/node_sets.rs +++ b/nym-api/src/node_status_api/cache/node_sets.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate}; use crate::support::storage::NymApiStorage; use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodePerformance}; diff --git a/nym-api/src/node_status_api/cache/refresher.rs b/nym-api/src/node_status_api/cache/refresher.rs index 7e84ea2ee8..e1dd3896f0 100644 --- a/nym-api/src/node_status_api/cache/refresher.rs +++ b/nym-api/src/node_status_api/cache/refresher.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use super::NodeStatusCache; use crate::{ node_status_api::cache::{ diff --git a/nym-api/src/node_status_api/helpers.rs b/nym-api/src/node_status_api/helpers.rs index 8fd24679de..894391253d 100644 --- a/nym-api/src/node_status_api/helpers.rs +++ b/nym-api/src/node_status_api/helpers.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::models::ErrorResponse; use crate::storage::NymApiStorage; diff --git a/nym-api/src/node_status_api/local_guard.rs b/nym-api/src/node_status_api/local_guard.rs index e9256a5d9c..fbbe8e27ec 100644 --- a/nym-api/src/node_status_api/local_guard.rs +++ b/nym-api/src/node_status_api/local_guard.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use rocket::http::Status; use rocket::request::{FromRequest, Outcome}; diff --git a/nym-api/src/node_status_api/mod.rs b/nym-api/src/node_status_api/mod.rs index c87b7e5b1c..dc6e34b779 100644 --- a/nym-api/src/node_status_api/mod.rs +++ b/nym-api/src/node_status_api/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use self::cache::refresher::NodeStatusCacheRefresher; use crate::support::config; diff --git a/nym-api/src/node_status_api/models.rs b/nym-api/src/node_status_api/models.rs index b1cb8cd79b..ca5e4fd6d1 100644 --- a/nym-api/src/node_status_api/models.rs +++ b/nym-api/src/node_status_api/models.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::utils::NodeUptimes; use crate::storage::models::NodeStatus; diff --git a/nym-api/src/node_status_api/reward_estimate.rs b/nym-api/src/node_status_api/reward_estimate.rs index e6166dae8c..e9bd60ba09 100644 --- a/nym-api/src/node_status_api/reward_estimate.rs +++ b/nym-api/src/node_status_api/reward_estimate.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use cosmwasm_std::Decimal; use nym_mixnet_contract_common::mixnode::MixNodeDetails; diff --git a/nym-api/src/node_status_api/routes.rs b/nym-api/src/node_status_api/routes.rs index 18ea70701e..c3b95e3336 100644 --- a/nym-api/src/node_status_api/routes.rs +++ b/nym-api/src/node_status_api/routes.rs @@ -1,5 +1,8 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use super::helpers::_get_gateways_detailed; use super::NodeStatusCache; diff --git a/nym-api/src/node_status_api/uptime_updater.rs b/nym-api/src/node_status_api/uptime_updater.rs index 78d4116e11..fc09185b53 100644 --- a/nym-api/src/node_status_api/uptime_updater.rs +++ b/nym-api/src/node_status_api/uptime_updater.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::models::{ GatewayStatusReport, MixnodeStatusReport, NymApiStorageError, diff --git a/nym-api/src/node_status_api/utils.rs b/nym-api/src/node_status_api/utils.rs index da824188b9..54d5b50d9a 100644 --- a/nym-api/src/node_status_api/utils.rs +++ b/nym-api/src/node_status_api/utils.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::node_status_api::models::Uptime; use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR}; diff --git a/nym-api/src/nym_contract_cache/cache/data.rs b/nym-api/src/nym_contract_cache/cache/data.rs index ede4bc6838..91852658ba 100644 --- a/nym-api/src/nym_contract_cache/cache/data.rs +++ b/nym-api/src/nym_contract_cache/cache/data.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::Cache; use nym_contracts_common::ContractBuildInformation; diff --git a/nym-api/src/nym_contract_cache/cache/mod.rs b/nym-api/src/nym_contract_cache/cache/mod.rs index 250ba423ac..f5ed467d37 100644 --- a/nym-api/src/nym_contract_cache/cache/mod.rs +++ b/nym-api/src/nym_contract_cache/cache/mod.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use crate::nym_contract_cache::cache::data::CachedContractsInfo; use crate::support::caching::Cache; use data::ValidatorCacheData; diff --git a/nym-api/src/nym_contract_cache/cache/refresher.rs b/nym-api/src/nym_contract_cache/cache/refresher.rs index e1fa5cf18f..ef94eec15d 100644 --- a/nym-api/src/nym_contract_cache/cache/refresher.rs +++ b/nym-api/src/nym_contract_cache/cache/refresher.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use super::NymContractCache; use crate::nym_contract_cache::cache::data::{CachedContractInfo, CachedContractsInfo}; use crate::nyxd::Client; @@ -44,44 +47,52 @@ impl NymContractCacheRefresher { } async fn get_nym_contracts_info(&self) -> Result { + use crate::query_guard; + let mut updated = HashMap::new(); let client_guard = self.nyxd_client.read().await; - let mixnet = client_guard.mixnet_contract_address(); - let vesting = client_guard.vesting_contract_address(); - let name_service = client_guard.name_service_contract_address(); - let service_provider = client_guard.service_provider_contract_address(); - let coconut_bandwidth = client_guard.coconut_bandwidth_contract_address(); - let coconut_dkg = client_guard.dkg_contract_address(); - let group = client_guard.group_contract_address(); - let multisig = client_guard.multisig_contract_address(); + let mixnet = query_guard!(client_guard, mixnet_contract_address()); + let vesting = query_guard!(client_guard, vesting_contract_address()); + let name_service = query_guard!(client_guard, name_service_contract_address()); + let service_provider = query_guard!(client_guard, service_provider_contract_address()); + let coconut_bandwidth = query_guard!(client_guard, coconut_bandwidth_contract_address()); + let coconut_dkg = query_guard!(client_guard, dkg_contract_address()); + let group = query_guard!(client_guard, group_contract_address()); + let multisig = query_guard!(client_guard, multisig_contract_address()); // get cw2 versions - let mixnet_cw2_future = client_guard.get_mixnet_contract_cw2_version(); - let vesting_cw2_future = client_guard.get_vesting_contract_cw2_version(); - let service_provider_cw2_future = client_guard.get_name_service_contract_cw2_version(); - let name_service_cw2_future = client_guard.get_name_service_contract_cw2_version(); + let mixnet_cw2_future = query_guard!(client_guard, get_mixnet_contract_cw2_version()); + let vesting_cw2_future = query_guard!(client_guard, get_vesting_contract_cw2_version()); + let service_provider_cw2_future = + query_guard!(client_guard, get_name_service_contract_cw2_version()); + let name_service_cw2_future = + query_guard!(client_guard, get_name_service_contract_cw2_version()); // group and multisig contract save that information in their storage but don't expose it via queries // so a temporary workaround... let multisig_cw2 = if let Some(multisig_contract) = multisig { - client_guard - .query_contract_raw(multisig_contract, b"contract_info".to_vec()) - .await - .map(|r| serde_json::from_slice(&r).ok()) - .ok() - .flatten() + query_guard!( + client_guard, + query_contract_raw(multisig_contract, b"contract_info".to_vec()) + .await + .map(|r| serde_json::from_slice(&r).ok()) + .ok() + .flatten() + ) } else { None }; let group_cw2 = if let Some(group_contract) = group { - client_guard - .query_contract_raw(group_contract, b"contract_info".to_vec()) - .await - .map(|r| serde_json::from_slice(&r).ok()) - .ok() - .flatten() + query_guard!( + client_guard, + query_contract_raw(group_contract, b"contract_info".to_vec()) + .await + .map(|r| serde_json::from_slice(&r).ok()) + .ok() + .flatten() + ) } else { None }; @@ -95,10 +106,12 @@ impl NymContractCacheRefresher { .await; // get detailed build info - let mixnet_detailed_future = client_guard.get_mixnet_contract_version(); - let vesting_detailed_future = client_guard.get_vesting_contract_version(); - let service_provider_detailed_future = client_guard.get_sp_contract_version(); - let name_service_detailed_future = client_guard.get_name_service_contract_version(); + let mixnet_detailed_future = query_guard!(client_guard, get_mixnet_contract_version()); + let vesting_detailed_future = query_guard!(client_guard, get_vesting_contract_version()); + let service_provider_detailed_future = + query_guard!(client_guard, get_sp_contract_version()); + let name_service_detailed_future = + query_guard!(client_guard, get_name_service_contract_version()); let mut build_info = join_all(vec![ mixnet_detailed_future, diff --git a/nym-api/src/nym_contract_cache/mod.rs b/nym-api/src/nym_contract_cache/mod.rs index a88b88165c..280bdb5534 100644 --- a/nym-api/src/nym_contract_cache/mod.rs +++ b/nym-api/src/nym_contract_cache/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::nym_contract_cache::cache::NymContractCache; use crate::support::{self, config, nyxd}; diff --git a/nym-api/src/nym_contract_cache/routes.rs b/nym-api/src/nym_contract_cache/routes.rs index 9840e51e97..5573d3a629 100644 --- a/nym-api/src/nym_contract_cache/routes.rs +++ b/nym-api/src/nym_contract_cache/routes.rs @@ -1,5 +1,8 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use crate::{ node_status_api::{ diff --git a/nym-api/src/nym_nodes/mod.rs b/nym-api/src/nym_nodes/mod.rs index 4e2a10c5b0..54e28db421 100644 --- a/nym-api/src/nym_nodes/mod.rs +++ b/nym-api/src/nym_nodes/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use okapi::openapi3::OpenApi; use rocket::Route; diff --git a/nym-api/src/nym_nodes/routes.rs b/nym-api/src/nym_nodes/routes.rs index cad4549b45..3dbf62120b 100644 --- a/nym-api/src/nym_nodes/routes.rs +++ b/nym-api/src/nym_nodes/routes.rs @@ -1,5 +1,8 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only + +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use crate::node_describe_cache::DescribedNodes; use crate::nym_contract_cache::cache::NymContractCache; diff --git a/nym-api/src/support/caching/cache.rs b/nym-api/src/support/caching/cache.rs index d330d2f667..fc33124f12 100644 --- a/nym-api/src/support/caching/cache.rs +++ b/nym-api/src/support/caching/cache.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use std::ops::Deref; use std::sync::Arc; diff --git a/nym-api/src/support/caching/mod.rs b/nym-api/src/support/caching/mod.rs index 7d647c6666..babc6375d1 100644 --- a/nym-api/src/support/caching/mod.rs +++ b/nym-api/src/support/caching/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod cache; pub(crate) mod refresher; diff --git a/nym-api/src/support/caching/refresher.rs b/nym-api/src/support/caching/refresher.rs index bb0435a6cf..6d32bb4f5a 100644 --- a/nym-api/src/support/caching/refresher.rs +++ b/nym-api/src/support/caching/refresher.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::caching::cache::SharedCache; use nym_task::TaskClient; diff --git a/nym-api/src/support/cli/build_info.rs b/nym-api/src/support/cli/build_info.rs new file mode 100644 index 0000000000..5189dbc0f6 --- /dev/null +++ b/nym-api/src/support/cli/build_info.rs @@ -0,0 +1,16 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: Args) -> anyhow::Result<()> { + println!("{}", args.output.format(&bin_info_owned!())); + Ok(()) +} diff --git a/nym-api/src/support/cli/init.rs b/nym-api/src/support/cli/init.rs new file mode 100644 index 0000000000..10ce41b6f9 --- /dev/null +++ b/nym-api/src/support/cli/init.rs @@ -0,0 +1,79 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::support::config::default_config_filepath; +use crate::support::config::helpers::initialise_new; +use anyhow::bail; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + /// Id of the nym-api we want to initialise. if unspecified, a default value will be used. + /// default: "default" + #[clap(long, default_value = "default")] + pub(crate) id: String, + + /// Specifies whether network monitoring is enabled on this API + /// default: false + #[clap(short = 'm', long)] + pub(crate) enable_monitor: bool, + + /// Specifies whether network rewarding is enabled on this API + /// default: false + #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] + pub(crate) enable_rewarding: bool, + + /// Endpoint to nyxd instance used for contract information. + /// default: http://localhost:26657 + #[clap(long)] + pub(crate) nyxd_validator: Option, + + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + /// default: None + #[clap(long)] + pub(crate) mnemonic: Option, + + /// Flag to indicate whether credential signer authority is enabled on this API + /// default: false + #[clap( + long, + requires = "mnemonic", + requires = "announce_address", + alias = "enable_coconut" + )] + pub(crate) enable_zk_nym: bool, + + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + /// default: None + #[clap(long)] + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + #[clap(long, requires = "enable_monitor")] + pub(crate) monitor_credentials_mode: bool, + // #[clap(short, long, default_value_t = OutputFormat::default())] + // output: OutputFormat, +} + +pub(crate) async fn execute(args: Args) -> anyhow::Result<()> { + eprintln!("initialising nym-api..."); + + // let output = args.output; + + let config_path = default_config_filepath(&args.id); + if config_path.exists() { + // don't bother with attempting to override some of the data and preserving the rest of it + // if the config exists. + // this situation should never occur under normal circumstances, so it's up to the user to deal with it + bail!("there already exists a configuration file at '{}'. If you intend to replace it, you need to manually remove it first. Make sure to make backup of any keys and datastores first.", config_path.display()) + } + + let config = initialise_new(&args.id)?; + // args take precedence over env + config + .override_with_env() + .override_with_args(args) + .try_save()?; + + Ok(()) +} diff --git a/nym-api/src/support/cli/mod.rs b/nym-api/src/support/cli/mod.rs index 4de111a1b8..2ea7738673 100644 --- a/nym-api/src/support/cli/mod.rs +++ b/nym-api/src/support/cli/mod.rs @@ -1,192 +1,50 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only -use super::config::Config; -use crate::support::config::default_config_filepath; -use crate::support::config::helpers::{initialise_new, try_load_current_config}; -use ::nym_config::defaults::var_names::{MIXNET_CONTRACT_ADDRESS, VESTING_CONTRACT_ADDRESS}; use anyhow::Result; -use clap::Parser; -use lazy_static::lazy_static; +use clap::{Parser, Subcommand}; use nym_bin_common::bin_info; -use nym_config::defaults::var_names::NYXD; -use nym_config::OptionalSet; -use nym_validator_client::nyxd; +use std::sync::OnceLock; -lazy_static! { - pub static ref PRETTY_BUILD_INFORMATION: String = bin_info!().pretty_print(); -} +pub(crate) mod build_info; +pub(crate) mod init; +pub(crate) mod run; // Helper for passing LONG_VERSION to clap fn pretty_build_info_static() -> &'static str { - &PRETTY_BUILD_INFORMATION + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) } -// explicitly defined custom parser (as opposed to just using -// #[arg(value_parser = clap::value_parser!(u8).range(0..100))] -// for better error message -fn threshold_in_range(s: &str) -> Result { - let threshold: usize = s - .parse() - .map_err(|_| format!("`{s}` isn't a valid threshold number"))?; - if threshold > 100 { - Err(format!("{threshold} is not within the range 0-100")) - } else { - Ok(threshold as u8) - } -} - -#[derive(Parser)] +#[derive(Parser, Debug)] #[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] -pub(crate) struct CliArgs { +pub(crate) struct Cli { /// Path pointing to an env file that configures the Nym API. #[clap(short, long)] pub(crate) config_env_file: Option, - /// Id of the nym-api we want to run - #[clap(long)] - pub(crate) id: String, - - /// Specifies whether network monitoring is enabled on this API - #[clap(short = 'm', long)] - pub(crate) enable_monitor: Option, - - /// Specifies whether network rewarding is enabled on this API - #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] - pub(crate) enable_rewarding: Option, - - /// Specifies whether ephemera is used to aggregate monitor data on this API - #[clap(short = 'e', long, requires = "enable_monitor")] - pub(crate) enable_ephemera: Option, - - /// Endpoint to nyxd instance from which the monitor will grab nodes to test - #[clap(long)] - pub(crate) nyxd_validator: Option, - - /// Address of the mixnet contract managing the network - #[clap(long)] - pub(crate) mixnet_contract: Option, - - /// Address of the vesting contract holding locked tokens - #[clap(long)] - pub(crate) vesting_contract: Option, - - /// Mnemonic of the network monitor used for rewarding operators - // even though we're currently converting the mnemonic to string (and then back to the concrete type) - // at least we're getting immediate validation when passing the arguments - #[clap(long)] - pub(crate) mnemonic: Option, - - /// Specifies whether a config file based on provided arguments should be saved to a file - #[clap(short = 'w', long)] - pub(crate) save_config: bool, - - /// Specifies the minimum percentage of monitor test run data present in order to distribute rewards for given interval. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) monitor_threshold: Option, - - /// Mixnodes with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) min_mixnode_reliability: Option, - - /// Gateways with reliability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set. - #[clap(long, value_parser = threshold_in_range)] - pub(crate) min_gateway_reliability: Option, - - /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement - #[clap(long)] - pub(crate) enabled_credentials_mode: Option, - - /// Announced address where coconut clients will connect. - #[clap(long, hide = true)] - pub(crate) announce_address: Option, - - /// Flag to indicate whether coconut signer authority is enabled on this API - #[clap( - long, - requires = "mnemonic", - requires = "announce_address", - hide = true - )] - pub(crate) enable_coconut: Option, - - /// Ephemera configuration arguments. - #[command(flatten)] - pub(crate) ephemera_args: ephemera::cli::init::Cmd, + #[clap(subcommand)] + pub(crate) command: Commands, } -pub(crate) fn override_config(config: Config, args: CliArgs) -> Config { - config - .with_optional_env( - Config::with_custom_nyxd_validator, - args.nyxd_validator, - NYXD, - ) - .with_optional_env( - Config::with_custom_mixnet_contract, - args.mixnet_contract, - MIXNET_CONTRACT_ADDRESS, - ) - .with_optional_env( - Config::with_custom_vesting_contract, - args.vesting_contract, - VESTING_CONTRACT_ADDRESS, - ) - .with_optional(Config::with_mnemonic, args.mnemonic) - .with_optional( - Config::with_minimum_interval_monitor_threshold, - args.monitor_threshold, - ) - .with_optional( - Config::with_min_mixnode_reliability, - args.min_mixnode_reliability, - ) - .with_optional( - Config::with_min_gateway_reliability, - args.min_gateway_reliability, - ) - .with_optional(Config::with_network_monitor_enabled, args.enable_monitor) - .with_optional(Config::with_rewarding_enabled, args.enable_rewarding) - .with_optional(Config::with_ephemera_enabled, args.enable_ephemera) - .with_optional( - Config::with_disabled_credentials_mode, - args.enabled_credentials_mode.map(|b| !b), - ) - .with_optional(Config::with_announce_address, args.announce_address) - .with_optional(Config::with_coconut_signer_enabled, args.enable_coconut) - .with_optional(Config::with_ephemera_ip, args.ephemera_args.ephemera_ip) - .with_optional( - Config::with_ephemera_protocol_port, - args.ephemera_args.ephemera_protocol_port, - ) - .with_optional( - Config::with_ephemera_websocket_port, - args.ephemera_args.ephemera_websocket_port, - ) - .with_optional( - Config::with_ephemera_http_api_port, - args.ephemera_args.ephemera_http_api_port, - ) -} - -pub(crate) fn build_config(args: CliArgs) -> Result { - let id = args.id.clone(); - - // try to load config from the file, if it doesn't exist, use default values - let config = match try_load_current_config(&id) { - Ok(cfg) => cfg, - Err(err) => { - let config_path = default_config_filepath(&id); - warn!( - "Could not load the configuration file from {}: {err}. Either the file did not exist or was malformed. Using the default values instead", - config_path.display() - ); - - initialise_new(&id)? +impl Cli { + pub(crate) async fn execute(self) -> Result<(), anyhow::Error> { + match self.command { + Commands::Init(args) => init::execute(args).await, + Commands::Run(args) => run::execute(args).await, + Commands::BuildInfo(args) => build_info::execute(args), } - }; - - let config = override_config(config, args); - - Ok(config) + } +} + +#[derive(Subcommand, Debug)] +pub(crate) enum Commands { + /// Initialise a Nym Api instance with persistent config.toml file. + Init(init::Args), + + /// Run the Nym Api with provided configuration optionally overriding set parameters + Run(run::Args), + + /// Show build information of this binary + BuildInfo(build_info::Args), } diff --git a/nym-api/src/support/cli/run.rs b/nym-api/src/support/cli/run.rs new file mode 100644 index 0000000000..549438d095 --- /dev/null +++ b/nym-api/src/support/cli/run.rs @@ -0,0 +1,83 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::start_nym_api_tasks; +use crate::support::config::helpers::try_load_current_config; +use anyhow::bail; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + /// Id of the nym-api we want to run.if unspecified, a default value will be used. + /// default: "default" + #[clap(long, default_value = "default")] + pub(crate) id: String, + + /// Specifies whether network monitoring is enabled on this API + /// default: None - config value will be used instead + #[clap(short = 'm', long)] + pub(crate) enable_monitor: Option, + + /// Specifies whether network rewarding is enabled on this API + /// default: None - config value will be used instead + #[clap(short = 'r', long, requires = "enable_monitor", requires = "mnemonic")] + pub(crate) enable_rewarding: Option, + + /// Endpoint to nyxd instance used for contract information. + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) nyxd_validator: Option, + + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) mnemonic: Option, + + /// Flag to indicate whether coconut signer authority is enabled on this API + /// default: None - config value will be used instead + #[clap( + long, + requires = "mnemonic", + requires = "announce_address", + alias = "enable_coconut" + )] + pub(crate) enable_zk_nym: Option, + + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + /// default: None - config value will be used instead + #[clap(long)] + pub(crate) monitor_credentials_mode: Option, + // /// Ephemera configuration arguments. + // #[command(flatten)] + // pub(crate) ephemera_args: ephemera::cli::init::Cmd, +} + +pub(crate) async fn execute(args: Args) -> anyhow::Result<()> { + // args take precedence over env + let config = try_load_current_config(&args.id)? + .override_with_env() + .override_with_args(args); + + config.validate()?; + + let shutdown_handlers = start_nym_api_tasks(config).await?; + + let res = shutdown_handlers + .task_manager_handle + .catch_interrupt() + .await; + log::info!("Stopping nym API"); + shutdown_handlers.rocket_handle.notify(); + + if let Err(err) = res { + // that's a nasty workaround, but anyhow errors are generally nicer, especially on exit + bail!("{err}") + } + + Ok(()) +} diff --git a/nym-api/src/support/config/helpers.rs b/nym-api/src/support/config/helpers.rs index 1c84a5e837..47b9d25557 100644 --- a/nym-api/src/support/config/helpers.rs +++ b/nym-api/src/support/config/helpers.rs @@ -1,42 +1,26 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only -use crate::support::config::old_config_v1_1_21::ConfigV1_1_21; -use crate::support::config::old_config_v1_1_27::ConfigV1_1_27; -use crate::support::config::{default_config_directory, default_data_directory, Config}; -use anyhow::Result; +use crate::support::config; +use crate::support::config::{ + default_config_directory, default_data_directory, upgrade_helpers, Config, +}; +use anyhow::{Context, Result}; +use nym_crypto::asymmetric::identity; +use rand_07::rngs::OsRng; use std::{fs, io}; -fn try_upgrade_v1_1_21_config(id: &str) -> Result<()> { - use nym_config::legacy_helpers::nym_config::MigrationNymConfig; +fn init_identity_keys(config: &config::NymApiPaths) -> Result<()> { + let keypaths = nym_pemstore::KeyPairPath::new( + &config.private_identity_key_file, + &config.public_identity_key_file, + ); - // explicitly load it as v1.1.21 (which is incompatible with the current, i.e. 1.1.22+) - let Ok(old_config) = ConfigV1_1_21::load_from_file(id) else { - // if we failed to load it, there might have been nothing to upgrade - // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); - }; - info!("It seems the nym-api is using <= v1.1.21 config template."); - info!("It is going to get updated to the current specification."); - - let updated: Config = old_config.into(); - Ok(updated.save_to_default_location()?) -} - -fn try_upgrade_v1_1_27_config(id: &str) -> Result<()> { - use nym_config::legacy_helpers::nym_config::MigrationNymConfig; - - // explicitly load it as v1.1.27 (which is incompatible with the current, i.e. 1.1.28+) - let Ok(old_config) = ConfigV1_1_27::load_from_file(id) else { - // if we failed to load it, there might have been nothing to upgrade - // or maybe it was an even older file. in either way. just ignore it and carry on with our day - return Ok(()); - }; - info!("It seems the nym-api is using <= v1.1.27 config template."); - info!("It is going to get updated to the current specification."); - - let updated: Config = old_config.into(); - Ok(updated.save_to_default_location()?) + let mut rng = OsRng; + let keypair = identity::KeyPair::new(&mut rng); + nym_pemstore::store_keypair(&keypair, &keypaths) + .context("failed to store identity keys of the nym api")?; + Ok(()) } fn init_paths(id: &str) -> io::Result<()> { @@ -46,14 +30,21 @@ fn init_paths(id: &str) -> io::Result<()> { pub(crate) fn initialise_new(id: &str) -> Result { let config = Config::new(id); + + // create base storage paths init_paths(id)?; + + // create identity keys + init_identity_keys(&config.base.storage_paths)?; + + // create DKG BTE keys crate::coconut::dkg::controller::init_keypair(&config.coconut_signer)?; Ok(config) } pub(crate) fn try_load_current_config(id: &str) -> Result { - try_upgrade_v1_1_21_config(id)?; - try_upgrade_v1_1_27_config(id)?; - - Ok(Config::read_from_default_path(id)?) + upgrade_helpers::try_upgrade_config(id)?; + Config::read_from_default_path(id).context( + "failed to load config.toml from the default path - are you sure you run `init` before?", + ) } diff --git a/nym-api/src/support/config/mod.rs b/nym-api/src/support/config/mod.rs index 43eb0ddf8a..8d71b43fd6 100644 --- a/nym-api/src/support/config/mod.rs +++ b/nym-api/src/support/config/mod.rs @@ -1,16 +1,19 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::config::persistence::{ - CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, + CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, NymApiPaths, }; +use crate::support::config::r#override::OverrideConfig; use crate::support::config::template::CONFIG_TEMPLATE; -use nym_config::defaults::{mainnet, NymNetworkDetails}; +use anyhow::bail; +use nym_config::defaults::mainnet::read_parsed_var_if_not_default; +use nym_config::defaults::var_names::{CONFIGURED, NYXD}; +use nym_config::serde_helpers::de_maybe_stringified; use nym_config::{ must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, DEFAULT_NYM_APIS_DIR, NYM_DIR, }; -use nym_validator_client::nyxd; use serde::{Deserialize, Serialize}; use std::io; use std::path::{Path, PathBuf}; @@ -19,13 +22,13 @@ use url::Url; use zeroize::{Zeroize, ZeroizeOnDrop}; pub(crate) mod helpers; -pub(crate) mod old_config_v1_1_21; -pub(crate) mod old_config_v1_1_27; + +mod r#override; mod persistence; mod template; +mod upgrade_helpers; pub const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; -pub const DEFAULT_NYM_API_PORT: u16 = 8080; pub const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); @@ -85,6 +88,10 @@ pub fn default_data_directory>(id: P) -> PathBuf { #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] pub struct Config { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + pub base: Base, // TODO: perhaps introduce separate 'path finder' field for all the paths and directories like we have with other configs @@ -103,16 +110,6 @@ pub struct Config { pub ephemera: Ephemera, } -impl<'a> From<&'a Config> for NymNetworkDetails { - fn from(value: &'a Config) -> Self { - // we get the current environmental details and then overwrite whatever is appropriate with - // the values from the config - NymNetworkDetails::new_from_env() - .with_mixnet_contract(Some(value.get_mixnet_contract_address().as_ref())) - .with_vesting_contract(Some(value.get_vesting_contract_address().as_ref())) - } -} - impl NymConfigTemplate for Config { fn template(&self) -> &'static str { CONFIG_TEMPLATE @@ -122,6 +119,7 @@ impl NymConfigTemplate for Config { impl Config { pub fn new>(id: S) -> Self { Config { + save_path: None, base: Base::new_default(id.as_ref()), network_monitor: NetworkMonitor::new_default(id.as_ref()), node_status_api: NodeStatusAPI::new_default(id.as_ref()), @@ -133,12 +131,78 @@ impl Config { } } + pub fn validate(&self) -> anyhow::Result<()> { + let can_sign = self.base.mnemonic.is_some(); + + if !can_sign && self.rewarding.enabled { + bail!("can't enable rewarding without providing a mnemonic") + } + + if !can_sign && self.coconut_signer.enabled { + bail!("can't enable coconut signer without providing a mnemonic") + } + + if !can_sign && self.ephemera.enabled { + bail!("can't enable ephemera without providing a mnemonic") + } + + Ok(()) + } + + pub fn override_with_args>(mut self, args: O) -> Self { + let args = args.into(); + + if let Some(enabled_monitor) = args.enable_monitor { + self.network_monitor.enabled = enabled_monitor; + } + if let Some(enable_rewarding) = args.enable_rewarding { + self.rewarding.enabled = enable_rewarding; + } + if let Some(nyxd_upstream) = args.nyxd_validator { + self.base.local_validator = nyxd_upstream; + } + if let Some(mnemonic) = args.mnemonic { + self.base.mnemonic = Some(mnemonic) + } + if let Some(enable_zk_nym) = args.enable_zk_nym { + self.coconut_signer.enabled = enable_zk_nym + } + if let Some(announce_address) = args.announce_address { + self.coconut_signer.announce_address = Some(announce_address) + } + if let Some(monitor_credentials_mode) = args.monitor_credentials_mode { + self.network_monitor.debug.disabled_credentials_mode = !monitor_credentials_mode + } + + self + } + + pub fn override_with_env(mut self) -> Self { + if std::env::var(CONFIGURED).is_ok() { + // currently the only value that can be overridden is 'nyxd' + if let Some(Ok(custom_nyxd)) = read_parsed_var_if_not_default(NYXD) { + self.base.local_validator = custom_nyxd + } + } + self + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> io::Result { + let path = path.as_ref(); + let mut loaded: Config = read_config_from_toml_file(path)?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + #[allow(dead_code)] pub fn read_from_toml_file>(path: P) -> io::Result { - read_config_from_toml_file(path) + Self::read_from_path(path) } pub fn read_from_default_path>(id: P) -> io::Result { - Self::read_from_toml_file(default_config_filepath(id)) + Self::read_from_path(default_config_filepath(id)) } pub fn default_location(&self) -> PathBuf { @@ -150,93 +214,13 @@ impl Config { save_formatted_config_to_file(self, config_save_location) } - pub fn get_network_details(&self) -> NymNetworkDetails { - self.into() - } - - pub fn with_network_monitor_enabled(mut self, enabled: bool) -> Self { - self.network_monitor.enabled = enabled; - self - } - - pub fn with_disabled_credentials_mode(mut self, disabled_credentials_mode: bool) -> Self { - self.network_monitor.debug.disabled_credentials_mode = disabled_credentials_mode; - self - } - - pub fn with_rewarding_enabled(mut self, enabled: bool) -> Self { - self.rewarding.enabled = enabled; - self - } - - pub fn with_coconut_signer_enabled(mut self, enabled: bool) -> Self { - self.coconut_signer.enabled = enabled; - self - } - - pub fn with_ephemera_enabled(mut self, enabled: bool) -> Self { - self.ephemera.enabled = enabled; - self - } - - pub fn with_custom_nyxd_validator(mut self, validator: Url) -> Self { - self.base.local_validator = validator; - self - } - - pub fn with_announce_address(mut self, announce_address: Url) -> Self { - self.coconut_signer.announce_address = announce_address; - self - } - - pub fn with_custom_mixnet_contract(mut self, mixnet_contract: nyxd::AccountId) -> Self { - self.base.mixnet_contract_address = mixnet_contract; - self - } - - pub fn with_custom_vesting_contract(mut self, vesting_contract: nyxd::AccountId) -> Self { - self.base.vesting_contract_address = vesting_contract; - self - } - - pub fn with_mnemonic(mut self, mnemonic: bip39::Mnemonic) -> Self { - self.base.mnemonic = mnemonic; - self - } - - pub fn with_minimum_interval_monitor_threshold(mut self, threshold: u8) -> Self { - self.rewarding.debug.minimum_interval_monitor_threshold = threshold; - self - } - - pub fn with_min_mixnode_reliability(mut self, min_mixnode_reliability: u8) -> Self { - self.network_monitor.debug.min_mixnode_reliability = min_mixnode_reliability; - self - } - - pub fn with_min_gateway_reliability(mut self, min_gateway_reliability: u8) -> Self { - self.network_monitor.debug.min_gateway_reliability = min_gateway_reliability; - self - } - - pub fn with_ephemera_ip(mut self, ip: String) -> Self { - self.ephemera.args.cmd.ephemera_ip = Some(ip); - self - } - - pub fn with_ephemera_protocol_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_protocol_port = Some(port); - self - } - - pub fn with_ephemera_websocket_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_websocket_port = Some(port); - self - } - - pub fn with_ephemera_http_api_port(mut self, port: u16) -> Self { - self.ephemera.args.cmd.ephemera_http_api_port = Some(port); - self + pub fn try_save(&self) -> io::Result<()> { + if let Some(save_location) = &self.save_path { + save_formatted_config_to_file(self, save_location) + } else { + debug!("config file save location is unknown. falling back to the default"); + self.save_to_default_location() + } } pub fn get_id(&self) -> String { @@ -247,16 +231,8 @@ impl Config { self.base.local_validator.clone() } - pub fn get_mixnet_contract_address(&self) -> nyxd::AccountId { - self.base.mixnet_contract_address.clone() - } - - pub fn get_vesting_contract_address(&self) -> nyxd::AccountId { - self.base.vesting_contract_address.clone() - } - - pub fn get_mnemonic(&self) -> bip39::Mnemonic { - self.base.mnemonic.clone() + pub fn get_mnemonic(&self) -> Option<&bip39::Mnemonic> { + self.base.mnemonic.as_ref() } pub fn get_ephemera_args(&self) -> &crate::ephemera::Args { @@ -272,22 +248,19 @@ impl Config { #[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Zeroize, ZeroizeOnDrop)] pub struct Base { /// ID specifies the human readable ID of this particular nym-api. - id: String, + pub id: String, #[zeroize(skip)] - local_validator: Url, - - /// Address of the validator contract managing the network - #[zeroize(skip)] - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - #[zeroize(skip)] - vesting_contract_address: nyxd::AccountId, + pub local_validator: Url, /// Mnemonic used for rewarding and/or multisig operations // TODO: similarly to the note in gateway, this should get moved to a separate file - mnemonic: bip39::Mnemonic, + #[serde(deserialize_with = "de_maybe_stringified")] + mnemonic: Option, + + /// Storage paths to the common nym-api files + #[zeroize(skip)] + pub storage_paths: NymApiPaths, } impl Base { @@ -296,13 +269,13 @@ impl Base { .parse() .expect("default local validator is malformed!"); + let id = id.into(); + Base { - id: id.into(), + storage_paths: NymApiPaths::new_default(&id), + id, local_validator: default_validator, - mixnet_contract_address: mainnet::MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: mainnet::VESTING_CONTRACT_ADDRESS.parse().unwrap(), - // this this doesn't make any sense since you really have a mnemonic beforehand... - mnemonic: bip39::Mnemonic::generate(24).unwrap(), + mnemonic: None, } } } @@ -550,7 +523,8 @@ pub struct CoconutSigner { /// Specifies whether rewarding service is enabled in this process. pub enabled: bool, - pub announce_address: Url, + #[serde(deserialize_with = "de_maybe_stringified")] + pub announce_address: Option, pub storage_paths: CoconutSignerPaths, @@ -560,17 +534,9 @@ pub struct CoconutSigner { impl CoconutSigner { pub fn new_default>(id: P) -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator; - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - CoconutSigner { enabled: false, - announce_address: default_announce_address, + announce_address: None, storage_paths: CoconutSignerPaths::new_default(id), debug: Default::default(), } diff --git a/nym-api/src/support/config/old_config_v1_1_21.rs b/nym-api/src/support/config/old_config_v1_1_21.rs deleted file mode 100644 index be8c06d7c8..0000000000 --- a/nym-api/src/support/config/old_config_v1_1_21.rs +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::support::config::persistence::{ - CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, -}; -use crate::support::config::{ - Base, CirculatingSupplyCacher, CirculatingSupplyCacherDebug, CoconutSigner, CoconutSignerDebug, - Config, NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, - RewardingDebug, TopologyCacher, TopologyCacherDebug, -}; -use nym_config::legacy_helpers::nym_config::MigrationNymConfig; -use nym_validator_client::nyxd; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use std::time::Duration; -use url::Url; - -const DEFAULT_NYM_API_PORT: u16 = 8080; -const MIXNET_CONTRACT_ADDRESS: &str = - "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; -const VESTING_CONTRACT_ADDRESS: &str = - "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; - -const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; - -const DEFAULT_DKG_CONTRACT_POLLING_RATE: Duration = Duration::from_secs(10); - -const DEFAULT_GATEWAY_SENDING_RATE: usize = 200; -const DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS: usize = 50; -const DEFAULT_PACKET_DELIVERY_TIMEOUT: Duration = Duration::from_secs(20); -const DEFAULT_MONITOR_RUN_INTERVAL: Duration = Duration::from_secs(15 * 60); -const DEFAULT_GATEWAY_PING_INTERVAL: Duration = Duration::from_secs(60); -// Set this to a high value for now, so that we don't risk sporadic timeouts that might cause -// bought bandwidth tokens to not have time to be spent; Once we remove the gateway from the -// bandwidth bridging protocol, we can come back to a smaller timeout value -const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_secs(5 * 60); -// This timeout value should be big enough to accommodate an initial bandwidth acquirement -const DEFAULT_GATEWAY_CONNECTION_TIMEOUT: Duration = Duration::from_secs(2 * 60); - -const DEFAULT_TEST_ROUTES: usize = 3; -const DEFAULT_MINIMUM_TEST_ROUTES: usize = 1; -const DEFAULT_ROUTE_TEST_PACKETS: usize = 1000; -const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3; - -const DEFAULT_TOPOLOGY_CACHE_INTERVAL: Duration = Duration::from_secs(30); -const DEFAULT_NODE_STATUS_CACHE_INTERVAL: Duration = Duration::from_secs(120); -const DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL: Duration = Duration::from_secs(3600); -const DEFAULT_MONITOR_THRESHOLD: u8 = 60; -const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50; -const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20; - -#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ConfigV1_1_21 { - #[serde(default)] - base: BaseV1_1_21, - - #[serde(default)] - network_monitor: NetworkMonitorV1_1_21, - - #[serde(default)] - node_status_api: NodeStatusAPIV1_1_21, - - #[serde(default)] - topology_cacher: TopologyCacherV1_1_21, - - #[serde(default)] - circulating_supply_cacher: CirculatingSupplyCacherV1_1_21, - - #[serde(default)] - rewarding: RewardingV1_1_21, - - #[serde(default)] - coconut_signer: CoconutSignerV1_1_21, -} - -impl From for Config { - fn from(value: ConfigV1_1_21) -> Self { - // this value was never properly saved (probably a bug) - // so explicitly set it to the default - - let dkg_persistent_state_path = - CoconutSignerV1_1_21::default_dkg_persistent_state_path(&value.base.id); - - Config { - base: Base { - id: value.base.id, - local_validator: value.base.local_validator, - mixnet_contract_address: value.base.mixnet_contract_address, - vesting_contract_address: value.base.vesting_contract_address, - mnemonic: value.base.mnemonic, - }, - network_monitor: NetworkMonitor { - enabled: value.network_monitor.enabled, - storage_paths: NetworkMonitorPaths { - credentials_database_path: value.network_monitor.credentials_database_path, - }, - debug: NetworkMonitorDebug { - min_mixnode_reliability: value.network_monitor.min_mixnode_reliability, - min_gateway_reliability: value.network_monitor.min_gateway_reliability, - disabled_credentials_mode: value.network_monitor.disabled_credentials_mode, - run_interval: value.network_monitor.run_interval, - gateway_ping_interval: value.network_monitor.gateway_ping_interval, - gateway_sending_rate: value.network_monitor.gateway_sending_rate, - max_concurrent_gateway_clients: value - .network_monitor - .max_concurrent_gateway_clients, - gateway_response_timeout: value.network_monitor.gateway_response_timeout, - gateway_connection_timeout: value.network_monitor.gateway_connection_timeout, - packet_delivery_timeout: value.network_monitor.packet_delivery_timeout, - test_routes: value.network_monitor.test_routes, - minimum_test_routes: value.network_monitor.minimum_test_routes, - route_test_packets: value.network_monitor.route_test_packets, - per_node_test_packets: value.network_monitor.per_node_test_packets, - }, - }, - node_status_api: NodeStatusAPI { - storage_paths: NodeStatusAPIPaths { - database_path: value.node_status_api.database_path, - }, - debug: NodeStatusAPIDebug { - caching_interval: value.node_status_api.caching_interval, - }, - }, - topology_cacher: TopologyCacher { - debug: TopologyCacherDebug { - caching_interval: value.topology_cacher.caching_interval, - ..Default::default() - }, - }, - circulating_supply_cacher: CirculatingSupplyCacher { - enabled: value.circulating_supply_cacher.enabled, - debug: CirculatingSupplyCacherDebug { - caching_interval: value.circulating_supply_cacher.caching_interval, - }, - }, - rewarding: Rewarding { - enabled: value.rewarding.enabled, - debug: RewardingDebug { - minimum_interval_monitor_threshold: value - .rewarding - .minimum_interval_monitor_threshold, - }, - }, - coconut_signer: CoconutSigner { - enabled: value.coconut_signer.enabled, - announce_address: value.base.announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path, - verification_key_path: value.coconut_signer.verification_key_path, - secret_key_path: value.coconut_signer.secret_key_path, - decryption_key_path: value.coconut_signer.decryption_key_path, - public_key_with_proof_path: value.coconut_signer.public_key_with_proof_path, - }, - debug: CoconutSignerDebug { - dkg_contract_polling_rate: value.coconut_signer.dkg_contract_polling_rate, - }, - }, - ephemera: Default::default(), - } - } -} - -impl MigrationNymConfig for ConfigV1_1_21 { - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("nym-api") - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct BaseV1_1_21 { - /// ID specifies the human readable ID of this particular nym-api. - id: String, - - local_validator: Url, - - /// Address announced to the directory server for the clients to connect to. - // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - // later on by using name resolvable with a DNS query, such as `nymtech.net`. - announce_address: Url, - - /// Address of the validator contract managing the network - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - vesting_contract_address: nyxd::AccountId, - - /// Mnemonic used for rewarding and/or multisig operations - mnemonic: bip39::Mnemonic, -} - -impl Default for BaseV1_1_21 { - fn default() -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator.clone(); - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - - BaseV1_1_21 { - id: String::default(), - local_validator: default_validator, - announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), - mnemonic: bip39::Mnemonic::generate(24).unwrap(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NetworkMonitorV1_1_21 { - min_mixnode_reliability: u8, // defaults to 50 - min_gateway_reliability: u8, // defaults to 20 - enabled: bool, - #[serde(default)] - disabled_credentials_mode: bool, - #[serde(with = "humantime_serde")] - run_interval: Duration, - #[serde(with = "humantime_serde")] - gateway_ping_interval: Duration, - gateway_sending_rate: usize, - max_concurrent_gateway_clients: usize, - #[serde(with = "humantime_serde")] - gateway_response_timeout: Duration, - #[serde(with = "humantime_serde")] - gateway_connection_timeout: Duration, - #[serde(with = "humantime_serde")] - packet_delivery_timeout: Duration, - credentials_database_path: PathBuf, - test_routes: usize, - minimum_test_routes: usize, - route_test_packets: usize, - per_node_test_packets: usize, -} - -impl Default for NetworkMonitorV1_1_21 { - fn default() -> Self { - NetworkMonitorV1_1_21 { - min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY, - min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY, - enabled: false, - disabled_credentials_mode: true, - run_interval: DEFAULT_MONITOR_RUN_INTERVAL, - gateway_ping_interval: DEFAULT_GATEWAY_PING_INTERVAL, - gateway_sending_rate: DEFAULT_GATEWAY_SENDING_RATE, - max_concurrent_gateway_clients: DEFAULT_MAX_CONCURRENT_GATEWAY_CLIENTS, - gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT, - gateway_connection_timeout: DEFAULT_GATEWAY_CONNECTION_TIMEOUT, - packet_delivery_timeout: DEFAULT_PACKET_DELIVERY_TIMEOUT, - credentials_database_path: Default::default(), - test_routes: DEFAULT_TEST_ROUTES, - minimum_test_routes: DEFAULT_MINIMUM_TEST_ROUTES, - route_test_packets: DEFAULT_ROUTE_TEST_PACKETS, - per_node_test_packets: DEFAULT_PER_NODE_TEST_PACKETS, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NodeStatusAPIV1_1_21 { - database_path: PathBuf, - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for NodeStatusAPIV1_1_21 { - fn default() -> Self { - NodeStatusAPIV1_1_21 { - database_path: Default::default(), - caching_interval: DEFAULT_NODE_STATUS_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct TopologyCacherV1_1_21 { - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for TopologyCacherV1_1_21 { - fn default() -> Self { - TopologyCacherV1_1_21 { - caching_interval: DEFAULT_TOPOLOGY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CirculatingSupplyCacherV1_1_21 { - enabled: bool, - - #[serde(with = "humantime_serde")] - caching_interval: Duration, -} - -impl Default for CirculatingSupplyCacherV1_1_21 { - fn default() -> Self { - CirculatingSupplyCacherV1_1_21 { - enabled: true, - caching_interval: DEFAULT_CIRCULATING_SUPPLY_CACHE_INTERVAL, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct RewardingV1_1_21 { - enabled: bool, - minimum_interval_monitor_threshold: u8, -} - -impl Default for RewardingV1_1_21 { - fn default() -> Self { - RewardingV1_1_21 { - enabled: false, - minimum_interval_monitor_threshold: DEFAULT_MONITOR_THRESHOLD, - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CoconutSignerV1_1_21 { - enabled: bool, - dkg_persistent_state_path: PathBuf, - verification_key_path: PathBuf, - secret_key_path: PathBuf, - decryption_key_path: PathBuf, - public_key_with_proof_path: PathBuf, - dkg_contract_polling_rate: Duration, -} - -impl CoconutSignerV1_1_21 { - pub const DKG_PERSISTENT_STATE_FILE: &'static str = "dkg_persistent_state.json"; - - fn default_dkg_persistent_state_path(id: &str) -> PathBuf { - ConfigV1_1_21::default_data_directory(id).join(Self::DKG_PERSISTENT_STATE_FILE) - } -} - -impl Default for CoconutSignerV1_1_21 { - fn default() -> Self { - Self { - enabled: Default::default(), - dkg_persistent_state_path: Default::default(), - verification_key_path: Default::default(), - secret_key_path: Default::default(), - decryption_key_path: Default::default(), - public_key_with_proof_path: Default::default(), - dkg_contract_polling_rate: DEFAULT_DKG_CONTRACT_POLLING_RATE, - } - } -} diff --git a/nym-api/src/support/config/old_config_v1_1_27.rs b/nym-api/src/support/config/old_config_v1_1_27.rs deleted file mode 100644 index 3bfb4b8581..0000000000 --- a/nym-api/src/support/config/old_config_v1_1_27.rs +++ /dev/null @@ -1,268 +0,0 @@ -// Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 - -use crate::support::config::persistence::{ - CoconutSignerPaths, NetworkMonitorPaths, NodeStatusAPIPaths, -}; -use crate::support::config::{ - Base, CirculatingSupplyCacher, CoconutSigner, CoconutSignerDebug, Config, Ephemera, - NetworkMonitor, NetworkMonitorDebug, NodeStatusAPI, NodeStatusAPIDebug, Rewarding, - TopologyCacher, -}; -use nym_config::legacy_helpers::nym_config::MigrationNymConfig; -use nym_validator_client::nyxd; -use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -use url::Url; - -const DEFAULT_NYM_API_PORT: u16 = 8080; -const MIXNET_CONTRACT_ADDRESS: &str = - "n17srjznxl9dvzdkpwpw24gg668wc73val88a6m5ajg6ankwvz9wtst0cznr"; -const VESTING_CONTRACT_ADDRESS: &str = - "n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw"; - -const DEFAULT_LOCAL_VALIDATOR: &str = "http://localhost:26657"; - -#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] -#[serde(deny_unknown_fields)] -pub struct ConfigV1_1_27 { - #[serde(default)] - base: BaseV1_1_27, - - #[serde(default)] - network_monitor: NetworkMonitorV1_1_27, - - #[serde(default)] - node_status_api: NodeStatusAPIV1_1_27, - - #[serde(default)] - topology_cacher: TopologyCacher, - - #[serde(default)] - circulating_supply_cacher: CirculatingSupplyCacher, - - #[serde(default)] - rewarding: Rewarding, - - #[serde(default)] - coconut_signer: CoconutSignerV1_1_27, -} - -impl From for Config { - fn from(value: ConfigV1_1_27) -> Self { - // this value was never properly saved (probably a bug) - // so explicitly set it to the default - - Config { - base: Base { - id: value.base.id.clone(), - local_validator: value.base.local_validator, - mixnet_contract_address: value.base.mixnet_contract_address, - vesting_contract_address: value.base.vesting_contract_address, - mnemonic: value.base.mnemonic, - }, - network_monitor: NetworkMonitor { - enabled: value.network_monitor.enabled, - storage_paths: NetworkMonitorPaths { - credentials_database_path: value - .network_monitor - .storage_paths - .credentials_database_path, - }, - debug: NetworkMonitorDebug { - min_mixnode_reliability: value.network_monitor.debug.min_mixnode_reliability, - min_gateway_reliability: value.network_monitor.debug.min_gateway_reliability, - disabled_credentials_mode: value - .network_monitor - .debug - .disabled_credentials_mode, - run_interval: value.network_monitor.debug.run_interval, - gateway_ping_interval: value.network_monitor.debug.gateway_ping_interval, - gateway_sending_rate: value.network_monitor.debug.gateway_sending_rate, - max_concurrent_gateway_clients: value - .network_monitor - .debug - .max_concurrent_gateway_clients, - gateway_response_timeout: value.network_monitor.debug.gateway_response_timeout, - gateway_connection_timeout: value - .network_monitor - .debug - .gateway_connection_timeout, - packet_delivery_timeout: value.network_monitor.debug.packet_delivery_timeout, - test_routes: value.network_monitor.debug.test_routes, - minimum_test_routes: value.network_monitor.debug.minimum_test_routes, - route_test_packets: value.network_monitor.debug.route_test_packets, - per_node_test_packets: value.network_monitor.debug.per_node_test_packets, - }, - }, - node_status_api: NodeStatusAPI { - storage_paths: NodeStatusAPIPaths { - database_path: value.node_status_api.storage_paths.database_path, - }, - debug: NodeStatusAPIDebug { - caching_interval: value.node_status_api.debug.caching_interval, - }, - }, - topology_cacher: value.topology_cacher, - circulating_supply_cacher: value.circulating_supply_cacher, - rewarding: value.rewarding, - coconut_signer: CoconutSigner { - enabled: value.coconut_signer.enabled, - announce_address: value.base.announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path: value - .coconut_signer - .storage_paths - .dkg_persistent_state_path, - verification_key_path: value.coconut_signer.storage_paths.verification_key_path, - secret_key_path: value.coconut_signer.storage_paths.secret_key_path, - decryption_key_path: value.coconut_signer.storage_paths.decryption_key_path, - public_key_with_proof_path: value - .coconut_signer - .storage_paths - .public_key_with_proof_path, - }, - debug: CoconutSignerDebug { - dkg_contract_polling_rate: value.coconut_signer.debug.dkg_contract_polling_rate, - }, - }, - ephemera: Ephemera::new_default(&value.base.id), - } - } -} - -impl MigrationNymConfig for ConfigV1_1_27 { - fn default_root_directory() -> PathBuf { - dirs::home_dir() - .expect("Failed to evaluate $HOME value") - .join(".nym") - .join("nym-api") - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct BaseV1_1_27 { - /// ID specifies the human readable ID of this particular nym-api. - id: String, - - local_validator: Url, - - /// Address announced to the directory server for the clients to connect to. - // It is useful, say, in NAT scenarios or wanting to more easily update actual IP address - // later on by using name resolvable with a DNS query, such as `nymtech.net`. - announce_address: Url, - - /// Address of the validator contract managing the network - mixnet_contract_address: nyxd::AccountId, - - /// Address of the vesting contract holding locked tokens - vesting_contract_address: nyxd::AccountId, - - /// Mnemonic used for rewarding and/or multisig operations - mnemonic: bip39::Mnemonic, -} - -impl Default for BaseV1_1_27 { - fn default() -> Self { - let default_validator: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - let mut default_announce_address = default_validator.clone(); - default_announce_address - .set_port(Some(DEFAULT_NYM_API_PORT)) - .expect("default local validator is malformed!"); - - BaseV1_1_27 { - id: String::default(), - local_validator: default_validator, - announce_address: default_announce_address, - mixnet_contract_address: MIXNET_CONTRACT_ADDRESS.parse().unwrap(), - vesting_contract_address: VESTING_CONTRACT_ADDRESS.parse().unwrap(), - mnemonic: bip39::Mnemonic::generate(24).unwrap(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NetworkMonitorV1_1_27 { - /// Specifies whether network monitoring service is enabled in this process. - pub enabled: bool, - - pub storage_paths: NetworkMonitorPaths, - - #[serde(default)] - pub debug: NetworkMonitorDebug, -} - -impl Default for NetworkMonitorV1_1_27 { - fn default() -> Self { - NetworkMonitorV1_1_27 { - enabled: false, - storage_paths: NetworkMonitorPaths { - credentials_database_path: Default::default(), - }, - debug: Default::default(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct NodeStatusAPIV1_1_27 { - // pub enabled: bool, - pub storage_paths: NodeStatusAPIPaths, - - #[serde(default)] - pub debug: NodeStatusAPIDebug, -} - -impl Default for NodeStatusAPIV1_1_27 { - fn default() -> Self { - NodeStatusAPIV1_1_27 { - storage_paths: NodeStatusAPIPaths { - database_path: Default::default(), - }, - debug: Default::default(), - } - } -} - -#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] -#[serde(default)] -#[serde(deny_unknown_fields)] -pub struct CoconutSignerV1_1_27 { - /// Specifies whether rewarding service is enabled in this process. - pub enabled: bool, - - pub announce_address: Url, - - pub storage_paths: CoconutSignerPaths, - - #[serde(default)] - pub debug: CoconutSignerDebug, -} - -impl Default for CoconutSignerV1_1_27 { - fn default() -> Self { - let announce_address: Url = DEFAULT_LOCAL_VALIDATOR - .parse() - .expect("default local validator is malformed!"); - CoconutSignerV1_1_27 { - enabled: Default::default(), - announce_address, - storage_paths: CoconutSignerPaths { - dkg_persistent_state_path: Default::default(), - verification_key_path: Default::default(), - secret_key_path: Default::default(), - decryption_key_path: Default::default(), - public_key_with_proof_path: Default::default(), - }, - debug: Default::default(), - } - } -} diff --git a/nym-api/src/support/config/override.rs b/nym-api/src/support/config/override.rs new file mode 100644 index 0000000000..dea892d2a9 --- /dev/null +++ b/nym-api/src/support/config/override.rs @@ -0,0 +1,57 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::support::cli::{init, run}; + +// Configuration that can be overridden. +pub(crate) struct OverrideConfig { + /// Specifies whether network monitoring is enabled on this API + pub(crate) enable_monitor: Option, + + /// Specifies whether network rewarding is enabled on this API + pub(crate) enable_rewarding: Option, + + /// Endpoint to nyxd instance used for contract information. + pub(crate) nyxd_validator: Option, + + /// Mnemonic of the network monitor used for sending rewarding and zk-nyms transactions + pub(crate) mnemonic: Option, + + /// Flag to indicate whether coconut signer authority is enabled on this API + pub(crate) enable_zk_nym: Option, + + /// Announced address that is going to be put in the DKG contract where zk-nym clients will connect + /// to obtain their credentials + pub(crate) announce_address: Option, + + /// Set this nym api to work in a enabled credentials that would attempt to use gateway with the bandwidth credential requirement + pub(crate) monitor_credentials_mode: Option, +} + +impl From for OverrideConfig { + fn from(args: init::Args) -> Self { + OverrideConfig { + enable_monitor: Some(args.enable_monitor), + enable_rewarding: Some(args.enable_rewarding), + nyxd_validator: args.nyxd_validator, + mnemonic: args.mnemonic, + enable_zk_nym: Some(args.enable_zk_nym), + announce_address: args.announce_address, + monitor_credentials_mode: Some(args.monitor_credentials_mode), + } + } +} + +impl From for OverrideConfig { + fn from(args: run::Args) -> Self { + OverrideConfig { + enable_monitor: args.enable_monitor, + enable_rewarding: args.enable_rewarding, + nyxd_validator: args.nyxd_validator, + mnemonic: args.mnemonic, + enable_zk_nym: args.enable_zk_nym, + announce_address: args.announce_address, + monitor_credentials_mode: args.monitor_credentials_mode, + } + } +} diff --git a/nym-api/src/support/config/persistence.rs b/nym-api/src/support/config/persistence.rs index 9914705ae6..9a9b253776 100644 --- a/nym-api/src/support/config/persistence.rs +++ b/nym-api/src/support/config/persistence.rs @@ -1,7 +1,9 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::support::config::default_data_directory; +use anyhow::Context; +use nym_crypto::asymmetric::identity; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; @@ -15,6 +17,9 @@ pub const DEFAULT_DKG_PUBLIC_KEY_WITH_PROOF_FILENAME: &str = "dkg_public_key_wit pub const DEFAULT_COCONUT_VERIFICATION_KEY_FILENAME: &str = "coconut_verification_key.pem"; pub const DEFAULT_COCONUT_SECRET_KEY_FILENAME: &str = "coconut_secret_key.pem"; +pub const DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME: &str = "private_identity.pem"; +pub const DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME: &str = "public_identity.pem"; + // #[derive(Debug, Deserialize, PartialEq, Eq, Serialize)] // pub struct NymApiPathfinder { // pub network_monitor: NetworkMonitorPathfinder, @@ -99,3 +104,35 @@ impl CoconutSignerPaths { } } } + +#[derive(Debug, Default, Deserialize, PartialEq, Eq, Serialize)] +#[serde(default)] +pub struct NymApiPaths { + /// Path to file containing private identity key of the nym-api. + pub private_identity_key_file: PathBuf, + + /// Path to file containing public identity key of the nym-api. + pub public_identity_key_file: PathBuf, +} + +impl NymApiPaths { + pub fn new_default>(id: P) -> Self { + let data_dir = default_data_directory(id); + + NymApiPaths { + private_identity_key_file: data_dir.join(DEFAULT_PRIVATE_IDENTITY_KEY_FILENAME), + public_identity_key_file: data_dir.join(DEFAULT_PUBLIC_IDENTITY_KEY_FILENAME), + } + } + + pub fn load_identity(&self) -> anyhow::Result { + let keypaths = nym_pemstore::KeyPairPath::new( + &self.private_identity_key_file, + &self.public_identity_key_file, + ); + + nym_pemstore::load_keypair(&keypaths).context(format!( + "failed to load identity keys of the nym api. paths: {keypaths:?}" + )) + } +} diff --git a/nym-api/src/support/config/template.rs b/nym-api/src/support/config/template.rs index 32d4f5e087..a859d94ad1 100644 --- a/nym-api/src/support/config/template.rs +++ b/nym-api/src/support/config/template.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) const CONFIG_TEMPLATE: &str = r#" # This is a TOML config file. @@ -15,15 +15,15 @@ id = '{{ base.id }}' # Validator server to which the API will be getting information about the network. local_validator = '{{ base.local_validator }}' -# Address of the validator contract managing the network. -mixnet_contract_address = '{{ base.mixnet_contract_address }}' - -# Address of the vesting contract holding locked tokens -vesting_contract_address = '{{ base.vesting_contract_address }}' - # Mnemonic used for rewarding and validator interaction mnemonic = '{{ base.mnemonic }}' +[base.storage_paths] +# Path to file containing private identity key of the nym-api. +private_identity_key_file = '{{ base.storage_paths.private_identity_key_file }}' + +# Path to file containing public identity key of the nym-api. +public_identity_key_file = '{{ base.storage_paths.public_identity_key_file }}' ##### network monitor config options ##### diff --git a/nym-api/src/support/config/upgrade_helpers.rs b/nym-api/src/support/config/upgrade_helpers.rs new file mode 100644 index 0000000000..d68e923306 --- /dev/null +++ b/nym-api/src/support/config/upgrade_helpers.rs @@ -0,0 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +pub(crate) fn try_upgrade_config(_id: &str) -> anyhow::Result<()> { + Ok(()) +} diff --git a/nym-api/src/support/http/mod.rs b/nym-api/src/support/http/mod.rs index 5d0b646107..7f54a35f8e 100644 --- a/nym-api/src/support/http/mod.rs +++ b/nym-api/src/support/http/mod.rs @@ -1,8 +1,9 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::circulating_supply_api::cache::CirculatingSupplyCache; -use crate::coconut::{self, comm::QueryCommunicationChannel, InternalSignRequest}; +use crate::coconut::client::Client; +use crate::coconut::{self, comm::QueryCommunicationChannel}; use crate::network::models::NetworkDetails; use crate::network::network_routes; use crate::node_describe_cache::DescribedNodes; @@ -12,7 +13,9 @@ use crate::support::caching::cache::SharedCache; use crate::support::config::Config; use crate::support::{nyxd, storage}; use crate::{circulating_supply_api, nym_contract_cache, nym_nodes::nym_node_routes}; -use anyhow::Result; +use anyhow::{bail, Result}; +use nym_crypto::asymmetric::identity; +use nym_validator_client::nyxd::Coin; use rocket::http::Method; use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; @@ -25,6 +28,7 @@ pub(crate) async fn setup_rocket( config: &Config, network_details: NetworkDetails, _nyxd_client: nyxd::Client, + identity_keypair: identity::KeyPair, coconut_keypair: coconut::keypair::KeyPair, ) -> anyhow::Result> { let openapi_settings = rocket_okapi::settings::OpenApiSettings::default(); @@ -65,10 +69,19 @@ pub(crate) async fn setup_rocket( }; let rocket = if config.coconut_signer.enabled { + // make sure we have some tokens to cover multisig fees + let balance = _nyxd_client.balance(&mix_denom).await?; + if balance.amount < coconut::MINIMUM_BALANCE { + let address = _nyxd_client.address().await; + let min = Coin::new(coconut::MINIMUM_BALANCE, mix_denom); + bail!("the account ({address}) doesn't have enough funds to cover verification fees. it has {balance} while it needs at least {min}") + } + let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone()); - rocket.attach(InternalSignRequest::stage( + rocket.attach(coconut::stage( _nyxd_client.clone(), mix_denom, + identity_keypair, coconut_keypair, comm_channel, storage.clone().unwrap(), diff --git a/nym-api/src/support/http/openapi.rs b/nym-api/src/support/http/openapi.rs index bda05b0c7a..858d201dc7 100644 --- a/nym-api/src/support/http/openapi.rs +++ b/nym-api/src/support/http/openapi.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use okapi::openapi3::OpenApi; use rocket_okapi::swagger_ui::SwaggerUIConfig; diff --git a/nym-api/src/support/mod.rs b/nym-api/src/support/mod.rs index 322c3625b3..28abaf3b94 100644 --- a/nym-api/src/support/mod.rs +++ b/nym-api/src/support/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) mod caching; pub(crate) mod cli; diff --git a/nym-api/src/support/nyxd/mod.rs b/nym-api/src/support/nyxd/mod.rs index 790be3ca31..452f93569f 100644 --- a/nym-api/src/support/nyxd/mod.rs +++ b/nym-api/src/support/nyxd/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::coconut::error::CoconutError; use crate::epoch_operations::MixnodeWithPerformance; @@ -16,7 +16,7 @@ use nym_coconut_dkg_common::{ types::{EncodedBTEPublicKeyWithProof, Epoch, EpochId}, verification_key::{ContractVKShare, VerificationKeyShare}, }; -use nym_config::defaults::ChainDetails; +use nym_config::defaults::{ChainDetails, NymNetworkDetails}; use nym_contracts_common::dealings::ContractSafeBytes; use nym_ephemera_common::msg::QueryMsg as EphemeraQueryMsg; use nym_ephemera_common::types::JsonPeerInfo; @@ -46,61 +46,122 @@ use nym_validator_client::nyxd::{ hash::{Hash, SHA256_HASH_SIZE}, AccountId, Coin, TendermintTime, }; -use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient}; +use nym_validator_client::{nyxd, DirectSigningHttpRpcNyxdClient, QueryHttpRpcNyxdClient}; use nym_vesting_contract_common::AccountVestingCoins; use serde::Deserialize; use std::sync::Arc; use tokio::sync::{RwLock, RwLockReadGuard}; -pub(crate) struct Client(pub(crate) Arc>); +#[macro_export] +macro_rules! query_guard { + ($guard:expr, $($op:tt)*) => {{ + match &*$guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(client) => client.$($op)*, + } + }}; +} -impl Clone for Client { - fn clone(&self) -> Self { - Client(Arc::clone(&self.0)) - } +macro_rules! nyxd_query { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.read().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(client) => client.$($op)*, + } + }}; +} + +macro_rules! nyxd_signing_shared { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.read().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), + } + }}; +} + +macro_rules! nyxd_signing { + ($self:expr, $($op:tt)*) => {{ + let guard = $self.inner.write().await; + match &*guard { + $crate::support::nyxd::ClientInner::Signing(client) => client.$($op)*, + $crate::support::nyxd::ClientInner::Query(_) => panic!("attempted to use a signing method on a query client"), + } + }}; +} + +#[derive(Clone)] +pub(crate) struct Client { + inner: Arc>, +} + +pub enum ClientInner { + Signing(DirectSigningHttpRpcNyxdClient), + Query(QueryHttpRpcNyxdClient), } impl Client { pub(crate) fn new(config: &Config) -> Self { - let details = config.get_network_details(); + let details = NymNetworkDetails::new_from_env(); let nyxd_url = config.get_nyxd_url(); let client_config = nyxd::Config::try_from_nym_network_details(&details) .expect("failed to construct valid validator client config with the provided network"); - let mnemonic = config.get_mnemonic(); + let inner = if let Some(mnemonic) = config.get_mnemonic() { + ClientInner::Signing( + DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( + client_config, + nyxd_url.as_str(), + mnemonic.clone(), + ) + .expect("Failed to connect to nyxd!"), + ) + } else { + ClientInner::Query( + QueryHttpRpcNyxdClient::connect(client_config, nyxd_url.as_str()) + .expect("Failed to connect to nyxd!"), + ) + }; - let inner = DirectSigningHttpRpcNyxdClient::connect_with_mnemonic( - client_config, - nyxd_url.as_str(), - mnemonic, - ) - .expect("Failed to connect to nyxd!"); - - Client(Arc::new(RwLock::new(inner))) + Client { + inner: Arc::new(RwLock::new(inner)), + } } - pub(crate) async fn read(&self) -> RwLockReadGuard<'_, DirectSigningHttpRpcNyxdClient> { - self.0.read().await + pub(crate) async fn read(&self) -> RwLockReadGuard<'_, ClientInner> { + self.inner.read().await } pub(crate) async fn client_address(&self) -> AccountId { - self.0.read().await.address() + nyxd_signing_shared!(self, address()) + } + + pub(crate) async fn balance>(&self, denom: S) -> Result { + let address = self.client_address().await; + let denom = denom.into(); + let balance = nyxd_query!(self, get_balance(&address, denom.clone()).await?); + + match balance { + None => Ok(Coin::new(0, denom)), + Some(coin) => Ok(coin), + } } pub(crate) async fn chain_details(&self) -> ChainDetails { - self.0.read().await.current_chain_details().clone() + nyxd_query!(self, current_chain_details().clone()) } pub(crate) async fn get_rewarding_validator_address(&self) -> Result { - let cosmwasm_addr = self - .0 - .read() - .await - .get_mixnet_contract_state() - .await? - .rewarding_validator_address - .into_string(); + let cosmwasm_addr = nyxd_query!( + self, + get_mixnet_contract_state() + .await? + .rewarding_validator_address + .into_string() + ); // this should never fail otherwise it implies either // 1) our mixnet contract state is invalid @@ -116,7 +177,7 @@ impl Client { // a helper function for the future to obtain the current block timestamp #[allow(dead_code)] pub(crate) async fn current_block_timestamp(&self) -> Result { - let time = self.0.read().await.get_current_block_timestamp().await?; + let time = nyxd_query!(self, get_current_block_timestamp().await?); Ok(time) } @@ -132,7 +193,7 @@ impl Client { &self, height: u32, ) -> Result, NyxdError> { - let hash = match self.0.read().await.get_block_hash(height).await? { + let hash = match nyxd_query!(self, get_block_hash(height).await?) { Hash::Sha256(hash) => Some(hash), Hash::None => None, }; @@ -141,44 +202,46 @@ impl Client { } pub(crate) async fn get_mixnodes(&self) -> Result, NyxdError> { - self.0.read().await.get_all_mixnodes_detailed().await + nyxd_query!(self, get_all_mixnodes_detailed().await) } pub(crate) async fn get_gateways(&self) -> Result, NyxdError> { - self.0.read().await.get_all_gateways().await + nyxd_query!(self, get_all_gateways().await) } pub(crate) async fn get_current_interval(&self) -> Result { - self.0.read().await.get_current_interval_details().await + nyxd_query!(self, get_current_interval_details().await) } pub(crate) async fn get_current_epoch_status(&self) -> Result { - self.0.read().await.get_current_epoch_status().await + nyxd_query!(self, get_current_epoch_status().await) } pub(crate) async fn get_current_rewarding_parameters( &self, ) -> Result { - self.0.read().await.get_rewarding_parameters().await + nyxd_query!(self, get_rewarding_parameters().await) } pub(crate) async fn get_rewarded_set_mixnodes( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_rewarded_set_mixnodes().await + nyxd_query!(self, get_all_rewarded_set_mixnodes().await) } pub(crate) async fn get_current_vesting_account_storage_key(&self) -> Result { - let guard = self.0.read().await; + let guard = self.inner.read().await; // the expect is fine as we always construct the client with the vesting contract explicitly set - let vesting_contract = guard - .vesting_contract_address() - .expect("vesting contract address is not available"); + let vesting_contract = query_guard!( + guard, + vesting_contract_address().expect("vesting contract address is not available") + ); // TODO: I don't like the usage of the hardcoded value here - let res = guard - .query_contract_raw(vesting_contract, b"key".to_vec()) - .await?; + let res = query_guard!( + guard, + query_contract_raw(vesting_contract, b"key".to_vec()).await? + ); if res.is_empty() { return Ok(0); } @@ -189,22 +252,22 @@ impl Client { pub(crate) async fn get_all_vesting_coins( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_accounts_vesting_coins().await + nyxd_query!(self, get_all_accounts_vesting_coins().await) } pub(crate) async fn get_all_family_members( &self, ) -> Result, NyxdError> { - self.0.read().await.get_all_family_members().await + nyxd_query!(self, get_all_family_members().await) } pub(crate) async fn get_pending_events_count(&self) -> Result { - let pending = self.0.read().await.get_number_of_pending_events().await?; + let pending = nyxd_query!(self, get_number_of_pending_events().await?); Ok(pending.epoch_events + pending.interval_events) } pub(crate) async fn begin_epoch_transition(&self) -> Result<(), NyxdError> { - self.0.write().await.begin_epoch_transition(None).await?; + nyxd_signing!(self, begin_epoch_transition(None).await?); Ok(()) } @@ -231,26 +294,29 @@ impl Client { } } - // "technically" we don't need a write access to the client, - // but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages - // as that would have messed up sequence numbers - let guard = self.0.write().await; - // the expect is fine as we always construct the client with the mixnet contract explicitly set - let mixnet_contract = guard - .mixnet_contract_address() - .expect("mixnet contract address is not available"); + let mixnet_contract = nyxd_query!( + self, + mixnet_contract_address() + .expect("mixnet contract address is not available") + .clone() + ); let msgs = generate_reward_messages(nodes); - guard - .execute_multiple( - mixnet_contract, + // "technically" we don't need a write access to the client, + // but we REALLY don't want to accidentally send any transactions while we're sending rewarding messages + // as that would have messed up sequence numbers + nyxd_signing!( + self, + execute_multiple( + &mixnet_contract, msgs, Default::default(), format!("rewarding {} mixnodes", nodes.len()), ) - .await?; + .await? + ); Ok(()) } @@ -259,20 +325,15 @@ impl Client { new_rewarded_set: Vec, expected_active_set_size: u32, ) -> Result<(), NyxdError> { - self.0 - .write() - .await - .advance_current_epoch(new_rewarded_set, expected_active_set_size, None) - .await?; + nyxd_signing!( + self, + advance_current_epoch(new_rewarded_set, expected_active_set_size, None).await? + ); Ok(()) } pub(crate) async fn reconcile_epoch_events(&self, limit: Option) -> Result<(), NyxdError> { - self.0 - .write() - .await - .reconcile_epoch_events(limit, None) - .await?; + nyxd_signing!(self, reconcile_epoch_events(limit, None).await?); Ok(()) } } @@ -283,89 +344,82 @@ impl crate::coconut::client::Client for Client { self.client_address().await } - async fn get_tx(&self, tx_hash: &str) -> crate::coconut::error::Result { - let tx_hash: Hash = tx_hash - .parse() - .map_err(|_| CoconutError::TxHashParseError)?; - Ok(self.0.read().await.get_tx(tx_hash).await?) + async fn get_tx(&self, tx_hash: Hash) -> crate::coconut::error::Result { + nyxd_query!(self, get_tx(tx_hash).await).map_err(|source| { + CoconutError::TxRetrievalFailure { + tx_hash: tx_hash.to_string(), + source, + } + }) } async fn get_proposal( &self, proposal_id: u64, ) -> crate::coconut::error::Result { - Ok(self.0.read().await.query_proposal(proposal_id).await?) + Ok(nyxd_query!(self, query_proposal(proposal_id).await?)) } async fn list_proposals(&self) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_all_proposals().await?) + Ok(nyxd_query!(self, get_all_proposals().await?)) } async fn get_spent_credential( &self, blinded_serial_number: String, ) -> crate::coconut::error::Result { - Ok(self - .0 - .read() - .await - .get_spent_credential(blinded_serial_number) - .await?) + Ok(nyxd_query!( + self, + get_spent_credential(blinded_serial_number).await? + )) } async fn get_current_epoch(&self) -> crate::coconut::error::Result { - Ok(self.0.read().await.get_current_epoch().await?) + Ok(nyxd_query!(self, get_current_epoch().await?)) } async fn group_member(&self, addr: String) -> crate::coconut::error::Result { - Ok(self.0.read().await.member(addr, None).await?) + Ok(nyxd_query!(self, member(addr, None).await?)) } async fn get_current_epoch_threshold( &self, ) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_current_epoch_threshold().await?) + Ok(nyxd_query!(self, get_current_epoch_threshold().await?)) } async fn get_initial_dealers( &self, ) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_initial_dealers().await?) + Ok(nyxd_query!(self, get_initial_dealers().await?)) } async fn get_self_registered_dealer_details( &self, ) -> crate::coconut::error::Result { let self_address = &self.address().await; - Ok(self.0.read().await.get_dealer_details(self_address).await?) + Ok(nyxd_query!(self, get_dealer_details(self_address).await?)) } async fn get_current_dealers(&self) -> crate::coconut::error::Result> { - Ok(self.0.read().await.get_all_current_dealers().await?) + Ok(nyxd_query!(self, get_all_current_dealers().await?)) } async fn get_dealings( &self, idx: usize, ) -> crate::coconut::error::Result> { - Ok(self - .0 - .read() - .await - .get_all_epoch_dealings(idx as u64) - .await?) + Ok(nyxd_query!(self, get_all_epoch_dealings(idx as u64).await?)) } async fn get_verification_key_shares( &self, epoch_id: EpochId, ) -> crate::coconut::error::Result> { - Ok(self - .0 - .read() - .await - .get_all_verification_key_shares(epoch_id) - .await?) + Ok(nyxd_query!( + self, + get_all_verification_key_shares(epoch_id).await? + )) } async fn vote_proposal( @@ -374,25 +428,17 @@ impl crate::coconut::client::Client for Client { vote_yes: bool, fee: Option, ) -> Result<(), CoconutError> { - self.0 - .read() - .await - .vote_proposal(proposal_id, vote_yes, fee) - .await?; + nyxd_signing!(self, vote_proposal(proposal_id, vote_yes, fee).await?); Ok(()) } async fn execute_proposal(&self, proposal_id: u64) -> crate::coconut::error::Result<()> { - self.0 - .read() - .await - .execute_proposal(proposal_id, None) - .await?; + nyxd_signing!(self, execute_proposal(proposal_id, None).await?); Ok(()) } async fn advance_epoch_state(&self) -> crate::coconut::error::Result<()> { - self.0.write().await.advance_dkg_epoch_state(None).await?; + nyxd_signing!(self, advance_dkg_epoch_state(None).await?); Ok(()) } @@ -402,12 +448,10 @@ impl crate::coconut::client::Client for Client { announce_address: String, resharing: bool, ) -> Result { - Ok(self - .0 - .write() - .await - .register_dealer(bte_key, announce_address, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + register_dealer(bte_key, announce_address, resharing, None).await? + )) } async fn submit_dealing( @@ -415,12 +459,10 @@ impl crate::coconut::client::Client for Client { dealing_bytes: ContractSafeBytes, resharing: bool, ) -> Result { - Ok(self - .0 - .write() - .await - .submit_dealing_bytes(dealing_bytes, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + submit_dealing_bytes(dealing_bytes, resharing, None).await? + )) } async fn submit_verification_key_share( @@ -428,31 +470,27 @@ impl crate::coconut::client::Client for Client { share: VerificationKeyShare, resharing: bool, ) -> crate::coconut::error::Result { - Ok(self - .0 - .write() - .await - .submit_verification_key_share(share, resharing, None) - .await?) + Ok(nyxd_signing!( + self, + submit_verification_key_share(share, resharing, None).await? + )) } } #[async_trait] impl crate::ephemera::client::Client for Client { async fn get_ephemera_peers(&self) -> crate::ephemera::error::Result> { - Ok(self.0.read().await.get_all_ephemera_peers().await?) + Ok(nyxd_query!(self, get_all_ephemera_peers().await?)) } async fn register_ephemera_peer( &self, peer_info: JsonPeerInfo, ) -> crate::ephemera::error::Result { - Ok(self - .0 - .write() - .await - .register_as_peer(peer_info, None) - .await?) + Ok(nyxd_signing!( + self, + register_as_peer(peer_info, None).await? + )) } } @@ -462,7 +500,7 @@ impl DkgQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_dkg_contract(query).await + nyxd_query!(self, query_dkg_contract(query).await) } } @@ -475,7 +513,7 @@ impl EphemeraQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_ephemera_contract(query).await + nyxd_query!(self, query_ephemera_contract(query).await) } } @@ -488,11 +526,7 @@ impl SpDirectoryQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0 - .read() - .await - .query_service_provider_contract(query) - .await + nyxd_query!(self, query_service_provider_contract(query).await) } } @@ -505,6 +539,6 @@ impl NameServiceQueryClient for Client { where for<'a> T: Deserialize<'a>, { - self.0.read().await.query_name_service_contract(query).await + nyxd_query!(self, query_name_service_contract(query).await) } } diff --git a/nym-api/src/support/storage/manager.rs b/nym-api/src/support/storage/manager.rs index 737cb1cfab..4e3c23c1e9 100644 --- a/nym-api/src/support/storage/manager.rs +++ b/nym-api/src/support/storage/manager.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::summary_producer::{GatewayResult, MixnodeResult}; use crate::node_status_api::models::{HistoricalUptime, Uptime}; use crate::node_status_api::utils::{ActiveGatewayStatuses, ActiveMixnodeStatuses}; @@ -487,15 +487,15 @@ impl StorageManager { // insert the actual status sqlx::query!( - r#" - INSERT INTO mixnode_status (mixnode_details_id, reliability, timestamp) VALUES (?, ?, ?); - "#, - mixnode_id, - mixnode_result.reliability, - timestamp - ) - .execute(&mut tx) - .await?; + r#" + INSERT INTO mixnode_status (mixnode_details_id, reliability, timestamp) VALUES (?, ?, ?); + "#, + mixnode_id, + mixnode_result.reliability, + timestamp + ) + .execute(&mut tx) + .await?; } // finally commit the transaction @@ -972,45 +972,4 @@ impl StorageManager { Ok(active_day_statuses) } - - /// Creates new encrypted blinded signature response entry for a given deposit tx hash. - /// - /// # Arguments - /// - /// * `tx_hash`: hash of the deposit transaction. - /// * `blinded_signature_response`: the encrypted blinded signature response. - pub(crate) async fn insert_blinded_signature_response( - &self, - tx_hash: &str, - blinded_signature_response: &str, - ) -> Result<(), sqlx::Error> { - sqlx::query!( - "INSERT INTO signed_deposit(tx_hash, blinded_signature_response) VALUES (?, ?)", - tx_hash, - blinded_signature_response - ) - .execute(&self.connection_pool) - .await?; - Ok(()) - } - - /// Tries to obtain encrypted blinded signature response for a given transaction hash. - /// - /// # Arguments - /// - /// * `tx_hash`: transaction hash of the deposit. - pub(crate) async fn get_blinded_signature_response( - &self, - tx_hash: &str, - ) -> Result, sqlx::Error> { - let blinded_signature_response = sqlx::query!( - "SELECT blinded_signature_response FROM signed_deposit WHERE tx_hash = ?", - tx_hash - ) - .fetch_optional(&self.connection_pool) - .await? - .map(|row| row.blinded_signature_response); - - Ok(blinded_signature_response) - } } diff --git a/nym-api/src/support/storage/mod.rs b/nym-api/src/support/storage/mod.rs index c4f6d8e294..3cd1e350cd 100644 --- a/nym-api/src/support/storage/mod.rs +++ b/nym-api/src/support/storage/mod.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::network_monitor::monitor::summary_producer::{GatewayResult, MixnodeResult}; use crate::network_monitor::test_route::TestRoute; @@ -716,25 +716,4 @@ impl NymApiStorage { .await .map_err(|err| err.into()) } - - pub(crate) async fn get_blinded_signature_response( - &self, - tx_hash: &str, - ) -> Result, NymApiStorageError> { - self.manager - .get_blinded_signature_response(tx_hash) - .await - .map_err(|err| err.into()) - } - - pub(crate) async fn insert_blinded_signature_response( - &self, - tx_hash: &str, - blinded_signature_response: &str, - ) -> Result<(), NymApiStorageError> { - self.manager - .insert_blinded_signature_response(tx_hash, blinded_signature_response) - .await - .map_err(|err| err.into()) - } } diff --git a/nym-api/src/support/storage/models.rs b/nym-api/src/support/storage/models.rs index 69d3554ca1..a07a11176c 100644 --- a/nym-api/src/support/storage/models.rs +++ b/nym-api/src/support/storage/models.rs @@ -1,5 +1,5 @@ // Copyright 2021 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_mixnet_contract_common::MixId; diff --git a/nym-api/tests/functional_test/status/status-gateway.test.ts b/nym-api/tests/functional_test/status/status-gateway.test.ts index af1c74b923..44cf3a3875 100644 --- a/nym-api/tests/functional_test/status/status-gateway.test.ts +++ b/nym-api/tests/functional_test/status/status-gateway.test.ts @@ -56,20 +56,18 @@ describe("Get gateway data", (): void => { it("Get gateway average uptime", async (): Promise => { const identity_key = config.environmentConfig.gateway_identity; const response = await status.getGatewayAverageUptime(identity_key); - if ("mix_id" in response) { + if ("identity" in response) { expect(identity_key).toStrictEqual(response.identity); - expect(typeof response.count).toBe("number"); + expect(typeof response.avg_uptime).toBe("number"); } else if ("message" in response) { - expect(response.message).toContain( - "could not find uptime history associated with mixnode" - ); + expect(response.message).toContain("gateway bond not found"); } }); it("Get a gateway status report", async (): Promise => { const identity_key = config.environmentConfig.gateway_identity; const response = await status.getGatewayStatusReport(identity_key); - if ("mix_id" in response) { + if ("identity" in response) { expect(identity_key).toStrictEqual(response.identity); expect(typeof response.owner).toBe("string"); expect(typeof response.most_recent).toBe("number"); diff --git a/nym-api/tests/package-lock.json b/nym-api/tests/package-lock.json index 8ade4dd44f..08689f1921 100644 --- a/nym-api/tests/package-lock.json +++ b/nym-api/tests/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "axios": "^0.27.2", + "axios": "^1.6.0", "eslint": "^8.21.0", "form-data": "4.0.0", "json-stringify-safe": "5.0.1", @@ -1671,12 +1671,13 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/axios-mock-adapter": { @@ -4186,6 +4187,11 @@ "node": ">= 6" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -6184,12 +6190,13 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", "requires": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "axios-mock-adapter": { @@ -8023,6 +8030,11 @@ "sisteransi": "^1.0.5" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", diff --git a/nym-api/tests/package.json b/nym-api/tests/package.json index 31731d6b16..5a6e097d58 100644 --- a/nym-api/tests/package.json +++ b/nym-api/tests/package.json @@ -24,7 +24,7 @@ "npm": "8.x" }, "dependencies": { - "axios": "^0.27.2", + "axios": "^1.6.0", "eslint": "^8.21.0", "form-data": "4.0.0", "json-stringify-safe": "5.0.1", diff --git a/nym-api/tests/src/endpoints/Status.ts b/nym-api/tests/src/endpoints/Status.ts index afaa2d6abd..1f1617c305 100644 --- a/nym-api/tests/src/endpoints/Status.ts +++ b/nym-api/tests/src/endpoints/Status.ts @@ -4,6 +4,7 @@ import { CoreCount, DetailedGateway, DetailedMixnodes, + GatewayUptimeResponse, InclusionProbabilities, InclusionProbability, NodeHistory, @@ -67,7 +68,7 @@ export default class Status extends APIClient { public async getGatewayAverageUptime( identity_key: string - ): Promise { + ): Promise { const response = await this.restClient.sendGet({ route: `/gateway/${identity_key}/avg_uptime`, }); diff --git a/nym-api/tests/src/types/StatusInterfaces.ts b/nym-api/tests/src/types/StatusInterfaces.ts index b0ad1cf1b5..9c492721f0 100644 --- a/nym-api/tests/src/types/StatusInterfaces.ts +++ b/nym-api/tests/src/types/StatusInterfaces.ts @@ -168,6 +168,12 @@ export interface AvgUptime { node_performance: nodePerformance; } +export interface GatewayUptimeResponse { + identity: string; + avg_uptime: number; + node_performance: nodePerformance; +} + export interface DetailedGateway { gateway_bond: GatewayBond; performance: string; diff --git a/nym-api/tests/yarn.lock b/nym-api/tests/yarn.lock index 64416eea92..54a944f272 100644 --- a/nym-api/tests/yarn.lock +++ b/nym-api/tests/yarn.lock @@ -981,13 +981,14 @@ axios-mock-adapter@^1.20.0: fast-deep-equal "^3.1.3" is-buffer "^2.0.5" -axios@^0.27.2: - version "0.27.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz" - integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== +axios@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" + integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== dependencies: - follow-redirects "^1.14.9" + follow-redirects "^1.15.0" form-data "^4.0.0" + proxy-from-env "^1.1.0" babel-jest@^28.1.3: version "28.1.3" @@ -1566,10 +1567,10 @@ flatted@^3.1.0: resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz" integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== -follow-redirects@^1.14.9: - version "1.15.1" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz" - integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== +follow-redirects@^1.15.0: + version "1.15.3" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.3.tgz#fe2f3ef2690afce7e82ed0b44db08165b207123a" + integrity sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q== form-data@4.0.0, form-data@^4.0.0: version "4.0.0" @@ -2575,6 +2576,11 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + punycode@^2.1.0: version "2.1.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" diff --git a/nym-browser-extension/storage/Cargo.toml b/nym-browser-extension/storage/Cargo.toml index 02661e3284..037386774c 100644 --- a/nym-browser-extension/storage/Cargo.toml +++ b/nym-browser-extension/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "extension-storage" -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" edition = "2021" license = "Apache-2.0" repository = "https://github.com/nymtech/nym" diff --git a/nym-connect/desktop/Cargo.lock b/nym-connect/desktop/Cargo.lock index e3a9fce4c2..a031a0ad4a 100644 --- a/nym-connect/desktop/Cargo.lock +++ b/nym-connect/desktop/Cargo.lock @@ -514,15 +514,6 @@ dependencies = [ "opaque-debug 0.2.3", ] -[[package]] -name = "blake2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" -dependencies = [ - "digest 0.10.7", -] - [[package]] name = "blake3" version = "1.4.1" @@ -578,27 +569,13 @@ dependencies = [ [[package]] name = "bls12_381" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" +version = "0.8.0" +source = "git+https://github.com/jstuczyn/bls12_381?branch=feature/gt-serialization-0.8.0#c4543fde7d02efea6ecfcf22e14476ddb516b483" dependencies = [ "digest 0.9.0", - "ff 0.10.1", - "group 0.10.0", - "pairing 0.20.0", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "bls12_381" -version = "0.6.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" -dependencies = [ - "digest 0.9.0", - "ff 0.11.1", - "group 0.11.0", - "pairing 0.21.0", + "ff 0.13.0", + "group 0.13.0", + "pairing", "rand_core 0.6.4", "subtle 2.4.1", "zeroize", @@ -610,29 +587,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" -[[package]] -name = "boringtun" -version = "0.6.0" -source = "git+https://github.com/cloudflare/boringtun?rev=e1d6360d6ab4529fc942a078e4c54df107abe2ba#e1d6360d6ab4529fc942a078e4c54df107abe2ba" -dependencies = [ - "aead", - "base64 0.13.1", - "blake2 0.10.6", - "chacha20poly1305", - "hex", - "hmac 0.12.1", - "ip_network", - "ip_network_table", - "libc", - "nix 0.25.1", - "parking_lot 0.12.1", - "rand_core 0.6.4", - "ring", - "tracing", - "untrusted 0.9.0", - "x25519-dalek 2.0.0", -] - [[package]] name = "brotli" version = "3.3.4" @@ -705,9 +659,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -1068,9 +1022,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ "prost", "prost-types", @@ -1079,19 +1033,19 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" +checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" dependencies = [ "bip32", "cosmos-sdk-proto", "ecdsa 0.16.8", "eyre", - "getrandom 0.2.10", "k256 0.13.1", "rand_core 0.6.4", "serde", "serde_json", + "signature 2.1.0", "subtle-encoding", "tendermint", "tendermint-rpc", @@ -1347,15 +1301,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.1", -] - [[package]] name = "ctor" version = "0.1.26" @@ -1393,6 +1338,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", + "serde", "subtle 2.4.1", "zeroize", ] @@ -1619,10 +1565,11 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -1819,6 +1766,7 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ + "serde", "signature 1.6.4", ] @@ -1855,6 +1803,7 @@ dependencies = [ "ed25519 1.5.3", "rand 0.7.3", "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -2078,26 +2027,6 @@ dependencies = [ "log", ] -[[package]] -name = "ff" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "ff" version = "0.12.1" @@ -2114,6 +2043,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle 2.4.1", ] @@ -2703,30 +2633,6 @@ dependencies = [ "system-deps 6.1.1", ] -[[package]] -name = "group" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" -dependencies = [ - "byteorder", - "ff 0.10.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "group" version = "0.12.1" @@ -2883,31 +2789,6 @@ dependencies = [ "hashbrown 0.14.0", ] -[[package]] -name = "headers" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.3.3" @@ -3120,40 +3001,17 @@ dependencies = [ ] [[package]] -name = "hyper-proxy" -version = "0.9.1" +name = "hyper-rustls" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" dependencies = [ - "bytes", - "futures", - "headers", + "futures-util", "http", "hyper", - "hyper-rustls", - "rustls-native-certs", + "rustls 0.21.7", "tokio", - "tokio-rustls 0.22.0", - "tower-service", - "webpki 0.21.4", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", - "rustls 0.19.1", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", - "webpki-roots 0.21.1", + "tokio-rustls 0.24.1", ] [[package]] @@ -3314,28 +3172,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "ip_network" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" - -[[package]] -name = "ip_network_table" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" -dependencies = [ - "ip_network", - "ip_network_table-deps-treebitmap", -] - -[[package]] -name = "ip_network_table-deps-treebitmap" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" - [[package]] name = "ipnet" version = "2.8.0" @@ -3468,9 +3304,7 @@ dependencies = [ "cfg-if", "ecdsa 0.16.8", "elliptic-curve 0.13.5", - "once_cell", "sha2 0.10.8", - "signature 2.1.0", ] [[package]] @@ -3523,9 +3357,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "libgit2-sys" @@ -3612,7 +3446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" dependencies = [ "arrayref", - "blake2 0.8.1", + "blake2", "chacha", "keystream", ] @@ -3768,9 +3602,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -3829,18 +3663,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nix" version = "0.26.2" @@ -3984,10 +3806,12 @@ dependencies = [ "cosmwasm-std", "getset", "nym-coconut-interface", + "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", "schemars", "serde", + "tendermint", ] [[package]] @@ -4004,6 +3828,7 @@ dependencies = [ "rand 0.7.3", "thiserror", "url", + "zeroize", ] [[package]] @@ -4074,12 +3899,12 @@ dependencies = [ name = "nym-coconut" version = "0.5.0" dependencies = [ - "bls12_381 0.6.0", + "bls12_381", "bs58 0.4.0", "digest 0.9.0", - "ff 0.11.1", + "ff 0.13.0", "getrandom 0.2.10", - "group 0.11.0", + "group 0.13.0", "itertools", "nym-dkg", "nym-pemstore", @@ -4212,7 +4037,7 @@ dependencies = [ name = "nym-credentials" version = "0.1.0" dependencies = [ - "bls12_381 0.5.0", + "bls12_381", "cosmrs", "log", "nym-api-requests", @@ -4220,6 +4045,7 @@ dependencies = [ "nym-crypto", "nym-validator-client", "thiserror", + "zeroize", ] [[package]] @@ -4239,6 +4065,8 @@ dependencies = [ "nym-pemstore", "nym-sphinx-types", "rand 0.7.3", + "serde", + "serde_bytes", "subtle-encoding", "thiserror", "x25519-dalek 1.1.1", @@ -4250,10 +4078,10 @@ name = "nym-dkg" version = "0.1.0" dependencies = [ "bitvec", - "bls12_381 0.6.0", + "bls12_381", "bs58 0.4.0", - "ff 0.11.1", - "group 0.11.0", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "nym-pemstore", "rand 0.8.5", @@ -4761,6 +4589,7 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.13.1", + "bip32", "bip39", "colored 2.0.4", "cosmrs", @@ -4821,8 +4650,8 @@ name = "nym-wireguard-types" version = "0.1.0" dependencies = [ "base64 0.21.4", - "boringtun", "dashmap", + "log", "nym-crypto", "serde", "thiserror", @@ -4994,20 +4823,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pairing" -version = "0.20.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" dependencies = [ - "group 0.10.0", -] - -[[package]] -name = "pairing" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" -dependencies = [ - "group 0.11.0", + "group 0.13.0", ] [[package]] @@ -5415,6 +5235,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -5488,9 +5314,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.9" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "f4fdd22f3b9c31b53c060df4a0613a1c7f062d4115a2b984dd15b1858f7e340d" dependencies = [ "bytes", "prost-derive", @@ -5498,22 +5324,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "265baba7fabd416cf5078179f7d2cbeca4ce7a9041111900675ea7c4cb8a4c32" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "e081b29f63d83a4bc75cfc9f3fe424f9156cf92d8a4f0c9407cce9a1b67327cf" dependencies = [ "prost", ] @@ -5770,6 +5596,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -5779,12 +5606,16 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls 0.21.7", + "rustls-native-certs", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls 0.24.1", "tokio-socks", "tokio-util", "tower-service", @@ -5851,7 +5682,7 @@ dependencies = [ "libc", "once_cell", "spin 0.5.2", - "untrusted 0.7.1", + "untrusted", "web-sys", "winapi", ] @@ -5968,13 +5799,25 @@ dependencies = [ ] [[package]] -name = "rustls-native-certs" -version = "0.5.0" +name = "rustls" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct 0.7.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls 0.19.1", + "rustls-pemfile", "schannel", "security-framework", ] @@ -5988,6 +5831,16 @@ dependencies = [ "base64 0.21.4", ] +[[package]] +name = "rustls-webpki" +version = "0.101.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -6068,7 +5921,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -6078,7 +5931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -6311,9 +6164,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.183" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" dependencies = [ "serde_derive", ] @@ -6338,9 +6191,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.190" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" dependencies = [ "proc-macro2", "quote", @@ -6584,9 +6437,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -6628,7 +6481,7 @@ checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" dependencies = [ "aes 0.7.5", "arrayref", - "blake2 0.8.1", + "blake2", "bs58 0.4.0", "byteorder", "chacha", @@ -7340,9 +7193,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" dependencies = [ "bytes", "digest 0.10.7", @@ -7371,9 +7224,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" +checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" dependencies = [ "flex-error", "serde", @@ -7385,9 +7238,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" dependencies = [ "bytes", "flex-error", @@ -7403,21 +7256,18 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" +checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" dependencies = [ "async-trait", "bytes", "flex-error", "futures", "getrandom 0.2.10", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", "peg", "pin-project", + "reqwest", "semver 1.0.18", "serde", "serde_bytes", @@ -7494,15 +7344,16 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa 1.0.9", "js-sys", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -7510,15 +7361,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -7540,9 +7391,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -7551,16 +7402,16 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -7599,6 +7450,16 @@ dependencies = [ "webpki 0.22.2", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.7", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.1" @@ -7639,9 +7500,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -7901,12 +7762,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - [[package]] name = "ureq" version = "2.7.1" @@ -7922,9 +7777,9 @@ dependencies = [ [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -8257,7 +8112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -8267,7 +8122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07ecc0cd7cac091bf682ec5efa18b1cff79d617b84181f38b3951dbe135f607f" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -8713,6 +8568,7 @@ checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek 3.2.0", "rand_core 0.5.1", + "serde", "zeroize", ] @@ -8743,7 +8599,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix 0.26.2", + "nix", "winapi", ] @@ -8780,7 +8636,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix 0.26.2", + "nix", "once_cell", "ordered-stream", "rand 0.8.5", diff --git a/nym-node/Cargo.toml b/nym-node/Cargo.toml index 7b70ae86e0..77a7125c9c 100644 --- a/nym-node/Cargo.toml +++ b/nym-node/Cargo.toml @@ -1,3 +1,6 @@ +# Copyright 2023 - Nym Technologies SA +# SPDX-License-Identifier: GPL-3.0-only + [package] name = "nym-node" version = "0.1.0" @@ -6,7 +9,7 @@ repository.workspace = true homepage.workspace = true documentation.workspace = true edition.workspace = true -license.workspace = true +license = "GPL-3.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,6 +17,8 @@ license.workspace = true anyhow = { workspace = true } bytes = "1.5.0" colored = "2" +ipnetwork = "0.16" +rand = "0.7.3" serde = { workspace = true, features = ["derive"] } serde_yaml = "0.9.25" serde_json = { workspace = true } @@ -48,6 +53,7 @@ nym-config = { path = "../common/config" } nym-crypto = { path = "../common/crypto", features = ["asymmetric" ]} nym-node-requests = { path = "nym-node-requests", default-features = false, features = ["openapi"]} nym-task = { path = "../common/task" } +nym-wireguard = { path = "../common/wireguard" } nym-wireguard-types = { path = "../common/wireguard-types", features = ["verify"] } [dev-dependencies] diff --git a/nym-node/nym-node-requests/src/api/client.rs b/nym-node/nym-node-requests/src/api/client.rs index 6815b1a023..c4c89e2514 100644 --- a/nym-node/nym-node-requests/src/api/client.rs +++ b/nym-node/nym-node-requests/src/api/client.rs @@ -11,6 +11,9 @@ use nym_bin_common::build_information::BinaryBuildInformationOwned; use nym_wireguard_types::{ClientMessage, ClientRegistrationResponse}; use crate::api::v1::health::models::NodeHealth; +use crate::api::v1::ip_packet_router::models::IpPacketRouter; +use crate::api::v1::network_requester::exit_policy::models::UsedExitPolicy; +use crate::api::v1::network_requester::models::NetworkRequester; pub use http_api_client::Client; pub type NymNodeApiClientError = HttpClientError; @@ -42,6 +45,21 @@ pub trait NymNodeApiClientExt: ApiClient { .await } + async fn get_network_requester(&self) -> Result { + self.get_json_from(routes::api::v1::network_requester_absolute()) + .await + } + + async fn get_exit_policy(&self) -> Result { + self.get_json_from(routes::api::v1::network_requester::exit_policy_absolute()) + .await + } + + async fn get_ip_packet_router(&self) -> Result { + self.get_json_from(routes::api::v1::ip_packet_router_absolute()) + .await + } + async fn post_gateway_register_client( &self, client_message: &ClientMessage, diff --git a/nym-node/nym-node-requests/src/api/v1/ip_packet_router/mod.rs b/nym-node/nym-node-requests/src/api/v1/ip_packet_router/mod.rs new file mode 100644 index 0000000000..c57081432b --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/ip_packet_router/mod.rs @@ -0,0 +1,4 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub mod models; diff --git a/nym-node/nym-node-requests/src/api/v1/ip_packet_router/models.rs b/nym-node/nym-node-requests/src/api/v1/ip_packet_router/models.rs new file mode 100644 index 0000000000..eea0783b67 --- /dev/null +++ b/nym-node/nym-node-requests/src/api/v1/ip_packet_router/models.rs @@ -0,0 +1,18 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)] +#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))] +pub struct IpPacketRouter { + /// Base58 encoded ed25519 EdDSA public key of the ip-packet-router. + pub encoded_identity_key: String, + + /// Base58-encoded x25519 public key used for performing key exchange with remote clients. + pub encoded_x25519_key: String, + + /// Nym address of this ip packet router. + pub address: String, +} diff --git a/nym-node/nym-node-requests/src/api/v1/mod.rs b/nym-node/nym-node-requests/src/api/v1/mod.rs index b07fdbb7aa..33d67d598e 100644 --- a/nym-node/nym-node-requests/src/api/v1/mod.rs +++ b/nym-node/nym-node-requests/src/api/v1/mod.rs @@ -3,6 +3,7 @@ pub mod gateway; pub mod health; +pub mod ip_packet_router; pub mod mixnode; pub mod network_requester; pub mod node; diff --git a/nym-node/nym-node-requests/src/api/v1/node/models.rs b/nym-node/nym-node-requests/src/api/v1/node/models.rs index 260752436b..3b79601a00 100644 --- a/nym-node/nym-node-requests/src/api/v1/node/models.rs +++ b/nym-node/nym-node-requests/src/api/v1/node/models.rs @@ -14,6 +14,7 @@ pub struct NodeRoles { pub mixnode_enabled: bool, pub gateway_enabled: bool, pub network_requester_enabled: bool, + pub ip_packet_router_enabled: bool, } #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] diff --git a/nym-node/nym-node-requests/src/lib.rs b/nym-node/nym-node-requests/src/lib.rs index cb7942f861..9bb345eca9 100644 --- a/nym-node/nym-node-requests/src/lib.rs +++ b/nym-node/nym-node-requests/src/lib.rs @@ -33,6 +33,7 @@ pub mod routes { pub const GATEWAY: &str = "/gateway"; pub const MIXNODE: &str = "/mixnode"; pub const NETWORK_REQUESTER: &str = "/network-requester"; + pub const IP_PACKET_ROUTER: &str = "/ip-packet-router"; pub const SWAGGER: &str = "/swagger"; // define helper functions to get absolute routes @@ -43,6 +44,7 @@ pub mod routes { absolute_route!(gateway_absolute, v1_absolute(), GATEWAY); absolute_route!(mixnode_absolute, v1_absolute(), MIXNODE); absolute_route!(network_requester_absolute, v1_absolute(), NETWORK_REQUESTER); + absolute_route!(ip_packet_router_absolute, v1_absolute(), IP_PACKET_ROUTER); absolute_route!(swagger_absolute, v1_absolute(), SWAGGER); pub mod gateway { @@ -96,6 +98,10 @@ pub mod routes { EXIT_POLICY ); } + + pub mod ip_packet_router { + // use super::*; + } } } } @@ -146,5 +152,9 @@ mod tests { "/api/v1/network-requester", routes::api::v1::network_requester_absolute() ); + assert_eq!( + "/api/v1/ip-packet-router", + routes::api::v1::ip_packet_router_absolute() + ); } } diff --git a/nym-node/src/config/mod.rs b/nym-node/src/config/mod.rs index ad4a9a8823..3b712f2e63 100644 --- a/nym-node/src/config/mod.rs +++ b/nym-node/src/config/mod.rs @@ -1,8 +1,7 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only -use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT; -use nym_wireguard_types::WG_PORT; +use nym_config::defaults::{DEFAULT_NYM_NODE_HTTP_PORT, WG_PORT}; use serde::{Deserialize, Serialize}; use serde_helpers::*; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; @@ -12,6 +11,7 @@ pub mod persistence; mod serde_helpers; pub const DEFAULT_WIREGUARD_PORT: u16 = WG_PORT; +pub const DEFAULT_WIREGUARD_PREFIX: u8 = 16; pub const DEFAULT_HTTP_PORT: u16 = DEFAULT_NYM_NODE_HTTP_PORT; // TODO: this is very much a WIP. we need proper ssl certificate support here @@ -75,6 +75,10 @@ pub struct Wireguard { /// Useful in the instances where the node is behind a proxy. pub announced_port: u16, + /// The prefix denoting the maximum number of the clients that can be connected via Wireguard. + /// The maximum value for IPv4 is 32 and for IPv6 is 128 + pub private_network_prefix: u8, + /// Paths for wireguard keys, client registries, etc. pub storage_paths: persistence::WireguardPaths, } @@ -88,6 +92,7 @@ impl Default for Wireguard { DEFAULT_WIREGUARD_PORT, ), announced_port: DEFAULT_WIREGUARD_PORT, + private_network_prefix: DEFAULT_WIREGUARD_PREFIX, storage_paths: persistence::WireguardPaths {}, } } diff --git a/nym-node/src/config/persistence.rs b/nym-node/src/config/persistence.rs index 2ceed4b3cc..c1cf1824ab 100644 --- a/nym-node/src/config/persistence.rs +++ b/nym-node/src/config/persistence.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use serde::{Deserialize, Serialize}; diff --git a/nym-node/src/config/serde_helpers.rs b/nym-node/src/config/serde_helpers.rs index df0f42bb44..75bfef8bb9 100644 --- a/nym-node/src/config/serde_helpers.rs +++ b/nym-node/src/config/serde_helpers.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use serde::{Deserialize, Deserializer}; use std::path::PathBuf; diff --git a/nym-node/src/error.rs b/nym-node/src/error.rs index 09cbbe8952..e5a14cf018 100644 --- a/nym-node/src/error.rs +++ b/nym-node/src/error.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::wireguard::error::WireguardError; use std::net::{IpAddr, SocketAddr}; @@ -31,6 +31,12 @@ pub enum NymNodeError { source: WireguardError, }, + #[error(transparent)] + KeyRecoveryError { + #[from] + source: nym_crypto::asymmetric::encryption::KeyRecoveryError, + }, + #[error("unimplemented")] Unimplemented, } diff --git a/nym-node/src/http/middleware/logging.rs b/nym-node/src/http/middleware/logging.rs index 68b12b4155..d191c6f161 100644 --- a/nym-node/src/http/middleware/logging.rs +++ b/nym-node/src/http/middleware/logging.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::{ extract::ConnectInfo, diff --git a/nym-node/src/http/middleware/mod.rs b/nym-node/src/http/middleware/mod.rs index 872a6d3520..54a67e0147 100644 --- a/nym-node/src/http/middleware/mod.rs +++ b/nym-node/src/http/middleware/mod.rs @@ -1,4 +1,4 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub mod logging; diff --git a/nym-node/src/http/mod.rs b/nym-node/src/http/mod.rs index a7a4fa0d4b..77cb66ba14 100644 --- a/nym-node/src/http/mod.rs +++ b/nym-node/src/http/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::extract::connect_info::IntoMakeServiceWithConnectInfo; use axum::Router; diff --git a/nym-node/src/http/router/api/mod.rs b/nym-node/src/http/router/api/mod.rs index 757ec48e97..5e8a4160ca 100644 --- a/nym-node/src/http/router/api/mod.rs +++ b/nym-node/src/http/router/api/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::http::{header, HeaderValue, StatusCode}; use axum::response::{IntoResponse, Response}; diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs b/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs index a9b4f6beb7..3bd789a248 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs +++ b/nym-node/src/http/router/api/v1/gateway/client_interfaces/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; use crate::http::api::{FormattedResponse, OutputParams}; diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs b/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs index 850443e325..d214d6304f 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs +++ b/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/client_registry.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::gateway::client_interfaces::wireguard::{ WireguardAppState, WireguardAppStateInner, @@ -14,6 +14,7 @@ use nym_crypto::asymmetric::encryption::PublicKey; use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::{ ClientMessage, ClientRegistrationResponse, GatewayClient, InitMessage, Nonce, PeerPublicKey, }; +use rand::{prelude::IteratorRandom, thread_rng}; async fn process_final_message( client: GatewayClient, @@ -30,10 +31,7 @@ async fn process_final_message( } }; - if client - .verify(state.dh_keypair.private_key(), preshared_nonce) - .is_ok() - { + if client.verify(&state.private_key, preshared_nonce).is_ok() { state.registration_in_progress.remove(&client.pub_key()); state.client_registry.insert(client.pub_key(), client); @@ -91,8 +89,23 @@ pub(crate) async fn register_client( let remote_public = PublicKey::from_bytes(init.pub_key().as_bytes()) .map_err(|_| RequestError::new_status(StatusCode::BAD_REQUEST))?; let nonce = process_init_message(init, state).await; - let gateway_data = - GatewayClient::new(state.dh_keypair.private_key(), remote_public, nonce); + let mut private_ip_ref = state + .free_private_network_ips + .iter_mut() + .filter(|r| **r) + .choose(&mut thread_rng()) + .ok_or(RequestError::new( + "No more space in the network", + StatusCode::SERVICE_UNAVAILABLE, + ))?; + // mark it as used, even though it's not final + *private_ip_ref = false; + let gateway_data = GatewayClient::new( + &state.private_key, + remote_public, + *private_ip_ref.key(), + nonce, + ); let response = ClientRegistrationResponse::PendingRegistration { nonce, gateway_data, diff --git a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs b/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs index 37348be94e..eec31b244c 100644 --- a/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs +++ b/nym-node/src/http/router/api/v1/gateway/client_interfaces/wireguard/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::gateway::client_interfaces::wireguard::client_registry::{ get_all_clients, get_client, register_client, @@ -7,8 +7,11 @@ use crate::http::api::v1::gateway::client_interfaces::wireguard::client_registry use crate::wireguard::types::{GatewayClientRegistry, PendingRegistrations}; use axum::routing::{get, post}; use axum::Router; -use nym_crypto::asymmetric::encryption; +use ipnetwork::IpNetwork; +use nym_crypto::asymmetric::encryption::PrivateKey; use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard; +use nym_wireguard::setup; +use nym_wireguard_types::registration::PrivateIPs; use std::sync::Arc; pub(crate) mod client_registry; @@ -22,19 +25,24 @@ pub struct WireguardAppState { impl WireguardAppState { pub fn new( - dh_keypair: Arc, client_registry: Arc, registration_in_progress: Arc, binding_port: u16, - ) -> Self { - WireguardAppState { + private_ip_network: IpNetwork, + ) -> Result { + Ok(WireguardAppState { inner: Some(WireguardAppStateInner { - dh_keypair, + private_key: Arc::new(PrivateKey::from_bytes( + setup::server_static_private_key().as_ref(), + )?), client_registry, registration_in_progress, binding_port, + free_private_network_ips: Arc::new( + private_ip_network.iter().map(|ip| (ip, true)).collect(), + ), }), - } + }) } // #[allow(dead_code)] @@ -73,10 +81,11 @@ macro_rules! get_state { #[derive(Clone)] pub(crate) struct WireguardAppStateInner { - dh_keypair: Arc, + private_key: Arc, client_registry: Arc, registration_in_progress: Arc, binding_port: u16, + free_private_network_ips: Arc, } pub(crate) fn routes(initial_state: WireguardAppState) -> Router { @@ -98,13 +107,17 @@ mod test { use axum::http::StatusCode; use dashmap::DashMap; use hmac::Mac; + use ipnetwork::IpNetwork; use nym_crypto::asymmetric::encryption; use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::{ ClientMac, ClientMessage, ClientRegistrationResponse, GatewayClient, InitMessage, PeerPublicKey, }; use nym_node_requests::routes::api::v1::gateway::client_interfaces::wireguard; + use nym_wireguard::setup::server_static_private_key; use nym_wireguard_types::registration::HmacSha256; + use std::net::IpAddr; + use std::str::FromStr; use std::sync::Arc; use tower::Service; use tower::ServiceExt; @@ -120,29 +133,42 @@ mod test { // 6. Gateway verifies mac digest and nonce, and stores client's public key and socket address and port let mut rng = rand::thread_rng(); + let gateway_private_key = + encryption::PrivateKey::from_bytes(server_static_private_key().as_bytes()).unwrap(); + let gateway_public_key = encryption::PublicKey::from(&gateway_private_key); - let gateway_key_pair = encryption::KeyPair::new(&mut rng); + let gateway_key_pair = encryption::KeyPair::from_bytes( + &gateway_private_key.to_bytes(), + &gateway_public_key.to_bytes(), + ) + .unwrap(); let client_key_pair = encryption::KeyPair::new(&mut rng); - let gateway_static_public = - PublicKey::try_from(gateway_key_pair.public_key().to_bytes()).unwrap(); + let gateway_static_public = PublicKey::from(gateway_key_pair.public_key().to_bytes()); - let client_static_private = - StaticSecret::try_from(client_key_pair.private_key().to_bytes()).unwrap(); - let client_static_public = - PublicKey::try_from(client_key_pair.public_key().to_bytes()).unwrap(); + let client_static_private = StaticSecret::from(client_key_pair.private_key().to_bytes()); + let client_static_public = PublicKey::from(client_key_pair.public_key().to_bytes()); let client_dh = client_static_private.diffie_hellman(&gateway_static_public); let registration_in_progress = Arc::new(DashMap::new()); let client_registry = Arc::new(DashMap::new()); + let free_private_network_ips = Arc::new( + IpNetwork::from_str("10.1.0.0/24") + .unwrap() + .iter() + .map(|ip| (ip, true)) + .collect(), + ); + let client_private_ip = IpAddr::from_str("10.1.0.42").unwrap(); let state = WireguardAppState { inner: Some(WireguardAppStateInner { client_registry: Arc::clone(&client_registry), - dh_keypair: Arc::new(gateway_key_pair), + private_key: Arc::new(gateway_private_key), registration_in_progress: Arc::clone(®istration_in_progress), binding_port: 8080, + free_private_network_ips, }), }; @@ -186,11 +212,13 @@ mod test { let mut mac = HmacSha256::new_from_slice(client_dh.as_bytes()).unwrap(); mac.update(client_static_public.as_bytes()); + mac.update(client_private_ip.to_string().as_bytes()); mac.update(&nonce.to_le_bytes()); let mac = mac.finalize().into_bytes(); let finalized_message = ClientMessage::Final(GatewayClient { pub_key: PeerPublicKey::new(client_static_public), + private_ip: client_private_ip, mac: ClientMac::new(mac.as_slice().to_vec()), }); diff --git a/nym-node/src/http/router/api/v1/gateway/mod.rs b/nym-node/src/http/router/api/v1/gateway/mod.rs index d2bba50118..6b9ee71d14 100644 --- a/nym-node/src/http/router/api/v1/gateway/mod.rs +++ b/nym-node/src/http/router/api/v1/gateway/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; use axum::routing::get; diff --git a/nym-node/src/http/router/api/v1/gateway/root.rs b/nym-node/src/http/router/api/v1/gateway/root.rs index 526d1825dd..04edf2681a 100644 --- a/nym-node/src/http/router/api/v1/gateway/root.rs +++ b/nym-node/src/http/router/api/v1/gateway/root.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/health.rs b/nym-node/src/http/router/api/v1/health.rs index 08e9ff28d5..7b26a61c35 100644 --- a/nym-node/src/http/router/api/v1/health.rs +++ b/nym-node/src/http/router/api/v1/health.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::{FormattedResponse, OutputParams}; use crate::http::state::AppState; diff --git a/nym-node/src/http/router/api/v1/ip_packet_router/mod.rs b/nym-node/src/http/router/api/v1/ip_packet_router/mod.rs new file mode 100644 index 0000000000..ab6b2ed72e --- /dev/null +++ b/nym-node/src/http/router/api/v1/ip_packet_router/mod.rs @@ -0,0 +1,23 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use axum::routing::get; +use axum::Router; +use nym_node_requests::api::v1::ip_packet_router::models; + +pub mod root; + +#[derive(Debug, Clone, Default)] +pub struct Config { + pub details: Option, +} + +pub(crate) fn routes(config: Config) -> Router { + Router::new().route( + "/", + get({ + let ip_packet_router_details = config.details; + move |query| root::root_ip_packet_router(ip_packet_router_details, query) + }), + ) +} diff --git a/nym-node/src/http/router/api/v1/ip_packet_router/root.rs b/nym-node/src/http/router/api/v1/ip_packet_router/root.rs new file mode 100644 index 0000000000..498bf7c084 --- /dev/null +++ b/nym-node/src/http/router/api/v1/ip_packet_router/root.rs @@ -0,0 +1,33 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::http::router::api::{FormattedResponse, OutputParams}; +use axum::extract::Query; +use axum::http::StatusCode; +use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; + +/// Returns root network requester information +#[utoipa::path( + get, + path = "", + context_path = "/api/v1/ip-packet-router", + tag = "IP Packet Router", + responses( + (status = 501, description = "the endpoint hasn't been implemented yet"), + (status = 200, content( + ("application/json" = IpPacketRouter), + ("application/yaml" = IpPacketRouter) + )) + ), + params(OutputParams) +)] +pub(crate) async fn root_ip_packet_router( + details: Option, + Query(output): Query, +) -> Result { + let details = details.ok_or(StatusCode::NOT_IMPLEMENTED)?; + let output = output.output.unwrap_or_default(); + Ok(output.to_response(details)) +} + +pub type IpPacketRouterResponse = FormattedResponse; diff --git a/nym-node/src/http/router/api/v1/mixnode/mod.rs b/nym-node/src/http/router/api/v1/mixnode/mod.rs index 998a0edd3e..8072b5a822 100644 --- a/nym-node/src/http/router/api/v1/mixnode/mod.rs +++ b/nym-node/src/http/router/api/v1/mixnode/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::routing::get; use axum::Router; diff --git a/nym-node/src/http/router/api/v1/mixnode/root.rs b/nym-node/src/http/router/api/v1/mixnode/root.rs index 2b71fea15c..1ca216b55d 100644 --- a/nym-node/src/http/router/api/v1/mixnode/root.rs +++ b/nym-node/src/http/router/api/v1/mixnode/root.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/mod.rs b/nym-node/src/http/router/api/v1/mod.rs index cc81f9948f..97d7aff172 100644 --- a/nym-node/src/http/router/api/v1/mod.rs +++ b/nym-node/src/http/router/api/v1/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; use crate::http::state::AppState; @@ -9,6 +9,7 @@ use nym_node_requests::routes::api::v1; pub mod gateway; pub mod health; +pub mod ip_packet_router; pub mod mixnode; pub mod network_requester; pub mod node; @@ -20,6 +21,7 @@ pub struct Config { pub gateway: gateway::Config, pub mixnode: mixnode::Config, pub network_requester: network_requester::Config, + pub ip_packet_router: ip_packet_router::Config, } pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Router { @@ -34,6 +36,10 @@ pub(super) fn routes(config: Config, initial_wg_state: WireguardAppState) -> Rou v1::NETWORK_REQUESTER, network_requester::routes(config.network_requester), ) + .nest( + v1::IP_PACKET_ROUTER, + ip_packet_router::routes(config.ip_packet_router), + ) .merge(node::routes(config.node)) .merge(openapi::route()) } diff --git a/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs b/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs index a743f20514..ab649711f4 100644 --- a/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs +++ b/nym-node/src/http/router/api/v1/network_requester/exit_policy.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/network_requester/mod.rs b/nym-node/src/http/router/api/v1/network_requester/mod.rs index 7b2ebabf34..19f08cb368 100644 --- a/nym-node/src/http/router/api/v1/network_requester/mod.rs +++ b/nym-node/src/http/router/api/v1/network_requester/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::network_requester::exit_policy::node_exit_policy; use axum::routing::get; diff --git a/nym-node/src/http/router/api/v1/network_requester/root.rs b/nym-node/src/http/router/api/v1/network_requester/root.rs index a1849e2560..4294a4d348 100644 --- a/nym-node/src/http/router/api/v1/network_requester/root.rs +++ b/nym-node/src/http/router/api/v1/network_requester/root.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/node/build_information.rs b/nym-node/src/http/router/api/v1/node/build_information.rs index 1288d94dda..98bd47e691 100644 --- a/nym-node/src/http/router/api/v1/node/build_information.rs +++ b/nym-node/src/http/router/api/v1/node/build_information.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/node/host_information.rs b/nym-node/src/http/router/api/v1/node/host_information.rs index 3581de10bc..c52f00a58b 100644 --- a/nym-node/src/http/router/api/v1/node/host_information.rs +++ b/nym-node/src/http/router/api/v1/node/host_information.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/node/mod.rs b/nym-node/src/http/router/api/v1/node/mod.rs index 6644e75572..620a446090 100644 --- a/nym-node/src/http/router/api/v1/node/mod.rs +++ b/nym-node/src/http/router/api/v1/node/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::api::v1::node::build_information::build_information; use crate::http::api::v1::node::host_information::host_information; diff --git a/nym-node/src/http/router/api/v1/node/roles.rs b/nym-node/src/http/router/api/v1/node/roles.rs index 811954ed52..43648e77c0 100644 --- a/nym-node/src/http/router/api/v1/node/roles.rs +++ b/nym-node/src/http/router/api/v1/node/roles.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api::{FormattedResponse, OutputParams}; use axum::extract::Query; diff --git a/nym-node/src/http/router/api/v1/openapi.rs b/nym-node/src/http/router/api/v1/openapi.rs index 6302429f73..e23eca84f3 100644 --- a/nym-node/src/http/router/api/v1/openapi.rs +++ b/nym-node/src/http/router/api/v1/openapi.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::http::router::api; use crate::http::router::types::{ErrorResponse, RequestError}; @@ -27,6 +27,7 @@ use utoipa_swagger_ui::SwaggerUi; api::v1::mixnode::root::root_mixnode, api::v1::network_requester::root::root_network_requester, api::v1::network_requester::exit_policy::node_exit_policy, + api::v1::ip_packet_router::root::root_ip_packet_router, ), components( schemas( @@ -56,6 +57,7 @@ use utoipa_swagger_ui::SwaggerUi; api_requests::v1::network_requester::exit_policy::models::AddressPortPattern, api_requests::v1::network_requester::exit_policy::models::PortRange, api_requests::v1::network_requester::exit_policy::models::UsedExitPolicy, + api_requests::v1::ip_packet_router::models::IpPacketRouter, ), responses(RequestError) ) diff --git a/nym-node/src/http/router/landing_page.rs b/nym-node/src/http/router/landing_page.rs index 425d59c4de..8a0f28b017 100644 --- a/nym-node/src/http/router/landing_page.rs +++ b/nym-node/src/http/router/landing_page.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::response::Html; use axum::routing::get; diff --git a/nym-node/src/http/router/mod.rs b/nym-node/src/http/router/mod.rs index d24033f1bc..71de3c3c8d 100644 --- a/nym-node/src/http/router/mod.rs +++ b/nym-node/src/http/router/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::error::NymNodeError; pub use crate::http::api::v1::gateway::client_interfaces::wireguard::WireguardAppState; @@ -8,6 +8,7 @@ use crate::http::state::AppState; use crate::http::NymNodeHTTPServer; use axum::Router; use nym_node_requests::api::v1::gateway::models::{Gateway, Wireguard}; +use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter; use nym_node_requests::api::v1::mixnode::models::Mixnode; use nym_node_requests::api::v1::network_requester::exit_policy::models::UsedExitPolicy; use nym_node_requests::api::v1::network_requester::models::NetworkRequester; @@ -45,6 +46,7 @@ impl Config { gateway: Default::default(), mixnode: Default::default(), network_requester: Default::default(), + ip_packet_router: Default::default(), }, }, } @@ -94,6 +96,13 @@ impl Config { self.api.v1_config.network_requester.exit_policy = Some(exit_policy); self } + + #[must_use] + pub fn with_ip_packet_router(mut self, ip_packet_router: IpPacketRouter) -> Self { + self.api.v1_config.node.roles.ip_packet_router_enabled = true; + self.api.v1_config.ip_packet_router.details = Some(ip_packet_router); + self + } } pub struct NymNodeRouter { diff --git a/nym-node/src/http/router/types.rs b/nym-node/src/http/router/types.rs index 6533a00a25..6eec804c12 100644 --- a/nym-node/src/http/router/types.rs +++ b/nym-node/src/http/router/types.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; diff --git a/nym-node/src/http/state.rs b/nym-node/src/http/state.rs index ee7cd3aad2..e6141fd238 100644 --- a/nym-node/src/http/state.rs +++ b/nym-node/src/http/state.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use tokio::time::Instant; diff --git a/nym-node/src/lib.rs b/nym-node/src/lib.rs index e32c757b15..70e48aeec1 100644 --- a/nym-node/src/lib.rs +++ b/nym-node/src/lib.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only #![warn(clippy::expect_used)] #![warn(clippy::unwrap_used)] diff --git a/nym-node/src/wireguard/error.rs b/nym-node/src/wireguard/error.rs index 47b24af92c..3682017e37 100644 --- a/nym-node/src/wireguard/error.rs +++ b/nym-node/src/wireguard/error.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use thiserror::Error; diff --git a/nym-node/src/wireguard/mod.rs b/nym-node/src/wireguard/mod.rs index ad3a0d85cd..8c2b1fd10f 100644 --- a/nym-node/src/wireguard/mod.rs +++ b/nym-node/src/wireguard/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only // obviously most of the features currently reside in the gateway, // but let's start putting everything in here diff --git a/nym-node/src/wireguard/types.rs b/nym-node/src/wireguard/types.rs index 0243133005..e5eb607115 100644 --- a/nym-node/src/wireguard/types.rs +++ b/nym-node/src/wireguard/types.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub use nym_node_requests::api::v1::gateway::client_interfaces::wireguard::models::*; pub use nym_wireguard_types::registration::{GatewayClientRegistry, PendingRegistrations}; diff --git a/nym-outfox/src/format.rs b/nym-outfox/src/format.rs index a613c55894..1d0a3681c5 100644 --- a/nym-outfox/src/format.rs +++ b/nym-outfox/src/format.rs @@ -302,7 +302,7 @@ impl MixStageParameters { &nonce.into(), &[], &mut buffer[self.header_range()], - tag.as_slice().try_into().unwrap(), + tag.as_slice().into(), ) .map_err(|e| OutfoxError::ChaCha20Poly1305Error(e.to_string()))?; diff --git a/nym-vpn/ui/.eslintrc.cjs b/nym-vpn/ui/.eslintrc.cjs index 5c7c3e4c95..ff65dd7c03 100644 --- a/nym-vpn/ui/.eslintrc.cjs +++ b/nym-vpn/ui/.eslintrc.cjs @@ -4,18 +4,47 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', + 'plugin:jsx-a11y/recommended', 'plugin:react-hooks/recommended', + 'plugin:import/typescript', 'prettier', ], parser: '@typescript-eslint/parser', parserOptions: { project: './tsconfig.json', }, - plugins: ['@typescript-eslint'], + plugins: ['@typescript-eslint', 'import'], root: true, settings: { react: { version: 'detect', }, + // https://github.com/import-js/eslint-import-resolver-typescript#configuration + 'import/parsers': { + '@typescript-eslint/parser': ['.ts', '.tsx'], + }, + 'import/resolver': { + typescript: { + alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` + project: 'tsconfig.json', + }, + }, + }, + rules: { + 'sort-imports': [ + 'error', + { + ignoreDeclarationSort: true, + allowSeparatedGroups: true, + }, + ], + 'import/first': 'error', + 'import/order': [ + 'error', + { + groups: ['builtin', 'external', 'parent', 'sibling', 'index'], + }, + ], + 'import/extensions': ['error', 'never', { json: 'always', svg: 'always' }], }, }; diff --git a/nym-vpn/ui/README.md b/nym-vpn/ui/README.md index a1fe5876a6..8e685878d0 100644 --- a/nym-vpn/ui/README.md +++ b/nym-vpn/ui/README.md @@ -13,18 +13,67 @@ This is the application UI layer for the next NymVPN clients. Some system libraries are required depending on the host platform. Follow the instructions for your specific OS [here](https://tauri.app/v1/guides/getting-started/prerequisites) -To install run +To install: ``` yarn ``` +## Required config + +First you can provide a network configuration using en env file, +pick the relevant one [here](https://github.com/nymtech/nym/tree/develop/envs). +The mainnet config will be used by default if not provided. + +Then create the main app config file `config.toml` under `nym-vpn` +directory, full path is platform specific: + +- Linux: Resolves to `$XDG_CONFIG_HOME` or `$HOME/.config` +- macOS: Resolves to `$HOME/Library/Application Support` +- Windows: Resolves to `{FOLDERID_RoamingAppData}` + +For example on Linux the path would be `~/.config/nym-vpn/config.toml` + +```toml +# example config on Linux + +# path to the env config file if you provide one +env_config_file = "/home//.config/nym-vpn/qa.env" +``` + ## Dev ``` yarn dev:app ``` +or + +``` +cd src-tauri +cargo tauri dev +``` + +**NOTE** Starting a VPN connection requires root privileges as it will set up a link interface. +If you want to connect during development, you need to run the app as root, +likely using `sudo` (or equivalent) + +```shell +sudo -E RUST_LOG=debug cargo tauri dev +``` + +#### Logging + +Rust logging (standard output) is controlled by the `RUST_LOG` +env variable + +Example: + +``` +cd src-tauri +RUST_LOG=trace cargo tauri dev +``` + ## Dev in the browser For convenience and better development experience, we can run the @@ -43,20 +92,24 @@ When creating new tauri command, be sure to add the corresponding mock definition into `nym-vpn/ui/src/dev/tauri-cmd-mocks/` and update `nym-vpn/ui/src/dev/setup.ts` accordingly. +## Type bindings + +[ts-rs](https://github.com/Aleph-Alpha/ts-rs) can be used to generate +TS type definitions from Rust types + +To generate bindings, first +[annotate](https://github.com/Aleph-Alpha/ts-rs/blob/main/example/src/lib.rs) +Rust types, then run + +``` +cd src-tauri +cargo test +``` + +Generated TS types will be located in `src-tauri/bindings/` + ## Build -To build as a **shared library** - ``` -yarn build && cd src-tauri && cargo build --release --lib --features custom-protocol - -#alias yarn build:app ``` - -You can build for a different platform using [Cross](https://github.com/cross-rs/cross). -For example, to build for Windows on Linux: - -``` -cross build --target x86_64-pc-windows-gnu --release --lib --features custom-protocol -``` diff --git a/nym-vpn/ui/index.html b/nym-vpn/ui/index.html index 4472026329..a548f6417f 100644 --- a/nym-vpn/ui/index.html +++ b/nym-vpn/ui/index.html @@ -1,14 +1,14 @@ - + - Tauri + React + TS + NymVPN - -

+ +
diff --git a/nym-vpn/ui/package.json b/nym-vpn/ui/package.json index 2570661bcc..ecfd29f4b2 100644 --- a/nym-vpn/ui/package.json +++ b/nym-vpn/ui/package.json @@ -1,14 +1,14 @@ { "name": "nym-vpn-ui", "private": true, - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { "dev": "vite", - "dev:app": "WEBKIT_DISABLE_COMPOSITING_MODE=1 tauri dev", + "dev:app": "tauri dev", "dev:browser": "vite --mode dev-browser", "build": "tsc && vite build", - "build:app": "yarn build && cd src-tauri && cargo build --release --lib --features custom-protocol", + "build:app": "yarn tauri build", "preview": "vite preview", "lint": "eslint --ext .ts,.tsx src/", "lint:fix": "eslint --ext .js,.ts --fix src/", @@ -18,30 +18,39 @@ "tauri": "tauri" }, "dependencies": { - "@mui/base": "^5.0.0-beta.20", - "@mui/material": "^5.14.14", + "@headlessui/react": "^1.7.17", + "@headlessui/tailwindcss": "^0.2.0", + "@mui/base": "^5.0.0-beta.24", "@tauri-apps/api": "^1.5.0", "clsx": "^2.0.0", + "dayjs": "^1.11.10", + "i18next": "^23.7.6", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-i18next": "^14.0.0", + "react-router-dom": "^6.18.0" }, "devDependencies": { "@tauri-apps/cli": "^1.5.0", - "@types/react": "^18.2.31", - "@types/react-dom": "^18.2.14", + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "@vitejs/plugin-react-swc": "^3.3.2", "autoprefixer": "^10.4.16", "eslint": "^8.52.0", "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "postcss": "^8.4.31", - "postcss-import": "^15.1.0", + "postcss-import": "^16.0.0", "prettier": "^3.0.3", "tailwindcss": "^3.3.3", "typescript": "^5.0.2", - "vite": "^4.4.5" + "vite": "^5.0.5" } } diff --git a/nym-vpn/ui/public/flags/ac.svg b/nym-vpn/ui/public/flags/ac.svg new file mode 100644 index 0000000000..5d5914549a --- /dev/null +++ b/nym-vpn/ui/public/flags/ac.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ad.svg b/nym-vpn/ui/public/flags/ad.svg new file mode 100644 index 0000000000..8e9d368f82 --- /dev/null +++ b/nym-vpn/ui/public/flags/ad.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ae.svg b/nym-vpn/ui/public/flags/ae.svg new file mode 100644 index 0000000000..779b9f50b5 --- /dev/null +++ b/nym-vpn/ui/public/flags/ae.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/af.svg b/nym-vpn/ui/public/flags/af.svg new file mode 100644 index 0000000000..eb83faf831 --- /dev/null +++ b/nym-vpn/ui/public/flags/af.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ag.svg b/nym-vpn/ui/public/flags/ag.svg new file mode 100644 index 0000000000..8ab60fe831 --- /dev/null +++ b/nym-vpn/ui/public/flags/ag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ai.svg b/nym-vpn/ui/public/flags/ai.svg new file mode 100644 index 0000000000..5530eb65c3 --- /dev/null +++ b/nym-vpn/ui/public/flags/ai.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/al.svg b/nym-vpn/ui/public/flags/al.svg new file mode 100644 index 0000000000..5bdcf48ba7 --- /dev/null +++ b/nym-vpn/ui/public/flags/al.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/am.svg b/nym-vpn/ui/public/flags/am.svg new file mode 100644 index 0000000000..537a16f355 --- /dev/null +++ b/nym-vpn/ui/public/flags/am.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/an.svg b/nym-vpn/ui/public/flags/an.svg new file mode 100644 index 0000000000..cad94ecca1 --- /dev/null +++ b/nym-vpn/ui/public/flags/an.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ao.svg b/nym-vpn/ui/public/flags/ao.svg new file mode 100644 index 0000000000..c21abd44d8 --- /dev/null +++ b/nym-vpn/ui/public/flags/ao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/aq.svg b/nym-vpn/ui/public/flags/aq.svg new file mode 100644 index 0000000000..e09d43f202 --- /dev/null +++ b/nym-vpn/ui/public/flags/aq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ar.svg b/nym-vpn/ui/public/flags/ar.svg new file mode 100644 index 0000000000..ce7316579e --- /dev/null +++ b/nym-vpn/ui/public/flags/ar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/as.svg b/nym-vpn/ui/public/flags/as.svg new file mode 100644 index 0000000000..0c58705301 --- /dev/null +++ b/nym-vpn/ui/public/flags/as.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/at.svg b/nym-vpn/ui/public/flags/at.svg new file mode 100644 index 0000000000..73a87c8870 --- /dev/null +++ b/nym-vpn/ui/public/flags/at.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/au.svg b/nym-vpn/ui/public/flags/au.svg new file mode 100644 index 0000000000..bccaab301b --- /dev/null +++ b/nym-vpn/ui/public/flags/au.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/aw.svg b/nym-vpn/ui/public/flags/aw.svg new file mode 100644 index 0000000000..872f466b6c --- /dev/null +++ b/nym-vpn/ui/public/flags/aw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ax.svg b/nym-vpn/ui/public/flags/ax.svg new file mode 100644 index 0000000000..f30a7ec4fc --- /dev/null +++ b/nym-vpn/ui/public/flags/ax.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/az.svg b/nym-vpn/ui/public/flags/az.svg new file mode 100644 index 0000000000..7308f67bc2 --- /dev/null +++ b/nym-vpn/ui/public/flags/az.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ba.svg b/nym-vpn/ui/public/flags/ba.svg new file mode 100644 index 0000000000..62babc7a98 --- /dev/null +++ b/nym-vpn/ui/public/flags/ba.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bb.svg b/nym-vpn/ui/public/flags/bb.svg new file mode 100644 index 0000000000..d31472b93f --- /dev/null +++ b/nym-vpn/ui/public/flags/bb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bd.svg b/nym-vpn/ui/public/flags/bd.svg new file mode 100644 index 0000000000..e2a36bbe39 --- /dev/null +++ b/nym-vpn/ui/public/flags/bd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/be.svg b/nym-vpn/ui/public/flags/be.svg new file mode 100644 index 0000000000..3825681949 --- /dev/null +++ b/nym-vpn/ui/public/flags/be.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bf.svg b/nym-vpn/ui/public/flags/bf.svg new file mode 100644 index 0000000000..641867b59e --- /dev/null +++ b/nym-vpn/ui/public/flags/bf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bg.svg b/nym-vpn/ui/public/flags/bg.svg new file mode 100644 index 0000000000..43c143c007 --- /dev/null +++ b/nym-vpn/ui/public/flags/bg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bh.svg b/nym-vpn/ui/public/flags/bh.svg new file mode 100644 index 0000000000..aa05f5e96e --- /dev/null +++ b/nym-vpn/ui/public/flags/bh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bi.svg b/nym-vpn/ui/public/flags/bi.svg new file mode 100644 index 0000000000..0abd98bd78 --- /dev/null +++ b/nym-vpn/ui/public/flags/bi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bj.svg b/nym-vpn/ui/public/flags/bj.svg new file mode 100644 index 0000000000..7a18b52d2b --- /dev/null +++ b/nym-vpn/ui/public/flags/bj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bl.svg b/nym-vpn/ui/public/flags/bl.svg new file mode 100644 index 0000000000..e03311749e --- /dev/null +++ b/nym-vpn/ui/public/flags/bl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bm.svg b/nym-vpn/ui/public/flags/bm.svg new file mode 100644 index 0000000000..d726a1fa02 --- /dev/null +++ b/nym-vpn/ui/public/flags/bm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bn.svg b/nym-vpn/ui/public/flags/bn.svg new file mode 100644 index 0000000000..c52c6c1475 --- /dev/null +++ b/nym-vpn/ui/public/flags/bn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bo.svg b/nym-vpn/ui/public/flags/bo.svg new file mode 100644 index 0000000000..3247e6bb88 --- /dev/null +++ b/nym-vpn/ui/public/flags/bo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bq.svg b/nym-vpn/ui/public/flags/bq.svg new file mode 100644 index 0000000000..367ed4581a --- /dev/null +++ b/nym-vpn/ui/public/flags/bq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/br.svg b/nym-vpn/ui/public/flags/br.svg new file mode 100644 index 0000000000..44aacbd43d --- /dev/null +++ b/nym-vpn/ui/public/flags/br.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bs.svg b/nym-vpn/ui/public/flags/bs.svg new file mode 100644 index 0000000000..3c3f9aa3b9 --- /dev/null +++ b/nym-vpn/ui/public/flags/bs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bt.svg b/nym-vpn/ui/public/flags/bt.svg new file mode 100644 index 0000000000..be25d68c79 --- /dev/null +++ b/nym-vpn/ui/public/flags/bt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bv.svg b/nym-vpn/ui/public/flags/bv.svg new file mode 100644 index 0000000000..bc702a397d --- /dev/null +++ b/nym-vpn/ui/public/flags/bv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bw.svg b/nym-vpn/ui/public/flags/bw.svg new file mode 100644 index 0000000000..6b63835e34 --- /dev/null +++ b/nym-vpn/ui/public/flags/bw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/by.svg b/nym-vpn/ui/public/flags/by.svg new file mode 100644 index 0000000000..4d2cc0756c --- /dev/null +++ b/nym-vpn/ui/public/flags/by.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/bz.svg b/nym-vpn/ui/public/flags/bz.svg new file mode 100644 index 0000000000..ece2b9f446 --- /dev/null +++ b/nym-vpn/ui/public/flags/bz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ca.svg b/nym-vpn/ui/public/flags/ca.svg new file mode 100644 index 0000000000..3348f9eea3 --- /dev/null +++ b/nym-vpn/ui/public/flags/ca.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cc.svg b/nym-vpn/ui/public/flags/cc.svg new file mode 100644 index 0000000000..2a4c0979e7 --- /dev/null +++ b/nym-vpn/ui/public/flags/cc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cd.svg b/nym-vpn/ui/public/flags/cd.svg new file mode 100644 index 0000000000..17ef2e2750 --- /dev/null +++ b/nym-vpn/ui/public/flags/cd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cf.svg b/nym-vpn/ui/public/flags/cf.svg new file mode 100644 index 0000000000..dd94895e0d --- /dev/null +++ b/nym-vpn/ui/public/flags/cf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cg.svg b/nym-vpn/ui/public/flags/cg.svg new file mode 100644 index 0000000000..620c50ad17 --- /dev/null +++ b/nym-vpn/ui/public/flags/cg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ch.svg b/nym-vpn/ui/public/flags/ch.svg new file mode 100644 index 0000000000..3943b1fbb5 --- /dev/null +++ b/nym-vpn/ui/public/flags/ch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ci.svg b/nym-vpn/ui/public/flags/ci.svg new file mode 100644 index 0000000000..1d21f102e3 --- /dev/null +++ b/nym-vpn/ui/public/flags/ci.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ck.svg b/nym-vpn/ui/public/flags/ck.svg new file mode 100644 index 0000000000..5ad7aeeaf1 --- /dev/null +++ b/nym-vpn/ui/public/flags/ck.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cl.svg b/nym-vpn/ui/public/flags/cl.svg new file mode 100644 index 0000000000..a7a4f89988 --- /dev/null +++ b/nym-vpn/ui/public/flags/cl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cm.svg b/nym-vpn/ui/public/flags/cm.svg new file mode 100644 index 0000000000..fad286c5d4 --- /dev/null +++ b/nym-vpn/ui/public/flags/cm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cn.svg b/nym-vpn/ui/public/flags/cn.svg new file mode 100644 index 0000000000..a61d24ca4d --- /dev/null +++ b/nym-vpn/ui/public/flags/cn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/co.svg b/nym-vpn/ui/public/flags/co.svg new file mode 100644 index 0000000000..ac562e38dd --- /dev/null +++ b/nym-vpn/ui/public/flags/co.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cp.svg b/nym-vpn/ui/public/flags/cp.svg new file mode 100644 index 0000000000..54000084f7 --- /dev/null +++ b/nym-vpn/ui/public/flags/cp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cq.svg b/nym-vpn/ui/public/flags/cq.svg new file mode 100644 index 0000000000..90c23bd7c4 --- /dev/null +++ b/nym-vpn/ui/public/flags/cq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cr.svg b/nym-vpn/ui/public/flags/cr.svg new file mode 100644 index 0000000000..ed16d798cd --- /dev/null +++ b/nym-vpn/ui/public/flags/cr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cu.svg b/nym-vpn/ui/public/flags/cu.svg new file mode 100644 index 0000000000..63758bd6ff --- /dev/null +++ b/nym-vpn/ui/public/flags/cu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cv.svg b/nym-vpn/ui/public/flags/cv.svg new file mode 100644 index 0000000000..a49dc54004 --- /dev/null +++ b/nym-vpn/ui/public/flags/cv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cw.svg b/nym-vpn/ui/public/flags/cw.svg new file mode 100644 index 0000000000..5e8772d859 --- /dev/null +++ b/nym-vpn/ui/public/flags/cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cx.svg b/nym-vpn/ui/public/flags/cx.svg new file mode 100644 index 0000000000..574ac205a9 --- /dev/null +++ b/nym-vpn/ui/public/flags/cx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cy.svg b/nym-vpn/ui/public/flags/cy.svg new file mode 100644 index 0000000000..126b94db22 --- /dev/null +++ b/nym-vpn/ui/public/flags/cy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/cz.svg b/nym-vpn/ui/public/flags/cz.svg new file mode 100644 index 0000000000..70668af648 --- /dev/null +++ b/nym-vpn/ui/public/flags/cz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/de.svg b/nym-vpn/ui/public/flags/de.svg new file mode 100644 index 0000000000..efde34193a --- /dev/null +++ b/nym-vpn/ui/public/flags/de.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/dg.svg b/nym-vpn/ui/public/flags/dg.svg new file mode 100644 index 0000000000..34dc359d28 --- /dev/null +++ b/nym-vpn/ui/public/flags/dg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/dj.svg b/nym-vpn/ui/public/flags/dj.svg new file mode 100644 index 0000000000..ac41c5d919 --- /dev/null +++ b/nym-vpn/ui/public/flags/dj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/dk.svg b/nym-vpn/ui/public/flags/dk.svg new file mode 100644 index 0000000000..9a0cb4c0f7 --- /dev/null +++ b/nym-vpn/ui/public/flags/dk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/dm.svg b/nym-vpn/ui/public/flags/dm.svg new file mode 100644 index 0000000000..c716af8df2 --- /dev/null +++ b/nym-vpn/ui/public/flags/dm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/do.svg b/nym-vpn/ui/public/flags/do.svg new file mode 100644 index 0000000000..5820015077 --- /dev/null +++ b/nym-vpn/ui/public/flags/do.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/dz.svg b/nym-vpn/ui/public/flags/dz.svg new file mode 100644 index 0000000000..b6287718c4 --- /dev/null +++ b/nym-vpn/ui/public/flags/dz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ea.svg b/nym-vpn/ui/public/flags/ea.svg new file mode 100644 index 0000000000..715a783f1f --- /dev/null +++ b/nym-vpn/ui/public/flags/ea.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ec.svg b/nym-vpn/ui/public/flags/ec.svg new file mode 100644 index 0000000000..c0a0b225bb --- /dev/null +++ b/nym-vpn/ui/public/flags/ec.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ee.svg b/nym-vpn/ui/public/flags/ee.svg new file mode 100644 index 0000000000..05e9d26d5d --- /dev/null +++ b/nym-vpn/ui/public/flags/ee.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/eg.svg b/nym-vpn/ui/public/flags/eg.svg new file mode 100644 index 0000000000..7c00ce5c21 --- /dev/null +++ b/nym-vpn/ui/public/flags/eg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/eh.svg b/nym-vpn/ui/public/flags/eh.svg new file mode 100644 index 0000000000..2bc578f594 --- /dev/null +++ b/nym-vpn/ui/public/flags/eh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/er.svg b/nym-vpn/ui/public/flags/er.svg new file mode 100644 index 0000000000..80fb3abf6f --- /dev/null +++ b/nym-vpn/ui/public/flags/er.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/es.svg b/nym-vpn/ui/public/flags/es.svg new file mode 100644 index 0000000000..833ad88a44 --- /dev/null +++ b/nym-vpn/ui/public/flags/es.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/et.svg b/nym-vpn/ui/public/flags/et.svg new file mode 100644 index 0000000000..a76da17a05 --- /dev/null +++ b/nym-vpn/ui/public/flags/et.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/eu.svg b/nym-vpn/ui/public/flags/eu.svg new file mode 100644 index 0000000000..6f584056a1 --- /dev/null +++ b/nym-vpn/ui/public/flags/eu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fi.svg b/nym-vpn/ui/public/flags/fi.svg new file mode 100644 index 0000000000..cac8507ca2 --- /dev/null +++ b/nym-vpn/ui/public/flags/fi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fj.svg b/nym-vpn/ui/public/flags/fj.svg new file mode 100644 index 0000000000..21ba9aaafe --- /dev/null +++ b/nym-vpn/ui/public/flags/fj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fk.svg b/nym-vpn/ui/public/flags/fk.svg new file mode 100644 index 0000000000..2ef39fa5d7 --- /dev/null +++ b/nym-vpn/ui/public/flags/fk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fm.svg b/nym-vpn/ui/public/flags/fm.svg new file mode 100644 index 0000000000..f0b3118002 --- /dev/null +++ b/nym-vpn/ui/public/flags/fm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fo.svg b/nym-vpn/ui/public/flags/fo.svg new file mode 100644 index 0000000000..27abfe72ae --- /dev/null +++ b/nym-vpn/ui/public/flags/fo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fr.svg b/nym-vpn/ui/public/flags/fr.svg new file mode 100644 index 0000000000..0fe5619a72 --- /dev/null +++ b/nym-vpn/ui/public/flags/fr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/fx.svg b/nym-vpn/ui/public/flags/fx.svg new file mode 100644 index 0000000000..0fe5619a72 --- /dev/null +++ b/nym-vpn/ui/public/flags/fx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ga.svg b/nym-vpn/ui/public/flags/ga.svg new file mode 100644 index 0000000000..b7ca0305c1 --- /dev/null +++ b/nym-vpn/ui/public/flags/ga.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gb.svg b/nym-vpn/ui/public/flags/gb.svg new file mode 100644 index 0000000000..4dca1e7828 --- /dev/null +++ b/nym-vpn/ui/public/flags/gb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gd.svg b/nym-vpn/ui/public/flags/gd.svg new file mode 100644 index 0000000000..ac39ac20bf --- /dev/null +++ b/nym-vpn/ui/public/flags/gd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ge.svg b/nym-vpn/ui/public/flags/ge.svg new file mode 100644 index 0000000000..199569aae5 --- /dev/null +++ b/nym-vpn/ui/public/flags/ge.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gf.svg b/nym-vpn/ui/public/flags/gf.svg new file mode 100644 index 0000000000..6b822a8574 --- /dev/null +++ b/nym-vpn/ui/public/flags/gf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gg.svg b/nym-vpn/ui/public/flags/gg.svg new file mode 100644 index 0000000000..d948f912d1 --- /dev/null +++ b/nym-vpn/ui/public/flags/gg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gh.svg b/nym-vpn/ui/public/flags/gh.svg new file mode 100644 index 0000000000..fed2c37e74 --- /dev/null +++ b/nym-vpn/ui/public/flags/gh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gi.svg b/nym-vpn/ui/public/flags/gi.svg new file mode 100644 index 0000000000..4327dacdcb --- /dev/null +++ b/nym-vpn/ui/public/flags/gi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gl.svg b/nym-vpn/ui/public/flags/gl.svg new file mode 100644 index 0000000000..cf8e3c8009 --- /dev/null +++ b/nym-vpn/ui/public/flags/gl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gm.svg b/nym-vpn/ui/public/flags/gm.svg new file mode 100644 index 0000000000..0ce0dc360d --- /dev/null +++ b/nym-vpn/ui/public/flags/gm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gn.svg b/nym-vpn/ui/public/flags/gn.svg new file mode 100644 index 0000000000..bc62f79a47 --- /dev/null +++ b/nym-vpn/ui/public/flags/gn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gp.svg b/nym-vpn/ui/public/flags/gp.svg new file mode 100644 index 0000000000..6ab4bd630a --- /dev/null +++ b/nym-vpn/ui/public/flags/gp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gq.svg b/nym-vpn/ui/public/flags/gq.svg new file mode 100644 index 0000000000..3f2050f991 --- /dev/null +++ b/nym-vpn/ui/public/flags/gq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gr.svg b/nym-vpn/ui/public/flags/gr.svg new file mode 100644 index 0000000000..5e991bc75a --- /dev/null +++ b/nym-vpn/ui/public/flags/gr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gs.svg b/nym-vpn/ui/public/flags/gs.svg new file mode 100644 index 0000000000..ed468e07a9 --- /dev/null +++ b/nym-vpn/ui/public/flags/gs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gt.svg b/nym-vpn/ui/public/flags/gt.svg new file mode 100644 index 0000000000..deaefb8286 --- /dev/null +++ b/nym-vpn/ui/public/flags/gt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gu.svg b/nym-vpn/ui/public/flags/gu.svg new file mode 100644 index 0000000000..9cc1a885c0 --- /dev/null +++ b/nym-vpn/ui/public/flags/gu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gw.svg b/nym-vpn/ui/public/flags/gw.svg new file mode 100644 index 0000000000..862416e5e4 --- /dev/null +++ b/nym-vpn/ui/public/flags/gw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/gy.svg b/nym-vpn/ui/public/flags/gy.svg new file mode 100644 index 0000000000..c43af53e58 --- /dev/null +++ b/nym-vpn/ui/public/flags/gy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/hk.svg b/nym-vpn/ui/public/flags/hk.svg new file mode 100644 index 0000000000..7b05101111 --- /dev/null +++ b/nym-vpn/ui/public/flags/hk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/hm.svg b/nym-vpn/ui/public/flags/hm.svg new file mode 100644 index 0000000000..bccaab301b --- /dev/null +++ b/nym-vpn/ui/public/flags/hm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/hn.svg b/nym-vpn/ui/public/flags/hn.svg new file mode 100644 index 0000000000..21f8b1a95e --- /dev/null +++ b/nym-vpn/ui/public/flags/hn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/hr.svg b/nym-vpn/ui/public/flags/hr.svg new file mode 100644 index 0000000000..5ddd7188b9 --- /dev/null +++ b/nym-vpn/ui/public/flags/hr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ht.svg b/nym-vpn/ui/public/flags/ht.svg new file mode 100644 index 0000000000..5423c4e2a2 --- /dev/null +++ b/nym-vpn/ui/public/flags/ht.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/hu.svg b/nym-vpn/ui/public/flags/hu.svg new file mode 100644 index 0000000000..a0a71b05ee --- /dev/null +++ b/nym-vpn/ui/public/flags/hu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ic.svg b/nym-vpn/ui/public/flags/ic.svg new file mode 100644 index 0000000000..a9d4433756 --- /dev/null +++ b/nym-vpn/ui/public/flags/ic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/id.svg b/nym-vpn/ui/public/flags/id.svg new file mode 100644 index 0000000000..5879cb4270 --- /dev/null +++ b/nym-vpn/ui/public/flags/id.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ie.svg b/nym-vpn/ui/public/flags/ie.svg new file mode 100644 index 0000000000..a680a7f2db --- /dev/null +++ b/nym-vpn/ui/public/flags/ie.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/il.svg b/nym-vpn/ui/public/flags/il.svg new file mode 100644 index 0000000000..35c11936ec --- /dev/null +++ b/nym-vpn/ui/public/flags/il.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/im.svg b/nym-vpn/ui/public/flags/im.svg new file mode 100644 index 0000000000..96f8f6d35f --- /dev/null +++ b/nym-vpn/ui/public/flags/im.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/in.svg b/nym-vpn/ui/public/flags/in.svg new file mode 100644 index 0000000000..2f070cbd6c --- /dev/null +++ b/nym-vpn/ui/public/flags/in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/io.svg b/nym-vpn/ui/public/flags/io.svg new file mode 100644 index 0000000000..34dc359d28 --- /dev/null +++ b/nym-vpn/ui/public/flags/io.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/iq.svg b/nym-vpn/ui/public/flags/iq.svg new file mode 100644 index 0000000000..376b6972a8 --- /dev/null +++ b/nym-vpn/ui/public/flags/iq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ir.svg b/nym-vpn/ui/public/flags/ir.svg new file mode 100644 index 0000000000..883fe5c451 --- /dev/null +++ b/nym-vpn/ui/public/flags/ir.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/is.svg b/nym-vpn/ui/public/flags/is.svg new file mode 100644 index 0000000000..5a2706c0d3 --- /dev/null +++ b/nym-vpn/ui/public/flags/is.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/it.svg b/nym-vpn/ui/public/flags/it.svg new file mode 100644 index 0000000000..56b4fb3008 --- /dev/null +++ b/nym-vpn/ui/public/flags/it.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/je.svg b/nym-vpn/ui/public/flags/je.svg new file mode 100644 index 0000000000..6198cafcb9 --- /dev/null +++ b/nym-vpn/ui/public/flags/je.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/jm.svg b/nym-vpn/ui/public/flags/jm.svg new file mode 100644 index 0000000000..410bf0308c --- /dev/null +++ b/nym-vpn/ui/public/flags/jm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/jo.svg b/nym-vpn/ui/public/flags/jo.svg new file mode 100644 index 0000000000..e725de0b4b --- /dev/null +++ b/nym-vpn/ui/public/flags/jo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/jp.svg b/nym-vpn/ui/public/flags/jp.svg new file mode 100644 index 0000000000..31f064f5d5 --- /dev/null +++ b/nym-vpn/ui/public/flags/jp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ke.svg b/nym-vpn/ui/public/flags/ke.svg new file mode 100644 index 0000000000..d2e7f43757 --- /dev/null +++ b/nym-vpn/ui/public/flags/ke.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kg.svg b/nym-vpn/ui/public/flags/kg.svg new file mode 100644 index 0000000000..11103bcc5d --- /dev/null +++ b/nym-vpn/ui/public/flags/kg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kh.svg b/nym-vpn/ui/public/flags/kh.svg new file mode 100644 index 0000000000..8277797c07 --- /dev/null +++ b/nym-vpn/ui/public/flags/kh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ki.svg b/nym-vpn/ui/public/flags/ki.svg new file mode 100644 index 0000000000..3d4d5c8397 --- /dev/null +++ b/nym-vpn/ui/public/flags/ki.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/km.svg b/nym-vpn/ui/public/flags/km.svg new file mode 100644 index 0000000000..d97952b6c9 --- /dev/null +++ b/nym-vpn/ui/public/flags/km.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kn.svg b/nym-vpn/ui/public/flags/kn.svg new file mode 100644 index 0000000000..1aa7f1520f --- /dev/null +++ b/nym-vpn/ui/public/flags/kn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kp.svg b/nym-vpn/ui/public/flags/kp.svg new file mode 100644 index 0000000000..83a1ca552f --- /dev/null +++ b/nym-vpn/ui/public/flags/kp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kr.svg b/nym-vpn/ui/public/flags/kr.svg new file mode 100644 index 0000000000..1056107339 --- /dev/null +++ b/nym-vpn/ui/public/flags/kr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kw.svg b/nym-vpn/ui/public/flags/kw.svg new file mode 100644 index 0000000000..6b14a167ea --- /dev/null +++ b/nym-vpn/ui/public/flags/kw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ky.svg b/nym-vpn/ui/public/flags/ky.svg new file mode 100644 index 0000000000..0042a7f79c --- /dev/null +++ b/nym-vpn/ui/public/flags/ky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/kz.svg b/nym-vpn/ui/public/flags/kz.svg new file mode 100644 index 0000000000..9fd8b6d11b --- /dev/null +++ b/nym-vpn/ui/public/flags/kz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/la.svg b/nym-vpn/ui/public/flags/la.svg new file mode 100644 index 0000000000..aaa0589730 --- /dev/null +++ b/nym-vpn/ui/public/flags/la.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lb.svg b/nym-vpn/ui/public/flags/lb.svg new file mode 100644 index 0000000000..e30e722199 --- /dev/null +++ b/nym-vpn/ui/public/flags/lb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lc.svg b/nym-vpn/ui/public/flags/lc.svg new file mode 100644 index 0000000000..8a1fff1509 --- /dev/null +++ b/nym-vpn/ui/public/flags/lc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/li.svg b/nym-vpn/ui/public/flags/li.svg new file mode 100644 index 0000000000..495815f55c --- /dev/null +++ b/nym-vpn/ui/public/flags/li.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lk.svg b/nym-vpn/ui/public/flags/lk.svg new file mode 100644 index 0000000000..2fb7256ecf --- /dev/null +++ b/nym-vpn/ui/public/flags/lk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lr.svg b/nym-vpn/ui/public/flags/lr.svg new file mode 100644 index 0000000000..37e8e2878b --- /dev/null +++ b/nym-vpn/ui/public/flags/lr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ls.svg b/nym-vpn/ui/public/flags/ls.svg new file mode 100644 index 0000000000..99024b5287 --- /dev/null +++ b/nym-vpn/ui/public/flags/ls.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lt.svg b/nym-vpn/ui/public/flags/lt.svg new file mode 100644 index 0000000000..3d5ff126d4 --- /dev/null +++ b/nym-vpn/ui/public/flags/lt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lu.svg b/nym-vpn/ui/public/flags/lu.svg new file mode 100644 index 0000000000..5e226fe95c --- /dev/null +++ b/nym-vpn/ui/public/flags/lu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/lv.svg b/nym-vpn/ui/public/flags/lv.svg new file mode 100644 index 0000000000..9152e7a00c --- /dev/null +++ b/nym-vpn/ui/public/flags/lv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ly.svg b/nym-vpn/ui/public/flags/ly.svg new file mode 100644 index 0000000000..bb7e16ecd6 --- /dev/null +++ b/nym-vpn/ui/public/flags/ly.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ma.svg b/nym-vpn/ui/public/flags/ma.svg new file mode 100644 index 0000000000..30cd031c96 --- /dev/null +++ b/nym-vpn/ui/public/flags/ma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mc.svg b/nym-vpn/ui/public/flags/mc.svg new file mode 100644 index 0000000000..2f54af2f6f --- /dev/null +++ b/nym-vpn/ui/public/flags/mc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/md.svg b/nym-vpn/ui/public/flags/md.svg new file mode 100644 index 0000000000..d5e76979a9 --- /dev/null +++ b/nym-vpn/ui/public/flags/md.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/me.svg b/nym-vpn/ui/public/flags/me.svg new file mode 100644 index 0000000000..2c03aaf3ef --- /dev/null +++ b/nym-vpn/ui/public/flags/me.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mf.svg b/nym-vpn/ui/public/flags/mf.svg new file mode 100644 index 0000000000..6719335892 --- /dev/null +++ b/nym-vpn/ui/public/flags/mf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mg.svg b/nym-vpn/ui/public/flags/mg.svg new file mode 100644 index 0000000000..a9fa058f16 --- /dev/null +++ b/nym-vpn/ui/public/flags/mg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mh.svg b/nym-vpn/ui/public/flags/mh.svg new file mode 100644 index 0000000000..ccade87c7c --- /dev/null +++ b/nym-vpn/ui/public/flags/mh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mk.svg b/nym-vpn/ui/public/flags/mk.svg new file mode 100644 index 0000000000..4a90573747 --- /dev/null +++ b/nym-vpn/ui/public/flags/mk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ml.svg b/nym-vpn/ui/public/flags/ml.svg new file mode 100644 index 0000000000..181215e70d --- /dev/null +++ b/nym-vpn/ui/public/flags/ml.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mm.svg b/nym-vpn/ui/public/flags/mm.svg new file mode 100644 index 0000000000..115d013a2d --- /dev/null +++ b/nym-vpn/ui/public/flags/mm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mn.svg b/nym-vpn/ui/public/flags/mn.svg new file mode 100644 index 0000000000..5b573ca669 --- /dev/null +++ b/nym-vpn/ui/public/flags/mn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mo.svg b/nym-vpn/ui/public/flags/mo.svg new file mode 100644 index 0000000000..1b49023db2 --- /dev/null +++ b/nym-vpn/ui/public/flags/mo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mp.svg b/nym-vpn/ui/public/flags/mp.svg new file mode 100644 index 0000000000..f54f65752d --- /dev/null +++ b/nym-vpn/ui/public/flags/mp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mq.svg b/nym-vpn/ui/public/flags/mq.svg new file mode 100644 index 0000000000..537736c444 --- /dev/null +++ b/nym-vpn/ui/public/flags/mq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mr.svg b/nym-vpn/ui/public/flags/mr.svg new file mode 100644 index 0000000000..4833a6fd1f --- /dev/null +++ b/nym-vpn/ui/public/flags/mr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ms.svg b/nym-vpn/ui/public/flags/ms.svg new file mode 100644 index 0000000000..e56e9b45e0 --- /dev/null +++ b/nym-vpn/ui/public/flags/ms.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mt.svg b/nym-vpn/ui/public/flags/mt.svg new file mode 100644 index 0000000000..6ee551ddf9 --- /dev/null +++ b/nym-vpn/ui/public/flags/mt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mu.svg b/nym-vpn/ui/public/flags/mu.svg new file mode 100644 index 0000000000..bc37ac4b41 --- /dev/null +++ b/nym-vpn/ui/public/flags/mu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mv.svg b/nym-vpn/ui/public/flags/mv.svg new file mode 100644 index 0000000000..a01de4554f --- /dev/null +++ b/nym-vpn/ui/public/flags/mv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mw.svg b/nym-vpn/ui/public/flags/mw.svg new file mode 100644 index 0000000000..76ed981cc0 --- /dev/null +++ b/nym-vpn/ui/public/flags/mw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mx.svg b/nym-vpn/ui/public/flags/mx.svg new file mode 100644 index 0000000000..f7d3230f98 --- /dev/null +++ b/nym-vpn/ui/public/flags/mx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/my.svg b/nym-vpn/ui/public/flags/my.svg new file mode 100644 index 0000000000..605c5ce181 --- /dev/null +++ b/nym-vpn/ui/public/flags/my.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/mz.svg b/nym-vpn/ui/public/flags/mz.svg new file mode 100644 index 0000000000..6d8d035c84 --- /dev/null +++ b/nym-vpn/ui/public/flags/mz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/na.svg b/nym-vpn/ui/public/flags/na.svg new file mode 100644 index 0000000000..0df1f168a5 --- /dev/null +++ b/nym-vpn/ui/public/flags/na.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nc.svg b/nym-vpn/ui/public/flags/nc.svg new file mode 100644 index 0000000000..88488700b4 --- /dev/null +++ b/nym-vpn/ui/public/flags/nc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ne.svg b/nym-vpn/ui/public/flags/ne.svg new file mode 100644 index 0000000000..89e18a822e --- /dev/null +++ b/nym-vpn/ui/public/flags/ne.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nf.svg b/nym-vpn/ui/public/flags/nf.svg new file mode 100644 index 0000000000..4c9ef6221e --- /dev/null +++ b/nym-vpn/ui/public/flags/nf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ng.svg b/nym-vpn/ui/public/flags/ng.svg new file mode 100644 index 0000000000..ee20b59a7c --- /dev/null +++ b/nym-vpn/ui/public/flags/ng.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ni.svg b/nym-vpn/ui/public/flags/ni.svg new file mode 100644 index 0000000000..89fcd2597c --- /dev/null +++ b/nym-vpn/ui/public/flags/ni.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nl.svg b/nym-vpn/ui/public/flags/nl.svg new file mode 100644 index 0000000000..25b6c9100f --- /dev/null +++ b/nym-vpn/ui/public/flags/nl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/no.svg b/nym-vpn/ui/public/flags/no.svg new file mode 100644 index 0000000000..bc702a397d --- /dev/null +++ b/nym-vpn/ui/public/flags/no.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/np.svg b/nym-vpn/ui/public/flags/np.svg new file mode 100644 index 0000000000..50b1e3dba4 --- /dev/null +++ b/nym-vpn/ui/public/flags/np.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nr.svg b/nym-vpn/ui/public/flags/nr.svg new file mode 100644 index 0000000000..b3c755f4f0 --- /dev/null +++ b/nym-vpn/ui/public/flags/nr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nu.svg b/nym-vpn/ui/public/flags/nu.svg new file mode 100644 index 0000000000..83d663b839 --- /dev/null +++ b/nym-vpn/ui/public/flags/nu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/nz.svg b/nym-vpn/ui/public/flags/nz.svg new file mode 100644 index 0000000000..688bb9d93a --- /dev/null +++ b/nym-vpn/ui/public/flags/nz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/om.svg b/nym-vpn/ui/public/flags/om.svg new file mode 100644 index 0000000000..79c4bf23fd --- /dev/null +++ b/nym-vpn/ui/public/flags/om.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pa.svg b/nym-vpn/ui/public/flags/pa.svg new file mode 100644 index 0000000000..c29cedd1b9 --- /dev/null +++ b/nym-vpn/ui/public/flags/pa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pe.svg b/nym-vpn/ui/public/flags/pe.svg new file mode 100644 index 0000000000..07a7190989 --- /dev/null +++ b/nym-vpn/ui/public/flags/pe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pf.svg b/nym-vpn/ui/public/flags/pf.svg new file mode 100644 index 0000000000..7d08fbf2a1 --- /dev/null +++ b/nym-vpn/ui/public/flags/pf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pg.svg b/nym-vpn/ui/public/flags/pg.svg new file mode 100644 index 0000000000..214d94126f --- /dev/null +++ b/nym-vpn/ui/public/flags/pg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ph.svg b/nym-vpn/ui/public/flags/ph.svg new file mode 100644 index 0000000000..a5fc569279 --- /dev/null +++ b/nym-vpn/ui/public/flags/ph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pk.svg b/nym-vpn/ui/public/flags/pk.svg new file mode 100644 index 0000000000..457a0518a6 --- /dev/null +++ b/nym-vpn/ui/public/flags/pk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pl.svg b/nym-vpn/ui/public/flags/pl.svg new file mode 100644 index 0000000000..cf3574a189 --- /dev/null +++ b/nym-vpn/ui/public/flags/pl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pm.svg b/nym-vpn/ui/public/flags/pm.svg new file mode 100644 index 0000000000..5f5586fabf --- /dev/null +++ b/nym-vpn/ui/public/flags/pm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pn.svg b/nym-vpn/ui/public/flags/pn.svg new file mode 100644 index 0000000000..ded89b52b6 --- /dev/null +++ b/nym-vpn/ui/public/flags/pn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pr.svg b/nym-vpn/ui/public/flags/pr.svg new file mode 100644 index 0000000000..25403954bf --- /dev/null +++ b/nym-vpn/ui/public/flags/pr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ps.svg b/nym-vpn/ui/public/flags/ps.svg new file mode 100644 index 0000000000..0b3657b075 --- /dev/null +++ b/nym-vpn/ui/public/flags/ps.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pt.svg b/nym-vpn/ui/public/flags/pt.svg new file mode 100644 index 0000000000..32ce13f2ff --- /dev/null +++ b/nym-vpn/ui/public/flags/pt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/pw.svg b/nym-vpn/ui/public/flags/pw.svg new file mode 100644 index 0000000000..67be6e211e --- /dev/null +++ b/nym-vpn/ui/public/flags/pw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/py.svg b/nym-vpn/ui/public/flags/py.svg new file mode 100644 index 0000000000..537af58cac --- /dev/null +++ b/nym-vpn/ui/public/flags/py.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/qa.svg b/nym-vpn/ui/public/flags/qa.svg new file mode 100644 index 0000000000..7ccf5bd280 --- /dev/null +++ b/nym-vpn/ui/public/flags/qa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/re.svg b/nym-vpn/ui/public/flags/re.svg new file mode 100644 index 0000000000..22fcc98f77 --- /dev/null +++ b/nym-vpn/ui/public/flags/re.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ro.svg b/nym-vpn/ui/public/flags/ro.svg new file mode 100644 index 0000000000..2d3bfd9c5d --- /dev/null +++ b/nym-vpn/ui/public/flags/ro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/rs.svg b/nym-vpn/ui/public/flags/rs.svg new file mode 100644 index 0000000000..96a0717c56 --- /dev/null +++ b/nym-vpn/ui/public/flags/rs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ru.svg b/nym-vpn/ui/public/flags/ru.svg new file mode 100644 index 0000000000..b49e8cc7ed --- /dev/null +++ b/nym-vpn/ui/public/flags/ru.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/rw.svg b/nym-vpn/ui/public/flags/rw.svg new file mode 100644 index 0000000000..ae6fc4769d --- /dev/null +++ b/nym-vpn/ui/public/flags/rw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sa.svg b/nym-vpn/ui/public/flags/sa.svg new file mode 100644 index 0000000000..ef73f0eb54 --- /dev/null +++ b/nym-vpn/ui/public/flags/sa.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sb.svg b/nym-vpn/ui/public/flags/sb.svg new file mode 100644 index 0000000000..3db190a4d5 --- /dev/null +++ b/nym-vpn/ui/public/flags/sb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sc.svg b/nym-vpn/ui/public/flags/sc.svg new file mode 100644 index 0000000000..f2eb4437e6 --- /dev/null +++ b/nym-vpn/ui/public/flags/sc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sd.svg b/nym-vpn/ui/public/flags/sd.svg new file mode 100644 index 0000000000..a2605fba78 --- /dev/null +++ b/nym-vpn/ui/public/flags/sd.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/se.svg b/nym-vpn/ui/public/flags/se.svg new file mode 100644 index 0000000000..c8520bcd22 --- /dev/null +++ b/nym-vpn/ui/public/flags/se.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sg.svg b/nym-vpn/ui/public/flags/sg.svg new file mode 100644 index 0000000000..901ba886ac --- /dev/null +++ b/nym-vpn/ui/public/flags/sg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sh.svg b/nym-vpn/ui/public/flags/sh.svg new file mode 100644 index 0000000000..7234ea657c --- /dev/null +++ b/nym-vpn/ui/public/flags/sh.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/si.svg b/nym-vpn/ui/public/flags/si.svg new file mode 100644 index 0000000000..520693c549 --- /dev/null +++ b/nym-vpn/ui/public/flags/si.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sj.svg b/nym-vpn/ui/public/flags/sj.svg new file mode 100644 index 0000000000..bc702a397d --- /dev/null +++ b/nym-vpn/ui/public/flags/sj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sk.svg b/nym-vpn/ui/public/flags/sk.svg new file mode 100644 index 0000000000..ee0e1292b9 --- /dev/null +++ b/nym-vpn/ui/public/flags/sk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sl.svg b/nym-vpn/ui/public/flags/sl.svg new file mode 100644 index 0000000000..58dc96b925 --- /dev/null +++ b/nym-vpn/ui/public/flags/sl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sm.svg b/nym-vpn/ui/public/flags/sm.svg new file mode 100644 index 0000000000..69f842700e --- /dev/null +++ b/nym-vpn/ui/public/flags/sm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sn.svg b/nym-vpn/ui/public/flags/sn.svg new file mode 100644 index 0000000000..f634dca72b --- /dev/null +++ b/nym-vpn/ui/public/flags/sn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/so.svg b/nym-vpn/ui/public/flags/so.svg new file mode 100644 index 0000000000..76a599c534 --- /dev/null +++ b/nym-vpn/ui/public/flags/so.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sr.svg b/nym-vpn/ui/public/flags/sr.svg new file mode 100644 index 0000000000..2cce22acf6 --- /dev/null +++ b/nym-vpn/ui/public/flags/sr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ss.svg b/nym-vpn/ui/public/flags/ss.svg new file mode 100644 index 0000000000..d8cc2db9fc --- /dev/null +++ b/nym-vpn/ui/public/flags/ss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/st.svg b/nym-vpn/ui/public/flags/st.svg new file mode 100644 index 0000000000..798731ba2f --- /dev/null +++ b/nym-vpn/ui/public/flags/st.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/su.svg b/nym-vpn/ui/public/flags/su.svg new file mode 100644 index 0000000000..d3be990a6a --- /dev/null +++ b/nym-vpn/ui/public/flags/su.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sv.svg b/nym-vpn/ui/public/flags/sv.svg new file mode 100644 index 0000000000..c3b8db7b61 --- /dev/null +++ b/nym-vpn/ui/public/flags/sv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sx.svg b/nym-vpn/ui/public/flags/sx.svg new file mode 100644 index 0000000000..9bb89b74cc --- /dev/null +++ b/nym-vpn/ui/public/flags/sx.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sy.svg b/nym-vpn/ui/public/flags/sy.svg new file mode 100644 index 0000000000..ab7adedd7f --- /dev/null +++ b/nym-vpn/ui/public/flags/sy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/sz.svg b/nym-vpn/ui/public/flags/sz.svg new file mode 100644 index 0000000000..b3fcc158e8 --- /dev/null +++ b/nym-vpn/ui/public/flags/sz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ta.svg b/nym-vpn/ui/public/flags/ta.svg new file mode 100644 index 0000000000..dd1d031c16 --- /dev/null +++ b/nym-vpn/ui/public/flags/ta.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tc.svg b/nym-vpn/ui/public/flags/tc.svg new file mode 100644 index 0000000000..764155ef6f --- /dev/null +++ b/nym-vpn/ui/public/flags/tc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/td.svg b/nym-vpn/ui/public/flags/td.svg new file mode 100644 index 0000000000..bb49f985c7 --- /dev/null +++ b/nym-vpn/ui/public/flags/td.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tf.svg b/nym-vpn/ui/public/flags/tf.svg new file mode 100644 index 0000000000..d2c44abef8 --- /dev/null +++ b/nym-vpn/ui/public/flags/tf.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tg.svg b/nym-vpn/ui/public/flags/tg.svg new file mode 100644 index 0000000000..f5b7c8775c --- /dev/null +++ b/nym-vpn/ui/public/flags/tg.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/th.svg b/nym-vpn/ui/public/flags/th.svg new file mode 100644 index 0000000000..680994bcf1 --- /dev/null +++ b/nym-vpn/ui/public/flags/th.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tj.svg b/nym-vpn/ui/public/flags/tj.svg new file mode 100644 index 0000000000..509da9ff8d --- /dev/null +++ b/nym-vpn/ui/public/flags/tj.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tk.svg b/nym-vpn/ui/public/flags/tk.svg new file mode 100644 index 0000000000..60ab627b77 --- /dev/null +++ b/nym-vpn/ui/public/flags/tk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tl.svg b/nym-vpn/ui/public/flags/tl.svg new file mode 100644 index 0000000000..d5ba3dc873 --- /dev/null +++ b/nym-vpn/ui/public/flags/tl.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tm.svg b/nym-vpn/ui/public/flags/tm.svg new file mode 100644 index 0000000000..bc5311dfa6 --- /dev/null +++ b/nym-vpn/ui/public/flags/tm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tn.svg b/nym-vpn/ui/public/flags/tn.svg new file mode 100644 index 0000000000..e43c73eff9 --- /dev/null +++ b/nym-vpn/ui/public/flags/tn.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/to.svg b/nym-vpn/ui/public/flags/to.svg new file mode 100644 index 0000000000..9a72ea06db --- /dev/null +++ b/nym-vpn/ui/public/flags/to.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tr.svg b/nym-vpn/ui/public/flags/tr.svg new file mode 100644 index 0000000000..f4c325d2b3 --- /dev/null +++ b/nym-vpn/ui/public/flags/tr.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tt.svg b/nym-vpn/ui/public/flags/tt.svg new file mode 100644 index 0000000000..5de43d9292 --- /dev/null +++ b/nym-vpn/ui/public/flags/tt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tv.svg b/nym-vpn/ui/public/flags/tv.svg new file mode 100644 index 0000000000..184185788c --- /dev/null +++ b/nym-vpn/ui/public/flags/tv.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tw.svg b/nym-vpn/ui/public/flags/tw.svg new file mode 100644 index 0000000000..a0a5387288 --- /dev/null +++ b/nym-vpn/ui/public/flags/tw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/tz.svg b/nym-vpn/ui/public/flags/tz.svg new file mode 100644 index 0000000000..cb00ae73b2 --- /dev/null +++ b/nym-vpn/ui/public/flags/tz.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ua.svg b/nym-vpn/ui/public/flags/ua.svg new file mode 100644 index 0000000000..ccd0a810d0 --- /dev/null +++ b/nym-vpn/ui/public/flags/ua.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/ug.svg b/nym-vpn/ui/public/flags/ug.svg new file mode 100644 index 0000000000..2394c76c8e --- /dev/null +++ b/nym-vpn/ui/public/flags/ug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/uk.svg b/nym-vpn/ui/public/flags/uk.svg new file mode 100644 index 0000000000..4dca1e7828 --- /dev/null +++ b/nym-vpn/ui/public/flags/uk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/um.svg b/nym-vpn/ui/public/flags/um.svg new file mode 100644 index 0000000000..e3560c6a51 --- /dev/null +++ b/nym-vpn/ui/public/flags/um.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/public/flags/us.svg b/nym-vpn/ui/public/flags/us.svg new file mode 100644 index 0000000000..e3560c6a51 --- /dev/null +++ b/nym-vpn/ui/public/flags/us.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nym-vpn/ui/src-tauri/.gitignore b/nym-vpn/ui/src-tauri/.gitignore index f4dfb82b2c..a324855cc5 100644 --- a/nym-vpn/ui/src-tauri/.gitignore +++ b/nym-vpn/ui/src-tauri/.gitignore @@ -1,4 +1,3 @@ -# Generated by Cargo -# will have compiled files and executables /target/ +/bindings/ diff --git a/nym-vpn/ui/src-tauri/Cargo.lock b/nym-vpn/ui/src-tauri/Cargo.lock index 4e4061f1a5..30140b28f2 100644 --- a/nym-vpn/ui/src-tauri/Cargo.lock +++ b/nym-vpn/ui/src-tauri/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + [[package]] name = "addr2line" version = "0.21.0" @@ -17,6 +23,86 @@ 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.7", +] + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "ctr 0.8.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +dependencies = [ + "aead 0.4.3", + "aes 0.7.5", + "cipher 0.3.0", + "ctr 0.8.0", + "ghash", + "subtle 2.4.1", +] + +[[package]] +name = "ahash" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a824f2aa7e75a0c98c5a504fceb80649e9c35265d44525b5f94de4771a395cd" +dependencies = [ + "getrandom 0.2.10", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -41,6 +127,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -56,12 +148,111 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a318f1f38d2418400f8209655bfd825785afd25aa30bb7ba6cc792e4596748" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "atk" version = "0.15.1" @@ -86,12 +277,86 @@ dependencies = [ "system-deps 6.1.2", ] +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa 1.0.9", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -107,6 +372,18 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.13.1" @@ -119,6 +396,59 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bip32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +dependencies = [ + "bs58 0.5.0", + "hmac 0.12.1", + "k256", + "once_cell", + "pbkdf2", + "rand_core 0.6.4", + "ripemd", + "sha2 0.10.8", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "bip39" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.5", + "rand_core 0.6.4", + "serde", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" + [[package]] name = "bitflags" version = "1.3.2" @@ -131,21 +461,102 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" +dependencies = [ + "byte-tools", + "crypto-mac 0.7.0", + "digest 0.8.1", + "opaque-debug 0.2.3", +] + +[[package]] +name = "blake3" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0231f06152bf547e9c2b5194f247cd97aacf6dcd8b15d8e5ec0663f64580da87" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + [[package]] name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.7", ] +[[package]] +name = "bls12_381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" +dependencies = [ + "digest 0.9.0", + "ff 0.10.1", + "group 0.10.0", + "pairing 0.20.0", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "bls12_381" +version = "0.6.0" +source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" +dependencies = [ + "digest 0.9.0", + "ff 0.11.1", + "group 0.11.0", + "pairing 0.21.0", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "bnum" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" + [[package]] name = "brotli" version = "3.4.0" @@ -167,6 +578,21 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bs58" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +dependencies = [ + "sha2 0.10.8", +] + [[package]] name = "bstr" version = "1.7.0" @@ -183,6 +609,28 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byte_string" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11aade7a05aa8c3a351cedc44c3fc45806430543382fcc4743a9b757a2a0b4ed" + +[[package]] +name = "bytecodec" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adf4c9d0bbf32eea58d7c0f812058138ee8edaf0f2802b6d03561b504729a325" +dependencies = [ + "byteorder", + "trackable 0.2.24", +] + [[package]] name = "bytemuck" version = "1.14.0" @@ -200,6 +648,9 @@ name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +dependencies = [ + "serde", +] [[package]] name = "cairo-rs" @@ -241,6 +692,7 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ + "jobserver", "libc", ] @@ -258,7 +710,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid", + "uuid 1.5.0", ] [[package]] @@ -286,6 +738,65 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" +dependencies = [ + "byteorder", + "keystream", +] + +[[package]] +name = "chacha20" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +dependencies = [ + "aead 0.4.3", + "chacha20 0.8.2", + "cipher 0.3.0", + "poly1305 0.7.2", + "zeroize", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead 0.5.2", + "chacha20 0.9.1", + "cipher 0.4.4", + "poly1305 0.8.0", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.31" @@ -294,9 +805,101 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets", + "wasm-bindgen", + "windows-targets 0.48.5", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim 0.10.0", +] + +[[package]] +name = "clap_complete" +version = "4.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bffe91f06a11b4b9420f62103854e90867812cd5d01557f853c5ee8e791b12ae" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_fig" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e571d70e22ec91d34e1c5317c8308035a2280d925167646bf094fc5de1737c" +dependencies = [ + "clap", + "clap_complete", +] + +[[package]] +name = "clap_derive" +version = "4.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "clap_lex" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" + +[[package]] +name = "classic-mceliece-rust" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45ce62f72a15a9071f83c5084bdf0af4e8cbf31431e79eb4a5509a2f7fe7fe5d" +dependencies = [ + "rand 0.8.5", + "sha3", + "zeroize", ] [[package]] @@ -335,6 +938,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colored" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +dependencies = [ + "is-terminal", + "lazy_static", + "windows-sys 0.48.0", +] + [[package]] name = "combine" version = "4.6.6" @@ -345,6 +965,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "const-oid" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "convert_case" version = "0.4.0" @@ -391,6 +1029,105 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmos-sdk-proto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" +dependencies = [ + "prost 0.12.3", + "prost-types 0.12.3", + "tendermint-proto", +] + +[[package]] +name = "cosmrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" +dependencies = [ + "bip32", + "cosmos-sdk-proto", + "ecdsa 0.16.9", + "eyre", + "k256", + "rand_core 0.6.4", + "serde", + "serde_json", + "signature 2.2.0", + "subtle-encoding", + "tendermint", + "tendermint-rpc", + "thiserror", +] + +[[package]] +name = "cosmwasm-crypto" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bb3c77c3b7ce472056968c745eb501c440fbc07be5004eba02782c35bfbbe3" +dependencies = [ + "digest 0.10.7", + "ecdsa 0.16.9", + "ed25519-zebra", + "k256", + "rand_core 0.6.4", + "thiserror", +] + +[[package]] +name = "cosmwasm-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea73e9162e6efde00018d55ed0061e93a108b5d6ec4548b4f8ce3c706249687" +dependencies = [ + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-schema" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6b2fb76758ef59cddc77f2e2ae91c22f77da49037e9f182e9c2833f0e959b1" +dependencies = [ + "cosmwasm-schema-derive", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cosmwasm-schema-derive" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfa39422f0d9f1c9a6fd3711573258495314dfa3aae738ea825ecd9964bc659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cosmwasm-std" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6dc2ee23313add5ecacc3ccac217b9967ad9d2d11bd56e5da6aa65a9da6138" +dependencies = [ + "base64 0.13.1", + "bnum", + "cosmwasm-crypto", + "cosmwasm-derive", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde", + "serde-json-wasm", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "cpufeatures" version = "0.2.10" @@ -400,6 +1137,36 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog 1.1.1", +] + +[[package]] +name = "crc" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +dependencies = [ + "crc-catalog 2.4.0", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.3.2" @@ -419,6 +1186,40 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.9.0", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.16" @@ -428,16 +1229,61 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.4", + "subtle 1.0.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.7", + "subtle 2.4.1", +] + [[package]] name = "cssparser" version = "0.27.2" @@ -475,14 +1321,205 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89b8c6a2e4b1f45971ad09761aafb85514a84744b67a95e32c3cc1352d1f65c" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "platforms", + "rustc_version", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "cw-controllers" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5d8edce4b78785f36413f67387e4be7d0cb7d032b5d4164bcc024f9c3f3f2ea" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw-storage-plus" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0e92a069d62067f3472c62e30adedb4cab1754725c0f2a682b3128d2bf3c79" +dependencies = [ + "cosmwasm-std", + "schemars", + "serde", +] + +[[package]] +name = "cw-utils" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c80e93d1deccb8588db03945016a292c3c631e6325d349ebb35d2db6f4f946f7" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw2", + "schemars", + "semver 1.0.20", + "serde", + "thiserror", +] + +[[package]] +name = "cw2" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ac2dc7a55ad64173ca1e0a46697c31b7a5c51342f55a1e84a724da4eb99908" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw20" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + +[[package]] +name = "cw3" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171af3d9127de6805a7dd819fb070c7d2f6c3ea85f4193f42cef259f0a7f33d5" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "cw20", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "cw4" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a398696307efadaaa2d0850076f865fa706c959d493cb4203314f72be6b77a64" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "schemars", + "serde", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + [[package]] name = "darling" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.3", + "darling_macro 0.20.3", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn 1.0.109", ] [[package]] @@ -495,21 +1532,98 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 2.0.38", ] +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", + "syn 1.0.109", +] + [[package]] name = "darling_macro" version = "0.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ - "darling_core", + "darling_core 0.20.3", "quote", "syn 2.0.38", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.2", + "lock_api", + "once_cell", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] + +[[package]] +name = "default-net" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85dc7576d8346d3c86ad64dc64d26d0f6c970ba4795b850f15ee94467d8e53eb" +dependencies = [ + "dlopen2", + "libc", + "memalloc", + "netlink-packet-core 0.7.0", + "netlink-packet-route 0.17.1", + "netlink-sys", + "once_cell", + "system-configuration", + "windows 0.48.0", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid 0.7.1", +] + +[[package]] +name = "der" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +dependencies = [ + "const-oid 0.9.5", + "zeroize", +] + [[package]] name = "deranged" version = "0.3.9" @@ -520,6 +1634,42 @@ dependencies = [ "serde", ] +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" +dependencies = [ + "darling 0.10.2", + "derive_builder_core", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" +dependencies = [ + "darling 0.10.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -533,14 +1683,52 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", + "block-buffer 0.10.4", + "const-oid 0.9.5", "crypto-common", + "subtle 2.4.1", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", ] [[package]] @@ -553,6 +1741,29 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -570,6 +1781,29 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dlopen2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" +dependencies = [ + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dtoa" version = "1.0.9" @@ -585,12 +1819,157 @@ dependencies = [ "dtoa", ] +[[package]] +name = "duct" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ae3fc31835f74c2a7ceda3aeede378b0ae2e74c8f1c36559fcc9ae2a4e7d3e" +dependencies = [ + "libc", + "once_cell", + "os_pipe 1.1.4", + "shared_child", +] + [[package]] name = "dunce" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "dyn-clone" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der 0.5.1", + "elliptic-curve 0.11.12", + "signature 1.4.0", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der 0.7.8", + "digest 0.10.7", + "elliptic-curve 0.13.8", + "rfc6979", + "signature 2.2.0", + "spki", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature 1.4.0", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519 1.5.3", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct 0.1.1", + "crypto-bigint 0.3.2", + "der 0.5.1", + "generic-array 0.14.7", + "rand_core 0.6.4", + "sec1 0.2.1", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.5", + "digest 0.10.7", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", + "pkcs8", + "rand_core 0.6.4", + "sec1 0.7.3", + "subtle 2.4.1", + "zeroize", +] + [[package]] name = "embed-resource" version = "2.4.0" @@ -599,9 +1978,9 @@ checksum = "f54cc3e827ee1c3812239a9a41dede7b4d7d5d5464faa32d71bd7cba28ce2cb2" dependencies = [ "cc", "rustc_version", - "toml 0.8.2", + "toml 0.8.5", "vswhom", - "winreg", + "winreg 0.51.0", ] [[package]] @@ -619,12 +1998,107 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "enum-as-inner" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "enum-iterator" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a0ac4aeb3a18f92eaf09c6bb9b3ac30ff61ca95514fc58cbead1c9a6bf5401" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime 2.1.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "err-context" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449aad22b1364e927ff3bf50f55404efd705c40065fb47f73f28704de707c89e" + +[[package]] +name = "err-derive" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34a887c8df3ed90498c1c437ce21f211c8e27672921a8ffa293cb8d6d4caa9e" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", + "synstructure", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + [[package]] name = "errno" version = "0.3.5" @@ -635,6 +2109,42 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "backtrace", + "version_check", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "eyre" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80f656be11ddf91bd709454d15d5bd896fbaf4cc3314e69349e4d1569f5b46cd" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -650,13 +2160,49 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27573eac26f4dd11e2b1916c3fe1baa56407c83c71a773a8ba17ec0bca03b6b7" + [[package]] name = "field-offset" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" dependencies = [ - "memoffset", + "memoffset 0.9.0", "rustc_version", ] @@ -672,6 +2218,18 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "finl_unicode" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -682,6 +2240,28 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -712,6 +2292,27 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -723,25 +2324,41 @@ dependencies = [ ] [[package]] -name = "futures-channel" -version = "0.3.28" +name = "futures" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -749,16 +2366,27 @@ dependencies = [ ] [[package]] -name = "futures-io" -version = "0.3.28" +name = "futures-intrusive" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", @@ -766,20 +2394,30 @@ dependencies = [ ] [[package]] -name = "futures-task" -version = "0.3.28" +name = "futures-sink" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ + "futures-channel", "futures-core", + "futures-io", "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -893,14 +2531,25 @@ dependencies = [ "windows 0.48.0", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ + "serde", "typenum", "version_check", + "zeroize", ] [[package]] @@ -910,8 +2559,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -921,8 +2572,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getset" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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]] @@ -961,6 +2636,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "git2" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" +dependencies = [ + "bitflags 1.3.2", + "libc", + "libgit2-sys", + "log", + "url", +] + [[package]] name = "glib" version = "0.15.12" @@ -1025,6 +2713,52 @@ dependencies = [ "regex", ] +[[package]] +name = "gloo-net" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66b4e3c7d9ed8d315fd6b97c8b1f74a7c6ecbbc2320e65ae7ed38b7068cc620" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "gobject-sys" version = "0.15.10" @@ -1036,6 +2770,41 @@ dependencies = [ "system-deps 6.1.2", ] +[[package]] +name = "group" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912" +dependencies = [ + "byteorder", + "ff 0.10.1", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "byteorder", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle 2.4.1", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", + "rand_core 0.6.4", + "subtle 2.4.1", +] + [[package]] name = "gtk" version = "0.15.5" @@ -1091,17 +2860,84 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util 0.7.10", + "tracing", +] + +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error 2.0.1", + "serde", + "serde_json", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.7", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.7", +] [[package]] name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash 0.8.6", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown 0.11.2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown 0.14.2", +] [[package]] name = "heck" @@ -1117,6 +2953,18 @@ name = "heck" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] [[package]] name = "hermit-abi" @@ -1130,6 +2978,70 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "hkdf" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" +dependencies = [ + "digest 0.9.0", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -1169,12 +3081,148 @@ dependencies = [ "itoa 1.0.9", ] +[[package]] +name = "http-api-client" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "reqwest", + "serde", + "serde_json", + "thiserror", + "tracing", + "url", + "wasmtimer", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + [[package]] name = "http-range" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpcodec" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f49d64351430cabd543943b79d48aaf0bc95a41d9ccf5b8774c2cfd23422775" +dependencies = [ + "bytecodec", + "trackable 0.2.24", +] + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error 1.2.3", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime 2.1.0", + "serde", +] + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.9", + "pin-project-lite", + "socket2 0.4.10", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http", + "hyper", + "rustls 0.21.9", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.58" @@ -1214,6 +3262,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "idna" version = "0.4.0" @@ -1254,6 +3313,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -1285,6 +3350,48 @@ dependencies = [ "cfb", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc" +dependencies = [ + "bitflags 1.3.2", + "futures-core", + "inotify-sys", + "libc", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "instant" version = "0.1.12" @@ -1294,6 +3401,92 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "internet-checksum" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6d6206008e25125b1f97fbe5d309eb7b85141cf9199d52dbd3729a1584dd16" + +[[package]] +name = "ioctl-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c429fffa658f288669529fc26565f728489a2e39bc7b24a428aaaf51355182e" + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.5", + "widestring", + "windows-sys 0.48.0", + "winreg 0.50.0", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "ipnetwork" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e" +dependencies = [ + "serde", +] + +[[package]] +name = "iprange" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37209be0ad225457e63814401415e748e2453a5297f9b637338f5fb8afa4ec00" +dependencies = [ + "ipnet", +] + +[[package]] +name = "is-terminal" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +dependencies = [ + "hermit-abi 0.3.3", + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.8" @@ -1329,6 +3522,20 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + [[package]] name = "jni" version = "0.20.0" @@ -1349,6 +3556,39 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jnix" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd797d41e48568eb956ded20d7e5e3f2df1c02980d9e5b9aab9b47bd3a9f599" +dependencies = [ + "jni 0.19.0", + "jnix-macros", + "once_cell", + "parking_lot 0.12.1", +] + +[[package]] +name = "jnix-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "002f4dfe6d97ae88c33f3489c0d31ffc6f81d9a492de98ff113b127d73bafff8" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "jobserver" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1370,6 +3610,66 @@ dependencies = [ "treediff", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "k256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f01b677d82ef7a676aa37e099defd83a28e15687112cafdd112d60236b6115b" +dependencies = [ + "cfg-if", + "ecdsa 0.16.9", + "elliptic-curve 0.13.8", + "once_cell", + "sha2 0.10.8", + "signature 2.2.0", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keystream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "kuchiki" version = "0.8.1" @@ -1407,6 +3707,56 @@ version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libgit2-sys" +version = "0.13.5+1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e5ea06c26926f1002dd553fded6cfcdc9784c1f60feeb58368b4d9b07b6dba" +dependencies = [ + "cc", + "libc", + "libz-sys", + "pkg-config", +] + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -1416,12 +3766,30 @@ dependencies = [ "safemem", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "lioness" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" +dependencies = [ + "arrayref", + "blake2", + "chacha", + "keystream", +] + [[package]] name = "lock_api" version = "0.4.11" @@ -1453,6 +3821,21 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "lru_time_cache" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9106e1d747ffd48e6be5bb2d97fa706ed25b144fbee4d5c02eae110cd8d6badd" + [[package]] name = "mac" version = "0.1.1" @@ -1496,6 +3879,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matchers" version = "0.1.0" @@ -1511,12 +3900,53 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", + "md5-asm", +] + +[[package]] +name = "md5-asm" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d33bc4cdfe5c60340e282bbbee0a6e2bc57f0b9279bb3489c5004d12492e5c" +dependencies = [ + "cc", +] + +[[package]] +name = "memalloc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df39d232f5c40b0891c10216992c2f250c054105cb1e56f0fc9032db6203ecc1" + [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + [[package]] name = "memoffset" version = "0.9.0" @@ -1526,6 +3956,24 @@ dependencies = [ "autocfg", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "minisign-verify" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1536,6 +3984,63 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "mio" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "mnl" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1a5469630da93e1813bb257964c0ccee3b26b6879dd858039ddec35cc8681ed" +dependencies = [ + "libc", + "log", + "mnl-sys", +] + +[[package]] +name = "mnl-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9750685b201e1ecfaaf7aa5d0387829170fa565989cc481b49080aa155f70457" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.6.0" @@ -1564,18 +4069,193 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "netlink-packet-core" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" +dependencies = [ + "anyhow", + "byteorder", + "libc", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5dee5ed749373c298237fe694eb0a51887f4cc1a27370c8464bac4382348f1a" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core 0.4.2", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core 0.7.0", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core 0.4.2", + "netlink-sys", + "thiserror", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +[[package]] +name = "nftnl" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9201688bd0bc571dfa4c21ce0a525480c8b782776cf88e12571fa89108dd920" +dependencies = [ + "bitflags 1.3.2", + "err-derive", + "log", + "nftnl-sys", +] + +[[package]] +name = "nftnl-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b5c587b6a5e76a3a5d51e0a757ae66dbff38c277563485807ae979ce361b56" +dependencies = [ + "cfg-if", + "libc", + "pkg-config", +] + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset 0.6.5", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + [[package]] name = "nodrop" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +dependencies = [ + "bitflags 1.3.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify 0.9.6", + "kqueue", + "libc", + "mio", + "walkdir", + "windows-sys 0.45.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1586,6 +4266,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1614,6 +4305,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -1622,7 +4314,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] @@ -1648,13 +4340,989 @@ dependencies = [ ] [[package]] -name = "nymvpn-ui" -version = "0.0.0" +name = "nym-api-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" dependencies = [ + "bs58 0.4.0", + "cosmrs", + "cosmwasm-std", + "getset", + "nym-coconut-interface", + "nym-mixnet-contract-common", + "nym-node-requests", + "schemars", + "serde", +] + +[[package]] +name = "nym-bandwidth-controller" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bip39", + "nym-coconut-interface", + "nym-credential-storage", + "nym-credentials", + "nym-crypto", + "nym-network-defaults", + "nym-validator-client", + "rand 0.7.3", + "thiserror", + "url", +] + +[[package]] +name = "nym-bin-common" +version = "0.6.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "atty", + "clap", + "clap_complete", + "clap_complete_fig", + "log", + "pretty_env_logger", + "schemars", + "semver 0.11.0", + "serde", + "vergen", +] + +[[package]] +name = "nym-client-core" +version = "1.1.15" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "base64 0.21.5", + "cfg-if", + "dashmap", + "dirs 4.0.0", + "futures", + "gloo-timers", + "humantime-serde", + "log", + "nym-bandwidth-controller", + "nym-config", + "nym-credential-storage", + "nym-crypto", + "nym-explorer-client", + "nym-gateway-client", + "nym-gateway-requests", + "nym-network-defaults", + "nym-nonexhaustive-delayqueue", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-topology", + "nym-validator-client", + "rand 0.7.3", + "reqwest", "serde", "serde_json", + "sha2 0.10.8", + "sqlx 0.6.3", + "tap", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-utils", + "wasmtimer", + "zeroize", +] + +[[package]] +name = "nym-coconut" +version = "0.5.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bls12_381 0.6.0", + "bs58 0.4.0", + "digest 0.9.0", + "ff 0.11.1", + "getrandom 0.2.10", + "group 0.11.0", + "itertools 0.10.5", + "nym-dkg", + "nym-pemstore", + "rand 0.8.5", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", +] + +[[package]] +name = "nym-coconut-bandwidth-contract-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "nym-multisig-contract-common", +] + +[[package]] +name = "nym-coconut-dkg-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", + "nym-multisig-contract-common", +] + +[[package]] +name = "nym-coconut-interface" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bs58 0.4.0", + "getset", + "nym-coconut", + "serde", + "thiserror", +] + +[[package]] +name = "nym-config" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "dirs 5.0.1", + "handlebars", + "log", + "nym-network-defaults", + "serde", + "toml 0.7.8", + "url", +] + +[[package]] +name = "nym-contracts-common" +version = "0.5.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bs58 0.4.0", + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-credential-storage" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "log", + "sqlx 0.5.13", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credential-utils" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "log", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-credential-storage", + "nym-credentials", + "nym-validator-client", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-credentials" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bls12_381 0.5.0", + "cosmrs", + "log", + "nym-api-requests", + "nym-coconut-interface", + "nym-crypto", + "nym-validator-client", + "thiserror", +] + +[[package]] +name = "nym-crypto" +version = "0.4.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "aes 0.8.3", + "blake3", + "bs58 0.4.0", + "cipher 0.4.4", + "ctr 0.9.2", + "digest 0.10.7", + "ed25519-dalek", + "generic-array 0.14.7", + "hkdf 0.12.3", + "hmac 0.12.1", + "nym-pemstore", + "nym-sphinx-types", + "rand 0.7.3", + "subtle-encoding", + "thiserror", + "x25519-dalek 1.1.1", + "zeroize", +] + +[[package]] +name = "nym-dkg" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bitvec", + "bls12_381 0.6.0", + "bs58 0.4.0", + "ff 0.11.1", + "group 0.11.0", + "lazy_static", + "nym-pemstore", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", + "serde_derive", + "sha2 0.9.9", + "thiserror", + "zeroize", +] + +[[package]] +name = "nym-ephemera-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "nym-contracts-common", +] + +[[package]] +name = "nym-exit-policy" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "serde", + "thiserror", + "tracing", +] + +[[package]] +name = "nym-explorer-api-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-api-requests", + "nym-contracts-common", + "nym-mixnet-contract-common", + "schemars", + "serde", +] + +[[package]] +name = "nym-explorer-client" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "log", + "nym-explorer-api-requests", + "reqwest", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "nym-gateway-client" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "futures", + "getrandom 0.2.10", + "gloo-utils", + "log", + "nym-bandwidth-controller", + "nym-coconut-interface", + "nym-credential-storage", + "nym-crypto", + "nym-gateway-requests", + "nym-network-defaults", + "nym-pemstore", + "nym-sphinx", + "nym-task", + "nym-validator-client", + "rand 0.7.3", + "serde", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-utils", + "wasmtimer", +] + +[[package]] +name = "nym-gateway-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bs58 0.4.0", + "futures", + "generic-array 0.14.7", + "log", + "nym-coconut-interface", + "nym-credentials", + "nym-crypto", + "nym-pemstore", + "nym-sphinx", + "rand 0.7.3", + "serde", + "serde_json", + "thiserror", + "tungstenite", + "zeroize", +] + +[[package]] +name = "nym-group-contract-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cw-controllers", + "cw4", + "schemars", + "serde", +] + +[[package]] +name = "nym-ip-packet-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bincode", + "bytes", + "nym-sphinx", + "rand 0.8.5", + "serde", + "thiserror", +] + +[[package]] +name = "nym-mixnet-contract-common" +version = "0.6.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bs58 0.4.0", + "cosmwasm-schema", + "cosmwasm-std", + "humantime-serde", + "log", + "nym-contracts-common", + "schemars", + "serde", + "serde-json-wasm", + "serde_repr", + "thiserror", + "time", +] + +[[package]] +name = "nym-multisig-contract-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "cw3", + "cw4", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "nym-name-service-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", + "serde", + "thiserror", +] + +[[package]] +name = "nym-network-defaults" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cfg-if", + "dotenvy", + "hex-literal", + "once_cell", + "schemars", + "serde", + "thiserror", + "url", +] + +[[package]] +name = "nym-node-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "base64 0.21.5", + "http-api-client", + "nym-bin-common", + "nym-crypto", + "nym-exit-policy", + "nym-wireguard-types", + "schemars", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-nonexhaustive-delayqueue" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "tokio", + "tokio-stream", + "tokio-util 0.7.10", + "wasmtimer", +] + +[[package]] +name = "nym-ordered-buffer" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "log", + "thiserror", +] + +[[package]] +name = "nym-outfox" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "blake3", + "chacha20 0.9.1", + "chacha20poly1305 0.10.1", + "curve25519-dalek 3.2.0", + "getrandom 0.2.10", + "log", + "rand 0.7.3", + "rayon", + "sphinx-packet", + "thiserror", + "zeroize", +] + +[[package]] +name = "nym-pemstore" +version = "0.3.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "pem", +] + +[[package]] +name = "nym-sdk" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "bip39", + "bytecodec", + "bytes", + "futures", + "http", + "httpcodec", + "log", + "nym-bandwidth-controller", + "nym-client-core", + "nym-credential-storage", + "nym-credential-utils", + "nym-credentials", + "nym-crypto", + "nym-gateway-requests", + "nym-network-defaults", + "nym-ordered-buffer", + "nym-service-providers-common", + "nym-socks5-client-core", + "nym-socks5-requests", + "nym-sphinx", + "nym-task", + "nym-topology", + "nym-validator-client", + "rand 0.7.3", + "tap", + "thiserror", + "toml 0.5.11", + "url", +] + +[[package]] +name = "nym-service-provider-directory-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "nym-contracts-common", + "thiserror", +] + +[[package]] +name = "nym-service-providers-common" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "log", + "nym-bin-common", + "nym-sphinx-anonymous-replies", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-socks5-client-core" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "anyhow", + "dirs 4.0.0", + "futures", + "log", + "nym-bandwidth-controller", + "nym-client-core", + "nym-config", + "nym-contracts-common", + "nym-credential-storage", + "nym-mixnet-contract-common", + "nym-network-defaults", + "nym-service-providers-common", + "nym-socks5-proxy-helpers", + "nym-socks5-requests", + "nym-sphinx", + "nym-task", + "nym-validator-client", + "pin-project", + "rand 0.7.3", + "reqwest", + "schemars", + "serde", + "tap", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "nym-socks5-proxy-helpers" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bytes", + "futures", + "log", + "nym-ordered-buffer", + "nym-socks5-requests", + "nym-task", + "tokio", + "tokio-util 0.7.10", +] + +[[package]] +name = "nym-socks5-requests" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bincode", + "log", + "nym-exit-policy", + "nym-service-providers-common", + "nym-sphinx-addressing", + "serde", + "serde_json", + "tap", + "thiserror", +] + +[[package]] +name = "nym-sphinx" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "log", + "nym-crypto", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-anonymous-replies", + "nym-sphinx-chunking", + "nym-sphinx-cover", + "nym-sphinx-forwarding", + "nym-sphinx-framing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "rand_distr", + "thiserror", + "tokio", +] + +[[package]] +name = "nym-sphinx-acknowledgements" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-crypto", + "nym-pemstore", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", + "zeroize", +] + +[[package]] +name = "nym-sphinx-addressing" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "serde", + "thiserror", +] + +[[package]] +name = "nym-sphinx-anonymous-replies" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bs58 0.4.0", + "nym-crypto", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "serde", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "nym-sphinx-chunking" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "log", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-cover" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-crypto", + "nym-sphinx-acknowledgements", + "nym-sphinx-addressing", + "nym-sphinx-chunking", + "nym-sphinx-forwarding", + "nym-sphinx-params", + "nym-sphinx-routing", + "nym-sphinx-types", + "nym-topology", + "rand 0.7.3", + "thiserror", +] + +[[package]] +name = "nym-sphinx-forwarding" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-outfox", + "nym-sphinx-addressing", + "nym-sphinx-params", + "nym-sphinx-types", + "thiserror", +] + +[[package]] +name = "nym-sphinx-framing" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "bytes", + "nym-sphinx-params", + "nym-sphinx-types", + "thiserror", + "tokio-util 0.7.10", +] + +[[package]] +name = "nym-sphinx-params" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-crypto", + "nym-sphinx-types", + "serde", + "thiserror", +] + +[[package]] +name = "nym-sphinx-routing" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-sphinx-addressing", + "nym-sphinx-types", + "thiserror", +] + +[[package]] +name = "nym-sphinx-types" +version = "0.2.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "nym-outfox", + "sphinx-packet", + "thiserror", +] + +[[package]] +name = "nym-task" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "futures", + "log", + "thiserror", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasmtimer", +] + +[[package]] +name = "nym-topology" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "bs58 0.4.0", + "log", + "nym-api-requests", + "nym-bin-common", + "nym-config", + "nym-crypto", + "nym-mixnet-contract-common", + "nym-sphinx-addressing", + "nym-sphinx-routing", + "nym-sphinx-types", + "rand 0.7.3", + "semver 0.11.0", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "nym-validator-client" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "async-trait", + "base64 0.13.1", + "bip32", + "bip39", + "colored", + "cosmrs", + "cosmwasm-std", + "cw-controllers", + "cw-utils", + "cw2", + "cw3", + "cw4", + "eyre", + "flate2", + "futures", + "http-api-client", + "itertools 0.10.5", + "log", + "nym-api-requests", + "nym-coconut-bandwidth-contract-common", + "nym-coconut-dkg-common", + "nym-coconut-interface", + "nym-config", + "nym-contracts-common", + "nym-ephemera-common", + "nym-group-contract-common", + "nym-mixnet-contract-common", + "nym-multisig-contract-common", + "nym-name-service-common", + "nym-network-defaults", + "nym-service-provider-directory-common", + "nym-vesting-contract-common", + "openssl", + "prost 0.12.3", + "reqwest", + "serde", + "serde_json", + "sha2 0.9.9", + "tendermint-rpc", + "thiserror", + "tokio", + "url", + "wasmtimer", + "zeroize", +] + +[[package]] +name = "nym-vesting-contract-common" +version = "0.7.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "nym-contracts-common", + "nym-mixnet-contract-common", + "serde", + "thiserror", +] + +[[package]] +name = "nym-vpn-lib" +version = "0.1.0" +dependencies = [ + "bincode", + "bytes", + "default-net", + "futures", + "ipnetwork", + "log", + "nym-bin-common", + "nym-client-core", + "nym-config", + "nym-crypto", + "nym-ip-packet-requests", + "nym-node-requests", + "nym-sdk", + "nym-task", + "nym-validator-client", + "nym-wireguard-types", + "rand 0.7.3", + "serde", + "serde_json", + "signature 1.4.0", + "talpid-core", + "talpid-routing", + "talpid-tunnel", + "talpid-types", + "talpid-wireguard", + "tap", + "thiserror", + "tokio", + "tracing", + "tracing-subscriber", + "tun", + "url", +] + +[[package]] +name = "nym-vpn-ui" +version = "0.0.1" +dependencies = [ + "anyhow", + "dotenvy", + "futures", + "nym-vpn-lib", + "once_cell", + "serde", + "serde_json", + "shadowsocks", + "shadowsocks-service", "tauri", "tauri-build", + "thiserror", + "time", + "tokio", + "toml 0.8.5", + "tracing", + "tracing-subscriber", + "ts-rs", +] + +[[package]] +name = "nym-wireguard-types" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "base64 0.21.5", + "dashmap", + "hmac 0.12.1", + "log", + "nym-crypto", + "serde", + "sha2 0.10.8", + "thiserror", + "x25519-dalek 2.0.0", ] [[package]] @@ -1667,6 +5335,17 @@ dependencies = [ "objc_exception", ] +[[package]] +name = "objc-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + [[package]] name = "objc_exception" version = "0.1.2" @@ -1700,6 +5379,18 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "open" version = "3.2.0" @@ -1710,12 +5401,132 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "openssl" +version = "0.10.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +dependencies = [ + "bitflags 2.4.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.1.6+3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439fac53e092cd7442a3660c85dde4643ab3b5bd39040912388dcdabf6b88085" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "os_pipe" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "overload" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "p256" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19736d80675fbe9fe33426268150b951a3fb8f5cfca2a23a17c85ef3adb24e3b" +dependencies = [ + "ecdsa 0.13.4", + "elliptic-curve 0.11.12", + "sec1 0.2.1", +] + +[[package]] +name = "p384" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755d8266e41f57bd8562ed9b6e93cdcf73ead050e1e8c3a27ea3871b6643a20c" +dependencies = [ + "ecdsa 0.13.4", + "elliptic-curve 0.11.12", + "sec1 0.2.1", +] + +[[package]] +name = "pairing" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de9d09263c9966e8196fe0380c9dbbc7ea114b5cf371ba29004bc1f9c6db7f3" +dependencies = [ + "group 0.10.0", +] + +[[package]] +name = "pairing" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +dependencies = [ + "group 0.11.0", +] + [[package]] name = "pango" version = "0.15.10" @@ -1741,6 +5552,31 @@ dependencies = [ "system-deps 6.1.2", ] +[[package]] +name = "parity-tokio-ipc" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" +dependencies = [ + "futures", + "libc", + "log", + "rand 0.7.3", + "tokio", + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + [[package]] name = "parking_lot" version = "0.12.1" @@ -1748,7 +5584,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core", + "parking_lot_core 0.9.9", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", ] [[package]] @@ -1761,21 +5611,144 @@ dependencies = [ "libc", "redox_syscall 0.4.1", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "pathdiff" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + +[[package]] +name = "peg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088" + +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap 2.0.2", +] + +[[package]] +name = "pfctl" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e0c1e1bc65fb241166b7ec8278d89cc2432d41adcbe57ffe1095c81e1d7b44" +dependencies = [ + "derive_builder", + "errno 0.2.8", + "error-chain", + "ioctl-sys", + "ipnetwork", + "libc", +] + [[package]] name = "phf" version = "0.8.0" @@ -1884,6 +5857,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -1896,12 +5889,28 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.8", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "platforms" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14e6ab3f592e6fb464fc9712d8d6e6912de6473954635fd76a589d832cffcbb0" + [[package]] name = "plist" version = "1.5.1" @@ -1929,6 +5938,40 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.5.1", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug 0.3.0", + "universal-hash 0.4.1", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1947,6 +5990,26 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger 0.7.1", + "log", +] + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1996,6 +6059,104 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + +[[package]] +name = "prost" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +dependencies = [ + "bytes", + "prost-derive 0.12.3", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools 0.10.5", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost 0.11.9", + "prost-types 0.11.9", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-derive" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +dependencies = [ + "anyhow", + "itertools 0.11.0", + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + +[[package]] +name = "prost-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +dependencies = [ + "prost 0.12.3", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quick-xml" version = "0.30.0" @@ -2014,6 +6175,22 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.7.3" @@ -2077,6 +6254,16 @@ dependencies = [ "getrandom 0.2.10", ] +[[package]] +name = "rand_distr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" +dependencies = [ + "num-traits", + "rand 0.7.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2101,6 +6288,26 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2183,6 +6390,165 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwest" +version = "0.11.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +dependencies = [ + "base64 0.21.5", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.9", + "rustls-native-certs", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "system-configuration", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.24.1", + "tokio-util 0.7.10", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "resolv-conf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" +dependencies = [ + "hostname", + "quick-error 1.2.3", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle 2.4.1", +] + +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom 0.2.10", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "ring-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6242f589b69a0555addb0bb759f81e5cba40485d38b36f780ab3a588b2bdf064" +dependencies = [ + "aead 0.4.3", + "digest 0.9.0", + "ecdsa 0.13.4", + "ed25519 1.5.3", + "generic-array 0.14.7", + "opaque-debug 0.3.0", + "p256", + "p384", + "ring 0.16.20", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rtnetlink" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f1cfa18f8cebe685373a2697915d7e0db3b4554918bba118385e0f71f258a7" +dependencies = [ + "futures", + "log", + "netlink-packet-route 0.13.0", + "netlink-proto", + "nix 0.24.3", + "thiserror", + "tokio", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -2195,7 +6561,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.20", ] [[package]] @@ -2205,12 +6571,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ce50cb2e16c2903e30d1cbccfd8387a74b9d4c938b6a4c5ec6cc7556f7a8a0" dependencies = [ "bitflags 2.4.1", - "errno", + "errno 0.3.5", "libc", "linux-raw-sys", "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.1", + "log", + "ring 0.16.20", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +dependencies = [ + "log", + "ring 0.16.20", + "sct 0.7.1", + "webpki 0.22.4", +] + +[[package]] +name = "rustls" +version = "0.21.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-webpki", + "sct 0.7.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.5", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.7", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -2238,6 +6672,40 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "schemars" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 1.0.109", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -2250,6 +6718,75 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.7", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der 0.5.1", + "generic-array 0.14.7", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.8", + "generic-array 0.14.7", + "pkcs8", + "subtle 2.4.1", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.22.0" @@ -2270,6 +6807,15 @@ dependencies = [ "thin-slice", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + [[package]] name = "semver" version = "1.0.20" @@ -2280,25 +6826,73 @@ dependencies = [ ] [[package]] -name = "serde" -version = "1.0.189" +name = "semver-parser" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "sendfd" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604b71b8fc267e13bb3023a2c901126c8f349393666a6d98ac1ae5729b701798" +dependencies = [ + "libc", + "tokio", +] + +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] -name = "serde_derive" -version = "1.0.189" +name = "serde-json-wasm" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "a15bee9b04dd165c3f4e142628982ddde884c2022a89e8ddf99c4829bf2c3a58" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] +[[package]] +name = "serde_derive_internals" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "serde_json" version = "1.0.107" @@ -2323,13 +6917,25 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.9", + "ryu", + "serde", +] + [[package]] name = "serde_with" version = "3.4.0" @@ -2353,7 +6959,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93634eb5f75a2323b16de4748022ac4297f9e76b6dced2be287a099f41b5e788" dependencies = [ - "darling", + "darling 0.20.3", "proc-macro2", "quote", "syn 2.0.38", @@ -2391,6 +6997,40 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", + "sha1-asm", +] + +[[package]] +name = "sha1-asm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ba6947745e7f86be3b8af00b7355857085dbdf8901393c89514510eb61f4e21" +dependencies = [ + "cc", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2399,7 +7039,101 @@ checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shadowsocks" +version = "1.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f16fc99441d1a1b91b5f7b5093773d631ad506761a973e0a632f1836d1b05939" +dependencies = [ + "arc-swap", + "async-trait", + "base64 0.13.1", + "byte_string", + "bytes", + "cfg-if", + "futures", + "libc", + "log", + "nix 0.23.2", + "notify", + "once_cell", + "pin-project", + "sendfd", + "serde", + "serde_json", + "serde_urlencoded", + "shadowsocks-crypto", + "socket2 0.4.10", + "thiserror", + "tokio", + "tokio-tfo", + "trust-dns-resolver", + "url", + "winapi", +] + +[[package]] +name = "shadowsocks-crypto" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd381517e3eb8fec5090696debfdea972d8afe6fc926c26c7bfd5fee9053efbd" +dependencies = [ + "aes 0.7.5", + "aes-gcm", + "cfg-if", + "chacha20 0.8.2", + "chacha20poly1305 0.9.1", + "hkdf 0.12.3", + "md-5", + "rand 0.8.5", + "ring-compat", + "sha1", +] + +[[package]] +name = "shadowsocks-service" +version = "1.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c3eb3ce85fb4c1f7451d2a5704503d3146ed7d626a17a63194092f07be45a4" +dependencies = [ + "arc-swap", + "async-trait", + "byte_string", + "byteorder", + "bytes", + "cfg-if", + "futures", + "idna 0.2.3", + "ipnet", + "iprange", + "json5", + "libc", + "log", + "lru_time_cache", + "nix 0.23.2", + "once_cell", + "pin-project", + "regex", + "serde", + "shadowsocks", + "socket2 0.4.10", + "spin 0.9.8", + "thiserror", + "tokio", + "winapi", ] [[package]] @@ -2411,6 +7145,50 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared_child" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "shell-escape" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bb67a18fa91266cc7807181f62f9178a6873bfad7dc788c42e6430db40184f" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -2438,6 +7216,26 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + [[package]] name = "soup2" version = "0.2.1" @@ -2466,6 +7264,248 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "sphinx-packet" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4" +dependencies = [ + "aes 0.7.5", + "arrayref", + "blake2", + "bs58 0.4.0", + "byteorder", + "chacha", + "curve25519-dalek 3.2.0", + "digest 0.9.0", + "hkdf 0.11.0", + "hmac 0.11.0", + "lioness", + "log", + "rand 0.7.3", + "rand_distr", + "sha2 0.9.9", + "subtle 2.4.1", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der 0.7.8", +] + +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools 0.10.5", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools 0.12.0", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" +dependencies = [ + "sqlx-core 0.5.13", + "sqlx-macros 0.5.13", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +dependencies = [ + "sqlx-core 0.6.3", + "sqlx-macros 0.6.3", +] + +[[package]] +name = "sqlx-core" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" +dependencies = [ + "ahash 0.7.7", + "atoi 0.4.0", + "bitflags 1.3.2", + "byteorder", + "bytes", + "crc 2.1.0", + "crossbeam-queue", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "hashlink 0.7.0", + "hex", + "indexmap 1.9.3", + "itoa 1.0.9", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.19.1", + "sha2 0.10.8", + "smallvec", + "sqlformat 0.1.8", + "sqlx-rt 0.5.13", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "webpki 0.21.4", + "webpki-roots 0.21.1", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +dependencies = [ + "ahash 0.7.7", + "atoi 1.0.0", + "bitflags 1.3.2", + "byteorder", + "bytes", + "crc 3.0.1", + "crossbeam-queue", + "dotenvy", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "hashlink 0.8.4", + "hex", + "indexmap 1.9.3", + "itoa 1.0.9", + "libc", + "libsqlite3-sys", + "log", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rustls 0.20.9", + "rustls-pemfile", + "sha2 0.10.8", + "smallvec", + "sqlformat 0.2.3", + "sqlx-rt 0.6.3", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "webpki-roots 0.22.6", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" +dependencies = [ + "dotenv", + "either", + "heck 0.4.1", + "once_cell", + "proc-macro2", + "quote", + "sha2 0.10.8", + "sqlx-core 0.5.13", + "sqlx-rt 0.5.13", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "once_cell", + "proc-macro2", + "quote", + "sha2 0.10.8", + "sqlx-core 0.6.3", + "sqlx-rt 0.6.3", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls 0.22.0", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls 0.23.4", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2489,7 +7529,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -2507,12 +7547,65 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +dependencies = [ + "finl_unicode", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subslice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a8e4809a3bb02de01f1f7faf1ba01a83af9e8eabcd4d31dd6e413d14d56aae" +dependencies = [ + "memchr", +] + +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + [[package]] name = "syn" version = "1.0.109" @@ -2535,6 +7628,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-xid", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "5.0.0" @@ -2557,10 +7689,264 @@ dependencies = [ "cfg-expr 0.15.5", "heck 0.4.1", "pkg-config", - "toml 0.8.2", + "toml 0.8.5", "version-compare 0.1.1", ] +[[package]] +name = "talpid-core" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "async-trait", + "atty", + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "chrono", + "duct", + "err-derive", + "futures", + "hex", + "inotify 0.10.2", + "internet-checksum", + "ipnetwork", + "jnix", + "lazy_static", + "libc", + "log", + "memoffset 0.6.5", + "mnl", + "netlink-packet-route 0.13.0", + "netlink-sys", + "nftnl", + "nix 0.23.2", + "once_cell", + "os_pipe 0.9.2", + "parity-tokio-ipc", + "parking_lot 0.11.2", + "pfctl", + "prost 0.11.9", + "rand 0.8.5", + "regex", + "resolv-conf", + "rtnetlink", + "shadowsocks-service", + "shell-escape", + "socket2 0.4.10", + "subslice", + "system-configuration", + "talpid-dbus", + "talpid-openvpn", + "talpid-routing", + "talpid-time", + "talpid-tunnel", + "talpid-tunnel-config-client", + "talpid-types", + "talpid-windows-net", + "talpid-wireguard", + "tokio", + "tonic", + "tonic-build", + "triggered", + "trust-dns-server", + "tun", + "uuid 0.8.2", + "which", + "widestring", + "windows-service", + "windows-sys 0.42.0", + "winreg 0.7.0", + "zeroize", +] + +[[package]] +name = "talpid-dbus" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "dbus", + "err-derive", + "lazy_static", + "libc", + "log", + "tokio", +] + +[[package]] +name = "talpid-openvpn" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "async-trait", + "atty", + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "duct", + "err-derive", + "futures", + "lazy_static", + "log", + "os_pipe 0.9.2", + "parity-tokio-ipc", + "parking_lot 0.11.2", + "prost 0.11.9", + "shadowsocks-service", + "shell-escape", + "socket2 0.4.10", + "talpid-routing", + "talpid-tunnel", + "talpid-types", + "talpid-windows-net", + "tokio", + "tonic", + "tonic-build", + "triggered", + "uuid 0.8.2", + "which", + "widestring", + "windows-sys 0.42.0", + "winreg 0.7.0", +] + +[[package]] +name = "talpid-routing" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "err-derive", + "futures", + "ipnetwork", + "lazy_static", + "libc", + "log", + "netlink-packet-route 0.13.0", + "netlink-sys", + "rtnetlink", + "socket2 0.4.10", + "talpid-types", + "talpid-windows-net", + "tokio", + "tokio-stream", + "widestring", + "windows-sys 0.42.0", +] + +[[package]] +name = "talpid-time" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "libc", + "tokio", +] + +[[package]] +name = "talpid-tunnel" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "cfg-if", + "duct", + "err-derive", + "futures", + "ipnetwork", + "jnix", + "log", + "nix 0.23.2", + "talpid-routing", + "talpid-types", + "talpid-windows-net", + "tokio", + "tun", + "windows-sys 0.42.0", +] + +[[package]] +name = "talpid-tunnel-config-client" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "classic-mceliece-rust", + "log", + "prost 0.11.9", + "rand 0.8.5", + "talpid-types", + "tokio", + "tonic", + "tonic-build", + "tower", +] + +[[package]] +name = "talpid-types" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "base64 0.13.1", + "err-derive", + "ipnetwork", + "jnix", + "rand 0.8.5", + "serde", + "x25519-dalek 2.0.0", + "zeroize", +] + +[[package]] +name = "talpid-windows-net" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "err-derive", + "futures", + "libc", + "socket2 0.4.10", + "winapi", + "windows-sys 0.42.0", +] + +[[package]] +name = "talpid-wireguard" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "chrono", + "duct", + "err-derive", + "futures", + "hex", + "internet-checksum", + "ipnetwork", + "lazy_static", + "libc", + "log", + "netlink-packet-core 0.4.2", + "netlink-packet-route 0.13.0", + "netlink-packet-utils", + "netlink-proto", + "nix 0.23.2", + "parking_lot 0.11.2", + "rand 0.8.5", + "rtnetlink", + "socket2 0.4.10", + "talpid-dbus", + "talpid-routing", + "talpid-tunnel", + "talpid-tunnel-config-client", + "talpid-types", + "talpid-windows-net", + "tokio", + "tokio-stream", + "tunnel-obfuscation", + "widestring", + "windows-sys 0.42.0", + "zeroize", +] + [[package]] name = "tao" version = "0.16.4" @@ -2586,7 +7972,7 @@ dependencies = [ "gtk", "image", "instant", - "jni", + "jni 0.20.0", "lazy_static", "libc", "log", @@ -2595,14 +7981,14 @@ dependencies = [ "ndk-sys", "objc", "once_cell", - "parking_lot", + "parking_lot 0.12.1", "png", "raw-window-handle", "scopeguard", "serde", "tao-macros", "unicode-segmentation", - "uuid", + "uuid 1.5.0", "windows 0.39.0", "windows-implement", "x11-dl", @@ -2619,6 +8005,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "tar" version = "0.4.40" @@ -2643,6 +8035,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9" dependencies = [ "anyhow", + "base64 0.21.5", + "bytes", "cocoa", "dirs-next", "embed_plist", @@ -2655,6 +8049,7 @@ dependencies = [ "heck 0.4.1", "http", "ignore", + "minisign-verify", "objc", "once_cell", "open", @@ -2662,7 +8057,9 @@ dependencies = [ "rand 0.8.5", "raw-window-handle", "regex", - "semver", + "reqwest", + "rfd", + "semver 1.0.20", "serde", "serde_json", "serde_repr", @@ -2675,12 +8072,14 @@ dependencies = [ "tauri-utils", "tempfile", "thiserror", + "time", "tokio", "url", - "uuid", + "uuid 1.5.0", "webkit2gtk", "webview2-com", "windows 0.39.0", + "zip", ] [[package]] @@ -2694,7 +8093,7 @@ dependencies = [ "dirs-next", "heck 0.4.1", "json-patch", - "semver", + "semver 1.0.20", "serde", "serde_json", "tauri-utils", @@ -2717,14 +8116,14 @@ dependencies = [ "proc-macro2", "quote", "regex", - "semver", + "semver 1.0.20", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "tauri-utils", "thiserror", "time", - "uuid", + "uuid 1.5.0", "walkdir", ] @@ -2758,7 +8157,7 @@ dependencies = [ "tauri-utils", "thiserror", "url", - "uuid", + "uuid 1.5.0", "webview2-com", "windows 0.39.0", ] @@ -2776,7 +8175,7 @@ dependencies = [ "raw-window-handle", "tauri-runtime", "tauri-utils", - "uuid", + "uuid 1.5.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -2803,7 +8202,7 @@ dependencies = [ "phf 0.10.1", "proc-macro2", "quote", - "semver", + "semver 1.0.20", "serde", "serde_json", "serde_with", @@ -2836,6 +8235,101 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tendermint" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519 2.2.3", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost 0.12.3", + "prost-types 0.12.3", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.8", + "signature 2.2.0", + "subtle 2.4.1", + "subtle-encoding", + "tendermint-proto", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-config" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" +dependencies = [ + "flex-error", + "serde", + "serde_json", + "tendermint", + "toml 0.5.11", + "url", +] + +[[package]] +name = "tendermint-proto" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.12.3", + "prost-types 0.12.3", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-rpc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" +dependencies = [ + "async-trait", + "bytes", + "flex-error", + "futures", + "getrandom 0.2.10", + "peg", + "pin-project", + "reqwest", + "semver 1.0.20", + "serde", + "serde_bytes", + "serde_json", + "subtle 2.4.1", + "subtle-encoding", + "tendermint", + "tendermint-config", + "tendermint-proto", + "thiserror", + "time", + "tokio", + "tracing", + "url", + "uuid 0.8.2", + "walkdir", +] + [[package]] name = "tendril" version = "0.4.3" @@ -2847,6 +8341,15 @@ dependencies = [ "utf-8", ] +[[package]] +name = "termcolor" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" +dependencies = [ + "winapi-util", +] + [[package]] name = "thin-slice" version = "0.1.1" @@ -2891,6 +8394,7 @@ checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa 1.0.9", + "js-sys", "powerfmt", "serde", "time-core", @@ -2935,8 +8439,150 @@ checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ "backtrace", "bytes", + "libc", + "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", + "socket2 0.5.5", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.9", + "tokio", + "webpki 0.22.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.9", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util 0.7.10", +] + +[[package]] +name = "tokio-tfo" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4279aec5ded232170bf39130dd0e0deaed2c9f31cd3b5db1f2021056bcf5f94a" +dependencies = [ + "cfg-if", + "futures", + "libc", + "log", + "once_cell", + "pin-project", + "socket2 0.4.10", + "tokio", + "winapi", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", + "tracing", ] [[package]] @@ -2962,21 +8608,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +checksum = "3efaf127c78d5339cc547cce4e4d973bd5e4f56e949a06d091c082ebeef2f800" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.20.2", + "toml_edit 0.20.5", ] [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" dependencies = [ "serde", ] @@ -2996,9 +8642,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.2" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +checksum = "782bf6c2ddf761c1e7855405e8975472acf76f7f36d0d4328bd3b7a2fae12a85" dependencies = [ "indexmap 2.0.2", "serde", @@ -3007,12 +8653,90 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "prost-derive 0.11.9", + "tokio", + "tokio-stream", + "tokio-util 0.7.10", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util 0.7.10", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3039,6 +8763,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -3068,6 +8802,35 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "trackable" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98abb9e7300b9ac902cc04920945a874c1973e08c310627cc4458c04b70dd32" +dependencies = [ + "trackable 1.3.0", + "trackable_derive", +] + +[[package]] +name = "trackable" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15bd114abb99ef8cee977e517c8f37aee63f184f2d08e3e6ceca092373369ae" +dependencies = [ + "trackable_derive", +] + +[[package]] +name = "trackable_derive" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebeb235c5847e2f82cfe0f07eb971d1e5f6804b18dac2ae16349cc604380f82f" +dependencies = [ + "quote", + "syn 1.0.109", +] + [[package]] name = "treediff" version = "4.0.2" @@ -3077,12 +8840,206 @@ dependencies = [ "serde_json", ] +[[package]] +name = "triggered" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce148eae0d1a376c1b94ae651fc3261d9cb8294788b962b7382066376503a2d1" + +[[package]] +name = "trust-dns-client" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d9ba1c6079f6f9b4664e482db1700bd53d2ee77b1c9752c1d7a66c0c8bda99" +dependencies = [ + "cfg-if", + "data-encoding", + "futures-channel", + "futures-util", + "lazy_static", + "log", + "radix_trie", + "rand 0.8.5", + "thiserror", + "time", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "trust-dns-proto" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna 0.2.3", + "ipnet", + "lazy_static", + "log", + "rand 0.8.5", + "serde", + "smallvec", + "thiserror", + "tinyvec", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" +dependencies = [ + "cfg-if", + "futures-util", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "parking_lot 0.12.1", + "resolv-conf", + "serde", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "trust-dns-server" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a395a2e0fd8aac9b4613767a5b4ba4b2040de1b767fa03ace8c9d6f351d60b2d" +dependencies = [ + "async-trait", + "bytes", + "cfg-if", + "enum-as-inner", + "env_logger 0.9.3", + "futures-executor", + "futures-util", + "log", + "serde", + "thiserror", + "time", + "tokio", + "toml 0.5.11", + "trust-dns-client", + "trust-dns-proto", + "trust-dns-resolver", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "ts-rs" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ff1f8c90369bc172200013ac17ae86e7b5def580687df4e6127883454ff2b0" +dependencies = [ + "chrono", + "thiserror", + "ts-rs-macros", +] + +[[package]] +name = "ts-rs-macros" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6f41cc0aeb7a4a55730188e147d3795a7349b501f8334697fd37629b896cdc2" +dependencies = [ + "Inflector", + "proc-macro2", + "quote", + "syn 2.0.38", + "termcolor", +] + +[[package]] +name = "tun" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc25e23adc6cac7dd895ce2780f255902290fc39b00e1ae3c33e89f3d20fa66" +dependencies = [ + "byteorder", + "bytes", + "futures-core", + "ioctl-sys", + "libc", + "thiserror", + "tokio", + "tokio-util 0.6.10", +] + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "tunnel-obfuscation" +version = "0.0.0" +source = "git+https://github.com/mullvad/mullvadvpn-app?tag=2023.2#ccfbaa279d04b4f93dff489880e3fb3ae67025bd" +dependencies = [ + "async-trait", + "err-derive", + "futures", + "tokio", + "udp-over-tcp", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "udp-over-tcp" +version = "0.2.0" +source = "git+https://github.com/mullvad/udp-over-tcp?rev=4d52f93cd9962562cb52d66e36771d5f5c70e25a#4d52f93cd9962562cb52d66e36771d5f5c70e25a" +dependencies = [ + "err-context", + "futures", + "lazy_static", + "log", + "nix 0.23.2", + "tokio", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -3110,6 +9067,50 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unicode_categories" +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.7", + "subtle 2.4.1", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle 2.4.1", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -3117,7 +9118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", - "idna", + "idna 0.4.0", "percent-encoding", "serde", ] @@ -3128,6 +9129,21 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom 0.2.10", +] + [[package]] name = "uuid" version = "1.5.0" @@ -3143,6 +9159,29 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vergen" +version = "7.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447f9238a4553957277b3ee09d80babeae0811f1b3baefb093de1c0448437a37" +dependencies = [ + "anyhow", + "cfg-if", + "enum-iterator", + "getset", + "git2", + "rustc_version", + "rustversion", + "thiserror", + "time", +] + [[package]] name = "version-compare" version = "0.0.11" @@ -3191,6 +9230,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" @@ -3228,6 +9276,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.87" @@ -3257,6 +9317,59 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-streams" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4609d447824375f43e1ffbc051b50ad8f4b3ae8219680c94452ea05eb240ac7" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasm-utils" +version = "0.1.0" +source = "git+https://github.com/nymtech/nym?rev=e8956603d76fb12be7f70052981dce32367e0405#e8956603d76fb12be7f70052981dce32367e0405" +dependencies = [ + "futures", + "getrandom 0.2.10", + "gloo-net", + "gloo-utils", + "js-sys", + "tungstenite", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmtimer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f656cd8858a5164932d8a90f936700860976ec21eb00e0fe2aa8cab13f6b4cf" +dependencies = [ + "futures", + "js-sys", + "parking_lot 0.12.1", + "pin-utils", + "slab", + "wasm-bindgen", +] + +[[package]] +name = "web-sys" +version = "0.3.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "0.18.2" @@ -3304,6 +9417,44 @@ dependencies = [ "system-deps 6.1.2", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring 0.17.7", + "untrusted 0.9.0", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki 0.22.4", +] + [[package]] name = "webview2-com" version = "0.19.1" @@ -3342,6 +9493,24 @@ dependencies = [ "windows-metadata", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "widestring" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653f141f39ec16bba3c5abe400a0c60da7468261cc2cbf36805022876bc721a8" + [[package]] name = "winapi" version = "0.3.9" @@ -3373,6 +9542,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + [[package]] name = "windows" version = "0.39.0" @@ -3393,7 +9575,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3412,7 +9594,7 @@ version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -3431,6 +9613,31 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" +[[package]] +name = "windows-service" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917fdb865e7ff03af9dd86609f8767bc88fefba89e8efd569de8e208af8724b3" +dependencies = [ + "bitflags 1.3.2", + "err-derive", + "widestring", + "windows-sys 0.36.1", +] + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -3446,13 +9653,46 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -3470,6 +9710,21 @@ dependencies = [ "windows_x86_64_msvc 0.48.5", ] +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm 0.52.0", + "windows_aarch64_msvc 0.52.0", + "windows_i686_gnu 0.52.0", + "windows_i686_msvc 0.52.0", + "windows_x86_64_gnu 0.52.0", + "windows_x86_64_gnullvm 0.52.0", + "windows_x86_64_msvc 0.52.0", +] + [[package]] name = "windows-tokens" version = "0.39.0" @@ -3488,6 +9743,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -3506,6 +9779,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -3524,6 +9815,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_i686_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -3542,6 +9851,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -3560,6 +9887,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3572,6 +9905,24 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" @@ -3590,6 +9941,12 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + [[package]] name = "winnow" version = "0.5.17" @@ -3599,6 +9956,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +dependencies = [ + "winapi", +] + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winreg" version = "0.51.0" @@ -3635,7 +10011,7 @@ dependencies = [ "once_cell", "serde", "serde_json", - "sha2", + "sha2 0.10.8", "soup2", "tao", "thiserror", @@ -3647,6 +10023,15 @@ dependencies = [ "windows-implement", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" @@ -3668,6 +10053,29 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "x25519-dalek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +dependencies = [ + "curve25519-dalek 3.2.0", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66477291e7e8d2b0ff1bcb900bf29489a9692816d79874bea351e7a8b6de96" +dependencies = [ + "curve25519-dalek 4.1.1", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "xattr" version = "1.0.1" @@ -3676,3 +10084,54 @@ checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" dependencies = [ "libc", ] + +[[package]] +name = "zerocopy" +version = "0.7.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d075cf85bbb114e933343e087b92f2146bac0d55b534cbb8188becf0039948e" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86cd5ca076997b97ef09d3ad65efe811fa68c9e874cb636ccb211223a813b0c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "zip" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", +] diff --git a/nym-vpn/ui/src-tauri/Cargo.toml b/nym-vpn/ui/src-tauri/Cargo.toml index 93b390a573..a464cf40ae 100644 --- a/nym-vpn/ui/src-tauri/Cargo.toml +++ b/nym-vpn/ui/src-tauri/Cargo.toml @@ -1,24 +1,39 @@ [package] -name = "nymvpn-ui" -version = "0.0.0" +name = "nym-vpn-ui" +version = "0.0.1" description = "Application UI for Nym VPN desktop clients" authors = ["you"] license = "" repository = "" edition = "2021" -[lib] -crate-type = ["cdylib"] - [build-dependencies] tauri-build = { version = "1.5", features = [] } [dependencies] -tauri = { version = "1.5", features = ["shell-open"] } +tauri = { version = "1.5.2", features = ["updater", "shell-open"] } +tokio = { version = "1.33", features = ["rt", "sync", "time", "fs"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +tracing = "0.1" +tracing-subscriber = { version = "0.3.1", features = ["tracing-log", "env-filter"] } +anyhow = "1.0" +dotenvy = "0.15.7" +thiserror = "1.0" +ts-rs = { version = "7.0.0", features = ["chrono-impl"] } +once_cell = "1.18.0" +toml = "0.8.5" +time = "0.3.9" +nym-vpn-lib = { path = "../../../../nym-vpn-client/nym-vpn-lib" } +futures = "0.3.15" + +# TODO Ugly workaround to force a working setup for nym-vpn-lib +# We should get rid of this ASAP +shadowsocks = { version = "~1.14.2" } +shadowsocks-service = { version = "~1.14.3" } [features] # this feature is used for production builds or when `devPath` points to the filesystem # DO NOT REMOVE!! custom-protocol = ["tauri/custom-protocol"] + diff --git a/nym-vpn/ui/src-tauri/icons/128x128.png b/nym-vpn/ui/src-tauri/icons/128x128.png index 6be5e50e9b..2f8c35c828 100644 Binary files a/nym-vpn/ui/src-tauri/icons/128x128.png and b/nym-vpn/ui/src-tauri/icons/128x128.png differ diff --git a/nym-vpn/ui/src-tauri/icons/128x128@2x.png b/nym-vpn/ui/src-tauri/icons/128x128@2x.png index e81becee57..32de9d1f0e 100644 Binary files a/nym-vpn/ui/src-tauri/icons/128x128@2x.png and b/nym-vpn/ui/src-tauri/icons/128x128@2x.png differ diff --git a/nym-vpn/ui/src-tauri/icons/32x32.png b/nym-vpn/ui/src-tauri/icons/32x32.png index a437dd5174..32722d4b2c 100644 Binary files a/nym-vpn/ui/src-tauri/icons/32x32.png and b/nym-vpn/ui/src-tauri/icons/32x32.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square107x107Logo.png b/nym-vpn/ui/src-tauri/icons/Square107x107Logo.png index 0ca4f27198..9a6a7d50cb 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square107x107Logo.png and b/nym-vpn/ui/src-tauri/icons/Square107x107Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square142x142Logo.png b/nym-vpn/ui/src-tauri/icons/Square142x142Logo.png index b81f820394..987134e92b 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square142x142Logo.png and b/nym-vpn/ui/src-tauri/icons/Square142x142Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square150x150Logo.png b/nym-vpn/ui/src-tauri/icons/Square150x150Logo.png index 624c7bfba0..bb84ee90c9 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square150x150Logo.png and b/nym-vpn/ui/src-tauri/icons/Square150x150Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square284x284Logo.png b/nym-vpn/ui/src-tauri/icons/Square284x284Logo.png index c021d2ba76..8b936410d9 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square284x284Logo.png and b/nym-vpn/ui/src-tauri/icons/Square284x284Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square30x30Logo.png b/nym-vpn/ui/src-tauri/icons/Square30x30Logo.png index 6219700230..ebf441338e 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square30x30Logo.png and b/nym-vpn/ui/src-tauri/icons/Square30x30Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square310x310Logo.png b/nym-vpn/ui/src-tauri/icons/Square310x310Logo.png index f9bc048394..4793ed5cd9 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square310x310Logo.png and b/nym-vpn/ui/src-tauri/icons/Square310x310Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square44x44Logo.png b/nym-vpn/ui/src-tauri/icons/Square44x44Logo.png index d5fbfb2ab4..966966252a 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square44x44Logo.png and b/nym-vpn/ui/src-tauri/icons/Square44x44Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square71x71Logo.png b/nym-vpn/ui/src-tauri/icons/Square71x71Logo.png index 63440d7984..b161cd4833 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square71x71Logo.png and b/nym-vpn/ui/src-tauri/icons/Square71x71Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/Square89x89Logo.png b/nym-vpn/ui/src-tauri/icons/Square89x89Logo.png index f3f705af2f..5304954324 100644 Binary files a/nym-vpn/ui/src-tauri/icons/Square89x89Logo.png and b/nym-vpn/ui/src-tauri/icons/Square89x89Logo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/StoreLogo.png b/nym-vpn/ui/src-tauri/icons/StoreLogo.png index 4556388261..28ddc2eb28 100644 Binary files a/nym-vpn/ui/src-tauri/icons/StoreLogo.png and b/nym-vpn/ui/src-tauri/icons/StoreLogo.png differ diff --git a/nym-vpn/ui/src-tauri/icons/icon.icns b/nym-vpn/ui/src-tauri/icons/icon.icns index 12a5bcee26..66d61665db 100644 Binary files a/nym-vpn/ui/src-tauri/icons/icon.icns and b/nym-vpn/ui/src-tauri/icons/icon.icns differ diff --git a/nym-vpn/ui/src-tauri/icons/icon.ico b/nym-vpn/ui/src-tauri/icons/icon.ico index b3636e4b22..c9a3c54620 100644 Binary files a/nym-vpn/ui/src-tauri/icons/icon.ico and b/nym-vpn/ui/src-tauri/icons/icon.ico differ diff --git a/nym-vpn/ui/src-tauri/icons/icon.png b/nym-vpn/ui/src-tauri/icons/icon.png index e1cd2619e0..0a6a4c715c 100644 Binary files a/nym-vpn/ui/src-tauri/icons/icon.png and b/nym-vpn/ui/src-tauri/icons/icon.png differ diff --git a/nym-vpn/ui/src-tauri/src/commands/app_data.rs b/nym-vpn/ui/src-tauri/src/commands/app_data.rs new file mode 100644 index 0000000000..d4acdaa5f1 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/commands/app_data.rs @@ -0,0 +1,167 @@ +use tauri::State; +use tracing::{debug, instrument}; + +use crate::country::COUNTRIES; +use crate::states::app::Country; +use crate::{ + error::{CmdError, CmdErrorSource}, + fs::data::{AppData, UiTheme}, + states::SharedAppData, +}; + +#[instrument] +#[tauri::command] +pub fn get_node_countries() -> Result, CmdError> { + debug!("get_node_countries"); + // TODO fetch the list of countries from some API + Ok(COUNTRIES.clone()) +} + +#[instrument(skip(state))] +#[tauri::command] +pub async fn set_app_data( + state: State<'_, SharedAppData>, + data: Option, +) -> Result<(), CmdError> { + debug!("set_app_data"); + let mut app_data_store = state.lock().await; + if let Some(data) = data { + app_data_store.data = data; + } + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + + Ok(()) +} + +#[instrument(skip_all)] +#[tauri::command] +pub async fn get_app_data( + state: State<'_, SharedAppData>, + data: Option, +) -> Result { + debug!("get_app_data"); + let mut app_data_store = state.lock().await; + if let Some(data) = data { + app_data_store.data = data; + } + let data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + + Ok(data) +} + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_ui_theme( + data_state: State<'_, SharedAppData>, + theme: UiTheme, +) -> Result<(), CmdError> { + debug!("set_ui_theme"); + + // save the selected UI theme to disk + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.ui_theme = Some(theme); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_root_font_size( + data_state: State<'_, SharedAppData>, + size: u32, +) -> Result<(), CmdError> { + debug!("set_root_font_size"); + + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.ui_root_font_size = Some(size); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_entry_location_selector( + data_state: State<'_, SharedAppData>, + entry_selector: bool, +) -> Result<(), CmdError> { + debug!("set_entry_location_selector"); + + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.entry_location_selector = Some(entry_selector); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_auto_connect( + data_state: State<'_, SharedAppData>, + entry_selector: bool, +) -> Result<(), CmdError> { + debug!("set_auto_connect"); + + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.autoconnect = Some(entry_selector); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} + +#[instrument(skip(data_state))] +#[tauri::command] +pub async fn set_monitoring( + data_state: State<'_, SharedAppData>, + entry_selector: bool, +) -> Result<(), CmdError> { + debug!("set_monitoring"); + + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.monitoring = Some(entry_selector); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} diff --git a/nym-vpn/ui/src-tauri/src/commands/connection.rs b/nym-vpn/ui/src-tauri/src/commands/connection.rs new file mode 100644 index 0000000000..74eca48b2f --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/commands/connection.rs @@ -0,0 +1,278 @@ +use futures::SinkExt; +use nym_vpn_lib::gateway_client::{EntryPoint, ExitPoint}; +use nym_vpn_lib::{NymVpnCtrlMessage, NymVpnHandle}; +use tauri::{Manager, State}; +use tracing::{debug, error, info, instrument, trace}; + +use crate::{ + error::{CmdError, CmdErrorSource}, + states::{ + app::{ConnectionState, VpnMode}, + SharedAppData, SharedAppState, + }, + vpn_client::{ + create_vpn_config, spawn_exit_listener, spawn_status_listener, ConnectProgressMsg, + ConnectionEventPayload, ProgressEventPayload, EVENT_CONNECTION_PROGRESS, + EVENT_CONNECTION_STATE, + }, +}; + +const DEFAULT_NODE_LOCATION: &str = "DE"; + +#[instrument(skip_all)] +#[tauri::command] +pub async fn get_connection_state( + state: State<'_, SharedAppState>, +) -> Result { + debug!("get_connection_state"); + let app_state = state.lock().await; + Ok(app_state.state) +} + +#[instrument(skip_all)] +#[tauri::command] +pub async fn connect( + app: tauri::AppHandle, + state: State<'_, SharedAppState>, +) -> Result { + debug!("connect"); + { + let mut app_state = state.lock().await; + if app_state.state != ConnectionState::Disconnected { + return Err(CmdError::new( + CmdErrorSource::CallerError, + format!("cannot connect from state {:?}", app_state.state), + )); + }; + + // switch to "Connecting" state + trace!("update connection state [Connecting]"); + app_state.state = ConnectionState::Connecting; + } + + debug!("sending event [{}]: Connecting", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new(ConnectionState::Connecting, None, None), + ) + .ok(); + + trace!( + "sending event [{}]: Initializing", + EVENT_CONNECTION_PROGRESS + ); + app.emit_all( + EVENT_CONNECTION_PROGRESS, + ProgressEventPayload { + key: ConnectProgressMsg::Initializing, + }, + ) + .ok(); + + let app_state = state.lock().await; + + let entry_point = match app_state.entry_node_location { + Some(ref entry_node_location) => { + debug!( + "entry node location set, using: {}", + entry_node_location.code + ); + EntryPoint::Location(entry_node_location.code.clone()) + } + _ => { + debug!( + "entry node location not set, using default: {}", + DEFAULT_NODE_LOCATION + ); + EntryPoint::Location(DEFAULT_NODE_LOCATION.into()) + } + }; + let exit_point = match app_state.exit_node_location { + Some(ref exit_node_location) => { + debug!("exit node location set, using: {}", exit_node_location.code); + ExitPoint::Location(exit_node_location.code.clone()) + } + _ => { + debug!( + "exit node location not set, using default: {}", + DEFAULT_NODE_LOCATION + ); + ExitPoint::Location(DEFAULT_NODE_LOCATION.into()) + } + }; + + let mut vpn_config = create_vpn_config(entry_point, exit_point); + if let VpnMode::TwoHop = app_state.vpn_mode { + info!("2-hop mode enabled"); + vpn_config.enable_two_hop = true; + } else { + info!("5-hop mode enabled"); + } + // vpn_config.disable_routing = true; + // !! release app_state mutex + drop(app_state); + + // spawn the VPN client and start a new connection + let NymVpnHandle { + vpn_ctrl_tx, + vpn_status_rx, + vpn_exit_rx, + } = nym_vpn_lib::spawn_nym_vpn(vpn_config).map_err(|e| { + let err_message = format!("fail to initialize Nym VPN client: {}", e); + error!(err_message); + debug!("sending event [{}]: Disconnected", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Disconnected, + Some(err_message.clone()), + None, + ), + ) + .ok(); + CmdError::new(CmdErrorSource::InternalError, err_message) + })?; + info!("nym vpn client spawned"); + trace!("sending event [{}]: InitDone", EVENT_CONNECTION_PROGRESS); + app.emit_all( + EVENT_CONNECTION_PROGRESS, + ProgressEventPayload { + key: ConnectProgressMsg::InitDone, + }, + ) + .ok(); + + // Start exit message listener + // This will listen for the (single) exit message from the VPN client and update the UI accordingly + debug!("starting exit listener"); + spawn_exit_listener(app.clone(), state.inner().clone(), vpn_exit_rx) + .await + .ok(); + + // Start the VPN status listener + // This will listen for status messages from the VPN client and update the UI accordingly + debug!("starting status listener"); + spawn_status_listener(app, state.inner().clone(), vpn_status_rx) + .await + .ok(); + + // Store the vpn control tx in the app state, which will be used to send control messages to + // the running background VPN task, such as to disconnect. + trace!("added vpn_ctrl_tx to app state"); + let mut state = state.lock().await; + state.vpn_ctrl_tx = Some(vpn_ctrl_tx); + + Ok(state.state) +} + +#[instrument(skip_all)] +#[tauri::command] +pub async fn disconnect( + app: tauri::AppHandle, + state: State<'_, SharedAppState>, +) -> Result { + debug!("disconnect"); + let mut app_state = state.lock().await; + if app_state.state != ConnectionState::Connected { + return Err(CmdError::new( + CmdErrorSource::CallerError, + format!("cannot disconnect from state {:?}", app_state.state), + )); + }; + + // switch to "Disconnecting" state + trace!("update connection state [Disconnecting]"); + app_state.state = ConnectionState::Disconnecting; + + debug!("sending event [{}]: Disconnecting", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new(ConnectionState::Disconnecting, None, None), + ) + .ok(); + + let Some(ref mut vpn_tx) = app_state.vpn_ctrl_tx else { + trace!("update connection state [Disconnected]"); + app_state.state = ConnectionState::Disconnected; + app_state.connection_start_time = None; + debug!("sending event [{}]: Disconnected", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Disconnected, + Some("vpn handle has not been initialized".to_string()), + None, + ), + ) + .ok(); + return Err(CmdError::new( + CmdErrorSource::InternalError, + "vpn handle has not been initialized".to_string(), + )); + }; + + // send Stop message to the VPN client + debug!("sending Stop message to VPN client"); + vpn_tx.send(NymVpnCtrlMessage::Stop).await.map_err(|e| { + let err_message = format!("failed to send Stop message to VPN client: {}", e); + error!(err_message); + debug!("sending event [{}]: Disconnected", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Disconnected, + Some(err_message.clone()), + None, + ), + ) + .ok(); + CmdError::new(CmdErrorSource::InternalError, err_message) + })?; + debug!("Stop message sent"); + + Ok(app_state.state) +} + +#[instrument(skip_all)] +#[tauri::command] +pub async fn get_connection_start_time( + state: State<'_, SharedAppState>, +) -> Result, CmdError> { + debug!("get_connection_start_time"); + let app_state = state.lock().await; + Ok(app_state.connection_start_time.map(|t| t.unix_timestamp())) +} + +#[instrument(skip(app_state, data_state))] +#[tauri::command] +pub async fn set_vpn_mode( + app_state: State<'_, SharedAppState>, + data_state: State<'_, SharedAppData>, + mode: VpnMode, +) -> Result<(), CmdError> { + debug!("set_vpn_mode"); + + let mut state = app_state.lock().await; + + if let ConnectionState::Disconnected = state.state { + } else { + let err_message = format!("cannot change vpn mode from state {:?}", state.state); + error!(err_message); + return Err(CmdError::new(CmdErrorSource::CallerError, err_message)); + } + state.vpn_mode = mode.clone(); + + // save the selected mode to disk + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + app_data.vpn_mode = Some(mode); + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + Ok(()) +} diff --git a/nym-vpn/ui/src-tauri/src/commands/mod.rs b/nym-vpn/ui/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000000..53529ee0ce --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/commands/mod.rs @@ -0,0 +1,3 @@ +pub mod app_data; +pub mod connection; +pub mod node_location; diff --git a/nym-vpn/ui/src-tauri/src/commands/node_location.rs b/nym-vpn/ui/src-tauri/src/commands/node_location.rs new file mode 100644 index 0000000000..7ef01976c1 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/commands/node_location.rs @@ -0,0 +1,66 @@ +use serde::{Deserialize, Serialize}; +use tauri::State; +use tracing::{debug, instrument}; +use ts_rs::TS; + +use crate::{ + country::DEFAULT_NODE_LOCATION, + error::{CmdError, CmdErrorSource}, + states::{app::Country, SharedAppData, SharedAppState}, +}; + +#[derive(Debug, Serialize, Deserialize, TS, Clone)] +pub enum NodeType { + Entry, + Exit, +} + +#[instrument(skip(app_state, data_state))] +#[tauri::command] +pub async fn set_node_location( + app_state: State<'_, SharedAppState>, + data_state: State<'_, SharedAppData>, + node_type: NodeType, + country: Country, +) -> Result<(), CmdError> { + debug!("set_node_location"); + let mut state = app_state.lock().await; + match node_type { + NodeType::Entry => { + state.entry_node_location = Some(country.clone()); + } + NodeType::Exit => { + state.exit_node_location = Some(country.clone()); + } + } + + // save the location on disk + let mut app_data_store = data_state.lock().await; + let mut app_data = app_data_store + .read() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + + match node_type { + NodeType::Entry => { + app_data.entry_node_location = Some(country); + } + NodeType::Exit => { + app_data.exit_node_location = Some(country); + } + } + app_data_store.data = app_data; + app_data_store + .write() + .await + .map_err(|e| CmdError::new(CmdErrorSource::InternalError, e.to_string()))?; + + Ok(()) +} + +#[instrument] +#[tauri::command] +pub async fn get_default_node_location() -> Result { + debug!("get_default_node_location"); + Ok(DEFAULT_NODE_LOCATION.clone()) +} diff --git a/nym-vpn/ui/src-tauri/src/country.rs b/nym-vpn/ui/src-tauri/src/country.rs new file mode 100644 index 0000000000..e9c4ede6dc --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/country.rs @@ -0,0 +1,34 @@ +use once_cell::sync::Lazy; + +use crate::states::app::Country; + +// TODO use hardcoded country list for now +pub static COUNTRIES: Lazy> = Lazy::new(|| { + vec![ + Country { + name: "France".to_string(), + code: "FR".to_string(), + }, + Country { + name: "Germany".to_string(), + code: "DE".to_string(), + }, + Country { + name: "Ireland".to_string(), + code: "IE".to_string(), + }, + Country { + name: "Japan".to_string(), + code: "JP".to_string(), + }, + Country { + name: "United Kingdom".to_string(), + code: "GB".to_string(), + }, + ] +}); + +pub static DEFAULT_NODE_LOCATION: Lazy = Lazy::new(|| Country { + code: "FR".to_string(), + name: "France".to_string(), +}); diff --git a/nym-vpn/ui/src-tauri/src/error.rs b/nym-vpn/ui/src-tauri/src/error.rs new file mode 100644 index 0000000000..e4be67cc80 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/error.rs @@ -0,0 +1,39 @@ +use std::fmt::{self, Display}; + +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use ts_rs::TS; + +#[derive(Error, Debug, Serialize, Deserialize, TS)] +#[ts(export)] +pub enum CmdErrorSource { + #[error("internal error")] + InternalError, + #[error("caller error")] + CallerError, + #[error("unknown error")] + Unknown, +} + +#[derive(Error, Debug, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct CmdError { + #[source] + pub source: CmdErrorSource, + pub message: String, +} + +impl CmdError { + pub fn new(error: CmdErrorSource, message: String) -> Self { + Self { + message, + source: error, + } + } +} + +impl Display for CmdError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.source, self.message) + } +} diff --git a/nym-vpn/ui/src-tauri/src/fs/cache.rs b/nym-vpn/ui/src-tauri/src/fs/cache.rs new file mode 100644 index 0000000000..64bc676b0f --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/cache.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +pub struct AppCache { + // TODO +} diff --git a/nym-vpn/ui/src-tauri/src/fs/config.rs b/nym-vpn/ui/src-tauri/src/fs/config.rs new file mode 100644 index 0000000000..b0d22ef37b --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/config.rs @@ -0,0 +1,9 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +pub struct AppConfig { + /// Path pointing to an env configuration file describing the network + pub env_config_file: Option, +} diff --git a/nym-vpn/ui/src-tauri/src/fs/data.rs b/nym-vpn/ui/src-tauri/src/fs/data.rs new file mode 100644 index 0000000000..797009af2a --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/data.rs @@ -0,0 +1,28 @@ +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +use crate::states::app::{Country, NodeConfig, VpnMode}; + +#[derive(Default, Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub enum UiTheme { + Dark, + #[default] + Light, +} + +#[derive(Default, Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct AppData { + pub monitoring: Option, + pub autoconnect: Option, + pub killswitch: Option, + pub entry_location_selector: Option, + pub ui_theme: Option, + pub ui_root_font_size: Option, + pub vpn_mode: Option, + pub entry_node: Option, + pub exit_node: Option, + pub entry_node_location: Option, + pub exit_node_location: Option, +} diff --git a/nym-vpn/ui/src-tauri/src/fs/log.rs b/nym-vpn/ui/src-tauri/src/fs/log.rs new file mode 100644 index 0000000000..7401d4e653 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/log.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +pub struct AppLog { + // TODO +} diff --git a/nym-vpn/ui/src-tauri/src/fs/mod.rs b/nym-vpn/ui/src-tauri/src/fs/mod.rs new file mode 100644 index 0000000000..429f48cc99 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/mod.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod data; +pub mod log; +pub mod storage; diff --git a/nym-vpn/ui/src-tauri/src/fs/storage.rs b/nym-vpn/ui/src-tauri/src/fs/storage.rs new file mode 100644 index 0000000000..cd718a52d6 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/fs/storage.rs @@ -0,0 +1,91 @@ +use anyhow::{anyhow, Context, Result}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fmt, path::PathBuf, str}; +use tauri::api::path::data_dir; +use tokio::fs; +use tracing::{debug, error, instrument}; + +#[derive(Debug, Clone)] +pub struct AppStorage +where + T: Serialize + DeserializeOwned + Default + fmt::Debug, +{ + pub data: T, + pub dir_path: PathBuf, + pub filename: String, + pub full_path: PathBuf, +} + +async fn create_directory_path(path: &PathBuf) -> Result<()> { + let mut data_dir = data_dir().ok_or(anyhow!( + "Failed to retrieve data directory {:?}", + path.display() + ))?; + data_dir.push(path); + + fs::create_dir_all(&data_dir).await.context(format!( + "Failed to create data directory {}", + data_dir.display() + )) +} + +impl AppStorage +where + T: Serialize + DeserializeOwned + Default + fmt::Debug, +{ + pub fn new(dir_path: PathBuf, filename: &str, data: Option) -> Self { + let mut full_path = dir_path.clone(); + full_path.push(filename); + + Self { + data: data.unwrap_or_default(), + dir_path, + filename: filename.to_owned(), + full_path, + } + } + + #[instrument] + pub async fn read(&self) -> Result { + // create the full directory path if it is missing + create_directory_path(&self.dir_path).await?; + + // check if the file exists, if not create it + match fs::try_exists(&self.full_path).await { + Ok(true) => {} + _ => fs::write(&self.full_path, []).await?, + } + + debug!("reading stored data from {}", self.full_path.display()); + let content = fs::read(&self.full_path).await.context(format!( + "Failed to read data from {}", + self.full_path.display() + ))?; + + toml::from_str::(str::from_utf8(&content)?).map_err(|e| { + error!("{e}"); + anyhow!("{e}") + }) + } + + #[instrument] + pub async fn write(&self) -> Result<()> { + // create the full directory path if it is missing + create_directory_path(&self.dir_path).await?; + + debug!("writing data to {}", self.full_path.display()); + let toml = toml::to_string(&self.data)?; + fs::write(&self.full_path, toml).await?; + Ok(()) + } + + #[instrument] + pub async fn clear(&self) -> Result<()> { + // create the full directory path if it is missing + create_directory_path(&self.dir_path).await?; + + debug!("clearing data {}", self.full_path.display()); + fs::write(&self.full_path, vec![]).await?; + Ok(()) + } +} diff --git a/nym-vpn/ui/src-tauri/src/lib.rs b/nym-vpn/ui/src-tauri/src/lib.rs deleted file mode 100644 index 940f438b54..0000000000 --- a/nym-vpn/ui/src-tauri/src/lib.rs +++ /dev/null @@ -1,16 +0,0 @@ -// Prevents additional console window on Windows in release, DO NOT REMOVE!! -#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] - -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -#[no_mangle] -pub extern "C" fn run_tauri() { - tauri::Builder::default() - .invoke_handler(tauri::generate_handler![greet]) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/nym-vpn/ui/src-tauri/src/main.rs b/nym-vpn/ui/src-tauri/src/main.rs index 523550d3b7..64eb32c2e3 100644 --- a/nym-vpn/ui/src-tauri/src/main.rs +++ b/nym-vpn/ui/src-tauri/src/main.rs @@ -1,15 +1,122 @@ // Prevents additional console window on Windows in release, DO NOT REMOVE!! #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) +use std::{env, sync::Arc}; + +use anyhow::{anyhow, Context, Result}; +use tauri::api::path::{config_dir, data_dir}; +use tokio::{fs::try_exists, sync::Mutex}; +use tracing::{debug, error, info}; + +use commands::*; +use states::app::AppState; + +use nym_vpn_lib::nym_config; + +use crate::fs::{config::AppConfig, data::AppData, storage::AppStorage}; + +mod commands; +mod country; +mod error; +mod fs; +mod states; +mod vpn_client; + +const APP_DIR: &str = "nym-vpn"; +const APP_DATA_FILE: &str = "app-data.toml"; +const APP_CONFIG_FILE: &str = "config.toml"; + +pub fn setup_logging() { + let filter = tracing_subscriber::EnvFilter::builder() + .with_default_directive(tracing_subscriber::filter::LevelFilter::INFO.into()) + .from_env() + .unwrap() + .add_directive("hyper::proto=info".parse().unwrap()) + .add_directive("netlink_proto=info".parse().unwrap()); + + tracing_subscriber::fmt() + .with_env_filter(filter) + .compact() + .init(); } -fn main() { +#[tokio::main] +async fn main() -> Result<()> { + dotenvy::dotenv().ok(); + setup_logging(); + + let app_data_store = { + let mut app_data_path = + data_dir().ok_or(anyhow!("Failed to retrieve data directory path"))?; + app_data_path.push(APP_DIR); + AppStorage::::new(app_data_path, APP_DATA_FILE, None) + }; + debug!("app_data_store: {}", app_data_store.full_path.display()); + + let app_config_store = { + let mut app_config_path = + config_dir().ok_or(anyhow!("Failed to retrieve config directory path"))?; + app_config_path.push(APP_DIR); + AppStorage::::new(app_config_path, APP_CONFIG_FILE, None) + }; + debug!( + "app_config_store: {}", + &app_config_store.full_path.display() + ); + + let app_data = app_data_store.read().await?; + debug!("app_data: {app_data:?}"); + let app_config = app_config_store.read().await?; + debug!("app_config: {app_config:?}"); + + // check for the existence of the env_config_file if provided + if let Some(env_config_file) = &app_config.env_config_file { + debug!("provided env_config_file: {}", env_config_file.display()); + if !(try_exists(env_config_file) + .await + .context("an error happened while trying to read env_config_file `{}`")?) + { + let err_message = format!( + "app config, env_config_file `{}`: file not found", + env_config_file.display() + ); + error!(err_message); + return Err(anyhow!(err_message)); + } + } + + // Read the env variables in the provided file and export them all to the local environment. + nym_config::defaults::setup_env(app_config.env_config_file); + + info!("Starting tauri app"); + tauri::Builder::default() - .invoke_handler(tauri::generate_handler![greet]) + .manage(Arc::new(Mutex::new(AppState::from(&app_data)))) + .manage(Arc::new(Mutex::new(app_data_store))) + .manage(Arc::new(Mutex::new(app_config_store))) + .setup(|_app| { + info!("app setup"); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + connection::set_vpn_mode, + connection::get_connection_state, + connection::connect, + connection::disconnect, + connection::get_connection_start_time, + app_data::get_app_data, + app_data::set_app_data, + app_data::set_ui_theme, + app_data::set_entry_location_selector, + app_data::set_monitoring, + app_data::set_auto_connect, + app_data::get_node_countries, + app_data::set_root_font_size, + node_location::set_node_location, + node_location::get_default_node_location, + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); + + Ok(()) } diff --git a/nym-vpn/ui/src-tauri/src/states/app.rs b/nym-vpn/ui/src-tauri/src/states/app.rs new file mode 100644 index 0000000000..1a54f73e03 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/states/app.rs @@ -0,0 +1,72 @@ +use futures::channel::mpsc::UnboundedSender; +use nym_vpn_lib::NymVpnCtrlMessage; +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; +use ts_rs::TS; + +use crate::fs::data::AppData; + +#[derive(Debug, Serialize, Deserialize, Clone, TS)] +#[ts(export)] +pub struct NodeConfig { + pub id: String, + pub country: Country, +} + +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, TS)] +#[ts(export)] +pub enum ConnectionState { + Connected, + #[default] + Disconnected, + Connecting, + Disconnecting, + Unknown, +} + +#[derive(Default, Debug, Serialize, Deserialize, TS, Clone, PartialEq, Eq)] +#[ts(export)] +pub enum VpnMode { + Mixnet, + #[default] + TwoHop, +} + +#[derive(Debug, Serialize, Deserialize, TS)] +#[ts(export)] +pub struct TunnelConfig { + pub id: String, + pub name: String, +} + +#[derive(Debug, Default)] +pub struct AppState { + pub state: ConnectionState, + pub error: Option, + pub vpn_mode: VpnMode, + pub entry_node: Option, + pub exit_node: Option, + pub entry_node_location: Option, + pub exit_node_location: Option, + pub tunnel: Option, + pub connection_start_time: Option, + pub vpn_ctrl_tx: Option>, +} + +impl From<&AppData> for AppState { + fn from(app_data: &AppData) -> Self { + AppState { + entry_node_location: app_data.entry_node_location.clone(), + exit_node_location: app_data.exit_node_location.clone(), + vpn_mode: app_data.vpn_mode.clone().unwrap_or_default(), + ..Default::default() + } + } +} + +#[derive(Default, Serialize, Deserialize, Debug, Clone, TS)] +#[ts(export)] +pub struct Country { + pub name: String, + pub code: String, +} diff --git a/nym-vpn/ui/src-tauri/src/states/mod.rs b/nym-vpn/ui/src-tauri/src/states/mod.rs new file mode 100644 index 0000000000..022f0760fd --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/states/mod.rs @@ -0,0 +1,12 @@ +use crate::fs::{config::AppConfig, data::AppData, storage::AppStorage}; +use std::sync::Arc; +use tokio::sync::Mutex; + +pub mod app; + +pub type SharedAppState = Arc>; +pub type SharedAppData = Arc>>; + +// TODO remove this macro +#[allow(dead_code)] +pub type SharedAppConfig = Arc>>; diff --git a/nym-vpn/ui/src-tauri/src/vpn_client.rs b/nym-vpn/ui/src-tauri/src/vpn_client.rs new file mode 100644 index 0000000000..1f0aa1c9f5 --- /dev/null +++ b/nym-vpn/ui/src-tauri/src/vpn_client.rs @@ -0,0 +1,172 @@ +use crate::states::{app::ConnectionState, SharedAppState}; +use anyhow::Result; +use futures::channel::oneshot::Receiver as OneshotReceiver; +use futures::StreamExt; +use nym_vpn_lib::gateway_client::{Config as GatewayClientConfig, EntryPoint, ExitPoint}; +use nym_vpn_lib::nym_config::OptionalSet; +use nym_vpn_lib::{NymVpn, NymVpnExitError, NymVpnExitStatusMessage, StatusReceiver}; +use tauri::Manager; +use time::OffsetDateTime; +use tracing::{debug, error, info, instrument, trace}; + +pub const EVENT_CONNECTION_STATE: &str = "connection-state"; +pub const EVENT_CONNECTION_PROGRESS: &str = "connection-progress"; + +#[derive(Clone, serde::Serialize)] +pub enum ConnectProgressMsg { + Initializing, + InitDone, +} + +#[derive(Clone, serde::Serialize)] +pub struct ProgressEventPayload { + pub key: ConnectProgressMsg, +} + +#[derive(Clone, serde::Serialize)] +pub struct ConnectionEventPayload { + state: ConnectionState, + error: Option, + start_time: Option, // unix timestamp in seconds +} + +impl ConnectionEventPayload { + pub fn new(state: ConnectionState, error: Option, start_time: Option) -> Self { + Self { + state, + error, + start_time, + } + } +} + +fn handle_vpn_exit_error(e: Box) -> String { + match e.downcast::>() { + Ok(e) => { + // TODO The double boxing here is unexpected, we should look into that + match **e { + NymVpnExitError::Generic { reason } => reason.to_string(), + NymVpnExitError::FailedToResetFirewallPolicy { reason } => reason.to_string(), + NymVpnExitError::FailedToResetDnsMonitor { reason } => reason.to_string(), + } + } + Err(e) => format!("unknown error: {e}"), + } +} + +#[instrument(skip_all)] +pub async fn spawn_exit_listener( + app: tauri::AppHandle, + app_state: SharedAppState, + exit_rx: OneshotReceiver, +) -> Result<()> { + tokio::spawn(async move { + match exit_rx.await { + Ok(res) => { + debug!("received vpn exit message: {res:?}"); + match res { + NymVpnExitStatusMessage::Stopped => { + info!("vpn connection stopped"); + debug!( + "vpn stopped, sending event [{}]: disconnected", + EVENT_CONNECTION_STATE + ); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new(ConnectionState::Disconnected, None, None), + ) + .ok(); + } + NymVpnExitStatusMessage::Failed(e) => { + let error = handle_vpn_exit_error(e); + debug!( + "vpn failed, sending event [{}]: disconnected", + EVENT_CONNECTION_STATE + ); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Disconnected, + Some(error), + None, + ), + ) + .ok(); + } + } + } + Err(e) => { + error!("vpn_exit_rx failed to receive exit message: {}", e); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Disconnected, + Some("exit channel with vpn client has been closed".to_string()), + None, + ), + ) + .ok(); + } + } + // update the connection state + let mut state = app_state.lock().await; + state.state = ConnectionState::Disconnected; + state.connection_start_time = None; + info!("vpn exit listener has exited"); + }); + Ok(()) +} + +#[instrument(skip_all)] +pub async fn spawn_status_listener( + app: tauri::AppHandle, + app_state: SharedAppState, + mut status_rx: StatusReceiver, +) -> Result<()> { + tokio::spawn(async move { + while let Some(msg) = status_rx.next().await { + info!("received vpn status message: {msg:?}"); + if "Ready" == msg.to_string().as_str() { + info!("vpn connection has been established"); + let now = OffsetDateTime::now_utc(); + { + let mut state = app_state.lock().await; + trace!("update connection state [Connected]"); + state.state = ConnectionState::Connected; + state.connection_start_time = Some(now); + } + debug!("sending event [{}]: Connected", EVENT_CONNECTION_STATE); + app.emit_all( + EVENT_CONNECTION_STATE, + ConnectionEventPayload::new( + ConnectionState::Connected, + None, + Some(now.unix_timestamp()), + ), + ) + .ok(); + } + } + info!("vpn status listener has exited"); + }); + Ok(()) +} + +fn setup_gateway_client_config(private_key: Option<&str>) -> GatewayClientConfig { + let mut config = GatewayClientConfig::default() + // Read in the environment variable NYM_API if it exists + .with_optional_env(GatewayClientConfig::with_custom_api_url, None, "NYM_API"); + info!("Using nym-api: {}", config.api_url()); + + if let Some(key) = private_key { + config = config.with_local_private_key(key.into()); + } + config +} + +#[instrument(skip_all)] +pub fn create_vpn_config(entry_point: EntryPoint, exit_point: ExitPoint) -> NymVpn { + let mut nym_vpn = NymVpn::new(entry_point, exit_point); + nym_vpn.gateway_config = setup_gateway_client_config(None); + nym_vpn +} diff --git a/nym-vpn/ui/src-tauri/tauri.conf.json b/nym-vpn/ui/src-tauri/tauri.conf.json index d3983c24e4..646fe5971a 100644 --- a/nym-vpn/ui/src-tauri/tauri.conf.json +++ b/nym-vpn/ui/src-tauri/tauri.conf.json @@ -7,10 +7,18 @@ "withGlobalTauri": false }, "package": { - "productName": "nymvpn-ui", - "version": "0.0.0" + "productName": "nym-vpn", + "version": "0.0.1" }, "tauri": { + "updater": { + "active": true, + "endpoints": [ + "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}" + ], + "dialog": true, + "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDUxMjFCMDhFOTczQzE5MjUKUldRbEdUeVhqckFoVVljRDZNZkRQZzIyYTBSZUVmSk1SVUlaTC9OeTk0NDFYUVl1blhWV2VTQi8K" + }, "allowlist": { "all": false, "shell": { @@ -21,7 +29,7 @@ "bundle": { "active": true, "targets": "all", - "identifier": "com.tauri.dev", + "identifier": "net.nymtech.vpn", "icon": [ "icons/32x32.png", "icons/128x128.png", @@ -38,8 +46,8 @@ "fullscreen": false, "resizable": true, "title": "NymVPN", - "width": 800, - "height": 600 + "width": 440, + "height": 920 } ] } diff --git a/nym-vpn/ui/src/App.tsx b/nym-vpn/ui/src/App.tsx index 27a4fd3126..273d7786f5 100644 --- a/nym-vpn/ui/src/App.tsx +++ b/nym-vpn/ui/src/App.tsx @@ -1,54 +1,21 @@ -import React, { useState } from 'react'; -import { invoke } from '@tauri-apps/api/tauri'; -import { Button as BaseButton, ButtonProps } from '@mui/base/Button'; -import clsx from 'clsx'; - -// eslint-disable-next-line react/display-name -const Button = React.forwardRef( - (props, ref) => { - const { className, ...other } = props; - return ( - - ); - }, -); +import { RouterProvider } from 'react-router-dom'; +import dayjs from 'dayjs'; +import { useTranslation } from 'react-i18next'; +import router from './router'; +import { MainStateProvider } from './state'; +import './i18n/config'; +import { ThemeSetter } from './ui'; function App() { - const [greetMsg, setGreetMsg] = useState(''); - const [name, setName] = useState(''); - - async function greet() { - // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command - setGreetMsg(await invoke('greet', { name })); - } + const { i18n } = useTranslation(); + dayjs.locale(i18n.language); return ( -
-

Welcome to Tauri!

- -
{ - e.preventDefault(); - greet(); - }} - > - setName(e.currentTarget.value)} - placeholder="Enter a name..." - /> - -
- -

{greetMsg}

-
+ + + + + ); } diff --git a/nym-vpn/ui/src/assets/.gitkeep b/nym-vpn/ui/src/assets/.gitkeep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/nym-vpn/ui/src/assets/fonts/MaterialSymbolsOutlined.woff2 b/nym-vpn/ui/src/assets/fonts/MaterialSymbolsOutlined.woff2 new file mode 100644 index 0000000000..93b97c722e Binary files /dev/null and b/nym-vpn/ui/src/assets/fonts/MaterialSymbolsOutlined.woff2 differ diff --git a/nym-vpn/ui/src/constants.ts b/nym-vpn/ui/src/constants.ts new file mode 100644 index 0000000000..3193f21cf6 --- /dev/null +++ b/nym-vpn/ui/src/constants.ts @@ -0,0 +1,17 @@ +export const routes = { + root: '/', + settings: '/settings', + display: '/settings/display', + logs: '/settings/logs', + feedback: '/settings/feedback', + legal: '/settings/legal', + entryNodeLocation: '/entry-node-location', + exitNodeLocation: '/exit-node-location', +} as const; + +export const AppName = 'NymVPN'; +export const ConnectionEvent = 'connection-state'; +export const ProgressEvent = 'connection-progress'; +export const QuickConnectPrefix = 'Fastest'; +// TODO ⚠ keep this value in sync with the one declared in `index.html` +export const DefaultRootFontSize = 12; // in px diff --git a/nym-vpn/ui/src/contexts/index.ts b/nym-vpn/ui/src/contexts/index.ts new file mode 100644 index 0000000000..aad1ca831e --- /dev/null +++ b/nym-vpn/ui/src/contexts/index.ts @@ -0,0 +1 @@ +export * from './main'; diff --git a/nym-vpn/ui/src/contexts/main.ts b/nym-vpn/ui/src/contexts/main.ts new file mode 100644 index 0000000000..cc9cb74eaf --- /dev/null +++ b/nym-vpn/ui/src/contexts/main.ts @@ -0,0 +1,15 @@ +import { createContext, useContext } from 'react'; +import { StateAction, initialState } from '../state'; +import { AppState } from '../types'; + +export const MainStateContext = createContext(initialState); +export const MainDispatchContext = + createContext | null>(null); + +export const useMainState = () => { + return useContext(MainStateContext); +}; + +export const useMainDispatch = () => { + return useContext(MainDispatchContext); +}; diff --git a/nym-vpn/ui/src/dev/setup.ts b/nym-vpn/ui/src/dev/setup.ts index 261a6aede9..7bedb656a7 100644 --- a/nym-vpn/ui/src/dev/setup.ts +++ b/nym-vpn/ui/src/dev/setup.ts @@ -1,11 +1,102 @@ import { mockIPC, mockWindows } from '@tauri-apps/api/mocks'; -import { greet } from './tauri-cmd-mocks'; +import { emit } from '@tauri-apps/api/event'; +import { AppDataFromBackend, ConnectionState, Country } from '../types'; +import { ConnectionEvent } from '../constants'; -mockWindows('main'); +export function mockTauriIPC() { + mockWindows('main'); -mockIPC(async (cmd, args) => { - console.log(`IPC call mocked "${cmd}"`); - if (cmd === 'greet') { - return greet(args.name as string); - } -}); + mockIPC(async (cmd, args) => { + console.log(`IPC call mocked "${cmd}"`); + console.log(args); + if (cmd === 'connect') { + await emit(ConnectionEvent, { state: 'Connecting' }); + return new Promise((resolve) => + setTimeout(async () => { + await emit(ConnectionEvent, { state: 'Connected' }); + resolve('Connected'); + }, 2000), + ); + } + if (cmd === 'disconnect') { + await emit(ConnectionEvent, { state: 'Disconnecting' }); + return new Promise((resolve) => + setTimeout(async () => { + await emit(ConnectionEvent, { state: 'Disconnected' }); + resolve('Disconnected'); + }, 2000), + ); + } + if (cmd === 'get_connection_state') { + return new Promise((resolve) => + setTimeout(() => resolve('Disconnected'), 2000), + ); + } + + if (cmd === 'get_node_countries') { + return new Promise((resolve) => + resolve([ + { + name: 'United States', + code: 'US', + }, + { + name: 'France', + code: 'FR', + }, + { + name: 'Switzerland', + code: 'CH', + }, + { + name: 'Germany', + code: 'DE', + }, + ]), + ); + } + + if (cmd === 'get_default_node_location') { + return new Promise((resolve) => + resolve({ + name: 'France', + code: 'FR', + }), + ); + } + + if (cmd === 'set_root_font_size') { + return new Promise((resolve) => resolve()); + } + + if (cmd === 'get_app_data') { + return new Promise((resolve) => + resolve({ + monitoring: false, + autoconnect: false, + killswitch: false, + entry_location_selector: false, + ui_theme: 'Dark', + ui_root_font_size: 12, + vpn_mode: 'TwoHop', + entry_node: { + country: { + name: 'France', + code: 'FR', + }, + id: 'nodeOne', + }, + exit_node: { + country: { + name: 'France', + code: 'FR', + }, + id: 'nodeTwo', + }, + entry_node_location: null, + exit_node_location: null, + }), + ); + } + }); +} diff --git a/nym-vpn/ui/src/dev/tauri-cmd-mocks/greet.ts b/nym-vpn/ui/src/dev/tauri-cmd-mocks/greet.ts deleted file mode 100644 index 30f2b2f5ac..0000000000 --- a/nym-vpn/ui/src/dev/tauri-cmd-mocks/greet.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function greet(name: string): string { - return `Hello, ${name}!`; -} diff --git a/nym-vpn/ui/src/dev/tauri-cmd-mocks/index.ts b/nym-vpn/ui/src/dev/tauri-cmd-mocks/index.ts deleted file mode 100644 index 72f4e4c640..0000000000 --- a/nym-vpn/ui/src/dev/tauri-cmd-mocks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as greet } from './greet'; diff --git a/nym-vpn/ui/src/i18n/config.ts b/nym-vpn/ui/src/i18n/config.ts new file mode 100644 index 0000000000..ffdb86b44c --- /dev/null +++ b/nym-vpn/ui/src/i18n/config.ts @@ -0,0 +1,31 @@ +import i18n from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import common from './en/common.json'; +import home from './en/home.json'; +import settings from './en/settings.json'; +import nodeLocation from './en/node-location.json'; +import backendMessages from './en/backend-messages.json'; + +const defaultNS = 'common'; + +i18n.use(initReactI18next).init({ + lng: 'en', + debug: import.meta.env.DEV, + resources: { + en: { + common, + home, + settings, + nodeLocation, + backendMessages, + }, + }, + ns: ['common', 'home', 'settings', 'nodeLocation', 'backendMessages'], + defaultNS, + + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, +}); + +export default i18n; diff --git a/nym-vpn/ui/src/i18n/en/backend-messages.json b/nym-vpn/ui/src/i18n/en/backend-messages.json new file mode 100644 index 0000000000..95dd6222a5 --- /dev/null +++ b/nym-vpn/ui/src/i18n/en/backend-messages.json @@ -0,0 +1,6 @@ +{ + "connection-progress": { + "Initializing": "Initializing Nym VPN client…", + "InitDone": "Nym VPN client initialized" + } +} diff --git a/nym-vpn/ui/src/i18n/en/common.json b/nym-vpn/ui/src/i18n/en/common.json new file mode 100644 index 0000000000..b959ce36db --- /dev/null +++ b/nym-vpn/ui/src/i18n/en/common.json @@ -0,0 +1,11 @@ +{ + "connect": "Connect", + "disconnect": "Disconnect", + "first-hop-selection": "First hop selection", + "last-hop-selection": "Last hop selection", + "settings": "Settings", + "display-theme": "Display theme", + "logs": "Logs", + "feedback": "Feedback", + "legal": "Legal" +} diff --git a/nym-vpn/ui/src/i18n/en/home.json b/nym-vpn/ui/src/i18n/en/home.json new file mode 100644 index 0000000000..2403849864 --- /dev/null +++ b/nym-vpn/ui/src/i18n/en/home.json @@ -0,0 +1,29 @@ +{ + "connect": "Connect", + "disconnect": "Disconnect", + "status": { + "connected": "Connected", + "disconnected": "Disconnected", + "connecting": "Connecting…", + "disconnecting": "Disconnecting…", + "unknown": "Unknown" + }, + "last-hop": "Last hop", + "first-hop": "First hop", + "connecting-message": "Establishing connection", + "connection-time": "Connection time", + "select-network-label": "Select network", + "select-node-title": "Connect to", + "mixnet-mode": { + "title": "5-hop mixnet", + "desc": "Best for payments, emails, messages" + }, + "twohop-mode": { + "title": "2-hop", + "desc": "Best for browsing, streaming, sharing" + }, + "last-node-select": { + "label": "Last hop", + "text": "Quick connect ({{country}})" + } +} diff --git a/nym-vpn/ui/src/i18n/en/node-location.json b/nym-vpn/ui/src/i18n/en/node-location.json new file mode 100644 index 0000000000..9766e025fb --- /dev/null +++ b/nym-vpn/ui/src/i18n/en/node-location.json @@ -0,0 +1,6 @@ +{ + "loading": "Loading..", + "none-found": "No results found. Please try another search", + "selected": "Selected", + "search-country": "Search country" +} diff --git a/nym-vpn/ui/src/i18n/en/settings.json b/nym-vpn/ui/src/i18n/en/settings.json new file mode 100644 index 0000000000..a2c67b4e14 --- /dev/null +++ b/nym-vpn/ui/src/i18n/en/settings.json @@ -0,0 +1,20 @@ +{ + "auto-connect": { + "title": "Auto-connect", + "desc": "Auto connect at app startup" + }, + "entry-selector": { + "title": "Entry location selector", + "desc": "Manually select your entry hop" + }, + "feedback": "Feedback", + "error-reporting": { + "title": "Error reporting", + "desc": "Help us improve NymVPN" + }, + "faq": "FAQ", + "display-theme": "Display theme", + "logs": "Logs", + "legal": "Legal", + "quit": "Quit NymVPN" +} diff --git a/nym-vpn/ui/src/main.tsx b/nym-vpn/ui/src/main.tsx index 4d42a8e37b..f0292032d0 100644 --- a/nym-vpn/ui/src/main.tsx +++ b/nym-vpn/ui/src/main.tsx @@ -1,13 +1,20 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import duration from 'dayjs/plugin/duration'; import App from './App'; +import { mockTauriIPC } from './dev/setup'; import './styles.css'; if (import.meta.env.MODE === 'dev-browser') { console.log('Running in dev-browser mode. Mocking tauri window and IPCs'); - import('./dev/setup'); + mockTauriIPC(); } +dayjs.extend(relativeTime); +dayjs.extend(duration); + ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/nym-vpn/ui/src/pages/Error.tsx b/nym-vpn/ui/src/pages/Error.tsx new file mode 100644 index 0000000000..f6b70714d0 --- /dev/null +++ b/nym-vpn/ui/src/pages/Error.tsx @@ -0,0 +1,21 @@ +import { useRouteError } from 'react-router-dom'; + +type routerErrorType = { + statusText: string; + message: string; +}; + +export default function Error() { + const error: routerErrorType = useRouteError() as unknown as routerErrorType; + console.error(error); + + return ( +
+

Oops!

+

Sorry, an unexpected error has occurred.

+

+ {error.statusText || error.message} +

+
+ ); +} diff --git a/nym-vpn/ui/src/pages/NavLayout.tsx b/nym-vpn/ui/src/pages/NavLayout.tsx new file mode 100644 index 0000000000..80584d461e --- /dev/null +++ b/nym-vpn/ui/src/pages/NavLayout.tsx @@ -0,0 +1,15 @@ +import { Outlet } from 'react-router-dom'; +import { TopBar } from '../ui'; + +function NavLayout() { + return ( +
+ +
+ +
+
+ ); +} + +export default NavLayout; diff --git a/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx b/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx new file mode 100644 index 0000000000..caf25782f0 --- /dev/null +++ b/nym-vpn/ui/src/pages/home/ConnectionStatus.tsx @@ -0,0 +1,95 @@ +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { ConnectionState } from '../../types'; +import { useMainState } from '../../contexts'; +import ConnectionTimer from './ConnectionTimer'; + +function ConnectionStatus() { + const state = useMainState(); + + const { t } = useTranslation('home'); + const statusBadgeDynStyles = { + Connected: [ + 'bg-blanc-nacre-icicle', + 'text-vert-menthe', + 'dark:bg-baltic-sea-quartzite', + ], + Disconnected: [ + 'bg-blanc-nacre-platinum', + 'text-coal-mine-light', + 'dark:bg-baltic-sea-oil', + 'dark:text-coal-mine-dark', + ], + Connecting: [ + 'bg-blanc-nacre-platinum', + 'text-baltic-sea', + 'dark:bg-baltic-sea-oil', + 'dark:text-white', + ], + Disconnecting: [ + 'bg-blanc-nacre-platinum', + 'text-baltic-sea', + 'dark:bg-baltic-sea-oil', + 'dark:text-white', + ], + Unknown: [ + 'bg-blanc-nacre-platinum', + 'text-coal-mine-light', + 'dark:bg-baltic-sea-oil', + 'dark:text-coal-mine-dark', + ], + }; + + const getStatusText = (state: ConnectionState) => { + switch (state) { + case 'Connected': + return t('status.connected'); + case 'Disconnected': + return t('status.disconnected'); + case 'Connecting': + return t('status.connecting'); + case 'Disconnecting': + return t('status.disconnecting'); + case 'Unknown': + return t('status.unknown'); + } + }; + return ( +
+
+
+ {getStatusText(state.state)} +
+
+
+ {state.loading && state.progressMessages.length > 0 && !state.error && ( +
+

+ {t( + `connection-progress.${ + state.progressMessages[state.progressMessages.length - 1] + }`, + { + ns: 'backendMessages', + }, + )} +

+
+ )} + {state.state === 'Connected' && } + {state.error && ( +
+

{state.error}

+
+ )} +
+
+ ); +} + +export default ConnectionStatus; diff --git a/nym-vpn/ui/src/pages/home/ConnectionTimer.tsx b/nym-vpn/ui/src/pages/home/ConnectionTimer.tsx new file mode 100644 index 0000000000..bc06d93102 --- /dev/null +++ b/nym-vpn/ui/src/pages/home/ConnectionTimer.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import dayjs from 'dayjs'; +import { useMainState } from '../../contexts'; + +function ConnectionTimer() { + const { sessionStartDate } = useMainState(); + const [connectionTime, setConnectionTime] = useState('00:00:00'); + const { t } = useTranslation('home'); + + useEffect(() => { + if (!sessionStartDate) { + return; + } + + const elapsed = dayjs.duration(dayjs().diff(sessionStartDate)); + setConnectionTime(elapsed.format('HH:mm:ss')); + + const interval = setInterval(() => { + const elapsed = dayjs.duration(dayjs().diff(sessionStartDate)); + setConnectionTime(elapsed.format('HH:mm:ss')); + }, 500); + + return () => { + clearInterval(interval); + }; + }, [sessionStartDate]); + + return ( +
+

+ {t('connection-time')} +

+

+ {connectionTime} +

+
+ ); +} + +export default ConnectionTimer; diff --git a/nym-vpn/ui/src/pages/home/Home.tsx b/nym-vpn/ui/src/pages/home/Home.tsx new file mode 100644 index 0000000000..27486a7823 --- /dev/null +++ b/nym-vpn/ui/src/pages/home/Home.tsx @@ -0,0 +1,128 @@ +import { useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { invoke } from '@tauri-apps/api'; +import clsx from 'clsx'; +import { Button } from '@mui/base'; +import { useNavigate } from 'react-router-dom'; +import { useMainDispatch, useMainState } from '../../contexts'; +import { CmdError, StateDispatch } from '../../types'; +import { routes } from '../../constants'; +import NetworkModeSelect from './NetworkModeSelect'; +import ConnectionStatus from './ConnectionStatus'; +import HopSelect from './HopSelect'; + +function Home() { + const { + state, + loading, + entryNodeLocation, + exitNodeLocation, + entrySelector, + defaultNodeLocation, + } = useMainState(); + const dispatch = useMainDispatch() as StateDispatch; + const navigate = useNavigate(); + const { t } = useTranslation('home'); + + const handleClick = async () => { + dispatch({ type: 'disconnect' }); + if (state === 'Connected') { + invoke('disconnect') + .then((result) => { + console.log('disconnect result'); + console.log(result); + }) + .catch((e: CmdError) => { + console.warn(`backend error: ${e.source} - ${e.message}`); + dispatch({ type: 'set-error', error: e.message }); + }); + } else if (state === 'Disconnected') { + dispatch({ type: 'connect' }); + invoke('connect') + .then((result) => { + console.log('connect result'); + console.log(result); + }) + .catch((e: CmdError) => { + console.warn(`backend error: ${e.source} - ${e.message}`); + dispatch({ type: 'set-error', error: e.message }); + }); + } + }; + + const getButtonText = useCallback(() => { + switch (state) { + case 'Connected': + return t('disconnect'); + case 'Disconnected': + return t('connect'); + case 'Connecting': + return ( +
+ autorenew +
+ ); + case 'Disconnecting': + return ( +
+ autorenew +
+ ); + case 'Unknown': + return t('status.unknown'); + } + }, [state, t]); + + return ( +
+ +
+
+ +
+
+ {t('select-node-title')} +
+ {entrySelector && ( + { + if (state === 'Disconnected') { + navigate(routes.entryNodeLocation); + } + }} + nodeHop="entry" + /> + )} + { + if (state === 'Disconnected') { + navigate(routes.exitNodeLocation); + } + }} + nodeHop="exit" + /> +
+
+ +
+
+ ); +} + +export default Home; diff --git a/nym-vpn/ui/src/pages/home/HopSelect.tsx b/nym-vpn/ui/src/pages/home/HopSelect.tsx new file mode 100644 index 0000000000..28a8463009 --- /dev/null +++ b/nym-vpn/ui/src/pages/home/HopSelect.tsx @@ -0,0 +1,54 @@ +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { Country, NodeHop } from '../../types'; +import { useMainState } from '../../contexts'; + +interface HopSelectProps { + country: Country; + onClick: () => void; + nodeHop: NodeHop; +} + +export default function HopSelect({ + nodeHop, + country, + onClick, +}: HopSelectProps) { + const { state } = useMainState(); + const { t } = useTranslation('home'); + + return ( +
+
+ {nodeHop === 'entry' ? t('first-hop') : t('last-hop')} +
+
+ {country.code} +
{country.name}
+
+ + arrow_right + +
+ ); +} diff --git a/nym-vpn/ui/src/pages/home/NetworkModeSelect.tsx b/nym-vpn/ui/src/pages/home/NetworkModeSelect.tsx new file mode 100644 index 0000000000..3721cf7a5d --- /dev/null +++ b/nym-vpn/ui/src/pages/home/NetworkModeSelect.tsx @@ -0,0 +1,122 @@ +import { useEffect, useState } from 'react'; +import { RadioGroup } from '@headlessui/react'; +import { invoke } from '@tauri-apps/api'; +import clsx from 'clsx'; +import { useTranslation } from 'react-i18next'; +import { useMainDispatch, useMainState } from '../../contexts'; +import { StateDispatch, VpnMode } from '../../types'; + +type VpnModeOption = { name: VpnMode; title: string; desc: string }; + +function NetworkModeSelect() { + const state = useMainState(); + const dispatch = useMainDispatch() as StateDispatch; + const [selected, setSelected] = useState(state.vpnMode); + const [loading, setLoading] = useState(false); + + const { t } = useTranslation('home'); + + useEffect(() => { + if (state.vpnMode !== selected) { + setSelected(state.vpnMode); + } + }, [state.vpnMode, selected]); + + const handleNetworkModeChange = async (value: VpnMode) => { + if (state.state === 'Disconnected' && value !== state.vpnMode) { + setLoading(true); + try { + await invoke('set_vpn_mode', { mode: value }); + dispatch({ type: 'set-vpn-mode', mode: value }); + } catch (e) { + console.log(e); + } finally { + setLoading(false); + } + } + }; + + const vpnModes: VpnModeOption[] = [ + { + name: 'Mixnet', + title: t('mixnet-mode.title'), + desc: t('mixnet-mode.desc'), + }, + { + name: 'TwoHop', + title: t('twohop-mode.title'), + desc: t('twohop-mode.desc'), + }, + ]; + + const handleSelect = (value: VpnMode) => { + setSelected(value); + handleNetworkModeChange(value); + }; + + return ( +
+ + + {t('select-network-label')} + +
+ {vpnModes.map((mode) => ( + + clsx([ + 'bg-white dark:bg-baltic-sea-jaguar relative flex rounded-lg px-5 py-3 shadow-md focus:outline-none', + (state.state !== 'Disconnected' || loading) && + 'cursor-not-allowed', + checked && + 'ring-0 ring-melon ring-offset-2 ring-offset-melon', + state.state === 'Disconnected' && 'cursor-pointer', + ]) + } + disabled={state.state !== 'Disconnected' || loading} + > + {({ checked }) => { + return ( +
+ {checked ? ( + + radio_button_checked + + ) : ( + + radio_button_unchecked + + )} +
+
+ + {mode.title} + + + {mode.desc} + +
+
+
+ ); + }} +
+ ))} +
+
+
+ ); +} + +export default NetworkModeSelect; diff --git a/nym-vpn/ui/src/pages/home/index.ts b/nym-vpn/ui/src/pages/home/index.ts new file mode 100644 index 0000000000..c2d92a5746 --- /dev/null +++ b/nym-vpn/ui/src/pages/home/index.ts @@ -0,0 +1 @@ +export { default as Home } from './Home'; diff --git a/nym-vpn/ui/src/pages/index.ts b/nym-vpn/ui/src/pages/index.ts new file mode 100644 index 0000000000..787ff42bd7 --- /dev/null +++ b/nym-vpn/ui/src/pages/index.ts @@ -0,0 +1,5 @@ +export * from './home'; +export * from './settings'; +export { default as NavLayout } from './NavLayout'; +export * from './location'; +export { default as Error } from './Error'; diff --git a/nym-vpn/ui/src/pages/location/CountryList.tsx b/nym-vpn/ui/src/pages/location/CountryList.tsx new file mode 100644 index 0000000000..d3bc814fa4 --- /dev/null +++ b/nym-vpn/ui/src/pages/location/CountryList.tsx @@ -0,0 +1,46 @@ +import { useTranslation } from 'react-i18next'; +import { Country } from '../../types'; +interface CountryListProps { + countries: Country[]; + onClick: (name: string, code: string) => void; + isSelected: (code: string) => boolean; +} +export default function CountryList({ + countries, + onClick, + isSelected, +}: CountryListProps) { + const { t } = useTranslation('nodeLocation'); + return ( +
    + {countries && countries.length > 0 ? ( + countries.map((country) => ( +
  • +
    onClick(country.name, country.code)} + className="flex flex-row justify-between dark:hover:bg-gun-powder hover:dark:bg-opacity-80 hover:bg-cement-feet hover:bg-opacity-20 rounded-lg cursor-pointer px-3" + onClick={() => onClick(country.name, country.code)} + > +
    + {country.code} +
    + {country.name} +
    +
    +
    + {isSelected(country.code) ? t('selected') : ''} +
    +
    +
  • + )) + ) : ( +

    {t('none-found')}

    + )} +
+ ); +} diff --git a/nym-vpn/ui/src/pages/location/NodeLocation.tsx b/nym-vpn/ui/src/pages/location/NodeLocation.tsx new file mode 100644 index 0000000000..880678ee1d --- /dev/null +++ b/nym-vpn/ui/src/pages/location/NodeLocation.tsx @@ -0,0 +1,88 @@ +import { useTranslation } from 'react-i18next'; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { invoke } from '@tauri-apps/api'; +import { useMainDispatch, useMainState } from '../../contexts'; +import { Country, InputEvent, NodeHop, StateDispatch } from '../../types'; +import { routes } from '../../constants'; +import SearchBox from './SearchBox'; +import CountryList from './CountryList'; +import QuickConnect from './QuickConnect'; + +function NodeLocation({ node }: { node: NodeHop }) { + const { t } = useTranslation('nodeLocation'); + const { entryNodeLocation, exitNodeLocation, countries } = useMainState(); + const [search, setSearch] = useState(''); + const [foundCountries, setFoundCountries] = useState(countries); + + const dispatch = useMainDispatch() as StateDispatch; + + const navigate = useNavigate(); + + //request backend to update countries cache + useEffect(() => { + const getNodeCountries = async () => { + const countries = await invoke('get_node_countries'); + dispatch({ type: 'set-countries', countries }); + }; + getNodeCountries().catch(console.error); + }, [dispatch]); + + const filter = (e: InputEvent) => { + const keyword = e.target.value; + if (keyword !== '') { + const results = countries.filter((country) => { + return country.name.toLowerCase().startsWith(keyword.toLowerCase()); + // Use the toLowerCase() method to make it case-insensitive + }); + setFoundCountries(results); + } else { + setFoundCountries(countries); + // If the text field is empty, show all users + } + setSearch(keyword); + }; + + const isCountrySelected = (code: string): boolean => { + return node === 'entry' + ? entryNodeLocation?.code === code + : exitNodeLocation?.code === code; + }; + + const handleCountrySelection = async (name: string, code: string) => { + try { + await invoke('set_node_location', { + nodeType: node === 'entry' ? 'Entry' : 'Exit', + country: { name, code }, + }); + dispatch({ + type: 'set-node-location', + payload: { hop: node, country: { name, code } }, + }); + } catch (e) { + console.log(e); + } + navigate(routes.root); + }; + + return ( +
+
+ + + + +
+
+ ); +} + +export default NodeLocation; diff --git a/nym-vpn/ui/src/pages/location/QuickConnect.tsx b/nym-vpn/ui/src/pages/location/QuickConnect.tsx new file mode 100644 index 0000000000..898cd4e3f8 --- /dev/null +++ b/nym-vpn/ui/src/pages/location/QuickConnect.tsx @@ -0,0 +1,26 @@ +import { useMainState } from '../../contexts'; +import { QuickConnectPrefix } from '../../constants'; + +interface QuickConnectProps { + onClick: (name: string, code: string) => void; +} + +export default function QuickConnect({ onClick }: QuickConnectProps) { + const { defaultNodeLocation } = useMainState(); + + return ( +
+ onClick(defaultNodeLocation.name, defaultNodeLocation.code) + } + className="flex px-1 flex-row items-center w-full py-8 cursor-pointer" + onClick={() => + onClick(defaultNodeLocation.name, defaultNodeLocation.code) + } + > + bolt +
{`${QuickConnectPrefix} (${defaultNodeLocation.name})`}
+
+ ); +} diff --git a/nym-vpn/ui/src/pages/location/SearchBox.tsx b/nym-vpn/ui/src/pages/location/SearchBox.tsx new file mode 100644 index 0000000000..89291fafcd --- /dev/null +++ b/nym-vpn/ui/src/pages/location/SearchBox.tsx @@ -0,0 +1,42 @@ +import clsx from 'clsx'; +import { InputEvent } from '../../types'; + +interface SearchProps { + value: string; + onChange: (e: InputEvent) => void; + placeholder: string; +} + +export default function SearchBox({ + value, + onChange, + placeholder, +}: SearchProps) { + return ( +
+ + + search + +
+ ); +} diff --git a/nym-vpn/ui/src/pages/location/index.ts b/nym-vpn/ui/src/pages/location/index.ts new file mode 100644 index 0000000000..991fa94860 --- /dev/null +++ b/nym-vpn/ui/src/pages/location/index.ts @@ -0,0 +1 @@ +export { default as NodeLocation } from './NodeLocation'; diff --git a/nym-vpn/ui/src/pages/settings/Settings.tsx b/nym-vpn/ui/src/pages/settings/Settings.tsx new file mode 100644 index 0000000000..8b4c0a9041 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/Settings.tsx @@ -0,0 +1,228 @@ +import { useEffect, useState } from 'react'; +import clsx from 'clsx'; +import { invoke } from '@tauri-apps/api'; +import { Switch } from '@headlessui/react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { routes } from '../../constants'; +import { useMainDispatch, useMainState } from '../../contexts'; +import { StateDispatch } from '../../types'; +import SettingsGroup from './SettingsGroup'; + +function Settings() { + const state = useMainState(); + const navigate = useNavigate(); + const dispatch = useMainDispatch() as StateDispatch; + const { t } = useTranslation('settings'); + + const [entrySelector, setEntrySelector] = useState(state.entrySelector); + const [autoConnect, setAutoConnect] = useState(state.autoConnect); + const [monitoring, setMonitoring] = useState(state.monitoring); + + useEffect(() => { + setEntrySelector(state.entrySelector); + setAutoConnect(state.autoConnect); + setMonitoring(state.monitoring); + }, [state]); + + const handleEntrySelectorChange = async () => { + const isSelected = !state.entrySelector; + dispatch({ type: 'set-entry-selector', entrySelector: isSelected }); + invoke('set_entry_location_selector', { + entrySelector: isSelected, + }).catch((e) => { + console.log(e); + }); + }; + + const handleAutoConnectChanged = async () => { + const isSelected = !state.autoConnect; + dispatch({ type: 'set-auto-connect', autoConnect: isSelected }); + invoke('set_auto_connect', { autoConnect: isSelected }).catch((e) => { + console.log(e); + }); + }; + + const handleMonitoringChanged = async () => { + const isSelected = !state.monitoring; + dispatch({ type: 'set-monitoring', monitoring: isSelected }); + invoke('set_monitoring', { monitoring: isSelected }).catch((e) => { + console.log(e); + }); + }; + + return ( +
+ + + + ), + }, + { + title: t('entry-selector.title'), + desc: t('entry-selector.desc'), + leadingIcon: 'looks_two', + trailing: ( + + + + ), + }, + ]} + /> + { + navigate(routes.display); + }, + trailing: ( +
+ arrow_right +
+ ), + }, + ]} + /> + { + navigate(routes.logs); + }, + trailing: ( +
+ arrow_right +
+ ), + disabled: true, + }, + ]} + /> + { + navigate(routes.feedback); + }, + trailing: ( +
+ arrow_right +
+ ), + disabled: true, + }, + { + title: t('error-reporting.title'), + desc: t('error-reporting.desc'), + leadingIcon: 'error', + disabled: true, + trailing: ( + + + + ), + }, + { + title: t('faq'), + leadingIcon: 'help', + disabled: true, + trailing: ( +
launch
+ ), + }, + ]} + /> + { + navigate(routes.legal); + }, + disabled: true, + trailing: ( +
+ arrow_right +
+ ), + }, + ]} + /> + { + //TODO shutdown gracefully + }, + disabled: true, + }, + ]} + /> +
+ Version {state.version} +
+
+ ); +} + +export default Settings; diff --git a/nym-vpn/ui/src/pages/settings/SettingsGroup.tsx b/nym-vpn/ui/src/pages/settings/SettingsGroup.tsx new file mode 100644 index 0000000000..3cbdf5c8c5 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/SettingsGroup.tsx @@ -0,0 +1,71 @@ +import clsx from 'clsx'; +import { ReactNode } from 'react'; +import { RadioGroup } from '@headlessui/react'; + +type Setting = { + title: string; + leadingIcon?: string; + desc?: string; + onClick?: () => void; + trailing?: ReactNode; + disabled?: boolean; +}; + +interface Props { + settings: Setting[]; +} + +function SettingsGroup({ settings }: Props) { + return ( + + {settings.map((setting, index) => ( + +
+ {setting.leadingIcon && ( + {setting.leadingIcon} + )} +
+
+ + {setting.title} + + + {setting.desc} + +
+
+
{setting.trailing}
+
+
+ ))} +
+ ); +} + +export default SettingsGroup; diff --git a/nym-vpn/ui/src/pages/settings/SettingsLayout.tsx b/nym-vpn/ui/src/pages/settings/SettingsLayout.tsx new file mode 100644 index 0000000000..90015a54fc --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/SettingsLayout.tsx @@ -0,0 +1,11 @@ +import { Outlet } from 'react-router-dom'; + +function SettingsLayout() { + return ( +
+ +
+ ); +} + +export default SettingsLayout; diff --git a/nym-vpn/ui/src/pages/settings/display/Display.tsx b/nym-vpn/ui/src/pages/settings/display/Display.tsx new file mode 100644 index 0000000000..3c35280a6d --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/display/Display.tsx @@ -0,0 +1,70 @@ +import { useEffect, useState } from 'react'; +import clsx from 'clsx'; +import { invoke } from '@tauri-apps/api'; +import { Switch } from '@headlessui/react'; +import { useMainDispatch, useMainState } from '../../../contexts'; +import { StateDispatch } from '../../../types'; +import UiScaler from './UiScaler'; + +function Display() { + const state = useMainState(); + const dispatch = useMainDispatch() as StateDispatch; + + const [darkModeEnabled, setDarkModeEnabled] = useState( + state.uiTheme === 'Dark', + ); + + useEffect(() => { + setDarkModeEnabled(state.uiTheme === 'Dark'); + }, [state]); + + const handleThemeChange = async (darkMode: boolean) => { + if (darkMode && state.uiTheme === 'Light') { + dispatch({ type: 'set-ui-theme', theme: 'Dark' }); + } else if (!darkMode && state.uiTheme === 'Dark') { + dispatch({ type: 'set-ui-theme', theme: 'Light' }); + } + invoke('set_ui_theme', { theme: darkMode ? 'Dark' : 'Light' }).catch( + (e) => { + console.log(e); + }, + ); + }; + + return ( +
+
+

+ Dark Mode +

+ + Dark mode + + +
+ +
+ ); +} + +export default Display; diff --git a/nym-vpn/ui/src/pages/settings/display/UiScaler.tsx b/nym-vpn/ui/src/pages/settings/display/UiScaler.tsx new file mode 100644 index 0000000000..154efb3d59 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/display/UiScaler.tsx @@ -0,0 +1,54 @@ +import { ChangeEvent, useEffect, useState } from 'react'; +import { invoke } from '@tauri-apps/api'; +import clsx from 'clsx'; +import { useMainDispatch, useMainState } from '../../../contexts'; +import { CmdError, StateDispatch } from '../../../types'; + +function UiScaler() { + const [slideValue, setSlideValue] = useState(12); + const dispatch = useMainDispatch() as StateDispatch; + const { rootFontSize } = useMainState(); + + useEffect(() => { + setSlideValue(rootFontSize); + }, [rootFontSize]); + + const handleChange = (e: ChangeEvent) => { + setSlideValue(parseInt(e.target.value)); + dispatch({ type: 'set-root-font-size', size: slideValue }); + }; + + const setNewFontSize = () => { + document.documentElement.style.fontSize = `${slideValue}px`; + dispatch({ type: 'set-root-font-size', size: slideValue }); + invoke('set_root_font_size', { size: slideValue }).catch((e: CmdError) => { + console.warn(`backend error: ${e.source} - ${e.message}`); + }); + }; + + return ( +
+

+ {`Zoom level: ${slideValue}`} +

+ +
+ ); +} + +export default UiScaler; diff --git a/nym-vpn/ui/src/pages/settings/display/index.ts b/nym-vpn/ui/src/pages/settings/display/index.ts new file mode 100644 index 0000000000..fa2dc562ff --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/display/index.ts @@ -0,0 +1 @@ +export { default as Display } from './Display'; diff --git a/nym-vpn/ui/src/pages/settings/feedback/Feedback.tsx b/nym-vpn/ui/src/pages/settings/feedback/Feedback.tsx new file mode 100644 index 0000000000..a2f48556f3 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/feedback/Feedback.tsx @@ -0,0 +1,9 @@ +function Feedback() { + return ( +
+
+
+ ); +} + +export default Feedback; diff --git a/nym-vpn/ui/src/pages/settings/feedback/index.ts b/nym-vpn/ui/src/pages/settings/feedback/index.ts new file mode 100644 index 0000000000..4a06655c88 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/feedback/index.ts @@ -0,0 +1 @@ +export { default as Feedback } from './Feedback'; diff --git a/nym-vpn/ui/src/pages/settings/index.ts b/nym-vpn/ui/src/pages/settings/index.ts new file mode 100644 index 0000000000..65ba8ccb8d --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/index.ts @@ -0,0 +1,5 @@ +export { default as Settings } from './Settings'; +export { default as SettingsLayout } from './SettingsLayout'; +export * from './display'; +export * from './feedback'; +export * from './legal'; diff --git a/nym-vpn/ui/src/pages/settings/legal/Legal.tsx b/nym-vpn/ui/src/pages/settings/legal/Legal.tsx new file mode 100644 index 0000000000..e7267f8cee --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/legal/Legal.tsx @@ -0,0 +1,9 @@ +function Legal() { + return ( +
+
+
+ ); +} + +export default Legal; diff --git a/nym-vpn/ui/src/pages/settings/legal/index.ts b/nym-vpn/ui/src/pages/settings/legal/index.ts new file mode 100644 index 0000000000..77d3ce9eb2 --- /dev/null +++ b/nym-vpn/ui/src/pages/settings/legal/index.ts @@ -0,0 +1 @@ +export { default as Legal } from './Legal'; diff --git a/nym-vpn/ui/src/router.tsx b/nym-vpn/ui/src/router.tsx new file mode 100644 index 0000000000..02300ee738 --- /dev/null +++ b/nym-vpn/ui/src/router.tsx @@ -0,0 +1,67 @@ +import { createBrowserRouter } from 'react-router-dom'; +import { + Display, + Error, + Feedback, + Home, + Legal, + NavLayout, + NodeLocation, + Settings, + SettingsLayout, +} from './pages'; +import { routes } from './constants'; + +const router = createBrowserRouter([ + { + path: routes.root, + element: , + children: [ + { + element: , + errorElement: , + index: true, + }, + { + path: routes.settings, + element: , + errorElement: , + children: [ + { + element: , + errorElement: , + index: true, + }, + { + path: routes.display, + element: , + errorElement: , + }, + { + path: routes.feedback, + element: , + errorElement: , + }, + { + path: routes.legal, + element: , + errorElement: , + }, + ], + }, + { + path: routes.entryNodeLocation, + // eslint-disable-next-line react/jsx-no-undef + element: , + errorElement: , + }, + { + path: routes.exitNodeLocation, + element: , + errorElement: , + }, + ], + }, +]); + +export default router; diff --git a/nym-vpn/ui/src/state/index.ts b/nym-vpn/ui/src/state/index.ts new file mode 100644 index 0000000000..c48bb0ca2d --- /dev/null +++ b/nym-vpn/ui/src/state/index.ts @@ -0,0 +1,2 @@ +export * from './main'; +export * from './provider'; diff --git a/nym-vpn/ui/src/state/main.ts b/nym-vpn/ui/src/state/main.ts new file mode 100644 index 0000000000..4c8a6d1fb9 --- /dev/null +++ b/nym-vpn/ui/src/state/main.ts @@ -0,0 +1,180 @@ +import dayjs from 'dayjs'; +import { + AppState, + ConnectProgressMsg, + ConnectionState, + Country, + NodeHop, + UiTheme, + VpnMode, +} from '../types'; + +export type StateAction = + | { type: 'set-partial-state'; partialState: Partial } + | { type: 'change-connection-state'; state: ConnectionState } + | { type: 'set-vpn-mode'; mode: VpnMode } + | { type: 'set-entry-selector'; entrySelector: boolean } + | { type: 'set-error'; error: string } + | { type: 'reset-error' } + | { type: 'new-progress-message'; message: ConnectProgressMsg } + | { type: 'connect' } + | { type: 'disconnect' } + | { type: 'set-version'; version: string } + | { type: 'set-connected'; startTime: number } + | { type: 'set-connection-start-time'; startTime?: number | null } + | { type: 'set-disconnected' } + | { type: 'set-auto-connect'; autoConnect: boolean } + | { type: 'set-monitoring'; monitoring: boolean } + | { type: 'reset' } + | { type: 'set-ui-theme'; theme: UiTheme } + | { type: 'set-countries'; countries: Country[] } + | { type: 'set-node-location'; payload: { hop: NodeHop; country: Country } } + | { type: 'set-default-node-location'; country: Country } + | { type: 'set-root-font-size'; size: number }; + +export const initialState: AppState = { + state: 'Disconnected', + version: null, + loading: false, + vpnMode: 'TwoHop', + entrySelector: false, + tunnel: { name: 'nym', id: 'nym' }, + uiTheme: 'Light', + progressMessages: [], + autoConnect: false, + monitoring: false, + entryNodeLocation: null, + exitNodeLocation: null, + defaultNodeLocation: { + name: 'France', + code: 'FR', + }, + countries: [], + rootFontSize: 12, +}; + +export function reducer(state: AppState, action: StateAction): AppState { + switch (action.type) { + case 'set-node-location': + if (action.payload.hop === 'entry') { + return { + ...state, + entryNodeLocation: action.payload.country, + }; + } + return { + ...state, + exitNodeLocation: action.payload.country, + }; + case 'set-vpn-mode': + return { + ...state, + vpnMode: action.mode, + }; + case 'set-entry-selector': + return { + ...state, + entrySelector: action.entrySelector, + }; + case 'set-auto-connect': + return { + ...state, + autoConnect: action.autoConnect, + }; + case 'set-monitoring': + return { + ...state, + monitoring: action.monitoring, + }; + case 'set-countries': + return { + ...state, + countries: action.countries, + }; + case 'set-partial-state': { + return { ...state, ...action.partialState }; + } + case 'change-connection-state': { + console.log( + `__REDUCER [change-connection-state] changing connection state to ${action.state}`, + ); + if (action.state === state.state) { + return state; + } + return { + ...state, + state: action.state, + loading: + action.state === 'Connecting' || action.state === 'Disconnecting', + }; + } + case 'connect': { + console.log( + `__REDUCER [connect] changing connection state to Connecting`, + ); + return { ...state, state: 'Connecting', loading: true }; + } + case 'disconnect': { + return { ...state, state: 'Disconnecting', loading: true }; + } + case 'set-version': + return { + ...state, + version: action.version, + }; + case 'set-connected': { + console.log( + `__REDUCER [set-connected] changing connection state to Connected`, + ); + return { + ...state, + state: 'Connected', + loading: false, + progressMessages: [], + sessionStartDate: dayjs.unix(action.startTime), + }; + } + case 'set-disconnected': { + return { + ...state, + state: 'Disconnected', + loading: false, + progressMessages: [], + sessionStartDate: null, + }; + } + case 'set-connection-start-time': + return { + ...state, + sessionStartDate: + (action.startTime && dayjs.unix(action.startTime)) || null, + }; + case 'set-error': + return { ...state, error: action.error }; + case 'reset-error': + return { ...state, error: null }; + case 'new-progress-message': + return { + ...state, + progressMessages: [...state.progressMessages, action.message], + }; + case 'set-ui-theme': + return { + ...state, + uiTheme: action.theme, + }; + case 'set-default-node-location': + return { + ...state, + defaultNodeLocation: action.country, + }; + case 'set-root-font-size': + return { + ...state, + rootFontSize: action.size, + }; + + case 'reset': + return initialState; + } +} diff --git a/nym-vpn/ui/src/state/provider.tsx b/nym-vpn/ui/src/state/provider.tsx new file mode 100644 index 0000000000..482ea353bd --- /dev/null +++ b/nym-vpn/ui/src/state/provider.tsx @@ -0,0 +1,143 @@ +import React, { useEffect, useReducer } from 'react'; +import { invoke } from '@tauri-apps/api'; +import { getVersion } from '@tauri-apps/api/app'; +import { MainDispatchContext, MainStateContext } from '../contexts'; +import { + AppDataFromBackend, + CmdError, + ConnectionState, + Country, +} from '../types'; +import { DefaultRootFontSize } from '../constants'; +import { initialState, reducer } from './main'; +import { useTauriEvents } from './useTauriEvents'; + +type Props = { + children?: React.ReactNode; +}; + +export function MainStateProvider({ children }: Props) { + const [state, dispatch] = useReducer(reducer, initialState); + + useTauriEvents(dispatch); + + // initialize connection state + useEffect(() => { + const getInitialConnectionState = async () => { + return await invoke('get_connection_state'); + }; + + // initialize session start time + const getSessionStartTime = async () => { + return await invoke('get_connection_start_time'); + }; + + // init country list + const getCountries = async () => { + return await invoke('get_node_countries'); + }; + + // init default node location + const getDefaultNodeLocation = async () => { + return await invoke('get_default_node_location'); + }; + + getVersion() + .then((version) => + dispatch({ + type: 'set-version', + version, + }), + ) + .catch((e) => { + console.warn(`command [set-version] returned an error: ${e}`); + }); + + getInitialConnectionState() + .then((state) => dispatch({ type: 'change-connection-state', state })) + .catch((e: CmdError) => { + console.warn( + `command [get_connection_state] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getSessionStartTime() + .then((startTime) => + dispatch({ type: 'set-connection-start-time', startTime }), + ) + .catch((e: CmdError) => { + console.warn( + `command [get_connection_start_time] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getCountries() + .then((countries) => { + dispatch({ type: 'set-countries', countries }); + }) + .catch((e: CmdError) => { + console.warn( + `command [get_node_countries] returned an error: ${e.source} - ${e.message}`, + ); + }); + + getDefaultNodeLocation() + .then((country) => { + dispatch({ type: 'set-default-node-location', country }); + }) + .catch((e: CmdError) => { + console.warn( + `command [get_default_node_location] returned an error: ${e.source} - ${e.message}`, + ); + }); + }, []); + + // get saved on disk app data and restore state from it + useEffect(() => { + const getAppData = async () => { + return await invoke('get_app_data'); + }; + + getAppData() + .then((data) => { + console.log('app data read from disk:'); + console.log(data); + + if (data.ui_root_font_size) { + document.documentElement.style.fontSize = `${data.ui_root_font_size}px`; + } + + const partialState: Partial = { + entrySelector: data.entry_location_selector || false, + uiTheme: data.ui_theme || 'Light', + vpnMode: data.vpn_mode || 'TwoHop', + autoConnect: data.autoconnect || false, + monitoring: data.monitoring || false, + rootFontSize: data.ui_root_font_size || DefaultRootFontSize, + }; + if (data.entry_node_location) { + partialState.entryNodeLocation = data.entry_node_location; + } + if (data.exit_node_location) { + partialState.exitNodeLocation = data.exit_node_location; + } + dispatch({ + type: 'set-partial-state', + partialState, + }); + }) + .catch((e: CmdError) => { + console.warn( + `command [get_app_data] returned an error: ${e.source} - ${e.message}`, + ); + }); + }, []); + + return ( + + + {children} + + + ); +} diff --git a/nym-vpn/ui/src/state/useTauriEvents.ts b/nym-vpn/ui/src/state/useTauriEvents.ts new file mode 100644 index 0000000000..9c22566c1b --- /dev/null +++ b/nym-vpn/ui/src/state/useTauriEvents.ts @@ -0,0 +1,76 @@ +import { useCallback, useEffect } from 'react'; +import { listen } from '@tauri-apps/api/event'; +import dayjs from 'dayjs'; +import { + ConnectionEventPayload, + ProgressEventPayload, + StateDispatch, +} from '../types'; +import { ConnectionEvent, ProgressEvent } from '../constants'; + +function handleError(dispatch: StateDispatch, error?: string | null) { + if (!error) { + dispatch({ type: 'reset-error' }); + return; + } + console.warn(`received backend error: ${error}`); + dispatch({ type: 'set-error', error }); +} + +export function useTauriEvents(dispatch: StateDispatch) { + const registerStateListener = useCallback(() => { + return listen(ConnectionEvent, (event) => { + console.log( + `received event ${event.event}, state: ${event.payload.state}`, + ); + switch (event.payload.state) { + case 'Connected': + dispatch({ + type: 'set-connected', + startTime: event.payload.start_time || dayjs().unix(), + }); + handleError(dispatch, event.payload.error); + break; + case 'Disconnected': + dispatch({ type: 'set-disconnected' }); + handleError(dispatch, event.payload.error); + break; + case 'Connecting': + dispatch({ type: 'change-connection-state', state: 'Connecting' }); + handleError(dispatch, event.payload.error); + break; + case 'Disconnecting': + dispatch({ type: 'change-connection-state', state: 'Disconnecting' }); + handleError(dispatch, event.payload.error); + break; + case 'Unknown': + dispatch({ type: 'change-connection-state', state: 'Unknown' }); + handleError(dispatch, event.payload.error); + break; + } + }); + }, [dispatch]); + + const registerProgressListener = useCallback(() => { + return listen(ProgressEvent, (event) => { + console.log( + `received event ${event.event}, message: ${event.payload.key}`, + ); + dispatch({ + type: 'new-progress-message', + message: event.payload.key, + }); + }); + }, [dispatch]); + + // register/unregister event listener + useEffect(() => { + const unlistenState = registerStateListener(); + const unlistenProgress = registerProgressListener(); + + return () => { + unlistenState.then((f) => f()); + unlistenProgress.then((f) => f()); + }; + }, [registerStateListener, registerProgressListener]); +} diff --git a/nym-vpn/ui/src/styles.css b/nym-vpn/ui/src/styles.css index b5c61c9567..0bb0a1a72d 100644 --- a/nym-vpn/ui/src/styles.css +++ b/nym-vpn/ui/src/styles.css @@ -1,3 +1,12 @@ @tailwind base; @tailwind components; @tailwind utilities; + +@layer base { + @font-face { + font-family: 'Material Symbols Outlined'; + font-style: normal; + font-weight: 400; + src: url(assets/fonts/MaterialSymbolsOutlined.woff2) format('woff2'); + } +} diff --git a/nym-vpn/ui/src/types/app-data.ts b/nym-vpn/ui/src/types/app-data.ts new file mode 100644 index 0000000000..6a0bb2f5be --- /dev/null +++ b/nym-vpn/ui/src/types/app-data.ts @@ -0,0 +1,28 @@ +import { VpnMode } from './app-state'; + +export type UiTheme = 'Dark' | 'Light'; + +export interface NodeConfig { + id: string; + country: Country; +} + +export type Country = { + name: string; + code: string; +}; + +// tauri type, hence the use of snake_case +export interface AppDataFromBackend { + monitoring: boolean | null; + autoconnect: boolean | null; + killswitch: boolean | null; + entry_location_selector: boolean | null; + ui_theme: UiTheme | null; + ui_root_font_size: number | null; + vpn_mode: VpnMode | null; + entry_node: NodeConfig | null; + exit_node: NodeConfig | null; + entry_node_location: Country | null; + exit_node_location: Country | null; +} diff --git a/nym-vpn/ui/src/types/app-state.ts b/nym-vpn/ui/src/types/app-state.ts new file mode 100644 index 0000000000..bbbd887c8c --- /dev/null +++ b/nym-vpn/ui/src/types/app-state.ts @@ -0,0 +1,52 @@ +import { Dispatch } from 'react'; +import { Dayjs } from 'dayjs'; +import { StateAction } from '../state'; +import { Country } from './app-data'; + +export type ConnectionState = + | 'Connected' + | 'Disconnected' + | 'Connecting' + | 'Disconnecting' + | 'Unknown'; + +export type VpnMode = 'TwoHop' | 'Mixnet'; + +export interface TunnelConfig { + id: string; + name: string; +} + +export type AppState = { + state: ConnectionState; + version: string | null; + loading: boolean; + error?: string | null; + progressMessages: ConnectProgressMsg[]; + sessionStartDate?: Dayjs | null; + vpnMode: VpnMode; + tunnel: TunnelConfig; + uiTheme: 'Light' | 'Dark'; + entrySelector: boolean; + autoConnect: boolean; + monitoring: boolean; + entryNodeLocation: Country | null; + exitNodeLocation: Country | null; + defaultNodeLocation: Country; + countries: Country[]; + rootFontSize: number; +}; + +export type ConnectionEventPayload = { + state: ConnectionState; + error?: string | null; + start_time?: number | null; // unix timestamp in seconds +}; + +export type ConnectProgressMsg = 'Initializing' | 'InitDone'; + +export type ProgressEventPayload = { + key: ConnectProgressMsg; +}; + +export type StateDispatch = Dispatch; diff --git a/nym-vpn/ui/src/types/general.ts b/nym-vpn/ui/src/types/general.ts new file mode 100644 index 0000000000..a7b89bc235 --- /dev/null +++ b/nym-vpn/ui/src/types/general.ts @@ -0,0 +1,5 @@ +import React from 'react'; + +export type InputEvent = React.ChangeEvent; + +export type NodeHop = 'entry' | 'exit'; diff --git a/nym-vpn/ui/src/types/index.ts b/nym-vpn/ui/src/types/index.ts new file mode 100644 index 0000000000..7bd3444e71 --- /dev/null +++ b/nym-vpn/ui/src/types/index.ts @@ -0,0 +1,5 @@ +export * from './app-state'; +export * from './app-data'; +export * from './tauri-ipc'; +export * from './routes'; +export * from './general'; diff --git a/nym-vpn/ui/src/types/routes.ts b/nym-vpn/ui/src/types/routes.ts new file mode 100644 index 0000000000..a18280924e --- /dev/null +++ b/nym-vpn/ui/src/types/routes.ts @@ -0,0 +1,3 @@ +import { routes } from '../constants'; + +export type Routes = (typeof routes)[keyof typeof routes]; diff --git a/nym-vpn/ui/src/types/tauri-ipc.ts b/nym-vpn/ui/src/types/tauri-ipc.ts new file mode 100644 index 0000000000..9df3c44f36 --- /dev/null +++ b/nym-vpn/ui/src/types/tauri-ipc.ts @@ -0,0 +1,6 @@ +export type CmdErrorSource = 'InternalError' | 'CallerError' | 'Unknown'; + +export interface CmdError { + source: CmdErrorSource; + message: string; +} diff --git a/nym-vpn/ui/src/ui/ThemeSetter.tsx b/nym-vpn/ui/src/ui/ThemeSetter.tsx new file mode 100644 index 0000000000..99941e74cc --- /dev/null +++ b/nym-vpn/ui/src/ui/ThemeSetter.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import clsx from 'clsx'; +import { useMainState } from '../contexts'; + +export default function ThemeSetter({ + children, +}: { + children: React.ReactNode; +}) { + const { uiTheme } = useMainState(); + + return ( +
+ {children} +
+ ); +} diff --git a/nym-vpn/ui/src/ui/TopBar.tsx b/nym-vpn/ui/src/ui/TopBar.tsx new file mode 100644 index 0000000000..2f3bfe7fa8 --- /dev/null +++ b/nym-vpn/ui/src/ui/TopBar.tsx @@ -0,0 +1,130 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import clsx from 'clsx'; +import { routes } from '../constants'; +import { Routes } from '../types'; + +type NavLocation = { + title: string; + leftIcon?: React.ReactNode; + handleLeftNav?: () => void; + rightIcon?: React.ReactNode; + handleRightNav?: () => void; +}; + +type NavBarData = { + [key in Routes]: NavLocation; +}; + +export default function TopBar() { + const location = useLocation(); + const navigate = useNavigate(); + const { t } = useTranslation(); + + const [currentNavLocation, setCurrentNavLocation] = useState({ + title: '', + rightIcon: 'settings', + handleRightNav: () => { + navigate(routes.settings); + }, + }); + + const navBarData = useMemo(() => { + return { + '/': { + title: '', + rightIcon: 'settings', + handleRightNav: () => { + navigate(routes.settings); + }, + }, + '/settings': { + title: t('settings'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/settings/display': { + title: t('display-theme'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/settings/logs': { + title: t('logs'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/settings/feedback': { + title: t('feedback'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/settings/legal': { + title: t('legal'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/entry-node-location': { + title: t('first-hop-selection'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + '/exit-node-location': { + title: t('last-hop-selection'), + leftIcon: 'arrow_back', + handleLeftNav: () => { + navigate(-1); + }, + }, + }; + }, [t, navigate]); + + useEffect(() => { + setCurrentNavLocation(navBarData[location.pathname as Routes]); + }, [location.pathname, navBarData]); + + return ( + + ); +} diff --git a/nym-vpn/ui/src/ui/index.ts b/nym-vpn/ui/src/ui/index.ts new file mode 100644 index 0000000000..f3af8c7dd6 --- /dev/null +++ b/nym-vpn/ui/src/ui/index.ts @@ -0,0 +1,2 @@ +export { default as ThemeSetter } from './ThemeSetter'; +export { default as TopBar } from './TopBar'; diff --git a/nym-vpn/ui/tailwind.config.js b/nym-vpn/ui/tailwind.config.js deleted file mode 100644 index a1f67d7f61..0000000000 --- a/nym-vpn/ui/tailwind.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -export default { - content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], - theme: { - extend: {}, - }, - plugins: [], - // Toggling dark mode manually - darkMode: 'class', -}; diff --git a/nym-vpn/ui/tailwind.config.ts b/nym-vpn/ui/tailwind.config.ts new file mode 100644 index 0000000000..41680e6295 --- /dev/null +++ b/nym-vpn/ui/tailwind.config.ts @@ -0,0 +1,89 @@ +import type { Config } from 'tailwindcss'; +import defaultTheme from 'tailwindcss/defaultTheme'; +import headlessui from '@headlessui/tailwindcss'; + +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + colors: { + transparent: 'transparent', + current: 'currentColor', + 'baltic-sea': { + // [D] bg for top-bar nav + // [D] bg for network modes + jaguar: '#2B2831', + // [L] status-line title text + icon + // [L] connection timer text + // [L] "Connecting" status text + // [L] main titles text + // [L] network mode title text + icon + // [L] node location select text value + icon + label + // [D] button text + DEFAULT: '#1C1B1F', // [D] main bg + oil: '#1F1F22', // [D] connection status bg + quartzite: '#202C25', // [D] "Connected" status bg + }, + 'blanc-nacre': { + DEFAULT: '#F2F4F6', // [L] main bg + platinum: '#E7E8EC', // [L] connection status bg + icicle: '#E1EFE7', // [L] "Connected" status bg + }, + // [DL] primary accent + melon: '#FB6E4E', + // [DL] secondary accent + cornflower: '#7075FF', + // [DL] error status text + teaberry: '#E33B5A', + comet: '#625B71', + // [DL] "Connected" status text + 'vert-menthe': '#2BC761', + // [D] main titles text + // [D] connection timer text + // [D] "Connecting" status text + // [L] bg for top-bar nav + // [L] bg for network modes + // [L] button text + white: '#FFF', + black: '#000', + mercury: { + // [D] status-line title text + icon + // [D] network mode title text + icon + // [D] node location select text value + icon + label + pinkish: '#E6E1E5', + DEFAULT: '#E1EFE7', + // [D] network mode desc text + // [D] "Connection time" + // [D] main status desc text + mist: '#938F99', + }, + // [DL] "Disconnected" status text + 'coal-mine': { dark: '#56545A', light: '#A4A4A4' }, + // [L] "Connection time" + // [L] main status desc text + 'dim-gray': '#696571', + // [L] network mode desc text + // [L] node location select outline + 'cement-feet': '#79747E', + // [D] node location select outline + 'gun-powder': '#49454F', + // [D] top-bar icon + 'laughing-jack': '#CAC4D0', + // [L] button bg in disabled state + 'wind-chime': '#DEDEE1', + }, + extend: { + fontFamily: { + sans: ['Lato', ...defaultTheme.fontFamily.sans], + icon: [ + 'Material Symbols Outlined', + { + fontVariationSettings: '"opsz" 24;', + }, + ], + }, + }, + }, + plugins: [headlessui], + // Toggling dark mode manually + darkMode: 'class', +} satisfies Config; diff --git a/nym-vpn/ui/vite.config.ts b/nym-vpn/ui/vite.config.ts index 8d90eb50cb..e198d2119d 100644 --- a/nym-vpn/ui/vite.config.ts +++ b/nym-vpn/ui/vite.config.ts @@ -4,7 +4,6 @@ import react from '@vitejs/plugin-react-swc'; // https://vitejs.dev/config/ export default defineConfig(async () => ({ plugins: [react()], - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` // // 1. prevent vite from obscuring rust errors diff --git a/nym-vpn/ui/yarn.lock b/nym-vpn/ui/yarn.lock index e243243e30..cd65f0aff0 100644 --- a/nym-vpn/ui/yarn.lock +++ b/nym-vpn/ui/yarn.lock @@ -4,178 +4,152 @@ "@aashutoshrathi/word-wrap@^1.2.3": version "1.2.6" - resolved "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@alloc/quick-lru@^5.2.0": version "5.2.0" - resolved "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz" + resolved "https://registry.yarnpkg.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== -"@babel/runtime@^7.23.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": - version "7.23.2" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz" - integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== +"@babel/runtime@^7.22.5", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.6": + version "7.23.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.7.tgz#dd7c88deeb218a0f8bd34d5db1aa242e0f203193" + integrity sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA== dependencies: regenerator-runtime "^0.14.0" -"@emotion/cache@^11.11.0": - version "11.11.0" - resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" - integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== - dependencies: - "@emotion/memoize" "^0.8.1" - "@emotion/sheet" "^1.2.2" - "@emotion/utils" "^1.2.1" - "@emotion/weak-memoize" "^0.3.1" - stylis "4.2.0" +"@esbuild/aix-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.11.tgz#2acd20be6d4f0458bc8c784103495ff24f13b1d3" + integrity sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g== -"@emotion/memoize@^0.8.1": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" - integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== +"@esbuild/android-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.11.tgz#b45d000017385c9051a4f03e17078abb935be220" + integrity sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q== -"@emotion/sheet@^1.2.2": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" - integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== +"@esbuild/android-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.11.tgz#f46f55414e1c3614ac682b29977792131238164c" + integrity sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw== -"@emotion/utils@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" - integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== +"@esbuild/android-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.11.tgz#bfc01e91740b82011ef503c48f548950824922b2" + integrity sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg== -"@emotion/weak-memoize@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" - integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== +"@esbuild/darwin-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.11.tgz#533fb7f5a08c37121d82c66198263dcc1bed29bf" + integrity sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ== -"@esbuild/android-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" - integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== +"@esbuild/darwin-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.11.tgz#62f3819eff7e4ddc656b7c6815a31cf9a1e7d98e" + integrity sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g== -"@esbuild/android-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" - integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== +"@esbuild/freebsd-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.11.tgz#d478b4195aa3ca44160272dab85ef8baf4175b4a" + integrity sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA== -"@esbuild/android-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" - integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== +"@esbuild/freebsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.11.tgz#7bdcc1917409178257ca6a1a27fe06e797ec18a2" + integrity sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw== -"@esbuild/darwin-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" - integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== +"@esbuild/linux-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.11.tgz#58ad4ff11685fcc735d7ff4ca759ab18fcfe4545" + integrity sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg== -"@esbuild/darwin-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" - integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== +"@esbuild/linux-arm@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.11.tgz#ce82246d873b5534d34de1e5c1b33026f35e60e3" + integrity sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q== -"@esbuild/freebsd-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" - integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== +"@esbuild/linux-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.11.tgz#cbae1f313209affc74b80f4390c4c35c6ab83fa4" + integrity sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA== -"@esbuild/freebsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" - integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== +"@esbuild/linux-loong64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.11.tgz#5f32aead1c3ec8f4cccdb7ed08b166224d4e9121" + integrity sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg== -"@esbuild/linux-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" - integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== +"@esbuild/linux-mips64el@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.11.tgz#38eecf1cbb8c36a616261de858b3c10d03419af9" + integrity sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg== -"@esbuild/linux-arm@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" - integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== +"@esbuild/linux-ppc64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.11.tgz#9c5725a94e6ec15b93195e5a6afb821628afd912" + integrity sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA== -"@esbuild/linux-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" - integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== +"@esbuild/linux-riscv64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.11.tgz#2dc4486d474a2a62bbe5870522a9a600e2acb916" + integrity sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ== -"@esbuild/linux-loong64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" - integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== +"@esbuild/linux-s390x@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.11.tgz#4ad8567df48f7dd4c71ec5b1753b6f37561a65a8" + integrity sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q== -"@esbuild/linux-mips64el@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" - integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== +"@esbuild/linux-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.11.tgz#b7390c4d5184f203ebe7ddaedf073df82a658766" + integrity sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA== -"@esbuild/linux-ppc64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" - integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== +"@esbuild/netbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.11.tgz#d633c09492a1721377f3bccedb2d821b911e813d" + integrity sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ== -"@esbuild/linux-riscv64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" - integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== +"@esbuild/openbsd-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.11.tgz#17388c76e2f01125bf831a68c03a7ffccb65d1a2" + integrity sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw== -"@esbuild/linux-s390x@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" - integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== +"@esbuild/sunos-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.11.tgz#e320636f00bb9f4fdf3a80e548cb743370d41767" + integrity sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ== -"@esbuild/linux-x64@0.18.20": - version "0.18.20" - resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz" - integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== +"@esbuild/win32-arm64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.11.tgz#c778b45a496e90b6fc373e2a2bb072f1441fe0ee" + integrity sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ== -"@esbuild/netbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" - integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== +"@esbuild/win32-ia32@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.11.tgz#481a65fee2e5cce74ec44823e6b09ecedcc5194c" + integrity sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg== -"@esbuild/openbsd-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" - integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== - -"@esbuild/sunos-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" - integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== - -"@esbuild/win32-arm64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" - integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== - -"@esbuild/win32-ia32@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" - integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== - -"@esbuild/win32-x64@0.18.20": - version "0.18.20" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" - integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== +"@esbuild/win32-x64@0.19.11": + version "0.19.11" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.11.tgz#a5d300008960bb39677c46bf16f53ec70d8dee04" + integrity sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" - resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.9.1" - resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.9.1.tgz" - integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA== + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@eslint/eslintrc@^2.1.2": - version "2.1.2" - resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz" - integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" @@ -187,41 +161,53 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.52.0": - version "8.52.0" - resolved "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz" - integrity sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA== +"@eslint/js@8.56.0": + version "8.56.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.56.0.tgz#ef20350fec605a7f7035a01764731b2de0f3782b" + integrity sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A== -"@floating-ui/core@^1.4.2": - version "1.5.0" - resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz" - integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg== - dependencies: - "@floating-ui/utils" "^0.1.3" - -"@floating-ui/dom@^1.5.1": +"@floating-ui/core@^1.5.3": version "1.5.3" - resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz" - integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA== + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.3.tgz#b6aa0827708d70971c8679a16cf680a515b8a52a" + integrity sha512-O0WKDOo0yhJuugCx6trZQj5jVJ9yR0ystG2JaNAemYUWce+pmM6WUEFIibnWyEJKdrDxhm75NoSRME35FNaM/Q== dependencies: - "@floating-ui/core" "^1.4.2" - "@floating-ui/utils" "^0.1.3" + "@floating-ui/utils" "^0.2.0" -"@floating-ui/react-dom@^2.0.2": - version "2.0.2" - resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz" - integrity sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ== +"@floating-ui/dom@^1.5.4": + version "1.5.4" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.4.tgz#28df1e1cb373884224a463235c218dcbd81a16bb" + integrity sha512-jByEsHIY+eEdCjnTVu+E3ephzTOzkQ8hgUfGwos+bg7NlH33Zc5uO+QHz1mrQUOgIKKDD1RtS201P9NvAfq3XQ== dependencies: - "@floating-ui/dom" "^1.5.1" + "@floating-ui/core" "^1.5.3" + "@floating-ui/utils" "^0.2.0" -"@floating-ui/utils@^0.1.3": - version "0.1.6" - resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz" - integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A== +"@floating-ui/react-dom@^2.0.4": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.5.tgz#851522899c34e3e2be1e29f3294f150834936e28" + integrity sha512-UsBK30Bg+s6+nsgblXtZmwHhgS2vmbuQK22qgt2pTQM6M3X6H1+cQcLXqgRY3ihVLcZJE6IvqDQozhsnIVqK/Q== + dependencies: + "@floating-ui/dom" "^1.5.4" + +"@floating-ui/utils@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.1.tgz#16308cea045f0fc777b6ff20a9f25474dd8293d2" + integrity sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q== + +"@headlessui/react@^1.7.17": + version "1.7.17" + resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.17.tgz#a0ec23af21b527c030967245fd99776aa7352bc6" + integrity sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow== + dependencies: + client-only "^0.0.1" + +"@headlessui/tailwindcss@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.0.tgz#2c55c98fd8eee4b4f21ec6eb35a014b840059eec" + integrity sha512-fpL830Fln1SykOCboExsWr3JIVeQKieLJ3XytLe/tt1A0XzqUthOftDmjcCYLW62w7mQI7wXcoPXr3tZ9QfGxw== "@humanwhocodes/config-array@^0.11.13": version "0.11.13" - resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.13.tgz#075dc9684f40a531d9b26b0822153c1e832ee297" integrity sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ== dependencies: "@humanwhocodes/object-schema" "^2.0.1" @@ -230,17 +216,29 @@ "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/object-schema@^2.0.1": version "2.0.1" - resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz#e5211452df060fa8522b55c7b3c0c4d1981cb044" integrity sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw== +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@jridgewell/gen-mapping@^0.3.2": version "0.3.3" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098" integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ== dependencies: "@jridgewell/set-array" "^1.0.1" @@ -249,114 +247,58 @@ "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA== "@jridgewell/set-array@^1.0.1": version "1.1.2" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== "@jridgewell/trace-mapping@^0.3.9": version "0.3.20" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f" integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q== dependencies: "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@mui/base@5.0.0-beta.20", "@mui/base@^5.0.0-beta.20": - version "5.0.0-beta.20" - resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.20.tgz" - integrity sha512-CS2pUuqxST7ch9VNDCklRYDbJ3rru20Tx7na92QvVVKfu3RL4z/QLuVIc8jYGsdCnauMaeUSlFNLAJNb0yXe6w== +"@mui/base@^5.0.0-beta.24": + version "5.0.0-beta.30" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.30.tgz#8feca6b70f2b9cd4d5cb97799ae9fcb5376c7f83" + integrity sha512-dc38W4W3K42atE9nSaOeoJ7/x9wGIfawdwC/UmMxMLlZ1iSsITQ8dQJaTATCbn98YvYPINK/EH541YA5enQIPQ== dependencies: - "@babel/runtime" "^7.23.1" - "@floating-ui/react-dom" "^2.0.2" - "@mui/types" "^7.2.6" - "@mui/utils" "^5.14.13" + "@babel/runtime" "^7.23.6" + "@floating-ui/react-dom" "^2.0.4" + "@mui/types" "^7.2.12" + "@mui/utils" "^5.15.3" "@popperjs/core" "^2.11.8" clsx "^2.0.0" prop-types "^15.8.1" -"@mui/core-downloads-tracker@^5.14.14": - version "5.14.14" - resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.14.tgz#a54894e9b4dc908ab2d59eac543219d9018448e6" - integrity sha512-Rw/xKiTOUgXD8hdKqj60aC6QcGprMipG7ne2giK6Mz7b4PlhL/xog9xLeclY3BxsRLkZQ05egFnIEY1CSibTbw== +"@mui/types@^7.2.12": + version "7.2.12" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.12.tgz#602acbb5aa3eb56a31f569a19f87f75d33de5c01" + integrity sha512-3kaHiNm9khCAo0pVe0RenketDSFoZGAlVZ4zDjB/QNZV0XiCj+sh1zkX0VVhQPgYJDlBEzAag+MHJ1tU3vf0Zw== -"@mui/material@^5.14.14": - version "5.14.14" - resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.14.tgz#e47f3992b609002cd57a71f70e829dc2d286028c" - integrity sha512-cAmCwAHFQXxb44kWbVFkhKATN8tACgMsFwrXo8ro6WzYW73U/qsR5AcCiJIhCyYYg+gcftfkmNcpRaV3JjhHCg== +"@mui/utils@^5.15.3": + version "5.15.3" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.3.tgz#421043be5279d31ca9b221a6398feb7c9d61209b" + integrity sha512-mT3LiSt9tZWCdx1pl7q4Q5tNo6gdZbvJel286ZHGuj6LQQXjWNAh8qiF9d+LogvNUI+D7eLkTnj605d1zoazfg== dependencies: - "@babel/runtime" "^7.23.1" - "@mui/base" "5.0.0-beta.20" - "@mui/core-downloads-tracker" "^5.14.14" - "@mui/system" "^5.14.14" - "@mui/types" "^7.2.6" - "@mui/utils" "^5.14.13" - "@types/react-transition-group" "^4.4.7" - clsx "^2.0.0" - csstype "^3.1.2" - prop-types "^15.8.1" - react-is "^18.2.0" - react-transition-group "^4.4.5" - -"@mui/private-theming@^5.14.14": - version "5.14.14" - resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.14.tgz#035dde1eb30c896c69a12b7dee1dce3a323c66e9" - integrity sha512-n77au3CQj9uu16hak2Y+rvbGSBaJKxziG/gEbOLVGrAuqZ+ycVSkorCfN6Y/4XgYOpG/xvmuiY3JwhAEOzY3iA== - dependencies: - "@babel/runtime" "^7.23.1" - "@mui/utils" "^5.14.13" - prop-types "^15.8.1" - -"@mui/styled-engine@^5.14.13": - version "5.14.14" - resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.14.tgz#b0ededf531fff1ef110f7b263c2d3d95a0b8ec9a" - integrity sha512-sF3DS2PVG+cFWvkVHQQaGFpL1h6gSwOW3L91pdxPLQDHDZ5mZ/X0SlXU5XA+WjypoysG4urdAQC7CH/BRvUiqg== - dependencies: - "@babel/runtime" "^7.23.1" - "@emotion/cache" "^11.11.0" - csstype "^3.1.2" - prop-types "^15.8.1" - -"@mui/system@^5.14.14": - version "5.14.14" - resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.14.tgz#f33327e74230523169107ace960e8bb51cbdbab7" - integrity sha512-y4InFmCgGGWXnz+iK4jRTWVikY0HgYnABjz4wgiUgEa2W1H8M4ow+27BegExUWPkj4TWthQ2qG9FOGSMtI+PKA== - dependencies: - "@babel/runtime" "^7.23.1" - "@mui/private-theming" "^5.14.14" - "@mui/styled-engine" "^5.14.13" - "@mui/types" "^7.2.6" - "@mui/utils" "^5.14.13" - clsx "^2.0.0" - csstype "^3.1.2" - prop-types "^15.8.1" - -"@mui/types@^7.2.6": - version "7.2.6" - resolved "https://registry.npmjs.org/@mui/types/-/types-7.2.6.tgz" - integrity sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng== - -"@mui/utils@^5.14.13": - version "5.14.14" - resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.14.14.tgz" - integrity sha512-3AKp8uksje5sRfVrtgG9Q/2TBsHWVBUtA0NaXliZqGcXo8J+A+Agp0qUW2rJ+ivgPWTCCubz9FZVT2IQZ3bGsw== - dependencies: - "@babel/runtime" "^7.23.1" - "@types/prop-types" "^15.7.7" + "@babel/runtime" "^7.23.6" + "@types/prop-types" "^15.7.11" prop-types "^15.8.1" react-is "^18.2.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: "@nodelib/fs.stat" "2.0.5" @@ -364,225 +306,325 @@ "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== dependencies: "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@popperjs/core@^2.11.8": version "2.11.8" - resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@swc/core-darwin-arm64@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.94.tgz#2fe8e513433cd5d5c987952e38ca06e6226de0f3" - integrity sha512-KNuE6opIy/wAXiGUWLhGWhCG3wA/AdjG6eYkv6dstrAURLaQMAoD8vDfVm8pxS8FA8Kx+0Z4QiDNPqk5aKIsqg== +"@remix-run/router@1.14.1": + version "1.14.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.1.tgz#6d2dd03d52e604279c38911afc1079d58c50a755" + integrity sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow== -"@swc/core-darwin-x64@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.94.tgz#6b626013657e18eaf1e795370eac70e86dc7d300" - integrity sha512-HypemhyehQrLqXwfJv5ronD4BMAXdgMCP4Ei7rt3B6Ftmt9axwGvdwGiXxsYR9h1ncyxoVxN+coGxbNIhKhahw== +"@rollup/rollup-android-arm-eabi@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.4.tgz#b1094962742c1a0349587040bc06185e2a667c9b" + integrity sha512-ub/SN3yWqIv5CWiAZPHVS1DloyZsJbtXmX4HxUTIpS0BHm9pW5iYBo2mIZi+hE3AeiTzHz33blwSnhdUo+9NpA== -"@swc/core-linux-arm-gnueabihf@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.94.tgz#300483c9e9a3a4084d8264f59daee19102e1084b" - integrity sha512-KzKN54c7Y6X1db+bBVSXG4+bXmAPvXtDWk+TgwNJH4yYliOrnP/RKkHA5QZ9VFSnqJF06/sAO4kYBiL/aVQDBQ== +"@rollup/rollup-android-arm64@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.4.tgz#96eb86fb549e05b187f2ad06f51d191a23cb385a" + integrity sha512-ehcBrOR5XTl0W0t2WxfTyHCR/3Cq2jfb+I4W+Ch8Y9b5G+vbAecVv0Fx/J1QKktOrgUYsIKxWAKgIpvw56IFNA== -"@swc/core-linux-arm64-gnu@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.94.tgz#ac099db32d60e161c11bf01a9496ea0ada347247" - integrity sha512-iAcR8Ho0Uck/SLSrgYfXkpcGOXuN5waMZO7GlL/52QODr7GJtOfZ0H1MCZLbIFkPJp/iXoJpYgym4d/qSd477Q== +"@rollup/rollup-darwin-arm64@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.4.tgz#2456630c007cc5905cb368acb9ff9fc04b2d37be" + integrity sha512-1fzh1lWExwSTWy8vJPnNbNM02WZDS8AW3McEOb7wW+nPChLKf3WG2aG7fhaUmfX5FKw9zhsF5+MBwArGyNM7NA== -"@swc/core-linux-arm64-musl@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.94.tgz#e555791abf27d74831dc3581327662f65e2b62f2" - integrity sha512-VCHL1Mb9ENHx+sAeubSSg481MUeP9/PYzPPy9tfswunj/w35M+vEWflwK2dzQL9kUTFD3zcFTpAgsKnj6aX24w== +"@rollup/rollup-darwin-x64@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.4.tgz#97742214fc7dfd47a0f74efba6f5ae264e29c70c" + integrity sha512-Gc6cukkF38RcYQ6uPdiXi70JB0f29CwcQ7+r4QpfNpQFVHXRd0DfWFidoGxjSx1DwOETM97JPz1RXL5ISSB0pA== -"@swc/core-linux-x64-gnu@1.3.94": - version "1.3.94" - resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.94.tgz" - integrity sha512-gjq7U6clhJi0Oel2a4gwR4MbSu+THQ2hmBNVCOSA3JjPZWZTkJXaJDpnh/r7PJxKBwUDlo0VPlwiwjepAQR2Rw== +"@rollup/rollup-linux-arm-gnueabihf@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.4.tgz#cd933e61d6f689c9cdefde424beafbd92cfe58e2" + integrity sha512-g21RTeFzoTl8GxosHbnQZ0/JkuFIB13C3T7Y0HtKzOXmoHhewLbVTFBQZu+z5m9STH6FZ7L/oPgU4Nm5ErN2fw== -"@swc/core-linux-x64-musl@1.3.94": - version "1.3.94" - resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.94.tgz" - integrity sha512-rSylruWyeol2ujZDHmwiovupMR5ukMXivlA7DDxmQ1dFUV9HuiPknQrU5rEbI3V2V3V5RkpbEKjnADen7AeMPQ== +"@rollup/rollup-linux-arm64-gnu@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.4.tgz#33b09bf462f1837afc1e02a1b352af6b510c78a6" + integrity sha512-TVYVWD/SYwWzGGnbfTkrNpdE4HON46orgMNHCivlXmlsSGQOx/OHHYiQcMIOx38/GWgwr/po2LBn7wypkWw/Mg== -"@swc/core-win32-arm64-msvc@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.94.tgz#1ddab334f90ba40fb2b7768476fb64f4e8f1d759" - integrity sha512-OenDUr5MQkz506ebVQq6ezoZ3GZ26nchgf5mPnwab4gx2TEiyR9zn7MdX5LWskTmOK3+FszPbGK0B5oLK6Y5yw== +"@rollup/rollup-linux-arm64-musl@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.4.tgz#50257fb248832c2308064e3764a16273b6ee4615" + integrity sha512-XcKvuendwizYYhFxpvQ3xVpzje2HHImzg33wL9zvxtj77HvPStbSGI9czrdbfrf8DGMcNNReH9pVZv8qejAQ5A== -"@swc/core-win32-ia32-msvc@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.94.tgz#e254b4ab6514cf0ebd89781c7bf348484d006b8b" - integrity sha512-mi6NcmtJKnaiHAxLtVz+WzunscsEwPdA0j15DuiYVx06Xo+MdRLJj4eVBgVLwGD1AI3IqKs4MVVx2cD7n0h5mg== +"@rollup/rollup-linux-riscv64-gnu@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.4.tgz#09589e4e1a073cf56f6249b77eb6c9a8e9b613a8" + integrity sha512-LFHS/8Q+I9YA0yVETyjonMJ3UA+DczeBd/MqNEzsGSTdNvSJa1OJZcSH8GiXLvcizgp9AlHs2walqRcqzjOi3A== -"@swc/core-win32-x64-msvc@1.3.94": - version "1.3.94" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.94.tgz#dc193537ccd87f40552e099038f543e0756f74de" - integrity sha512-Ba0ZLcGMnqPWWF9Xa+rWhhnkpvE7XoQegMP/VCF2JIHb2ieGBC8jChO6nKRFKZjib/3wghGzxakyDQx3LDhDug== +"@rollup/rollup-linux-x64-gnu@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.4.tgz#bd312bb5b5f02e54d15488605d15cfd3f90dda7c" + integrity sha512-dIYgo+j1+yfy81i0YVU5KnQrIJZE8ERomx17ReU4GREjGtDW4X+nvkBak2xAUpyqLs4eleDSj3RrV72fQos7zw== -"@swc/core@^1.3.85": - version "1.3.94" - resolved "https://registry.npmjs.org/@swc/core/-/core-1.3.94.tgz" - integrity sha512-jTHn8UJOGgERKZLy8euEixVAzC/w/rUSuMlM3e7hxgap/TC595hSkuQwtkpL238dsuEPveD44GMy2A5UBtSvjg== +"@rollup/rollup-linux-x64-musl@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.4.tgz#25b3bede85d86438ce28cc642842d10d867d40e9" + integrity sha512-RoaYxjdHQ5TPjaPrLsfKqR3pakMr3JGqZ+jZM0zP2IkDtsGa4CqYaWSfQmZVgFUCgLrTnzX+cnHS3nfl+kB6ZQ== + +"@rollup/rollup-win32-arm64-msvc@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.4.tgz#95957067eb107f571da1d81939f017d37b4958d3" + integrity sha512-T8Q3XHV+Jjf5e49B4EAaLKV74BbX7/qYBRQ8Wop/+TyyU0k+vSjiLVSHNWdVd1goMjZcbhDmYZUYW5RFqkBNHQ== + +"@rollup/rollup-win32-ia32-msvc@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.4.tgz#71b6facad976db527863f698692c6964c0b6e10e" + integrity sha512-z+JQ7JirDUHAsMecVydnBPWLwJjbppU+7LZjffGf+Jvrxq+dVjIE7By163Sc9DKc3ADSU50qPVw0KonBS+a+HQ== + +"@rollup/rollup-win32-x64-msvc@4.9.4": + version "4.9.4" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.4.tgz#16295ccae354707c9bc6842906bdeaad4f3ba7a5" + integrity sha512-LfdGXCV9rdEify1oxlN9eamvDSjv9md9ZVMAbNHA87xqIfFCxImxan9qZ8+Un54iK2nnqPlbnSi4R54ONtbWBw== + +"@swc/core-darwin-arm64@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.102.tgz#2bbd90a8751e6eee981f857ec3f0b6233208da37" + integrity sha512-CJDxA5Wd2cUMULj3bjx4GEoiYyyiyL8oIOu4Nhrs9X+tlg8DnkCm4nI57RJGP8Mf6BaXPIJkHX8yjcefK2RlDA== + +"@swc/core-darwin-x64@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.102.tgz#df16d51f45771d3c9cca8554b28a3190cdb075cf" + integrity sha512-X5akDkHwk6oAer49oER0qZMjNMkLH3IOZaV1m98uXIasAGyjo5WH1MKPeMLY1sY6V6TrufzwiSwD4ds571ytcg== + +"@swc/core-linux-arm-gnueabihf@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.102.tgz#eb71697590c56ea261fa9a4b198c45304c7ece39" + integrity sha512-kJH3XtZP9YQdjq/wYVBeFuiVQl4HaC4WwRrIxAHwe2OyvrwUI43dpW3LpxSggBnxXcVCXYWf36sTnv8S75o2Gw== + +"@swc/core-linux-arm64-gnu@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.102.tgz#75d72d5253d56723fa7054e1a8f313bf3d17b1a2" + integrity sha512-flQP2WDyCgO24WmKA1wjjTx+xfCmavUete2Kp6yrM+631IHLGnr17eu7rYJ/d4EnDBId/ytMyrnWbTVkaVrpbQ== + +"@swc/core-linux-arm64-musl@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.102.tgz#7db86022fec57c1e06c573d45cef5e911bcc420e" + integrity sha512-bQEQSnC44DyoIGLw1+fNXKVGoCHi7eJOHr8BdH0y1ooy9ArskMjwobBFae3GX4T1AfnrTaejyr0FvLYIb0Zkog== + +"@swc/core-linux-x64-gnu@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.102.tgz#298a25aa854924bedc7e4b69da52da19f84fc7a8" + integrity sha512-dFvnhpI478svQSxqISMt00MKTDS0e4YtIr+ioZDG/uJ/q+RpcNy3QI2KMm05Fsc8Y0d4krVtvCKWgfUMsJZXAg== + +"@swc/core-linux-x64-musl@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.102.tgz#1bcd911aaa88b96f3bb665b0fd84ef4d21adf886" + integrity sha512-+a0M3CvjeIRNA/jTCzWEDh2V+mhKGvLreHOL7J97oULZy5yg4gf7h8lQX9J8t9QLbf6fsk+0F8bVH1Ie/PbXjA== + +"@swc/core-win32-arm64-msvc@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.102.tgz#59084786364d03fa4a120bdd589a557a00caedeb" + integrity sha512-w76JWLjkZNOfkB25nqdWUNCbt0zJ41CnWrJPZ+LxEai3zAnb2YtgB/cCIrwxDebRuMgE9EJXRj7gDDaTEAMOOQ== + +"@swc/core-win32-ia32-msvc@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.102.tgz#27954889d940a63796d58ff7753f5f27ed381a1f" + integrity sha512-vlDb09HiGqKwz+2cxDS9T5/461ipUQBplvuhW+cCbzzGuPq8lll2xeyZU0N1E4Sz3MVdSPx1tJREuRvlQjrwNg== + +"@swc/core-win32-x64-msvc@1.3.102": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.102.tgz#477da542e6b01b3eb64476ec9a78f497a9b87807" + integrity sha512-E/jfSD7sShllxBwwgDPeXp1UxvIqehj/ShSUqq1pjR/IDRXngcRSXKJK92mJkNFY7suH6BcCWwzrxZgkO7sWmw== + +"@swc/core@^1.3.96": + version "1.3.102" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.102.tgz#870874bcf1d78cd7bb1bc66b31bf2b1a87c1a667" + integrity sha512-OAjNLY/f6QWKSDzaM3bk31A+OYHu6cPa9P/rFIx8X5d24tHXUpRiiq6/PYI6SQRjUPlB72GjsjoEU8F+ALadHg== dependencies: "@swc/counter" "^0.1.1" "@swc/types" "^0.1.5" optionalDependencies: - "@swc/core-darwin-arm64" "1.3.94" - "@swc/core-darwin-x64" "1.3.94" - "@swc/core-linux-arm-gnueabihf" "1.3.94" - "@swc/core-linux-arm64-gnu" "1.3.94" - "@swc/core-linux-arm64-musl" "1.3.94" - "@swc/core-linux-x64-gnu" "1.3.94" - "@swc/core-linux-x64-musl" "1.3.94" - "@swc/core-win32-arm64-msvc" "1.3.94" - "@swc/core-win32-ia32-msvc" "1.3.94" - "@swc/core-win32-x64-msvc" "1.3.94" + "@swc/core-darwin-arm64" "1.3.102" + "@swc/core-darwin-x64" "1.3.102" + "@swc/core-linux-arm-gnueabihf" "1.3.102" + "@swc/core-linux-arm64-gnu" "1.3.102" + "@swc/core-linux-arm64-musl" "1.3.102" + "@swc/core-linux-x64-gnu" "1.3.102" + "@swc/core-linux-x64-musl" "1.3.102" + "@swc/core-win32-arm64-msvc" "1.3.102" + "@swc/core-win32-ia32-msvc" "1.3.102" + "@swc/core-win32-x64-msvc" "1.3.102" "@swc/counter@^0.1.1": version "0.1.2" - resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.2.tgz#bf06d0770e47c6f1102270b744e17b934586985e" integrity sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw== "@swc/types@^0.1.5": version "0.1.5" - resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.5.tgz#043b731d4f56a79b4897a3de1af35e75d56bc63a" integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== "@tauri-apps/api@^1.5.0": - version "1.5.1" - resolved "https://registry.npmjs.org/@tauri-apps/api/-/api-1.5.1.tgz" - integrity sha512-6unsZDOdlXTmauU3NhWhn+Cx0rODV+rvNvTdvolE5Kls5ybA6cqndQENDt1+FS0tF7ozCP66jwWoH6a5h90BrA== + version "1.5.3" + resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.5.3.tgz#f7b362b1f30aadb0a8bbeb7ae111755c0ed33d73" + integrity sha512-zxnDjHHKjOsrIzZm6nO5Xapb/BxqUq1tc7cGkFXsFkGTsSWgCPH1D8mm0XS9weJY2OaR73I3k3S+b7eSzJDfqA== -"@tauri-apps/cli-darwin-arm64@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.5.tgz#394fbc2920bc84524c8adb0021b1f788495a643f" - integrity sha512-CmKc/PjlI1+oD88VtR1Nr0pmrf/cUU1XFRazU+FB9ChWO3ZPp4MeA+eSemiln0F1XJR9fMJw/QS58IPH4GydLw== +"@tauri-apps/cli-darwin-arm64@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.9.tgz#38c6cc3a82cb5e13d311a0b55507c9e9c830685c" + integrity sha512-7C2Jf8f0gzv778mLYb7Eszqqv1bm9Wzews81MRTqKrUIcC+eZEtDXLex+JaEkEzFEUrgIafdOvMBVEavF030IA== -"@tauri-apps/cli-darwin-x64@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.5.tgz#76f91cdb629d3f2996fe41a9401080baaabd956c" - integrity sha512-d7l/4FB5uWGkMHM08UI6+qk45PAeBYMSC19l0Sz47WrRHQDMIX4V591ydnUg8AffWK/I3r1DJtQmd6C89g7JwQ== +"@tauri-apps/cli-darwin-x64@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.9.tgz#b0bb7958945e74b33e41cc840d6289558d8bc9b1" + integrity sha512-LHKytpkofPYgH8RShWvwDa3hD1ws131x7g7zNasJPfOiCWLqYVQFUuQVmjEUt8+dpHe/P/err5h4z+YZru2d0A== -"@tauri-apps/cli-linux-arm-gnueabihf@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.5.tgz#94cad5de0ce908271768aae931ed455352516b44" - integrity sha512-avFw/BvW01qhXPbzfVPy/KU/FYJ/SUoCe9DP8oA/eSh49VzE9JvlH62iqjtGtA8XzxfTJRezXdCQbrq7OkQHKQ== +"@tauri-apps/cli-linux-arm-gnueabihf@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.9.tgz#610023c827cc71b4d6715cb966112db34bcfde5c" + integrity sha512-teGK20IYKx+dVn8wFq/Lg57Q9ce7foq1KHSfyHi464LVt1T0V1rsmULSgZpQPPj/NYPF5BG78PcWYv64yH86jw== -"@tauri-apps/cli-linux-arm64-gnu@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.5.tgz#9601a48572080796f30621a3723a2bbab5109e29" - integrity sha512-j7yvbZ/IG+W5QtEqK9nSz33lJtaZEFvNnFs0Bxz8r2TjF80m8SdlfxL38R/OVl7xM7ctJWRyM6ws9mBWT0uHNA== +"@tauri-apps/cli-linux-arm64-gnu@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.9.tgz#d2b088d16ee321b7021f62cd873c976690c3da46" + integrity sha512-onJ/DW5Crw38qVx+wquY4uBbfCxVhzhdJmlCYqnYyXsZZmSiPUfSyhV58y+5TYB0q1hG8eYdB5x8VAwzByhGzw== -"@tauri-apps/cli-linux-arm64-musl@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.5.tgz#c99c3612ffe65f4cc603baa821f7d8dcc1afd226" - integrity sha512-neLu3FEYE2IixnqtX10+jsvkJx26kxmh5ekktzjolu5HqV73nquCj7VK/V5uyRMyMQeGEPyhbT09A36DUl+zDA== +"@tauri-apps/cli-linux-arm64-musl@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.9.tgz#433f35e7daa59cc8fb8e0212c370f49c9fbfd598" + integrity sha512-23AYoLD3acakLp9NtheKQDJl8F66eTOflxoPzdJNRy13hUSxb+W9qpz4rRA+CIzkjICFvO2i3UWjeV9QwDVpsQ== -"@tauri-apps/cli-linux-x64-gnu@1.5.5": - version "1.5.5" - resolved "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.5.tgz" - integrity sha512-zZlfklupFaV6RxPze9kQytp1N/K4q/QuYUsgQ5GB/7/OX4EWTUkOpNCeVEocmHag4+9UCQkb1HxdTkXiEVcXEQ== +"@tauri-apps/cli-linux-x64-gnu@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.9.tgz#3d733f9d9706273bdb1f247bbfcca21f2acedcc8" + integrity sha512-9PQA1rE7gh41W2ylyKd5qOGOds55ymaYPml9KOpM0g+cxmCXa+8Wf9K5NKvACnJldJJ6cekWzIyB4eN6o5T+yQ== -"@tauri-apps/cli-linux-x64-musl@1.5.5": - version "1.5.5" - resolved "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.5.tgz" - integrity sha512-2VByWblZnSgLZJyhRxggy528ahcYFH8jboZZ2BUaYT/P5WeJ1lOoQwQj9ssEUrGauGPNS3PmmfBCF7u5oaMRJA== +"@tauri-apps/cli-linux-x64-musl@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.9.tgz#eeb0b55fa7257d7566b873734d2c230dca0f1c6c" + integrity sha512-5hdbNFeDsrJ/pXZ4cSQV4bJwUXPPxXxN3/pAtNUqIph7q+vLcBXOXIMoS64iuyaluJC59lhEwlWZFz+EPv0Hqg== -"@tauri-apps/cli-win32-arm64-msvc@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.5.tgz#44b6fcf0966a3bb9fc163d19ace83e98ee03cdce" - integrity sha512-4UZFHMIJaqgPGT+PHfDDp63OgJsXwLd+0u8x1+2hFMT25dEYj+KzKOVwktYgN6UT9F7rEyzNTTZe7ZZpAkGT5Q== +"@tauri-apps/cli-win32-arm64-msvc@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.9.tgz#f772456d3ef97050e467e88e9d95b6b68b4206e7" + integrity sha512-O18JufjSB3hSJYu5WWByONouGeX7DraLAtXLErsG1r/VS3zHd/zyuzycrVUaObNXk5bfGlIP0Ypt+RvZJILN2w== -"@tauri-apps/cli-win32-ia32-msvc@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.5.tgz#04c7c7eba376a82e2f13c6e3c640cf34c988502c" - integrity sha512-t4XbmMyDtX7kW+wQrlWO4tZus+w77w+Hz5/NBQsjRNnO3lbuYMYaF4IZpt0tZG6lQ0uyvH+o2v5dbZhUTpVT0Q== +"@tauri-apps/cli-win32-ia32-msvc@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.9.tgz#2aa3b267e23d0dd2ece4e183bbd2a85c3e7e4c60" + integrity sha512-FQxtxTZu0JVBihfd/lmpxo7jyMOesjWQehfyVUqtgMfm5+Pvvw0Y+ZioeDi1TZkFVrT3QDYy8R4LqDLSZVMQRA== -"@tauri-apps/cli-win32-x64-msvc@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.5.tgz#cc4f5336e958e5d754a551e5c5df5e32680b89ec" - integrity sha512-7OiUfBmYjQ9LGTvl0Zs567JQIQuxpTCDraca3cpJFV/6TsRLEZAvXo3sgqEFOJopImrCWTpUT4FyzsGC76KlIg== +"@tauri-apps/cli-win32-x64-msvc@1.5.9": + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.9.tgz#3b970c949326f5874cc88a016372cb87485c15a1" + integrity sha512-EeI1+L518cIBLKw0qUFwnLIySBeSmPQjPLIlNwSukHSro4tAQPHycEVGgKrdToiCWgaZJBA0e5aRSds0Du2TWg== "@tauri-apps/cli@^1.5.0": - version "1.5.5" - resolved "https://registry.npmjs.org/@tauri-apps/cli/-/cli-1.5.5.tgz" - integrity sha512-AUFqiA5vbriMd6xWDLWwxzW2FtEhSmL0KcMktkQQGzM+QKFnFbQsubvvd95YDAIX2Q4L1eygGv7ebNX0QVA7sg== + version "1.5.9" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.9.tgz#43391e4b6997483d485d84d97e0d72f648eae581" + integrity sha512-knSt/9AvCTeyfC6wkyeouF9hBW/0Mzuw+5vBKEvzaGPQsfFJo1ZCp5FkdiZpGBBfnm09BhugasGRTGofzatfqQ== optionalDependencies: - "@tauri-apps/cli-darwin-arm64" "1.5.5" - "@tauri-apps/cli-darwin-x64" "1.5.5" - "@tauri-apps/cli-linux-arm-gnueabihf" "1.5.5" - "@tauri-apps/cli-linux-arm64-gnu" "1.5.5" - "@tauri-apps/cli-linux-arm64-musl" "1.5.5" - "@tauri-apps/cli-linux-x64-gnu" "1.5.5" - "@tauri-apps/cli-linux-x64-musl" "1.5.5" - "@tauri-apps/cli-win32-arm64-msvc" "1.5.5" - "@tauri-apps/cli-win32-ia32-msvc" "1.5.5" - "@tauri-apps/cli-win32-x64-msvc" "1.5.5" + "@tauri-apps/cli-darwin-arm64" "1.5.9" + "@tauri-apps/cli-darwin-x64" "1.5.9" + "@tauri-apps/cli-linux-arm-gnueabihf" "1.5.9" + "@tauri-apps/cli-linux-arm64-gnu" "1.5.9" + "@tauri-apps/cli-linux-arm64-musl" "1.5.9" + "@tauri-apps/cli-linux-x64-gnu" "1.5.9" + "@tauri-apps/cli-linux-x64-musl" "1.5.9" + "@tauri-apps/cli-win32-arm64-msvc" "1.5.9" + "@tauri-apps/cli-win32-ia32-msvc" "1.5.9" + "@tauri-apps/cli-win32-x64-msvc" "1.5.9" + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== "@types/json-schema@^7.0.12": - version "7.0.14" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz" - integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw== + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== -"@types/prop-types@*", "@types/prop-types@^15.7.7": - version "15.7.9" - resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz" - integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/react-dom@^18.2.14": - version "18.2.14" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539" - integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ== +"@types/prop-types@*", "@types/prop-types@^15.7.11": + version "15.7.11" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" + integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + +"@types/react-dom@^18.2.15": + version "18.2.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd" + integrity sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw== dependencies: "@types/react" "*" -"@types/react-transition-group@^4.4.7": - version "4.4.8" - resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.8.tgz#46f87d80512959cac793ecc610a93d80ef241ccf" - integrity sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg== +"@types/react-router-dom@^5.3.3": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" "@types/react" "*" -"@types/react@*", "@types/react@^18.2.31": - version "18.2.31" - resolved "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz" - integrity sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g== +"@types/react@*", "@types/react@^18.2.37": + version "18.2.47" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.47.tgz#85074b27ab563df01fbc3f68dc64bf7050b0af40" + integrity sha512-xquNkkOirwyCgoClNk85BjP+aqnIS+ckAJ8i37gAbDs14jfW/J23f2GItAf33oiUPQnqNMALiFeoM9Y5mbjpVQ== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/scheduler@*": - version "0.16.5" - resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz" - integrity sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw== + version "0.16.8" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.8.tgz#ce5ace04cfeabe7ef87c0091e50752e36707deff" + integrity sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A== "@types/semver@^7.5.0": - version "7.5.4" - resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz" - integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== + version "7.5.6" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" + integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== "@typescript-eslint/eslint-plugin@^6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz" - integrity sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw== + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.0.tgz#94b86f3c25b468c714a04bd490017ecec2fd3746" + integrity sha512-3lqEvQUdCozi6d1mddWqd+kf8KxmGq2Plzx36BlkjuQe3rSTm/O98cLf0A4uDO+a5N1KD2SeEEl6fW97YHY+6w== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.8.0" - "@typescript-eslint/type-utils" "6.8.0" - "@typescript-eslint/utils" "6.8.0" - "@typescript-eslint/visitor-keys" "6.8.0" + "@typescript-eslint/scope-manager" "6.18.0" + "@typescript-eslint/type-utils" "6.18.0" + "@typescript-eslint/utils" "6.18.0" + "@typescript-eslint/visitor-keys" "6.18.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -591,98 +633,99 @@ ts-api-utils "^1.0.1" "@typescript-eslint/parser@^6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.8.0.tgz" - integrity sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg== + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.18.0.tgz#d494161d64832e869f0a6acc6000a2cdff858383" + integrity sha512-v6uR68SFvqhNQT41frCMCQpsP+5vySy6IdgjlzUWoo7ALCnpaWYcz/Ij2k4L8cEsL0wkvOviCMpjmtRtHNOKzA== dependencies: - "@typescript-eslint/scope-manager" "6.8.0" - "@typescript-eslint/types" "6.8.0" - "@typescript-eslint/typescript-estree" "6.8.0" - "@typescript-eslint/visitor-keys" "6.8.0" + "@typescript-eslint/scope-manager" "6.18.0" + "@typescript-eslint/types" "6.18.0" + "@typescript-eslint/typescript-estree" "6.18.0" + "@typescript-eslint/visitor-keys" "6.18.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz" - integrity sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g== +"@typescript-eslint/scope-manager@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.18.0.tgz#24ca6fc1f4a2afa71122dcfca9282878687d9997" + integrity sha512-o/UoDT2NgOJ2VfHpfr+KBY2ErWvCySNUIX/X7O9g8Zzt/tXdpfEU43qbNk8LVuWUT2E0ptzTWXh79i74PP0twA== dependencies: - "@typescript-eslint/types" "6.8.0" - "@typescript-eslint/visitor-keys" "6.8.0" + "@typescript-eslint/types" "6.18.0" + "@typescript-eslint/visitor-keys" "6.18.0" -"@typescript-eslint/type-utils@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz" - integrity sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g== +"@typescript-eslint/type-utils@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.18.0.tgz#a492da599da5c38c70aa9ff9bfb473961b8ae663" + integrity sha512-ZeMtrXnGmTcHciJN1+u2CigWEEXgy1ufoxtWcHORt5kGvpjjIlK9MUhzHm4RM8iVy6dqSaZA/6PVkX6+r+ChjQ== dependencies: - "@typescript-eslint/typescript-estree" "6.8.0" - "@typescript-eslint/utils" "6.8.0" + "@typescript-eslint/typescript-estree" "6.18.0" + "@typescript-eslint/utils" "6.18.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.8.0.tgz" - integrity sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ== +"@typescript-eslint/types@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.18.0.tgz#ffce610a1540c17cf7d8ecf2bb34b8b0e2e77101" + integrity sha512-/RFVIccwkwSdW/1zeMx3hADShWbgBxBnV/qSrex6607isYjj05t36P6LyONgqdUrNLl5TYU8NIKdHUYpFvExkA== -"@typescript-eslint/typescript-estree@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz" - integrity sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg== +"@typescript-eslint/typescript-estree@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.0.tgz#1c357c3ca435c3cfa2af6b9daf45ca0bc2bb059a" + integrity sha512-klNvl+Ql4NsBNGB4W9TZ2Od03lm7aGvTbs0wYaFYsplVPhr+oeXjlPZCDI4U9jgJIDK38W1FKhacCFzCC+nbIg== dependencies: - "@typescript-eslint/types" "6.8.0" - "@typescript-eslint/visitor-keys" "6.8.0" + "@typescript-eslint/types" "6.18.0" + "@typescript-eslint/visitor-keys" "6.18.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" + minimatch "9.0.3" semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.8.0.tgz" - integrity sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q== +"@typescript-eslint/utils@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.18.0.tgz#4d07c9c08f84b9939a1aca7aef98c8f378936142" + integrity sha512-wiKKCbUeDPGaYEYQh1S580dGxJ/V9HI7K5sbGAVklyf+o5g3O+adnS4UNJajplF4e7z2q0uVBaTdT/yLb4XAVA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.8.0" - "@typescript-eslint/types" "6.8.0" - "@typescript-eslint/typescript-estree" "6.8.0" + "@typescript-eslint/scope-manager" "6.18.0" + "@typescript-eslint/types" "6.18.0" + "@typescript-eslint/typescript-estree" "6.18.0" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.8.0": - version "6.8.0" - resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz" - integrity sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg== +"@typescript-eslint/visitor-keys@6.18.0": + version "6.18.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.0.tgz#3c8733737786fa6c78a347b4fa306ae7155b560f" + integrity sha512-1wetAlSZpewRDb2h9p/Q8kRjdGuqdTAQbkJIOUMLug2LBLG+QOjiWoSj6/3B/hA9/tVTFFdtiKvAYoYnSRW/RA== dependencies: - "@typescript-eslint/types" "6.8.0" + "@typescript-eslint/types" "6.18.0" eslint-visitor-keys "^3.4.1" "@ungap/structured-clone@^1.2.0": version "1.2.0" - resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== "@vitejs/plugin-react-swc@^3.3.2": - version "3.4.0" - resolved "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.0.tgz" - integrity sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ== + version "3.5.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz#1fadff5148003e8091168c431e44c850f9a39e74" + integrity sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig== dependencies: - "@swc/core" "^1.3.85" + "@swc/core" "^1.3.96" acorn-jsx@^5.3.2: version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn@^8.9.0: - version "8.10.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz" - integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== ajv@^6.12.4: version "6.12.6" - resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== dependencies: fast-deep-equal "^3.1.1" @@ -692,24 +735,34 @@ ajv@^6.12.4: ansi-regex@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^4.1.0: +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== dependencies: color-convert "^2.0.1" +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + any-promise@^1.0.0: version "1.3.0" - resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== anymatch@~3.1.2: version "3.1.3" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== dependencies: normalize-path "^3.0.0" @@ -717,25 +770,32 @@ anymatch@~3.1.2: arg@^5.0.2: version "5.0.2" - resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" + resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== argparse@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +aria-query@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.3.0.tgz#650c569e41ad90b51b3d7df5e5eed1c7549c103e" + integrity sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A== + dependencies: + dequal "^2.0.3" + array-buffer-byte-length@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== dependencies: call-bind "^1.0.2" is-array-buffer "^3.0.1" -array-includes@^3.1.6: +array-includes@^3.1.6, array-includes@^3.1.7: version "3.1.7" - resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.7.tgz#8cd2e01b26f7a3086cbc87271593fe921c62abda" integrity sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ== dependencies: call-bind "^1.0.2" @@ -746,12 +806,23 @@ array-includes@^3.1.6: array-union@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@^1.3.1: +array.prototype.findlastindex@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: version "1.3.2" - resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== dependencies: call-bind "^1.0.2" @@ -759,9 +830,9 @@ array.prototype.flat@^1.3.1: es-abstract "^1.22.1" es-shim-unscopables "^1.0.0" -array.prototype.flatmap@^1.3.1: +array.prototype.flatmap@^1.3.1, array.prototype.flatmap@^1.3.2: version "1.3.2" - resolved "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz#c9a7c6831db8e719d6ce639190146c24bbd3e527" integrity sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ== dependencies: call-bind "^1.0.2" @@ -771,7 +842,7 @@ array.prototype.flatmap@^1.3.1: array.prototype.tosorted@^1.1.1: version "1.1.2" - resolved "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.2.tgz#620eff7442503d66c799d95503f82b475745cefd" integrity sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg== dependencies: call-bind "^1.0.2" @@ -782,7 +853,7 @@ array.prototype.tosorted@^1.1.1: arraybuffer.prototype.slice@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== dependencies: array-buffer-byte-length "^1.0.0" @@ -793,16 +864,21 @@ arraybuffer.prototype.slice@^1.0.2: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" +ast-types-flow@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.8.tgz#0a85e1c92695769ac13a428bb653e7538bea27d6" + integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== + asynciterator.prototype@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== dependencies: has-symbols "^1.0.3" autoprefixer@^10.4.16: version "10.4.16" - resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.16.tgz#fad1411024d8670880bdece3970aa72e3572feb8" integrity sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ== dependencies: browserslist "^4.21.10" @@ -814,47 +890,66 @@ autoprefixer@^10.4.16: available-typed-arrays@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== +axe-core@=4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.7.0.tgz#34ba5a48a8b564f67e103f0aa5768d76e15bbbbf" + integrity sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ== + +axobject-query@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.2.1.tgz#39c378a6e3b06ca679f29138151e45b2b32da62a" + integrity sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg== + dependencies: + dequal "^2.0.3" + balanced-match@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== binary-extensions@^2.0.0: version "2.2.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== brace-expansion@^1.1.7: version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2, braces@~3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" browserslist@^4.21.10: - version "4.22.1" - resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== + version "4.22.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.2.tgz#704c4943072bd81ea18997f3bd2180e89c77874b" + integrity sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A== dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" + caniuse-lite "^1.0.30001565" + electron-to-chromium "^1.4.601" + node-releases "^2.0.14" update-browserslist-db "^1.0.13" call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== dependencies: function-bind "^1.1.2" @@ -863,22 +958,22 @@ call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: callsites@^3.0.0: version "3.1.0" - resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== camelcase-css@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541: - version "1.0.30001553" - resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001553.tgz" - integrity sha512-N0ttd6TrFfuqKNi+pMgWJTb9qrdJu4JSpgPFLe/lrD19ugC6fZgF0pUewRowDwzdDnb9V41mFcdlYgl/PyKf4A== +caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001565: + version "1.0.30001576" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001576.tgz#893be772cf8ee6056d6c1e2d07df365b9ec0a5c4" + integrity sha512-ff5BdakGe2P3SQsMsiqmt1Lc8221NR1VzHj5jXN5vBny9A6fpze94HiVV/n7XRosOlsShJcvMv5mdnpjOGCEgg== chalk@^4.0.0: version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== dependencies: ansi-styles "^4.1.0" @@ -886,7 +981,7 @@ chalk@^4.0.0: chokidar@^3.5.3: version "3.5.3" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" @@ -899,36 +994,41 @@ chokidar@^3.5.3: optionalDependencies: fsevents "~2.3.2" +client-only@^0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1" + integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== + clsx@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b" - integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + version "2.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.0.tgz#e851283bcb5c80ee7608db18487433f7b23f77cb" + integrity sha512-m3iNNWpd9rl3jvvcBnu70ylMdrXt8Vlq4HYadnU5fwcOtvkSQWPmj7amUcDT2qYI7risszBjI5AUIUox9D16pg== color-convert@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== dependencies: color-name "~1.1.4" color-name@~1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== commander@^4.0.0: version "4.1.1" - resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== concat-map@0.0.1: version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -cross-spawn@^7.0.2: +cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" - resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== dependencies: path-key "^3.1.0" @@ -937,91 +1037,128 @@ cross-spawn@^7.0.2: cssesc@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== -csstype@^3.0.2, csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== +csstype@^3.0.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +damerau-levenshtein@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" + integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== + +dayjs@^1.11.10: + version "1.11.10" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0" + integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ== + +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" deep-is@^0.1.3: version "0.1.4" - resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== define-data-property@^1.0.1, define-data-property@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== dependencies: get-intrinsic "^1.2.1" gopd "^1.0.1" has-property-descriptors "^1.0.0" -define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== dependencies: define-data-property "^1.0.1" has-property-descriptors "^1.0.0" object-keys "^1.1.1" +dequal@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + didyoumean@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== dir-glob@^3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== dependencies: path-type "^4.0.0" dlv@^1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== doctrine@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== dependencies: esutils "^2.0.2" doctrine@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== dependencies: esutils "^2.0.2" -dom-helpers@^5.0.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.535: - version "1.4.563" - resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.563.tgz" - integrity sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw== +electron-to-chromium@^1.4.601: + version "1.4.623" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.623.tgz#0f7400114ac3425500e9244d2b0e9c3107c331cb" + integrity sha512-lKoz10iCYlP1WtRYdh5MvocQPWVRoI7ysp6qf18bmeBgR8abE6+I2CsfyNKztRDZvhdWc+krKT6wS7Neg8sw3A== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + +enhanced-resolve@^5.12.0: + version "5.15.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" + integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" es-abstract@^1.22.1: version "1.22.3" - resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== dependencies: array-buffer-byte-length "^1.0.0" @@ -1064,9 +1201,9 @@ es-abstract@^1.22.1: unbox-primitive "^1.0.2" which-typed-array "^1.1.13" -es-iterator-helpers@^1.0.12: +es-iterator-helpers@^1.0.12, es-iterator-helpers@^1.0.15: version "1.0.15" - resolved "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz" + resolved "https://registry.yarnpkg.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== dependencies: asynciterator.prototype "^1.0.0" @@ -1086,7 +1223,7 @@ es-iterator-helpers@^1.0.12: es-set-tostringtag@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== dependencies: get-intrinsic "^1.2.2" @@ -1095,62 +1232,137 @@ es-set-tostringtag@^2.0.1: es-shim-unscopables@^1.0.0: version "1.0.2" - resolved "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763" integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw== dependencies: hasown "^2.0.0" es-to-primitive@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== dependencies: is-callable "^1.1.4" is-date-object "^1.0.1" is-symbol "^1.0.2" -esbuild@^0.18.10: - version "0.18.20" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz" - integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== +esbuild@^0.19.3: + version "0.19.11" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.11.tgz#4a02dca031e768b5556606e1b468fe72e3325d96" + integrity sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA== optionalDependencies: - "@esbuild/android-arm" "0.18.20" - "@esbuild/android-arm64" "0.18.20" - "@esbuild/android-x64" "0.18.20" - "@esbuild/darwin-arm64" "0.18.20" - "@esbuild/darwin-x64" "0.18.20" - "@esbuild/freebsd-arm64" "0.18.20" - "@esbuild/freebsd-x64" "0.18.20" - "@esbuild/linux-arm" "0.18.20" - "@esbuild/linux-arm64" "0.18.20" - "@esbuild/linux-ia32" "0.18.20" - "@esbuild/linux-loong64" "0.18.20" - "@esbuild/linux-mips64el" "0.18.20" - "@esbuild/linux-ppc64" "0.18.20" - "@esbuild/linux-riscv64" "0.18.20" - "@esbuild/linux-s390x" "0.18.20" - "@esbuild/linux-x64" "0.18.20" - "@esbuild/netbsd-x64" "0.18.20" - "@esbuild/openbsd-x64" "0.18.20" - "@esbuild/sunos-x64" "0.18.20" - "@esbuild/win32-arm64" "0.18.20" - "@esbuild/win32-ia32" "0.18.20" - "@esbuild/win32-x64" "0.18.20" + "@esbuild/aix-ppc64" "0.19.11" + "@esbuild/android-arm" "0.19.11" + "@esbuild/android-arm64" "0.19.11" + "@esbuild/android-x64" "0.19.11" + "@esbuild/darwin-arm64" "0.19.11" + "@esbuild/darwin-x64" "0.19.11" + "@esbuild/freebsd-arm64" "0.19.11" + "@esbuild/freebsd-x64" "0.19.11" + "@esbuild/linux-arm" "0.19.11" + "@esbuild/linux-arm64" "0.19.11" + "@esbuild/linux-ia32" "0.19.11" + "@esbuild/linux-loong64" "0.19.11" + "@esbuild/linux-mips64el" "0.19.11" + "@esbuild/linux-ppc64" "0.19.11" + "@esbuild/linux-riscv64" "0.19.11" + "@esbuild/linux-s390x" "0.19.11" + "@esbuild/linux-x64" "0.19.11" + "@esbuild/netbsd-x64" "0.19.11" + "@esbuild/openbsd-x64" "0.19.11" + "@esbuild/sunos-x64" "0.19.11" + "@esbuild/win32-arm64" "0.19.11" + "@esbuild/win32-ia32" "0.19.11" + "@esbuild/win32-x64" "0.19.11" escalade@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== escape-string-regexp@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz" - integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== + +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-typescript@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" + integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== + dependencies: + debug "^4.3.4" + enhanced-resolve "^5.12.0" + eslint-module-utils "^2.7.4" + fast-glob "^3.3.1" + get-tsconfig "^4.5.0" + is-core-module "^2.11.0" + is-glob "^4.0.3" + +eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-import@^2.29.0: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + +eslint-plugin-jsx-a11y@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz#2fa9c701d44fcd722b7c771ec322432857fcbad2" + integrity sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA== + dependencies: + "@babel/runtime" "^7.23.2" + aria-query "^5.3.0" + array-includes "^3.1.7" + array.prototype.flatmap "^1.3.2" + ast-types-flow "^0.0.8" + axe-core "=4.7.0" + axobject-query "^3.2.1" + damerau-levenshtein "^1.0.8" + emoji-regex "^9.2.2" + es-iterator-helpers "^1.0.15" + hasown "^2.0.0" + jsx-ast-utils "^3.3.5" + language-tags "^1.0.9" + minimatch "^3.1.2" + object.entries "^1.1.7" + object.fromentries "^2.0.7" eslint-plugin-react-hooks@^4.6.0: version "4.6.0" @@ -1159,7 +1371,7 @@ eslint-plugin-react-hooks@^4.6.0: eslint-plugin-react@^7.33.2: version "7.33.2" - resolved "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== dependencies: array-includes "^3.1.6" @@ -1181,7 +1393,7 @@ eslint-plugin-react@^7.33.2: eslint-scope@^7.2.2: version "7.2.2" - resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" @@ -1189,18 +1401,18 @@ eslint-scope@^7.2.2: eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" - resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== eslint@^8.52.0: - version "8.52.0" - resolved "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz" - integrity sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg== + version "8.56.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" + integrity sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.2" - "@eslint/js" "8.52.0" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.56.0" "@humanwhocodes/config-array" "^0.11.13" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -1238,7 +1450,7 @@ eslint@^8.52.0: espree@^9.6.0, espree@^9.6.1: version "9.6.1" - resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== dependencies: acorn "^8.9.0" @@ -1247,37 +1459,37 @@ espree@^9.6.0, espree@^9.6.1: esquery@^1.4.2: version "1.5.0" - resolved "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== dependencies: estraverse "^5.1.0" esrecurse@^4.3.0: version "4.3.0" - resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== dependencies: estraverse "^5.2.0" estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" - resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== esutils@^2.0.2: version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" - resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.12, fast-glob@^3.2.9: - version "3.3.1" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" - integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== +fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" @@ -1287,47 +1499,47 @@ fast-glob@^3.2.12, fast-glob@^3.2.9: fast-json-stable-stringify@^2.0.0: version "2.1.0" - resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6: version "2.0.6" - resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fastq@^1.6.0: - version "1.15.0" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz" - integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw== + version "1.16.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.16.0.tgz#83b9a9375692db77a822df081edb6a9cf6839320" + integrity sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA== dependencies: reusify "^1.0.4" file-entry-cache@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== dependencies: flat-cache "^3.0.4" fill-range@^7.0.1: version "7.0.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== dependencies: to-regex-range "^5.0.1" find-up@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== dependencies: locate-path "^6.0.0" path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.1.1" - resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.1.tgz" - integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: flatted "^3.2.9" keyv "^4.5.3" @@ -1335,39 +1547,47 @@ flat-cache@^3.0.4: flatted@^3.2.9: version "3.2.9" - resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== for-each@^0.3.3: version "0.3.3" - resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz" + resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== dependencies: is-callable "^1.1.3" +foreground-child@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" + integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + fraction.js@^4.3.6: version "4.3.7" - resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== fs.realpath@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@~2.3.2: +fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: version "1.1.6" - resolved "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== dependencies: call-bind "^1.0.2" @@ -1377,12 +1597,12 @@ function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: functions-have-names@^1.2.3: version "1.2.3" - resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: version "1.2.2" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== dependencies: function-bind "^1.1.2" @@ -1392,41 +1612,47 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ get-symbol-description@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: call-bind "^1.0.2" get-intrinsic "^1.1.1" +get-tsconfig@^4.5.0: + version "4.7.2" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.2.tgz#0dcd6fb330391d46332f4c6c1bf89a6514c2ddce" + integrity sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A== + dependencies: + resolve-pkg-maps "^1.0.0" + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" glob-parent@^6.0.2: version "6.0.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^10.3.10: + version "10.3.10" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" + integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" + foreground-child "^3.1.0" + jackspeak "^2.3.5" + minimatch "^9.0.1" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-scurry "^1.10.1" glob@^7.1.3: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -1437,22 +1663,22 @@ glob@^7.1.3: path-is-absolute "^1.0.0" globals@^13.19.0: - version "13.23.0" - resolved "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz" - integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA== + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" globalthis@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== dependencies: define-properties "^1.1.3" globby@^11.1.0: version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== dependencies: array-union "^2.1.0" @@ -1464,65 +1690,84 @@ globby@^11.1.0: gopd@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== dependencies: get-intrinsic "^1.1.3" +graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + graphemer@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== has-flag@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== has-property-descriptors@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340" integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg== dependencies: get-intrinsic "^1.2.2" has-proto@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0" integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg== has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== has-tostringtag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== dependencies: has-symbols "^1.0.2" hasown@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== dependencies: function-bind "^1.1.2" +html-parse-stringify@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2" + integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== + dependencies: + void-elements "3.1.0" + +i18next@^23.7.6: + version "23.7.16" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.7.16.tgz#7026d18b7a3ac9e2ecfeb78da5e4da5ca33312ef" + integrity sha512-SrqFkMn9W6Wb43ZJ9qrO6U2U4S80RsFMA7VYFSqp7oc7RllQOYDCdRfsse6A7Cq/V8MnpxKvJCYgM8++27n4Fw== + dependencies: + "@babel/runtime" "^7.23.2" + ignore@^5.2.0, ignore@^5.2.4: - version "5.2.4" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz" - integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ== + version "5.3.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" + integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== import-fresh@^3.2.1: version "3.3.0" - resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== dependencies: parent-module "^1.0.0" @@ -1530,12 +1775,12 @@ import-fresh@^3.2.1: imurmurhash@^0.1.4: version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== inflight@^1.0.4: version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" @@ -1543,12 +1788,12 @@ inflight@^1.0.4: inherits@2: version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== internal-slot@^1.0.5: version "1.0.6" - resolved "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== dependencies: get-intrinsic "^1.2.2" @@ -1557,7 +1802,7 @@ internal-slot@^1.0.5: is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== dependencies: call-bind "^1.0.2" @@ -1566,28 +1811,28 @@ is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: is-async-function@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-async-function/-/is-async-function-2.0.0.tgz#8e4418efd3e5d3a6ebb0164c05ef5afb69aa9646" integrity sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA== dependencies: has-tostringtag "^1.0.0" is-bigint@^1.0.1: version "1.0.4" - resolved "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== dependencies: has-bigints "^1.0.1" is-binary-path@~2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== dependencies: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: version "1.1.2" - resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== dependencies: call-bind "^1.0.2" @@ -1595,79 +1840,84 @@ is-boolean-object@^1.1.0: is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" - resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0: +is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: version "2.13.1" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: hasown "^2.0.0" is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" - resolved "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== dependencies: has-tostringtag "^1.0.0" is-extglob@^2.1.1: version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-finalizationregistry@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz#c8749b65f17c133313e661b1289b95ad3dbd62e6" integrity sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw== dependencies: call-bind "^1.0.2" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-generator-function@^1.0.10: version "1.0.10" - resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.10.tgz#f1558baf1ac17e0deea7c0415c438351ff2b3c72" integrity sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A== dependencies: has-tostringtag "^1.0.0" is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" is-map@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== is-negative-zero@^2.0.2: version "2.0.2" - resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== is-number-object@^1.0.4: version "1.0.7" - resolved "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== dependencies: has-tostringtag "^1.0.0" is-number@^7.0.0: version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== is-path-inside@^3.0.3: version "3.0.3" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== is-regex@^1.1.4: version "1.1.4" - resolved "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== dependencies: call-bind "^1.0.2" @@ -1675,52 +1925,52 @@ is-regex@^1.1.4: is-set@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== is-shared-array-buffer@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== dependencies: call-bind "^1.0.2" is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== dependencies: has-tostringtag "^1.0.0" is-symbol@^1.0.2, is-symbol@^1.0.3: version "1.0.4" - resolved "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: has-symbols "^1.0.2" is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: version "1.1.12" - resolved "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: which-typed-array "^1.1.11" is-weakmap@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2" integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA== is-weakref@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== dependencies: call-bind "^1.0.2" is-weakset@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.2.tgz#4569d67a747a1ce5a994dfd4ef6dcea76e7c0a1d" integrity sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg== dependencies: call-bind "^1.0.2" @@ -1728,17 +1978,17 @@ is-weakset@^2.0.1: isarray@^2.0.5: version "2.0.5" - resolved "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== isexe@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== iterator.prototype@^1.1.2: version "1.1.2" - resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz" + resolved "https://registry.yarnpkg.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== dependencies: define-properties "^1.2.1" @@ -1747,41 +1997,57 @@ iterator.prototype@^1.1.2: reflect.getprototypeof "^1.0.4" set-function-name "^2.0.1" -jiti@^1.18.2: - version "1.20.0" - resolved "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz" - integrity sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA== +jackspeak@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" + integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + +jiti@^1.19.1: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== "js-tokens@^3.0.0 || ^4.0.0": version "4.0.0" - resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" json-buffer@3.0.1: version "3.0.1" - resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== json-schema-traverse@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -"jsx-ast-utils@^2.4.1 || ^3.0.0": +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5: version "3.3.5" - resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" integrity sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ== dependencies: array-includes "^3.1.6" @@ -1791,173 +2057,227 @@ json-stable-stringify-without-jsonify@^1.0.1: keyv@^4.5.3: version "4.5.4" - resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== dependencies: json-buffer "3.0.1" +language-subtag-registry@^0.3.20: + version "0.3.22" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" + integrity sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w== + +language-tags@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.9.tgz#1ffdcd0ec0fafb4b1be7f8b11f306ad0f9c08777" + integrity sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA== + dependencies: + language-subtag-registry "^0.3.20" + levn@^0.4.1: version "0.4.1" - resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== dependencies: prelude-ls "^1.2.1" type-check "~0.4.0" -lilconfig@^2.0.5, lilconfig@^2.1.0: +lilconfig@^2.1.0: version "2.1.0" - resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== +lilconfig@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.0.0.tgz#f8067feb033b5b74dab4602a5f5029420be749bc" + integrity sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g== + lines-and-columns@^1.1.6: version "1.2.4" - resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== locate-path@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== dependencies: p-locate "^5.0.0" lodash.merge@^4.6.2: version "4.6.2" - resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" lru-cache@^6.0.0: version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" +"lru-cache@^9.1.1 || ^10.0.0": + version "10.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.1.0.tgz#2098d41c2dc56500e6c88584aa656c84de7d0484" + integrity sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== dependencies: braces "^3.0.2" picomatch "^2.3.1" -minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@9.0.3, minimatch@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== dependencies: brace-expansion "^1.1.7" +minimist@^1.2.0, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0": + version "7.0.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" + integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== + ms@2.1.2: version "2.1.2" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + mz@^2.7.0: version "2.7.0" - resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== dependencies: any-promise "^1.0.0" object-assign "^4.0.1" thenify-all "^1.0.0" -nanoid@^3.3.6: - version "3.3.6" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz" - integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== natural-compare@^1.4.0: version "1.4.0" - resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-range@^0.1.2: version "0.1.2" - resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== object-hash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== object-inspect@^1.13.1, object-inspect@^1.9.0: version "1.13.1" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== object-keys@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + version "4.1.5" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" + integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" + call-bind "^1.0.5" + define-properties "^1.2.1" has-symbols "^1.0.3" object-keys "^1.1.1" -object.entries@^1.1.6: +object.entries@^1.1.6, object.entries@^1.1.7: version "1.1.7" - resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.7.tgz" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.7.tgz#2b47760e2a2e3a752f39dd874655c61a7f03c131" integrity sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" es-abstract "^1.22.1" -object.fromentries@^2.0.6: +object.fromentries@^2.0.6, object.fromentries@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.7.tgz#71e95f441e9a0ea6baf682ecaaf37fa2a8d7e616" integrity sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" es-abstract "^1.22.1" +object.groupby@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + object.hasown@^1.1.2: version "1.1.3" - resolved "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== dependencies: define-properties "^1.2.0" es-abstract "^1.22.1" -object.values@^1.1.6: +object.values@^1.1.6, object.values@^1.1.7: version "1.1.7" - resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.7.tgz#617ed13272e7e1071b43973aa1655d9291b8442a" integrity sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng== dependencies: call-bind "^1.0.2" @@ -1966,14 +2286,14 @@ object.values@^1.1.6: once@^1.3.0: version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" optionator@^0.9.3: version "0.9.3" - resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: "@aashutoshrathi/word-wrap" "^1.2.3" @@ -1985,136 +2305,153 @@ optionator@^0.9.3: p-limit@^3.0.2: version "3.1.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== dependencies: yocto-queue "^0.1.0" p-locate@^5.0.0: version "5.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== dependencies: p-limit "^3.0.2" parent-module@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== dependencies: callsites "^3.0.0" path-exists@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== path-is-absolute@^1.0.0: version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-key@^3.1.0: version "3.1.1" - resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" + integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== + dependencies: + lru-cache "^9.1.1 || ^10.0.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-type@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== pify@^2.3.0: version "2.3.0" - resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== pirates@^4.0.1: version "4.0.6" - resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== postcss-import@^15.1.0: version "15.1.0" - resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== dependencies: postcss-value-parser "^4.0.0" read-cache "^1.0.0" resolve "^1.1.7" +postcss-import@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-16.0.0.tgz#2be1c78391b3f43f129fccfe5cc0cc1a11baef54" + integrity sha512-e77lhVvrD1I2y7dYmBv0k9ULTdArgEYZt97T4w6sFIU5uxIHvDFQlKgUUyY7v7Barj0Yf/zm5A4OquZN7jKm5Q== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + postcss-js@^4.0.1: version "4.0.1" - resolved "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz" + resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== dependencies: camelcase-css "^2.0.1" postcss-load-config@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz" - integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== + version "4.0.2" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz#7159dcf626118d33e299f485d6afe4aff7c4a3e3" + integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ== dependencies: - lilconfig "^2.0.5" - yaml "^2.1.1" + lilconfig "^3.0.0" + yaml "^2.3.4" postcss-nested@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== dependencies: postcss-selector-parser "^6.0.11" postcss-selector-parser@^6.0.11: - version "6.0.13" - resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz" - integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== + version "6.0.15" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz#11cc2b21eebc0b99ea374ffb9887174855a01535" + integrity sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: version "4.2.0" - resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.23, postcss@^8.4.27, postcss@^8.4.31: - version "8.4.31" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" - integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== +postcss@^8.4.23, postcss@^8.4.31, postcss@^8.4.32: + version "8.4.33" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742" + integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg== dependencies: - nanoid "^3.3.6" + nanoid "^3.3.7" picocolors "^1.0.0" source-map-js "^1.0.2" prelude-ls@^1.2.1: version "1.2.1" - resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz" - integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg== + version "3.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.1.1.tgz#6ba9f23165d690b6cbdaa88cb0807278f7019848" + integrity sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw== -prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.8.1: version "15.8.1" - resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== dependencies: loose-envify "^1.4.0" @@ -2122,67 +2459,80 @@ prop-types@^15.6.2, prop-types@^15.8.1: react-is "^16.13.1" punycode@^2.1.0: - version "2.3.0" - resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz" - integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA== + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== queue-microtask@^1.2.2: version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== react-dom@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" scheduler "^0.23.0" +react-i18next@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-14.0.0.tgz#eb39d2245fd1024237828c955f770e409a1ccb12" + integrity sha512-OCrS8rHNAmnr8ggGRDxjakzihrMW7HCbsplduTm3EuuQ6fyvWGT41ksZpqbduYoqJurBmEsEVZ1pILSUWkHZng== + dependencies: + "@babel/runtime" "^7.22.5" + html-parse-stringify "^3.0.1" + react-is@^16.13.1: version "16.13.1" - resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== react-is@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-transition-group@^4.4.5: - version "4.4.5" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" - integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== +react-router-dom@^6.18.0: + version "6.21.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.21.1.tgz#58b459d2fe1841388c95bb068f85128c45e27349" + integrity sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA== dependencies: - "@babel/runtime" "^7.5.5" - dom-helpers "^5.0.1" - loose-envify "^1.4.0" - prop-types "^15.6.2" + "@remix-run/router" "1.14.1" + react-router "6.21.1" + +react-router@6.21.1: + version "6.21.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.21.1.tgz#8db7ee8d7cfc36513c9a66b44e0897208c33be34" + integrity sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA== + dependencies: + "@remix-run/router" "1.14.1" react@^18.2.0: version "18.2.0" - resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" read-cache@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== dependencies: pify "^2.3.0" readdirp@~3.6.0: version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" reflect.getprototypeof@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== dependencies: call-bind "^1.0.2" @@ -2193,13 +2543,13 @@ reflect.getprototypeof@^1.0.4: which-builtin-type "^1.1.3" regenerator-runtime@^0.14.0: - version "0.14.0" - resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz" - integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: version "1.5.1" - resolved "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== dependencies: call-bind "^1.0.2" @@ -2208,12 +2558,17 @@ regexp.prototype.flags@^1.5.0, regexp.prototype.flags@^1.5.1: resolve-from@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== -resolve@^1.1.7, resolve@^1.22.2: +resolve-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.1.7, resolve@^1.22.2, resolve@^1.22.4: version "1.22.8" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== dependencies: is-core-module "^2.13.0" @@ -2222,7 +2577,7 @@ resolve@^1.1.7, resolve@^1.22.2: resolve@^2.0.0-next.4: version "2.0.0-next.5" - resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.5.tgz#6b0ec3107e671e52b68cd068ef327173b90dc03c" integrity sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA== dependencies: is-core-module "^2.13.0" @@ -2231,33 +2586,48 @@ resolve@^2.0.0-next.4: reusify@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== rimraf@^3.0.2: version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== dependencies: glob "^7.1.3" -rollup@^3.27.1: - version "3.29.4" - resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz" - integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== +rollup@^4.2.0: + version "4.9.4" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.4.tgz#37bc0c09ae6b4538a9c974f4d045bb64b2e7c27c" + integrity sha512-2ztU7pY/lrQyXSCnnoU4ICjT/tCG9cdH3/G25ERqE3Lst6vl2BCM5hL2Nw+sslAvAf+ccKsAq1SkKQALyqhR7g== + dependencies: + "@types/estree" "1.0.5" optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.9.4" + "@rollup/rollup-android-arm64" "4.9.4" + "@rollup/rollup-darwin-arm64" "4.9.4" + "@rollup/rollup-darwin-x64" "4.9.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.9.4" + "@rollup/rollup-linux-arm64-gnu" "4.9.4" + "@rollup/rollup-linux-arm64-musl" "4.9.4" + "@rollup/rollup-linux-riscv64-gnu" "4.9.4" + "@rollup/rollup-linux-x64-gnu" "4.9.4" + "@rollup/rollup-linux-x64-musl" "4.9.4" + "@rollup/rollup-win32-arm64-msvc" "4.9.4" + "@rollup/rollup-win32-ia32-msvc" "4.9.4" + "@rollup/rollup-win32-x64-msvc" "4.9.4" fsevents "~2.3.2" run-parallel@^1.1.9: version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== dependencies: queue-microtask "^1.2.2" safe-array-concat@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== dependencies: call-bind "^1.0.2" @@ -2267,7 +2637,7 @@ safe-array-concat@^1.0.1: safe-regex-test@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== dependencies: call-bind "^1.0.2" @@ -2276,26 +2646,26 @@ safe-regex-test@^1.0.0: scheduler@^0.23.0: version "0.23.0" - resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" semver@^6.3.1: version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== semver@^7.5.4: version "7.5.4" - resolved "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" set-function-length@^1.1.1: version "1.1.1" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== dependencies: define-data-property "^1.1.1" @@ -2305,7 +2675,7 @@ set-function-length@^1.1.1: set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" - resolved "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz" + resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== dependencies: define-data-property "^1.0.1" @@ -2314,38 +2684,62 @@ set-function-name@^2.0.0, set-function-name@^2.0.1: shebang-command@^2.0.0: version "2.0.0" - resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== dependencies: shebang-regex "^3.0.0" shebang-regex@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== side-channel@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== dependencies: call-bind "^1.0.0" get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + slash@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== source-map-js@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0: + name string-width-cjs + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.matchall@^4.0.8: version "4.0.10" - resolved "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz#a1553eb532221d4180c51581d6072cd65d1ee100" integrity sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ== dependencies: call-bind "^1.0.2" @@ -2360,7 +2754,7 @@ string.prototype.matchall@^4.0.8: string.prototype.trim@^1.2.8: version "1.2.8" - resolved "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== dependencies: call-bind "^1.0.2" @@ -2369,7 +2763,7 @@ string.prototype.trim@^1.2.8: string.prototype.trimend@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== dependencies: call-bind "^1.0.2" @@ -2378,38 +2772,45 @@ string.prototype.trimend@^1.0.7: string.prototype.trimstart@^1.0.7: version "1.0.7" - resolved "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== dependencies: call-bind "^1.0.2" define-properties "^1.2.0" es-abstract "^1.22.1" -strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-json-comments@^3.1.1: version "3.1.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -stylis@4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" - integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== - sucrase@^3.32.0: - version "3.34.0" - resolved "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz" - integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + version "3.35.0" + resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.35.0.tgz#57f17a3d7e19b36d8995f06679d121be914ae263" + integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA== dependencies: "@jridgewell/gen-mapping" "^0.3.2" commander "^4.0.0" - glob "7.1.6" + glob "^10.3.10" lines-and-columns "^1.1.6" mz "^2.7.0" pirates "^4.0.1" @@ -2417,30 +2818,30 @@ sucrase@^3.32.0: supports-color@^7.1.0: version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== tailwindcss@^3.3.3: - version "3.3.3" - resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz" - integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== + version "3.4.1" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.1.tgz#f512ca5d1dd4c9503c7d3d28a968f1ad8f5c839d" + integrity sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" chokidar "^3.5.3" didyoumean "^1.2.2" dlv "^1.1.3" - fast-glob "^3.2.12" + fast-glob "^3.3.0" glob-parent "^6.0.2" is-glob "^4.0.3" - jiti "^1.18.2" + jiti "^1.19.1" lilconfig "^2.1.0" micromatch "^4.0.5" normalize-path "^3.0.0" @@ -2455,57 +2856,72 @@ tailwindcss@^3.3.3: resolve "^1.22.2" sucrase "^3.32.0" +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + text-table@^0.2.0: version "0.2.0" - resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== thenify-all@^1.0.0: version "1.6.0" - resolved "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== dependencies: thenify ">= 3.1.0 < 4" "thenify@>= 3.1.0 < 4": version "3.3.1" - resolved "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== dependencies: any-promise "^1.0.0" to-regex-range@^5.0.1: version "5.0.1" - resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== dependencies: is-number "^7.0.0" ts-api-utils@^1.0.1: version "1.0.3" - resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== ts-interface-checker@^0.1.9: version "0.1.13" - resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz" + resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" - resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== dependencies: prelude-ls "^1.2.1" type-fest@^0.20.2: version "0.20.2" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== typed-array-buffer@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== dependencies: call-bind "^1.0.2" @@ -2514,7 +2930,7 @@ typed-array-buffer@^1.0.0: typed-array-byte-length@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== dependencies: call-bind "^1.0.2" @@ -2524,7 +2940,7 @@ typed-array-byte-length@^1.0.0: typed-array-byte-offset@^1.0.0: version "1.0.0" - resolved "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz" + resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== dependencies: available-typed-arrays "^1.0.5" @@ -2535,7 +2951,7 @@ typed-array-byte-offset@^1.0.0: typed-array-length@^1.0.4: version "1.0.4" - resolved "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== dependencies: call-bind "^1.0.2" @@ -2543,13 +2959,13 @@ typed-array-length@^1.0.4: is-typed-array "^1.1.9" typescript@^5.0.2: - version "5.2.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== unbox-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== dependencies: call-bind "^1.0.2" @@ -2559,7 +2975,7 @@ unbox-primitive@^1.0.2: update-browserslist-db@^1.0.13: version "1.0.13" - resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== dependencies: escalade "^3.1.1" @@ -2567,30 +2983,35 @@ update-browserslist-db@^1.0.13: uri-js@^4.2.2: version "4.4.1" - resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" util-deprecate@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -vite@^4.4.5: - version "4.5.0" - resolved "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz" - integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw== +vite@^5.0.5: + version "5.0.11" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.11.tgz#31562e41e004cb68e1d51f5d2c641ab313b289e4" + integrity sha512-XBMnDjZcNAw/G1gEiskiM1v6yzM4GE5aMGvhWTlHAYYhxb7S3/V1s3m2LDHa8Vh6yIWYYB0iJwsEaS523c4oYA== dependencies: - esbuild "^0.18.10" - postcss "^8.4.27" - rollup "^3.27.1" + esbuild "^0.19.3" + postcss "^8.4.32" + rollup "^4.2.0" optionalDependencies: - fsevents "~2.3.2" + fsevents "~2.3.3" + +void-elements@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09" + integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== which-boxed-primitive@^1.0.2: version "1.0.2" - resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== dependencies: is-bigint "^1.0.1" @@ -2601,7 +3022,7 @@ which-boxed-primitive@^1.0.2: which-builtin-type@^1.1.3: version "1.1.3" - resolved "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== dependencies: function.prototype.name "^1.1.5" @@ -2619,7 +3040,7 @@ which-builtin-type@^1.1.3: which-collection@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906" integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A== dependencies: is-map "^2.0.1" @@ -2629,7 +3050,7 @@ which-collection@^1.0.1: which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: version "1.1.13" - resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== dependencies: available-typed-arrays "^1.0.5" @@ -2640,27 +3061,45 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.9: which@^2.0.1: version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== yallist@^4.0.0: version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^2.1.1: - version "2.3.3" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz" - integrity sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ== +yaml@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.4.tgz#53fc1d514be80aabf386dc6001eb29bf3b7523b2" + integrity sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA== yocto-queue@^0.1.0: version "0.1.0" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 77fbfe47eb..89cfcaf991 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -400,12 +400,12 @@ dependencies = [ [[package]] name = "bls12_381" -version = "0.6.0" -source = "git+https://github.com/jstuczyn/bls12_381?branch=gt-serialisation#10fb6f700bfda17c8475af3bfd31e3fec15f2278" +version = "0.8.0" +source = "git+https://github.com/jstuczyn/bls12_381?branch=feature/gt-serialization-0.8.0#c4543fde7d02efea6ecfcf22e14476ddb516b483" dependencies = [ "digest 0.9.0", - "ff 0.11.1", - "group 0.11.0", + "ff 0.13.0", + "group 0.13.0", "pairing", "rand_core 0.6.4", "subtle 2.4.1", @@ -418,29 +418,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "845141a4fade3f790628b7daaaa298a25b204fb28907eb54febe5142db6ce653" -[[package]] -name = "boringtun" -version = "0.6.0" -source = "git+https://github.com/cloudflare/boringtun?rev=e1d6360d6ab4529fc942a078e4c54df107abe2ba#e1d6360d6ab4529fc942a078e4c54df107abe2ba" -dependencies = [ - "aead", - "base64 0.13.1", - "blake2 0.10.6", - "chacha20poly1305", - "hex", - "hmac 0.12.1", - "ip_network", - "ip_network_table", - "libc", - "nix", - "parking_lot", - "rand_core 0.6.4", - "ring", - "tracing", - "untrusted 0.9.0", - "x25519-dalek 2.0.0", -] - [[package]] name = "brotli" version = "3.3.4" @@ -513,9 +490,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" dependencies = [ "serde", ] @@ -615,30 +592,6 @@ dependencies = [ "keystream", ] -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher 0.4.4", - "poly1305", - "zeroize", -] - [[package]] name = "cipher" version = "0.3.0" @@ -656,7 +609,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -847,9 +799,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9d2043a9e617b0d602fbc0a0ecd621568edbf3a9774890a6d562389bd8e1c" +checksum = "32560304ab4c365791fd307282f76637213d8083c1a98490c35159cd67852237" dependencies = [ "prost", "prost-types", @@ -858,19 +810,19 @@ dependencies = [ [[package]] name = "cosmrs" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af13955d6f356272e6def9ff5e2450a7650df536d8934f47052a20c76513d2f6" +checksum = "47126f5364df9387b9d8559dcef62e99010e1d4098f39eb3f7ee4b5c254e40ea" dependencies = [ "bip32", "cosmos-sdk-proto", "ecdsa 0.16.8", "eyre", - "getrandom 0.2.10", "k256 0.13.1", "rand_core 0.6.4", "serde", "serde_json", + "signature 2.1.0", "subtle-encoding", "tendermint", "tendermint-rpc", @@ -1062,15 +1014,6 @@ dependencies = [ "syn 2.0.28", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct", -] - [[package]] name = "ctor" version = "0.1.26" @@ -1108,6 +1051,7 @@ dependencies = [ "byteorder", "digest 0.9.0", "rand_core 0.5.1", + "serde", "subtle 2.4.1", "zeroize", ] @@ -1318,9 +1262,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +dependencies = [ + "powerfmt", +] [[package]] name = "derivative" @@ -1509,6 +1456,7 @@ version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ + "serde", "signature 1.6.4", ] @@ -1545,6 +1493,7 @@ dependencies = [ "ed25519 1.5.3", "rand 0.7.3", "serde", + "serde_bytes", "sha2 0.9.9", "zeroize", ] @@ -1719,16 +1668,6 @@ dependencies = [ "log", ] -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "ff" version = "0.12.1" @@ -1745,6 +1684,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ + "bitvec", "rand_core 0.6.4", "subtle 2.4.1", ] @@ -2224,18 +2164,6 @@ dependencies = [ "system-deps 6.1.1", ] -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "byteorder", - "ff 0.11.1", - "rand_core 0.6.4", - "subtle 2.4.1", -] - [[package]] name = "group" version = "0.12.1" @@ -2361,31 +2289,6 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" -[[package]] -name = "headers" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" -dependencies = [ - "base64 0.13.1", - "bitflags 1.3.2", - "bytes", - "headers-core", - "http", - "httpdate", - "mime", - "sha1", -] - -[[package]] -name = "headers-core" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" -dependencies = [ - "http", -] - [[package]] name = "heck" version = "0.3.3" @@ -2575,40 +2478,17 @@ dependencies = [ ] [[package]] -name = "hyper-proxy" -version = "0.9.1" +name = "hyper-rustls" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca815a891b24fdfb243fa3239c86154392b0953ee584aa1a2a1f66d20cbe75cc" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ - "bytes", - "futures", - "headers", + "futures-util", "http", "hyper", - "hyper-rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tower-service", - "webpki", -] - -[[package]] -name = "hyper-rustls" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" -dependencies = [ - "ct-logs", - "futures-util", - "hyper", - "log", "rustls", - "rustls-native-certs", "tokio", "tokio-rustls", - "webpki", - "webpki-roots", ] [[package]] @@ -2734,28 +2614,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "ip_network" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" - -[[package]] -name = "ip_network_table" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4099b7cfc5c5e2fe8c5edf3f6f7adf7a714c9cc697534f63a5a5da30397cb2c0" -dependencies = [ - "ip_network", - "ip_network_table-deps-treebitmap", -] - -[[package]] -name = "ip_network_table-deps-treebitmap" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e537132deb99c0eb4b752f0346b6a836200eaaa3516dd7e5514b63930a09e5d" - [[package]] name = "ipnet" version = "2.8.0" @@ -2927,9 +2785,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" [[package]] name = "libgit2-sys" @@ -3105,9 +2963,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -3166,18 +3024,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", -] - [[package]] name = "nodrop" version = "0.1.14" @@ -3285,10 +3131,12 @@ dependencies = [ "cosmwasm-std", "getset", "nym-coconut-interface", + "nym-crypto", "nym-mixnet-contract-common", "nym-node-requests", "schemars", "serde", + "tendermint", ] [[package]] @@ -3314,9 +3162,9 @@ dependencies = [ "bls12_381", "bs58 0.4.0", "digest 0.9.0", - "ff 0.11.1", + "ff 0.13.0", "getrandom 0.2.10", - "group 0.11.0", + "group 0.13.0", "itertools 0.10.5", "nym-dkg", "nym-pemstore", @@ -3392,6 +3240,8 @@ dependencies = [ "nym-pemstore", "nym-sphinx-types", "rand 0.7.3", + "serde", + "serde_bytes", "subtle-encoding", "thiserror", "x25519-dalek 1.1.1", @@ -3405,8 +3255,8 @@ dependencies = [ "bitvec", "bls12_381", "bs58 0.4.0", - "ff 0.11.1", - "group 0.11.0", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "nym-pemstore", "rand 0.8.5", @@ -3602,6 +3452,7 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.13.1", + "bip32", "bip39", "colored 2.0.4", "cosmrs", @@ -3696,8 +3547,8 @@ name = "nym-wireguard-types" version = "0.1.0" dependencies = [ "base64 0.21.4", - "boringtun", "dashmap", + "log", "nym-crypto", "serde", "thiserror", @@ -3706,7 +3557,7 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.2.10" +version = "1.2.11" dependencies = [ "async-trait", "base64 0.13.1", @@ -3899,11 +3750,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "pairing" -version = "0.21.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" dependencies = [ - "group 0.11.0", + "group 0.13.0", ] [[package]] @@ -4264,17 +4115,6 @@ dependencies = [ "miniz_oxide", ] -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug 0.3.0", - "universal-hash", -] - [[package]] name = "polyval" version = "0.6.1" @@ -4287,6 +4127,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -4360,9 +4206,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" dependencies = [ "bytes", "prost-derive", @@ -4370,22 +4216,22 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.11.0", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.28", ] [[package]] name = "prost-types" -version = "0.11.9" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" dependencies = [ "prost", ] @@ -4611,6 +4457,7 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -4620,12 +4467,16 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", "tokio-native-tls", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", @@ -4681,17 +4532,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" dependencies = [ "cc", + "getrandom 0.2.10", "libc", - "once_cell", "spin", - "untrusted 0.7.1", - "web-sys", - "winapi", + "untrusted", + "windows-sys 0.48.0", ] [[package]] @@ -4733,29 +4583,47 @@ dependencies = [ [[package]] name = "rustls" -version = "0.19.1" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ - "base64 0.13.1", "log", "ring", + "rustls-webpki", "sct", - "webpki", ] [[package]] name = "rustls-native-certs" -version = "0.5.0" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls", + "rustls-pemfile", "schannel", "security-framework", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.4", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -4831,12 +4699,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", - "untrusted 0.7.1", + "untrusted", ] [[package]] @@ -4939,9 +4807,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.183" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] @@ -4966,9 +4834,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.183" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", @@ -4988,9 +4856,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.104" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa 1.0.9", "ryu", @@ -5083,17 +4951,6 @@ dependencies = [ "stable_deref_trait", ] -[[package]] -name = "sha1" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha2" version = "0.9.9" @@ -5195,9 +5052,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.3" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys 0.48.0", @@ -5257,9 +5114,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" @@ -5729,9 +5586,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f0a7d05cf78524782337f8edd55cbc578d159a16ad4affe2135c92f7dbac7f0" +checksum = "bc2294fa667c8b548ee27a9ba59115472d0a09c2ba255771092a7f1dcf03a789" dependencies = [ "bytes", "digest 0.10.7", @@ -5760,9 +5617,9 @@ dependencies = [ [[package]] name = "tendermint-config" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a72dbbea6dde12045d261f2c70c0de039125675e8a026c8d5ad34522756372" +checksum = "5a25dbe8b953e80f3d61789fbdb83bf9ad6c0ef16df5ca6546f49912542cc137" dependencies = [ "flex-error", "serde", @@ -5774,9 +5631,9 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cec054567d16d85e8c3f6a3139963d1a66d9d3051ed545d31562550e9bcc3d" +checksum = "2cc728a4f9e891d71adf66af6ecaece146f9c7a11312288a3107b3e1d6979aaf" dependencies = [ "bytes", "flex-error", @@ -5792,21 +5649,18 @@ dependencies = [ [[package]] name = "tendermint-rpc" -version = "0.32.2" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d119d83a130537fc4a98c3c9eb6899ebe857fea4860400a61675bfb5f0b35129" +checksum = "dfbf0a4753b46a190f367337e0163d0b552a2674a6bac54e74f9f2cdcde2969b" dependencies = [ "async-trait", "bytes", "flex-error", "futures", "getrandom 0.2.10", - "http", - "hyper", - "hyper-proxy", - "hyper-rustls", "peg", "pin-project", + "reqwest", "semver 1.0.18", "serde", "serde_bytes", @@ -5883,14 +5737,15 @@ dependencies = [ [[package]] name = "time" -version = "0.3.25" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa 1.0.9", "libc", "num_threads", + "powerfmt", "serde", "time-core", "time-macros", @@ -5898,15 +5753,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] @@ -5928,9 +5783,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.31.0" +version = "1.35.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40de3a2ba249dcb097e01be5e67a5ff53cf250397715a071a81543e8a832a920" +checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" dependencies = [ "backtrace", "bytes", @@ -5940,16 +5795,16 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.3", + "socket2 0.5.5", "tokio-macros", "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -5968,13 +5823,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ "rustls", "tokio", - "webpki", ] [[package]] @@ -6189,12 +6043,6 @@ dependencies = [ "subtle 2.4.1", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -6203,9 +6051,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", @@ -6455,25 +6303,6 @@ dependencies = [ "system-deps 6.1.1", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted 0.7.1", -] - -[[package]] -name = "webpki-roots" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" -dependencies = [ - "webpki", -] - [[package]] name = "webview2-com" version = "0.19.1" @@ -6898,6 +6727,7 @@ checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ "curve25519-dalek 3.2.0", "rand_core 0.5.1", + "serde", "zeroize", ] diff --git a/nym-wallet/nym-wallet-types/Cargo.toml b/nym-wallet/nym-wallet-types/Cargo.toml index ca422a9832..f9cb2d4552 100644 --- a/nym-wallet/nym-wallet-types/Cargo.toml +++ b/nym-wallet/nym-wallet-types/Cargo.toml @@ -3,6 +3,7 @@ name = "nym-wallet-types" version = "1.0.0" edition = "2021" rust-version = "1.58" +license = "Apache-2.0" [dependencies] hex-literal = "0.3.3" @@ -12,7 +13,7 @@ strum = { version = "0.23", features = ["derive"] } ts-rs = "7.0.0" cosmwasm-std = "1.3.0" -cosmrs = "=0.14.0" +cosmrs = "=0.15.0" nym-config = { path = "../../common/config" } nym-network-defaults = { path = "../../common/network-defaults" } diff --git a/nym-wallet/nym-wallet-types/src/network/sandbox.rs b/nym-wallet/nym-wallet-types/src/network/sandbox.rs index ace2ff7663..1186c07eac 100644 --- a/nym-wallet/nym-wallet-types/src/network/sandbox.rs +++ b/nym-wallet/nym-wallet-types/src/network/sandbox.rs @@ -30,7 +30,7 @@ pub(crate) const EPHEMERA_CONTRACT_ADDRESS: &str = pub(crate) fn validators() -> Vec { vec![ValidatorDetails::new( - "https://sandbox-validator1.nymtech.net", + "https://rpc.sandbox.nymtech.net", Some("https://sandbox-nym-api1.nymtech.net/api"), )] } diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 5a011cdee1..f0d1b31155 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nym-wallet-app", - "version": "1.2.12-rc.1", + "version": "1.2.12-rc.2", "license": "MIT", "main": "index.js", "scripts": { diff --git a/nym-wallet/src-tauri/Cargo.toml b/nym-wallet/src-tauri/Cargo.toml index eaced888a4..1182ef2d1c 100644 --- a/nym-wallet/src-tauri/Cargo.toml +++ b/nym-wallet/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nym_wallet" -version = "1.2.10" +version = "1.2.11" description = "Nym Native Wallet" authors = ["Nym Technologies SA"] license = "" @@ -49,7 +49,7 @@ base64 = "0.13" zeroize = { version = "1.5", features = ["zeroize_derive", "serde"] } cosmwasm-std = "1.3.0" -cosmrs = "=0.14.0" +cosmrs = "=0.15.0" nym-validator-client = { path = "../../common/client-libs/validator-client" } nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] } diff --git a/nym-wallet/src-tauri/src/config/mod.rs b/nym-wallet/src-tauri/src/config/mod.rs index bdb369ade6..371a634398 100644 --- a/nym-wallet/src-tauri/src/config/mod.rs +++ b/nym-wallet/src-tauri/src/config/mod.rs @@ -25,7 +25,7 @@ pub const REMOTE_SOURCE_OF_NYXD_URLS: &str = const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1; const CURRENT_NETWORK_CONFIG_VERSION: u32 = 1; -pub(crate) const CUSTOM_SIMULATED_GAS_MULTIPLIER: f32 = 1.4; +pub(crate) const CUSTOM_SIMULATED_GAS_MULTIPLIER: f32 = 1.5; #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Config { diff --git a/nym-wallet/src-tauri/src/operations/simulate/admin.rs b/nym-wallet/src-tauri/src/operations/simulate/admin.rs index 1884c7472e..ddb24bb63b 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/admin.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/admin.rs @@ -32,6 +32,6 @@ pub async fn simulate_update_contract_settings( vec![], )?; - let result = client.nyxd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg], "").await?; guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs index 6d538175c4..447e624ae9 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/cosmos.rs @@ -12,6 +12,7 @@ use std::str::FromStr; pub async fn simulate_send( address: &str, amount: DecCoin, + memo: String, state: tauri::State<'_, WalletState>, ) -> Result { let guard = state.read().await; @@ -30,7 +31,7 @@ pub async fn simulate_send( amount, }; - let result = client.nyxd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg], memo).await?; guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs index 11d360ef71..c64fcc53af 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/mixnet.rs @@ -38,7 +38,7 @@ async fn simulate_mixnet_operation( .nyxd .wrap_contract_execute_message(mixnet_contract, &msg, funds)?; - let result = client.nyxd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg], "").await?; guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs index b225aa9ff3..9a33e2a3ed 100644 --- a/nym-wallet/src-tauri/src/operations/simulate/vesting.rs +++ b/nym-wallet/src-tauri/src/operations/simulate/vesting.rs @@ -39,7 +39,7 @@ async fn simulate_vesting_operation( .nyxd .wrap_contract_execute_message(vesting_contract, &msg, funds)?; - let result = client.nyxd.simulate(vec![msg]).await?; + let result = client.nyxd.simulate(vec![msg], "").await?; guard.create_detailed_fee(result) } diff --git a/nym-wallet/src-tauri/tauri.conf.json b/nym-wallet/src-tauri/tauri.conf.json index 43330b2587..8aabb2bc80 100644 --- a/nym-wallet/src-tauri/tauri.conf.json +++ b/nym-wallet/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "package": { "productName": "nym-wallet", - "version": "1.2.10" + "version": "1.2.11" }, "build": { "distDir": "../dist", diff --git a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts index 3230fc0714..9d742573aa 100644 --- a/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts +++ b/nym-wallet/src/components/Bonding/forms/mixnodeValidationSchema.ts @@ -1,5 +1,13 @@ import * as Yup from 'yup'; -import { isLessThan, isValidHostname, validateAmount, validateKey, validateRawPort, validateVersion } from 'src/utils'; +import { + isGreaterThan, + isLessThan, + isValidHostname, + validateAmount, + validateKey, + validateRawPort, + validateVersion, +} from 'src/utils'; export const mixnodeValidationSchema = Yup.object().shape({ identityKey: Yup.string() @@ -34,18 +42,21 @@ export const mixnodeValidationSchema = Yup.object().shape({ }); const operatingCostAndPmValidation = { - profitMargin: Yup.number().required('Profit Percentage is required').min(0).max(100), + profitMargin: Yup.number().required('Profit Percentage is required').min(7).max(80), operatorCost: Yup.object().shape({ amount: Yup.string() .required('An operating cost is required') // eslint-disable-next-line prefer-arrow-callback - .test('valid-operating-cost', 'A valid amount is required (min 40)', async function isValidAmount(this, value) { - if (value && (!Number(value) || isLessThan(+value, 40))) { - return false; - } - - return true; - }), + .test( + 'valid-operating-cost', + 'A valid amount is required (min 500 - max 2000)', + async function isValidAmount(this, value) { + if (value && (!Number(value) || isLessThan(+value, 500) || isGreaterThan(+value, 2000))) { + return this.createError({ message: 'A valid amount is required (min 500 - max 2000)' }); + } + return true; + }, + ), }), }; diff --git a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx index 4e4c39f9cf..4cfcdfcceb 100644 --- a/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx +++ b/nym-wallet/src/components/Bonding/modals/BondMixnodeModal.tsx @@ -26,7 +26,7 @@ const defaultMixnodeValues: MixnodeData = { const defaultAmountValues = (denom: CurrencyDenom) => ({ amount: { amount: '100', denom }, - operatorCost: { amount: '40', denom }, + operatorCost: { amount: '500', denom }, profitMargin: '10', tokenPool: 'balance', }); diff --git a/nym-wallet/src/components/Send/SendModal.tsx b/nym-wallet/src/components/Send/SendModal.tsx index b6d391d0ef..b8c1ca5eeb 100644 --- a/nym-wallet/src/components/Send/SendModal.tsx +++ b/nym-wallet/src/components/Send/SendModal.tsx @@ -21,7 +21,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void const [gasError, setGasError] = useState(); const [isLoading, setIsLoading] = useState(false); const [userFees, setUserFees] = useState(); - const [memo, setMemo] = useState(); + const [memo, setMemo] = useState(''); const [txDetails, setTxDetails] = useState(); const [showMoreOptions, setShowMoreOptions] = useState(false); @@ -37,7 +37,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void useEffect(() => { if (!showMoreOptions) { setUserFees(undefined); - setMemo(undefined); + setMemo(''); } }, [showMoreOptions]); @@ -52,7 +52,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void if (userFees) { await setFeeManually(userFees); } else { - await getFee(simulateSend, { address: toAddress, amount }); + await getFee(simulateSend, { address: toAddress, amount, memo }); } setModal('send details'); } catch (e) { diff --git a/nym-wallet/src/pages/balance/index.tsx b/nym-wallet/src/pages/balance/index.tsx index 89767f370e..795cd2d0aa 100644 --- a/nym-wallet/src/pages/balance/index.tsx +++ b/nym-wallet/src/pages/balance/index.tsx @@ -13,7 +13,12 @@ export const Balance = () => { const { userBalance, clientDetails, network } = useContext(AppContext); useEffect(() => { - userBalance.fetchBalance(); + const interval = setInterval(() => { + userBalance.fetchBalance(); + userBalance.fetchTokenAllocation(); + }, 10000); + + return () => clearInterval(interval); }, []); const handleShowTransferModal = async () => { diff --git a/nym-wallet/src/pages/bonding/Bonding.tsx b/nym-wallet/src/pages/bonding/Bonding.tsx index b138f6309b..24aeb768ee 100644 --- a/nym-wallet/src/pages/bonding/Bonding.tsx +++ b/nym-wallet/src/pages/bonding/Bonding.tsx @@ -204,7 +204,7 @@ const Bonding = () => { {confirmationDetails && confirmationDetails.status === 'success' && ( { diff --git a/nym-wallet/src/requests/simulate.ts b/nym-wallet/src/requests/simulate.ts index 158df8e5be..a8f603ed72 100644 --- a/nym-wallet/src/requests/simulate.ts +++ b/nym-wallet/src/requests/simulate.ts @@ -69,8 +69,8 @@ export const simulateVestingUpdateGatewayConfig = async (update: GatewayConfigUp export const simulateWithdrawVestedCoins = async (args: any) => invokeWrapper('simulate_withdraw_vested_coins', args); -export const simulateSend = async ({ address, amount }: { address: string; amount: DecCoin }) => - invokeWrapper('simulate_send', { address, amount }); +export const simulateSend = async ({ address, amount, memo }: { address: string; amount: DecCoin; memo: string }) => + invokeWrapper('simulate_send', { address, amount, memo }); export const getCustomFees = async ({ feesAmount }: { feesAmount: DecCoin }) => invokeWrapper('get_custom_fees', { feesAmount }); diff --git a/ppa/InRelease b/ppa/InRelease new file mode 100644 index 0000000000..1e9143f37a --- /dev/null +++ b/ppa/InRelease @@ -0,0 +1,37 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +Date: Tue, 21 Nov 2023 07:58:30 +0000 +MD5Sum: + 29bbd50eeae096bab7db9273b3d2460e 1133 Packages + fbfb649fbc1ea80e81f3f29aa8e0559c 711 Packages.gz + 55c1789b21080802c80bbc323569f324 38 Release +SHA1: + 28d2f5d52a8684a4a3d7d76765c10d30f9848ad7 1133 Packages + c6b80620b228848a9d1b006e83214612ecbcfca5 711 Packages.gz + 827f0dfd30c546e23b5014285f1cadff4068a5aa 38 Release +SHA256: + 76cc1482e7bcaebdc6d1f87140b25b2647b3d8426e2306c5ebe2ae08663c889e 1133 Packages + d7e55a929f67da9fb91f67ee86b9853a3c1f796b110eaca995753fd80018ff57 711 Packages.gz + 379613daac02063562de7a5ec48e32582b3ea8ca8dd136b4ca1f710fd7ce7b28 38 Release +SHA512: + e23aa596c3b8831fc8bc5b51b5073239b68f1d5e471802cba896b6449b493194da7e42603a478125a1dc41650262f46489f422fe37bef8ea810d71854ccf4ee5 1133 Packages + 672b6fc16c6daae98f994882ca2293b43efa8e24c92d5c85f838d9b97bfb970fff85209df077a6d65bc12f981b96a49b8a5d75108da4418760bbfd58958162b3 711 Packages.gz + d1ea202095914d4b4ad29af3fb4021878c7a3bf6688a6329e775eaadcf95bf8b30d70f45bec64d70ad20b56014f3399a096fe5c05219136727c3223174832da3 38 Release +-----BEGIN PGP SIGNATURE----- + +iQJEBAEBCgAuFiEEzWLrAxjj1jSobw5ZgQ9E11+JZgIFAmVcY00QHG55bUBueW10 +ZWNoLm5ldAAKCRCBD0TXX4lmAp8JD/4ycYaTE29tarxSjvL+f8Sp76eR7+WfKo6f +c5v0EMvf28UVMVhn5Ngljnu6df0RZhCv3SnDwT5l1DC+GUkEaAcH7QtF8uOGcOvA +8PGRYxpeLBMWE6Q2O1J7x1haOrOIXW3KJp7yrYmSj+PIlSmy/SttgrDOHVFdvm1V +DVePNzEJwCQk/or2TKvsgZ1TWdcQOox+MyksUGmOzrHb6l18CnxR9Z3QAn/TXk+I +uoJ0rNhWR8VyG7oOC/PNa5301pWenZnnP4wNowdqDq6PpORDj2814n1CJGBpcbVW +EIWwcS1lVKVTuQtnTVsWG+YytWKhuamA/twSAkuQVmm3mX8+T0R+CvW1iQPqrm2c +c2NSuDj+KDDDNG+ZynVh2yAjdSjHyXKKFHQPGcF1gyN7/D2SOsp49A61WeovcLV6 +y0GkuZrRy7u8jgGF5p8rh0WNDpbcSs1O+qdWP/wT9Vmn9KezDO0MuJNh4MtCh4lE +jo01HFttQ8sMCdSjjvvKrzL8gW1lGxH+yAvsHfCA4PBy6MrdzwqazxAGJR86Fxu4 +MqGlmHoyFwv8E7D/RcG8YMY25c1DA5wQ9cYSRS4OovPhf1THkK9ICyZymUbP0Sex +Iie4K91xdTseT1QBO5i9VAFbV0hDgeU2bggmeganrNHO3bIvNmQiFh9RUSQLpYEv +qCFYa62C3w== +=ttDt +-----END PGP SIGNATURE----- diff --git a/ppa/Packages b/ppa/Packages new file mode 100644 index 0000000000..354b23d2ed --- /dev/null +++ b/ppa/Packages @@ -0,0 +1,34 @@ +Package: nym-mixnode +Version: 1.1.33-1 +Architecture: amd64 +Maintainer: Dave Hrycyszyn +Installed-Size: 23794 +Depends: libc6 (>= 2.34) +Filename: ./debian/nym-mixnode_1.1.33-1_amd64.deb +Size: 5245732 +MD5sum: a9377a1aabfda9d95ce3a937fb4f02ed +SHA1: 535e76d7c5c0d8dc96b30c34fa1de46fbc9c40dd +SHA256: 70874c9d738998b6b671965ee724c47f56a8486d8eb6c73e94498f8c240fa62b +Priority: optional +Description: Implementation of a Loopix-based Mixnode + + . + # Nym Mixnode + . + A Rust mixnode implementation. + . + ## Usage + . + * `nym-mixnode` prints a help message showing usage options + * `nym-mixnode run --help` prints a help message showing usage options for the + run command + * `nym-mixnode run --layer 1 --host x.x.x.x` will start the mixnode in layer 1 + and bind to the specified host IP address. Coordinate with other people in your + network to find out which layer needs coverage. + . + By default, the Nym Mixnode will start on port 1789. If desired, you can change + the port using the `--port` option. + diff --git a/ppa/Packages.gz b/ppa/Packages.gz new file mode 100644 index 0000000000..0351e21497 Binary files /dev/null and b/ppa/Packages.gz differ diff --git a/ppa/Release b/ppa/Release new file mode 100644 index 0000000000..739d421404 --- /dev/null +++ b/ppa/Release @@ -0,0 +1,17 @@ +Date: Tue, 21 Nov 2023 07:58:30 +0000 +MD5Sum: + 29bbd50eeae096bab7db9273b3d2460e 1133 Packages + fbfb649fbc1ea80e81f3f29aa8e0559c 711 Packages.gz + 55c1789b21080802c80bbc323569f324 38 Release +SHA1: + 28d2f5d52a8684a4a3d7d76765c10d30f9848ad7 1133 Packages + c6b80620b228848a9d1b006e83214612ecbcfca5 711 Packages.gz + 827f0dfd30c546e23b5014285f1cadff4068a5aa 38 Release +SHA256: + 76cc1482e7bcaebdc6d1f87140b25b2647b3d8426e2306c5ebe2ae08663c889e 1133 Packages + d7e55a929f67da9fb91f67ee86b9853a3c1f796b110eaca995753fd80018ff57 711 Packages.gz + 379613daac02063562de7a5ec48e32582b3ea8ca8dd136b4ca1f710fd7ce7b28 38 Release +SHA512: + e23aa596c3b8831fc8bc5b51b5073239b68f1d5e471802cba896b6449b493194da7e42603a478125a1dc41650262f46489f422fe37bef8ea810d71854ccf4ee5 1133 Packages + 672b6fc16c6daae98f994882ca2293b43efa8e24c92d5c85f838d9b97bfb970fff85209df077a6d65bc12f981b96a49b8a5d75108da4418760bbfd58958162b3 711 Packages.gz + d1ea202095914d4b4ad29af3fb4021878c7a3bf6688a6329e775eaadcf95bf8b30d70f45bec64d70ad20b56014f3399a096fe5c05219136727c3223174832da3 38 Release diff --git a/ppa/Release.gpg b/ppa/Release.gpg new file mode 100644 index 0000000000..0e27c968f0 --- /dev/null +++ b/ppa/Release.gpg @@ -0,0 +1,17 @@ +-----BEGIN PGP SIGNATURE----- + +iQJEBAABCgAuFiEEzWLrAxjj1jSobw5ZgQ9E11+JZgIFAmVcY0UQHG55bUBueW10 +ZWNoLm5ldAAKCRCBD0TXX4lmAmCfD/9dCZBTQnC6b97PQWR6EPooORsyyNNO6apN +WGKcgQ+nj8H1cyhVM0P1xUyukhnU7DJvz9yp4v+mS87178K4/EGONanSxqR3c4PX +q+F2wOXLiOalwQbS7flSbD5dYkvIcjCo5GT8+U/09JFYxn4pqItMQnkE2zD+oP98 +rSv70m8UV6ZLgRGpXWMl5qw3F2pU3Xes25brb+Gqon1EHJV+zW/2K8LBF0Zv6s5t +TjBrYM+foFnKb68fmtl8t7d47uTpzm0lLxUx8PzZliCfQwkzr1Bz7hTBpdTcpvYb ++BI32ocHgLm/tnwQapLarVFj60//0HLK6YxXlufQg3lqScPy/mEEK3Agw1ZzOhX7 +tOiMp78M4KCK3EhCx7LiMbv1LvPPK6tXzMYILwXqSz/iScK+cN/KtEOD8+75PdjK +N4mouHciCzCpeeele1yZICurktQ+7+kxhwL6Wm88sG3rXjrIEGm7Ez+0FPe9WYFD +P+R6/Zpmc/dSjGroOJst5PuEqLHs8RscgqKNtpVL1rjY22RtaOEzwUruduR5sGeA +H74V6YkehNTNAYZCLpcjwU6fDYWNPLo4Coy2kczLjsLwAXgyXPagiKWkAoOC6zcr +6ZIpzJb5OnZ9yYdoW7RrNhUedAlD89JH2tuPKdoO/qQBrCHg4YtxG3zNMcYzP+bb +VhXK4THvNg== +=0u7L +-----END PGP SIGNATURE----- diff --git a/ppa/debian/nym-mixnode_1.1.33-1_amd64.deb b/ppa/debian/nym-mixnode_1.1.33-1_amd64.deb new file mode 100644 index 0000000000..ac3e1ae786 Binary files /dev/null and b/ppa/debian/nym-mixnode_1.1.33-1_amd64.deb differ diff --git a/ppa/nymtech.gpg b/ppa/nymtech.gpg new file mode 100644 index 0000000000..657b19e391 --- /dev/null +++ b/ppa/nymtech.gpg @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGVcYlgBEAC1f7K2SEVFXR3cTs4wxh6hZtiwX5e+NDyfw/td4SOg2yM3h0QH +wXBwp0RGKilfcnsL8VuhIbqGNH8EImmQW3zRvSfh+wEw+dyFX0jacWDE87pKSha7 +BmjLQFMoRpxvSFT3z2UEaDwZcO9wA/kx3MZ6WckAkhT2LgkR+RiKbkjWXTKRui9K +YUWumMrDcyEdKrYHunfzf6BjUxPat/5hJWMdJ+LUz67dTbAqXHmDXv3baGnJRGYs +p/HXWxSr8yh6XmfHw5h3IEuc4i1S3JEh+s3rMUl27Q4g1tYbdvpLYC0ex7iOOqvK +0MS416F6phW5CXbDwHhiCWbBcPI6KPIWfRvQPfIDePTd3ElwoIc2zTHDODBHRy2Q +eiCgcsMyIUeBCHHXurYh1bzWZnTEpOJut7usNA2rVDKw2r2hweGAHZ/9NhQ4iQ+3 ++udUVYZZrGag/c/Sn4+wkVUIdfsdLquVWTvlkhCNOJqLT5M7rI3veAWK75H0xVxX +om4JlN0lCet9cwnttuIhbiSeP0swi/4dOjitPqyAjbhuFJHVLq5AP6uDO5F5Uja/ +wVOHdca4QWiQy7X00bRF1d26rNT9+flW1fmRZpRg4i52/iJ1+7nm+eemYEIFl1nd +o+gNxWpZQXB81Edu38QidzejmtwqBlqkNp1qI9uLj0Ai6QGUq1kWmvblEQARAQAB +tDROeW0gVGVjaG5vbG9naWVzIChQUEEgc2lnbmluZyBrZXkpIDxueW1AbnltdGVj +aC5uZXQ+iQJOBBMBCgA4FiEEzWLrAxjj1jSobw5ZgQ9E11+JZgIFAmVcYlgCGwMF +CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQgQ9E11+JZgJHVA/9EkPmVMeLHG1s +hE2tyuAu0chS+Fu9pxOGM7R+fcMZ6FeRRFZG7ovF3s51tq5kynpxYGqnSEEDbEVk +uu3e9dOrfyIzDCTQ5Kf8p/eP2ku9VBBHqIaZJq3dScXaDEy60w+tR6Kp5Y+zUknJ +rxC5RRVZlvCjimeh5OuYk6Gj2xShO+elSqPg7m83Oq5qk4rirX9XBfRca96r6i4M +fCEOUdOOh8EqDALu9PeGEIjWgU7QHfW5aLC0ohoNohuOeyN1vlkLBJoThNFFnUVi +nUldMLdFr8N/0c11jOpEodfST+KzjMbFMVTdGGxbDyrE8mmS/0rnijjFtOBPQYDc +VJ3lT0oZ7ra+1D341WUClbFvc/Bo8xZoBFPnD/ulJFnZhm+9xv4ec2euaQ3YS88i +vdlMJFlM/UWpOTsxNirc/Oi2GQWGCV0kAJVOxeRKPiwSB/EmS17KsNlmh6j8M2lS +MvaG7p9unXU3xc/IF91sxs4HIFgWci0bv+fHUzwEIj07PziwBoEBYueic0dE1OlM +aQAFhZvEnWfcx3/oG9hNihTP1Ocew08gNW26vDM5tAXUR8k0V9IgId9OA5f/hWXp +o8rNGcFJXkqa2nwFYnPnSEntvgYuMA0y3MOOawVjaOq9Sg1yf2SdZ4cG6pXe/e8R +Hl3R6DpHfsUnJpfE6xziwzAOXHMGxZi5Ag0EZVxiWAEQALPbcMkoIZydfjY0r7Eo +RLz6tO/rXve9SAlYqz4ZeEzr+wG/1dxdV31vyba3hHz7WVaEbyNbaR8F7NsKMhSB +ge9NVkuEOccjXcKVEHh/DoxXEWrEmdqy3DGQt0cGt67N+wiD0rufQcpRv2x4DP7T +nqXTDxrp9zx3LSZM7KRXeFcAR8ucmt/e+26kFtBHQ5zfD9IOfFORmRzAlN5mE6XJ +7Nt96BABSF/wW9iSRJnNt8mM37WwDwMZRjgZauI7jXaDOBbnEbicbXxSlQG7iGNL +XHC3znl/evYrUqDNgd6SxaBHcONoKt/l1jO0YF5o1MozHElnNTx6/y9Ynni7gEjE +U9zpNBfATUwxnML2uZ5UpJB1DL7w7ZbckGzt16/u+cdpHgfWd2O0wsGNNgNg1yEg +l0YBeukgXWsLCptVfcwpx9XXXyB2dwAcBZr5gTtu9rNUOkV/O4en5EaL0U+0oOvT +pyv8FKxgSw+IQzHxk4mlxL9ufTQF7blpW9c0dGPz3oc4VOMtTCvbw8+dGlOpBOZO +4d6pEp5jvQ0c3nuMRhKu6w4WTUiVPcCC7yHgKeBijfLM4bSTDR7MxS5H9pRB59rl +ZgBJyA6tpbjgkrBnr77hM9YsQPaed5Mb53hO6lO9rYlixRIQLgzM0GMBHIY37EXi +y7lu5XCXvVLOMifqs27j5f79ABEBAAGJAjYEGAEKACAWIQTNYusDGOPWNKhvDlmB +D0TXX4lmAgUCZVxiWAIbDAAKCRCBD0TXX4lmAjsKD/4iKJH7a8wgbmSuBxzyIZnf +HI87D6v9DtBdjrBI+X6EDeIViO6uFm5x5xP9G5ArYUgu+gyFHu8GooLmIVqmFrwz +O6sz3XPsVi9d3p23JldNW0V8cW+MyRg2DXaOVAhpU5Mj5bt9ZMLOiQi/SnH6KcAW +7w6KGVuVN+BzlpCnLe17ZLdZP17evphdF2JoFqaFjkukCHhwciNCWprJA/TVwIV/ +garCyn2hczxD58nQPxJL8h+YN7tuQeuY2HDGNl42WIhECgB+4IFi0xxsfaVzbGie +RLgshbBUWaYScWASgTtigSuYUYnoPqgIC7fztpZiF9xYaQPCzjuxz2SWeNX0bSvq +gGYZ72JnhXhhC6S+GhKZu864JHLAaKPAgWfN7iYMjlNrNZX749yomRXqZ3Ok+dFt +TMXPz/6bQ0RDe7mpYbGvBfrNlcUNp+79prh5L1Mt+ZxE3cNWxAxnQVgVTTQlzWRQ +t0JtHvNR8B4RaJ3P0PTaU5tWOmkRoplhjFjiYcU2cdUpCZ/FbQCYFA7dJFjFiE4f +19mrWn+KxHjSBTkveBBpBRVY2/tUMNjPovag0IWIowkkbRH/AdEvNwEAzc1YYbDa +P9RiJMihoLWRD9t9NxA0AdQSk5ZcbUyAuUoroSjyy7QGobQRT1osoHk1IfbbyTi1 +aXa0WNKki+64QaYA9Ystrg== +=9Wrg +-----END PGP PUBLIC KEY BLOCK----- diff --git a/ppa/nymtech.list b/ppa/nymtech.list new file mode 100644 index 0000000000..0cecfea5fa --- /dev/null +++ b/ppa/nymtech.list @@ -0,0 +1 @@ +deb [signed-by=/etc/apt/trusted.gpg.d/nymtech.gpg] https://nymtech.github.io/nym/ / \ No newline at end of file diff --git a/scripts/ppa.sh b/scripts/ppa.sh new file mode 100755 index 0000000000..e021753b14 --- /dev/null +++ b/scripts/ppa.sh @@ -0,0 +1,20 @@ +#/bin/bash +# Run from repository root +# Needs PPA_SIGNING_KEY base64 encoded in env var + +echo ${PPA_SIGNING_KEY} | base64 -d > ppa-signing-key.asc +gpg --import ppa-signing-key.asc +rm ppa-signing-key.asc + +cargo deb -p nym-mixnode + +mv target/debian/*.deb ppa/debian + +cd ppa + +dpkg-scanpackages --multiversion . > Packages +gzip -k -f Packages + +apt-ftparchive release . > Release +gpg --default-key "nym@nymtech.net" -abs -o - Release > Release.gpg +gpg --default-key "nym@nymtech.net" --clearsign -o - Release > InRelease diff --git a/sdk/lib/socks5-listener/Cargo.toml b/sdk/lib/socks5-listener/Cargo.toml index 462ce33b20..a3355fe3c6 100644 --- a/sdk/lib/socks5-listener/Cargo.toml +++ b/sdk/lib/socks5-listener/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-socks5-listener" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -28,7 +29,7 @@ tokio = { workspace = true, features = ["sync", "time"] } log = "0.4.17" rand = "0.7.3" -safer-ffi = { version = "0.1.0-rc1" } +safer-ffi = { version = "0.1.4" } [target.'cfg(target_os="android")'.dependencies] jni = { version = "0.21", default-features = false } diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index fb5cbf4ac8..c0a0960f79 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-sdk" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -49,7 +50,7 @@ nym-bin-common = { path = "../../../common/bin-common" } # extra dependencies for libp2p examples libp2p = { git = "https://github.com/ChainSafe/rust-libp2p.git", rev = "e3440d25681df380c9f0f8cfdcfd5ecc0a4f2fb6", features = [ "identify", "macros", "ping", "tokio", "tcp", "dns", "websocket", "noise", "mplex", "yamux", "gossipsub" ]} tokio-stream = "0.1.12" -tokio-util = { version = "0.7", features = ["codec"] } +tokio-util = { workspace = true, features = ["codec"] } parking_lot = "0.12" hex = "0.4" diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs index fd5c41ccd1..04c166ce59 100644 --- a/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/connection.rs @@ -109,7 +109,7 @@ impl Connection { }, }), }) - .map_err(|e| Error::OutboundSendError(e.to_string()))?; + .map_err(|e| Error::OutboundSendFailure(e.to_string()))?; // track pending outbound substreams // TODO we should probably lock this? storing map values should be atomic @@ -159,7 +159,7 @@ impl Connection { // notify poll_close that the substream is closed self.close_tx .send(substream_id) - .map_err(|e| Error::InboundSendError(e.to_string())) + .map_err(|e| Error::InboundSendFailure(e.to_string())) } } @@ -217,13 +217,13 @@ impl StreamMuxer for Connection { }, }), }) - .map_err(|e| Error::OutboundSendError(e.to_string()))?; + .map_err(|e| Error::OutboundSendFailure(e.to_string()))?; debug!("wrote OpenResponse for substream: {:?}", &msg.substream_id); // send the substream to our own channel to be returned in poll_inbound self.inbound_open_tx .send(substream) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; debug!("new inbound substream: {:?}", &msg.substream_id); } diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs index bf1ebc8175..a81674b0fa 100644 --- a/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/error.rs @@ -48,15 +48,15 @@ pub enum Error { #[error("no substream found for given ID")] SubstreamIdDoesNotExist(SubstreamId), #[error("recv error: channel closed")] - OneshotRecvError(#[from] tokio::sync::oneshot::error::RecvError), + OneshotRecvFailure(#[from] tokio::sync::oneshot::error::RecvError), #[error("recv error: channel closed")] - RecvError, + RecvFailure, #[error("outbound send error")] - OutboundSendError(String), + OutboundSendFailure(String), #[error("inbound send error")] - InboundSendError(String), + InboundSendFailure(String), #[error("failed to send new connection; receiver dropped")] - ConnectionSendError, + ConnectionSendFailure, #[error("failed to send initial TransportEvent::NewAddress")] SendErrorTransportEvent, #[error("dial timed out")] diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs index f75048203b..c696bce8fd 100644 --- a/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/mixnet.rs @@ -62,7 +62,7 @@ async fn check_inbound( if let Some(notify_tx) = notify_inbound_tx { notify_tx .send(()) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; } handle_inbound(msg, inbound_tx).await?; @@ -78,7 +78,7 @@ async fn handle_inbound( let data = parse_message_data(&msg.message)?; inbound_tx .send(data) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; Ok(()) } @@ -95,7 +95,7 @@ async fn check_outbound( ) .await } - None => Err(Error::RecvError), + None => Err(Error::RecvFailure), } } diff --git a/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs index 79c3de5cbb..6fde934bf8 100644 --- a/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs +++ b/sdk/rust/nym-sdk/examples/libp2p_shared/transport.rs @@ -170,7 +170,7 @@ impl NymTransport { ); inbound_tx .send(msg.message.clone()) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; } } None => { @@ -207,7 +207,7 @@ impl NymTransport { pending_conn .connection_tx .send(conn) - .map_err(|_| Error::ConnectionSendError)?; + .map_err(|_| Error::ConnectionSendFailure)?; if let Some(waker) = self.waker.take() { waker.wake(); @@ -247,7 +247,7 @@ impl NymTransport { message: Message::ConnectionResponse(resp), recipient: msg.recipient.unwrap(), }) - .map_err(|e| Error::OutboundSendError(e.to_string()))?; + .map_err(|e| Error::OutboundSendFailure(e.to_string()))?; if let Some(waker) = self.waker.take() { waker.wake(); @@ -287,7 +287,7 @@ impl NymTransport { ); inbound_tx .send(msg.message.clone()) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; // try to pop queued messages and send them on inbound channel while let Some(msg) = queue.pop() { @@ -297,7 +297,7 @@ impl NymTransport { ); inbound_tx .send(msg.message.clone()) - .map_err(|e| Error::InboundSendError(e.to_string()))?; + .map_err(|e| Error::InboundSendFailure(e.to_string()))?; } if let Some(waker) = self.waker.clone().take() { @@ -340,7 +340,7 @@ impl NymTransport { let upgrade = Upgrade::new(connection_rx); connection_tx .send((inner.peer_id, conn)) - .map_err(|_| Error::ConnectionSendError)?; + .map_err(|_| Error::ConnectionSendFailure)?; Ok(InboundTransportEvent::ConnectionRequest(upgrade)) } Err(e) => Err(e), @@ -380,7 +380,7 @@ impl Future for Upgrade { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.connection_tx .poll_unpin(cx) - .map_err(|_| Error::RecvError) + .map_err(|_| Error::RecvFailure) } } @@ -441,7 +441,7 @@ impl Transport for NymTransport { message: Message::ConnectionRequest(msg), recipient, }) - .map_err(|e| Error::OutboundSendError(e.to_string()))?; + .map_err(|e| Error::OutboundSendFailure(e.to_string()))?; debug!("sent outbound ConnectionRequest"); if let Some(waker) = waker.take() { @@ -555,7 +555,7 @@ mod test { message: msg, }), }) - .map_err(|e| Error::OutboundSendError(e.to_string()))?; + .map_err(|e| Error::OutboundSendFailure(e.to_string()))?; Ok(()) } } diff --git a/sdk/rust/nym-sdk/examples/surb-reply.rs b/sdk/rust/nym-sdk/examples/surb_reply.rs similarity index 100% rename from sdk/rust/nym-sdk/examples/surb-reply.rs rename to sdk/rust/nym-sdk/examples/surb_reply.rs diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index 3c6e6cf3f4..1c18a5f3d0 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -11,7 +11,9 @@ use crate::{Error, Result}; use futures::channel::mpsc; use futures::StreamExt; use log::warn; -use nym_client_core::client::base_client::storage::gateway_details::GatewayDetailsStore; +use nym_client_core::client::base_client::storage::gateway_details::{ + GatewayDetailsStore, PersistedGatewayDetails, +}; use nym_client_core::client::base_client::storage::{ Ephemeral, MixnetClientStorage, OnDiskPersistent, }; @@ -24,6 +26,7 @@ use nym_client_core::{ client::{base_client::BaseClientBuilder, replies::reply_storage::ReplyStorageBackend}, config::GatewayEndpointConfig, }; +use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, WG_TUN_DEVICE_ADDRESS}; use nym_socks5_client_core::config::Socks5; use nym_task::manager::TaskStatus; use nym_task::{TaskClient, TaskHandle}; @@ -35,7 +38,7 @@ use std::path::PathBuf; use url::Url; // The number of surbs to include in a message by default -const DEFAULT_NUMBER_OF_SURBS: u32 = 5; +const DEFAULT_NUMBER_OF_SURBS: u32 = 10; #[derive(Default)] pub struct MixnetClientBuilder { @@ -44,6 +47,7 @@ pub struct MixnetClientBuilder { gateway_config: Option, socks5_config: Option, + wireguard_mode: bool, wait_for_gateway: bool, custom_topology_provider: Option>, custom_gateway_transceiver: Option>, @@ -79,6 +83,7 @@ impl MixnetClientBuilder { storage_paths: None, gateway_config: None, socks5_config: None, + wireguard_mode: false, wait_for_gateway: false, custom_topology_provider: None, storage: storage_paths @@ -109,6 +114,7 @@ where storage_paths: None, gateway_config: None, socks5_config: None, + wireguard_mode: false, wait_for_gateway: false, custom_topology_provider: None, custom_gateway_transceiver: None, @@ -127,6 +133,7 @@ where storage_paths: self.storage_paths, gateway_config: self.gateway_config, socks5_config: self.socks5_config, + wireguard_mode: self.wireguard_mode, wait_for_gateway: self.wait_for_gateway, custom_topology_provider: self.custom_topology_provider, custom_gateway_transceiver: self.custom_gateway_transceiver, @@ -205,6 +212,13 @@ where self } + /// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded. + #[must_use] + pub fn with_wireguard_mode(mut self, wireguard_mode: bool) -> Self { + self.wireguard_mode = wireguard_mode; + self + } + /// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded. #[must_use] pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self { @@ -235,6 +249,7 @@ where .custom_gateway_transceiver(self.custom_gateway_transceiver) .custom_topology_provider(self.custom_topology_provider) .custom_shutdown(self.custom_shutdown) + .wireguard_mode(self.wireguard_mode) .wait_for_gateway(self.wait_for_gateway) .force_tls(self.force_tls); @@ -274,6 +289,9 @@ where /// advanced usage of custom gateways custom_gateway_transceiver: Option>, + /// If the client connects via Wireguard tunnel to the gateway. + wireguard_mode: bool, + /// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded. wait_for_gateway: bool, @@ -326,6 +344,7 @@ where storage, custom_topology_provider: None, custom_gateway_transceiver: None, + wireguard_mode: false, wait_for_gateway: false, force_tls: false, custom_shutdown: None, @@ -356,6 +375,12 @@ where self } + #[must_use] + pub fn wireguard_mode(mut self, wireguard_mode: bool) -> Self { + self.wireguard_mode = wireguard_mode; + self + } + #[must_use] pub fn wait_for_gateway(mut self, wait_for_gateway: bool) -> Self { self.wait_for_gateway = wait_for_gateway; @@ -503,11 +528,7 @@ where let known_gateway = self.has_valid_gateway_info().await; - let mut base_builder: BaseClientBuilder<_, _> = - BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) - .with_wait_for_gateway(self.wait_for_gateway); - - if !known_gateway { + let mut base_builder: BaseClientBuilder<_, _> = if !known_gateway { let selection_spec = GatewaySelectionSpecification::new( self.config.user_chosen_gateway, None, @@ -515,14 +536,49 @@ where ); let mut rng = OsRng; + let mut available_gateways = current_gateways(&mut rng, &nym_api_endpoints).await?; + if self.wireguard_mode { + available_gateways + .iter_mut() + .for_each(|node| node.host = WG_TUN_DEVICE_ADDRESS.parse().unwrap()); + } let setup = GatewaySetup::New { specification: selection_spec, - available_gateways: current_gateways(&mut rng, &nym_api_endpoints).await?, + available_gateways, overwrite_data: !self.config.key_mode.is_keep(), }; - base_builder = base_builder.with_gateway_setup(setup) - } + BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) + .with_wait_for_gateway(self.wait_for_gateway) + .with_gateway_setup(setup) + } else if self.wireguard_mode { + if let Ok(PersistedGatewayDetails::Default(mut config)) = self + .storage + .gateway_details_store() + .load_gateway_details() + .await + { + config.details.gateway_listener = format!( + "ws://{}:{}", + WG_TUN_DEVICE_ADDRESS, DEFAULT_CLIENT_LISTENING_PORT + ); + if let Err(e) = self + .storage + .gateway_details_store() + .store_gateway_details(&PersistedGatewayDetails::Default(config)) + .await + { + warn!("Could not switch to using wireguard mode - {:?}", e); + } + } else { + warn!("Storage type not supported with wireguard mode"); + } + BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) + .with_wait_for_gateway(self.wait_for_gateway) + } else { + BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client) + .with_wait_for_gateway(self.wait_for_gateway) + }; if let Some(topology_provider) = self.custom_topology_provider { base_builder = base_builder.with_topology_provider(topology_provider); diff --git a/sdk/typescript/codegen/contract-clients/package.json b/sdk/typescript/codegen/contract-clients/package.json index 99f6bf253a..b4f91a948b 100644 --- a/sdk/typescript/codegen/contract-clients/package.json +++ b/sdk/typescript/codegen/contract-clients/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/contract-clients", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "description": "A client for all Nym smart contracts", "license": "Apache-2.0", "author": "Nym Technologies SA", diff --git a/sdk/typescript/docs/code-examples/mixfetch-example-code.mdx b/sdk/typescript/docs/code-examples/mixfetch-example-code.mdx index 70ba77f9d1..780df512ed 100644 --- a/sdk/typescript/docs/code-examples/mixfetch-example-code.mdx +++ b/sdk/typescript/docs/code-examples/mixfetch-example-code.mdx @@ -8,16 +8,21 @@ import Box from '@mui/material/Box'; import { mixFetch } from '@nymproject/mix-fetch-full-fat'; import Stack from '@mui/material/Stack'; import Paper from '@mui/material/Paper'; +import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; const defaultUrl = 'https://nymtech.net/favicon.svg'; const args = { mode: 'unsafe-ignore-cors' }; -const mixFetchOptions = { - preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', + + +const mixFetchOptions: SetupMixFetchOps = { + preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS preferredNetworkRequester: 'GiRjFWrMxt58pEMuusm4yT3RxoMD1MMPrR9M2N4VWRJP.3CNZBPq4vg7v7qozjGjdPMXcvDmkbWPCgbGCjQVw9n6Z@2xU4CBE6QiiYt6EyBXSALwxkNvM7gqJfjHXaMkjiFmYW', mixFetchOverride: { requestTimeoutMs: 60_000, }, + forceTls: true, // force WSS + extra: {}, }; export const MixFetch = () => { @@ -77,4 +82,5 @@ export const MixFetch = () => { ); }; + ``` diff --git a/sdk/typescript/docs/code-examples/traffic-example-code.mdx b/sdk/typescript/docs/code-examples/traffic-example-code.mdx index d64b300479..351e5bdbde 100644 --- a/sdk/typescript/docs/code-examples/traffic-example-code.mdx +++ b/sdk/typescript/docs/code-examples/traffic-example-code.mdx @@ -25,6 +25,7 @@ export const Traffic = () => { await client?.client.start({ clientId: crypto.randomUUID(), nymApiUrl, + forceTls: true, // force WSS }); client?.events.subscribeToConnected((e) => { diff --git a/sdk/typescript/docs/components/client/index.ts b/sdk/typescript/docs/components/client/index.ts index afaf1253e8..ba7a70ef76 100644 --- a/sdk/typescript/docs/components/client/index.ts +++ b/sdk/typescript/docs/components/client/index.ts @@ -6,7 +6,7 @@ export const mainnetSettings = { }; export const qaSettings = { - url: 'wss://sandbox-validator1.nymtech.net/', + url: 'wss://rpc.sandbox.nymtech.net', mixnetContractAddress: 'n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav', mnemonic: process.env.QA_MNEMONIC, address: 'n13uryxldwdllpakevsmt6n0uyfn3kgr2wvj5dnf', diff --git a/sdk/typescript/docs/package.json b/sdk/typescript/docs/package.json index 2d877b8888..f4c99e0a38 100644 --- a/sdk/typescript/docs/package.json +++ b/sdk/typescript/docs/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/ts-sdk-docs", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "description": "Nym Typescript SDK Docs", "license": "Apache-2.0", "author": "Nym Technologies SA", @@ -28,9 +28,9 @@ "@mui/icons-material": "^5.14.9", "@mui/lab": "^5.0.0-alpha.145", "@mui/material": "^5.14.8", - "@nymproject/contract-clients": ">=1.2.4-rc.1 || ^1", - "@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.1 || ^1", - "@nymproject/sdk-full-fat": ">=1.2.4-rc.1 || ^1", + "@nymproject/contract-clients": ">=1.2.4-rc.2 || ^1", + "@nymproject/mix-fetch-full-fat": ">=1.2.4-rc.2 || ^1", + "@nymproject/sdk-full-fat": ">=1.2.4-rc.2 || ^1", "chain-registry": "^1.19.0", "cosmjs-types": "^0.8.0", "next": "^13.4.19", @@ -50,4 +50,4 @@ "typescript": "^4.9.3" }, "private": false -} \ No newline at end of file +} diff --git a/sdk/typescript/docs/pages/examples/mix-fetch.mdx b/sdk/typescript/docs/pages/examples/mix-fetch.mdx index 6d839864d2..fbe9c70d93 100644 --- a/sdk/typescript/docs/pages/examples/mix-fetch.mdx +++ b/sdk/typescript/docs/pages/examples/mix-fetch.mdx @@ -16,6 +16,8 @@ in combination with their own configuration. If you are trying to access somethi 3. If you are using `mixFetch` in a web app with HTTPS you will need to use a gateway that has Secure Websockets to avoid getting a [mixed content](https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content) error. +4. For now, mixfetch doesn't work with SURBS, altough this may change in the future. + Read [this article](https://blog.nymtech.net/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more about mixFetch. @@ -29,18 +31,6 @@ Read [this article](https://blog.nymtech.net/mixfetch-like-the-fetch-api-but-via // For mainnet import type { SetupMixFetchOps } from '@nymproject/mix-fetch'; -const extra = { - hiddenGateways: [ - { - owner: 'n1kymvkx6vsq7pvn6hfurkpg06h3j4gxj4em7tlg', - host: 'gateway1.nymtech.net', - explicitIp: '213.219.38.119', - identityKey: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', - sphinxKey: 'CYcrjoJ8GT7Dp54zViUyyRUfegeRCyPifWQZHRgMZrfX', - }, - ], -}; - const mixFetchOptions: SetupMixFetchOps = { preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS preferredNetworkRequester: @@ -49,7 +39,7 @@ const mixFetchOptions: SetupMixFetchOps = { requestTimeoutMs: 60_000, }, forceTls: true, // force WSS - extra, // manually set the gateway details for WSS so certificates will work for hostname + extra: {}, }; ``` @@ -84,10 +74,6 @@ import { mixFetch } from "@nymproject/mix-fetch-full-fat"; ##### Example: using the `mixFetch` client: - - Again, for this example, we will be using the `full-fat` version of the ESM SDK. - - `Get` and `Post` outputs will be observable from your console. ```ts @@ -95,18 +81,6 @@ import "./App.css"; import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat'; import React from 'react'; -const extra = { - hiddenGateways: [ - { - owner: 'n1kymvkx6vsq7pvn6hfurkpg06h3j4gxj4em7tlg', - host: 'gateway1.nymtech.net', - explicitIp: '213.219.38.119', - identityKey: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', - sphinxKey: 'CYcrjoJ8GT7Dp54zViUyyRUfegeRCyPifWQZHRgMZrfX', - }, - ], -}; - const mixFetchOptions: SetupMixFetchOps = { preferredGateway: 'E3mvZTHQCdBvhfr178Swx9g4QG3kkRUun7YnToLMcMbM', // with WSS preferredNetworkRequester: @@ -115,7 +89,7 @@ const mixFetchOptions: SetupMixFetchOps = { requestTimeoutMs: 60_000, }, forceTls: true, // force WSS - extra + extra: {}, }; diff --git a/sdk/typescript/docs/pages/examples/nym-smart-contracts.mdx b/sdk/typescript/docs/pages/examples/nym-smart-contracts.mdx index 0c9ffb73c1..669d284611 100644 --- a/sdk/typescript/docs/pages/examples/nym-smart-contracts.mdx +++ b/sdk/typescript/docs/pages/examples/nym-smart-contracts.mdx @@ -9,7 +9,7 @@ Lists of the different available clients and methods from the `Contract Clients` | Client name | Functionality| Methods list | | :-------------: | :----------: | :----------: | | Coconut Bandwidth Client| Manages the depositing and release of funds. Tracks double spending. | [Coconut Bandwidth](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutBandwidth.client.ts) | -| Coconut DKG Client | Allows signers partcipating in issuing Coconut credentials to derive keys to be used. | [Coconut DKG](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) | +| Coconut DKG Client | Allows signers participating in issuing Coconut credentials to derive keys to be used. | [Coconut DKG](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/CoconutDkg.client.ts) | | Cw3FlexMultisig Client | Used by the Coconut APIs to issue credentials. [This](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw3-flex-multisig) is a multisig contract that is backed by the cw4 (group) contract, which independently maintains the voter set. | [Cw3Flex Multisig](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw3FlexMultisig.client.ts) | | Cw4Group Client | Used by the Coconut APIs to issue credentials. [Cw4 Group](https://github.com/CosmWasm/cw-plus/tree/main/contracts/cw4-group) stores a set of members along with an admin, and allows the admin to update the state. | [Cw4Group](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Cw4Group.client.ts) | | Mixnet Client | Manages the network topology of the mixnet, tracking delegations and rewards. | [Mixnet](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Mixnet.client.ts) | @@ -18,11 +18,6 @@ Lists of the different available clients and methods from the `Contract Clients` | Vesting Client | Manages NYM token vesting functionality. | [Vesting](https://github.com/nymtech/nym/blob/develop/sdk/typescript/codegen/contract-clients/src/Vesting.client.ts) | -Depending on your app or project's architecture, this could be any of the ESM or CJS versions of the `Contract Clients`. - - This and the following examples will use the ESbuild bundler. - If you'd like to use another one, we will document different bundlers and polyfills in the [bundling](https://sdk.nymtech.net/bundling) page. - ##### Environment Setup Begin by creating a directory and configuring your application environment: diff --git a/sdk/typescript/docs/pages/playground/traffic.mdx b/sdk/typescript/docs/pages/playground/traffic.mdx index 8435f3b2f0..6c845227e2 100644 --- a/sdk/typescript/docs/pages/playground/traffic.mdx +++ b/sdk/typescript/docs/pages/playground/traffic.mdx @@ -4,9 +4,6 @@ import { Traffic } from '../../components/traffic'; import Box from '@mui/material/Box'; import FormattedTrafficExampleCode from '../../code-examples/traffic-example-code.mdx'; - - Currently, due to SSL-related issues, the mixnet client is functional exclusively in local development environments. Unless you clone this repository or create your own build, you may encounter limitations when attempting to test this example. - Use this tool to experiment with the mixnet: send and receive messages! diff --git a/sdk/typescript/examples/chat-app/parcel/package.json b/sdk/typescript/examples/chat-app/parcel/package.json index 5902778256..0f9dbebffd 100644 --- a/sdk/typescript/examples/chat-app/parcel/package.json +++ b/sdk/typescript/examples/chat-app/parcel/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-plain-html-parcel", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM and plain HTML bundled with Parcel v2", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/examples/chat-app/plain-html/package.json b/sdk/typescript/examples/chat-app/plain-html/package.json index 907de17a93..4d3915855c 100644 --- a/sdk/typescript/examples/chat-app/plain-html/package.json +++ b/sdk/typescript/examples/chat-app/plain-html/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-plain-html", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM and plain HTML", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json index 076ad4ebf3..6deb168308 100644 --- a/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json +++ b/sdk/typescript/examples/chat-app/react-webpack-with-theme-example/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-react-webpack-wasm", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM, React, Webpack, Typescript and the Nym theme + components library", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/examples/chrome-extension/package.json b/sdk/typescript/examples/chrome-extension/package.json index 167863bd7c..4a3759680a 100644 --- a/sdk/typescript/examples/chrome-extension/package.json +++ b/sdk/typescript/examples/chrome-extension/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-chrome-extension", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "This is an example of how Nym can be used within the context of a Chrome extension.", "license": "ISC", "author": "", diff --git a/sdk/typescript/examples/firefox-extension/package.json b/sdk/typescript/examples/firefox-extension/package.json index 8204cf7e13..9ca8a5111b 100644 --- a/sdk/typescript/examples/firefox-extension/package.json +++ b/sdk/typescript/examples/firefox-extension/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-firefox-extension", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "This is an example of how Nym can be used within the context of a Firefox extension.", "license": "ISC", "author": "", diff --git a/sdk/typescript/examples/mix-fetch/browser/package.json b/sdk/typescript/examples/mix-fetch/browser/package.json index 69efa51b2f..c5e4ffbdcd 100644 --- a/sdk/typescript/examples/mix-fetch/browser/package.json +++ b/sdk/typescript/examples/mix-fetch/browser/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch-example-parcel", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "license": "Apache-2.0", "scripts": { "build": "parcel build --no-cache --no-content-hash", diff --git a/sdk/typescript/examples/node-tester/parcel/package.json b/sdk/typescript/examples/node-tester/parcel/package.json index 311361410d..12dc69acf7 100644 --- a/sdk/typescript/examples/node-tester/parcel/package.json +++ b/sdk/typescript/examples/node-tester/parcel/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-node-tester-plain-html-parcel", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM and plain HTML bundled with Parcel v2", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/examples/node-tester/plain-html/package.json b/sdk/typescript/examples/node-tester/plain-html/package.json index 8e9c645e1c..4fe5e3b7be 100644 --- a/sdk/typescript/examples/node-tester/plain-html/package.json +++ b/sdk/typescript/examples/node-tester/plain-html/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-node-tester-plain-html", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM node tester and plain HTML", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/examples/node-tester/react/package.json b/sdk/typescript/examples/node-tester/react/package.json index e391f87d5a..db804c92fa 100644 --- a/sdk/typescript/examples/node-tester/react/package.json +++ b/sdk/typescript/examples/node-tester/react/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-example-node-tester-react", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "description": "An example project that uses WASM node tester and React", "license": "Apache-2.0", "scripts": { diff --git a/sdk/typescript/packages/mix-fetch-node/package.json b/sdk/typescript/packages/mix-fetch-node/package.json index 07f345c9c8..3367b87244 100644 --- a/sdk/typescript/packages/mix-fetch-node/package.json +++ b/sdk/typescript/packages/mix-fetch-node/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch-node", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "description": "This package is a drop-in replacement for `fetch` in NodeJS to send HTTP requests over the Nym Mixnet.", "license": "Apache-2.0", "author": "Nym Technologies SA", @@ -28,7 +28,7 @@ "tsc": "tsc --noEmit true" }, "dependencies": { - "@nymproject/mix-fetch-wasm-node": ">=1.2.4-rc.1 || ^1", + "@nymproject/mix-fetch-wasm-node": ">=1.2.4-rc.2 || ^1", "comlink": "^4.3.1", "fake-indexeddb": "^5.0.0", "node-fetch": "^3.3.2", @@ -68,4 +68,4 @@ }, "private": false, "types": "./dist/cjs/index.d.ts" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/mix-fetch/internal-dev/package.json b/sdk/typescript/packages/mix-fetch/internal-dev/package.json index ffee8a5c7c..8c97273244 100644 --- a/sdk/typescript/packages/mix-fetch/internal-dev/package.json +++ b/sdk/typescript/packages/mix-fetch/internal-dev/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch-tester-webpack", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "license": "Apache-2.0", "scripts": { "build": "webpack build --progress --config webpack.prod.js", diff --git a/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json b/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json index 4a92c88588..1aa9dc375d 100644 --- a/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json +++ b/sdk/typescript/packages/mix-fetch/internal-dev/parcel/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch-tester-parcel", - "version": "1.0.4-rc.1", + "version": "1.0.4-rc.2", "license": "Apache-2.0", "scripts": { "build": "npx parcel build --no-cache --no-content-hash", diff --git a/sdk/typescript/packages/mix-fetch/package.json b/sdk/typescript/packages/mix-fetch/package.json index d0b060a186..381ea96372 100644 --- a/sdk/typescript/packages/mix-fetch/package.json +++ b/sdk/typescript/packages/mix-fetch/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/mix-fetch", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "description": "This package is a drop-in replacement for `fetch` to send HTTP requests over the Nym Mixnet.", "license": "Apache-2.0", "author": "Nym Technologies SA", @@ -34,7 +34,7 @@ "tsc": "tsc --noEmit true" }, "dependencies": { - "@nymproject/mix-fetch-wasm": ">=1.2.4-rc.1 || ^1", + "@nymproject/mix-fetch-wasm": ">=1.2.4-rc.2 || ^1", "comlink": "^4.3.1" }, "devDependencies": { @@ -82,4 +82,4 @@ "private": false, "type": "module", "types": "./dist/esm/index.d.ts" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/node-tester/package.json b/sdk/typescript/packages/node-tester/package.json index bb585756be..eb377423bd 100644 --- a/sdk/typescript/packages/node-tester/package.json +++ b/sdk/typescript/packages/node-tester/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/node-tester", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "description": "This package provides a tester that can send test packets to mixnode that is part of the Nym Mixnet.", "license": "Apache-2.0", "author": "Nym Technologies SA", @@ -25,7 +25,7 @@ "tsc": "tsc --noEmit true" }, "dependencies": { - "@nymproject/nym-node-tester-wasm": ">=1.2.4-rc.1 || ^1", + "@nymproject/nym-node-tester-wasm": ">=1.2.4-rc.2 || ^1", "comlink": "^4.3.1" }, "devDependencies": { @@ -71,4 +71,4 @@ "private": false, "type": "module", "types": "./dist/esm/index.d.ts" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/nodejs-client/package.json b/sdk/typescript/packages/nodejs-client/package.json index cc73204f02..e8df393fc4 100644 --- a/sdk/typescript/packages/nodejs-client/package.json +++ b/sdk/typescript/packages/nodejs-client/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/nodejs-client", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "license": "Apache-2.0", "author": "Nym Technologies SA", "files": [ @@ -25,7 +25,7 @@ "tsc": "tsc --noEmit true" }, "dependencies": { - "@nymproject/nym-client-wasm-node": ">=1.2.4-rc.1 || ^1", + "@nymproject/nym-client-wasm-node": ">=1.2.4-rc.2 || ^1", "comlink": "^4.3.1", "fake-indexeddb": "^4.0.2", "rollup-plugin-polyfill": "^4.2.0", @@ -66,4 +66,4 @@ }, "private": false, "types": "./dist/index.d.ts" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/nodejs-client/src/worker.ts b/sdk/typescript/packages/nodejs-client/src/worker.ts index 75c7fadd46..a4e4eaeaf5 100644 --- a/sdk/typescript/packages/nodejs-client/src/worker.ts +++ b/sdk/typescript/packages/nodejs-client/src/worker.ts @@ -107,7 +107,7 @@ class ClientWrapper { send = async ({ payload, recipient, - replySurbs = 0, + replySurbs = 10, }: { payload: Uint8Array; recipient: string; diff --git a/sdk/typescript/packages/sdk-react/package.json b/sdk/typescript/packages/sdk-react/package.json index 3d5077c292..6a163d7269 100644 --- a/sdk/typescript/packages/sdk-react/package.json +++ b/sdk/typescript/packages/sdk-react/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk-react", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "license": "Apache-2.0", "author": "Nym Technologies SA", "files": [ diff --git a/sdk/typescript/packages/sdk/package.json b/sdk/typescript/packages/sdk/package.json index 5a54835cf4..fabc99574a 100644 --- a/sdk/typescript/packages/sdk/package.json +++ b/sdk/typescript/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@nymproject/sdk", - "version": "1.2.4-rc.1", + "version": "1.2.4-rc.2", "license": "Apache-2.0", "author": "Nym Technologies SA", "files": [ @@ -31,7 +31,7 @@ "tsc": "tsc --noEmit true" }, "dependencies": { - "@nymproject/nym-client-wasm": ">=1.2.4-rc.1 || ^1", + "@nymproject/nym-client-wasm": ">=1.2.4-rc.2 || ^1", "comlink": "^4.3.1" }, "devDependencies": { @@ -81,4 +81,4 @@ "private": false, "type": "module", "types": "./dist/esm/index.d.ts" -} \ No newline at end of file +} diff --git a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts index 0f848433b8..0c65d27d79 100644 --- a/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts +++ b/sdk/typescript/packages/sdk/src/mixnet/wasm/worker.ts @@ -118,7 +118,7 @@ class ClientWrapper { send = async ({ payload, recipient, - replySurbs = 0, + replySurbs = 10, }: { payload: Uint8Array; recipient: string; diff --git a/service-providers/common/Cargo.toml b/service-providers/common/Cargo.toml index a0601f35af..88893e2c9f 100644 --- a/service-providers/common/Cargo.toml +++ b/service-providers/common/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-service-providers-common" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/service-providers/ip-packet-router/Cargo.toml b/service-providers/ip-packet-router/Cargo.toml index 3aa52688f4..01ae4fb897 100644 --- a/service-providers/ip-packet-router/Cargo.toml +++ b/service-providers/ip-packet-router/Cargo.toml @@ -9,20 +9,32 @@ edition.workspace = true license.workspace = true [dependencies] +bincode = "1.3.3" +bytes = "1.5.0" etherparse = "0.13.0" futures = { workspace = true } log = { workspace = true } nym-bin-common = { path = "../../common/bin-common" } nym-client-core = { path = "../../common/client-core" } nym-config = { path = "../../common/config" } +nym-exit-policy = { path = "../../common/exit-policy" } +nym-ip-packet-requests = { path = "../../common/ip-packet-requests" } +nym-network-requester = { path = "../network-requester" } nym-sdk = { path = "../../sdk/rust/nym-sdk" } nym-service-providers-common = { path = "../common" } nym-sphinx = { path = "../../common/nymsphinx" } nym-task = { path = "../../common/task" } +nym-tun = { path = "../../common/tun" } nym-wireguard = { path = "../../common/wireguard" } nym-wireguard-types = { path = "../../common/wireguard-types" } +rand = "0.8.5" +reqwest.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tap.workspace = true thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] } +url.workspace = true + +[target.'cfg(target_os = "linux")'.dependencies] +tokio-tun = "0.11.2" diff --git a/service-providers/ip-packet-router/src/config/mod.rs b/service-providers/ip-packet-router/src/config/mod.rs index 4b6b811bda..d8c9aac583 100644 --- a/service-providers/ip-packet-router/src/config/mod.rs +++ b/service-providers/ip-packet-router/src/config/mod.rs @@ -2,7 +2,8 @@ pub use nym_client_core::config::Config as BaseClientConfig; use nym_bin_common::logging::LoggingSettings; use nym_config::{ - must_get_home, save_formatted_config_to_file, NymConfigTemplate, DEFAULT_CONFIG_DIR, + defaults::mainnet, must_get_home, save_formatted_config_to_file, + serde_helpers::de_maybe_stringified, NymConfigTemplate, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, }; use nym_service_providers_common::DEFAULT_SERVICE_PROVIDERS_DIR; @@ -11,6 +12,7 @@ use std::{ io, path::{Path, PathBuf}, }; +use url::Url; use crate::config::persistence::IpPacketRouterPaths; @@ -55,6 +57,9 @@ pub struct Config { #[serde(flatten)] pub base: BaseClientConfig, + #[serde(default)] + pub ip_packet_router: IpPacketRouter, + pub storage_paths: IpPacketRouterPaths, pub logging: LoggingSettings, @@ -70,6 +75,7 @@ impl Config { pub fn new>(id: S) -> Self { Config { base: BaseClientConfig::new(id.as_ref(), env!("CARGO_PKG_VERSION")), + ip_packet_router: Default::default(), storage_paths: IpPacketRouterPaths::new_base(default_data_directory(id.as_ref())), logging: Default::default(), } @@ -101,4 +107,33 @@ impl Config { // no other sections have explicit requirements (yet) self.base.validate() } + + #[doc(hidden)] + pub fn set_no_poisson_process(&mut self) { + self.base.set_no_poisson_process() + } +} + +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)] +#[serde(default, deny_unknown_fields)] +pub struct IpPacketRouter { + /// Disable Poisson sending rate. + pub disable_poisson_rate: bool, + + /// Specifies the url for an upstream source of the exit policy used by this node. + #[serde(deserialize_with = "de_maybe_stringified")] + pub upstream_exit_policy_url: Option, +} + +impl Default for IpPacketRouter { + fn default() -> Self { + IpPacketRouter { + disable_poisson_rate: true, + upstream_exit_policy_url: Some( + mainnet::EXIT_POLICY_URL + .parse() + .expect("invalid default exit policy URL"), + ), + } + } } diff --git a/service-providers/ip-packet-router/src/constants.rs b/service-providers/ip-packet-router/src/constants.rs new file mode 100644 index 0000000000..8fc028a88d --- /dev/null +++ b/service-providers/ip-packet-router/src/constants.rs @@ -0,0 +1,9 @@ +use std::time::Duration; + +// The interface used to route traffic +pub const TUN_BASE_NAME: &str = "nymtun"; +pub const TUN_DEVICE_ADDRESS: &str = "10.0.0.1"; +pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0"; + +pub(crate) const DISCONNECT_TIMER_INTERVAL: Duration = Duration::from_secs(10); +pub(crate) const CLIENT_INACTIVITY_TIMEOUT: Duration = Duration::from_secs(5 * 60); diff --git a/service-providers/ip-packet-router/src/error.rs b/service-providers/ip-packet-router/src/error.rs index 8bf599e232..77b2797c8e 100644 --- a/service-providers/ip-packet-router/src/error.rs +++ b/service-providers/ip-packet-router/src/error.rs @@ -1,4 +1,7 @@ +use std::net::{IpAddr, SocketAddr}; + pub use nym_client_core::error::ClientCoreError; +use nym_exit_policy::PolicyError; #[derive(thiserror::Error, Debug)] pub enum IpPacketRouterError { @@ -27,9 +30,62 @@ pub enum IpPacketRouterError { #[error("the entity wrapping the network requester has disconnected")] DisconnectedParent, + #[error("received packet has an invalid version: {0}")] + InvalidPacketVersion(u8), + + #[error("failed to serialize response packet: {source}")] + FailedToSerializeResponsePacket { source: Box }, + + #[error("failed to deserialize tagged packet: {source}")] + FailedToDeserializeTaggedPacket { source: bincode::Error }, + #[error("failed to parse incoming packet: {source}")] PacketParseFailed { source: etherparse::ReadError }, #[error("parsed packet is missing IP header")] - PacketMissingHeader, + PacketMissingIpHeader, + + #[error("parsed packet is missing transport header")] + PacketMissingTransportHeader, + + #[error("failed to write packet to tun")] + FailedToWritePacketToTun, + + #[error("failed to send packet to mixnet: {source}")] + FailedToSendPacketToMixnet { source: nym_sdk::Error }, + + #[error("the provided socket address, '{addr}' is not covered by the exit policy!")] + AddressNotCoveredByExitPolicy { addr: SocketAddr }, + + #[error("the provided ip address, '{ip}' is not covered by the exit policy!")] + IpNotCoveredByExitPolicy { ip: IpAddr }, + + #[error("failed filter check: '{addr}'")] + AddressFailedFilterCheck { addr: SocketAddr }, + + #[error("failed filter check: '{ip}'")] + IpFailedFilterCheck { ip: IpAddr }, + + #[error("failed to apply the exit policy: {source}")] + ExitPolicyFailure { + #[from] + source: PolicyError, + }, + + #[error("the url provided for the upstream exit policy source is malformed: {source}")] + MalformedExitPolicyUpstreamUrl { + #[source] + source: reqwest::Error, + }, + + #[error("can't setup an exit policy without any upstream urls")] + NoUpstreamExitPolicy, + + #[error("no recipient in response packet")] + NoRecipientInResponse, + + #[error("failed to update client activity")] + FailedToUpdateClientActivity, } + +pub type Result = std::result::Result; diff --git a/service-providers/ip-packet-router/src/ip_packet_router.rs b/service-providers/ip-packet-router/src/ip_packet_router.rs new file mode 100644 index 0000000000..bcc0bf0601 --- /dev/null +++ b/service-providers/ip-packet-router/src/ip_packet_router.rs @@ -0,0 +1,178 @@ +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] +#![cfg_attr(not(target_os = "linux"), allow(unused_imports))] + +use std::path::Path; + +use futures::channel::oneshot; +use nym_client_core::{ + client::mix_traffic::transceiver::GatewayTransceiver, HardcodedTopologyProvider, + TopologyProvider, +}; +use nym_sdk::mixnet::Recipient; +use nym_task::{TaskClient, TaskHandle}; + +use crate::{ + error::IpPacketRouterError, + request_filter::{self, RequestFilter}, + Config, +}; + +pub struct OnStartData { + // to add more fields as required + pub address: Recipient, + + pub request_filter: RequestFilter, +} + +impl OnStartData { + pub fn new(address: Recipient, request_filter: RequestFilter) -> Self { + Self { + address, + request_filter, + } + } +} + +pub struct IpPacketRouter { + #[allow(unused)] + config: Config, + wait_for_gateway: bool, + custom_topology_provider: Option>, + custom_gateway_transceiver: Option>, + shutdown: Option, + on_start: Option>, +} + +impl IpPacketRouter { + pub fn new(config: Config) -> Self { + Self { + config, + wait_for_gateway: false, + custom_topology_provider: None, + custom_gateway_transceiver: None, + shutdown: None, + on_start: None, + } + } + + #[must_use] + pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self { + self.shutdown = Some(shutdown); + self + } + + #[must_use] + pub fn with_custom_gateway_transceiver( + mut self, + gateway_transceiver: Box, + ) -> Self { + self.custom_gateway_transceiver = Some(gateway_transceiver); + self + } + + #[must_use] + pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self { + self.wait_for_gateway = wait_for_gateway; + self + } + + #[must_use] + pub fn with_on_start(mut self, on_start: oneshot::Sender) -> Self { + self.on_start = Some(on_start); + self + } + + #[must_use] + pub fn with_custom_topology_provider( + mut self, + topology_provider: Box, + ) -> Self { + self.custom_topology_provider = Some(topology_provider); + self + } + + pub fn with_stored_topology>( + mut self, + file: P, + ) -> Result { + self.custom_topology_provider = + Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?)); + Ok(self) + } + + #[cfg(not(target_os = "linux"))] + pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> { + todo!("service provider is not yet supported on this platform") + } + + #[cfg(target_os = "linux")] + pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> { + // Used to notify tasks to shutdown. Not all tasks fully supports this (yet). + + use crate::{mixnet_listener, tun_listener}; + let task_handle: TaskHandle = self.shutdown.map(Into::into).unwrap_or_default(); + + // Connect to the mixnet + let mixnet_client = crate::mixnet_client::create_mixnet_client( + &self.config.base, + task_handle.get_handle().named("nym_sdk::MixnetClient"), + self.custom_gateway_transceiver, + self.custom_topology_provider, + self.wait_for_gateway, + &self.config.storage_paths.common_paths, + ) + .await?; + + let self_address = *mixnet_client.nym_address(); + + // Create the TUN device that we interact with the rest of the world with + let config = nym_tun::tun_device::TunDeviceConfig { + base_name: crate::constants::TUN_BASE_NAME.to_string(), + ip: crate::constants::TUN_DEVICE_ADDRESS.parse().unwrap(), + netmask: crate::constants::TUN_DEVICE_NETMASK.parse().unwrap(), + }; + let (tun_reader, tun_writer) = + tokio::io::split(nym_tun::tun_device::TunDevice::new_device_only(config)); + + // Channel used by the IpPacketRouter to signal connected and disconnected clients to the + // TunListener + let (connected_clients, connected_clients_rx) = mixnet_listener::ConnectedClients::new(); + // let (connected_client_tx, connected_client_rx) = tokio::sync::mpsc::unbounded_channel(); + + let tun_listener = tun_listener::TunListener { + tun_reader, + mixnet_client_sender: mixnet_client.split_sender(), + task_client: task_handle.get_handle(), + connected_clients: connected_clients_rx, + }; + tun_listener.start(); + + let request_filter = request_filter::RequestFilter::new(&self.config).await?; + request_filter.start_update_tasks().await; + + let mixnet_listener = mixnet_listener::MixnetListener { + _config: self.config, + request_filter: request_filter.clone(), + tun_writer, + mixnet_client, + task_handle, + connected_clients, + // connected_client_tx, + }; + + log::info!("The address of this client is: {self_address}"); + log::info!("All systems go. Press CTRL-C to stop the server."); + + if let Some(on_start) = self.on_start { + if on_start + .send(OnStartData::new(self_address, request_filter)) + .is_err() + { + // the parent has dropped the channel before receiving the response + return Err(IpPacketRouterError::DisconnectedParent); + } + } + + mixnet_listener.run().await + } +} diff --git a/service-providers/ip-packet-router/src/lib.rs b/service-providers/ip-packet-router/src/lib.rs index 22608cc5de..d3dab9bde1 100644 --- a/service-providers/ip-packet-router/src/lib.rs +++ b/service-providers/ip-packet-router/src/lib.rs @@ -1,316 +1,15 @@ -use std::{net::IpAddr, path::Path}; - -use error::IpPacketRouterError; -use futures::{channel::oneshot, StreamExt}; -use nym_client_core::{ - client::mix_traffic::transceiver::GatewayTransceiver, - config::disk_persistence::CommonClientPaths, HardcodedTopologyProvider, TopologyProvider, -}; -use nym_sdk::{ - mixnet::{InputMessage, MixnetMessageSender, Recipient}, - NymNetworkDetails, -}; -use nym_sphinx::receiver::ReconstructedMessage; -use nym_task::{connections::TransmissionLane, TaskClient, TaskHandle}; -use tap::TapFallible; - -use crate::config::BaseClientConfig; +#![cfg_attr(not(target_os = "linux"), allow(dead_code))] +#![cfg_attr(not(target_os = "linux"), allow(unused_imports))] pub use crate::config::Config; +pub use ip_packet_router::{IpPacketRouter, OnStartData}; pub mod config; +mod constants; pub mod error; - -// The interface used to route traffic -pub const TUN_BASE_NAME: &str = "nymtun"; -pub const TUN_DEVICE_ADDRESS: &str = "10.0.0.1"; -pub const TUN_DEVICE_NETMASK: &str = "255.255.255.0"; - -pub struct OnStartData { - // to add more fields as required - pub address: Recipient, -} - -impl OnStartData { - pub fn new(address: Recipient) -> Self { - Self { address } - } -} - -pub struct IpPacketRouterBuilder { - #[allow(unused)] - config: Config, - wait_for_gateway: bool, - custom_topology_provider: Option>, - custom_gateway_transceiver: Option>, - shutdown: Option, - on_start: Option>, -} - -impl IpPacketRouterBuilder { - pub fn new(config: Config) -> Self { - Self { - config, - wait_for_gateway: false, - custom_topology_provider: None, - custom_gateway_transceiver: None, - shutdown: None, - on_start: None, - } - } - - #[must_use] - pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self { - self.shutdown = Some(shutdown); - self - } - - #[must_use] - pub fn with_custom_gateway_transceiver( - mut self, - gateway_transceiver: Box, - ) -> Self { - self.custom_gateway_transceiver = Some(gateway_transceiver); - self - } - - #[must_use] - pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self { - self.wait_for_gateway = wait_for_gateway; - self - } - - #[must_use] - pub fn with_on_start(mut self, on_start: oneshot::Sender) -> Self { - self.on_start = Some(on_start); - self - } - - #[must_use] - pub fn with_custom_topology_provider( - mut self, - topology_provider: Box, - ) -> Self { - self.custom_topology_provider = Some(topology_provider); - self - } - - pub fn with_stored_topology>( - mut self, - file: P, - ) -> Result { - self.custom_topology_provider = - Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?)); - Ok(self) - } - - #[cfg(not(target_os = "linux"))] - pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> { - todo!("service provider is not yet supported on this platform") - } - - #[cfg(target_os = "linux")] - pub async fn run_service_provider(self) -> Result<(), IpPacketRouterError> { - // Used to notify tasks to shutdown. Not all tasks fully supports this (yet). - let task_handle: TaskHandle = self.shutdown.map(Into::into).unwrap_or_default(); - - // Connect to the mixnet - let mixnet_client = create_mixnet_client( - &self.config.base, - task_handle.get_handle().named("nym_sdk::MixnetClient"), - self.custom_gateway_transceiver, - self.custom_topology_provider, - self.wait_for_gateway, - &self.config.storage_paths.common_paths, - ) - .await?; - - let self_address = *mixnet_client.nym_address(); - - // Create the TUN device that we interact with the rest of the world with - let config = nym_wireguard::tun_device::TunDeviceConfig { - base_name: TUN_BASE_NAME.to_string(), - ip: TUN_DEVICE_ADDRESS.parse().unwrap(), - netmask: TUN_DEVICE_NETMASK.parse().unwrap(), - }; - let (tun, tun_task_tx, tun_task_response_rx) = nym_wireguard::tun_device::TunDevice::new( - nym_wireguard::tun_device::RoutingMode::new_nat(), - config, - ); - tun.start(); - - let ip_packet_router_service = IpPacketRouter { - _config: self.config, - // tun, - tun_task_tx, - tun_task_response_rx, - mixnet_client, - task_handle, - }; - - log::info!("The address of this client is: {self_address}"); - log::info!("All systems go. Press CTRL-C to stop the server."); - - if let Some(on_start) = self.on_start { - if on_start.send(OnStartData::new(self_address)).is_err() { - // the parent has dropped the channel before receiving the response - return Err(IpPacketRouterError::DisconnectedParent); - } - } - - ip_packet_router_service.run().await - } -} - -#[allow(unused)] -struct IpPacketRouter { - _config: Config, - // tun: nym_wireguard::tun_device::TunDevice, - tun_task_tx: nym_wireguard::tun_task_channel::TunTaskTx, - tun_task_response_rx: nym_wireguard::tun_task_channel::TunTaskResponseRx, - mixnet_client: nym_sdk::mixnet::MixnetClient, - task_handle: TaskHandle, -} - -#[allow(unused)] -impl IpPacketRouter { - async fn run(mut self) -> Result<(), IpPacketRouterError> { - let mut task_client = self.task_handle.fork("main_loop"); - - while !task_client.is_shutdown() { - tokio::select! { - _ = task_client.recv() => { - log::debug!("IpPacketRouter [main loop]: received shutdown"); - }, - msg = self.mixnet_client.next() => { - if let Some(msg) = msg { - self.on_message(msg).await.ok(); - } else { - log::trace!("IpPacketRouter [main loop]: stopping since channel closed"); - break; - }; - }, - packet = self.tun_task_response_rx.recv() => { - if let Some((_tag, packet)) = packet { - // Read recipient from env variable NYM_CLIENT_ADDR which is a base58 - // string of the nym-address of the client that the packet should be - // sent back to. - // - // In the near future we will let the client expose it's nym-address - // directly, and after that, provide SURBS - let recipient = std::env::var("NYM_CLIENT_ADDR").ok().and_then(|addr| { - Recipient::try_from_base58_string(addr).ok() - }); - - if let Some(recipient) = recipient { - let lane = TransmissionLane::General; - let packet_type = None; - let input_message = InputMessage::new_regular(recipient, packet, lane, packet_type); - - self.mixnet_client - .send(input_message) - .await - .tap_err(|err| { - log::error!("IpPacketRouter [main loop]: failed to send packet to mixnet: {err}"); - }) - .ok(); - } else { - log::error!("NYM_CLIENT_ADDR not set or invalid"); - } - } else { - log::trace!("IpPacketRouter [main loop]: stopping since channel closed"); - break; - } - } - - } - } - log::info!("IpPacketRouter: stopping"); - Ok(()) - } - - async fn on_message( - &mut self, - reconstructed: ReconstructedMessage, - ) -> Result<(), IpPacketRouterError> { - log::info!("Received message: {:?}", reconstructed.sender_tag); - - let headers = etherparse::SlicedPacket::from_ip(&reconstructed.message).map_err(|err| { - log::warn!("Received non-IP packet: {err}"); - IpPacketRouterError::PacketParseFailed { source: err } - })?; - - let (src_addr, dst_addr): (IpAddr, IpAddr) = match headers.ip { - Some(etherparse::InternetSlice::Ipv4(ipv4_header, _)) => ( - ipv4_header.source_addr().into(), - ipv4_header.destination_addr().into(), - ), - Some(etherparse::InternetSlice::Ipv6(ipv6_header, _)) => ( - ipv6_header.source_addr().into(), - ipv6_header.destination_addr().into(), - ), - None => { - log::warn!("Received non-IP packet"); - return Err(IpPacketRouterError::PacketMissingHeader); - } - }; - log::info!("Received packet: {src_addr} -> {dst_addr}"); - - // TODO: set the tag correctly. Can we just reuse sender_tag? - let peer_tag = 0; - self.tun_task_tx - .send((peer_tag, reconstructed.message)) - .await - .tap_err(|err| { - log::error!("Failed to send packet to tun device: {err}"); - }) - .ok(); - - Ok(()) - } -} - -// Helper function to create the mixnet client. -// This is NOT in the SDK since we don't want to expose any of the client-core config types. -// We could however consider moving it to a crate in common in the future. -// TODO: refactor this function and its arguments -#[allow(unused)] -async fn create_mixnet_client( - config: &BaseClientConfig, - shutdown: TaskClient, - custom_transceiver: Option>, - custom_topology_provider: Option>, - wait_for_gateway: bool, - paths: &CommonClientPaths, -) -> Result { - let debug_config = config.debug; - - let storage_paths = nym_sdk::mixnet::StoragePaths::from(paths.clone()); - - let mut client_builder = - nym_sdk::mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths) - .await - .map_err(|err| IpPacketRouterError::FailedToSetupMixnetClient { source: err })? - .network_details(NymNetworkDetails::new_from_env()) - .debug_config(debug_config) - .custom_shutdown(shutdown) - .with_wait_for_gateway(wait_for_gateway); - if !config.get_disabled_credentials_mode() { - client_builder = client_builder.enable_credentials_mode(); - } - if let Some(gateway_transceiver) = custom_transceiver { - client_builder = client_builder.custom_gateway_transceiver(gateway_transceiver); - } - if let Some(topology_provider) = custom_topology_provider { - client_builder = client_builder.custom_topology_provider(topology_provider); - } - - let mixnet_client = client_builder - .build() - .map_err(|err| IpPacketRouterError::FailedToSetupMixnetClient { source: err })?; - - mixnet_client - .connect_to_mixnet() - .await - .map_err(|err| IpPacketRouterError::FailedToConnectToMixnet { source: err }) -} +mod ip_packet_router; +mod mixnet_client; +mod mixnet_listener; +mod request_filter; +mod tun_listener; +mod util; diff --git a/service-providers/ip-packet-router/src/mixnet_client.rs b/service-providers/ip-packet-router/src/mixnet_client.rs new file mode 100644 index 0000000000..1d9695c001 --- /dev/null +++ b/service-providers/ip-packet-router/src/mixnet_client.rs @@ -0,0 +1,49 @@ +use nym_client_core::{config::disk_persistence::CommonClientPaths, TopologyProvider}; +use nym_sdk::{GatewayTransceiver, NymNetworkDetails}; +use nym_task::TaskClient; + +use crate::{config::BaseClientConfig, error::IpPacketRouterError}; + +// Helper function to create the mixnet client. +// This is NOT in the SDK since we don't want to expose any of the client-core config types. +// We could however consider moving it to a crate in common in the future. +// TODO: refactor this function and its arguments +pub(crate) async fn create_mixnet_client( + config: &BaseClientConfig, + shutdown: TaskClient, + custom_transceiver: Option>, + custom_topology_provider: Option>, + wait_for_gateway: bool, + paths: &CommonClientPaths, +) -> Result { + let debug_config = config.debug; + + let storage_paths = nym_sdk::mixnet::StoragePaths::from(paths.clone()); + + let mut client_builder = + nym_sdk::mixnet::MixnetClientBuilder::new_with_default_storage(storage_paths) + .await + .map_err(|err| IpPacketRouterError::FailedToSetupMixnetClient { source: err })? + .network_details(NymNetworkDetails::new_from_env()) + .debug_config(debug_config) + .custom_shutdown(shutdown) + .with_wait_for_gateway(wait_for_gateway); + if !config.get_disabled_credentials_mode() { + client_builder = client_builder.enable_credentials_mode(); + } + if let Some(gateway_transceiver) = custom_transceiver { + client_builder = client_builder.custom_gateway_transceiver(gateway_transceiver); + } + if let Some(topology_provider) = custom_topology_provider { + client_builder = client_builder.custom_topology_provider(topology_provider); + } + + let mixnet_client = client_builder + .build() + .map_err(|err| IpPacketRouterError::FailedToSetupMixnetClient { source: err })?; + + mixnet_client + .connect_to_mixnet() + .await + .map_err(|err| IpPacketRouterError::FailedToConnectToMixnet { source: err }) +} diff --git a/service-providers/ip-packet-router/src/mixnet_listener.rs b/service-providers/ip-packet-router/src/mixnet_listener.rs new file mode 100644 index 0000000000..7eef17cec2 --- /dev/null +++ b/service-providers/ip-packet-router/src/mixnet_listener.rs @@ -0,0 +1,453 @@ +use std::{ + collections::HashMap, + net::{IpAddr, SocketAddr}, +}; + +use futures::StreamExt; +use nym_ip_packet_requests::{ + DynamicConnectFailureReason, IpPacketRequest, IpPacketRequestData, IpPacketResponse, + StaticConnectFailureReason, +}; +use nym_sdk::mixnet::{MixnetMessageSender, Recipient}; +use nym_sphinx::receiver::ReconstructedMessage; +use nym_task::TaskHandle; +#[cfg(target_os = "linux")] +use tokio::io::AsyncWriteExt; + +use crate::{ + constants::{CLIENT_INACTIVITY_TIMEOUT, DISCONNECT_TIMER_INTERVAL}, + error::{IpPacketRouterError, Result}, + request_filter::{self}, + util::generate_new_ip, + util::{ + create_message::create_input_message, + parse_ip::{parse_packet, ParsedPacket}, + }, + Config, +}; + +#[cfg(target_os = "linux")] +pub(crate) struct MixnetListener { + pub(crate) _config: Config, + pub(crate) request_filter: request_filter::RequestFilter, + pub(crate) tun_writer: tokio::io::WriteHalf, + pub(crate) mixnet_client: nym_sdk::mixnet::MixnetClient, + pub(crate) task_handle: TaskHandle, + pub(crate) connected_clients: ConnectedClients, +} + +pub(crate) struct ConnectedClients { + clients: HashMap, + connected_client_tx: tokio::sync::mpsc::UnboundedSender, +} + +pub(crate) struct ConnectedClientsListener { + clients: HashMap, + pub(crate) connected_client_rx: tokio::sync::mpsc::UnboundedReceiver, +} + +impl ConnectedClientsListener { + pub(crate) fn get(&self, ip: &IpAddr) -> Option<&ConnectedClient> { + self.clients.get(ip) + } + + pub(crate) fn update(&mut self, event: ConnectedClientEvent) { + match event { + ConnectedClientEvent::Connect(connected_event) => { + let ConnectEvent { + ip, + nym_address, + mix_hops, + } = *connected_event; + log::trace!("Connect client: {ip}"); + self.clients.insert( + ip, + ConnectedClient { + nym_address, + mix_hops, + last_activity: std::time::Instant::now(), + }, + ); + } + ConnectedClientEvent::Disconnect(DisconnectEvent(ip)) => { + log::trace!("Disconnect client: {ip}"); + self.clients.remove(&ip); + } + } + } +} + +impl ConnectedClients { + pub(crate) fn new() -> (Self, ConnectedClientsListener) { + let (connected_client_tx, connected_client_rx) = tokio::sync::mpsc::unbounded_channel(); + ( + Self { + clients: Default::default(), + connected_client_tx, + }, + ConnectedClientsListener { + clients: Default::default(), + connected_client_rx, + }, + ) + } + + fn is_ip_connected(&self, ip: &IpAddr) -> bool { + self.clients.contains_key(ip) + } + + fn is_nym_address_connected(&self, nym_address: &Recipient) -> bool { + self.clients + .values() + .any(|client| client.nym_address == *nym_address) + } + + fn get_ip(&self, nym_address: &Recipient) -> Option { + self.clients.iter().find_map(|(ip, client)| { + if client.nym_address == *nym_address { + Some(*ip) + } else { + None + } + }) + } + + fn get_client_by_nym_address(&self, nym_address: &Recipient) -> Option<&ConnectedClient> { + self.clients + .values() + .find(|client| client.nym_address == *nym_address) + } + + fn connect(&mut self, ip: IpAddr, nym_address: Recipient, mix_hops: Option) { + self.clients.insert( + ip, + ConnectedClient { + nym_address, + mix_hops, + last_activity: std::time::Instant::now(), + }, + ); + self.connected_client_tx + .send(ConnectedClientEvent::Connect(Box::new(ConnectEvent { + ip, + nym_address, + mix_hops, + }))) + .unwrap(); + } + + fn update_activity(&mut self, ip: &IpAddr) -> Result<()> { + if let Some(client) = self.clients.get_mut(ip) { + client.last_activity = std::time::Instant::now(); + Ok(()) + } else { + Err(IpPacketRouterError::FailedToUpdateClientActivity) + } + } + + fn disconnect_inactive_clients(&mut self) { + let now = std::time::Instant::now(); + let inactive_clients: Vec = self + .clients + .iter() + .filter_map(|(ip, client)| { + if now.duration_since(client.last_activity) > CLIENT_INACTIVITY_TIMEOUT { + Some(*ip) + } else { + None + } + }) + .collect(); + for ip in inactive_clients { + log::info!("Disconnect inactive client: {ip}"); + self.clients.remove(&ip); + self.connected_client_tx + .send(ConnectedClientEvent::Disconnect(DisconnectEvent(ip))) + .unwrap(); + } + } + + fn find_new_ip(&self) -> Option { + generate_new_ip::find_new_ip(&self.clients) + } +} + +pub(crate) struct ConnectedClient { + pub(crate) nym_address: Recipient, + pub(crate) mix_hops: Option, + pub(crate) last_activity: std::time::Instant, +} + +#[cfg(target_os = "linux")] +impl MixnetListener { + async fn on_static_connect_request( + &mut self, + connect_request: nym_ip_packet_requests::StaticConnectRequest, + ) -> Result> { + log::info!( + "Received static connect request from {sender_address}", + sender_address = connect_request.reply_to + ); + + let request_id = connect_request.request_id; + let requested_ip = connect_request.ip; + let reply_to = connect_request.reply_to; + let reply_to_hops = connect_request.reply_to_hops; + // TODO: ignoring reply_to_avg_mix_delays for now + + // Check that the IP is available in the set of connected clients + let is_ip_taken = self.connected_clients.is_ip_connected(&requested_ip); + + // Check that the nym address isn't already registered + let is_nym_address_taken = self.connected_clients.is_nym_address_connected(&reply_to); + + match (is_ip_taken, is_nym_address_taken) { + (true, true) => { + log::info!("Connecting an already connected client"); + if self + .connected_clients + .update_activity(&requested_ip) + .is_err() + { + log::error!("Failed to update activity for client"); + }; + Ok(Some(IpPacketResponse::new_static_connect_success( + request_id, reply_to, + ))) + } + (false, false) => { + log::info!("Connecting a new client"); + self.connected_clients + .connect(requested_ip, reply_to, reply_to_hops); + Ok(Some(IpPacketResponse::new_static_connect_success( + request_id, reply_to, + ))) + } + (true, false) => { + log::info!("Requested IP is not available"); + Ok(Some(IpPacketResponse::new_static_connect_failure( + request_id, + reply_to, + StaticConnectFailureReason::RequestedIpAlreadyInUse, + ))) + } + (false, true) => { + log::info!("Nym address is already registered"); + Ok(Some(IpPacketResponse::new_static_connect_failure( + request_id, + reply_to, + StaticConnectFailureReason::RequestedNymAddressAlreadyInUse, + ))) + } + } + } + + async fn on_dynamic_connect_request( + &mut self, + connect_request: nym_ip_packet_requests::DynamicConnectRequest, + ) -> Result> { + log::info!( + "Received dynamic connect request from {sender_address}", + sender_address = connect_request.reply_to + ); + + let request_id = connect_request.request_id; + let reply_to = connect_request.reply_to; + let reply_to_hops = connect_request.reply_to_hops; + // TODO: ignoring reply_to_avg_mix_delays for now + + // Check if it's the same client connecting again, then we just reuse the same IP + // TODO: this is problematic. Until we sign connect requests this means you can spam people + // with return traffic + + if let Some(existing_ip) = self.connected_clients.get_ip(&reply_to) { + log::info!("Found existing client for nym address"); + if self + .connected_clients + .update_activity(&existing_ip) + .is_err() + { + log::error!("Failed to update activity for client"); + } + return Ok(Some(IpPacketResponse::new_dynamic_connect_success( + request_id, + reply_to, + existing_ip, + ))); + } + + let Some(new_ip) = self.connected_clients.find_new_ip() else { + log::info!("No available IP address"); + return Ok(Some(IpPacketResponse::new_dynamic_connect_failure( + request_id, + reply_to, + DynamicConnectFailureReason::NoAvailableIp, + ))); + }; + + self.connected_clients + .connect(new_ip, reply_to, reply_to_hops); + Ok(Some(IpPacketResponse::new_dynamic_connect_success( + request_id, reply_to, new_ip, + ))) + } + + async fn on_data_request( + &mut self, + data_request: nym_ip_packet_requests::DataRequest, + ) -> Result> { + log::trace!("Received data request"); + + // We don't forward packets that we are not able to parse. BUT, there might be a good + // reason to still forward them. + // + // For example, if we are running in a mode where we are only supposed to forward + // packets to a specific destination, we might want to forward them anyway. + // + // TODO: look into this + let ParsedPacket { + packet_type, + src_addr, + dst_addr, + dst, + } = parse_packet(&data_request.ip_packet)?; + + let dst_str = dst.map_or(dst_addr.to_string(), |dst| dst.to_string()); + log::info!("Received packet: {packet_type}: {src_addr} -> {dst_str}"); + + // Check if there is a connected client for this src_addr. If there is, update the last activity time + // for the client. If there isn't, drop the packet. + if self.connected_clients.update_activity(&src_addr).is_err() { + log::info!("Dropping packet: no connected client for {src_addr}"); + return Ok(None); + } + + // Filter check + let dst = dst.unwrap_or_else(|| SocketAddr::new(dst_addr, 0)); + if !self.request_filter.check_address(&dst).await { + log::info!("Denied filter check: {dst}"); + // TODO: we could consider sending back a response here + return Err(IpPacketRouterError::AddressFailedFilterCheck { addr: dst }); + } + + // TODO: consider changing from Vec to bytes::Bytes? + let packet = data_request.ip_packet; + self.tun_writer + .write_all(&packet) + .await + .map_err(|_| IpPacketRouterError::FailedToWritePacketToTun)?; + + Ok(None) + } + async fn on_reconstructed_message( + &mut self, + reconstructed: ReconstructedMessage, + ) -> Result> { + log::debug!( + "Received message with sender_tag: {:?}", + reconstructed.sender_tag + ); + + // Check version of request + if let Some(version) = reconstructed.message.first() { + // The idea is that in the future we can add logic here to parse older versions to stay + // backwards compatible. + if *version != nym_ip_packet_requests::CURRENT_VERSION { + log::warn!("Received packet with invalid version"); + return Err(IpPacketRouterError::InvalidPacketVersion(*version)); + } + } + + let request = IpPacketRequest::from_reconstructed_message(&reconstructed) + .map_err(|err| IpPacketRouterError::FailedToDeserializeTaggedPacket { source: err })?; + + match request.data { + IpPacketRequestData::StaticConnect(connect_request) => { + self.on_static_connect_request(connect_request).await + } + IpPacketRequestData::DynamicConnect(connect_request) => { + self.on_dynamic_connect_request(connect_request).await + } + IpPacketRequestData::Data(data_request) => self.on_data_request(data_request).await, + } + } + + // When an incoming mixnet message triggers a response that we send back, such as during + // connect handshake. + async fn handle_response(&self, response: IpPacketResponse) -> Result<()> { + let Some(recipient) = response.recipient() else { + log::error!("no recipient in response packet, this should NOT happen!"); + return Err(IpPacketRouterError::NoRecipientInResponse); + }; + + let response_packet = response.to_bytes().map_err(|err| { + log::error!("Failed to serialize response packet"); + IpPacketRouterError::FailedToSerializeResponsePacket { source: err } + })?; + + // We could avoid this lookup if we check this when we create the response. + let mix_hops = self + .connected_clients + .get_client_by_nym_address(recipient) + .and_then(|c| c.mix_hops); + + let input_message = create_input_message(*recipient, response_packet, mix_hops); + self.mixnet_client + .send(input_message) + .await + .map_err(|err| IpPacketRouterError::FailedToSendPacketToMixnet { source: err }) + } + + pub(crate) async fn run(mut self) -> Result<()> { + let mut task_client = self.task_handle.fork("main_loop"); + let mut disconnect_timer = tokio::time::interval(DISCONNECT_TIMER_INTERVAL); + + while !task_client.is_shutdown() { + tokio::select! { + _ = task_client.recv() => { + log::debug!("IpPacketRouter [main loop]: received shutdown"); + }, + _ = disconnect_timer.tick() => { + self.connected_clients.disconnect_inactive_clients(); + }, + msg = self.mixnet_client.next() => { + if let Some(msg) = msg { + match self.on_reconstructed_message(msg).await { + Ok(Some(response)) => { + if let Err(err) = self.handle_response(response).await { + log::error!("Mixnet listener failed to handle response: {err}"); + } + }, + Ok(None) => { + continue; + }, + Err(err) => { + log::error!("Error handling mixnet message: {err}"); + } + + }; + } else { + log::trace!("IpPacketRouter [main loop]: stopping since channel closed"); + break; + }; + }, + + } + } + log::debug!("IpPacketRouter: stopping"); + Ok(()) + } +} + +pub(crate) enum ConnectedClientEvent { + Disconnect(DisconnectEvent), + Connect(Box), +} + +pub(crate) struct DisconnectEvent(pub(crate) IpAddr); + +pub(crate) struct ConnectEvent { + pub(crate) ip: IpAddr, + pub(crate) nym_address: Recipient, + pub(crate) mix_hops: Option, +} diff --git a/service-providers/ip-packet-router/src/request_filter/exit_policy/mod.rs b/service-providers/ip-packet-router/src/request_filter/exit_policy/mod.rs new file mode 100644 index 0000000000..a0eee039bc --- /dev/null +++ b/service-providers/ip-packet-router/src/request_filter/exit_policy/mod.rs @@ -0,0 +1,50 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::net::SocketAddr; + +use crate::error::IpPacketRouterError; +use nym_exit_policy::client::get_exit_policy; +use nym_exit_policy::ExitPolicy; +use reqwest::IntoUrl; +use url::Url; + +pub struct ExitPolicyRequestFilter { + upstream: Option, + policy: ExitPolicy, +} + +impl ExitPolicyRequestFilter { + pub(crate) async fn new_upstream(url: impl IntoUrl) -> Result { + let url = url + .into_url() + .map_err(|source| IpPacketRouterError::MalformedExitPolicyUpstreamUrl { source })?; + + Ok(ExitPolicyRequestFilter { + upstream: Some(url.clone()), + policy: get_exit_policy(url).await?, + }) + } + + #[allow(unused)] + pub(crate) fn new(policy: ExitPolicy) -> Self { + ExitPolicyRequestFilter { + upstream: None, + policy, + } + } + + pub fn policy(&self) -> &ExitPolicy { + &self.policy + } + + pub fn upstream(&self) -> Option<&Url> { + self.upstream.as_ref() + } + + pub(crate) async fn check(&self, addr: &SocketAddr) -> Result { + self.policy + .allows_sockaddr(addr) + .ok_or(IpPacketRouterError::AddressNotCoveredByExitPolicy { addr: *addr }) + } +} diff --git a/service-providers/ip-packet-router/src/request_filter/mod.rs b/service-providers/ip-packet-router/src/request_filter/mod.rs new file mode 100644 index 0000000000..2e3ca58b7e --- /dev/null +++ b/service-providers/ip-packet-router/src/request_filter/mod.rs @@ -0,0 +1,68 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::IpPacketRouterError; +use crate::request_filter::exit_policy::ExitPolicyRequestFilter; +use log::{info, warn}; +use std::{net::SocketAddr, sync::Arc}; + +pub mod exit_policy; + +enum RequestFilterInner { + ExitPolicy { + policy_filter: ExitPolicyRequestFilter, + }, +} + +#[derive(Clone)] +pub struct RequestFilter { + inner: Arc, +} + +impl RequestFilter { + pub(crate) async fn new(config: &Config) -> Result { + info!("setting up ExitPolicy based request filter..."); + Self::new_exit_policy_filter(config).await + } + + pub fn current_exit_policy_filter(&self) -> Option<&ExitPolicyRequestFilter> { + match &*self.inner { + RequestFilterInner::ExitPolicy { policy_filter } => Some(policy_filter), + } + } + + pub(crate) async fn start_update_tasks(&self) { + match &*self.inner { + RequestFilterInner::ExitPolicy { .. } => { + // nothing to do for the exit policy (yet; we might add a refresher at some point) + } + } + } + + async fn new_exit_policy_filter(config: &Config) -> Result { + let upstream_url = config + .ip_packet_router + .upstream_exit_policy_url + .as_ref() + .ok_or(IpPacketRouterError::NoUpstreamExitPolicy)?; + let policy_filter = ExitPolicyRequestFilter::new_upstream(upstream_url.clone()).await?; + Ok(RequestFilter { + inner: Arc::new(RequestFilterInner::ExitPolicy { policy_filter }), + }) + } + + pub(crate) async fn check_address(&self, address: &SocketAddr) -> bool { + match &*self.inner { + RequestFilterInner::ExitPolicy { policy_filter } => { + match policy_filter.check(address).await { + Err(err) => { + warn!("failed to validate '{address}' against the exit policy: {err}"); + false + } + Ok(res) => res, + } + } + } + } +} diff --git a/service-providers/ip-packet-router/src/tun_listener.rs b/service-providers/ip-packet-router/src/tun_listener.rs new file mode 100644 index 0000000000..5dcc59d205 --- /dev/null +++ b/service-providers/ip-packet-router/src/tun_listener.rs @@ -0,0 +1,94 @@ +use nym_ip_packet_requests::IpPacketResponse; +use nym_sdk::mixnet::MixnetMessageSender; +use nym_task::TaskClient; +#[cfg(target_os = "linux")] +use tokio::io::AsyncReadExt; + +use crate::{ + error::{IpPacketRouterError, Result}, + mixnet_listener::{self}, + util::{create_message::create_input_message, parse_ip::parse_dst_addr}, +}; + +// Reads packet from TUN and writes to mixnet client +#[cfg(target_os = "linux")] +pub(crate) struct TunListener { + pub(crate) tun_reader: tokio::io::ReadHalf, + pub(crate) mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender, + pub(crate) task_client: TaskClient, + pub(crate) connected_clients: mixnet_listener::ConnectedClientsListener, +} + +#[cfg(target_os = "linux")] +impl TunListener { + async fn handle_packet(&mut self, buf: &[u8], len: usize) -> Result<()> { + let Some(dst_addr) = parse_dst_addr(&buf[..len]) else { + log::warn!("Failed to parse packet"); + return Ok(()); + }; + + if let Some(mixnet_listener::ConnectedClient { + nym_address, + mix_hops, + .. + }) = self.connected_clients.get(&dst_addr) + { + let packet = buf[..len].to_vec(); + let response_packet = IpPacketResponse::new_ip_packet(packet.into()) + .to_bytes() + .map_err(|err| IpPacketRouterError::FailedToSerializeResponsePacket { + source: err, + })?; + let input_message = create_input_message(*nym_address, response_packet, *mix_hops); + + self.mixnet_client_sender + .send(input_message) + .await + .map_err(|err| IpPacketRouterError::FailedToSendPacketToMixnet { source: err })?; + } else { + log::info!("No registered nym-address for packet - dropping"); + } + + Ok(()) + } + + async fn run(mut self) -> Result<()> { + let mut buf = [0u8; 65535]; + while !self.task_client.is_shutdown() { + tokio::select! { + _ = self.task_client.recv() => { + log::trace!("TunListener: received shutdown"); + }, + // TODO: ConnectedClientsListener::update should poll the channel instead + event = self.connected_clients.connected_client_rx.recv() => match event { + Some(event) => self.connected_clients.update(event), + None => { + log::error!("TunListener: connected client channel closed"); + break; + }, + }, + len = self.tun_reader.read(&mut buf) => match len { + Ok(len) => { + if let Err(err) = self.handle_packet(&buf, len).await { + log::error!("tun: failed to handle packet: {err}"); + } + }, + Err(err) => { + log::warn!("iface: read error: {err}"); + // break; + } + } + } + } + log::debug!("TunListener: stopping"); + Ok(()) + } + + pub(crate) fn start(self) { + tokio::spawn(async move { + if let Err(err) = self.run().await { + log::error!("tun listener router has failed: {err}") + } + }); + } +} diff --git a/service-providers/ip-packet-router/src/util/create_message.rs b/service-providers/ip-packet-router/src/util/create_message.rs new file mode 100644 index 0000000000..d04d85bd22 --- /dev/null +++ b/service-providers/ip-packet-router/src/util/create_message.rs @@ -0,0 +1,18 @@ +use nym_sdk::mixnet::{InputMessage, Recipient}; +use nym_task::connections::TransmissionLane; + +pub(crate) fn create_input_message( + nym_address: Recipient, + response_packet: Vec, + mix_hops: Option, +) -> InputMessage { + let lane = TransmissionLane::General; + let packet_type = None; + InputMessage::new_regular_with_custom_hops( + nym_address, + response_packet, + lane, + packet_type, + mix_hops, + ) +} diff --git a/service-providers/ip-packet-router/src/util/generate_new_ip.rs b/service-providers/ip-packet-router/src/util/generate_new_ip.rs new file mode 100644 index 0000000000..5824ce7bba --- /dev/null +++ b/service-providers/ip-packet-router/src/util/generate_new_ip.rs @@ -0,0 +1,38 @@ +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr}, +}; + +use crate::{constants::TUN_DEVICE_ADDRESS, mixnet_listener::ConnectedClient}; + +// Find an available IP address in self.connected_clients +// TODO: make this nicer +fn generate_random_ip_within_subnet() -> Ipv4Addr { + let mut rng = rand::thread_rng(); + // Generate a random number in the range 1-254 + let last_octet = rand::Rng::gen_range(&mut rng, 1..=254); + Ipv4Addr::new(10, 0, 0, last_octet) +} + +fn is_ip_taken( + connected_clients: &HashMap, + tun_ip: Ipv4Addr, + ip: Ipv4Addr, +) -> bool { + connected_clients.contains_key(&ip.into()) || ip == tun_ip +} + +// TODO: brute force approach. We could consider using a more efficient algorithm +pub(crate) fn find_new_ip(connected_clients: &HashMap) -> Option { + let mut new_ip = generate_random_ip_within_subnet(); + let mut tries = 0; + let tun_ip = TUN_DEVICE_ADDRESS.parse::().unwrap(); + while is_ip_taken(connected_clients, tun_ip, new_ip) { + new_ip = generate_random_ip_within_subnet(); + tries += 1; + if tries > 100 { + return None; + } + } + Some(new_ip.into()) +} diff --git a/service-providers/ip-packet-router/src/util/mod.rs b/service-providers/ip-packet-router/src/util/mod.rs new file mode 100644 index 0000000000..c096a4a930 --- /dev/null +++ b/service-providers/ip-packet-router/src/util/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod create_message; +pub(crate) mod generate_new_ip; +pub(crate) mod parse_ip; diff --git a/service-providers/ip-packet-router/src/util/parse_ip.rs b/service-providers/ip-packet-router/src/util/parse_ip.rs new file mode 100644 index 0000000000..6b99a2ba89 --- /dev/null +++ b/service-providers/ip-packet-router/src/util/parse_ip.rs @@ -0,0 +1,104 @@ +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +use crate::error::IpPacketRouterError; + +pub(crate) struct ParsedPacket<'a> { + pub(crate) packet_type: &'a str, + pub(crate) src_addr: IpAddr, + pub(crate) dst_addr: IpAddr, + pub(crate) dst: Option, +} + +pub(crate) fn parse_packet(packet: &[u8]) -> Result { + let headers = etherparse::SlicedPacket::from_ip(packet).map_err(|err| { + log::warn!("Unable to parse incoming data as IP packet: {err}"); + IpPacketRouterError::PacketParseFailed { source: err } + })?; + + let (packet_type, dst_port) = match headers.transport { + Some(etherparse::TransportSlice::Udp(header)) => ("udp", Some(header.destination_port())), + Some(etherparse::TransportSlice::Tcp(header)) => ("tcp", Some(header.destination_port())), + Some(etherparse::TransportSlice::Icmpv4(_)) => ("icmpv4", None), + Some(etherparse::TransportSlice::Icmpv6(_)) => ("icmpv6", None), + Some(etherparse::TransportSlice::Unknown(_)) => ("unknown", None), + None => { + log::warn!("Received packet missing transport header"); + return Err(IpPacketRouterError::PacketMissingTransportHeader); + } + }; + + let (src_addr, dst_addr, dst) = match headers.ip { + Some(etherparse::InternetSlice::Ipv4(ipv4_header, _)) => { + let src_addr: IpAddr = ipv4_header.source_addr().into(); + let dst_addr: IpAddr = ipv4_header.destination_addr().into(); + let dst = dst_port.map(|port| SocketAddr::new(dst_addr, port)); + (src_addr, dst_addr, dst) + } + Some(etherparse::InternetSlice::Ipv6(ipv6_header, _)) => { + let src_addr: IpAddr = ipv6_header.source_addr().into(); + let dst_addr: IpAddr = ipv6_header.destination_addr().into(); + let dst = dst_port.map(|port| SocketAddr::new(dst_addr, port)); + (src_addr, dst_addr, dst) + } + None => { + log::warn!("Received packet missing IP header"); + return Err(IpPacketRouterError::PacketMissingIpHeader); + } + }; + Ok(ParsedPacket { + packet_type, + src_addr, + dst_addr, + dst, + }) +} + +// Constants for IPv4 and IPv6 headers +const IPV4_DEST_ADDR_START: usize = 16; +const IPV4_DEST_ADDR_LEN: usize = 4; +const IPV6_DEST_ADDR_START: usize = 24; +const IPV6_DEST_ADDR_LEN: usize = 16; + +// Only parse the destination address, for when we don't need the other stuff +pub(crate) fn parse_dst_addr(packet: &[u8]) -> Option { + let version = packet.first().map(|v| v >> 4)?; + match version { + 4 => { + // IPv4 + let addr_end = IPV4_DEST_ADDR_START + IPV4_DEST_ADDR_LEN; + let addr_array: [u8; IPV4_DEST_ADDR_LEN] = packet + .get(IPV4_DEST_ADDR_START..addr_end)? + .try_into() + .ok()?; + Some(IpAddr::V4(Ipv4Addr::from(addr_array))) + } + 6 => { + // IPv6 + let addr_end = IPV6_DEST_ADDR_START + IPV6_DEST_ADDR_LEN; + let addr_array: [u8; IPV6_DEST_ADDR_LEN] = packet + .get(IPV6_DEST_ADDR_START..addr_end)? + .try_into() + .ok()?; + Some(IpAddr::V6(Ipv6Addr::from(addr_array))) + } + _ => None, // Unknown IP version + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_destination_from_ip_packet() { + // Create packet + let builder = + etherparse::PacketBuilder::ipv4([192, 168, 1, 1], [192, 168, 1, 2], 20).udp(21, 1234); + let payload = [1, 2, 3, 4, 5, 6, 7, 8]; + let mut packet = Vec::::with_capacity(builder.size(payload.len())); + builder.write(&mut packet, &payload).unwrap(); + + let dst_addr = parse_dst_addr(&packet).unwrap(); + assert_eq!(dst_addr, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2))); + } +} diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 9ea3693ab4..2c6884ff90 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -1,9 +1,10 @@ # Copyright 2020 - Nym Technologies SA -# SPDX-License-Identifier: Apache-2.0 +# SPDX-License-Identifier: GPL-3.0-only [package] name = "nym-network-requester" -version = "1.1.31" +license = "GPL-3.0" +version = "1.1.32" authors.workspace = true edition.workspace = true rust-version = "1.65" @@ -31,7 +32,7 @@ regex = "1.8.4" reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -sqlx = { version = "0.6.1", features = ["runtime-tokio-rustls", "chrono"]} +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "chrono"]} tap = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = [ "net", "rt-multi-thread", "macros" ] } diff --git a/service-providers/network-requester/README.md b/service-providers/network-requester/README.md index fb414d7e75..ad40966e60 100644 --- a/service-providers/network-requester/README.md +++ b/service-providers/network-requester/README.md @@ -1,7 +1,24 @@ Copyright 2022 - Nym Technologies SA -SPDX-License-Identifier: Apache-2.0 +SPDX-License-Identifier: GPL-3.0-only --> +## License + +Copyright (C) 2022 Nym Technologies SA + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + ## Network requester The network requester is used to interpret socks5 client messages that need to diff --git a/service-providers/network-requester/src/cli/build_info.rs b/service-providers/network-requester/src/cli/build_info.rs index 55dc3bfd3f..5389e92634 100644 --- a/service-providers/network-requester/src/cli/build_info.rs +++ b/service-providers/network-requester/src/cli/build_info.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use clap::Args; use nym_bin_common::bin_info_owned; diff --git a/service-providers/network-requester/src/cli/init.rs b/service-providers/network-requester/src/cli/init.rs index d8439247b5..4333aa4970 100644 --- a/service-providers/network-requester/src/cli/init.rs +++ b/service-providers/network-requester/src/cli/init.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::cli::try_upgrade_config; use crate::config::{default_config_directory, default_config_filepath, default_data_directory}; diff --git a/service-providers/network-requester/src/cli/mod.rs b/service-providers/network-requester/src/cli/mod.rs index fcc2fe6750..2dba60cb16 100644 --- a/service-providers/network-requester/src/cli/mod.rs +++ b/service-providers/network-requester/src/cli/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_13::OldConfigV1_1_13; use crate::config::old_config_v1_1_20::ConfigV1_1_20; diff --git a/service-providers/network-requester/src/cli/run.rs b/service-providers/network-requester/src/cli/run.rs index 929ba57661..1295734bda 100644 --- a/service-providers/network-requester/src/cli/run.rs +++ b/service-providers/network-requester/src/cli/run.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::cli::{try_load_current_config, version_check}; use crate::{ diff --git a/service-providers/network-requester/src/cli/sign.rs b/service-providers/network-requester/src/cli/sign.rs index 053a431e4b..7a0dc29d7b 100644 --- a/service-providers/network-requester/src/cli/sign.rs +++ b/service-providers/network-requester/src/cli/sign.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::cli::{try_load_current_config, version_check}; use crate::error::NetworkRequesterError; diff --git a/service-providers/network-requester/src/config/mod.rs b/service-providers/network-requester/src/config/mod.rs index faaf3487ee..bce2d5effa 100644 --- a/service-providers/network-requester/src/config/mod.rs +++ b/service-providers/network-requester/src/config/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::persistence::NetworkRequesterPaths; use crate::config::template::CONFIG_TEMPLATE; @@ -22,7 +22,6 @@ use std::time::Duration; use url::Url; pub use nym_client_core::config::Config as BaseClientConfig; -pub use nym_client_core::config::{DebugConfig, GatewayEndpointConfig}; pub mod old_config_v1_1_13; pub mod old_config_v1_1_20; diff --git a/service-providers/network-requester/src/config/old_config_v1_1_13.rs b/service-providers/network-requester/src/config/old_config_v1_1_13.rs index 83a9b60020..a57d9222a6 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_13.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_13.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_20::ConfigV1_1_20; use nym_client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13; diff --git a/service-providers/network-requester/src/config/old_config_v1_1_19.rs b/service-providers/network-requester/src/config/old_config_v1_1_19.rs index 5da0601669..0a0c159d02 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_19.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_19.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_20::{ ConfigV1_1_20, DebugV1_1_20, NetworkRequesterPathsV1_1_20, diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20.rs b/service-providers/network-requester/src/config/old_config_v1_1_20.rs index 71b77a8f49..a3ab8c6610 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_20.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_20.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::old_config_v1_1_20_2::{ ConfigV1_1_20_2, DebugV1_1_20_2, NetworkRequesterPathsV1_1_20_2, diff --git a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs index 25aa2e1975..2263bfd42a 100644 --- a/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs +++ b/service-providers/network-requester/src/config/old_config_v1_1_20_2.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::{ config::{ diff --git a/service-providers/network-requester/src/config/persistence.rs b/service-providers/network-requester/src/config/persistence.rs index d62ffcea9a..242562c1c7 100644 --- a/service-providers/network-requester/src/config/persistence.rs +++ b/service-providers/network-requester/src/config/persistence.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_client_core::config::disk_persistence::CommonClientPaths; use serde::{Deserialize, Serialize}; diff --git a/service-providers/network-requester/src/config/template.rs b/service-providers/network-requester/src/config/template.rs index ae039f5da4..ee6e3ffc51 100644 --- a/service-providers/network-requester/src/config/template.rs +++ b/service-providers/network-requester/src/config/template.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub(crate) const CONFIG_TEMPLATE: &str = // While using normal toml marshalling would have been way simpler with less overhead, diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 8751b2265c..7affa6c849 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::{BaseClientConfig, Config}; use crate::error::NetworkRequesterError; diff --git a/service-providers/network-requester/src/error.rs b/service-providers/network-requester/src/error.rs index fb05dc976f..47d7b12bbc 100644 --- a/service-providers/network-requester/src/error.rs +++ b/service-providers/network-requester/src/error.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + pub use nym_client_core::error::ClientCoreError; use nym_exit_policy::policy::PolicyError; use nym_socks5_requests::{RemoteAddress, Socks5RequestError}; diff --git a/service-providers/network-requester/src/lib.rs b/service-providers/network-requester/src/lib.rs index 1b730aacd1..a8e54ad19f 100644 --- a/service-providers/network-requester/src/lib.rs +++ b/service-providers/network-requester/src/lib.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only pub mod config; pub mod core; diff --git a/service-providers/network-requester/src/main.rs b/service-providers/network-requester/src/main.rs index a8efdf0f76..391fe38242 100644 --- a/service-providers/network-requester/src/main.rs +++ b/service-providers/network-requester/src/main.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use clap::{crate_name, crate_version, Parser}; use error::NetworkRequesterError; diff --git a/service-providers/network-requester/src/reply.rs b/service-providers/network-requester/src/reply.rs index 4a2c620eb6..4c656aeb32 100644 --- a/service-providers/network-requester/src/reply.rs +++ b/service-providers/network-requester/src/reply.rs @@ -1,5 +1,5 @@ // Copyright 2022-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use nym_sdk::mixnet::InputMessage; use nym_service_providers_common::interface::{ @@ -189,6 +189,7 @@ impl MixnetAddress { recipient: *recipient, data: message, lane: TransmissionLane::ConnectionId(connection_id), + mix_hops: None, }), packet_type, }, diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/filter.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/filter.rs index 5b46f85c41..9a4cc04138 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/filter.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/filter.rs @@ -1,5 +1,5 @@ // Copyright 2020-2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::HostsStore; use crate::request_filter::allowed_hosts::group::HostsGroup; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/group.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/group.rs index 5b5ba4c75a..099d46cdf2 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/group.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/group.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::request_filter::allowed_hosts::host::Host; use ipnetwork::IpNetwork; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/host.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/host.rs index b2678ca34a..7f86e16aa8 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/host.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/host.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use ipnetwork::IpNetwork; use std::str::FromStr; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/hosts.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/hosts.rs index 58ac73b669..8840c78293 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/hosts.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/hosts.rs @@ -1,3 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + use super::host::Host; use crate::request_filter::allowed_hosts::group::HostsGroup; use ipnetwork::IpNetwork; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/mod.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/mod.rs index 60a0a3638b..225f6418b5 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/mod.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only mod filter; mod group; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/standard_list.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/standard_list.rs index 76cc3289a0..79b7c75901 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/standard_list.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/standard_list.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::request_filter::allowed_hosts::group::HostsGroup; use crate::request_filter::allowed_hosts::host::Host; diff --git a/service-providers/network-requester/src/request_filter/allowed_hosts/stored_allowed_hosts.rs b/service-providers/network-requester/src/request_filter/allowed_hosts/stored_allowed_hosts.rs index a7d87d803e..aeccebff05 100644 --- a/service-providers/network-requester/src/request_filter/allowed_hosts/stored_allowed_hosts.rs +++ b/service-providers/network-requester/src/request_filter/allowed_hosts/stored_allowed_hosts.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::request_filter::allowed_hosts::HostsStore; use async_file_watcher::{AsyncFileWatcher, FileWatcherEventReceiver}; diff --git a/service-providers/network-requester/src/request_filter/exit_policy/mod.rs b/service-providers/network-requester/src/request_filter/exit_policy/mod.rs index beab727a2b..5fdcc28b57 100644 --- a/service-providers/network-requester/src/request_filter/exit_policy/mod.rs +++ b/service-providers/network-requester/src/request_filter/exit_policy/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::error::NetworkRequesterError; use log::trace; diff --git a/service-providers/network-requester/src/request_filter/mod.rs b/service-providers/network-requester/src/request_filter/mod.rs index c3a3697ec0..27ed3207ec 100644 --- a/service-providers/network-requester/src/request_filter/mod.rs +++ b/service-providers/network-requester/src/request_filter/mod.rs @@ -1,5 +1,5 @@ // Copyright 2023 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::config::{self, Config}; use crate::error::NetworkRequesterError; diff --git a/service-providers/network-requester/src/socks5/mod.rs b/service-providers/network-requester/src/socks5/mod.rs index 8b084b2250..c8c0b5cf74 100644 --- a/service-providers/network-requester/src/socks5/mod.rs +++ b/service-providers/network-requester/src/socks5/mod.rs @@ -1 +1,4 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + pub(super) mod tcp; diff --git a/service-providers/network-requester/src/socks5/tcp.rs b/service-providers/network-requester/src/socks5/tcp.rs index baed05393d..a1e3eabec2 100644 --- a/service-providers/network-requester/src/socks5/tcp.rs +++ b/service-providers/network-requester/src/socks5/tcp.rs @@ -1,5 +1,5 @@ // Copyright 2020 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use crate::reply; use crate::reply::MixnetMessage; diff --git a/service-providers/network-requester/src/statistics/collector.rs b/service-providers/network-requester/src/statistics/collector.rs index e0169165fb..95bc4cce71 100644 --- a/service-providers/network-requester/src/statistics/collector.rs +++ b/service-providers/network-requester/src/statistics/collector.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use super::error::StatsError; use crate::core::new_legacy_request_version; diff --git a/service-providers/network-requester/src/statistics/error.rs b/service-providers/network-requester/src/statistics/error.rs index 0795d526df..dd81ee4ef9 100644 --- a/service-providers/network-requester/src/statistics/error.rs +++ b/service-providers/network-requester/src/statistics/error.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only use thiserror::Error; diff --git a/service-providers/network-requester/src/statistics/mod.rs b/service-providers/network-requester/src/statistics/mod.rs index 189015f2cd..e2e8dbc0cf 100644 --- a/service-providers/network-requester/src/statistics/mod.rs +++ b/service-providers/network-requester/src/statistics/mod.rs @@ -1,5 +1,5 @@ // Copyright 2022 - Nym Technologies SA -// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: GPL-3.0-only mod collector; mod error; diff --git a/service-providers/network-statistics/Cargo.toml b/service-providers/network-statistics/Cargo.toml index e2f5d56ae1..1895d2b88c 100644 --- a/service-providers/network-statistics/Cargo.toml +++ b/service-providers/network-statistics/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "nym-network-statistics" -version = "1.1.31" +version = "1.1.32" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,7 +12,7 @@ log = { workspace = true } pretty_env_logger = "0.4" rocket = { version = "0.5.0-rc.2", features = ["json"] } serde = { workspace = true, features = ["derive"] } -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "chrono"]} +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate", "chrono"]} thiserror = { workspace = true } tokio = { version = "1.4", features = [ "net", "rt-multi-thread", "macros", "time" ] } nym-bin-common = { path = "../../common/bin-common"} @@ -19,5 +20,5 @@ nym-statistics-common = { path = "../../common/statistics" } nym-task = { path = "../../common/task" } [build-dependencies] -sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } +sqlx = { workspace = true, features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } diff --git a/service-providers/network-statistics/src/api/routes.rs b/service-providers/network-statistics/src/api/routes.rs index 6d36a9f909..632841a675 100644 --- a/service-providers/network-statistics/src/api/routes.rs +++ b/service-providers/network-statistics/src/api/routes.rs @@ -1,14 +1,15 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use rocket::serde::json::Json; -use rocket::State; -use serde::{Deserialize, Serialize}; - -use nym_statistics_common::StatsMessage; +// due to the macro expansion of rather old rocket macros... +#![allow(unused_imports)] use crate::api::error::Result; use crate::storage::NetworkStatisticsStorage; +use nym_statistics_common::StatsMessage; +use rocket::serde::json::Json; +use rocket::State; +use serde::{Deserialize, Serialize}; #[derive(Clone, Serialize, Deserialize, Debug)] pub struct StatisticsRequest { diff --git a/testnet-faucet/yarn.lock b/testnet-faucet/yarn.lock index be27983742..1b8a5bce2b 100644 --- a/testnet-faucet/yarn.lock +++ b/testnet-faucet/yarn.lock @@ -397,6 +397,36 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38" + integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ== + +"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz#f954f34355712212a8e06c465bc06c40852c6bb3" + integrity sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw== + +"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz#45c63037f045c2b15c44f80f0393fa24f9655367" + integrity sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg== + +"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz#35707efeafe6d22b3f373caf9e8775e8920d1399" + integrity sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA== + +"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz#091b1218b66c341f532611477ef89e83f25fae4f" + integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA== + +"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" + integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== + "@mui/base@5.0.0-alpha.55": version "5.0.0-alpha.55" resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.55.tgz#564d6c9374b4cfe86a4493512356047636076d4f" @@ -2799,20 +2829,26 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -msgpackr-extract@^1.0.14: - version "1.0.15" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-1.0.15.tgz#3010a3ff0b033782d525116071b6c32864a79db2" - integrity sha512-vgJgzFva0/4/mt84wXf3CRCDPHKqiqk5t7/kVSjk/V2IvwSjoStHhxyq/b2+VrWcch3sxiNQOJEWXgI86Fm7AQ== +msgpackr-extract@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz#e05ec1bb4453ddf020551bcd5daaf0092a2c279d" + integrity sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A== dependencies: - nan "^2.14.2" - node-gyp-build "^4.2.3" + node-gyp-build-optional-packages "5.0.7" + optionalDependencies: + "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" + "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" msgpackr@^1.5.4: - version "1.5.5" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.5.5.tgz#c0562abc2951d7e29f75d77a8656b01f103a042c" - integrity sha512-JG0V47xRIQ9pyUnx6Hb4+3TrQoia2nA3UIdmyTldhxaxtKFkekkKpUW/N6fwHwod9o4BGuJGtouxOk+yCP5PEA== + version "1.10.1" + resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.10.1.tgz#51953bb4ce4f3494f0c4af3f484f01cfbb306555" + integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== optionalDependencies: - msgpackr-extract "^1.0.14" + msgpackr-extract "^3.0.2" nan@^2.14.2: version "2.15.0" @@ -2834,6 +2870,11 @@ node-addon-api@^3.2.1: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== +node-gyp-build-optional-packages@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz#5d2632bbde0ab2f6e22f1bbac2199b07244ae0b3" + integrity sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w== + node-gyp-build@^4.2.3, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" diff --git a/tools/internal/sdk-version-bump/src/helpers.rs b/tools/internal/sdk-version-bump/src/helpers.rs index 55d293d798..5d3a5d4e75 100644 --- a/tools/internal/sdk-version-bump/src/helpers.rs +++ b/tools/internal/sdk-version-bump/src/helpers.rs @@ -44,7 +44,7 @@ pub(crate) trait ReleasePackage: Sized { Ok(()) } - fn update_nym_dependencies(&mut self, _: &HashSet) -> anyhow::Result<()> { + fn update_nym_dependencies(&mut self, _: &HashSet, _: bool) -> anyhow::Result<()> { Ok(()) } } @@ -57,28 +57,29 @@ pub(crate) trait VersionBumpExt: Sized { fn try_remove_prerelease(&self) -> anyhow::Result; } +pub(crate) fn try_bump_raw_prerelease(raw: &str) -> anyhow::Result { + // ugh that's disgusting + let (rc_prefix, pre_version) = raw + .split_once('.') + .context("the prerelease version does not contain a valid rc.X suffix")?; + + let parsed_version: u32 = pre_version.parse()?; + let updated_version = parsed_version + 1; + + Ok(format!("{rc_prefix}.{updated_version}").parse()?) +} + impl VersionBumpExt for Version { fn try_bump_prerelease(&self) -> anyhow::Result { if self.pre.is_empty() { bail!("the current version ({self}) does not have pre-release data set - are you sure you followed the release process correctly?") } - // ugh that's disgusting - let (rc_prefix, pre_version) = self - .pre - .as_str() - .split_once('.') - .context("the prerelease version does not contain a valid rc.X suffix")?; - - let parsed_version: u32 = pre_version.parse()?; - let updated_version = parsed_version + 1; - - let pre = format!("{rc_prefix}.{updated_version}").parse()?; Ok(Version { major: self.major, minor: self.minor, patch: self.patch, - pre, + pre: try_bump_raw_prerelease(self.pre.as_str())?, build: self.build.clone(), }) } diff --git a/tools/internal/sdk-version-bump/src/json.rs b/tools/internal/sdk-version-bump/src/json.rs index 79b89e9d8d..5c6816bf7c 100644 --- a/tools/internal/sdk-version-bump/src/json.rs +++ b/tools/internal/sdk-version-bump/src/json.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::helpers::try_bump_raw_prerelease; use crate::json_types::DepsSet; use crate::{json_types, ReleasePackage}; use anyhow::{bail, Context}; @@ -14,10 +15,18 @@ pub struct PackageJson { inner: json_types::Package, } -fn update_dependencies(deps: &mut DepsSet, names: &HashSet) -> anyhow::Result<()> { +fn update_dependencies( + deps: &mut DepsSet, + names: &HashSet, + pre_release: bool, +) -> anyhow::Result<()> { for (package, version) in deps.iter_mut() { if names.contains(package) { - let updated = try_bump_minor_version_req(version)?; + let updated = if pre_release { + try_bump_prerelease_version_req(version)? + } else { + try_bump_minor_version_req(version)? + }; println!("\t\t>>> updating '{package}' from {version} to {updated}"); *version = updated @@ -52,21 +61,25 @@ impl ReleasePackage for PackageJson { self.inner.version = version.to_string() } - fn update_nym_dependencies(&mut self, names: &HashSet) -> anyhow::Result<()> { + fn update_nym_dependencies( + &mut self, + names: &HashSet, + pre_release: bool, + ) -> anyhow::Result<()> { println!("\t>>> updating @nymproject dependencies..."); - update_dependencies(&mut self.inner.dependencies, names)?; + update_dependencies(&mut self.inner.dependencies, names, pre_release)?; println!("\t>>> updating @nymproject peerDependencies..."); - update_dependencies(&mut self.inner.peer_dependencies, names)?; + update_dependencies(&mut self.inner.peer_dependencies, names, pre_release)?; println!("\t>>> updating @nymproject devDependencies..."); - update_dependencies(&mut self.inner.dev_dependencies, names)?; + update_dependencies(&mut self.inner.dev_dependencies, names, pre_release)?; println!("\t>>> updating @nymproject optionalDependencies..."); - update_dependencies(&mut self.inner.optional_dependencies, names)?; + update_dependencies(&mut self.inner.optional_dependencies, names, pre_release)?; println!("\t>>> updating @nymproject bundledDependencies..."); - update_dependencies(&mut self.inner.bundled_dependencies, names)?; + update_dependencies(&mut self.inner.bundled_dependencies, names, pre_release)?; Ok(()) } @@ -101,9 +114,9 @@ pub(crate) fn find_package_path(dir: &Path) -> anyhow::Result { // expected structure: `>=X.Y.Z-rc.W || ^X` fn try_bump_minor_version_req(raw_req: &str) -> anyhow::Result { - let (req, major) = raw_req - .split_once("||") - .context("invalid version requirement")?; + let (req, major) = raw_req.split_once("||").context(format!( + "'{raw_req}' is not a valid semver version requirement - we expect '`>=X.Y.Z-rc.W || ^X`'" + ))?; let parsed_req = VersionReq::parse(req)?; let parsed_major = VersionReq::parse(major)?; if parsed_req.comparators.len() != 1 { @@ -123,6 +136,30 @@ fn try_bump_minor_version_req(raw_req: &str) -> anyhow::Result { Ok(format!("{updated} || {parsed_major}")) } +// expected structure: `>=X.Y.Z-rc.W || ^X` +fn try_bump_prerelease_version_req(raw_req: &str) -> anyhow::Result { + let (req, major) = raw_req.split_once("||").context(format!( + "'{raw_req}' is not a valid semver version requirement - we expect '`>=X.Y.Z-rc.W || ^X`'" + ))?; + let parsed_req = VersionReq::parse(req)?; + let parsed_major = VersionReq::parse(major)?; + if parsed_req.comparators.len() != 1 { + bail!("wrong number of version requirements present in {parsed_req}") + } + + let updated = VersionReq { + comparators: vec![Comparator { + op: parsed_req.comparators[0].op, + major: parsed_req.comparators[0].major, + minor: parsed_req.comparators[0].minor, + patch: parsed_req.comparators[0].patch, + pre: try_bump_raw_prerelease(parsed_req.comparators[0].pre.as_str())?, + }], + }; + + Ok(format!("{updated} || {parsed_major}")) +} + #[cfg(test)] mod tests { use super::*; diff --git a/tools/internal/sdk-version-bump/src/main.rs b/tools/internal/sdk-version-bump/src/main.rs index ae6590e03e..cf77ba6be0 100644 --- a/tools/internal/sdk-version-bump/src/main.rs +++ b/tools/internal/sdk-version-bump/src/main.rs @@ -5,7 +5,7 @@ use crate::cargo::CargoPackage; use crate::helpers::ReleasePackage; use crate::json::PackageJson; use clap::{Parser, Subcommand}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; use std::path::{Path, PathBuf}; @@ -18,6 +18,48 @@ fn default_root() -> PathBuf { env::current_dir().unwrap() } +struct Summary { + cargo_results: HashMap>, + + json_results: HashMap>, +} + +impl Summary { + fn new( + cargo_results: HashMap>, + json_results: HashMap>, + ) -> Self { + Summary { + cargo_results, + json_results, + } + } + + fn print(&self) { + let cargo_ok = self.cargo_results.values().filter(|p| p.is_ok()).count(); + let json_ok = self.json_results.values().filter(|p| p.is_ok()).count(); + + println!("SUMMARY"); + println!("inspected {} cargo packages", self.cargo_results.len()); + println!("updated {cargo_ok} cargo packages"); + for (package, res) in &self.cargo_results { + if let Err(err) = res { + println!( + "\t>>> ❌ FAILURE: cargo package '{package}' failed to get updated: {err}" + ); + } + } + + println!("inspected {} json packages", self.json_results.len()); + println!("updated {json_ok} json packages"); + for (package, res) in &self.json_results { + if let Err(err) = res { + println!("\t>>> ❌ FAILURE: json package '{package}' failed to get updated: {err}"); + } + } + } +} + #[derive(Parser)] struct Args { #[arg(default_value=default_root().into_os_string())] @@ -36,12 +78,13 @@ enum Commands { /// It will also update the `@nymproject/...` dependencies from `">=X.Y.Z-rc.0 || ^X"` to `">=X.Y.(Z+1)-rc.0 || ^X"` BumpVersion { #[arg(long)] - /// If enabled, the packages will only have their rc version bumped and the dependencies won't get updated at all + /// If enabled, the packages will only have their rc version bumped and the dependencies + /// will get updated from `">=X.Y.Z-rc.W || ^X"` to `">=X.Y.Z-rc.(W+1) || ^X"` pre_release: bool, }, } -fn remove_suffix(root: &Path, path: impl AsRef) { +fn remove_suffix(root: &Path, path: impl AsRef) -> anyhow::Result<()> { let path = root.join(path); println!( ">>> [{}] UPDATING PACKAGE {}: ", @@ -51,8 +94,10 @@ fn remove_suffix(root: &Path, path: impl AsRef) { if let Err(err) = { remove_suffix_inner::(path) } { println!("\t>>> ❌ FAILURE: {err}"); + Err(err) } else { println!("\t>>> ✅ SUCCESS"); + Ok(()) } } @@ -70,7 +115,7 @@ fn bump_version( path: impl AsRef, dependencies_to_update: &HashSet, pre_release: bool, -) { +) -> anyhow::Result<()> { let path = root.join(path); println!( ">>> [{}] UPDATING PACKAGE {}: ", @@ -79,8 +124,10 @@ fn bump_version( ); if let Err(err) = { bump_version_inner::(path, dependencies_to_update, pre_release) } { println!("\t>>> ❌ FAILURE: {err}"); + Err(err) } else { println!("\t>>> ✅ SUCCESS"); + Ok(()) } } @@ -93,9 +140,7 @@ fn bump_version_inner( let mut package = Pkg::open(path)?; package.bump_version(pre_release)?; - if !pre_release { - package.update_nym_dependencies(dependencies_to_update)?; - } + package.update_nym_dependencies(dependencies_to_update, pre_release)?; println!("\t>>> saving the package file..."); package.save_changes() @@ -132,34 +177,46 @@ impl InternalPackages { self.internal_js_dependencies.insert(name.into()); } - pub fn remove_suffix(&self) { + pub fn remove_suffix(&self) -> Summary { + let mut cargo_results = HashMap::new(); for cargo_package in &self.cargo { - remove_suffix::(&self.root, cargo_package); + let res = remove_suffix::(&self.root, cargo_package); + cargo_results.insert(cargo_package.clone(), res); } + let mut json_results = HashMap::new(); for package_json in &self.json { - remove_suffix::(&self.root, package_json); + let res = remove_suffix::(&self.root, package_json); + json_results.insert(package_json.clone(), res); } + + Summary::new(cargo_results, json_results) } - pub fn bump_version(&self, pre_release: bool) { + pub fn bump_version(&self, pre_release: bool) -> Summary { + let mut cargo_results = HashMap::new(); for cargo_package in &self.cargo { - bump_version::( + let res = bump_version::( &self.root, cargo_package, &Default::default(), pre_release, ); + cargo_results.insert(cargo_package.clone(), res); } + let mut json_results = HashMap::new(); for package_json in &self.json { - bump_version::( + let res = bump_version::( &self.root, package_json, &self.internal_js_dependencies, pre_release, ); + json_results.insert(package_json.clone(), res); } + + Summary::new(cargo_results, json_results) } } @@ -226,10 +283,12 @@ fn main() -> anyhow::Result<()> { let args = Args::parse(); let packages = initialise_internal_packages(args.root); - match args.command { + let summary = match args.command { Commands::RemoveSuffix => packages.remove_suffix(), Commands::BumpVersion { pre_release } => packages.bump_version(pre_release), - } + }; + + summary.print(); Ok(()) } diff --git a/tools/internal/ssl-inject/Cargo.toml b/tools/internal/ssl-inject/Cargo.toml index 41d42fc496..70a28fcc08 100644 --- a/tools/internal/ssl-inject/Cargo.toml +++ b/tools/internal/ssl-inject/Cargo.toml @@ -2,6 +2,7 @@ name = "ssl-inject" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nym-cli/Cargo.toml b/tools/nym-cli/Cargo.toml index a537c80b1c..955cb02cc1 100644 --- a/tools/nym-cli/Cargo.toml +++ b/tools/nym-cli/Cargo.toml @@ -1,8 +1,9 @@ [package] name = "nym-cli" -version = "1.1.31" +version = "1.1.33" authors.workspace = true edition = "2021" +license.workspace = true [dependencies] base64 = "0.13.0" @@ -10,7 +11,7 @@ bs58 = "0.4" clap = { workspace = true, features = ["derive"] } clap_complete = "4.0" clap_complete_fig = "4.0" -dotenvy = "0.15.6" +dotenvy = { workspace = true } log = { workspace = true } pretty_env_logger = "0.4" serde = { version = "1", features = ["derive"] } diff --git a/tools/nym-cli/src/validator/mixnet/operators/mixnodes/settings/mod.rs b/tools/nym-cli/src/validator/mixnet/operators/mixnodes/settings/mod.rs index 77b12e2cc4..a5d5c02883 100644 --- a/tools/nym-cli/src/validator/mixnet/operators/mixnodes/settings/mod.rs +++ b/tools/nym-cli/src/validator/mixnet/operators/mixnodes/settings/mod.rs @@ -13,6 +13,9 @@ pub(crate) async fn execute( nym_cli_commands::validator::mixnet::operators::mixnode::settings::MixnetOperatorsMixnodeSettingsCommands::UpdateConfig(args) => { nym_cli_commands::validator::mixnet::operators::mixnode::settings::update_config::update_config(args, create_signing_client(global_args, network_details)?).await } + nym_cli_commands::validator::mixnet::operators::mixnode::settings::MixnetOperatorsMixnodeSettingsCommands::UpdateCostParameters(args) => { + nym_cli_commands::validator::mixnet::operators::mixnode::settings::update_cost_params::update_cost_params(args, create_signing_client(global_args, network_details)?).await + } _ => unreachable!(), } Ok(()) diff --git a/tools/nym-nr-query/Cargo.toml b/tools/nym-nr-query/Cargo.toml index 804577e583..8309e7e9c8 100644 --- a/tools/nym-nr-query/Cargo.toml +++ b/tools/nym-nr-query/Cargo.toml @@ -2,6 +2,7 @@ name = "nym-nr-query" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tools/nymvisor/Cargo.toml b/tools/nymvisor/Cargo.toml new file mode 100644 index 0000000000..47c2ab9101 --- /dev/null +++ b/tools/nymvisor/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "nymvisor" +version = "0.1.0" +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +edition.workspace = true +license.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +bytes = { version = "1.5.0", features = ["std"]} +clap = { workspace = true, features = ["derive"] } +dotenvy = { workspace = true } +flate2 = "1.0.28" +futures = { workspace = true } +hex = "0.4.3" +humantime = "2.1.0" +humantime-serde = "1.1.1" +nix = { version = "0.27.1", features = ["signal", "fs"] } +reqwest = { workspace = true, features = ["json", "stream"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +sha2 = "0.10.8" +tar = "0.4.40" +time = { workspace = true, features = [ "serde-human-readable" ] } +tokio = { workspace = true, features = ["rt", "macros", "signal", "process", "sync"] } +thiserror = { workspace = true } +tracing = { workspace = true } +url = { workspace = true, features = ["serde"] } + +async-file-watcher = { path = "../../common/async-file-watcher" } +nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "basic_tracing"] } +nym-config = { path = "../../common/config" } +nym-task = { path = "../../common/task"} + +[dev-dependencies] +tokio = { workspace = true, features = ["full"] } \ No newline at end of file diff --git a/tools/nymvisor/src/cli/add_upgrade.rs b/tools/nymvisor/src/cli/add_upgrade.rs new file mode 100644 index 0000000000..06e1717ebd --- /dev/null +++ b/tools/nymvisor/src/cli/add_upgrade.rs @@ -0,0 +1,136 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::helpers::copy_binary; +use crate::cli::try_load_current_config; +use crate::daemon::Daemon; +use crate::env::Env; +use crate::error::NymvisorError; +use crate::helpers::init_path; +use crate::upgrades::types::{UpgradeInfo, UpgradePlan}; +use nym_bin_common::output_format::OutputFormat; +use std::path::PathBuf; +use std::time::Duration; +use time::format_description::well_known::Rfc3339; +use time::OffsetDateTime; + +const DEFAULT_UPGRADE_DELAY: Duration = Duration::from_secs(15 * 60); + +fn parse_rfc3339_upgrade_time(raw: &str) -> Result { + OffsetDateTime::parse(raw, &Rfc3339) +} + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + /// Path to the daemon's upgrade executable. + daemon_binary: PathBuf, + + /// Name of this upgrade + #[arg(long)] + upgrade_name: String, + + /// Overwrite existing upgrade binary / upgrade-info.json file + #[arg(long)] + force: bool, + + /// Indicate that this command should only add binary to an *existing* scheduled upgrade + #[arg(long)] + add_binary: bool, + + /// Force the upgrade to happen immediately + #[arg(long, group = "time", conflicts_with = "add_binary")] + now: bool, + + /// Specifies the publish date metadata field of this upgrade. + /// If unset, the current time will be used. + #[arg(long, value_parser = parse_rfc3339_upgrade_time, conflicts_with = "add_binary")] + publish_date: Option, + + /// Specifies the time at which the provided upgrade will be performed (RFC3339 formatted). + /// If left unset, the upgrade will be performed in 15min + #[arg(long, value_parser = parse_rfc3339_upgrade_time, group = "time", conflicts_with = "add_binary")] + upgrade_time: Option, + + /// Specifies delay until the provided upgrade is going to get performed. + /// If let unset, the upgrade will be performed in 15min + #[arg(long, value_parser = humantime::parse_duration, group = "time", conflicts_with = "add_binary")] + upgrade_delay: Option, + + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +impl Args { + fn determine_upgrade_time(&self) -> OffsetDateTime { + // 1. there's always going to be at most one of: upgrade-delay, upgrade-time or now + // 2. if missing use 15min + if let Some(upgrade_time) = self.upgrade_time { + upgrade_time + } else if let Some(upgrade_delay) = self.upgrade_delay { + OffsetDateTime::now_utc() + upgrade_delay + } else if self.now { + OffsetDateTime::now_utc() + } else { + OffsetDateTime::now_utc() + DEFAULT_UPGRADE_DELAY + } + } +} + +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + let env = Env::try_read()?; + + let tmp_daemon = Daemon::new(&args.daemon_binary); + tmp_daemon.verify_binary()?; + + let bin_info = tmp_daemon.get_build_information()?; + let config = try_load_current_config(&env)?; + + let upgrade_info_path = config.upgrade_info_filepath(&args.upgrade_name); + let bin_path = config.upgrade_binary(&args.upgrade_name); + if bin_path.exists() && !args.force { + return Err(NymvisorError::ExistingUpgrade { + name: args.upgrade_name, + path: bin_path, + }); + } + + // if we're just adding the binary, the upgrade plan MUST already exist, + // otherwise it MUSTN'T exist (unless --force is used) + if args.add_binary { + let upgrade_info = UpgradeInfo::try_load(upgrade_info_path)?; + upgrade_info.ensure_matches_bin_info(&bin_info)?; + } else { + if upgrade_info_path.exists() && !args.force { + return Err(NymvisorError::ExistingUpgradeInfo { + name: args.upgrade_name, + path: upgrade_info_path, + }); + } + + let mut current_upgrade_plan = UpgradePlan::try_load(config.upgrade_plan_filepath())?; + let upgrade_info = UpgradeInfo { + manual: false, + name: args.upgrade_name.clone(), + notes: "manually added via 'add-upgrade' command".to_string(), + publish_date: Some(args.publish_date.unwrap_or(OffsetDateTime::now_utc())), + version: bin_info.build_version.clone(), + platforms: Default::default(), + upgrade_time: args.determine_upgrade_time(), + binary_details: Some(bin_info), + }; + + if current_upgrade_plan.has_planned_by_name(&args.upgrade_name) { + return Err(NymvisorError::UpgradePlanWithNoInfo { + name: args.upgrade_name, + }); + } + + init_path(config.upgrade_binary_dir(&args.upgrade_name))?; + upgrade_info.save(config.upgrade_info_filepath(&upgrade_info.name))?; + current_upgrade_plan.insert_new_upgrade(upgrade_info)?; + } + + copy_binary(&args.daemon_binary, bin_path)?; + + Ok(()) +} diff --git a/tools/nymvisor/src/cli/build_info.rs b/tools/nymvisor/src/cli/build_info.rs new file mode 100644 index 0000000000..07c8b3545a --- /dev/null +++ b/tools/nymvisor/src/cli/build_info.rs @@ -0,0 +1,17 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::NymvisorError; +use nym_bin_common::bin_info_owned; +use nym_bin_common::output_format::OutputFormat; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + println!("{}", args.output.format(&bin_info_owned!())); + Ok(()) +} diff --git a/tools/nymvisor/src/cli/config.rs b/tools/nymvisor/src/cli/config.rs new file mode 100644 index 0000000000..21f069bf67 --- /dev/null +++ b/tools/nymvisor/src/cli/config.rs @@ -0,0 +1,21 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::try_load_current_config; +use crate::env::Env; +use crate::error::NymvisorError; +use nym_bin_common::output_format::OutputFormat; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + let env = Env::try_read()?; + let config = try_load_current_config(&env)?; + + println!("{}", args.output.format(&config)); + Ok(()) +} diff --git a/tools/nymvisor/src/cli/daemon_build_info.rs b/tools/nymvisor/src/cli/daemon_build_info.rs new file mode 100644 index 0000000000..9035353b20 --- /dev/null +++ b/tools/nymvisor/src/cli/daemon_build_info.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::try_load_current_config; +use crate::daemon::Daemon; +use crate::env::Env; +use crate::error::NymvisorError; +use nym_bin_common::output_format::OutputFormat; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + let env = Env::try_read()?; + let config = try_load_current_config(&env)?; + + let daemon = Daemon::from_config(&config); + let build_info = daemon.get_build_information()?; + + println!("{}", args.output.format(&build_info)); + Ok(()) +} diff --git a/tools/nymvisor/src/cli/helpers/mod.rs b/tools/nymvisor/src/cli/helpers/mod.rs new file mode 100644 index 0000000000..21663ef14d --- /dev/null +++ b/tools/nymvisor/src/cli/helpers/mod.rs @@ -0,0 +1,48 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::env::Env; +use crate::error::NymvisorError; +use std::fs; +use std::path::{Path, PathBuf}; + +pub(crate) fn copy_binary(source: P1, target: P2) -> Result<(), NymvisorError> +where + P1: AsRef, + P2: AsRef, +{ + let source_path = source.as_ref(); + let target_path = target.as_ref(); + + fs::copy(source_path, target_path).map_err(|source| { + NymvisorError::DaemonBinaryCopyFailure { + source_path: source_path.to_path_buf(), + target_path: target_path.to_path_buf(), + source, + } + })?; + Ok(()) +} + +pub(crate) fn daemon_home( + args_home: &Option, + env: &Env, +) -> Result { + if let Some(home) = args_home { + Ok(home.clone()) + } else if let Some(home) = &env.daemon_home { + Ok(home.clone()) + } else { + Err(NymvisorError::DaemonHomeUnavailable) + } +} + +pub(crate) fn use_logs(args_disable_logs: bool, env: &Env) -> bool { + if args_disable_logs { + false + } else if let Some(disable_logs) = env.nymvisor_disable_logs { + !disable_logs + } else { + true + } +} diff --git a/tools/nymvisor/src/cli/init.rs b/tools/nymvisor/src/cli/init.rs new file mode 100644 index 0000000000..40003eff36 --- /dev/null +++ b/tools/nymvisor/src/cli/init.rs @@ -0,0 +1,436 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::helpers::{copy_binary, daemon_home, use_logs}; +use crate::config::{ + default_config_filepath, Config, BIN_DIR, CURRENT_VERSION_FILENAME, GENESIS_DIR, +}; +use crate::daemon::Daemon; +use crate::env::Env; +use crate::error::NymvisorError; +use crate::helpers::init_path; +use crate::upgrades::types::{CurrentVersionInfo, UpgradeInfo, UpgradePlan}; +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use nym_bin_common::logging::setup_tracing_logger; +use nym_bin_common::output_format::OutputFormat; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use time::OffsetDateTime; +use tracing::{debug, info, warn}; +use url::Url; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + /// Path to the daemon's executable. + daemon_binary: PathBuf, + + /// ID specifies the human readable ID of this particular nymvisor instance. + /// Can be overridden with $NYMVISOR_ID environmental variable. + #[arg(long)] + id: Option, + + /// Sets the base url of the upstream source for obtaining upgrade information for the deaemon. + /// It will be used fo constructing the full url, i.e. $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json + /// Can be overridden with $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL environmental variable. + #[arg(long)] + upstream_base_upgrade_url: Option, + + /// Specifies the rate of polling the upstream url for upgrade information. + /// default: 1h + /// Can be overridden with $NYMVISOR_UPSTREAM_POLLING_RATE + #[arg(long, value_parser = humantime::parse_duration)] + upstream_polling_rate: Option, + + /// If enabled, this will disable `nymvisor` logs (but not the underlying process) + /// Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable. + #[arg(long)] + disable_nymvisor_logs: bool, + + /// Set custom directory for upgrade data - binaries and upgrade plans. + /// If not set, the global nymvisors' data directory will be used instead. + /// Can be overridden with $NYMVISOR_UPGRADE_DATA_DIRECTORY environmental variable. + #[arg(long)] + upgrade_data_directory: Option, + + /// The location where the `nymvisor/` directory is kept that contains the auxiliary files associated + /// with the underlying daemon, such as any backups or current version information. + /// (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.). + /// Can be overridden with $DAEMON_HOME environmental variable. + #[arg(long)] + daemon_home: Option, + + /// Override url to the upstream source for upgrade plans for this daeamon. + /// The Url has to point to an endpoint containing a valid [`UpgradeInfo`] json. + /// Note: if set this takes precedence over `upstream_base_upgrade_url` + /// Can be overridden with $DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL environmental variable. + #[arg(long)] + daemon_absolute_upstream_upgrade_url: Option, + + /// If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json` + /// Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable. + #[arg(long)] + allow_download_upgrade_binaries: Option, + + /// If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. + /// If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. + /// Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable. + #[arg(long)] + enforce_download_checksum: Option, + + /// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. + /// Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it. + /// Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. + /// Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable. + #[arg(long)] + restart_daemon_after_upgrade: Option, + + /// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed + /// Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable. + #[arg(long)] + restart_daemon_on_failure: bool, + + /// If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before + /// restarting the subprocess. + /// Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable. + #[arg(long, value_parser = humantime::parse_duration)] + on_failure_daemon_restart_delay: Option, + + /// Defines the maximum number of startup failures the subprocess can experience in a quick succession before + /// no further restarts will be attempted and `nymvisor` will exit. + /// Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable. + #[arg(long)] + max_daemon_startup_failures: Option, + + /// Defines the length of time during which the subprocess is still considered to be in the startup phase + /// when its failures are going to be considered in `max_startup_failures`. + /// Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable. + #[arg(long, value_parser = humantime::parse_duration)] + startup_period_duration: Option, + + /// Specifies the amount of time `nymvisor` is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt + /// (for either an upgrade or shutdown of the `nymvisor` itself) + /// Once the time passes, a kill signal is going to be sent instead. + /// Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable. + #[arg(long, value_parser = humantime::parse_duration)] + daemon_shutdown_grace_period: Option, + + /// Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead. + /// Can be overridden with $DAEMON_BACKUP_DATA_DIRECTORY environmental variable. + #[arg(long)] + daemon_backup_data_directory: Option, + + /// If enabled, `nymvisor` will perform upgrades directly without performing any backups. + /// default: false + /// Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable. + #[arg(long)] + unsafe_skip_backup: bool, + + #[arg(short, long, default_value_t = OutputFormat::default())] + output: OutputFormat, +} + +impl Args { + pub(crate) fn override_config(&self, config: &mut Config) { + if let Some(nymvisor_id) = &self.id { + config.nymvisor.id = nymvisor_id.clone(); + } + if let Some(upstream) = &self.upstream_base_upgrade_url { + config.nymvisor.debug.upstream_base_upgrade_url = upstream.clone() + } + if let Some(polling_rate) = self.upstream_polling_rate { + config.nymvisor.debug.upstream_polling_rate = polling_rate + } + if self.disable_nymvisor_logs { + config.nymvisor.debug.disable_logs = self.disable_nymvisor_logs; + } + if let Some(nymvisor_upgrade_data_directory) = &self.upgrade_data_directory { + config.nymvisor.debug.upgrade_data_directory = + Some(nymvisor_upgrade_data_directory.clone()); + } + if let Some(daemon_home) = &self.daemon_home { + config.daemon.home = daemon_home.clone(); + } + if let Some(upstream) = &self.daemon_absolute_upstream_upgrade_url { + config.daemon.debug.absolute_upstream_upgrade_url = Some(upstream.clone()) + } + if let Some(daemon_allow_binaries_download) = self.allow_download_upgrade_binaries { + config.daemon.debug.allow_binaries_download = daemon_allow_binaries_download; + } + if let Some(enforce_download_checksum) = self.enforce_download_checksum { + config.daemon.debug.enforce_download_checksum = enforce_download_checksum; + } + if let Some(restart_daemon_after_upgrade) = self.restart_daemon_after_upgrade { + config.daemon.debug.restart_after_upgrade = restart_daemon_after_upgrade; + } + if self.restart_daemon_on_failure { + config.daemon.debug.restart_on_failure = self.restart_daemon_on_failure; + } + if let Some(on_failure_daemon_restart_delay) = self.on_failure_daemon_restart_delay { + config.daemon.debug.failure_restart_delay = on_failure_daemon_restart_delay; + } + if let Some(max_daemon_startup_failures) = self.max_daemon_startup_failures { + config.daemon.debug.max_startup_failures = max_daemon_startup_failures; + } + if let Some(startup_period_duration) = self.startup_period_duration { + config.daemon.debug.startup_period_duration = startup_period_duration; + } + if let Some(daemon_shutdown_grace_period) = self.daemon_shutdown_grace_period { + config.daemon.debug.shutdown_grace_period = daemon_shutdown_grace_period; + } + if let Some(daemon_backup_data_directory) = &self.daemon_backup_data_directory { + config.daemon.debug.backup_data_directory = Some(daemon_backup_data_directory.clone()); + } + if self.unsafe_skip_backup { + config.daemon.debug.unsafe_skip_backup = self.unsafe_skip_backup; + } + } +} + +fn try_build_config( + args: &Args, + env: &Env, + daemon_info: &BinaryBuildInformationOwned, +) -> Result { + let daemon_name = &daemon_info.binary_name; + let daemon_home = daemon_home(&args.daemon_home, env)?; + + debug!( + "building config for '{daemon_name}' with home at {}", + daemon_home.display() + ); + + let mut config = Config::new(daemon_name, daemon_home); + + // override config with environmental variables + debug!("overriding the config with command line arguments"); + args.override_config(&mut config); + + // and then override the result with the passed arguments + debug!("overriding the config with environmental variables"); + env.override_config(&mut config); + + Ok(config) +} + +fn init_paths(config: &Config) -> Result<(), NymvisorError> { + info!("initialising the directory structure"); + + init_path(config.daemon_nymvisor_dir())?; + init_path(config.daemon_backup_dir())?; + init_path(config.upgrade_data_dir())?; + init_path(config.genesis_daemon_dir().join(BIN_DIR))?; + init_path(config.upgrades_dir())?; + + Ok(()) +} + +fn setup_daemon_current_version( + config: &Config, + daemon_info: &BinaryBuildInformationOwned, +) -> Result<(), NymvisorError> { + info!("setting up initial {}", CURRENT_VERSION_FILENAME); + let path = config.current_daemon_version_filepath(); + + let initial = CurrentVersionInfo { + name: GENESIS_DIR.to_string(), + version: daemon_info.build_version.clone(), + upgrade_time: OffsetDateTime::now_utc(), + binary_details: daemon_info.clone(), + }; + + initial.save(path) +} + +fn setup_genesis( + config: &Config, + source: &Path, + daemon_info: &BinaryBuildInformationOwned, +) -> Result<(), NymvisorError> { + info!("setting up the genesis binary"); + let target = config.genesis_daemon_binary(); + + if target.exists() { + // if there already exists a binary at the genesis location, see if it's the same one + let existing_bin_info = Daemon::new(target).get_build_information()?; + return if &existing_bin_info != daemon_info { + Err(NymvisorError::DuplicateDaemonGenesisBinary { + daemon_name: config.daemon.name.clone(), + existing_info: Box::new(existing_bin_info), + provided_genesis: Box::new(daemon_info.clone()), + }) + } else { + debug!("there was already a genesis daemon binary present, but it was the same as the one provided"); + Ok(()) + }; + } + + let genesis_info = generate_and_save_genesis_upgrade_info(config, daemon_info)?; + setup_initial_upgrade_plan(config, genesis_info)?; + copy_binary(source, target) +} + +fn create_current_symlink(config: &Config) -> Result<(), NymvisorError> { + info!("setting up the symlink to the genesis directory"); + + let original = config.genesis_daemon_dir(); + let link = config.current_daemon_dir(); + + // check if a symlink already exists + if let Ok(existing_target) = fs::read_link(&link) { + return if existing_target != original { + Err(NymvisorError::ExistingCurrentSymlink { + daemon_name: config.daemon.name.clone(), + link: existing_target, + expected_link: original, + }) + } else { + debug!( + "there already exist a symlink between {} and {}", + original.display(), + link.display() + ); + Ok(()) + }; + } + + std::os::unix::fs::symlink(&original, &link).map_err(|source| { + NymvisorError::SymlinkCreationFailure { + source_path: original, + target_path: link, + source, + } + }) +} + +fn generate_and_save_genesis_upgrade_info( + config: &Config, + genesis_info: &BinaryBuildInformationOwned, +) -> Result { + info!("setting up the genesis upgrade-info.json"); + + let info = UpgradeInfo { + manual: true, + name: GENESIS_DIR.to_string(), + notes: "".to_string(), + publish_date: None, + version: genesis_info.build_version.clone(), + platforms: Default::default(), + upgrade_time: OffsetDateTime::UNIX_EPOCH, + binary_details: Some(genesis_info.clone()), + }; + let save_path = config.upgrade_info_filepath(&info.name); + + // if the upgrade info file already exists return an error since there is no associated binary + if save_path.exists() { + Err(NymvisorError::UpgradeInfoWithNoBinary { + name: info.name, + path: save_path, + }) + } else { + info.save(save_path)?; + Ok(info) + } +} + +fn setup_initial_upgrade_plan( + config: &Config, + genesis_info: UpgradeInfo, +) -> Result<(), NymvisorError> { + info!("setting up initial upgrade-plan.json"); + + let plan_path = config.upgrade_plan_filepath(); + + if plan_path.exists() { + warn!("there is already an upgrade-plan.json file present"); + // if the file already exists, try to load it and see if the 'current' matches + let existing_plan = UpgradePlan::try_load(&plan_path)?; + if let (Some(current_info), Some(existing_info)) = ( + &genesis_info.binary_details, + &existing_plan.current().binary_details, + ) { + if current_info != existing_info { + // if possible, compare the actual full details + return Err(NymvisorError::PreexistingUpgradePlan { + path: plan_path, + current_name: genesis_info.name, + existing_name: existing_plan.current().name.clone(), + }); + } + } else if genesis_info.name != existing_plan.current().name { + // otherwise just check the upgrade name + return Err(NymvisorError::PreexistingUpgradePlan { + path: plan_path, + current_name: genesis_info.name, + existing_name: existing_plan.current().name.clone(), + }); + } + + return Ok(()); + } + + UpgradePlan::new(genesis_info).save_new(plan_path) +} + +fn save_config(config: &Config, env: &Env) -> Result<(), NymvisorError> { + let id = &config.nymvisor.id; + let config_save_location = env + .nymvisor_config_path + .clone() + .unwrap_or(default_config_filepath(id)); + + info!( + "saving the config file to {}", + config_save_location.display() + ); + + config + .save_to_path(&config_save_location) + .map_err(|err| NymvisorError::ConfigSaveFailure { + path: config_save_location, + id: id.to_string(), + source: err, + })?; + Ok(()) +} + +/// Initialise the nymvisor by performing the following: +/// - executing the `build-info` command on the daemon executable to check its validity and obtain its name +/// - creating `/nymvisor` folder if it doesn't yet exist +/// - creating `` folder if it doesn't yet exist +/// - creating `` folder if it doesn't yet exist +/// - creating `//genesis/bin` folder if it doesn't yet exist +/// - creating `//upgrades` folder if it doesn't yet exist +/// - copying the provided executable to `//genesis/bin/` +/// - generating initial `//genesis/upgrade-info.json` file +/// - generating initial `/nymvisor/current-version-info.json` file +/// - creating a `//current` symlink pointing to `//genesis` +/// - saving nymvisor's config file to `` and creating the full directory structure. +/// +/// note: it requires either passing `--daemon-home` flag or setting the `$DAEMON_HOME` environmental variable +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + let env = Env::try_read()?; + + if use_logs(args.disable_nymvisor_logs, &env) { + setup_tracing_logger(); + info!("enabled nymvisor logging"); + } + + info!("initialising the nymvisor"); + + // this serves two purposes: + // 1. we get daemon name if it wasn't provided via either a flag or env variable + // 2. we check if valid executable was provided + let daemon_info = Daemon::new(args.daemon_binary.clone()).get_build_information()?; + + let config = try_build_config(&args, &env, &daemon_info)?; + + init_paths(&config)?; + setup_genesis(&config, &args.daemon_binary, &daemon_info)?; + setup_daemon_current_version(&config, &daemon_info)?; + create_current_symlink(&config)?; + save_config(&config, &env)?; + + println!("{}", args.output.format(&config)); + Ok(()) +} diff --git a/tools/nymvisor/src/cli/mod.rs b/tools/nymvisor/src/cli/mod.rs new file mode 100644 index 0000000000..bb90d2d15b --- /dev/null +++ b/tools/nymvisor/src/cli/mod.rs @@ -0,0 +1,154 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::{default_config_filepath, default_instances_directory, Config}; +use crate::env::{setup_env, Env}; +use crate::error::NymvisorError; +use clap::{Parser, Subcommand}; +use nym_bin_common::bin_info; +use nym_config::{DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME}; +use std::path::{Path, PathBuf}; +use std::sync::OnceLock; +use tracing::{debug, error}; + +mod add_upgrade; +mod build_info; +mod config; +mod daemon_build_info; +pub(crate) mod helpers; +mod init; +mod run; + +// Helper for passing LONG_VERSION to clap +fn pretty_build_info_static() -> &'static str { + static PRETTY_BUILD_INFORMATION: OnceLock = OnceLock::new(); + PRETTY_BUILD_INFORMATION.get_or_init(|| bin_info!().pretty_print()) +} + +#[derive(Parser, Debug)] +#[clap(author = "Nymtech", version, long_version = pretty_build_info_static(), about)] +pub(crate) struct Cli { + /// Path pointing to an env file that configures the nymvisor and overrides any preconfigured values. + #[clap(short, long)] + pub(crate) config_env_file: Option, + + #[clap(subcommand)] + command: Commands, +} + +impl Cli { + pub(crate) fn execute(self) -> Result<(), NymvisorError> { + setup_env(&self.config_env_file)?; + + match self.command { + Commands::Init(args) => init::execute(*args), + Commands::Run(args) => run::execute(args), + Commands::BuildInfo(args) => build_info::execute(args), + Commands::DaemonBuildInfo(args) => daemon_build_info::execute(args), + Commands::AddUpgrade(args) => add_upgrade::execute(args), + Commands::Config(args) => config::execute(args), + } + } +} + +#[derive(Subcommand, Debug)] +pub(crate) enum Commands { + /// Initialise a nymvisor instance with persistent Config.toml file. + Init(Box), + + /// Run the associated daemon with the preconfigured settings. + Run(run::Args), + + /// Show build information of this binary + BuildInfo(build_info::Args), + + /// Show build information of the associated daemon + DaemonBuildInfo(daemon_build_info::Args), + + /// Queues up another upgrade for the associated daemon + AddUpgrade(add_upgrade::Args), + + /// Show configuration options being used by this instance of nymvisor + Config(config::Args), +} + +fn open_config_file(env: &Env) -> Result { + let config_load_location = if let Some(config_path) = &env.nymvisor_config_path { + config_path.clone() + } else if let Some(nymvisor_id) = &env.nymvisor_id { + // if no explicit path was provided in the environment, try to use the default one based on the nymvisor id + default_config_filepath(nymvisor_id) + } else { + // finally, if all else fails, see if this is a singleton -> if so try to load the only instance + try_get_singleton_nymvisor_config_path()? + }; + + debug!( + "attempting to load configuration file from {}", + config_load_location.display() + ); + + if let Ok(cfg) = Config::read_from_toml_file(&config_load_location) { + return Ok(cfg); + } + + // we couldn't load it - try upgrading it from older revisions + try_upgrade_config(&config_load_location)?; + + match Config::read_from_toml_file(&config_load_location) { + Ok(cfg) => Ok(cfg), + Err(source) => { + error!("Failed to load config from {}. Are you sure you have run `init` before? (Error was: {source})", config_load_location.display()); + Err(NymvisorError::ConfigLoadFailure { + id: env.nymvisor_id.clone().unwrap_or("UNKNOWN".to_string()), + path: config_load_location, + source, + }) + } + } +} + +// attempt to get a path to nymvisor's config path if there is only a single instance +pub(crate) fn try_get_singleton_nymvisor_config_path() -> Result { + let instances_dir = default_instances_directory(); + let mut instances = instances_dir + .read_dir() + .map_err(|source| NymvisorError::InstancesReadFailure { + source, + path: instances_dir.clone(), + })? + .collect::>(); + + if instances.len() != 1 { + return Err(NymvisorError::NotSingleton { + instances: instances.len(), + }); + } + + // safety: that unwrap is fine as we've just checked we have 1 entry in the vector + #[allow(clippy::unwrap_used)] + let instance_dir = instances + .pop() + .unwrap() + .map_err(|source| NymvisorError::InstancesReadFailure { + source, + path: instances_dir, + })? + .path(); + + // join the instance directory with `/config/config.toml` + Ok(instance_dir + .join(DEFAULT_CONFIG_DIR) + .join(DEFAULT_CONFIG_FILENAME)) +} + +pub(crate) fn try_load_current_config(env: &Env) -> Result { + let mut config = open_config_file(env)?; + env.override_config(&mut config); + Ok(config) +} + +// no upgrades for now +fn try_upgrade_config>(_config_location: P) -> Result<(), NymvisorError> { + Ok(()) +} diff --git a/tools/nymvisor/src/cli/run.rs b/tools/nymvisor/src/cli/run.rs new file mode 100644 index 0000000000..14ec534ff2 --- /dev/null +++ b/tools/nymvisor/src/cli/run.rs @@ -0,0 +1,82 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::try_load_current_config; +use crate::env::Env; +use crate::error::NymvisorError; +use crate::tasks::launcher::DaemonLauncher; +use crate::tasks::upgrade_plan_watcher::start_upgrade_plan_watcher; +use crate::tasks::upstream_poller::UpstreamPoller; +use nym_bin_common::logging::setup_tracing_logger; +use std::future::Future; +use std::time::Duration; +use tokio::runtime; +use tokio::time::timeout; +use tracing::{error, info}; + +#[derive(clap::Args, Debug)] +pub(crate) struct Args { + #[clap(trailing_var_arg = true)] + // #[clap(raw = true)] + daemon_args: Vec, +} + +pub(crate) fn execute(args: Args) -> Result<(), NymvisorError> { + let env = Env::try_read()?; + let config = try_load_current_config(&env)?; + if !config.nymvisor.debug.disable_logs { + setup_tracing_logger(); + } + + info!("starting nymvisor for {}", config.daemon.name); + + // TODO: experiment with other minimal runtimes, maybe futures::executor::LocalPool + // + // well, if the creation of the runtime failed, there isn't much we could do + #[allow(clippy::expect_used)] + let rt = runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(|source| NymvisorError::RuntimeCreationFailure { source })?; + + // we have three tasks only: + // - one for managing the daemon launcher + // - the other one for watching the upgrade plan file + // - the last one for polling upstream source for upgrade info + // so once the daemon has finished, for whatever reason, abort the file watcher and upstream poller to terminate the nymvisor + + // spawn the root task + rt.block_on(async { + let (upgrade_receiver, watcher_handle) = start_upgrade_plan_watcher(&config)?; + let upstream_poller_handle = UpstreamPoller::new(&config).start(); + let mut launcher = DaemonLauncher::new(config, upgrade_receiver); + + if let Err(err) = launcher.run_loop(args.daemon_args).await { + error!("the daemon could not continue running: {err}"); + } else { + info!("the daemon has finished execution"); + } + + if !watcher_handle.is_finished() { + watcher_handle.abort(); + } + + if !upstream_poller_handle.is_finished() { + upstream_poller_handle.abort(); + } + + wait_for_task_termination(watcher_handle, "Upgrade plan watcher").await; + wait_for_task_termination(upstream_poller_handle, "Upstream poller").await; + + Ok(()) + }) +} + +async fn wait_for_task_termination(task: F, name: &str) { + match timeout(Duration::from_secs(2), task).await { + Ok(_) => info!("{name} has finished execution"), + Err(_timeout) => { + error!("{name} task has timed out and has not shutdown gracefully") + } + } +} diff --git a/tools/nymvisor/src/config/mod.rs b/tools/nymvisor/src/config/mod.rs new file mode 100644 index 0000000000..cd907841b2 --- /dev/null +++ b/tools/nymvisor/src/config/mod.rs @@ -0,0 +1,531 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::template::CONFIG_TEMPLATE; +use nym_config::serde_helpers::de_maybe_stringified; +use nym_config::{ + must_get_home, read_config_from_toml_file, save_formatted_config_to_file, NymConfigTemplate, + DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, DEFAULT_DATA_DIR, NYM_DIR, +}; +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; +use std::io; +use std::path::{Path, PathBuf}; +use std::time::Duration; +use tracing::{debug, warn}; +use url::Url; + +mod template; + +pub(crate) const DEFAULT_FAILURE_RESTART_DELAY: Duration = Duration::from_secs(10); +pub(crate) const DEFAULT_STARTUP_PERIOD: Duration = Duration::from_secs(120); +pub(crate) const DEFAULT_MAX_STARTUP_FAILURES: usize = 10; +pub(crate) const DEFAULT_SHUTDOWN_GRACE_PERIOD: Duration = Duration::from_secs(10); +pub(crate) const DEFAULT_UPSTREAM_POLLING_RATE: Duration = Duration::from_secs(60 * 60); + +pub(crate) const DEFAULT_BASE_UPSTREAM_UPGRADE_INFO_SOURCE: &str = + "https://nymtech.net/.wellknown/"; + +pub(crate) const UPGRADE_PLAN_FILENAME: &str = "upgrade-plan.json"; +pub(crate) const UPGRADE_HISTORY_FILENAME: &str = "upgrade-history.json"; +pub(crate) const UPGRADE_LOCK_FILENAME: &str = "upgrade.lock"; +pub(crate) const UPGRADE_INFO_FILENAME: &str = "upgrade-info.json"; +pub(crate) const CURRENT_VERSION_FILENAME: &str = "current-version.json"; +pub(crate) const NYMVISOR_DIR: &str = "nymvisor"; +pub(crate) const BACKUP_DIR: &str = "backups"; +pub(crate) const GENESIS_DIR: &str = "genesis"; +pub(crate) const CURRENT_DIR: &str = "current"; +pub(crate) const BIN_DIR: &str = "bin"; +pub(crate) const UPGRADES_DIR: &str = "upgrades"; +pub(crate) const DEFAULT_NYMVISORS_DIR: &str = "nymvisors"; +pub(crate) const DEFAULT_NYMVISORS_INSTANCES_DIR: &str = "instances"; + +/// Derive default path top the nymvisors instance directory. +/// It should get resolved to `$HOME/.nym/nymvisor/instances` +pub fn default_instances_directory() -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYMVISORS_DIR) + .join(DEFAULT_NYMVISORS_INSTANCES_DIR) +} + +/// Derive default path to the nymvisor's config directory. +/// It should get resolved to `$HOME/.nym/nymvisor/instances//config` +pub fn default_config_directory>(id: P) -> PathBuf { + default_instances_directory() + .join(id) + .join(DEFAULT_CONFIG_DIR) +} + +/// Derive default path to the nymvisor's config file. +/// It should get resolved to `$HOME/.nym/nymvisor/instances//config/config.toml` +pub fn default_config_filepath>(id: P) -> PathBuf { + default_config_directory(id).join(DEFAULT_CONFIG_FILENAME) +} + +// /// Derive default path to the nymvisor's data directory where additional files are stored. +// /// It should get resolved to `$HOME/.nym/nymvisor/instances//data` +// pub fn default_data_directory>(id: P) -> PathBuf { +// must_get_home() +// .join(NYM_DIR) +// .join(DEFAULT_NYMVISORS_DIR) +// .join(DEFAULT_NYMVISORS_INSTANCES_DIR) +// .join(id) +// .join(DEFAULT_DATA_DIR) +// } + +/// Get default path to nymvisors global data directory where files, such as upgrade plans or binaries are stored. +/// It should get resolved to `$HOME/.nym/nymvisors/data` +pub fn default_global_data_directory() -> PathBuf { + must_get_home() + .join(NYM_DIR) + .join(DEFAULT_NYMVISORS_DIR) + .join(DEFAULT_DATA_DIR) +} + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Config { + // additional metadata holding on-disk location of this config file + #[serde(skip)] + pub(crate) save_path: Option, + + pub nymvisor: Nymvisor, + + pub daemon: Daemon, +} + +impl NymConfigTemplate for Config { + fn template(&self) -> &'static str { + CONFIG_TEMPLATE + } +} + +impl Display for Config { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#" +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{:?} +{:<35}{:?} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{} +{:<35}{:?} +{:<35}{} +"#, + "id:", + self.nymvisor.id, + "daemon name:", + self.daemon.name, + "daemon home:", + self.daemon.home.display(), + "upstream base upgrade url:", + self.nymvisor.debug.upstream_base_upgrade_url, + "disable nymvisor logs:", + self.nymvisor.debug.disable_logs, + "CUSTOM upgrade data directory", + self.nymvisor + .debug + .upgrade_data_directory + .as_ref() + .map(|p| p.display().to_string()) + .unwrap_or_default(), + "upstream absolute upgrade url:", + self.daemon + .debug + .absolute_upstream_upgrade_url + .as_ref() + .map(|p| p.to_string()) + .unwrap_or_default(), + "allow binaries download:", + self.daemon.debug.allow_binaries_download, + "enforce download checksum:", + self.daemon.debug.enforce_download_checksum, + "restart after upgrade:", + self.daemon.debug.restart_after_upgrade, + "restart on failure:", + self.daemon.debug.restart_on_failure, + "on failure restart delay:", + humantime::format_duration(self.daemon.debug.failure_restart_delay), + "max startup failures:", + self.daemon.debug.max_startup_failures, + "startup period duration:", + humantime::format_duration(self.daemon.debug.startup_period_duration), + "shutdown grace period:", + humantime::format_duration(self.daemon.debug.shutdown_grace_period), + "CUSTOM backup data directory", + self.daemon + .debug + .backup_data_directory + .as_ref() + .map(|p| p.display().to_string()) + .unwrap_or_default(), + "UNSAFE skip backups", + self.daemon.debug.unsafe_skip_backup, + ) + } +} + +impl Config { + pub fn default_id(daemon_name: S) -> String { + format!("{daemon_name}-default") + } + + pub fn new>(daemon_name: S, daemon_home: PathBuf) -> Self { + let daemon_name = daemon_name.into(); + + Config { + save_path: None, + nymvisor: Nymvisor { + id: Self::default_id(&daemon_name), + debug: Default::default(), + }, + daemon: Daemon { + name: daemon_name, + home: daemon_home, + debug: Default::default(), + }, + } + } + + // simple wrapper that reads config file and assigns path location + fn read_from_path>(path: P) -> io::Result { + let path = path.as_ref(); + let mut loaded: Config = read_config_from_toml_file(path)?; + loaded.save_path = Some(path.to_path_buf()); + debug!("loaded config file from {}", path.display()); + Ok(loaded) + } + + pub fn read_from_toml_file>(path: P) -> io::Result { + Self::read_from_path(path) + } + + pub fn default_location(&self) -> PathBuf { + default_config_filepath(&self.nymvisor.id) + } + + pub fn save_to_default_location(&self) -> io::Result<()> { + let config_save_location: PathBuf = self.default_location(); + save_formatted_config_to_file(self, config_save_location) + } + + pub fn save_to_path>(&self, path: P) -> io::Result<()> { + save_formatted_config_to_file(self, path) + } + + // this code will be needed for config upgrades + #[allow(dead_code)] + pub fn try_save(&self) -> io::Result<()> { + if let Some(save_location) = &self.save_path { + save_formatted_config_to_file(self, save_location) + } else { + warn!("config file save location is unknown. falling back to the default"); + self.save_to_default_location() + } + } + + // e.g. $HOME/.nym/nym-api//nymvisor + pub fn daemon_nymvisor_dir(&self) -> PathBuf { + self.daemon.home.join(NYMVISOR_DIR) + } + + // e.g. $HOME/.nym/nym-apis//nymvisor/backups + pub fn daemon_backup_dir(&self) -> PathBuf { + if let Some(backup_dir) = &self.daemon.debug.backup_data_directory { + backup_dir.clone() + } else { + self.daemon_nymvisor_dir().join(BACKUP_DIR) + } + } + + // e.g. $HOME/.nym/nym-apis//nymvisor/backups/ + pub fn daemon_upgrade_backup_dir>(&self, upgrade_name: P) -> PathBuf { + self.daemon_backup_dir().join(upgrade_name) + } + + // e.g. $HOME/.nym/nym-api//nymvisor/current-version.json + pub fn current_daemon_version_filepath(&self) -> PathBuf { + self.daemon_nymvisor_dir().join(CURRENT_VERSION_FILENAME) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/ + pub fn upgrade_data_dir(&self) -> PathBuf { + self.nymvisor + .debug + .upgrade_data_directory + .clone() + .unwrap_or(default_global_data_directory().join(&self.daemon.name)) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/genesis + pub fn genesis_daemon_dir(&self) -> PathBuf { + self.upgrade_data_dir().join(GENESIS_DIR) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/genesis/bin/nym-api + pub fn genesis_daemon_binary(&self) -> PathBuf { + self.genesis_daemon_dir() + .join(BIN_DIR) + .join(&self.daemon.name) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/current + pub fn current_daemon_dir(&self) -> PathBuf { + self.upgrade_data_dir().join(CURRENT_DIR) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/current/bin/nym-api + pub fn current_daemon_binary(&self) -> PathBuf { + self.current_daemon_dir() + .join(BIN_DIR) + .join(&self.daemon.name) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/current/upgrade-info.json + pub fn current_upgrade_info_filepath(&self) -> PathBuf { + self.current_daemon_dir().join(UPGRADE_INFO_FILENAME) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades//upgrade-info.json + // or $HOME/.nym/nymvisors/data/nym-api/genesis/upgrade-info.json + pub fn upgrade_info_filepath>(&self, upgrade_name: S) -> PathBuf { + // special case for genesis + let name = upgrade_name.as_ref(); + if name == GENESIS_DIR { + self.genesis_daemon_dir().join(UPGRADE_INFO_FILENAME) + } else { + self.upgrade_dir(name).join(UPGRADE_INFO_FILENAME) + } + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades/ + pub fn upgrade_dir>(&self, upgrade_name: P) -> PathBuf { + self.upgrades_dir().join(upgrade_name) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades//bin + pub fn upgrade_binary_dir>(&self, upgrade_name: P) -> PathBuf { + self.upgrade_dir(upgrade_name).join(BIN_DIR) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades//bin/nym-api + pub fn upgrade_binary>(&self, upgrade_name: P) -> PathBuf { + self.upgrade_binary_dir(upgrade_name) + .join(&self.daemon.name) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades//bin/nym-api.tmp + pub fn temp_upgrade_binary>(&self, upgrade_name: P) -> PathBuf { + self.upgrade_binary_dir(upgrade_name) + .join(format!("{}.tmp", self.daemon.name)) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrades/ + pub fn upgrades_dir(&self) -> PathBuf { + self.upgrade_data_dir().join(UPGRADES_DIR) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrade-plan.json + pub fn upgrade_plan_filepath(&self) -> PathBuf { + self.upgrade_data_dir().join(UPGRADE_PLAN_FILENAME) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrade-history.json + pub fn upgrade_history_filepath(&self) -> PathBuf { + self.upgrade_data_dir().join(UPGRADE_HISTORY_FILENAME) + } + + // e.g. $HOME/.nym/nymvisors/data/nym-api/upgrade.lock + pub fn upgrade_lock_filepath(&self) -> PathBuf { + self.upgrade_data_dir().join(UPGRADE_LOCK_FILENAME) + } + + pub fn upstream_upgrade_url(&self) -> Url { + if let Some(absolute_url) = &self.daemon.debug.absolute_upstream_upgrade_url { + absolute_url.clone() + } else { + let mut base = self.nymvisor.debug.upstream_base_upgrade_url.clone(); + let prefix = base.path().trim_end_matches('/'); + let daemon = &self.daemon.name; + + base.set_path(&format!("{prefix}/{daemon}/{UPGRADE_INFO_FILENAME}")); + base + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Nymvisor { + /// ID specifies the human readable ID of this particular nymvisor instance. + /// Can be overridden with $NYMVISOR_ID environmental variable. + pub id: String, + + /// Further optional configuration options associated with the nymvisor. + #[serde(flatten)] + pub debug: NymvisorDebug, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct NymvisorDebug { + /// Sets the base url of the upstream source for obtaining upgrade information for the deaemon. + /// default: "https://nymtech.net/.wellknown/" + /// It will be used fo constructing the full url, i.e. $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json + /// Can be overridden with $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL environmental variable. + pub upstream_base_upgrade_url: Url, + + /// Specifies the rate of polling the upstream url for upgrade information. + /// default: 1h + /// Can be overridden with $NYMVISOR_UPSTREAM_POLLING_RATE + #[serde(with = "humantime_serde")] + pub upstream_polling_rate: Duration, + + /// If set to true, this will disable `nymvisor` logs (but not the underlying process) + /// default: false + /// Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable. + pub disable_logs: bool, + + /// Set custom directory for upgrade data - binaries and upgrade plans. + /// If not set, the global nymvisors' data directory will be used instead. + /// Can be overridden with $NYMVISOR_UPGRADE_DATA_DIRECTORY environmental variable. + #[serde(deserialize_with = "de_maybe_stringified")] + pub upgrade_data_directory: Option, +} + +impl Default for NymvisorDebug { + fn default() -> Self { + NymvisorDebug { + // this expect is fine as we're parsing a constant, hardcoded value that should always be valid + #[allow(clippy::expect_used)] + upstream_base_upgrade_url: DEFAULT_BASE_UPSTREAM_UPGRADE_INFO_SOURCE + .parse() + .expect("default upstream url was malformed"), + upstream_polling_rate: DEFAULT_UPSTREAM_POLLING_RATE, + disable_logs: false, + upgrade_data_directory: None, + } + } +} + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct Daemon { + /// The name of the managed binary itself (e.g. nym-api, nym-mixnode, nym-gateway, etc.) + /// Can be overridden with $DAEMON_NAME environmental variable. + pub name: String, + + /// The location where the `nymvisor/` directory is kept that contains the auxiliary files associated + /// with the underlying daemon, such as any backups or current version information. + /// (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.). + /// Can be overridden with $DAEMON_HOME environmental variable. + pub home: PathBuf, + + /// Further optional configuration options associated with the daemon. + #[serde(flatten)] + pub debug: DaemonDebug, +} + +#[derive(Debug, Deserialize, PartialEq, Serialize, Clone)] +#[serde(deny_unknown_fields)] +pub struct DaemonDebug { + /// Override url to the upstream source for upgrade plans for this daeamon. + /// The Url has to point to an endpoint containing a valid [`UpgradeInfo`] json. + /// Note: if set this takes precedence over .nymvisor.debug.upstream_base_upgrade_url + /// default: None + /// Can be overridden with $DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL environmental variable. + #[serde(deserialize_with = "de_maybe_stringified")] + pub absolute_upstream_upgrade_url: Option, + + /// If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json` + /// default: true + /// Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable. + pub allow_binaries_download: bool, + + /// If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. + /// If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. + /// default: true + /// Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable. + pub enforce_download_checksum: bool, + + /// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. + /// Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it. + /// Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. + /// default: true + /// Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable. + pub restart_after_upgrade: bool, + + /// If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed + /// default: false + /// Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable. + pub restart_on_failure: bool, + + /// If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before + /// restarting the subprocess. + /// default: 10s + /// Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable. + // The default value is so relatively high as to prevent constant restart loops in case of some underlying issue. + #[serde(with = "humantime_serde")] + pub failure_restart_delay: Duration, + + /// Defines the maximum number of startup failures the subprocess can experience in a quick succession before + /// no further restarts will be attempted and `nymvisor` will exit. + /// default: 10 + /// Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable. + pub max_startup_failures: usize, + + /// Defines the length of time during which the subprocess is still considered to be in the startup phase + /// when its failures are going to be considered in `max_startup_failures`. + /// default: 120s + /// Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable. + #[serde(with = "humantime_serde")] + pub startup_period_duration: Duration, + + /// Specifies the amount of time `nymvisor` is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt + /// (for either an upgrade or shutdown of the `nymvisor` itself) + /// Once the time passes, a kill signal is going to be sent instead. + /// default: 10s + /// Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable. + #[serde(with = "humantime_serde")] + pub shutdown_grace_period: Duration, + + /// Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead. + /// Can be overridden with $DAEMON_BACKUP_DATA_DIRECTORY environmental variable. + #[serde(deserialize_with = "de_maybe_stringified")] + pub backup_data_directory: Option, + + /// If enabled, `nymvisor` will perform upgrades directly without performing any backups. + /// default: false + /// Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable. + pub unsafe_skip_backup: bool, +} + +impl Default for DaemonDebug { + fn default() -> Self { + DaemonDebug { + absolute_upstream_upgrade_url: None, + allow_binaries_download: true, + enforce_download_checksum: true, + restart_after_upgrade: true, + restart_on_failure: false, + failure_restart_delay: DEFAULT_FAILURE_RESTART_DELAY, + max_startup_failures: DEFAULT_MAX_STARTUP_FAILURES, + startup_period_duration: DEFAULT_STARTUP_PERIOD, + shutdown_grace_period: DEFAULT_SHUTDOWN_GRACE_PERIOD, + backup_data_directory: None, + unsafe_skip_backup: false, + } + } +} diff --git a/tools/nymvisor/src/config/template.rs b/tools/nymvisor/src/config/template.rs new file mode 100644 index 0000000000..fcfd66a8e2 --- /dev/null +++ b/tools/nymvisor/src/config/template.rs @@ -0,0 +1,118 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) const CONFIG_TEMPLATE: &str = r#" +# This is a TOML config file. +# For more information, see https://github.com/toml-lang/toml + +##### main base nymvisor config options ##### + +[nymvisor] +# ID specifies the human readable ID of this particular nymvisor instance. +# Can be overridden with $NYMVISOR_ID environmental variable. +id = '{{ nymvisor.id }}' + +##### further optional configuration nymvisor options ##### + +# Sets the base url of the upstream source for obtaining upgrade information for the deaemon. +# default: "https://nymtech.net/.wellknown/" +# It will be used fo constructing the full url, i.e. $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL/$DAEMON_NAME/upgrade-info.json +# Can be overridden with $NYMVISOR_UPSTREAM_BASE_UPGRADE_URL environmental variable. +upstream_base_upgrade_url = '{{ nymvisor.upstream_base_upgrade_url }}' + +# Specifies the rate of polling the upstream url for upgrade information. +# Can be overridden with $NYMVISOR_UPSTREAM_POLLING_RATE +upstream_polling_rate = '{{ nymvisor.upstream_polling_rate }}' + +# If set to true, this will disable `nymvisor` logs (but not the underlying process) +# default: false +# Can be overridden with $NYMVISOR_DISABLE_LOGS environmental variable. +disable_logs = {{ nymvisor.disable_logs }} + +# Set custom directory for upgrade data - binaries and upgrade plans. +# If not set, the global nymvisors' data directory will be used instead. +# Can be overridden with $NYMVISOR_UPGRADE_DATA_DIRECTORY environmental variable. +upgrade_data_directory = '{{ nymvisor.upgrade_data_directory }}' + +##### main base daemon config options ##### + +[daemon] + +# The name of the managed binary itself (e.g. nym-api, nym-mixnode, nym-gateway, etc.) +# Can be overridden with $DAEMON_NAME environmental variable. +name = '{{ daemon.name }}' + +# The location where the `nymvisor/` directory is kept that contains the auxiliary files associated +# with the underlying daemon, such as any backups or current version information. +# (e.g. $HOME/.nym/nym-api/my-nym-api, $HOME/.nym/mixnodes/my-mixnode, etc.). +# Can be overridden with $DAEMON_HOME environmental variable. +home = '{{ daemon.home }}' + +##### further optional configuration daemon options ##### + +# Override url to the upstream source for upgrade plans for this daeamon. +# The Url has to point to an endpoint containing a valid [`UpgradeInfo`] json. +# Note: if set this takes precedence over .nymvisor.debug.upstream_base_upgrade_url +# default: None +# Can be overridden with $DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL environmental variable. +absolute_upstream_upgrade_url = '{{ daemon.absolute_upstream_upgrade_url }}' + +# If set to true, this will enable auto-downloading of new binaries using the url provided in the `upgrade-info.json` +# default: true +# Can be overridden with $DAEMON_ALLOW_BINARIES_DOWNLOAD environmental variable. +allow_binaries_download = {{ daemon.allow_binaries_download }} + +# If enabled nymvisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. +# If disabled, nymvisor will not require a checksum to be provided, but still check the checksum if one is provided. +# default: true +# Can be overridden with $DAEMON_ENFORCE_DOWNLOAD_CHECKSUM environmental variable. +enforce_download_checksum = {{ daemon.enforce_download_checksum }} + +# If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. +# Otherwise (if disabled), nymvisor will stop running after an upgrade and will require the system administrator to manually restart it. +# Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. +# default: true +# Can be overridden with $DAEMON_RESTART_AFTER_UPGRADE environmental variable. +restart_after_upgrade = {{ daemon.restart_after_upgrade }} + +# If enabled, nymvisor will restart the subprocess with the same command-line arguments and flags after it has crashed +# default: false +# Can be overridden with $DAEMON_RESTART_ON_FAILURE environmental variable. +restart_on_failure = {{ daemon.restart_on_failure }} + +# If `restart_on_failure` is enabled, the following value defines the amount of time `nymvisor` shall wait before +# restarting the subprocess. +# default: 10s +# Can be overridden with $DAEMON_FAILURE_RESTART_DELAY environmental variable. +# The default value is so relatively high as to prevent constant restart loops in case of some underlying issue. +failure_restart_delay = '{{ daemon.failure_restart_delay }}' + +# Defines the maximum number of startup failures the subprocess can experience in a quick succession before +# no further restarts will be attempted and `nymvisor` will exit/ +# default: 10 +# Can be overridden with $DAEMON_MAX_STARTUP_FAILURES environmental variable. +max_startup_failures = {{ daemon.max_startup_failures }} + +# Defines the length of time during which the subprocess is still considered to be in the startup phase +# when its failures are going to be considered in `max_startup_failures`. +# default: 120s +# Can be overridden with $DAEMON_STARTUP_PERIOD_DURATION environmental variable. +startup_period_duration = '{{ daemon.startup_period_duration }}' + +# Specifies the amount of time `nymvisor` is willing to wait for the subprocess to undergo graceful shutdown after receiving an interrupt +# (for either an upgrade or shutdown of the `nymvisor` itself) +# Once the time passes, a kill signal is going to be sent instead. +# default: 10s +# Can be overridden with $DAEMON_SHUTDOWN_GRACE_PERIOD environmental variable. +shutdown_grace_period = '{{ daemon.shutdown_grace_period }}' + +# Set custom backup directory for daemon data. If not set, the daemon's home directory will be used instead. +# Can be overridden with $DAEMON_BACKUP_DATA_DIRECTORY environmental variable. +backup_data_directory = '{{ daemon.backup_data_directory }}' + +# If enabled, `nymvisor` will perform upgrades directly without performing any backups. +# default: false +# Can be overridden with $DAEMON_UNSAFE_SKIP_BACKUP environmental variable. +unsafe_skip_backup = {{ daemon.unsafe_skip_backup }} + +"#; diff --git a/tools/nymvisor/src/daemon/helpers.rs b/tools/nymvisor/src/daemon/helpers.rs new file mode 100644 index 0000000000..2782a3b654 --- /dev/null +++ b/tools/nymvisor/src/daemon/helpers.rs @@ -0,0 +1,17 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::NymvisorError; +use crate::error::NymvisorError::DaemonBuildInformationParseFailure; +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use std::fmt::Debug; +use std::os::unix::prelude::ExitStatusExt; +use std::path::Path; + +// each of our nym binaries (that are supported by `nymvisor`) expose `build-info` command +// that outputs the build information +#[instrument] +pub(crate) fn get_daemon_build_information + Debug>( + executable_path: P, +) -> Result { +} diff --git a/tools/nymvisor/src/daemon/mod.rs b/tools/nymvisor/src/daemon/mod.rs new file mode 100644 index 0000000000..6c552e8238 --- /dev/null +++ b/tools/nymvisor/src/daemon/mod.rs @@ -0,0 +1,237 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::{Config, DEFAULT_SHUTDOWN_GRACE_PERIOD}; +use crate::error::NymvisorError; +use nix::sys::signal::Signal; +use nix::unistd::Pid; +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use std::ffi::OsStr; +use std::fmt::Debug; +use std::fs; +use std::future::Future; +use std::os::unix::fs::PermissionsExt; +use std::path::{Path, PathBuf}; +use std::pin::Pin; +use std::process::{ExitStatus, Stdio}; +use std::sync::Arc; +use std::task::{Context, Poll}; +use std::time::Duration; +use tokio::sync::Notify; +use tokio::time::{sleep, Sleep}; +use tracing::{debug, info, instrument, warn}; + +pub(crate) struct InterruptHandle(Arc); + +impl InterruptHandle { + pub(crate) fn interrupt_daemon(&self) { + self.0.notify_one() + } +} + +impl Drop for InterruptHandle { + fn drop(&mut self) { + self.interrupt_daemon(); + } +} + +#[derive(Debug)] +pub(crate) struct Daemon { + executable_path: PathBuf, + kill_timeout: Duration, +} + +impl Daemon { + pub(crate) fn from_config(config: &Config) -> Self { + Daemon { + executable_path: config.current_daemon_binary(), + kill_timeout: config.daemon.debug.shutdown_grace_period, + } + } + + pub(crate) fn new>(executable_path: P) -> Self { + Daemon { + executable_path: executable_path.as_ref().to_path_buf(), + kill_timeout: DEFAULT_SHUTDOWN_GRACE_PERIOD, + } + } + + #[instrument(skip(self), fields(self.executable_path = ?self.executable_path))] + pub(crate) fn get_build_information( + &self, + ) -> Result { + info!("attempting to obtain daemon build information"); + + // TODO: do we need any timeouts here or could we just assume this is not going to take an eternity to execute? + // I'm leaning towards the former + let raw = std::process::Command::new(&self.executable_path) + .args(["--no-banner", "build-info", "--output=json"]) + .output() + .map_err(|source| NymvisorError::DaemonBuildInformationFailure { + binary_path: self.executable_path.clone(), + source, + })?; + + debug!("execution status: {}", raw.status); + + if !raw.status.success() { + return Err(raw.status.into()); + } + + serde_json::from_slice(&raw.stdout) + .map_err(|source| NymvisorError::DaemonBuildInformationParseFailure { source }) + } + + #[instrument(skip(self), fields(self.executable_path = ?self.executable_path))] + pub(crate) fn verify_binary(&self) -> Result<(), NymvisorError> { + let metadata = fs::metadata(&self.executable_path).map_err(|source| { + NymvisorError::MetadataReadFailure { + path: self.executable_path.clone(), + source, + } + })?; + + if !metadata.is_file() { + return Err(NymvisorError::DaemonNotAFile { + path: self.executable_path.clone(), + }); + } + + let mut permissions = metadata.permissions(); + let mode = permissions.mode(); + let is_executable = mode & 0o111 != 0; + if !is_executable { + warn!( + "the binary does not seem to have executable bits sets. attempting to fix that..." + ); + let new_mode = mode | 0o111; // Set the three execute bits to on (a+x). + permissions.set_mode(new_mode); + + fs::set_permissions(&self.executable_path, permissions).map_err(|source| { + NymvisorError::DaemonPermissionFailure { + path: self.executable_path.clone(), + source, + } + })?; + } + + Ok(()) + } + + pub(crate) fn execute_async(&self, args: I) -> Result + where + I: IntoIterator, + S: AsRef, + { + // // TODO: we might have to pass env here + let child = tokio::process::Command::new(&self.executable_path) + .args(args) + .stdin(Stdio::null()) + .kill_on_drop(true) + .spawn() + .map_err(|source| NymvisorError::DaemonIoFailure { source })?; + + ExecutingDaemon::new(self.kill_timeout, child) + } +} + +#[must_use = "futures do nothing unless polled"] +#[non_exhaustive] +pub(crate) struct ExecutingDaemon { + child_id: i32, + kill_timeout_duration: Duration, + interrupt_sent: bool, + interrupt_handle: Option>, + + // TODO: can we maybe get rid of that dynamic dispatch here in favour of concrete types? + // interrupted: Option>>>, + interrupted: Pin + Send + Sync>>, + kill_timeout: Option>>, + child_future: Pin> + Send + Sync>>, + // child_future: futures::future::BoxFuture<> +} + +impl ExecutingDaemon { + fn new( + kill_timeout_duration: Duration, + mut child: tokio::process::Child, + ) -> Result { + if let Some(id) = child.id() { + let interrupt_handle = Arc::new(Notify::new()); + let notified_handle = Arc::clone(&interrupt_handle); + Ok(ExecutingDaemon { + child_id: id as i32, + kill_timeout_duration, + interrupt_sent: false, + interrupt_handle: Some(interrupt_handle), + interrupted: Box::pin(async move { notified_handle.notified().await }), + kill_timeout: None, + child_future: Box::pin(async move { child.wait().await }), + }) + } else { + // safety: if the child didn't return an id it means it has already terminated so it must be ready + #[allow(clippy::expect_used)] + Err(child + .try_wait() + .map_err(|source| NymvisorError::DaemonIoFailure { source })? + .expect("finished child did not return an exit status") + .into()) + } + } + + pub(crate) fn interrupt_handle(&mut self) -> InterruptHandle { + #[allow(clippy::expect_used)] + InterruptHandle( + self.interrupt_handle + .take() + .expect("the interrupt handle has already been obtained"), + ) + } + + fn signal_child(&self, signal: Signal) -> Result<(), NymvisorError> { + info!("sending {signal} to the daemon"); + nix::sys::signal::kill(Pid::from_raw(self.child_id), signal) + .map_err(|source| NymvisorError::DaemonSignalFailure { signal, source }) + } +} + +impl Future for ExecutingDaemon { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // 1. check if the child is done + if let Poll::Ready(result) = Pin::new(&mut self.child_future).poll(cx) { + return match result { + Ok(exit_status) => Poll::Ready(Ok(exit_status)), + Err(source) => Poll::Ready(Err(NymvisorError::DaemonIoFailure { source })), + }; + } + + // 2. check if we reached the timeout to kill the child + if let Some(ref mut kill_timeout) = &mut self.kill_timeout { + if kill_timeout.as_mut().poll(cx).is_ready() { + warn!("reached the graceful shutdown timeout. the daemon still hasn't finished. sending SIGKILL"); + self.signal_child(Signal::SIGKILL)?; + self.kill_timeout = None; + } + } + + // 3. check if we received a signal to interrupt the child + // note: Notified is a fused future so there's no point in polling it after it already finished + // safety: self.interrupted is always `Some` so the unwrap is fine + #[allow(clippy::unwrap_used)] + if !self.interrupt_sent && Pin::new(&mut self.interrupted).poll(cx).is_ready() { + assert!(self.kill_timeout.is_none()); + + self.signal_child(Signal::SIGINT)?; + self.interrupt_sent = true; + + // it seems we have to poll the future here to make sure it's registered for waking the waker + // note: this is guaranteed to either produce Poll::Ready or polling the kill timeout future + cx.waker().wake_by_ref(); + self.kill_timeout = Some(Box::pin(sleep(self.kill_timeout_duration))); + } + + Poll::Pending + } +} diff --git a/tools/nymvisor/src/env.rs b/tools/nymvisor/src/env.rs new file mode 100644 index 0000000000..24357e13ae --- /dev/null +++ b/tools/nymvisor/src/env.rs @@ -0,0 +1,230 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::NymvisorError; +use std::env::VarError; +use std::path::PathBuf; +use std::time::Duration; +use url::Url; + +const TRUTHY_BOOLS: &[&str] = &["true", "t", "1"]; +const FALSY_BOOLS: &[&str] = &["false", "f", "0"]; + +pub mod vars { + pub const NYMVISOR_ID: &str = "NYMVISOR_ID"; + pub const NYMVISOR_CONFIG_PATH: &str = "NYMVISOR_CONFIG_PATH"; + pub const NYMVISOR_UPSTREAM_BASE_UPGRADE_URL: &str = "NYMVISOR_UPSTREAM_BASE_UPGRADE_URL"; + pub const NYMVISOR_UPSTREAM_POLLING_RATE: &str = "NYMVISOR_UPSTREAM_POLLING_RATE"; + pub const NYMVISOR_DISABLE_LOGS: &str = "NYMVISOR_DISABLE_LOGS"; + pub const NYMVISOR_UPGRADE_DATA_DIRECTORY: &str = "NYMVISOR_UPGRADE_DATA_DIRECTORY"; + + pub const DAEMON_NAME: &str = "DAEMON_NAME"; + pub const DAEMON_HOME: &str = "DAEMON_HOME"; + pub const DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL: &str = "DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL"; + pub const DAEMON_ALLOW_BINARIES_DOWNLOAD: &str = "DAEMON_ALLOW_BINARIES_DOWNLOAD"; + pub const DAEMON_ENFORCE_DOWNLOAD_CHECKSUM: &str = "DAEMON_ENFORCE_DOWNLOAD_CHECKSUM"; + pub const DAEMON_RESTART_AFTER_UPGRADE: &str = "DAEMON_RESTART_AFTER_UPGRADE"; + pub const DAEMON_RESTART_ON_FAILURE: &str = "DAEMON_RESTART_ON_FAILURE"; + pub const DAEMON_FAILURE_RESTART_DELAY: &str = "DAEMON_FAILURE_RESTART_DELAY"; + pub const DAEMON_MAX_STARTUP_FAILURES: &str = "DAEMON_MAX_STARTUP_FAILURES"; + pub const DAEMON_STARTUP_PERIOD_DURATION: &str = "DAEMON_STARTUP_PERIOD_DURATION"; + pub const DAEMON_SHUTDOWN_GRACE_PERIOD: &str = "DAEMON_SHUTDOWN_GRACE_PERIOD"; + pub const DAEMON_BACKUP_DATA_DIRECTORY: &str = "DAEMON_BACKUP_DATA_DIRECTORY"; + pub const DAEMON_UNSAFE_SKIP_BACKUP: &str = "DAEMON_UNSAFE_SKIP_BACKUP"; +} + +pub(crate) fn setup_env(config_env_file: &Option) -> Result<(), NymvisorError> { + if let Some(env_file) = config_env_file { + dotenvy::from_path_override(env_file).map_err(Into::into) + } else { + Ok(()) + } +} + +pub(crate) struct Env { + pub(crate) nymvisor_id: Option, + pub(crate) nymvisor_config_path: Option, + pub(crate) nymvisor_upstream_base_upgrade_url: Option, + pub(crate) nymvisor_upstream_polling_rate: Option, + pub(crate) nymvisor_disable_logs: Option, + pub(crate) nymvisor_upgrade_data_directory: Option, + + pub(crate) daemon_name: Option, + pub(crate) daemon_home: Option, + pub(crate) daemon_absolute_upstream_upgrade_url: Option, + pub(crate) daemon_allow_binaries_download: Option, + pub(crate) daemon_enforce_download_checksum: Option, + pub(crate) daemon_restart_after_upgrade: Option, + pub(crate) daemon_restart_on_failure: Option, + pub(crate) daemon_failure_restart_delay: Option, + pub(crate) daemon_max_startup_failures: Option, + pub(crate) daemon_startup_period_duration: Option, + pub(crate) daemon_shutdown_grace_period: Option, + pub(crate) backup_data_directory: Option, + pub(crate) daemon_unsafe_skip_backup: Option, +} + +impl Env { + pub(crate) fn override_config(&self, config: &mut Config) { + if let Some(nymvisor_id) = &self.nymvisor_id { + config.nymvisor.id = nymvisor_id.clone(); + } + if let Some(upstream) = &self.nymvisor_upstream_base_upgrade_url { + config.nymvisor.debug.upstream_base_upgrade_url = upstream.clone() + } + if let Some(polling_rate) = self.nymvisor_upstream_polling_rate { + config.nymvisor.debug.upstream_polling_rate = polling_rate + } + if let Some(nymvisor_disable_logs) = self.nymvisor_disable_logs { + config.nymvisor.debug.disable_logs = nymvisor_disable_logs; + } + if let Some(nymvisor_upgrade_data_directory) = &self.nymvisor_upgrade_data_directory { + config.nymvisor.debug.upgrade_data_directory = + Some(nymvisor_upgrade_data_directory.clone()); + } + if let Some(daemon_name) = &self.daemon_name { + config.daemon.name = daemon_name.clone(); + } + if let Some(daemon_home) = &self.daemon_home { + config.daemon.home = daemon_home.clone(); + } + if let Some(upstream) = &self.daemon_absolute_upstream_upgrade_url { + config.daemon.debug.absolute_upstream_upgrade_url = Some(upstream.clone()) + } + if let Some(daemon_allow_binaries_download) = self.daemon_allow_binaries_download { + config.daemon.debug.allow_binaries_download = daemon_allow_binaries_download; + } + if let Some(daemon_enforce_download_checksum) = self.daemon_enforce_download_checksum { + config.daemon.debug.enforce_download_checksum = daemon_enforce_download_checksum; + } + if let Some(daemon_restart_after_upgrade) = self.daemon_restart_after_upgrade { + config.daemon.debug.restart_after_upgrade = daemon_restart_after_upgrade; + } + if let Some(daemon_restart_on_failure) = self.daemon_restart_on_failure { + config.daemon.debug.restart_on_failure = daemon_restart_on_failure; + } + if let Some(daemon_failure_restart_delay) = self.daemon_failure_restart_delay { + config.daemon.debug.failure_restart_delay = daemon_failure_restart_delay; + } + if let Some(daemon_max_startup_failures) = self.daemon_max_startup_failures { + config.daemon.debug.max_startup_failures = daemon_max_startup_failures; + } + if let Some(daemon_startup_period_duration) = self.daemon_startup_period_duration { + config.daemon.debug.startup_period_duration = daemon_startup_period_duration; + } + if let Some(daemon_shutdown_grace_period) = self.daemon_shutdown_grace_period { + config.daemon.debug.shutdown_grace_period = daemon_shutdown_grace_period; + } + if let Some(backup_data_directory) = &self.backup_data_directory { + config.daemon.debug.backup_data_directory = Some(backup_data_directory.clone()); + } + if let Some(daemon_unsafe_skip_backup) = self.daemon_unsafe_skip_backup { + config.daemon.debug.unsafe_skip_backup = daemon_unsafe_skip_backup; + } + } +} + +// TODO: all of those seem like they could be moved to some common crate if we ever needed similar functionality elsewhere +fn read_string(var: &str) -> Result, NymvisorError> { + match std::env::var(var) { + Ok(val) => Ok(Some(val)), + Err(VarError::NotPresent) => Ok(None), + Err(VarError::NotUnicode(value)) => Err(NymvisorError::MalformedEnvVariable { + variable: var.to_string(), + value, + }), + } +} + +fn read_bool(var: &str) -> Result, NymvisorError> { + read_string(var)? + .map(|raw| { + let normalised = raw.to_ascii_lowercase(); + if TRUTHY_BOOLS.contains(&&*normalised) { + Ok(true) + } else if FALSY_BOOLS.contains(&&*normalised) { + Ok(false) + } else { + Err(NymvisorError::MalformedBoolEnvVariable { + variable: var.to_string(), + value: raw.to_string(), + }) + } + }) + .transpose() +} + +fn read_duration(var: &str) -> Result, NymvisorError> { + read_string(var)? + .map(|raw| { + humantime::parse_duration(&raw).map_err(|source| { + NymvisorError::MalformedDurationEnvVariable { + variable: var.to_string(), + value: raw.to_string(), + source, + } + }) + }) + .transpose() +} + +fn read_pathbuf(var: &str) -> Result, NymvisorError> { + Ok(read_string(var)?.map(PathBuf::from)) +} + +fn read_url(var: &str) -> Result, NymvisorError> { + read_string(var)? + .map(|raw| { + raw.parse() + .map_err(|source| NymvisorError::MalformedUrlEnvVariable { + variable: var.to_string(), + value: raw.to_string(), + source, + }) + }) + .transpose() +} + +fn read_usize(var: &str) -> Result, NymvisorError> { + read_string(var)? + .map(|raw| { + raw.parse() + .map_err(|source| NymvisorError::MalformedNumberEnvVariable { + variable: var.to_string(), + value: raw.to_string(), + source, + }) + }) + .transpose() +} + +impl Env { + // in general, if variable is missing from the environment that's fine. + // however, if something is out there, it MUST BE valid + pub(crate) fn try_read() -> Result { + Ok(Env { + nymvisor_id: read_string(vars::NYMVISOR_ID)?, + nymvisor_config_path: read_pathbuf(vars::NYMVISOR_CONFIG_PATH)?, + nymvisor_upstream_base_upgrade_url: read_url(vars::NYMVISOR_UPSTREAM_BASE_UPGRADE_URL)?, + nymvisor_upstream_polling_rate: read_duration(vars::NYMVISOR_UPSTREAM_POLLING_RATE)?, + nymvisor_disable_logs: read_bool(vars::NYMVISOR_DISABLE_LOGS)?, + nymvisor_upgrade_data_directory: read_pathbuf(vars::NYMVISOR_UPGRADE_DATA_DIRECTORY)?, + daemon_name: read_string(vars::DAEMON_NAME)?, + daemon_home: read_pathbuf(vars::DAEMON_HOME)?, + daemon_absolute_upstream_upgrade_url: read_url( + vars::DAEMON_ABSOLUTE_UPSTREAM_UPGRADE_URL, + )?, + daemon_allow_binaries_download: read_bool(vars::DAEMON_ALLOW_BINARIES_DOWNLOAD)?, + daemon_enforce_download_checksum: read_bool(vars::DAEMON_ENFORCE_DOWNLOAD_CHECKSUM)?, + daemon_restart_after_upgrade: read_bool(vars::DAEMON_RESTART_AFTER_UPGRADE)?, + daemon_restart_on_failure: read_bool(vars::DAEMON_RESTART_ON_FAILURE)?, + daemon_failure_restart_delay: read_duration(vars::DAEMON_FAILURE_RESTART_DELAY)?, + daemon_max_startup_failures: read_usize(vars::DAEMON_MAX_STARTUP_FAILURES)?, + daemon_startup_period_duration: read_duration(vars::DAEMON_STARTUP_PERIOD_DURATION)?, + daemon_shutdown_grace_period: read_duration(vars::DAEMON_SHUTDOWN_GRACE_PERIOD)?, + backup_data_directory: read_pathbuf(vars::DAEMON_BACKUP_DATA_DIRECTORY)?, + daemon_unsafe_skip_backup: read_bool(vars::DAEMON_UNSAFE_SKIP_BACKUP)?, + }) + } +} diff --git a/tools/nymvisor/src/error.rs b/tools/nymvisor/src/error.rs new file mode 100644 index 0000000000..9d12c78861 --- /dev/null +++ b/tools/nymvisor/src/error.rs @@ -0,0 +1,435 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::upgrades::types::{CurrentVersionInfo, DigestAlgorithm, UpgradeInfo}; +use async_file_watcher::NotifyError; +use nix::errno::Errno; +use nix::sys::signal::Signal; +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use std::ffi::OsString; +use std::io; +use std::num::ParseIntError; +use std::path::PathBuf; +use std::process::ExitStatus; +use thiserror::Error; +use url::Url; + +#[derive(Debug, Error)] +pub(crate) enum NymvisorError { + #[error( + "failed to load config file for id {id} using path '{}'. detailed message: {source}", path.display() + )] + ConfigLoadFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save config file for id {id} using path '{}'. detailed message: {source}", path.display() + )] + ConfigSaveFailure { + id: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to load upgrade info using path '{}'. detailed message: {source}", path.display() + )] + UpgradeInfoLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save upgrade info for upgrade '{name}' using path '{}'. detailed message: {source}", path.display() + )] + UpgradeInfoSaveFailure { + name: String, + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("there seem to be an upgrade-info.json file present without the associated binary for upgrade '{name}' at path {}", path.display())] + UpgradeInfoWithNoBinary { name: String, path: PathBuf }, + + #[error("there seem to already exist the upgrade-plan.json at {}, but it has mismatched current information. The expected current is {current_name} but the saved one specifies {existing_name}", path.display())] + PreexistingUpgradePlan { + path: PathBuf, + current_name: String, + existing_name: String, + }, + + #[error( + "failed to load upgrade plan using path '{}'. detailed message: {source}", path.display() + )] + UpgradePlanLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save upgrade plan using path '{}'. detailed message: {source}", path.display() + )] + UpgradePlanSaveFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to load upgrade history using path '{}'. detailed message: {source}", path.display() + )] + UpgradeHistoryLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save upgrade history using path '{}'. detailed message: {source}", path.display() + )] + UpgradeHistorySaveFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to load current version information using path '{}'. detailed message: {source}", path.display() + )] + CurrentVersionInfoLoadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "failed to save current version information using path '{}'. detailed message: {source}", path.display() + )] + CurrentVersionInfoSaveFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error( + "the current version information does not match the expected `current/upgrade-info.json`.\n\ +The daemon version is:\n{current_version_info:#?}\n\ +While the stored info point to:\n{current_info:#?}" + )] + UnexpectedCurrentVersionInfo { + current_info: Box, + current_version_info: Box, + }, + + #[error( + "the current daemon build information does not match the expected stored value.\n\ +The daemon build is:\n{daemon_info:#?}\n\ +While the stored info point to:\n{stored_info:#?}" + )] + UnexpectedDaemonBuild { + daemon_info: Box, + stored_info: Box, + }, + + #[error("the daemon for upgrade '{upgrade_name}' has version {daemon_version} while {expected} was expected instead")] + UnexpectedUpgradeDaemonVersion { + upgrade_name: String, + daemon_version: String, + expected: String, + }, + + #[error("the provided daemon at {} is not a file", path.display())] + DaemonNotAFile { path: PathBuf }, + + #[error("could not read daemon's metadata at {}: {source}", path.display())] + MetadataReadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not adjust permission of the daemon at: {}: {source}", path.display())] + DaemonPermissionFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not acquire the lock at {} to perform binary upgrade with error code {libc_code}. It is either held by another process or this nymvisor has experienced a critical failure during previous upgrade attempt", lock_path.display())] + UnableToAcquireUpgradePlanLock { + lock_path: PathBuf, + libc_code: Errno, + }, + + #[error("could not create the lock file at {} to perform binary upgrade: {source}", path.display())] + LockFileCreationFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not remove the lock file at {} after performing binary upgrade: {source}", path.display())] + LockFileRemovalFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not create the backup file at {} before performing binary upgrade: {source}", path.display())] + BackupFileCreationFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not tar backup directory {} to {}: {source}", data_source.display(), path.display())] + BackupTarDirFailure { + path: PathBuf, + data_source: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not tar backup file {} to {}: {source}", data_source.display(), path.display())] + BackupTarFileFailure { + path: PathBuf, + data_source: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not finalize the tar backup file at {}: {source}", path.display())] + BackupTarFinalizationFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to initialise the path '{}': {source}", path.display())] + PathInitFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the provided env file was malformed: {source}")] + MalformedEnvFile { + #[from] + source: dotenvy::Error, + }, + + #[error("the value provided for environmental variable '{variable}' was not valid unicode: {value:?}")] + MalformedEnvVariable { variable: String, value: OsString }, + + #[error("the value provided for environmental boolean variable '{variable}': '{value}' is not a valid boolean")] + MalformedBoolEnvVariable { variable: String, value: String }, + + #[error("the value provided for environmental duration variable '{variable}': '{value}' is not a valid duration: {source}")] + MalformedDurationEnvVariable { + variable: String, + value: String, + #[source] + source: humantime::DurationError, + }, + + #[error("the value provided for environmental numerical variable '{variable}': '{value}' is not a valid number: {source}")] + MalformedNumberEnvVariable { + variable: String, + value: String, + #[source] + source: ParseIntError, + }, + + #[error("the value provided for environmental Url '{variable}': '{value}' is not a valid number: {source}")] + MalformedUrlEnvVariable { + variable: String, + value: String, + #[source] + source: url::ParseError, + }, + + #[error("failed to copy daemon binary from '{}' to '{}': {source}", source_path.display(), target_path.display())] + DaemonBinaryCopyFailure { + source_path: PathBuf, + target_path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to create symlink from '{}' to '{}': {source}", source_path.display(), target_path.display())] + SymlinkCreationFailure { + source_path: PathBuf, + target_path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to remove symlink at '{}': {source}", path.display())] + SymlinkRemovalFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the value of daemon home has to be provided by either `--daemon-home` flag or `$DAEMON_HOME` environmental variable")] + DaemonHomeUnavailable, + + #[error("failed to obtain build information from the daemon executable ('{}'): {source}", binary_path.display())] + DaemonBuildInformationFailure { + binary_path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("failed to parse build information from the daemon executable: {source}")] + DaemonBuildInformationParseFailure { + #[source] + source: serde_json::Error, + }, + + #[error("the daemon execution has failed with the following exit code: {exit_code:?}. the associated signal code: {signal_code:?}. the core was dumped: {core_dumped}")] + DaemonExecutionFailure { + // exit code of the process, if any + exit_code: Option, + + // if the process was WIFSIGNALED, this returns WTERMSIG. + signal_code: Option, + core_dumped: bool, + }, + + #[error("the daemon execution has experienced an io failure: {source}")] + DaemonIoFailure { + #[source] + source: io::Error, + }, + + #[error("there was already a genesis binary present for {daemon_name} which was different that the one provided.\nProvided:\n{provided_genesis:#?}\nExisting:\n{existing_info:#?}")] + DuplicateDaemonGenesisBinary { + daemon_name: String, + existing_info: Box, + provided_genesis: Box, + }, + + #[error("there already exist upgrade binary for '{name}' at: {}. if you want to overwrite its content, use --force flag", path.display())] + ExistingUpgrade { name: String, path: PathBuf }, + + #[error("there already exist upgrade information for '{name}' at: {}. if you want to overwrite its content, use --force flag", path.display())] + ExistingUpgradeInfo { name: String, path: PathBuf }, + + #[error("the current upgrade-plan.json has planned upgrade for '{name}', but no corresponding upgrade-info.json file could be found")] + UpgradePlanWithNoInfo { name: String }, + + #[error("there was already a symlink for the 'current' binary of {daemon_name}. it's pointing to {} while we needed to create one to {}", link.display(), expected_link.display())] + ExistingCurrentSymlink { + daemon_name: String, + link: PathBuf, + expected_link: PathBuf, + }, + + #[error("failed to send to send {signal} to the daemon process: {source}")] + DaemonSignalFailure { + signal: Signal, + #[source] + source: nix::Error, + }, + + #[error("failed to watch for changes in the upgrade-plan.json: {source}")] + UpgradePlanFileWatchFailure { + #[from] + source: NotifyError, + }, + + #[error("failed to query the upstream url ('{url}'): {source}")] + UpstreamQueryFailure { + url: Url, + #[source] + source: reqwest::Error, + }, + + #[error( + "attempted to perform binary upgrade with no upgrades queued up in the upgrade plan file" + )] + NoQueuedUpgrades, + + #[error("could not find the upgrade binary at {} while the binary download is disabled", path.display())] + NoUpgradeBinaryWithDisabledDownload { path: PathBuf }, + + #[error("upgrade '{upgrade_name}' does not have any valid download URLs for the current arch '{arch}'. The available arches are: {available:?}")] + NoDownloadUrls { + upgrade_name: String, + arch: String, + available: Vec, + }, + + #[error("failed to download the upgrade binary from '{url}': {source}")] + UpgradeDownloadFailure { + url: Url, + #[source] + source: reqwest::Error, + }, + + #[error("failed to verify checksum for upgrade '{upgrade_name}' using {algorithm}. Got '{encoded_checksum}' while expected '{expected_checksum}'")] + DownloadChecksumFailure { + upgrade_name: String, + encoded_checksum: String, + expected_checksum: String, + algorithm: DigestAlgorithm, + }, + + #[error("could not calculate checksum for downloaded file at '{}': {source}", path.display())] + ChecksumCalculationFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("download information for upgrade '{upgrade_name}' is missing checksum")] + MissingDownloadChecksum { upgrade_name: String }, + + #[error("failed to create daemon binary at {}: {source}", path.display())] + DaemonBinaryCreationFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("the daemon has reached the maximum number of startup failures ({failures})")] + DaemonMaximumStartupFailures { failures: usize }, + + #[error("the daemon restart on failure is disabled")] + DisabledRestartOnFailure, + + #[error("failed to read directory content of nymvisor instances at {}: {source}", path.display())] + InstancesReadFailure { + path: PathBuf, + #[source] + source: io::Error, + }, + + #[error("could not load the default config file as there isn't a single nymvisor instance initiated (there are {instances}). please specify either $NYMVISOR_CONFIG_PATH or $NYMVISOR_ID")] + NotSingleton { instances: usize }, + + #[error("failed to crate tokio's runtime: {source}")] + RuntimeCreationFailure { source: io::Error }, +} + +impl From for NymvisorError { + fn from(value: ExitStatus) -> Self { + use std::os::unix::prelude::ExitStatusExt; + + assert!(!value.success()); + NymvisorError::DaemonExecutionFailure { + exit_code: value.code(), + signal_code: value.signal(), + core_dumped: value.core_dumped(), + } + } +} diff --git a/tools/nymvisor/src/helpers.rs b/tools/nymvisor/src/helpers.rs new file mode 100644 index 0000000000..184243a9fa --- /dev/null +++ b/tools/nymvisor/src/helpers.rs @@ -0,0 +1,43 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::error::NymvisorError; +use sha2::Digest; +use std::fs::File; +use std::io::{BufReader, Read}; +use std::path::Path; +use std::{fs, io}; +use tracing::trace; + +pub(crate) fn init_path>(path: P) -> Result<(), NymvisorError> { + let path = path.as_ref(); + trace!("initialising {}", path.display()); + + fs::create_dir_all(path).map_err(|source| NymvisorError::PathInitFailure { + path: path.to_path_buf(), + source, + }) +} + +pub fn calculate_file_checksum>( + filepath: P, +) -> Result, io::Error> { + let file = File::open(filepath)?; + let mut reader = BufReader::new(file); + + let mut hasher = D::new(); + let mut buf = vec![0; 4096]; + loop { + let n = reader.read(&mut buf)?; + if n == 0 { + break; + } + hasher.update(&buf[..n]) + } + + Ok(hasher.finalize().to_vec()) +} + +pub fn to_hex_string>(input: T) -> String { + hex::encode(input) +} diff --git a/tools/nymvisor/src/main.rs b/tools/nymvisor/src/main.rs new file mode 100644 index 0000000000..50d703a2da --- /dev/null +++ b/tools/nymvisor/src/main.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +#![warn(clippy::expect_used)] +#![warn(clippy::unwrap_used)] +#![warn(clippy::todo)] +#![warn(clippy::dbg_macro)] + +use crate::cli::Cli; +use clap::Parser; + +pub(crate) mod cli; +pub(crate) mod config; +pub(crate) mod daemon; +pub(crate) mod env; +pub(crate) mod error; +pub(crate) mod helpers; +pub(crate) mod tasks; +pub(crate) mod upgrades; + +fn main() -> anyhow::Result<()> { + let args = Cli::parse(); + + Ok(args.execute()?) +} diff --git a/tools/nymvisor/src/tasks/launcher/backup.rs b/tools/nymvisor/src/tasks/launcher/backup.rs new file mode 100644 index 0000000000..b63c10aecd --- /dev/null +++ b/tools/nymvisor/src/tasks/launcher/backup.rs @@ -0,0 +1,122 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::NYMVISOR_DIR; +use crate::error::NymvisorError; +use crate::helpers::init_path; +use flate2::write::GzEncoder; +use flate2::Compression; +use std::fs; +use std::fs::{DirEntry, File}; +use std::path::{Path, PathBuf}; +use time::{format_description, OffsetDateTime}; +use tracing::info; + +fn generate_backup_filename() -> String { + // safety: this expect is fine as we're using a constant formatter. + #[allow(clippy::expect_used)] + let format = format_description::parse( + "[year]-[month]-[day]-[hour][minute][second][subsecond digits:3]", + ) + .expect("our time formatter is malformed"); + #[allow(clippy::expect_used)] + let now = OffsetDateTime::now_utc() + .format(&format) + .expect("our time formatter failed to format the current time"); + + format!("backup-{now}-preupgrade.tar.gz") +} + +pub(crate) struct BackupBuilder { + tar_builder: tar::Builder>, + backup_filepath: PathBuf, +} + +impl BackupBuilder { + pub(crate) fn new>(backup_directory: P) -> Result { + let backup_directory = backup_directory.as_ref(); + let backup_filepath = backup_directory.join(generate_backup_filename()); + + // create the backup directory itself (i.e. specific for this upgrade) if it doesn't yet exist + init_path(backup_directory)?; + + // create the backup file + let backup_file = fs::File::create(&backup_filepath).map_err(|source| { + NymvisorError::BackupFileCreationFailure { + path: backup_filepath.clone(), + source, + } + })?; + + let enc = GzEncoder::new(backup_file, Compression::default()); + let tar_builder = tar::Builder::new(enc); + Ok(BackupBuilder { + tar_builder, + backup_filepath, + }) + } + + fn backup_subdir(&mut self, dir_entry: DirEntry) -> Result<(), NymvisorError> { + let path = dir_entry.path(); + let filename = dir_entry.file_name(); + info!( + "attempting to put {} into the backup tar file", + path.display() + ); + + if dir_entry.file_name() == NYMVISOR_DIR { + info!("skipping the /{NYMVISOR_DIR}..."); + return Ok(()); + } + + if path.is_dir() { + self.tar_builder + .append_dir_all(filename, &path) + .map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: path, + source, + }) + } else { + self.tar_builder + .append_path_with_name(&path, filename) + .map_err(|source| NymvisorError::BackupTarFileFailure { + path: self.backup_filepath.clone(), + data_source: path, + source, + }) + } + } + + fn finish(mut self) -> Result<(), NymvisorError> { + self.tar_builder + .finish() + .map_err(|source| NymvisorError::BackupTarFinalizationFailure { + path: self.backup_filepath, + source, + }) + } + + pub(crate) fn backup_daemon_home>( + mut self, + daemon_home: P, + ) -> Result<(), NymvisorError> { + let home = daemon_home.as_ref(); + let home_entry = + fs::read_dir(home).map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: home.to_path_buf(), + source, + })?; + + for path in home_entry { + let dir_entry = path.map_err(|source| NymvisorError::BackupTarDirFailure { + path: self.backup_filepath.clone(), + data_source: home.to_path_buf(), + source, + })?; + self.backup_subdir(dir_entry)?; + } + self.finish() + } +} diff --git a/tools/nymvisor/src/tasks/launcher/mod.rs b/tools/nymvisor/src/tasks/launcher/mod.rs new file mode 100644 index 0000000000..a091ba1866 --- /dev/null +++ b/tools/nymvisor/src/tasks/launcher/mod.rs @@ -0,0 +1,235 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::daemon::Daemon; +use crate::error::NymvisorError; +use crate::tasks::launcher::backup::BackupBuilder; +use crate::upgrades::types::{CurrentVersionInfo, UpgradeInfo}; +use crate::upgrades::{perform_upgrade, types::UpgradePlan, UpgradeResult}; +use async_file_watcher::FileWatcherEventReceiver; +use futures::future::{FusedFuture, OptionFuture}; +use futures::{FutureExt, StreamExt}; +use nym_task::signal::wait_for_signal; +use std::time::Duration; +use time::OffsetDateTime; +use tokio::pin; +use tokio::time::sleep; +use tracing::{debug, error, info, warn}; + +mod backup; + +pub(crate) struct DaemonLauncher { + config: Config, + upgrade_plan_watcher: FileWatcherEventReceiver, +} + +impl DaemonLauncher { + pub(crate) fn new(config: Config, upgrade_plan_watcher: FileWatcherEventReceiver) -> Self { + DaemonLauncher { + config, + upgrade_plan_watcher, + } + } + + pub(crate) async fn run_loop(&mut self, args: Vec) -> Result<(), NymvisorError> { + let mut startup_failures = 0; + loop { + let run_start = tokio::time::Instant::now(); + + let res = self.run_and_upgrade(args.clone()).await; + let run_duration = run_start.elapsed(); + info!( + "the daemon has run for {}", + humantime::format_duration(run_duration) + ); + + match res { + Ok(upgrade_result) => { + if upgrade_result.requires_manual_intervention { + info!("this upgrade requires manual intervention. Please read the release notes carefully and follow the provided instructions before starting nymvisor again"); + return Ok(()); + } + + if upgrade_result.binary_swapped { + if !self.config.daemon.debug.restart_after_upgrade { + info!("upgrade detected, DAEMON_RESTART_AFTER_UPGRADE is off. Verify new upgrade and start nymvisor again"); + return Ok(()); + } + // else - binary has been swapped and restarting is enabled: do restart + } else { + // binary has finished its execution (short-lived process) without upgrades + return Ok(()); + } + } + Err(failure) => { + error!("daemon failed with the following error: {failure}"); + + if !self.config.daemon.debug.restart_on_failure { + return Err(NymvisorError::DisabledRestartOnFailure); + } + + if run_duration < self.config.daemon.debug.startup_period_duration { + startup_failures += 1; + } else { + startup_failures = 1; + } + + if startup_failures >= self.config.daemon.debug.max_startup_failures { + return Err(NymvisorError::DaemonMaximumStartupFailures { + failures: startup_failures, + }); + } + + info!( + "waiting for {} before attempting to restart the daemon...", + humantime::format_duration(self.config.daemon.debug.failure_restart_delay) + ); + sleep(self.config.daemon.debug.failure_restart_delay).await; + // restart + } + } + info!("the daemon will be now restarted") + } + } + + /// the full upgrade process process, i.e. run until upgrade, do backup and perform the upgrade. + /// returns a boolean indicating whether an upgrade has been performed + async fn run_and_upgrade(&mut self, args: Vec) -> Result { + let upgrade_available = self.wait_for_upgrade_or_termination(args.clone()).await?; + if !upgrade_available { + return Ok(UpgradeResult::new_shortlived()); + } + + if !self.config.daemon.debug.unsafe_skip_backup { + self.perform_backup()?; + } + + perform_upgrade(&self.config).await + // if we ever wanted to introduce any pre-upgrade scripts like cosmovisor, they'd go here + } + + /// this function gets called whenever the file watcher detects changes in the upgrade plan file + /// it returns an option indicating when the next upgrade should be performed + fn check_upgrade_plan_changes(&self) -> Option { + info!("checking changes in the upgrade plan file..."); + + let current_upgrade_plan = match UpgradePlan::try_load(self.config.upgrade_plan_filepath()) + { + Ok(upgrade_plan) => upgrade_plan, + Err(err) => { + error!("failed to read the current upgrade plan: {err}"); + return None; + } + }; + + if let Some(next) = current_upgrade_plan.next_upgrade() { + let now = OffsetDateTime::now_utc(); + Some((next.upgrade_time - now).try_into().unwrap_or_default()) + } else { + None + } + } + + // responsible for running until exit or until update is detected + async fn wait_for_upgrade_or_termination( + &mut self, + args: Vec, + ) -> Result { + let daemon = Daemon::from_config(&self.config); + + let current_info = UpgradeInfo::try_load(self.config.current_upgrade_info_filepath())?; + let expected_version = + CurrentVersionInfo::try_load(self.config.current_daemon_version_filepath())?; + let daemon_info = daemon.get_build_information()?; + + current_info.ensure_matches(&expected_version)?; + if expected_version.binary_details != daemon_info { + return Err(NymvisorError::UnexpectedDaemonBuild { + daemon_info: Box::new(daemon_info), + stored_info: Box::new(expected_version.binary_details), + }); + } + + let mut running_daemon = daemon.execute_async(args)?; + let interrupt_handle = running_daemon.interrupt_handle(); + + // we need to fuse the daemon future so that we could check if it has already terminated + let mut fused_runner = running_daemon.fuse(); + + let mut upgrade_timeout: OptionFuture<_> = self + .check_upgrade_plan_changes() + .map(sleep) + .map(Box::pin) + .map(FutureExt::fuse) + .into(); + + let signal_fut = wait_for_signal(); + pin!(signal_fut); + + let mut received_interrupt = false; + loop { + tokio::select! { + daemon_res = &mut fused_runner => { + warn!("the daemon has terminated by itself - was it a short lived command?"); + let exit_status = daemon_res?; + info!("it finished with the following exit status: {exit_status}"); + return Ok(false) + } + event = &mut self.upgrade_plan_watcher.next() => { + let Some(event) = event else { + // this is a critical failure since the file watcher task should NEVER terminate by itself + error!("CRITICAL FAILURE: the upgrade plan watcher channel got closed"); + panic!("CRITICAL FAILURE: the upgrade plan watcher channel got closed") + }; + + debug!("the file has changed - {event:?}"); + if let Some(next_upgrade) = self.check_upgrade_plan_changes() { + info!("setting the upgrade timeout to {}", humantime::format_duration(next_upgrade)); + upgrade_timeout = Some(Box::pin(sleep(next_upgrade)).fuse()).into() + } + + } + _ = &mut upgrade_timeout, if !upgrade_timeout.is_terminated() => { + info!("the upgrade timeout has elapsed. the daemon will be now stopped in order to perform the upgrade"); + break + } + _ = &mut signal_fut => { + received_interrupt = true; + info!("the nymvisor has received an interrupt. the daemon will be now stopped before exiting"); + break + } + } + } + + if fused_runner.is_terminated() { + return Ok(false); + } + interrupt_handle.interrupt_daemon(); + + match fused_runner.await { + Ok(exit_status) => { + info!("the daemon finished with the following exit status: {exit_status}"); + } + Err(err) => { + warn!("the daemon finished with an error: {err}"); + } + } + + // if we received an interrupt, don't try to perform upgrade, just exit the nymvisor + Ok(!received_interrupt) + } + + fn perform_backup(&self) -> Result<(), NymvisorError> { + let plan = UpgradePlan::try_load(self.config.upgrade_plan_filepath())?; + + let Some(upgrade_name) = plan.next_upgrade().map(|u| &u.name) else { + // this should NEVER be possible, but because those famous last words have been said before, + // let's just return an error when it inevitably happens + return Err(NymvisorError::NoQueuedUpgrades); + }; + + BackupBuilder::new(self.config.daemon_upgrade_backup_dir(upgrade_name))? + .backup_daemon_home(&self.config.daemon.home) + } +} diff --git a/tools/nymvisor/src/tasks/mod.rs b/tools/nymvisor/src/tasks/mod.rs new file mode 100644 index 0000000000..52ab5f1c33 --- /dev/null +++ b/tools/nymvisor/src/tasks/mod.rs @@ -0,0 +1,6 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod launcher; +pub(crate) mod upgrade_plan_watcher; +pub(crate) mod upstream_poller; diff --git a/tools/nymvisor/src/tasks/upgrade_plan_watcher.rs b/tools/nymvisor/src/tasks/upgrade_plan_watcher.rs new file mode 100644 index 0000000000..7ca896946b --- /dev/null +++ b/tools/nymvisor/src/tasks/upgrade_plan_watcher.rs @@ -0,0 +1,25 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::NymvisorError; +use async_file_watcher::{AsyncFileWatcher, FileWatcherEventReceiver, NotifyResult}; +use futures::channel::mpsc; +use tokio::task::JoinHandle; +use tracing::warn; + +pub(crate) fn start_upgrade_plan_watcher( + config: &Config, +) -> Result<(FileWatcherEventReceiver, JoinHandle>), NymvisorError> { + let (events_sender, events_receiver) = mpsc::unbounded(); + let mut watcher = + AsyncFileWatcher::new_file_changes_watcher(config.upgrade_plan_filepath(), events_sender)?; + + let join_handle = tokio::spawn(async move { + let res = watcher.watch().await; + warn!("the upgrade plan watcher has stopped"); + res + }); + + Ok((events_receiver, join_handle)) +} diff --git a/tools/nymvisor/src/tasks/upstream_poller.rs b/tools/nymvisor/src/tasks/upstream_poller.rs new file mode 100644 index 0000000000..21e1be8891 --- /dev/null +++ b/tools/nymvisor/src/tasks/upstream_poller.rs @@ -0,0 +1,81 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::NymvisorError; +use crate::upgrades::types::{UpgradeInfo, UpgradePlan}; +use reqwest::get; +use tokio::task::JoinHandle; +use tracing::{debug, error, warn}; + +pub(crate) struct UpstreamPoller { + config: Config, +} + +impl UpstreamPoller { + pub(crate) fn new(config: &Config) -> Self { + UpstreamPoller { + config: config.clone(), + } + } + + /// Poll the upstream url to see if new upgrade has been published. + /// If so, save it to `upgrade-info.json` and update the `upgrade-plan.json` + async fn check_upstream(&self) -> Result<(), NymvisorError> { + let upgrade_info: UpgradeInfo = get(self.config.upstream_upgrade_url()) + .await + .map_err(|source| NymvisorError::UpstreamQueryFailure { + url: self.config.upstream_upgrade_url(), + source, + })? + .json() + .await + .map_err(|source| NymvisorError::UpstreamQueryFailure { + url: self.config.upstream_upgrade_url(), + source, + })?; + + debug!("obtained the following upgrade info: {upgrade_info:?}"); + + let mut plan = UpgradePlan::try_load(self.config.upgrade_plan_filepath())?; + + // if the current version is the same as the one announced by upstream, we're done + if upgrade_info.version == plan.current().version { + debug!("the upstream version is the same one as the currently used"); + return Ok(()); + } + + if !plan.has_planned(&upgrade_info) { + if let Err(err) = + upgrade_info.save(self.config.upgrade_info_filepath(&upgrade_info.name)) + { + error!("failed to save new upgrade info: {err}"); + return Err(err); + } + debug!("saved new upgrade info"); + + if let Err(err) = plan.insert_new_upgrade(upgrade_info) { + error!("failed to insert new upgrade info into the current upgrade plan: {err}"); + return Err(err); + } + debug!("inserted new upgrade info into the upgrade plan"); + } + + Ok(()) + } + + pub(crate) async fn run(&mut self) { + let mut interval = tokio::time::interval(self.config.nymvisor.debug.upstream_polling_rate); + loop { + // note: first tick happens immediately + interval.tick().await; + if let Err(err) = self.check_upstream().await { + warn!("failed to check the upstream for new upgrade information: {err}. we will try to poll it again in {}", humantime::format_duration(interval.period())); + } + } + } + + pub(crate) fn start(mut self) -> JoinHandle<()> { + tokio::spawn(async move { self.run().await }) + } +} diff --git a/tools/nymvisor/src/upgrades/download.rs b/tools/nymvisor/src/upgrades/download.rs new file mode 100644 index 0000000000..1be5cf5252 --- /dev/null +++ b/tools/nymvisor/src/upgrades/download.rs @@ -0,0 +1,155 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::error::NymvisorError; +use crate::helpers::{init_path, to_hex_string}; +use crate::upgrades::types::{DownloadUrl, UpgradeInfo}; +use bytes::Buf; +use futures::stream::StreamExt; +use std::io::BufWriter; +use std::path::PathBuf; +use std::time::Duration; +use std::{env, fs, io}; +use tracing::info; + +const LOGGING_RATE: Duration = Duration::from_millis(25); + +fn log_progress_bar(downloaded: u64, length: u64) { + let percentage = downloaded as f32 * 100. / length as f32; + + let width = 40; + let filled = (percentage * width as f32 / 100.) as usize; + let empty = width - filled; + + let filled = format!("{:#^width$}", "", width = filled); + let empty = format!("{: ^width$}", "", width = empty); + + let mb_downloaded = downloaded as f64 / (1024. * 1024.); + let mb_total = length as f64 / (1024. * 1024.); + + info!("[{filled}{empty}] {mb_downloaded:.2}MB/{mb_total:.2}MB ({percentage:.2}%)"); +} + +async fn chunk_download( + download_url: &DownloadUrl, + download_target: &PathBuf, +) -> Result<(), NymvisorError> { + info!( + "attempting to download the binary from '{}'", + download_url.url + ); + let response = reqwest::get(download_url.url.clone()) + .await + .map_err(|source| NymvisorError::UpgradeDownloadFailure { + url: download_url.url.clone(), + source, + })?; + + let maybe_length = response.content_length(); + let mut source = response.bytes_stream(); + + let output_binary = fs::File::create(download_target).map_err(|source| { + NymvisorError::DaemonBinaryCreationFailure { + path: download_target.clone(), + source, + } + })?; + let mut out = BufWriter::new(output_binary); + + info!("beginning the download"); + let mut downloaded = 0; + let mut last_logged = tokio::time::Instant::now(); + while let Some(chunk) = source.next().await { + let mut bytes = chunk + .map_err(|err_source| NymvisorError::UpgradeDownloadFailure { + url: download_url.url.clone(), + source: err_source, + })? + .reader(); + + downloaded += io::copy(&mut bytes, &mut out).map_err(|err_source| { + NymvisorError::DaemonBinaryCreationFailure { + path: download_target.clone(), + source: err_source, + } + })?; + + if let Some(length) = maybe_length { + if last_logged.elapsed() > LOGGING_RATE { + log_progress_bar(downloaded, length); + last_logged = tokio::time::Instant::now(); + } + } + } + if let Some(length) = maybe_length { + log_progress_bar(length, length) + } + info!("finished the download"); + Ok(()) +} + +fn maybe_verify_checksum( + upgrade_name: String, + download_url: &DownloadUrl, + download_target: &PathBuf, +) -> Result<(), NymvisorError> { + if !download_url.checksum.is_empty() { + let checksum = download_url + .checksum_algorithm + .calculate_file_checksum(download_target)?; + if checksum != download_url.checksum { + return Err(NymvisorError::DownloadChecksumFailure { + upgrade_name, + encoded_checksum: to_hex_string(&checksum), + expected_checksum: to_hex_string(&download_url.checksum), + algorithm: download_url.checksum_algorithm, + }); + } + } + Ok(()) +} + +pub(super) async fn download_upgrade_binary( + config: &Config, + info: &UpgradeInfo, +) -> Result<(), NymvisorError> { + info!("attempting to download the upgrade binary"); + let download_url = info.get_download_url()?; + + // if the config specifies checksum MUST be verified and it's missing - return an error + if config.daemon.debug.enforce_download_checksum && download_url.checksum.is_empty() { + return Err(NymvisorError::MissingDownloadChecksum { + upgrade_name: info.name.clone(), + }); + } + + init_path(config.upgrade_binary_dir(&info.name))?; + + let temp_target = config.temp_upgrade_binary(&info.name); + let target = config.upgrade_binary(&info.name); + + // perform the download + chunk_download(download_url, &temp_target).await?; + + // if the checksum is available, do verify it + maybe_verify_checksum(info.name.clone(), download_url, &temp_target)?; + + // if the checksum exists and it matches, move the file to the correct location + fs::rename(&temp_target, &target).map_err(|source| NymvisorError::DaemonBinaryCopyFailure { + source_path: temp_target, + target_path: target, + source, + }) +} + +pub(crate) fn os_arch() -> String { + let os = env::consts::OS; + let arch = env::consts::ARCH; + // a special case for macos because of course it's its own special snowflake + if os == "macos" { + format!("darwin-{arch}") + } else { + format!("{os}-{arch}") + } +} diff --git a/tools/nymvisor/src/upgrades/mod.rs b/tools/nymvisor/src/upgrades/mod.rs new file mode 100644 index 0000000000..bb8ae36d60 --- /dev/null +++ b/tools/nymvisor/src/upgrades/mod.rs @@ -0,0 +1,141 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::config::Config; +use crate::daemon::Daemon; +use crate::error::NymvisorError; +use crate::upgrades::download::download_upgrade_binary; +use crate::upgrades::types::{CurrentVersionInfo, UpgradeHistory, UpgradePlan}; +use nix::fcntl::{flock, FlockArg}; +use std::fs; +use std::fs::File; +use std::os::fd::AsRawFd; +use std::path::PathBuf; +use time::OffsetDateTime; +use tracing::{debug, info}; + +pub(crate) mod download; +mod serde_helpers; +pub(crate) mod types; + +#[derive(Default)] +pub(crate) struct UpgradeResult { + pub(crate) binary_swapped: bool, + pub(crate) requires_manual_intervention: bool, +} + +impl UpgradeResult { + pub(crate) fn new_shortlived() -> UpgradeResult { + UpgradeResult { + binary_swapped: false, + requires_manual_intervention: false, + } + } +} + +pub(crate) async fn perform_upgrade(config: &Config) -> Result { + info!("attempting to perform binary upgrade"); + + let mut plan = UpgradePlan::try_load(config.upgrade_plan_filepath())?; + let Some(next) = plan.pop_next_upgrade() else { + return Err(NymvisorError::NoQueuedUpgrades); + }; + + let requires_manual_intervention = next.manual; + let upgrade_name = next.name.clone(); + + let history_path = config.upgrade_history_filepath(); + let mut upgrade_history = if history_path.exists() { + UpgradeHistory::try_load(history_path)? + } else { + UpgradeHistory::new(history_path) + }; + + debug!("creating the lock file"); + let lock_path = config.upgrade_lock_filepath(); + let lock_file = + File::create(&lock_path).map_err(|source| NymvisorError::LockFileCreationFailure { + path: lock_path.clone(), + source, + })?; + let lock_fd = lock_file.as_raw_fd(); + + debug!("attempting to acquire the lock"); + if let Err(err) = flock(lock_fd, FlockArg::LockExclusiveNonblock) { + return Err(NymvisorError::UnableToAcquireUpgradePlanLock { + lock_path, + libc_code: err, + }); + } + + let upgrade_binary_path = config.upgrade_binary(&upgrade_name); + + if !upgrade_binary_path.exists() { + if !config.daemon.debug.allow_binaries_download { + return Err(NymvisorError::NoUpgradeBinaryWithDisabledDownload { + path: upgrade_binary_path, + }); + } + info!( + "upgrade binary not found at '{}'. attempting to to download it", + upgrade_binary_path.display() + ); + + download_upgrade_binary(config, &next).await?; + } + + let tmp_daemon = Daemon::new(upgrade_binary_path); + tmp_daemon.verify_binary()?; + + let new_bin_info = tmp_daemon.get_build_information()?; + next.ensure_matches_bin_info(&new_bin_info)?; + + // update the 'current-version-history.json' + CurrentVersionInfo { + name: next.name.clone(), + version: next.version.clone(), + upgrade_time: OffsetDateTime::now_utc(), + binary_details: new_bin_info, + } + .save(config.current_daemon_version_filepath())?; + + // update the 'upgrade-plan.json' + plan.set_current(next.clone()); + plan.update_on_disk()?; + + // update the 'upgrade-history.json' + upgrade_history.insert_new_upgrade(next)?; + + // update the 'current' symlink + set_upgrade_link(config, config.upgrade_dir(&upgrade_name))?; + + // finally remove the lock file + fs::remove_file(&lock_path).map_err(|source| NymvisorError::LockFileRemovalFailure { + path: lock_path.clone(), + source, + })?; + + Ok(UpgradeResult { + binary_swapped: true, + requires_manual_intervention, + }) +} + +fn set_upgrade_link(config: &Config, upgrade_path: PathBuf) -> Result<(), NymvisorError> { + // remove the existing symlink if it exists + let link = config.current_daemon_dir(); + if fs::read_link(&link).is_ok() { + fs::remove_file(&link).map_err(|source| NymvisorError::SymlinkRemovalFailure { + path: link.clone(), + source, + })?; + } + + std::os::unix::fs::symlink(&upgrade_path, &link).map_err(|source| { + NymvisorError::SymlinkCreationFailure { + source_path: upgrade_path, + target_path: link, + source, + } + }) +} diff --git a/tools/nymvisor/src/upgrades/serde_helpers.rs b/tools/nymvisor/src/upgrades/serde_helpers.rs new file mode 100644 index 0000000000..574ae0e9ac --- /dev/null +++ b/tools/nymvisor/src/upgrades/serde_helpers.rs @@ -0,0 +1,41 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +pub(super) mod hex { + use serde::{Deserialize, Deserializer, Serializer}; + + pub fn serialize(bytes: &[u8], serializer: S) -> Result { + serializer.serialize_str(&hex::encode(bytes)) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let s = ::deserialize(deserializer)?; + hex::decode(s).map_err(serde::de::Error::custom) + } +} + +pub(super) mod option_offsetdatetime { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use time::OffsetDateTime; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Serialize)] + struct Helper<'a>(#[serde(with = "time::serde::rfc3339")] &'a OffsetDateTime); + + value.as_ref().map(Helper).serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + struct Helper(#[serde(with = "time::serde::rfc3339")] OffsetDateTime); + + let helper = Option::deserialize(deserializer)?; + Ok(helper.map(|Helper(external)| external)) + } +} diff --git a/tools/nymvisor/src/upgrades/types.rs b/tools/nymvisor/src/upgrades/types.rs new file mode 100644 index 0000000000..abeea0ed70 --- /dev/null +++ b/tools/nymvisor/src/upgrades/types.rs @@ -0,0 +1,493 @@ +// Copyright 2023 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use super::serde_helpers::{hex, option_offsetdatetime}; +use crate::error::NymvisorError; +use crate::helpers::{calculate_file_checksum, init_path}; +use crate::upgrades::download::os_arch; +use nym_bin_common::build_information::BinaryBuildInformationOwned; +use serde::{Deserialize, Serialize}; +use sha2::{Sha256, Sha512}; +use std::collections::HashMap; +use std::fmt::{Display, Formatter}; +use std::fs::OpenOptions; +use std::path::{Path, PathBuf}; +use std::{fs, io}; +use time::OffsetDateTime; +use tracing::error; +use url::Url; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpgradePlan { + // metadata indicating save location of the underlying file + #[serde(skip)] + _save_path: Option, + + current: UpgradeInfo, + + // TODO: or maybe BTreeMap, would be more appropriate? + queued_up: Vec, +} + +impl UpgradePlan { + pub(crate) fn new(current: UpgradeInfo) -> Self { + UpgradePlan { + _save_path: None, + current, + queued_up: Vec::new(), + } + } + + fn push_next_upgrade(&mut self, upgrade: UpgradeInfo) { + self.queued_up.push(upgrade); + + // we could be fancy and try to determine the correct index for the insertion point + // or we could just do a naive thing of sorting the elements by the upgrade time. + // is it less efficient? sure + // does it matter? no because we'll have at most few elements here + // so the overhead will be in the order of nanoseconds/microseconds + self.queued_up.sort_by_key(|u| u.upgrade_time) + } + + pub(crate) fn update_on_disk(&self) -> Result<(), NymvisorError> { + // it should be impossible to update an existing upgrade plan that wasn't loaded from disk + assert!(self._save_path.is_some()); + + // safety: the except here is fine as this failure implies failure in the underlying logic of the code + // as opposed to user error + #[allow(clippy::expect_used)] + let save_path = self + ._save_path + .as_ref() + .expect("loaded upgrade plan does not have an associate save path!"); + + let file = OpenOptions::new() + .write(true) + .truncate(true) + .open(save_path) + .map_err(|source| NymvisorError::UpgradePlanSaveFailure { + path: save_path.to_path_buf(), + source, + })?; + + // we're not using any non-standard serializer and thus the serialization should not ever fail + #[allow(clippy::expect_used)] + serde_json::to_writer_pretty(file, self) + .expect("unexpected UpgradeInfo serialization failure"); + Ok(()) + } + + pub(crate) fn insert_new_upgrade(&mut self, upgrade: UpgradeInfo) -> Result<(), NymvisorError> { + self.push_next_upgrade(upgrade); + self.update_on_disk() + } + + pub(crate) fn current(&self) -> &UpgradeInfo { + &self.current + } + + pub(crate) fn set_current(&mut self, new_current: UpgradeInfo) { + self.current = new_current + } + + pub(crate) fn next_upgrade(&self) -> Option<&UpgradeInfo> { + self.queued_up.first() + } + + pub(crate) fn pop_next_upgrade(&mut self) -> Option { + // yes, yes. VecDeque would have been perfect for this instead, + // but again, we'll hardly ever have more than 2-3 elements here so it doesn't matter + if !self.queued_up.is_empty() { + Some(self.queued_up.remove(0)) + } else { + None + } + } + + pub(crate) fn has_planned(&self, upgrade: &UpgradeInfo) -> bool { + for planned in &self.queued_up { + if planned.version == upgrade.version { + if planned.name != upgrade.name { + // TODO: should we maybe return a hard error here instead? + error!("we have already a planned upgrade for version {} under name '{}' which differs from provided '{}'", planned.version, planned.name, upgrade.name); + } + return true; + } + } + false + } + + pub(crate) fn has_planned_by_name(&self, upgrade_name: &str) -> bool { + for planned in &self.queued_up { + if planned.name == upgrade_name { + return true; + } + } + false + } + + pub(crate) fn save_new>(&self, path: P) -> Result<(), NymvisorError> { + debug_assert!(self._save_path.is_none()); + + let path = path.as_ref(); + let file = OpenOptions::new() + .create_new(true) + .write(true) + .open(path) + .map_err(|source| NymvisorError::UpgradePlanSaveFailure { + path: path.to_path_buf(), + source, + })?; + + // we're not using any non-standard serializer and thus the serialization should not ever fail + #[allow(clippy::expect_used)] + serde_json::to_writer_pretty(file, self) + .expect("unexpected UpgradeInfo serialization failure"); + Ok(()) + } + + pub(crate) fn try_load>(path: P) -> Result { + let path = path.as_ref(); + let mut upgrade_plan: UpgradePlan = fs::File::open(path) + .and_then(|file| { + serde_json::from_reader(file) + .map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err)) + }) + .map_err(|source| NymvisorError::UpgradePlanLoadFailure { + path: path.to_path_buf(), + source, + })?; + + upgrade_plan._save_path = Some(path.to_path_buf()); + Ok(upgrade_plan) + } +} + +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "camelCase")] +pub enum DigestAlgorithm { + Sha256, + Sha512, +} + +impl Display for DigestAlgorithm { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + DigestAlgorithm::Sha256 => write!(f, "sha256"), + DigestAlgorithm::Sha512 => write!(f, "sha512"), + } + } +} + +impl DigestAlgorithm { + pub fn calculate_file_checksum>( + &self, + filepath: P, + ) -> Result, NymvisorError> { + let path = filepath.as_ref(); + match self { + DigestAlgorithm::Sha256 => calculate_file_checksum::(path), + DigestAlgorithm::Sha512 => calculate_file_checksum::(path), + } + .map_err(|source| NymvisorError::ChecksumCalculationFailure { + path: path.to_path_buf(), + source, + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DownloadUrl { + /// The hex-encoded checksum of the file behind the download url. + #[serde(with = "hex")] + pub checksum: Vec, + + /// The algorithm used for computing the checksum + pub checksum_algorithm: DigestAlgorithm, + + /// Download url for this particular platform + pub url: Url, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct UpgradeInfo { + /// Specifies whether this upgrade requires manual intervention and cannot be done automatically by the nymvisor. + pub manual: bool, + + /// Name of this upgrade, for example `2023.4-galaxy` + pub name: String, + + /// Additional information about this release + pub notes: String, + + /// Optional rfc3339 datetime of the publishing date of the release, + #[serde(with = "option_offsetdatetime")] + pub publish_date: Option, + + /// Version of this upgrade, for example `1.1.69` + pub version: String, + + /// Platform specific download urls, for example `linux-x86_64` + pub platforms: HashMap, + + /// Time when the upgrade should happen. + #[serde(with = "time::serde::rfc3339")] + pub upgrade_time: OffsetDateTime, + + /// Optional build information of the upgraded binary for additional verification + pub binary_details: Option, +} + +impl UpgradeInfo { + pub(crate) fn save>(&self, path: P) -> Result<(), NymvisorError> { + let path = path.as_ref(); + + // in case we're saving brand new upgrade info, make sure the parent directory exists + #[allow(clippy::expect_used)] + let parent = path + .parent() + .expect("attempted to save the upgrade info as the root of the fs"); + + init_path(parent)?; + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) + .map_err(|source| NymvisorError::UpgradeInfoSaveFailure { + name: self.name.clone(), + path: path.to_path_buf(), + source, + })?; + + // we're not using any non-standard serializer and thus the serialization should not ever fail + #[allow(clippy::expect_used)] + serde_json::to_writer_pretty(file, self) + .expect("unexpected UpgradeInfo serialization failure"); + Ok(()) + } + + pub(crate) fn try_load>(path: P) -> Result { + let path = path.as_ref(); + fs::File::open(path) + .and_then(|file| { + serde_json::from_reader(file) + .map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err)) + }) + .map_err(|source| NymvisorError::UpgradeInfoLoadFailure { + path: path.to_path_buf(), + source, + }) + } + + // pub(crate) fn is_genesis(&self) -> bool { + // self.name == GENESIS_DIR + // } + + pub(crate) fn get_download_url(&self) -> Result<&DownloadUrl, NymvisorError> { + if let Some(download_url) = self.platforms.get(&os_arch()) { + return Ok(download_url); + } + self.platforms + .get("any") + .ok_or(NymvisorError::NoDownloadUrls { + upgrade_name: self.name.clone(), + arch: os_arch(), + available: self.platforms.keys().cloned().collect(), + }) + } + + /// Check whether the loaded (presumably `current`) upgrade-info matches the provided current version information. + pub(crate) fn ensure_matches( + &self, + current_info: &CurrentVersionInfo, + ) -> Result<(), NymvisorError> { + if self.name != current_info.name || self.version != current_info.version { + return Err(NymvisorError::UnexpectedCurrentVersionInfo { + current_info: Box::new(self.clone()), + current_version_info: Box::new(current_info.clone()), + }); + } + + if let Some(bin_info) = &self.binary_details { + if bin_info != ¤t_info.binary_details { + return Err(NymvisorError::UnexpectedCurrentVersionInfo { + current_info: Box::new(self.clone()), + current_version_info: Box::new(current_info.clone()), + }); + } + } + + Ok(()) + } + + pub(crate) fn ensure_matches_bin_info( + &self, + info: &BinaryBuildInformationOwned, + ) -> Result<(), NymvisorError> { + if let Some(self_info) = &self.binary_details { + if self_info != info { + return Err(NymvisorError::UnexpectedDaemonBuild { + daemon_info: Box::new(info.clone()), + stored_info: Box::new(self_info.clone()), + }); + } + } + if self.version != info.build_version { + return Err(NymvisorError::UnexpectedUpgradeDaemonVersion { + upgrade_name: self.name.clone(), + daemon_version: info.build_version.clone(), + expected: self.version.clone(), + }); + } + + Ok(()) + } +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct UpgradeHistory { + // metadata indicating save location of the underlying file + #[serde(skip)] + _save_path: Option, + + history: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct UpgradeHistoryEntry { + #[serde(with = "time::serde::rfc3339")] + performed_at: OffsetDateTime, + + info: UpgradeInfo, +} + +impl UpgradeHistoryEntry { + fn new(info: UpgradeInfo) -> Self { + UpgradeHistoryEntry { + performed_at: OffsetDateTime::now_utc(), + info, + } + } +} + +impl UpgradeHistory { + pub(crate) fn new>(save_path: P) -> Self { + UpgradeHistory { + _save_path: Some(save_path.as_ref().to_path_buf()), + history: vec![], + } + } + + pub(crate) fn update_on_disk(&self) -> Result<(), NymvisorError> { + // it should be impossible to update an existing upgrade history that wasn't loaded from disk + assert!(self._save_path.is_some()); + + // safety: the except here is fine as this failure implies failure in the underlying logic of the code + // as opposed to user error + #[allow(clippy::expect_used)] + let save_path = self + ._save_path + .as_ref() + .expect("loaded upgrade history does not have an associate save path!"); + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(save_path) + .map_err(|source| NymvisorError::UpgradeHistorySaveFailure { + path: save_path.to_path_buf(), + source, + })?; + + // we're not using any non-standard serializer and thus the serialization should not ever fail + #[allow(clippy::expect_used)] + serde_json::to_writer_pretty(file, self) + .expect("unexpected UpgradeHistory serialization failure"); + Ok(()) + } + + fn push_upgrade(&mut self, upgrade: UpgradeInfo) { + self.history.push(UpgradeHistoryEntry::new(upgrade)); + } + + pub(crate) fn insert_new_upgrade(&mut self, upgrade: UpgradeInfo) -> Result<(), NymvisorError> { + self.push_upgrade(upgrade); + self.update_on_disk() + } + + pub(crate) fn try_load>(path: P) -> Result { + let path = path.as_ref(); + let mut history: UpgradeHistory = fs::File::open(path) + .and_then(|file| { + serde_json::from_reader(file) + .map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err)) + }) + .map_err(|source| NymvisorError::UpgradeHistoryLoadFailure { + path: path.to_path_buf(), + source, + })?; + + history._save_path = Some(path.to_path_buf()); + Ok(history) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CurrentVersionInfo { + /// Name of the current version, for example `2023.4-galaxy` + pub name: String, + + /// Version of this upgrade, for example `1.1.69` + pub version: String, + + /// Time when the upgrade has happened. + #[serde(with = "time::serde::rfc3339")] + pub upgrade_time: OffsetDateTime, + + /// Build information of the expected current binary for additional verification + pub binary_details: BinaryBuildInformationOwned, +} + +impl CurrentVersionInfo { + pub(crate) fn save>(&self, path: P) -> Result<(), NymvisorError> { + let path = path.as_ref(); + + let file = OpenOptions::new() + .create(true) + .write(true) + .truncate(true) + .open(path) + .map_err(|source| NymvisorError::CurrentVersionInfoSaveFailure { + path: path.to_path_buf(), + source, + })?; + + // we're not using any non-standard serializer and thus the serialization should not ever fail + #[allow(clippy::expect_used)] + serde_json::to_writer_pretty(file, self) + .expect("unexpected CurrentVersionInfo serialization failure"); + Ok(()) + } + + pub(crate) fn try_load>(path: P) -> Result { + let path = path.as_ref(); + fs::File::open(path) + .and_then(|file| { + serde_json::from_reader(file) + .map_err(|serde_json_err| io::Error::new(io::ErrorKind::Other, serde_json_err)) + }) + .map_err(|source| NymvisorError::CurrentVersionInfoLoadFailure { + path: path.to_path_buf(), + source, + }) + } +} diff --git a/tools/ts-rs-cli/Cargo.toml b/tools/ts-rs-cli/Cargo.toml index 5e315a761c..e8cd5402e3 100644 --- a/tools/ts-rs-cli/Cargo.toml +++ b/tools/ts-rs-cli/Cargo.toml @@ -2,6 +2,7 @@ name = "ts-rs-cli" version = "0.1.0" edition = "2021" +license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 1dcaa740b1..6961325d61 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-client-wasm" authors = ["Dave Hrycyszyn ", "Jedrzej Stuczynski "] -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" edition = "2021" keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"] license = "Apache-2.0" diff --git a/wasm/mix-fetch/Cargo.toml b/wasm/mix-fetch/Cargo.toml index 641650a958..298335d6b5 100644 --- a/wasm/mix-fetch/Cargo.toml +++ b/wasm/mix-fetch/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "mix-fetch-wasm" authors = ["Jedrzej Stuczynski "] -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" edition = "2021" keywords = ["nym", "fetch", "wasm", "webassembly", "privacy"] license = "Apache-2.0" diff --git a/wasm/mix-fetch/go-mix-conn/internal/jstypes/conv/request.go b/wasm/mix-fetch/go-mix-conn/internal/jstypes/conv/request.go index 883ce82724..19792dbc06 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/jstypes/conv/request.go +++ b/wasm/mix-fetch/go-mix-conn/internal/jstypes/conv/request.go @@ -195,7 +195,7 @@ func parseHeaders(headers js.Value, reqOpts types.RequestOptions, method string) // 3.1.1 origin := jstypes.Origin() - serializedOrigin := &origin + serializedOrigin := origin // Reference: https://fetch.spec.whatwg.org/#origin-header // TODO: 3.1.2: check response tainting // 3.1.3 @@ -224,7 +224,7 @@ func parseBody(request *js.Value) (io.Reader, error) { jsBody := request.Get(fieldRequestBody) var bodyReader io.Reader - if jsBody.InstanceOf(js.Global().Get("ReadableStream")) && jsBody.Get("getReader").Type() == js.TypeFunction { + if jsBody.InstanceOf(js.Global().Get("ReadableStream")) && jsBody.Get("getReader").Type() == js.TypeFunction { // Check to see if getReader is a function log.Debug("stream body - getReader") bodyReader = external.NewStreamReader(jsBody.Call("getReader")) diff --git a/wasm/mix-fetch/go-mix-conn/internal/jstypes/globals.go b/wasm/mix-fetch/go-mix-conn/internal/jstypes/globals.go index 51dda48c8e..86e7ee3e0e 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/jstypes/globals.go +++ b/wasm/mix-fetch/go-mix-conn/internal/jstypes/globals.go @@ -20,20 +20,26 @@ var ( Headers = js.Global().Get("Headers") ) -func Origin() string { +func Origin() *string { // nodejs doesn't have origin location := js.Global().Get("location") if !location.IsUndefined() && !location.IsNull() { - return location.Get("origin").String() + origin := location.Get("origin").String() + return &origin } else { - return "" + return nil } } func OriginUrl() *url.URL { - originUrl, originErr := url.Parse(Origin()) - if originErr != nil { - panic(fmt.Sprintf("could not obtain origin: %s", originErr)) + origin := Origin() + if origin == nil { + return nil + } else { + originUrl, originErr := url.Parse(*origin) + if originErr != nil { + panic(fmt.Sprintf("could not obtain origin: %s", originErr)) + } + return originUrl } - return originUrl } diff --git a/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go b/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go index 3dbc4c077f..7725be27b6 100644 --- a/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go +++ b/wasm/mix-fetch/go-mix-conn/internal/mixfetch/mixfetch.go @@ -92,7 +92,7 @@ func mainFetchChecks(req *conv.ParsedRequest) error { return nil } if req.Options.Mode == jstypes.ModeSameOrigin { - return errors.New(fmt.Sprintf("MixFetch API cannot load %s. Request mode is \"%s\" but the URL's origin is not same as the request origin %s.", req.Request.URL.String(), jstypes.ModeSameOrigin, jstypes.Origin)) + return errors.New(fmt.Sprintf("MixFetch API cannot load %s. Request mode is \"%s\" but the URL's origin is not same as the request origin %v.", req.Request.URL.String(), jstypes.ModeSameOrigin, jstypes.Origin())) } if req.Options.Mode == jstypes.ModeNoCors { if req.Options.Redirect != jstypes.RequestRedirectFollow { @@ -241,8 +241,17 @@ func doCorsCheck(reqOpts *types.RequestOptions, resp *http.Response) error { // 4.9.4 // TODO: presumably this needs to better account for the wildcard? - if jstypes.Origin() != originHeader { - return errors.New(fmt.Sprintf("\"%s\" does not match the origin \"%s\" on \"%s\" remote header", jstypes.Origin, originHeader, jstypes.HeaderAllowOrigin)) + + // if origin is null it means 4.9.2 would have failed anyway + origin := jstypes.Origin() + if origin == nil { + // TODO: won't this essentially fail all node requests? + return errors.New("the local origin is null") + } + + // safety: it's fine to dereference the pointer here as we've just checked if it's null + if *origin != originHeader { + return errors.New(fmt.Sprintf("\"%v\" does not match the origin \"%s\" on \"%s\" remote header", jstypes.Origin(), originHeader, jstypes.HeaderAllowOrigin)) } // 4.9.5 diff --git a/wasm/node-tester/Cargo.toml b/wasm/node-tester/Cargo.toml index d435a0e728..6a3d8126e7 100644 --- a/wasm/node-tester/Cargo.toml +++ b/wasm/node-tester/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nym-node-tester-wasm" authors = ["Jedrzej Stuczynski "] -version = "1.2.4-rc.1" +version = "1.2.4-rc.2" edition = "2021" keywords = ["nym", "sphinx", "webassembly", "privacy", "tester"] license = "Apache-2.0" diff --git a/yarn.lock b/yarn.lock index 4651229bd9..56876f3eb2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8,9 +8,9 @@ integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== "@adobe/css-tools@^4.0.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28" - integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg== + version "4.3.2" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.2.tgz#a6abc715fb6884851fca9dad37fc34739a04fd11" + integrity sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw== "@ampproject/remapping@^2.2.0": version "2.2.1" @@ -4252,71 +4252,71 @@ resolved "https://registry.yarnpkg.com/@tauri-apps/api/-/api-1.5.0.tgz#46a0f9b6edd4bfc39be32afc4ad242e0b8cca3ea" integrity sha512-yQY9wpVNuiYhLLuyDlu1nBpqJELT1fGp7OctN4rW9I2W1T2p7A3tqPxsEzQprEwneQRBAlPM9vC8NsnMbct+pg== -"@tauri-apps/cli-darwin-arm64@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.2.tgz#a579fadfc20be7119151d24f99d0416ac95a0afb" - integrity sha512-3mYN15jpYjuuHRWii5Xk6JvB3ZLEgAZkeVgc5Hg7k3Yw1ZBrEGiUNe3Ku4DjbkYWL/QDd7oteIJnQk3j0mXwEg== +"@tauri-apps/cli-darwin-arm64@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.5.6.tgz#0d68eaf6fa1b35197a9d4991ac5377e6085a569a" + integrity sha512-NNvG3XLtciCMsBahbDNUEvq184VZmOveTGOuy0So2R33b/6FDkuWaSgWZsR1mISpOuP034htQYW0VITCLelfqg== -"@tauri-apps/cli-darwin-x64@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.2.tgz#d2251c0a1c3782645b90dd0c753b30a44d98ef6f" - integrity sha512-8Jsl+EKBGdBoRUIR5ohjiTV6uG+pFpkNwHB4WaOzzR//v6p0lGULk2ohPIjJxhoQIFIN9oBd8bsSSNbZO/57/g== +"@tauri-apps/cli-darwin-x64@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.5.6.tgz#a48e1f66b12c33cf6c9c216865f2e9a3ff18a923" + integrity sha512-nkiqmtUQw3N1j4WoVjv81q6zWuZFhBLya/RNGUL94oafORloOZoSY0uTZJAoeieb3Y1YK0rCHSDl02MyV2Fi4A== -"@tauri-apps/cli-linux-arm-gnueabihf@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.2.tgz#0fb25b4842195f425084be93524b2e7d11459b58" - integrity sha512-oWzcQtZogchdCSdQ+eEbDJHjJO9BGPO9EZNfvI2u3JzS/hJj4wCwnGHvZGI4jJ/6cnmcNvtt9ZftYYN4rpQW7A== +"@tauri-apps/cli-linux-arm-gnueabihf@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.5.6.tgz#385cd8054be7722cc07acc2d6d86f8e20c6265ef" + integrity sha512-z6SPx+axZexmWXTIVPNs4Tg7FtvdJl9EKxYN6JPjOmDZcqA13iyqWBQal2DA/GMZ1Xqo3vyJf6EoEaKaliymPQ== -"@tauri-apps/cli-linux-arm64-gnu@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.2.tgz#4d6d86584861b8221e9ad679989b74be495766c7" - integrity sha512-hfIUtys+SrrGEpLhQJwmL16g1FDhfObR4AGahCMqvMwA25uQKhtzRhxieO4X3k03vBeboTyJla5a2rX4TQ4lXA== +"@tauri-apps/cli-linux-arm64-gnu@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.5.6.tgz#bd3f8f61637a1073909cb2d663aa0c6e8e036337" + integrity sha512-QuQjMQmpsCbzBrmtQiG4uhnfAbdFx3nzm+9LtqjuZlurc12+Mj5MTgqQ3AOwQedH3f7C+KlvbqD2AdXpwTg7VA== -"@tauri-apps/cli-linux-arm64-musl@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.2.tgz#caa1da5f21ce231dd1916f4525444a368cc4aec5" - integrity sha512-ZpFX4HyjkJHfF0geVVpq5fNjEXG8766LRg/BL4/wKy8MNbqcc/aW3sLEpVj0GvdulxOnd2trDb1FJdZ206Kp8g== +"@tauri-apps/cli-linux-arm64-musl@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.6.tgz#77fb4a804b77447bffba725a4b8b62df45f9ff97" + integrity sha512-8j5dH3odweFeom7bRGlfzDApWVOT4jIq8/214Wl+JeiNVehouIBo9lZGeghZBH3XKFRwEvU23i7sRVjuh2s8mg== -"@tauri-apps/cli-linux-x64-gnu@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.2.tgz#b8a7d75c8562fdf134abd037a4fc45c28671bd89" - integrity sha512-/kEZrDVZv1+qYpZigtLntX4s+gx7WQqp8cmxKZL5GrzAjUNSTnfWK5VL7o0lYm09QBJAdTvjcJu2aPzNHLDS+Q== +"@tauri-apps/cli-linux-x64-gnu@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.5.6.tgz#e70a6c9dd243c2a3fa1fec69de9ebc9abf4dc795" + integrity sha512-gbFHYHfdEGW0ffk8SigDsoXks6USpilF6wR0nqB/JbWzbzFR/sBuLVNQlJl1RKNakyJHu+lsFxGy0fcTdoX8xA== -"@tauri-apps/cli-linux-x64-musl@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.2.tgz#6309639fd01a3771637aa3e6800fc06b5458d017" - integrity sha512-vOrTqfW7NZLYmB1ZXjEq9JC8Z5knIHVGltN3jfyq9UY+tu7d6jNaSHuDdsVHLaKIRQajTHz+5cphDMoL+6/MUQ== +"@tauri-apps/cli-linux-x64-musl@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.6.tgz#f25dad778b2da6ab7b2b81f81c0805026d742c33" + integrity sha512-9v688ogoLkeFYQNgqiSErfhTreLUd8B3prIBSYUt+x4+5Kcw91zWvIh+VSxL1n3KCGGsM7cuXhkGPaxwlEh1ug== -"@tauri-apps/cli-win32-arm64-msvc@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.2.tgz#7a6b89ee7d992e51084f1427e3885dfe5bcf5bae" - integrity sha512-PCAIxH8TvIy1PlJK2qrdwpRCa7pZV648whRhmoLwj9lvn9PR4+vwuUWhSTXmp1ISHWZm/mEJpYuVq0ZxJDQKZQ== +"@tauri-apps/cli-win32-arm64-msvc@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-1.5.6.tgz#47d0f359e04d93c1fd0b527e18a56606f3df9bed" + integrity sha512-DRNDXFNZb6y5IZrw+lhTTA9l4wbzO4TNRBAlHAiXUrH+pRFZ/ZJtv5WEuAj9ocVSahVw2NaK5Yaold4NPAxHog== -"@tauri-apps/cli-win32-ia32-msvc@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.2.tgz#17e02c30e29596d815f0ad0458a11bc752502921" - integrity sha512-0rCXZC/9qyWZI2cgVnsT4JEjcxjQcZlrZjaSwhGynPUMkIUUBsWDimzkQeOP8abw5j/kLS2QPQRHAHqd0IF6Rg== +"@tauri-apps/cli-win32-ia32-msvc@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.5.6.tgz#54c7ec152065e8167068411b82748a25a483d948" + integrity sha512-oUYKNR/IZjF4fsOzRpw0xesl2lOjhsQEyWlgbpT25T83EU113Xgck9UjtI7xemNI/OPCv1tPiaM1e7/ABdg5iA== -"@tauri-apps/cli-win32-x64-msvc@1.5.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.2.tgz#bec591ca7c8a6ed77f705da73c0213c454582e5a" - integrity sha512-HrOwIujKTql8yUOvR+IEGW/SXwMwPrGWrcM35cDfCxpkLH6h3F5i4JtRqYTAiFdSfTk1J2rcFVQXfFJYIDntnA== +"@tauri-apps/cli-win32-x64-msvc@1.5.6": + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.5.6.tgz#963280a4f7001c27d4e4745f302728297c007deb" + integrity sha512-RmEf1os9C8//uq2hbjXi7Vgz9ne7798ZxqemAZdUwo1pv3oLVZSz1/IvZmUHPdy2e6zSeySqWu1D0Y3QRNN+dg== "@tauri-apps/cli@^1.0.5", "@tauri-apps/cli@^1.2.2": - version "1.5.2" - resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.2.tgz#c945eed048774bf5a7418cabed5b63e7048b4eb4" - integrity sha512-x4rPinrdyWLdTU5TbV7wrXudOyMilv0nmi1rngm/jwN3MmZdAj5J2xaS2rYtA8BU3oM/SE34qQwYxv1a2sM9ug== + version "1.5.6" + resolved "https://registry.yarnpkg.com/@tauri-apps/cli/-/cli-1.5.6.tgz#5011c9dd3a36dc89073fed7d4cb429d38b37862b" + integrity sha512-k4Y19oVCnt7WZb2TnDzLqfs7o98Jq0tUoVMv+JQSzuRDJqaVu2xMBZ8dYplEn+EccdR5SOMyzaLBJWu38TVK1A== optionalDependencies: - "@tauri-apps/cli-darwin-arm64" "1.5.2" - "@tauri-apps/cli-darwin-x64" "1.5.2" - "@tauri-apps/cli-linux-arm-gnueabihf" "1.5.2" - "@tauri-apps/cli-linux-arm64-gnu" "1.5.2" - "@tauri-apps/cli-linux-arm64-musl" "1.5.2" - "@tauri-apps/cli-linux-x64-gnu" "1.5.2" - "@tauri-apps/cli-linux-x64-musl" "1.5.2" - "@tauri-apps/cli-win32-arm64-msvc" "1.5.2" - "@tauri-apps/cli-win32-ia32-msvc" "1.5.2" - "@tauri-apps/cli-win32-x64-msvc" "1.5.2" + "@tauri-apps/cli-darwin-arm64" "1.5.6" + "@tauri-apps/cli-darwin-x64" "1.5.6" + "@tauri-apps/cli-linux-arm-gnueabihf" "1.5.6" + "@tauri-apps/cli-linux-arm64-gnu" "1.5.6" + "@tauri-apps/cli-linux-arm64-musl" "1.5.6" + "@tauri-apps/cli-linux-x64-gnu" "1.5.6" + "@tauri-apps/cli-linux-x64-musl" "1.5.6" + "@tauri-apps/cli-win32-arm64-msvc" "1.5.6" + "@tauri-apps/cli-win32-ia32-msvc" "1.5.6" + "@tauri-apps/cli-win32-x64-msvc" "1.5.6" "@tauri-apps/tauri-forage@^1.0.0-beta.2": version "1.0.0-beta.2" @@ -6159,9 +6159,9 @@ axios@^0.21.1, axios@^0.21.2: follow-redirects "^1.14.0" axios@^1.0.0, axios@^1.3.3: - version "1.5.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.5.1.tgz#11fbaa11fc35f431193a9564109c88c1f27b585f" - integrity sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A== + version "1.6.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" + integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0"